1#[cfg(not(any(target_os = "android", target_os = "ios")))]
8use crate::window::WindowId;
9use crate::{window::is_label_valid, Rect, Runtime, UserEvent};
10
11use http::Request;
12use tauri_utils::config::{
13 BackgroundThrottlingPolicy, Color, ScrollBarStyle as ConfigScrollBarStyle, WebviewUrl,
14 WindowConfig, WindowEffectsConfig,
15};
16use url::Url;
17
18use std::{
19 borrow::Cow,
20 collections::HashMap,
21 hash::{Hash, Hasher},
22 path::PathBuf,
23 sync::Arc,
24};
25
26type UriSchemeProtocolHandler = dyn Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
27 + Send
28 + Sync
29 + 'static;
30
31type WebResourceRequestHandler =
32 dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
33
34type NavigationHandler = dyn Fn(&Url) -> bool + Send;
35
36type NewWindowHandler = dyn Fn(Url, NewWindowFeatures) -> NewWindowResponse + Send + Sync;
37
38type OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send;
39
40type DocumentTitleChangedHandler = dyn Fn(String) + Send + 'static;
41
42type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync;
43
44#[cfg(target_os = "ios")]
45type InputAccessoryViewBuilderFn = dyn Fn(&objc2_ui_kit::UIView) -> Option<objc2::rc::Retained<objc2_ui_kit::UIView>>
46 + Send
47 + Sync
48 + 'static;
49
50pub enum DownloadEvent<'a> {
52 Requested {
54 url: Url,
56 destination: &'a mut PathBuf,
60 },
61 Finished {
63 url: Url,
65 path: Option<PathBuf>,
67 success: bool,
69 },
70}
71
72#[cfg(target_os = "android")]
73pub struct CreationContext<'a, 'b> {
74 pub env: &'a mut jni::JNIEnv<'b>,
75 pub activity: &'a jni::objects::JObject<'b>,
76 pub webview: &'a jni::objects::JObject<'b>,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub enum PageLoadEvent {
82 Started,
84 Finished,
86}
87
88#[derive(Debug)]
90pub struct NewWindowOpener {
91 #[cfg(any(
95 target_os = "linux",
96 target_os = "dragonfly",
97 target_os = "freebsd",
98 target_os = "netbsd",
99 target_os = "openbsd",
100 ))]
101 pub webview: webkit2gtk::WebView,
102 #[cfg(windows)]
106 pub webview: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2,
107 #[cfg(windows)]
108 pub environment: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment,
109 #[cfg(target_os = "macos")]
111 pub webview: objc2::rc::Retained<objc2_web_kit::WKWebView>,
112 #[cfg(target_os = "macos")]
116 pub target_configuration: objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>,
117}
118
119#[derive(Debug)]
121pub struct NewWindowFeatures {
122 pub(crate) size: Option<crate::dpi::LogicalSize<f64>>,
123 pub(crate) position: Option<crate::dpi::LogicalPosition<f64>>,
124 pub(crate) opener: NewWindowOpener,
125}
126
127impl NewWindowFeatures {
128 pub fn new(
129 size: Option<crate::dpi::LogicalSize<f64>>,
130 position: Option<crate::dpi::LogicalPosition<f64>>,
131 opener: NewWindowOpener,
132 ) -> Self {
133 Self {
134 size,
135 position,
136 opener,
137 }
138 }
139
140 pub fn size(&self) -> Option<crate::dpi::LogicalSize<f64>> {
143 self.size
144 }
145
146 pub fn position(&self) -> Option<crate::dpi::LogicalPosition<f64>> {
149 self.position
150 }
151
152 pub fn opener(&self) -> &NewWindowOpener {
154 &self.opener
155 }
156}
157
158pub enum NewWindowResponse {
160 Allow,
162 #[cfg(not(any(target_os = "android", target_os = "ios")))]
169 Create { window_id: WindowId },
170 Deny,
172}
173
174#[non_exhaustive]
180#[derive(Debug, Clone, Copy, Default)]
181pub enum ScrollBarStyle {
182 #[default]
183 Default,
185
186 #[cfg(windows)]
187 FluentOverlay,
192}
193
194pub struct PendingWebview<T: UserEvent, R: Runtime<T>> {
196 pub label: String,
198
199 pub webview_attributes: WebviewAttributes,
201
202 pub uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocolHandler>>,
204
205 pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
207
208 pub navigation_handler: Option<Box<NavigationHandler>>,
210
211 pub new_window_handler: Option<Box<NewWindowHandler>>,
212
213 pub document_title_changed_handler: Option<Box<DocumentTitleChangedHandler>>,
214
215 pub url: String,
217
218 #[cfg(target_os = "android")]
219 #[allow(clippy::type_complexity)]
220 pub on_webview_created:
221 Option<Box<dyn Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send>>,
222
223 pub web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
224
225 pub on_page_load_handler: Option<Box<OnPageLoadHandler>>,
226
227 pub download_handler: Option<Arc<DownloadHandler>>,
228}
229
230impl<T: UserEvent, R: Runtime<T>> PendingWebview<T, R> {
231 pub fn new(
233 webview_attributes: WebviewAttributes,
234 label: impl Into<String>,
235 ) -> crate::Result<Self> {
236 let label = label.into();
237 if !is_label_valid(&label) {
238 Err(crate::Error::InvalidWindowLabel)
239 } else {
240 Ok(Self {
241 webview_attributes,
242 uri_scheme_protocols: Default::default(),
243 label,
244 ipc_handler: None,
245 navigation_handler: None,
246 new_window_handler: None,
247 document_title_changed_handler: None,
248 url: "tauri://localhost".to_string(),
249 #[cfg(target_os = "android")]
250 on_webview_created: None,
251 web_resource_request_handler: None,
252 on_page_load_handler: None,
253 download_handler: None,
254 })
255 }
256 }
257
258 pub fn register_uri_scheme_protocol<
259 N: Into<String>,
260 H: Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
261 + Send
262 + Sync
263 + 'static,
264 >(
265 &mut self,
266 uri_scheme: N,
267 protocol_handler: H,
268 ) {
269 let uri_scheme = uri_scheme.into();
270 self
271 .uri_scheme_protocols
272 .insert(uri_scheme, Box::new(protocol_handler));
273 }
274
275 #[cfg(target_os = "android")]
276 pub fn on_webview_created<
277 F: Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + 'static,
278 >(
279 mut self,
280 f: F,
281 ) -> Self {
282 self.on_webview_created.replace(Box::new(f));
283 self
284 }
285}
286
287#[derive(Debug)]
289pub struct DetachedWebview<T: UserEvent, R: Runtime<T>> {
290 pub label: String,
292
293 pub dispatcher: R::WebviewDispatcher,
295}
296
297impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWebview<T, R> {
298 fn clone(&self) -> Self {
299 Self {
300 label: self.label.clone(),
301 dispatcher: self.dispatcher.clone(),
302 }
303 }
304}
305
306impl<T: UserEvent, R: Runtime<T>> Hash for DetachedWebview<T, R> {
307 fn hash<H: Hasher>(&self, state: &mut H) {
309 self.label.hash(state)
310 }
311}
312
313impl<T: UserEvent, R: Runtime<T>> Eq for DetachedWebview<T, R> {}
314impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWebview<T, R> {
315 fn eq(&self, other: &Self) -> bool {
317 self.label.eq(&other.label)
318 }
319}
320
321#[derive(Debug)]
323pub struct WebviewAttributes {
324 pub url: WebviewUrl,
325 pub user_agent: Option<String>,
326 pub initialization_scripts: Vec<InitializationScript>,
340 pub data_directory: Option<PathBuf>,
341 pub drag_drop_handler_enabled: bool,
342 pub clipboard: bool,
343 pub accept_first_mouse: bool,
344 pub additional_browser_args: Option<String>,
345 pub window_effects: Option<WindowEffectsConfig>,
346 pub incognito: bool,
347 pub transparent: bool,
348 pub focus: bool,
349 pub bounds: Option<Rect>,
350 pub auto_resize: bool,
351 pub proxy_url: Option<Url>,
352 pub zoom_hotkeys_enabled: bool,
353 pub browser_extensions_enabled: bool,
354 pub extensions_path: Option<PathBuf>,
355 pub data_store_identifier: Option<[u8; 16]>,
356 pub use_https_scheme: bool,
357 pub devtools: Option<bool>,
358 pub background_color: Option<Color>,
359 pub traffic_light_position: Option<dpi::Position>,
360 pub background_throttling: Option<BackgroundThrottlingPolicy>,
361 pub javascript_disabled: bool,
362 pub allow_link_preview: bool,
365 pub scroll_bar_style: ScrollBarStyle,
366 #[cfg(target_os = "ios")]
378 pub input_accessory_view_builder: Option<InputAccessoryViewBuilder>,
379
380 #[cfg(windows)]
383 pub environment: Option<webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment>,
384
385 #[cfg(any(
388 target_os = "linux",
389 target_os = "dragonfly",
390 target_os = "freebsd",
391 target_os = "netbsd",
392 target_os = "openbsd",
393 ))]
394 pub related_view: Option<webkit2gtk::WebView>,
395
396 #[cfg(target_os = "macos")]
397 pub webview_configuration: Option<objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>>,
398}
399
400unsafe impl Send for WebviewAttributes {}
401unsafe impl Sync for WebviewAttributes {}
402
403#[cfg(target_os = "ios")]
404#[non_exhaustive]
405pub struct InputAccessoryViewBuilder(pub Box<InputAccessoryViewBuilderFn>);
406
407#[cfg(target_os = "ios")]
408impl std::fmt::Debug for InputAccessoryViewBuilder {
409 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
410 f.debug_struct("InputAccessoryViewBuilder").finish()
411 }
412}
413
414#[cfg(target_os = "ios")]
415impl InputAccessoryViewBuilder {
416 pub fn new(builder: Box<InputAccessoryViewBuilderFn>) -> Self {
417 Self(builder)
418 }
419}
420
421impl From<&WindowConfig> for WebviewAttributes {
422 fn from(config: &WindowConfig) -> Self {
423 let mut builder = Self::new(config.url.clone())
424 .incognito(config.incognito)
425 .focused(config.focus)
426 .zoom_hotkeys_enabled(config.zoom_hotkeys_enabled)
427 .use_https_scheme(config.use_https_scheme)
428 .browser_extensions_enabled(config.browser_extensions_enabled)
429 .background_throttling(config.background_throttling.clone())
430 .devtools(config.devtools)
431 .scroll_bar_style(match config.scroll_bar_style {
432 ConfigScrollBarStyle::Default => ScrollBarStyle::Default,
433 #[cfg(windows)]
434 ConfigScrollBarStyle::FluentOverlay => ScrollBarStyle::FluentOverlay,
435 _ => ScrollBarStyle::Default,
436 });
437
438 #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
439 {
440 builder = builder.transparent(config.transparent);
441 }
442 #[cfg(target_os = "macos")]
443 {
444 if let Some(position) = &config.traffic_light_position {
445 builder =
446 builder.traffic_light_position(dpi::LogicalPosition::new(position.x, position.y).into());
447 }
448 }
449 builder = builder.accept_first_mouse(config.accept_first_mouse);
450 if !config.drag_drop_enabled {
451 builder = builder.disable_drag_drop_handler();
452 }
453 if let Some(user_agent) = &config.user_agent {
454 builder = builder.user_agent(user_agent);
455 }
456 if let Some(additional_browser_args) = &config.additional_browser_args {
457 builder = builder.additional_browser_args(additional_browser_args);
458 }
459 if let Some(effects) = &config.window_effects {
460 builder = builder.window_effects(effects.clone());
461 }
462 if let Some(url) = &config.proxy_url {
463 builder = builder.proxy_url(url.to_owned());
464 }
465 if let Some(color) = config.background_color {
466 builder = builder.background_color(color);
467 }
468 builder.javascript_disabled = config.javascript_disabled;
469 builder.allow_link_preview = config.allow_link_preview;
470 #[cfg(target_os = "ios")]
471 if config.disable_input_accessory_view {
472 builder
473 .input_accessory_view_builder
474 .replace(InputAccessoryViewBuilder::new(Box::new(|_webview| None)));
475 }
476 builder
477 }
478}
479
480impl WebviewAttributes {
481 pub fn new(url: WebviewUrl) -> Self {
483 Self {
484 url,
485 user_agent: None,
486 initialization_scripts: Vec::new(),
487 data_directory: None,
488 drag_drop_handler_enabled: true,
489 clipboard: false,
490 accept_first_mouse: false,
491 additional_browser_args: None,
492 window_effects: None,
493 incognito: false,
494 transparent: false,
495 focus: true,
496 bounds: None,
497 auto_resize: false,
498 proxy_url: None,
499 zoom_hotkeys_enabled: false,
500 browser_extensions_enabled: false,
501 data_store_identifier: None,
502 extensions_path: None,
503 use_https_scheme: false,
504 devtools: None,
505 background_color: None,
506 traffic_light_position: None,
507 background_throttling: None,
508 javascript_disabled: false,
509 allow_link_preview: true,
510 scroll_bar_style: ScrollBarStyle::Default,
511 #[cfg(target_os = "ios")]
512 input_accessory_view_builder: None,
513 #[cfg(windows)]
514 environment: None,
515 #[cfg(any(
516 target_os = "linux",
517 target_os = "dragonfly",
518 target_os = "freebsd",
519 target_os = "netbsd",
520 target_os = "openbsd",
521 ))]
522 related_view: None,
523 #[cfg(target_os = "macos")]
524 webview_configuration: None,
525 }
526 }
527
528 #[must_use]
530 pub fn user_agent(mut self, user_agent: &str) -> Self {
531 self.user_agent = Some(user_agent.to_string());
532 self
533 }
534
535 #[must_use]
553 pub fn initialization_script(mut self, script: impl Into<String>) -> Self {
554 self.initialization_scripts.push(InitializationScript {
555 script: script.into(),
556 for_main_frame_only: true,
557 });
558 self
559 }
560
561 #[must_use]
579 pub fn initialization_script_on_all_frames(mut self, script: impl Into<String>) -> Self {
580 self.initialization_scripts.push(InitializationScript {
581 script: script.into(),
582 for_main_frame_only: false,
583 });
584 self
585 }
586
587 #[must_use]
589 pub fn data_directory(mut self, data_directory: PathBuf) -> Self {
590 self.data_directory.replace(data_directory);
591 self
592 }
593
594 #[must_use]
596 pub fn disable_drag_drop_handler(mut self) -> Self {
597 self.drag_drop_handler_enabled = false;
598 self
599 }
600
601 #[must_use]
606 pub fn enable_clipboard_access(mut self) -> Self {
607 self.clipboard = true;
608 self
609 }
610
611 #[must_use]
613 pub fn accept_first_mouse(mut self, accept: bool) -> Self {
614 self.accept_first_mouse = accept;
615 self
616 }
617
618 #[must_use]
620 pub fn additional_browser_args(mut self, additional_args: &str) -> Self {
621 self.additional_browser_args = Some(additional_args.to_string());
622 self
623 }
624
625 #[must_use]
627 pub fn window_effects(mut self, effects: WindowEffectsConfig) -> Self {
628 self.window_effects = Some(effects);
629 self
630 }
631
632 #[must_use]
634 pub fn incognito(mut self, incognito: bool) -> Self {
635 self.incognito = incognito;
636 self
637 }
638
639 #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
641 #[must_use]
642 pub fn transparent(mut self, transparent: bool) -> Self {
643 self.transparent = transparent;
644 self
645 }
646
647 #[must_use]
649 pub fn focused(mut self, focus: bool) -> Self {
650 self.focus = focus;
651 self
652 }
653
654 #[must_use]
656 pub fn auto_resize(mut self) -> Self {
657 self.auto_resize = true;
658 self
659 }
660
661 #[must_use]
663 pub fn proxy_url(mut self, url: Url) -> Self {
664 self.proxy_url = Some(url);
665 self
666 }
667
668 #[must_use]
678 pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {
679 self.zoom_hotkeys_enabled = enabled;
680 self
681 }
682
683 #[must_use]
690 pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self {
691 self.browser_extensions_enabled = enabled;
692 self
693 }
694
695 #[must_use]
705 pub fn use_https_scheme(mut self, enabled: bool) -> Self {
706 self.use_https_scheme = enabled;
707 self
708 }
709
710 #[must_use]
720 pub fn devtools(mut self, enabled: Option<bool>) -> Self {
721 self.devtools = enabled;
722 self
723 }
724
725 #[must_use]
731 pub fn background_color(mut self, color: Color) -> Self {
732 self.background_color = Some(color);
733 self
734 }
735
736 #[must_use]
744 pub fn traffic_light_position(mut self, position: dpi::Position) -> Self {
745 self.traffic_light_position = Some(position);
746 self
747 }
748
749 #[must_use]
759 pub fn allow_link_preview(mut self, allow_link_preview: bool) -> Self {
760 self.allow_link_preview = allow_link_preview;
761 self
762 }
763
764 #[must_use]
779 pub fn background_throttling(mut self, policy: Option<BackgroundThrottlingPolicy>) -> Self {
780 self.background_throttling = policy;
781 self
782 }
783
784 #[must_use]
798 pub fn scroll_bar_style(mut self, style: ScrollBarStyle) -> Self {
799 self.scroll_bar_style = style;
800 self
801 }
802}
803
804pub type WebviewIpcHandler<T, R> = Box<dyn Fn(DetachedWebview<T, R>, Request<String>) + Send>;
806
807#[derive(Debug, Clone)]
809pub struct InitializationScript {
810 pub script: String,
812 pub for_main_frame_only: bool,
814}