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 UriSchemeProtocol = 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<UriSchemeProtocol>>,
203
204 pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
206
207 pub navigation_handler: Option<Box<NavigationHandler>>,
209
210 pub new_window_handler: Option<Box<NewWindowHandler>>,
211
212 pub document_title_changed_handler: Option<Box<DocumentTitleChangedHandler>>,
213
214 pub url: String,
216
217 #[cfg(target_os = "android")]
218 #[allow(clippy::type_complexity)]
219 pub on_webview_created:
220 Option<Box<dyn Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send>>,
221
222 pub web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
223
224 pub on_page_load_handler: Option<Box<OnPageLoadHandler>>,
225
226 pub download_handler: Option<Arc<DownloadHandler>>,
227}
228
229impl<T: UserEvent, R: Runtime<T>> PendingWebview<T, R> {
230 pub fn new(
232 webview_attributes: WebviewAttributes,
233 label: impl Into<String>,
234 ) -> crate::Result<Self> {
235 let label = label.into();
236 if !is_label_valid(&label) {
237 Err(crate::Error::InvalidWindowLabel)
238 } else {
239 Ok(Self {
240 webview_attributes,
241 uri_scheme_protocols: Default::default(),
242 label,
243 ipc_handler: None,
244 navigation_handler: None,
245 new_window_handler: None,
246 document_title_changed_handler: None,
247 url: "tauri://localhost".to_string(),
248 #[cfg(target_os = "android")]
249 on_webview_created: None,
250 web_resource_request_handler: None,
251 on_page_load_handler: None,
252 download_handler: None,
253 })
254 }
255 }
256
257 pub fn register_uri_scheme_protocol<
258 N: Into<String>,
259 H: Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
260 + Send
261 + Sync
262 + 'static,
263 >(
264 &mut self,
265 uri_scheme: N,
266 protocol: H,
267 ) {
268 let uri_scheme = uri_scheme.into();
269 self
270 .uri_scheme_protocols
271 .insert(uri_scheme, Box::new(protocol));
272 }
273
274 #[cfg(target_os = "android")]
275 pub fn on_webview_created<
276 F: Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + 'static,
277 >(
278 mut self,
279 f: F,
280 ) -> Self {
281 self.on_webview_created.replace(Box::new(f));
282 self
283 }
284}
285
286#[derive(Debug)]
288pub struct DetachedWebview<T: UserEvent, R: Runtime<T>> {
289 pub label: String,
291
292 pub dispatcher: R::WebviewDispatcher,
294}
295
296impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWebview<T, R> {
297 fn clone(&self) -> Self {
298 Self {
299 label: self.label.clone(),
300 dispatcher: self.dispatcher.clone(),
301 }
302 }
303}
304
305impl<T: UserEvent, R: Runtime<T>> Hash for DetachedWebview<T, R> {
306 fn hash<H: Hasher>(&self, state: &mut H) {
308 self.label.hash(state)
309 }
310}
311
312impl<T: UserEvent, R: Runtime<T>> Eq for DetachedWebview<T, R> {}
313impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWebview<T, R> {
314 fn eq(&self, other: &Self) -> bool {
316 self.label.eq(&other.label)
317 }
318}
319
320#[derive(Debug)]
322pub struct WebviewAttributes {
323 pub url: WebviewUrl,
324 pub user_agent: Option<String>,
325 pub initialization_scripts: Vec<InitializationScript>,
339 pub data_directory: Option<PathBuf>,
340 pub drag_drop_handler_enabled: bool,
341 pub clipboard: bool,
342 pub accept_first_mouse: bool,
343 pub additional_browser_args: Option<String>,
344 pub window_effects: Option<WindowEffectsConfig>,
345 pub incognito: bool,
346 pub transparent: bool,
347 pub focus: bool,
348 pub bounds: Option<Rect>,
349 pub auto_resize: bool,
350 pub proxy_url: Option<Url>,
351 pub zoom_hotkeys_enabled: bool,
352 pub browser_extensions_enabled: bool,
353 pub extensions_path: Option<PathBuf>,
354 pub data_store_identifier: Option<[u8; 16]>,
355 pub use_https_scheme: bool,
356 pub devtools: Option<bool>,
357 pub background_color: Option<Color>,
358 pub traffic_light_position: Option<dpi::Position>,
359 pub background_throttling: Option<BackgroundThrottlingPolicy>,
360 pub javascript_disabled: bool,
361 pub allow_link_preview: bool,
364 pub scroll_bar_style: ScrollBarStyle,
365 #[cfg(target_os = "ios")]
377 pub input_accessory_view_builder: Option<InputAccessoryViewBuilder>,
378
379 #[cfg(windows)]
382 pub environment: Option<webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment>,
383
384 #[cfg(any(
387 target_os = "linux",
388 target_os = "dragonfly",
389 target_os = "freebsd",
390 target_os = "netbsd",
391 target_os = "openbsd",
392 ))]
393 pub related_view: Option<webkit2gtk::WebView>,
394
395 #[cfg(target_os = "macos")]
396 pub webview_configuration: Option<objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>>,
397}
398
399unsafe impl Send for WebviewAttributes {}
400unsafe impl Sync for WebviewAttributes {}
401
402#[cfg(target_os = "ios")]
403#[non_exhaustive]
404pub struct InputAccessoryViewBuilder(pub Box<InputAccessoryViewBuilderFn>);
405
406#[cfg(target_os = "ios")]
407impl std::fmt::Debug for InputAccessoryViewBuilder {
408 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
409 f.debug_struct("InputAccessoryViewBuilder").finish()
410 }
411}
412
413#[cfg(target_os = "ios")]
414impl InputAccessoryViewBuilder {
415 pub fn new(builder: Box<InputAccessoryViewBuilderFn>) -> Self {
416 Self(builder)
417 }
418}
419
420impl From<&WindowConfig> for WebviewAttributes {
421 fn from(config: &WindowConfig) -> Self {
422 let mut builder = Self::new(config.url.clone())
423 .incognito(config.incognito)
424 .focused(config.focus)
425 .zoom_hotkeys_enabled(config.zoom_hotkeys_enabled)
426 .use_https_scheme(config.use_https_scheme)
427 .browser_extensions_enabled(config.browser_extensions_enabled)
428 .background_throttling(config.background_throttling.clone())
429 .devtools(config.devtools)
430 .scroll_bar_style(match config.scroll_bar_style {
431 ConfigScrollBarStyle::Default => ScrollBarStyle::Default,
432 #[cfg(windows)]
433 ConfigScrollBarStyle::FluentOverlay => ScrollBarStyle::FluentOverlay,
434 _ => ScrollBarStyle::Default,
435 });
436
437 #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
438 {
439 builder = builder.transparent(config.transparent);
440 }
441 #[cfg(target_os = "macos")]
442 {
443 if let Some(position) = &config.traffic_light_position {
444 builder =
445 builder.traffic_light_position(dpi::LogicalPosition::new(position.x, position.y).into());
446 }
447 }
448 builder = builder.accept_first_mouse(config.accept_first_mouse);
449 if !config.drag_drop_enabled {
450 builder = builder.disable_drag_drop_handler();
451 }
452 if let Some(user_agent) = &config.user_agent {
453 builder = builder.user_agent(user_agent);
454 }
455 if let Some(additional_browser_args) = &config.additional_browser_args {
456 builder = builder.additional_browser_args(additional_browser_args);
457 }
458 if let Some(effects) = &config.window_effects {
459 builder = builder.window_effects(effects.clone());
460 }
461 if let Some(url) = &config.proxy_url {
462 builder = builder.proxy_url(url.to_owned());
463 }
464 if let Some(color) = config.background_color {
465 builder = builder.background_color(color);
466 }
467 builder.javascript_disabled = config.javascript_disabled;
468 builder.allow_link_preview = config.allow_link_preview;
469 #[cfg(target_os = "ios")]
470 if config.disable_input_accessory_view {
471 builder
472 .input_accessory_view_builder
473 .replace(InputAccessoryViewBuilder::new(Box::new(|_webview| None)));
474 }
475 builder
476 }
477}
478
479impl WebviewAttributes {
480 pub fn new(url: WebviewUrl) -> Self {
482 Self {
483 url,
484 user_agent: None,
485 initialization_scripts: Vec::new(),
486 data_directory: None,
487 drag_drop_handler_enabled: true,
488 clipboard: false,
489 accept_first_mouse: false,
490 additional_browser_args: None,
491 window_effects: None,
492 incognito: false,
493 transparent: false,
494 focus: true,
495 bounds: None,
496 auto_resize: false,
497 proxy_url: None,
498 zoom_hotkeys_enabled: false,
499 browser_extensions_enabled: false,
500 data_store_identifier: None,
501 extensions_path: None,
502 use_https_scheme: false,
503 devtools: None,
504 background_color: None,
505 traffic_light_position: None,
506 background_throttling: None,
507 javascript_disabled: false,
508 allow_link_preview: true,
509 scroll_bar_style: ScrollBarStyle::Default,
510 #[cfg(target_os = "ios")]
511 input_accessory_view_builder: None,
512 #[cfg(windows)]
513 environment: None,
514 #[cfg(any(
515 target_os = "linux",
516 target_os = "dragonfly",
517 target_os = "freebsd",
518 target_os = "netbsd",
519 target_os = "openbsd",
520 ))]
521 related_view: None,
522 #[cfg(target_os = "macos")]
523 webview_configuration: None,
524 }
525 }
526
527 #[must_use]
529 pub fn user_agent(mut self, user_agent: &str) -> Self {
530 self.user_agent = Some(user_agent.to_string());
531 self
532 }
533
534 #[must_use]
552 pub fn initialization_script(mut self, script: impl Into<String>) -> Self {
553 self.initialization_scripts.push(InitializationScript {
554 script: script.into(),
555 for_main_frame_only: true,
556 });
557 self
558 }
559
560 #[must_use]
578 pub fn initialization_script_on_all_frames(mut self, script: impl Into<String>) -> Self {
579 self.initialization_scripts.push(InitializationScript {
580 script: script.into(),
581 for_main_frame_only: false,
582 });
583 self
584 }
585
586 #[must_use]
588 pub fn data_directory(mut self, data_directory: PathBuf) -> Self {
589 self.data_directory.replace(data_directory);
590 self
591 }
592
593 #[must_use]
595 pub fn disable_drag_drop_handler(mut self) -> Self {
596 self.drag_drop_handler_enabled = false;
597 self
598 }
599
600 #[must_use]
605 pub fn enable_clipboard_access(mut self) -> Self {
606 self.clipboard = true;
607 self
608 }
609
610 #[must_use]
612 pub fn accept_first_mouse(mut self, accept: bool) -> Self {
613 self.accept_first_mouse = accept;
614 self
615 }
616
617 #[must_use]
619 pub fn additional_browser_args(mut self, additional_args: &str) -> Self {
620 self.additional_browser_args = Some(additional_args.to_string());
621 self
622 }
623
624 #[must_use]
626 pub fn window_effects(mut self, effects: WindowEffectsConfig) -> Self {
627 self.window_effects = Some(effects);
628 self
629 }
630
631 #[must_use]
633 pub fn incognito(mut self, incognito: bool) -> Self {
634 self.incognito = incognito;
635 self
636 }
637
638 #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
640 #[must_use]
641 pub fn transparent(mut self, transparent: bool) -> Self {
642 self.transparent = transparent;
643 self
644 }
645
646 #[must_use]
648 pub fn focused(mut self, focus: bool) -> Self {
649 self.focus = focus;
650 self
651 }
652
653 #[must_use]
655 pub fn auto_resize(mut self) -> Self {
656 self.auto_resize = true;
657 self
658 }
659
660 #[must_use]
662 pub fn proxy_url(mut self, url: Url) -> Self {
663 self.proxy_url = Some(url);
664 self
665 }
666
667 #[must_use]
677 pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {
678 self.zoom_hotkeys_enabled = enabled;
679 self
680 }
681
682 #[must_use]
689 pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self {
690 self.browser_extensions_enabled = enabled;
691 self
692 }
693
694 #[must_use]
704 pub fn use_https_scheme(mut self, enabled: bool) -> Self {
705 self.use_https_scheme = enabled;
706 self
707 }
708
709 #[must_use]
719 pub fn devtools(mut self, enabled: Option<bool>) -> Self {
720 self.devtools = enabled;
721 self
722 }
723
724 #[must_use]
730 pub fn background_color(mut self, color: Color) -> Self {
731 self.background_color = Some(color);
732 self
733 }
734
735 #[must_use]
743 pub fn traffic_light_position(mut self, position: dpi::Position) -> Self {
744 self.traffic_light_position = Some(position);
745 self
746 }
747
748 #[must_use]
758 pub fn allow_link_preview(mut self, allow_link_preview: bool) -> Self {
759 self.allow_link_preview = allow_link_preview;
760 self
761 }
762
763 #[must_use]
778 pub fn background_throttling(mut self, policy: Option<BackgroundThrottlingPolicy>) -> Self {
779 self.background_throttling = policy;
780 self
781 }
782
783 #[must_use]
797 pub fn scroll_bar_style(mut self, style: ScrollBarStyle) -> Self {
798 self.scroll_bar_style = style;
799 self
800 }
801}
802
803pub type WebviewIpcHandler<T, R> = Box<dyn Fn(DetachedWebview<T, R>, Request<String>) + Send>;
805
806#[derive(Debug, Clone)]
808pub struct InitializationScript {
809 pub script: String,
811 pub for_main_frame_only: bool,
813}