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