Skip to main content

tauri_runtime/
webview.rs

1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! A layer between raw [`Runtime`] webviews and Tauri.
6//!
7pub use crate::webview_permissions::{PermissionKind, PermissionResponse};
8#[cfg(not(any(target_os = "android", target_os = "ios")))]
9use crate::window::WindowId;
10use crate::{Rect, Runtime, UserEvent, window::is_label_valid};
11
12use http::Request;
13use tauri_utils::config::{
14  BackgroundThrottlingPolicy, Color, ScrollBarStyle as ConfigScrollBarStyle, WebviewUrl,
15  WindowConfig, WindowEffectsConfig,
16};
17use url::Url;
18
19use std::{
20  borrow::Cow,
21  collections::HashMap,
22  hash::{Hash, Hasher},
23  path::PathBuf,
24  sync::Arc,
25};
26
27type UriSchemeProtocolHandler = dyn Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
28  + Send
29  + Sync
30  + 'static;
31
32type WebResourceRequestHandler =
33  dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
34
35type NavigationHandler = dyn Fn(&Url) -> bool + Send;
36
37type NewWindowHandler = dyn Fn(Url, NewWindowFeatures) -> NewWindowResponse + Send;
38
39type OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send;
40
41type DocumentTitleChangedHandler = dyn Fn(String) + Send + 'static;
42
43type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync;
44
45type PermissionRequestHandler = dyn Fn(PermissionKind) -> PermissionResponse + Send + Sync;
46
47#[cfg(any(target_os = "macos", target_os = "ios"))]
48type OnWebContentProcessTerminateHandler = dyn Fn() + Send;
49
50#[cfg(target_os = "ios")]
51type InputAccessoryViewBuilderFn = dyn Fn(&objc2_ui_kit::UIView) -> Option<objc2::rc::Retained<objc2_ui_kit::UIView>>
52  + Send
53  + Sync
54  + 'static;
55
56/// Download event.
57pub enum DownloadEvent<'a> {
58  /// Download requested.
59  Requested {
60    /// The url being downloaded.
61    url: Url,
62    /// Represents where the file will be downloaded to.
63    /// Can be used to set the download location by assigning a new path to it.
64    /// The assigned path _must_ be absolute.
65    destination: &'a mut PathBuf,
66  },
67  /// Download finished.
68  Finished {
69    /// The URL of the original download request.
70    url: Url,
71    /// Potentially representing the filesystem path the file was downloaded to.
72    path: Option<PathBuf>,
73    /// Indicates if the download succeeded or not.
74    success: bool,
75  },
76}
77
78#[cfg(target_os = "android")]
79pub struct CreationContext<'a, 'b> {
80  pub env: &'a mut jni::JNIEnv<'b>,
81  pub activity: &'a jni::objects::JObject<'b>,
82  pub webview: &'a jni::objects::JObject<'b>,
83}
84
85/// Kind of event for the page load handler.
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum PageLoadEvent {
88  /// Page started to load.
89  Started,
90  /// Page finished loading.
91  Finished,
92}
93
94/// Information about the webview that initiated a new window request.
95#[derive(Debug)]
96pub struct NewWindowOpener {
97  /// The instance of the webview that initiated the new window request.
98  ///
99  /// This must be set as the related view of the new webview. See [`WebviewAttributes::related_view`].
100  #[cfg(any(
101    target_os = "linux",
102    target_os = "dragonfly",
103    target_os = "freebsd",
104    target_os = "netbsd",
105    target_os = "openbsd",
106  ))]
107  pub webview: webkit2gtk::WebView,
108  /// The instance of the webview that initiated the new window request.
109  ///
110  /// The target webview environment **MUST** match the environment of the opener webview. See [`WebviewAttributes::with_environment`].
111  #[cfg(windows)]
112  pub webview: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2,
113  #[cfg(windows)]
114  pub environment: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment,
115  /// The instance of the webview that initiated the new window request.
116  #[cfg(target_os = "macos")]
117  pub webview: objc2::rc::Retained<objc2_web_kit::WKWebView>,
118  /// Configuration of the target webview.
119  ///
120  /// This **MUST** be used when creating the target webview. See [`WebviewAttributes::webview_configuration`].
121  #[cfg(target_os = "macos")]
122  pub target_configuration: objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>,
123}
124
125/// Window features of a window requested to open.
126#[derive(Debug)]
127pub struct NewWindowFeatures {
128  pub(crate) size: Option<crate::dpi::LogicalSize<f64>>,
129  pub(crate) position: Option<crate::dpi::LogicalPosition<f64>>,
130  pub(crate) opener: NewWindowOpener,
131}
132
133impl NewWindowFeatures {
134  pub fn new(
135    size: Option<crate::dpi::LogicalSize<f64>>,
136    position: Option<crate::dpi::LogicalPosition<f64>>,
137    opener: NewWindowOpener,
138  ) -> Self {
139    Self {
140      size,
141      position,
142      opener,
143    }
144  }
145
146  /// Specifies the size of the content area
147  /// as defined by the user's operating system where the new window will be generated.
148  pub fn size(&self) -> Option<crate::dpi::LogicalSize<f64>> {
149    self.size
150  }
151
152  /// Specifies the position of the window relative to the work area
153  /// as defined by the user's operating system where the new window will be generated.
154  pub fn position(&self) -> Option<crate::dpi::LogicalPosition<f64>> {
155    self.position
156  }
157
158  /// Returns information about the webview that initiated a new window request.
159  pub fn opener(&self) -> &NewWindowOpener {
160    &self.opener
161  }
162}
163
164/// Response for the new window request handler.
165pub enum NewWindowResponse {
166  /// Allow the window to be opened with the default implementation.
167  Allow,
168  /// Allow the window to be opened, with the given window.
169  ///
170  /// ## Platform-specific:
171  ///
172  /// **Linux**: The webview must be related to the caller webview. See [`WebviewAttributes::related_view`].
173  /// **Windows**: The webview must use the same environment as the caller webview. See [`WebviewAttributes::with_environment`].
174  #[cfg(not(any(target_os = "android", target_os = "ios")))]
175  Create { window_id: WindowId },
176  /// Deny the window from being opened.
177  Deny,
178}
179
180/// The scrollbar style to use in the webview.
181///
182/// ## Platform-specific
183///
184/// - **Windows**: This option must be given the same value for all webviews that target the same data directory.
185#[non_exhaustive]
186#[derive(Debug, Clone, Copy, Default)]
187pub enum ScrollBarStyle {
188  #[default]
189  /// The default scrollbar style for the webview.
190  Default,
191
192  #[cfg(windows)]
193  /// Fluent UI style overlay scrollbars. **Windows Only**
194  ///
195  /// Requires WebView2 Runtime version 125.0.2535.41 or higher, does nothing on older versions,
196  /// see <https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/?tabs=dotnetcsharp#10253541>
197  FluentOverlay,
198}
199
200/// A webview that has yet to be built.
201pub struct PendingWebview<T: UserEvent, R: Runtime<T>> {
202  /// The label that the webview will be named.
203  pub label: String,
204
205  /// The [`WebviewAttributes`] that the webview will be created with.
206  pub webview_attributes: WebviewAttributes,
207
208  /// Custom protocols to register on the webview
209  pub uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocolHandler>>,
210
211  /// How to handle IPC calls on the webview.
212  pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
213
214  /// A handler to decide if incoming url is allowed to navigate.
215  pub navigation_handler: Option<Box<NavigationHandler>>,
216
217  pub new_window_handler: Option<Box<NewWindowHandler>>,
218
219  pub document_title_changed_handler: Option<Box<DocumentTitleChangedHandler>>,
220
221  /// The resolved URL to load on the webview.
222  pub url: String,
223
224  #[cfg(target_os = "android")]
225  #[allow(clippy::type_complexity)]
226  pub on_webview_created:
227    Option<Box<dyn Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + Sync>>,
228
229  pub web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
230
231  pub on_page_load_handler: Option<Box<OnPageLoadHandler>>,
232
233  pub download_handler: Option<Arc<DownloadHandler>>,
234
235  pub permission_request_handler: Option<Box<PermissionRequestHandler>>,
236
237  #[cfg(any(target_os = "macos", target_os = "ios"))]
238  pub on_web_content_process_terminate_handler: Option<Box<OnWebContentProcessTerminateHandler>>,
239}
240
241impl<T: UserEvent, R: Runtime<T>> PendingWebview<T, R> {
242  /// Create a new [`PendingWebview`] with a label from the given [`WebviewAttributes`].
243  pub fn new(
244    webview_attributes: WebviewAttributes,
245    label: impl Into<String>,
246  ) -> crate::Result<Self> {
247    let label = label.into();
248    if !is_label_valid(&label) {
249      Err(crate::Error::InvalidWindowLabel)
250    } else {
251      Ok(Self {
252        webview_attributes,
253        uri_scheme_protocols: Default::default(),
254        label,
255        ipc_handler: None,
256        navigation_handler: None,
257        new_window_handler: None,
258        document_title_changed_handler: None,
259        url: "tauri://localhost".to_string(),
260        #[cfg(target_os = "android")]
261        on_webview_created: None,
262        web_resource_request_handler: None,
263        on_page_load_handler: None,
264        download_handler: None,
265        permission_request_handler: None,
266        #[cfg(any(target_os = "macos", target_os = "ios"))]
267        on_web_content_process_terminate_handler: None,
268      })
269    }
270  }
271
272  pub fn register_uri_scheme_protocol<
273    N: Into<String>,
274    H: Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
275      + Send
276      + Sync
277      + 'static,
278  >(
279    &mut self,
280    uri_scheme: N,
281    protocol_handler: H,
282  ) {
283    let uri_scheme = uri_scheme.into();
284    self
285      .uri_scheme_protocols
286      .insert(uri_scheme, Box::new(protocol_handler));
287  }
288
289  #[cfg(target_os = "android")]
290  pub fn on_webview_created<
291    F: Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + Sync + 'static,
292  >(
293    mut self,
294    f: F,
295  ) -> Self {
296    self.on_webview_created.replace(Box::new(f));
297    self
298  }
299}
300
301/// A webview that is not yet managed by Tauri.
302#[derive(Debug)]
303pub struct DetachedWebview<T: UserEvent, R: Runtime<T>> {
304  /// Name of the window
305  pub label: String,
306
307  /// The [`crate::WebviewDispatch`] associated with the window.
308  pub dispatcher: R::WebviewDispatcher,
309}
310
311impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWebview<T, R> {
312  fn clone(&self) -> Self {
313    Self {
314      label: self.label.clone(),
315      dispatcher: self.dispatcher.clone(),
316    }
317  }
318}
319
320impl<T: UserEvent, R: Runtime<T>> Hash for DetachedWebview<T, R> {
321  /// Only use the [`DetachedWebview`]'s label to represent its hash.
322  fn hash<H: Hasher>(&self, state: &mut H) {
323    self.label.hash(state)
324  }
325}
326
327impl<T: UserEvent, R: Runtime<T>> Eq for DetachedWebview<T, R> {}
328impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWebview<T, R> {
329  /// Only use the [`DetachedWebview`]'s label to compare equality.
330  fn eq(&self, other: &Self) -> bool {
331    self.label.eq(&other.label)
332  }
333}
334
335/// The attributes used to create an webview.
336#[derive(Debug)]
337pub struct WebviewAttributes {
338  pub url: WebviewUrl,
339  pub user_agent: Option<String>,
340  /// A list of initialization javascript scripts to run when loading new pages.
341  /// When webview load a new page, this initialization code will be executed.
342  /// It is guaranteed that code is executed before `window.onload`.
343  ///
344  /// ## Platform-specific
345  ///
346  /// - **Windows:** scripts are always added to subframes.
347  /// - **Android:** When [addDocumentStartJavaScript] is not supported,
348  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).
349  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.
350  ///
351  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)
352  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)
353  pub initialization_scripts: Vec<InitializationScript>,
354  pub data_directory: Option<PathBuf>,
355  pub drag_drop_handler_enabled: bool,
356  pub clipboard: bool,
357  pub accept_first_mouse: bool,
358  pub additional_browser_args: Option<String>,
359  pub window_effects: Option<WindowEffectsConfig>,
360  pub incognito: bool,
361  pub transparent: bool,
362  pub focus: bool,
363  pub bounds: Option<Rect>,
364  pub auto_resize: bool,
365  pub proxy_url: Option<Url>,
366  pub zoom_hotkeys_enabled: bool,
367  pub browser_extensions_enabled: bool,
368  pub extensions_path: Option<PathBuf>,
369  pub data_store_identifier: Option<[u8; 16]>,
370  pub use_https_scheme: bool,
371  pub devtools: Option<bool>,
372  pub background_color: Option<Color>,
373  pub traffic_light_position: Option<dpi::Position>,
374  pub background_throttling: Option<BackgroundThrottlingPolicy>,
375  pub javascript_disabled: bool,
376  /// on macOS and iOS there is a link preview on long pressing links, this is enabled by default.
377  /// see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview
378  pub allow_link_preview: bool,
379  pub scroll_bar_style: ScrollBarStyle,
380  /// Controls the WebView's browser-level general autofill behavior.
381  ///
382  /// **This option does not disable password or credit card autofill.**
383  ///
384  /// When set to `false`, the WebView will not automatically populate
385  /// general form fields using previously stored data such as addresses
386  /// or contact information.
387  ///
388  /// If not specified, this is `true` by default.
389  ///
390  /// ## Platform-specific
391  ///
392  /// - **Windows**: Supported. WebView2's autofill feature (called
393  ///   "Suggestions") may not honor `autocomplete="off"` on input
394  ///   elements in some cases.
395  /// - **Linux / Android / iOS / macOS**: Unsupported and performs no
396  ///   operation.
397  pub general_autofill_enabled: bool,
398  /// Allows overriding the keyboard accessory view on iOS.
399  /// Returning `None` effectively removes the view.
400  ///
401  /// The closure parameter is the webview instance.
402  ///
403  /// The accessory view is the view that appears above the keyboard when a text input element is focused.
404  /// It usually displays a view with "Done", "Next" buttons.
405  ///
406  /// # Stability
407  ///
408  /// This relies on [`objc2_ui_kit`] which does not provide a stable API yet, so it can receive breaking changes in minor releases.
409  #[cfg(target_os = "ios")]
410  pub input_accessory_view_builder: Option<InputAccessoryViewBuilder>,
411  #[cfg(target_os = "ios")]
412  pub limit_navigations_to_app_bound_domains: bool,
413
414  /// Set the environment for the webview.
415  /// Useful if you need to share the same environment, for instance when using the [`PendingWebview::new_window_handler`].
416  #[cfg(windows)]
417  pub environment: Option<webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment>,
418
419  /// Creates a new webview sharing the same web process with the provided webview.
420  /// Useful if you need to link a webview to another, for instance when using the [`PendingWebview::new_window_handler`].
421  #[cfg(any(
422    target_os = "linux",
423    target_os = "dragonfly",
424    target_os = "freebsd",
425    target_os = "netbsd",
426    target_os = "openbsd",
427  ))]
428  pub related_view: Option<webkit2gtk::WebView>,
429
430  #[cfg(target_os = "macos")]
431  pub webview_configuration: Option<objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>>,
432}
433
434unsafe impl Send for WebviewAttributes {}
435unsafe impl Sync for WebviewAttributes {}
436
437#[cfg(target_os = "ios")]
438#[non_exhaustive]
439pub struct InputAccessoryViewBuilder(pub Box<InputAccessoryViewBuilderFn>);
440
441#[cfg(target_os = "ios")]
442impl std::fmt::Debug for InputAccessoryViewBuilder {
443  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
444    f.debug_struct("InputAccessoryViewBuilder").finish()
445  }
446}
447
448#[cfg(target_os = "ios")]
449impl InputAccessoryViewBuilder {
450  pub fn new(builder: Box<InputAccessoryViewBuilderFn>) -> Self {
451    Self(builder)
452  }
453}
454
455impl From<&WindowConfig> for WebviewAttributes {
456  fn from(config: &WindowConfig) -> Self {
457    let mut builder = Self::new(config.url.clone())
458      .incognito(config.incognito)
459      .focused(config.focus)
460      .zoom_hotkeys_enabled(config.zoom_hotkeys_enabled)
461      .use_https_scheme(config.use_https_scheme)
462      .browser_extensions_enabled(config.browser_extensions_enabled)
463      .background_throttling(config.background_throttling.clone())
464      .devtools(config.devtools)
465      .scroll_bar_style(match config.scroll_bar_style {
466        ConfigScrollBarStyle::Default => ScrollBarStyle::Default,
467        #[cfg(windows)]
468        ConfigScrollBarStyle::FluentOverlay => ScrollBarStyle::FluentOverlay,
469        _ => ScrollBarStyle::Default,
470      })
471      .limit_navigations_to_app_bound_domains(config.limit_navigations_to_app_bound_domains)
472      .general_autofill_enabled(config.general_autofill_enabled);
473
474    #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
475    {
476      builder = builder.transparent(config.transparent);
477    }
478    #[cfg(target_os = "macos")]
479    {
480      if let Some(position) = &config.traffic_light_position {
481        builder =
482          builder.traffic_light_position(dpi::LogicalPosition::new(position.x, position.y).into());
483      }
484    }
485    builder = builder.accept_first_mouse(config.accept_first_mouse);
486    if !config.drag_drop_enabled {
487      builder = builder.disable_drag_drop_handler();
488    }
489    if let Some(user_agent) = &config.user_agent {
490      builder = builder.user_agent(user_agent);
491    }
492    if let Some(additional_browser_args) = &config.additional_browser_args {
493      builder = builder.additional_browser_args(additional_browser_args);
494    }
495    if let Some(effects) = &config.window_effects {
496      builder = builder.window_effects(effects.clone());
497    }
498    if let Some(url) = &config.proxy_url {
499      builder = builder.proxy_url(url.to_owned());
500    }
501    if let Some(color) = config.background_color {
502      builder = builder.background_color(color);
503    }
504    builder.javascript_disabled = config.javascript_disabled;
505    builder.allow_link_preview = config.allow_link_preview;
506    #[cfg(target_os = "ios")]
507    if config.disable_input_accessory_view {
508      builder
509        .input_accessory_view_builder
510        .replace(InputAccessoryViewBuilder::new(Box::new(|_webview| None)));
511    }
512    builder
513  }
514}
515
516impl WebviewAttributes {
517  /// Initializes the default attributes for a webview.
518  pub fn new(url: WebviewUrl) -> Self {
519    Self {
520      url,
521      user_agent: None,
522      initialization_scripts: Vec::new(),
523      data_directory: None,
524      drag_drop_handler_enabled: true,
525      clipboard: false,
526      accept_first_mouse: false,
527      additional_browser_args: None,
528      window_effects: None,
529      incognito: false,
530      transparent: false,
531      focus: true,
532      bounds: None,
533      auto_resize: false,
534      proxy_url: None,
535      zoom_hotkeys_enabled: false,
536      browser_extensions_enabled: false,
537      data_store_identifier: None,
538      extensions_path: None,
539      use_https_scheme: false,
540      devtools: None,
541      background_color: None,
542      traffic_light_position: None,
543      background_throttling: None,
544      javascript_disabled: false,
545      allow_link_preview: true,
546      scroll_bar_style: ScrollBarStyle::Default,
547      general_autofill_enabled: true,
548      #[cfg(target_os = "ios")]
549      input_accessory_view_builder: None,
550      #[cfg(target_os = "ios")]
551      limit_navigations_to_app_bound_domains: false,
552      #[cfg(windows)]
553      environment: None,
554      #[cfg(any(
555        target_os = "linux",
556        target_os = "dragonfly",
557        target_os = "freebsd",
558        target_os = "netbsd",
559        target_os = "openbsd",
560      ))]
561      related_view: None,
562      #[cfg(target_os = "macos")]
563      webview_configuration: None,
564    }
565  }
566
567  /// Sets the user agent
568  #[must_use]
569  pub fn user_agent(mut self, user_agent: &str) -> Self {
570    self.user_agent = Some(user_agent.to_string());
571    self
572  }
573
574  /// Adds an init script for the main frame.
575  ///
576  /// When webview load a new page, this initialization code will be executed.
577  /// It is guaranteed that code is executed before `window.onload`.
578  ///
579  /// This is executed only on the main frame.
580  /// If you only want to run it in all frames, use [`Self::initialization_script_on_all_frames`] instead.
581  ///
582  /// ## Platform-specific
583  ///
584  /// - **Windows:** scripts are always added to subframes.
585  /// - **Android:** When [addDocumentStartJavaScript] is not supported,
586  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).
587  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.
588  ///
589  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)
590  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)
591  #[must_use]
592  pub fn initialization_script(mut self, script: impl Into<String>) -> Self {
593    self.initialization_scripts.push(InitializationScript {
594      script: script.into(),
595      for_main_frame_only: true,
596    });
597    self
598  }
599
600  /// Adds an init script for all frames.
601  ///
602  /// When webview load a new page, this initialization code will be executed.
603  /// It is guaranteed that code is executed before `window.onload`.
604  ///
605  /// This is executed on all frames, main frame and also sub frames.
606  /// If you only want to run it in the main frame, use [`Self::initialization_script`] instead.
607  ///
608  /// ## Platform-specific
609  ///
610  /// - **Windows:** scripts are always added to subframes.
611  /// - **Android:** When [addDocumentStartJavaScript] is not supported,
612  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).
613  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.
614  ///
615  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)
616  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)
617  #[must_use]
618  pub fn initialization_script_on_all_frames(mut self, script: impl Into<String>) -> Self {
619    self.initialization_scripts.push(InitializationScript {
620      script: script.into(),
621      for_main_frame_only: false,
622    });
623    self
624  }
625
626  /// Data directory for the webview.
627  #[must_use]
628  pub fn data_directory(mut self, data_directory: PathBuf) -> Self {
629    self.data_directory.replace(data_directory);
630    self
631  }
632
633  /// Disables the drag and drop handler used internally to generate [`DragDropEvent`](crate::window::DragDropEvent)s.
634  ///
635  /// This is required to use HTML5 drag and drop APIs on the frontend on Windows since we replace the drag drop handler of WebView2.
636  #[must_use]
637  pub fn disable_drag_drop_handler(mut self) -> Self {
638    self.drag_drop_handler_enabled = false;
639    self
640  }
641
642  /// Enables clipboard access for the page rendered on **Linux** and **Windows**.
643  ///
644  /// **macOS** doesn't provide such method and is always enabled by default,
645  /// but you still need to add menu item accelerators to use shortcuts.
646  #[must_use]
647  pub fn enable_clipboard_access(mut self) -> Self {
648    self.clipboard = true;
649    self
650  }
651
652  /// Sets whether clicking an inactive window also clicks through to the webview.
653  #[must_use]
654  pub fn accept_first_mouse(mut self, accept: bool) -> Self {
655    self.accept_first_mouse = accept;
656    self
657  }
658
659  /// Sets additional browser arguments. **Windows Only**
660  #[must_use]
661  pub fn additional_browser_args(mut self, additional_args: &str) -> Self {
662    self.additional_browser_args = Some(additional_args.to_string());
663    self
664  }
665
666  /// Sets window effects
667  #[must_use]
668  pub fn window_effects(mut self, effects: WindowEffectsConfig) -> Self {
669    self.window_effects = Some(effects);
670    self
671  }
672
673  /// Enable or disable incognito mode for the WebView.
674  #[must_use]
675  pub fn incognito(mut self, incognito: bool) -> Self {
676    self.incognito = incognito;
677    self
678  }
679
680  /// Enable or disable transparency for the WebView.
681  #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
682  #[must_use]
683  pub fn transparent(mut self, transparent: bool) -> Self {
684    self.transparent = transparent;
685    self
686  }
687
688  /// Whether the webview should be focused or not.
689  #[must_use]
690  pub fn focused(mut self, focus: bool) -> Self {
691    self.focus = focus;
692    self
693  }
694
695  /// Sets the webview to automatically grow and shrink its size and position when the parent window resizes.
696  #[must_use]
697  pub fn auto_resize(mut self) -> Self {
698    self.auto_resize = true;
699    self
700  }
701
702  /// Enable proxy for the WebView
703  #[must_use]
704  pub fn proxy_url(mut self, url: Url) -> Self {
705    self.proxy_url = Some(url);
706    self
707  }
708
709  /// Whether page zooming by hotkeys is enabled
710  ///
711  /// ## Platform-specific:
712  ///
713  /// - **Windows**: Controls WebView2's [`IsZoomControlEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings?view=webview2-winrt-1.0.2420.47#iszoomcontrolenabled) setting.
714  /// - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`,
715  ///   20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission
716  ///
717  /// - **Android / iOS**: Unsupported.
718  #[must_use]
719  pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {
720    self.zoom_hotkeys_enabled = enabled;
721    self
722  }
723
724  /// Whether browser extensions can be installed for the webview process
725  ///
726  /// ## Platform-specific:
727  ///
728  /// - **Windows**: Enables the WebView2 environment's [`AreBrowserExtensionsEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2environmentoptions?view=webview2-winrt-1.0.2739.15#arebrowserextensionsenabled)
729  /// - **MacOS / Linux / iOS / Android** - Unsupported.
730  #[must_use]
731  pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self {
732    self.browser_extensions_enabled = enabled;
733    self
734  }
735
736  /// Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.
737  ///
738  /// ## Note
739  ///
740  /// Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.
741  ///
742  /// ## Warning
743  ///
744  /// Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.
745  #[must_use]
746  pub fn use_https_scheme(mut self, enabled: bool) -> Self {
747    self.use_https_scheme = enabled;
748    self
749  }
750
751  /// Whether web inspector, which is usually called browser devtools, is enabled or not. Enabled by default.
752  ///
753  /// This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.
754  ///
755  /// ## Platform-specific
756  ///
757  /// - macOS: This will call private functions on **macOS**.
758  /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.
759  /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.
760  #[must_use]
761  pub fn devtools(mut self, enabled: Option<bool>) -> Self {
762    self.devtools = enabled;
763    self
764  }
765
766  /// Set the window and webview background color.
767  /// ## Platform-specific:
768  ///
769  /// - **Windows**: On Windows 7, alpha channel is ignored for the webview layer.
770  /// - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored.
771  #[must_use]
772  pub fn background_color(mut self, color: Color) -> Self {
773    self.background_color = Some(color);
774    self
775  }
776
777  /// Change the position of the window controls. Available on macOS only.
778  ///
779  /// Requires titleBarStyle: Overlay and decorations: true.
780  ///
781  /// ## Platform-specific
782  ///
783  /// - **Linux / Windows / iOS / Android:** Unsupported.
784  #[must_use]
785  pub fn traffic_light_position(mut self, position: dpi::Position) -> Self {
786    self.traffic_light_position = Some(position);
787    self
788  }
789
790  /// Whether to show a link preview when long pressing on links. Available on macOS and iOS only.
791  ///
792  /// Default is true.
793  ///
794  /// See https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview
795  ///
796  /// ## Platform-specific
797  ///
798  /// - **Linux / Windows / Android:** Unsupported.
799  #[must_use]
800  pub fn allow_link_preview(mut self, allow_link_preview: bool) -> Self {
801    self.allow_link_preview = allow_link_preview;
802    self
803  }
804
805  /// Whether to limit navigations to App-Bound Domains. This is necessary to
806  /// enable Service Workers on iOS according to
807  /// [StackOverflow](https://stackoverflow.com/questions/49673399/service-workers-unavailable-in-wkwebview-in-ios-11-3/64155509#64155509).
808  ///
809  /// Default is false.
810  ///
811  /// Note: If you pass in `true` make sure to add localhost and any [`registrable
812  /// domains`](https://developer.mozilla.org/en-US/docs/Glossary/Registrable_domain)
813  /// used in this webview to tauri-src/Info.ios.plist:
814  ///
815  /// ```xml
816  /// <plist>
817  /// <dict>
818  ///     <key>WKAppBoundDomains</key>
819  ///     <array>
820  ///         <string>localhost</string>
821  ///         <string>aregistrabledomain.example</string>
822  ///     </array>
823  /// </dict>
824  /// </plist>
825  /// ```
826  ///
827  /// You must add `localhost` if any webview with this set to true opens a
828  /// local webpage, makes any localhost calls, or uses the isolation pattern
829  /// because Tauri uses the `localhost` domain for hosting the application
830  /// webpage, the IPC protocol, and the isolation pattern's iframe.
831  ///
832  /// Requests served through custom uri schemes are allowed so long as they use
833  /// a registrable domain specified in the `WKAppBoundDomains` array for all the
834  /// requests from the app, including requests for the `localhost` domain.
835  ///
836  /// In theory, you can whitelist an entire uri scheme by including the
837  /// protocol name followed by a colon. For example, to allow all requests
838  /// using a custom "stream" uri scheme (see [this tauri
839  /// example](https://github.com/tauri-apps/tauri/blob/dev/examples/streaming/main.rs)),
840  /// you could add `stream:` to the AppBoundDomains array. That said, I'm not
841  /// sure whether Apple would let your app through app review if you do
842  /// whitelist an entire protocol because this feature is not mentioned in
843  /// [their blog post on App-Bound
844  /// Domains](https://webkit.org/blog/10882/app-bound-domains/).
845  ///
846  /// See https://webkit.org/blog/10882/app-bound-domains/ and
847  /// https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/limitsnavigationstoappbounddomains
848  /// for the official documentation on App-Bound Domains.
849  ///
850  /// ## Platform-specific
851  ///
852  /// - **iOS**: Supported since version 14.0+.
853  /// - **Linux / Windows / Android / MacOS:** Unsupported.
854  #[must_use]
855  #[allow(unused_variables, unused_mut)]
856  pub fn limit_navigations_to_app_bound_domains(mut self, limit_navigations: bool) -> Self {
857    #[cfg(target_os = "ios")]
858    {
859      self.limit_navigations_to_app_bound_domains = limit_navigations;
860    }
861    self
862  }
863
864  /// Change the default background throttling behavior.
865  ///
866  /// By default, browsers use a suspend policy that will throttle timers and even unload
867  /// the whole tab (view) to free resources after roughly 5 minutes when a view became
868  /// minimized or hidden. This will pause all tasks until the documents visibility state
869  /// changes back from hidden to visible by bringing the view back to the foreground.
870  ///
871  /// ## Platform-specific
872  ///
873  /// - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.
874  /// - **iOS**: Supported since version 17.0+.
875  /// - **macOS**: Supported since version 14.0+.
876  ///
877  /// see <https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578>
878  #[must_use]
879  pub fn background_throttling(mut self, policy: Option<BackgroundThrottlingPolicy>) -> Self {
880    self.background_throttling = policy;
881    self
882  }
883
884  /// Specifies the native scrollbar style to use with the webview.
885  /// CSS styles that modify the scrollbar are applied on top of the native appearance configured here.
886  ///
887  /// Defaults to [`ScrollBarStyle::Default`], which is the browser default.
888  ///
889  /// ## Platform-specific
890  ///
891  /// - **Windows**:
892  ///   - [`ScrollBarStyle::FluentOverlay`] requires WebView2 Runtime version 125.0.2535.41 or higher,
893  ///     and does nothing on older versions.
894  ///   - This option must be given the same value for all webviews that target the same data directory. Use
895  ///     [`WebviewAttributes::data_directory`] to change data directories if needed.
896  /// - **Linux / Android / iOS / macOS**: Unsupported. Only supports `Default` and performs no operation.
897  #[must_use]
898  pub fn scroll_bar_style(mut self, style: ScrollBarStyle) -> Self {
899    self.scroll_bar_style = style;
900    self
901  }
902
903  /// Controls the WebView's browser-level general autofill behavior.
904  ///
905  /// **This option does not disable password or credit card autofill.**
906  ///
907  /// When set to `false`, the WebView will not automatically populate
908  /// general form fields using previously stored data such as addresses
909  /// or contact information.
910  ///
911  /// By default, this is `true`.
912  ///
913  /// ## Platform-specific
914  ///
915  /// - **Windows**: Supported. WebView2's autofill feature (called
916  ///   "Suggestions") may not honor `autocomplete="off"` on input
917  ///   elements in some cases.
918  /// - **Linux / Android / iOS / macOS**: Unsupported and performs no
919  ///   operation.
920  #[must_use]
921  pub fn general_autofill_enabled(mut self, enabled: bool) -> Self {
922    self.general_autofill_enabled = enabled;
923    self
924  }
925}
926
927/// IPC handler.
928pub type WebviewIpcHandler<T, R> = Box<dyn Fn(DetachedWebview<T, R>, Request<String>) + Send>;
929
930/// An initialization script
931#[derive(Debug, Clone)]
932pub struct InitializationScript {
933  /// The script to run
934  pub script: String,
935  /// Whether the script should be injected to main frame only
936  pub for_main_frame_only: bool,
937}