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: *mut std::ffi::c_void,
139 /// The [WKUserContentController](https://developer.apple.com/documentation/webkit/wkusercontentcontroller) pointer.
140 pub manager: *mut std::ffi::c_void,
141 /// The [UIViewController](https://developer.apple.com/documentation/uikit/uiviewcontroller) hosting the webview.
142 pub view_controller: *mut 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. **Windows Only**
677 #[must_use]
678 pub fn additional_browser_args(mut self, additional_args: &str) -> Self {
679 self.additional_browser_args = Some(additional_args.to_string());
680 self
681 }
682
683 /// Sets window effects
684 #[must_use]
685 pub fn window_effects(mut self, effects: WindowEffectsConfig) -> Self {
686 self.window_effects = Some(effects);
687 self
688 }
689
690 /// Enable or disable incognito mode for the WebView.
691 #[must_use]
692 pub fn incognito(mut self, incognito: bool) -> Self {
693 self.incognito = incognito;
694 self
695 }
696
697 /// Enable or disable transparency for the WebView.
698 #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
699 #[must_use]
700 pub fn transparent(mut self, transparent: bool) -> Self {
701 self.transparent = transparent;
702 self
703 }
704
705 /// Whether the webview should be focused or not.
706 #[must_use]
707 pub fn focused(mut self, focus: bool) -> Self {
708 self.focus = focus;
709 self
710 }
711
712 /// Sets the webview to automatically grow and shrink its size and position when the parent window resizes.
713 #[must_use]
714 pub fn auto_resize(mut self) -> Self {
715 self.auto_resize = true;
716 self
717 }
718
719 /// Enable proxy for the WebView
720 #[must_use]
721 pub fn proxy_url(mut self, url: Url) -> Self {
722 self.proxy_url = Some(url);
723 self
724 }
725
726 /// Whether page zooming by hotkeys is enabled
727 ///
728 /// ## Platform-specific:
729 ///
730 /// - **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.
731 /// - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`,
732 /// 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission
733 ///
734 /// - **Android / iOS**: Unsupported.
735 #[must_use]
736 pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {
737 self.zoom_hotkeys_enabled = enabled;
738 self
739 }
740
741 /// Whether browser extensions can be installed for the webview process
742 ///
743 /// ## Platform-specific:
744 ///
745 /// - **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)
746 /// - **MacOS / Linux / iOS / Android** - Unsupported.
747 #[must_use]
748 pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self {
749 self.browser_extensions_enabled = enabled;
750 self
751 }
752
753 /// Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.
754 ///
755 /// ## Note
756 ///
757 /// 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.
758 ///
759 /// ## Warning
760 ///
761 /// 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.
762 #[must_use]
763 pub fn use_https_scheme(mut self, enabled: bool) -> Self {
764 self.use_https_scheme = enabled;
765 self
766 }
767
768 /// Whether web inspector, which is usually called browser devtools, is enabled or not. Enabled by default.
769 ///
770 /// This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.
771 ///
772 /// ## Platform-specific
773 ///
774 /// - macOS: This will call private functions on **macOS**.
775 /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.
776 /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.
777 #[must_use]
778 pub fn devtools(mut self, enabled: Option<bool>) -> Self {
779 self.devtools = enabled;
780 self
781 }
782
783 /// Set the window and webview background color.
784 /// ## Platform-specific:
785 ///
786 /// - **Windows**: On Windows 7, alpha channel is ignored for the webview layer.
787 /// - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored.
788 #[must_use]
789 pub fn background_color(mut self, color: Color) -> Self {
790 self.background_color = Some(color);
791 self
792 }
793
794 /// Change the position of the window controls. Available on macOS only.
795 ///
796 /// Requires titleBarStyle: Overlay and decorations: true.
797 ///
798 /// ## Platform-specific
799 ///
800 /// - **Linux / Windows / iOS / Android:** Unsupported.
801 #[must_use]
802 pub fn traffic_light_position(mut self, position: dpi::Position) -> Self {
803 self.traffic_light_position = Some(position);
804 self
805 }
806
807 /// Whether to show a link preview when long pressing on links. Available on macOS and iOS only.
808 ///
809 /// Default is true.
810 ///
811 /// See https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview
812 ///
813 /// ## Platform-specific
814 ///
815 /// - **Linux / Windows / Android:** Unsupported.
816 #[must_use]
817 pub fn allow_link_preview(mut self, allow_link_preview: bool) -> Self {
818 self.allow_link_preview = allow_link_preview;
819 self
820 }
821
822 /// Whether to limit navigations to App-Bound Domains. This is necessary to
823 /// enable Service Workers on iOS according to
824 /// [StackOverflow](https://stackoverflow.com/questions/49673399/service-workers-unavailable-in-wkwebview-in-ios-11-3/64155509#64155509).
825 ///
826 /// Default is false.
827 ///
828 /// Note: If you pass in `true` make sure to add localhost and any [`registrable
829 /// domains`](https://developer.mozilla.org/en-US/docs/Glossary/Registrable_domain)
830 /// used in this webview to tauri-src/Info.ios.plist:
831 ///
832 /// ```xml
833 /// <plist>
834 /// <dict>
835 /// <key>WKAppBoundDomains</key>
836 /// <array>
837 /// <string>localhost</string>
838 /// <string>aregistrabledomain.example</string>
839 /// </array>
840 /// </dict>
841 /// </plist>
842 /// ```
843 ///
844 /// You must add `localhost` if any webview with this set to true opens a
845 /// local webpage, makes any localhost calls, or uses the isolation pattern
846 /// because Tauri uses the `localhost` domain for hosting the application
847 /// webpage, the IPC protocol, and the isolation pattern's iframe.
848 ///
849 /// Requests served through custom uri schemes are allowed so long as they use
850 /// a registrable domain specified in the `WKAppBoundDomains` array for all the
851 /// requests from the app, including requests for the `localhost` domain.
852 ///
853 /// In theory, you can whitelist an entire uri scheme by including the
854 /// protocol name followed by a colon. For example, to allow all requests
855 /// using a custom "stream" uri scheme (see [this tauri
856 /// example](https://github.com/tauri-apps/tauri/blob/dev/examples/streaming/main.rs)),
857 /// you could add `stream:` to the AppBoundDomains array. That said, I'm not
858 /// sure whether Apple would let your app through app review if you do
859 /// whitelist an entire protocol because this feature is not mentioned in
860 /// [their blog post on App-Bound
861 /// Domains](https://webkit.org/blog/10882/app-bound-domains/).
862 ///
863 /// See https://webkit.org/blog/10882/app-bound-domains/ and
864 /// https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/limitsnavigationstoappbounddomains
865 /// for the official documentation on App-Bound Domains.
866 ///
867 /// ## Platform-specific
868 ///
869 /// - **iOS**: Supported since version 14.0+.
870 /// - **Linux / Windows / Android / MacOS:** Unsupported.
871 #[must_use]
872 #[allow(unused_variables, unused_mut)]
873 pub fn limit_navigations_to_app_bound_domains(mut self, limit_navigations: bool) -> Self {
874 #[cfg(target_os = "ios")]
875 {
876 self.limit_navigations_to_app_bound_domains = limit_navigations;
877 }
878 self
879 }
880
881 /// Change the default background throttling behavior.
882 ///
883 /// By default, browsers use a suspend policy that will throttle timers and even unload
884 /// the whole tab (view) to free resources after roughly 5 minutes when a view became
885 /// minimized or hidden. This will pause all tasks until the documents visibility state
886 /// changes back from hidden to visible by bringing the view back to the foreground.
887 ///
888 /// ## Platform-specific
889 ///
890 /// - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.
891 /// - **iOS**: Supported since version 17.0+.
892 /// - **macOS**: Supported since version 14.0+.
893 ///
894 /// see <https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578>
895 #[must_use]
896 pub fn background_throttling(mut self, policy: Option<BackgroundThrottlingPolicy>) -> Self {
897 self.background_throttling = policy;
898 self
899 }
900
901 /// Specifies the native scrollbar style to use with the webview.
902 /// CSS styles that modify the scrollbar are applied on top of the native appearance configured here.
903 ///
904 /// Defaults to [`ScrollBarStyle::Default`], which is the browser default.
905 ///
906 /// ## Platform-specific
907 ///
908 /// - **Windows**:
909 /// - [`ScrollBarStyle::FluentOverlay`] requires WebView2 Runtime version 125.0.2535.41 or higher,
910 /// and does nothing on older versions.
911 /// - This option must be given the same value for all webviews that target the same data directory. Use
912 /// [`WebviewAttributes::data_directory`] to change data directories if needed.
913 /// - **Linux / Android / iOS / macOS**: Unsupported. Only supports `Default` and performs no operation.
914 #[must_use]
915 pub fn scroll_bar_style(mut self, style: ScrollBarStyle) -> Self {
916 self.scroll_bar_style = style;
917 self
918 }
919
920 /// Controls the WebView's browser-level general autofill behavior.
921 ///
922 /// **This option does not disable password or credit card autofill.**
923 ///
924 /// When set to `false`, the WebView will not automatically populate
925 /// general form fields using previously stored data such as addresses
926 /// or contact information.
927 ///
928 /// By default, this is `true`.
929 ///
930 /// ## Platform-specific
931 ///
932 /// - **Windows**: Supported. WebView2's autofill feature (called
933 /// "Suggestions") may not honor `autocomplete="off"` on input
934 /// elements in some cases.
935 /// - **Linux / Android / iOS / macOS**: Unsupported and performs no
936 /// operation.
937 #[must_use]
938 pub fn general_autofill_enabled(mut self, enabled: bool) -> Self {
939 self.general_autofill_enabled = enabled;
940 self
941 }
942}
943
944/// IPC handler.
945pub type WebviewIpcHandler<T, R> = Box<dyn Fn(DetachedWebview<T, R>, Request<String>) + Send>;
946
947/// The page script that binds the DevTools keyboard shortcut - Ctrl+Shift+I, or
948/// Cmd+Alt+I on macOS - to the `webview` plugin's `internal_toggle_devtools` command.
949///
950/// It is up to each runtime to inject this into the webviews it creates, and only into
951/// the ones that have no shortcut of their own:
952///
953/// * `tauri-runtime-wry` injects it always. None of the webviews it drives - WebView2,
954/// WKWebView, WebKitGTK - binds the chord itself.
955/// * `tauri-runtime-cef` injects it only into Alloy style browsers. A Chrome style one
956/// already dispatches `IDC_DEV_TOOLS` for the same chord, and with both in place the
957/// toggle closes the window the accelerator just opened.
958///
959/// Returns the script with its one template value resolved for the target this crate
960/// was compiled for.
961#[cfg(any(debug_assertions, feature = "devtools"))]
962pub fn devtools_shortcut_script() -> String {
963 include_str!("scripts/toggle-devtools.js").replace(
964 "__TEMPLATE_is_macos__",
965 if cfg!(target_os = "macos") {
966 "true"
967 } else {
968 "false"
969 },
970 )
971}
972
973/// An initialization script
974#[derive(Debug, Clone)]
975pub struct InitializationScript {
976 /// The script to run
977 pub script: String,
978 /// Whether the script should be injected to main frame only
979 pub for_main_frame_only: bool,
980}