Skip to main content

tauri/webview/
mod.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//! The Tauri webview types and functions.
6
7pub(crate) mod plugin;
8mod webview_window;
9
10pub use webview_window::{WebviewWindow, WebviewWindowBuilder};
11
12/// Cookie crate used for [`Webview::set_cookie`] and [`Webview::delete_cookie`].
13///
14/// # Stability
15///
16/// This re-exported crate is still on an alpha release and might receive updates in minor Tauri releases.
17pub use cookie;
18use http::HeaderMap;
19use serde::Serialize;
20use tauri_macros::default_runtime;
21pub use tauri_runtime::webview::{NewWindowFeatures, PageLoadEvent, ScrollBarStyle};
22// Remove this re-export in v3
23pub use tauri_runtime::Cookie;
24#[cfg(desktop)]
25use tauri_runtime::{
26  dpi::{PhysicalPosition, PhysicalSize, Position, Size},
27  WindowDispatch,
28};
29use tauri_runtime::{
30  webview::{DetachedWebview, InitializationScript, PendingWebview, WebviewAttributes},
31  WebviewDispatch,
32};
33pub use tauri_utils::config::Color;
34use tauri_utils::config::{BackgroundThrottlingPolicy, WebviewUrl, WindowConfig};
35pub use url::Url;
36
37use crate::{
38  app::{UriSchemeResponder, WebviewEvent},
39  event::{EmitArgs, EventTarget},
40  ipc::{
41    CallbackFn, CommandArg, CommandItem, CommandScope, GlobalScope, Invoke, InvokeBody,
42    InvokeError, InvokeMessage, InvokeResolver, Origin, OwnedInvokeResponder, ScopeObject,
43  },
44  manager::AppManager,
45  path::SafePathBuf,
46  sealed::{ManagerBase, RuntimeOrDispatch},
47  AppHandle, Emitter, Event, EventId, EventLoopMessage, EventName, Listener, Manager,
48  ResourceTable, Runtime, Window,
49};
50
51use std::{
52  borrow::Cow,
53  hash::{Hash, Hasher},
54  path::{Path, PathBuf},
55  sync::{Arc, Mutex, MutexGuard},
56};
57
58pub(crate) type WebResourceRequestHandler =
59  dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
60pub(crate) type NavigationHandler = dyn Fn(&Url) -> bool + Send;
61pub(crate) type NewWindowHandler<R> = dyn Fn(Url, NewWindowFeatures) -> NewWindowResponse<R> + Send;
62pub(crate) type UriSchemeProtocolHandler =
63  Box<dyn Fn(&str, http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync>;
64pub(crate) type OnPageLoad<R> = dyn Fn(Webview<R>, PageLoadPayload<'_>) + Send + Sync + 'static;
65pub(crate) type OnDocumentTitleChanged<R> = dyn Fn(Webview<R>, String) + Send + 'static;
66pub(crate) type DownloadHandler<R> = dyn Fn(Webview<R>, DownloadEvent<'_>) -> bool + Send + Sync;
67
68#[derive(Clone, Serialize)]
69pub(crate) struct CreatedEvent {
70  pub(crate) label: String,
71}
72
73/// Download event for the [`WebviewBuilder#method.on_download`] hook.
74#[non_exhaustive]
75pub enum DownloadEvent<'a> {
76  /// Download requested.
77  Requested {
78    /// The url being downloaded.
79    url: Url,
80    /// Represents where the file will be downloaded to.
81    /// Can be used to set the download location by assigning a new path to it.
82    /// The assigned path _must_ be absolute.
83    destination: &'a mut PathBuf,
84  },
85  /// Download finished.
86  Finished {
87    /// The URL of the original download request.
88    url: Url,
89    /// Potentially representing the filesystem path the file was downloaded to.
90    ///
91    /// A value of `None` being passed instead of a `PathBuf` does not necessarily indicate that the download
92    /// did not succeed, and may instead indicate some other failure - always check the third parameter if you need to
93    /// know if the download succeeded.
94    ///
95    /// ## Platform-specific:
96    ///
97    /// - **macOS**: The second parameter indicating the path the file was saved to is always empty, due to API
98    ///   limitations.
99    path: Option<PathBuf>,
100    /// Indicates if the download succeeded or not.
101    success: bool,
102  },
103}
104
105/// The payload for the [`WebviewBuilder::on_page_load`] hook.
106#[derive(Debug, Clone)]
107pub struct PageLoadPayload<'a> {
108  pub(crate) url: &'a Url,
109  pub(crate) event: PageLoadEvent,
110}
111
112impl<'a> PageLoadPayload<'a> {
113  /// The page URL.
114  pub fn url(&self) -> &'a Url {
115    self.url
116  }
117
118  /// The page load event.
119  pub fn event(&self) -> PageLoadEvent {
120    self.event
121  }
122}
123
124/// The IPC invoke request.
125///
126/// # Stability
127///
128/// This struct is **NOT** part of the public stable API and is only meant to be used
129/// by internal code and external testing/fuzzing tools or custom invoke systems.
130#[derive(Debug)]
131pub struct InvokeRequest {
132  /// The invoke command.
133  pub cmd: String,
134  /// The success callback.
135  pub callback: CallbackFn,
136  /// The error callback.
137  pub error: CallbackFn,
138  /// URL of the frame that requested this command.
139  pub url: Url,
140  /// The body of the request.
141  pub body: InvokeBody,
142  /// The request headers.
143  pub headers: HeaderMap,
144  /// The invoke key. Must match what was passed to the app manager.
145  pub invoke_key: String,
146}
147
148/// The platform webview handle. Accessed with [`Webview#method.with_webview`];
149#[cfg(feature = "wry")]
150#[cfg_attr(docsrs, doc(cfg(feature = "wry")))]
151pub struct PlatformWebview(tauri_runtime_wry::Webview);
152
153#[cfg(feature = "wry")]
154impl PlatformWebview {
155  /// Returns [`webkit2gtk::WebView`] handle.
156  #[cfg(any(
157    target_os = "linux",
158    target_os = "dragonfly",
159    target_os = "freebsd",
160    target_os = "netbsd",
161    target_os = "openbsd"
162  ))]
163  #[cfg_attr(
164    docsrs,
165    doc(cfg(any(
166      target_os = "linux",
167      target_os = "dragonfly",
168      target_os = "freebsd",
169      target_os = "netbsd",
170      target_os = "openbsd"
171    )))
172  )]
173  pub fn inner(&self) -> webkit2gtk::WebView {
174    self.0.clone()
175  }
176
177  /// Returns the WebView2 controller.
178  #[cfg(windows)]
179  #[cfg_attr(docsrs, doc(cfg(windows)))]
180  pub fn controller(
181    &self,
182  ) -> webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Controller {
183    self.0.controller.clone()
184  }
185
186  /// Returns the WebView2 environment.
187  #[cfg(windows)]
188  #[cfg_attr(docsrs, doc(cfg(windows)))]
189  pub fn environment(
190    &self,
191  ) -> webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment {
192    self.0.environment.clone()
193  }
194
195  /// Returns the [WKWebView] handle.
196  ///
197  /// [WKWebView]: https://developer.apple.com/documentation/webkit/wkwebview
198  #[cfg(any(target_os = "macos", target_os = "ios"))]
199  #[cfg_attr(docsrs, doc(cfg(any(target_os = "macos", target_os = "ios"))))]
200  pub fn inner(&self) -> *mut std::ffi::c_void {
201    self.0.webview
202  }
203
204  /// Returns WKWebView [controller] handle.
205  ///
206  /// [controller]: https://developer.apple.com/documentation/webkit/wkusercontentcontroller
207  #[cfg(any(target_os = "macos", target_os = "ios"))]
208  #[cfg_attr(docsrs, doc(cfg(any(target_os = "macos", target_os = "ios"))))]
209  pub fn controller(&self) -> *mut std::ffi::c_void {
210    self.0.manager
211  }
212
213  /// Returns [NSWindow] associated with the WKWebView webview.
214  ///
215  /// [NSWindow]: https://developer.apple.com/documentation/appkit/nswindow
216  #[cfg(target_os = "macos")]
217  #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
218  pub fn ns_window(&self) -> *mut std::ffi::c_void {
219    self.0.ns_window
220  }
221
222  /// Returns [UIViewController] used by the WKWebView webview NSWindow.
223  ///
224  /// [UIViewController]: https://developer.apple.com/documentation/uikit/uiviewcontroller
225  #[cfg(target_os = "ios")]
226  #[cfg_attr(docsrs, doc(cfg(target_os = "ios")))]
227  pub fn view_controller(&self) -> *mut std::ffi::c_void {
228    self.0.view_controller
229  }
230
231  /// Returns handle for JNI execution.
232  #[cfg(target_os = "android")]
233  pub fn jni_handle(&self) -> tauri_runtime_wry::wry::JniHandle {
234    self.0
235  }
236}
237
238/// Response for the new window request handler.
239pub enum NewWindowResponse<R: Runtime> {
240  /// Allow the window to be opened with the default implementation.
241  Allow,
242  /// Allow the window to be opened, with the given window.
243  ///
244  /// ## Platform-specific:
245  ///
246  /// **Linux**: The webview must be related to the caller webview. See [`WebviewBuilder::with_related_view`].
247  /// **Windows**: The webview must use the same environment as the caller webview. See [`WebviewBuilder::with_environment`].
248  /// **macOS**: The webview must use the same webview configuration as the caller webview. See [`WebviewBuilder::with_webview_configuration`] and [`NewWindowFeatures::webview_configuration`].
249  Create {
250    /// Window that was created.
251    window: crate::WebviewWindow<R>,
252  },
253  /// Deny the window from being opened.
254  Deny,
255}
256
257macro_rules! unstable_struct {
258    (#[doc = $doc:expr] $($tokens:tt)*) => {
259      #[cfg(any(test, feature = "unstable"))]
260      #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
261      #[doc = $doc]
262      pub $($tokens)*
263
264      #[cfg(not(any(test, feature = "unstable")))]
265      pub(crate) $($tokens)*
266    }
267}
268
269unstable_struct!(
270  #[doc = "A builder for a webview."]
271  struct WebviewBuilder<R: Runtime> {
272    pub(crate) label: String,
273    pub(crate) webview_attributes: WebviewAttributes,
274    pub(crate) web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
275    pub(crate) navigation_handler: Option<Box<NavigationHandler>>,
276    pub(crate) new_window_handler: Option<Box<NewWindowHandler<R>>>,
277    pub(crate) on_page_load_handler: Option<Box<OnPageLoad<R>>>,
278    pub(crate) document_title_changed_handler: Option<Box<OnDocumentTitleChanged<R>>>,
279    pub(crate) download_handler: Option<Arc<DownloadHandler<R>>>,
280  }
281);
282
283#[cfg_attr(not(feature = "unstable"), allow(dead_code))]
284impl<R: Runtime> WebviewBuilder<R> {
285  /// Initializes a webview builder with the given webview label and URL to load.
286  ///
287  /// # Known issues
288  ///
289  /// On Windows, this function deadlocks when used in a synchronous command or event handlers, see [the Webview2 issue].
290  /// You should use `async` commands and separate threads when creating webviews.
291  ///
292  /// # Examples
293  ///
294  /// - Create a webview in the setup hook:
295  ///
296  #[cfg_attr(
297    feature = "unstable",
298    doc = r####"
299```
300tauri::Builder::default()
301  .setup(|app| {
302    let window = tauri::window::WindowBuilder::new(app, "label").build()?;
303    let webview_builder = tauri::webview::WebviewBuilder::new("label", tauri::WebviewUrl::App("index.html".into()));
304    let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap());
305    Ok(())
306  });
307```
308  "####
309  )]
310  ///
311  /// - Create a webview in a separate thread:
312  ///
313  #[cfg_attr(
314    feature = "unstable",
315    doc = r####"
316```
317tauri::Builder::default()
318  .setup(|app| {
319    let handle = app.handle().clone();
320    std::thread::spawn(move || {
321      let window = tauri::window::WindowBuilder::new(&handle, "label").build().unwrap();
322      let webview_builder = tauri::webview::WebviewBuilder::new("label", tauri::WebviewUrl::App("index.html".into()));
323      window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap());
324    });
325    Ok(())
326  });
327```
328   "####
329  )]
330  ///
331  /// - Create a webview in a command:
332  ///
333  #[cfg_attr(
334    feature = "unstable",
335    doc = r####"
336```
337#[tauri::command]
338async fn create_window(app: tauri::AppHandle) {
339  let window = tauri::window::WindowBuilder::new(&app, "label").build().unwrap();
340  let webview_builder = tauri::webview::WebviewBuilder::new("label", tauri::WebviewUrl::External("https://tauri.app/".parse().unwrap()));
341  window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap());
342}
343```
344  "####
345  )]
346  ///
347  /// [the Webview2 issue]: https://github.com/tauri-apps/wry/issues/583
348  pub fn new<L: Into<String>>(label: L, url: WebviewUrl) -> Self {
349    Self {
350      label: label.into(),
351      webview_attributes: WebviewAttributes::new(url),
352      web_resource_request_handler: None,
353      navigation_handler: None,
354      new_window_handler: None,
355      on_page_load_handler: None,
356      document_title_changed_handler: None,
357      download_handler: None,
358    }
359  }
360
361  /// Initializes a webview builder from a [`WindowConfig`] from tauri.conf.json.
362  /// Keep in mind that you can't create 2 webviews with the same `label` so make sure
363  /// that the initial webview was closed or change the label of the new [`WebviewBuilder`].
364  ///
365  /// # Known issues
366  ///
367  /// On Windows, this function deadlocks when used in a synchronous command or event handlers, see [the Webview2 issue].
368  /// You should use `async` commands and separate threads when creating webviews.
369  ///
370  /// # Examples
371  ///
372  /// - Create a webview in a command:
373  ///
374  #[cfg_attr(
375    feature = "unstable",
376    doc = r####"
377```
378#[tauri::command]
379async fn create_window(app: tauri::AppHandle) {
380  let window = tauri::window::WindowBuilder::new(&app, "label").build().unwrap();
381  let webview_builder = tauri::webview::WebviewBuilder::from_config(&app.config().app.windows.get(0).unwrap().clone());
382  window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap());
383}
384```
385  "####
386  )]
387  ///
388  /// [the Webview2 issue]: https://github.com/tauri-apps/wry/issues/583
389  pub fn from_config(config: &WindowConfig) -> Self {
390    let mut config = config.to_owned();
391
392    if let Some(data_directory) = &config.data_directory {
393      let resolve_data_dir_res = dirs::data_local_dir()
394        .or({
395          #[cfg(feature = "tracing")]
396          tracing::error!("failed to resolve data directory");
397          None
398        })
399        .and_then(|local_dir| {
400          SafePathBuf::new(data_directory.clone())
401            .inspect_err(|_err| {
402              #[cfg(feature = "tracing")]
403              tracing::error!(
404                "data_directory `{}` is not a safe path, ignoring config. Validation error was: {_err}",
405                data_directory.display()
406              );
407            })
408            .map(|p| (local_dir, p))
409            .ok()
410        })
411        .and_then(|(local_dir, data_directory)| {
412          if data_directory.as_ref().is_relative() {
413            Some(local_dir.join(&config.label).join(data_directory.as_ref()))
414            } else {
415              #[cfg(feature = "tracing")]
416              tracing::error!(
417                "data_directory `{}` is not a relative path, ignoring config.",
418                data_directory.display()
419              );
420              None
421            }
422        });
423      if let Some(resolved_data_directory) = resolve_data_dir_res {
424        config.data_directory = Some(resolved_data_directory);
425      }
426    }
427
428    Self {
429      label: config.label.clone(),
430      webview_attributes: WebviewAttributes::from(&config),
431      web_resource_request_handler: None,
432      navigation_handler: None,
433      new_window_handler: None,
434      on_page_load_handler: None,
435      document_title_changed_handler: None,
436      download_handler: None,
437    }
438  }
439
440  /// Defines a closure to be executed when the webview makes an HTTP request for a web resource, allowing you to modify the response.
441  ///
442  /// Currently only implemented for the `tauri` URI protocol.
443  ///
444  /// **NOTE:** Currently this is **not** executed when using external URLs such as a development server,
445  /// but it might be implemented in the future. **Always** check the request URL.
446  ///
447  /// # Examples
448  ///
449  #[cfg_attr(
450    feature = "unstable",
451    doc = r####"
452```rust,no_run
453use tauri::{
454  utils::config::{Csp, CspDirectiveSources, WebviewUrl},
455  window::WindowBuilder,
456  webview::WebviewBuilder,
457};
458use http::header::HeaderValue;
459use std::collections::HashMap;
460tauri::Builder::default()
461  .setup(|app| {
462    let window = tauri::window::WindowBuilder::new(app, "label").build()?;
463
464    let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
465      .on_web_resource_request(|request, response| {
466        if request.uri().scheme_str() == Some("tauri") {
467          // if we have a CSP header, Tauri is loading an HTML file
468          //  for this example, let's dynamically change the CSP
469          if let Some(csp) = response.headers_mut().get_mut("Content-Security-Policy") {
470            // use the tauri helper to parse the CSP policy to a map
471            let mut csp_map: HashMap<String, CspDirectiveSources> = Csp::Policy(csp.to_str().unwrap().to_string()).into();
472            csp_map.entry("script-src".to_string()).or_insert_with(Default::default).push("'unsafe-inline'");
473            // use the tauri helper to get a CSP string from the map
474            let csp_string = Csp::from(csp_map).to_string();
475            *csp = HeaderValue::from_str(&csp_string).unwrap();
476          }
477        }
478      });
479
480    let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
481
482    Ok(())
483  });
484```
485  "####
486  )]
487  pub fn on_web_resource_request<
488    F: Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync + 'static,
489  >(
490    mut self,
491    f: F,
492  ) -> Self {
493    self.web_resource_request_handler.replace(Box::new(f));
494    self
495  }
496
497  /// Defines a closure to be executed when the webview navigates to a URL. Returning `false` cancels the navigation.
498  ///
499  /// # Examples
500  ///
501  #[cfg_attr(
502    feature = "unstable",
503    doc = r####"
504```rust,no_run
505use tauri::{
506  utils::config::{Csp, CspDirectiveSources, WebviewUrl},
507  window::WindowBuilder,
508  webview::WebviewBuilder,
509};
510use http::header::HeaderValue;
511use std::collections::HashMap;
512tauri::Builder::default()
513  .setup(|app| {
514    let window = tauri::window::WindowBuilder::new(app, "label").build()?;
515
516    let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
517      .on_navigation(|url| {
518        // allow the production URL or localhost on dev
519        url.scheme() == "tauri" || (cfg!(dev) && url.host_str() == Some("localhost"))
520      });
521
522    let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
523    Ok(())
524  });
525```
526  "####
527  )]
528  pub fn on_navigation<F: Fn(&Url) -> bool + Send + 'static>(mut self, f: F) -> Self {
529    self.navigation_handler.replace(Box::new(f));
530    self
531  }
532
533  /// Set a new window request handler to decide if incoming url is allowed to be opened.
534  ///
535  /// A new window is requested to be opened by the [window.open] API.
536  ///
537  /// The closure take the URL to open and the window features object and returns [`NewWindowResponse`] to determine whether the window should open.
538  ///
539  #[cfg_attr(
540    feature = "unstable",
541    doc = r####"
542```rust,no_run
543use tauri::{
544  utils::config::{Csp, CspDirectiveSources, WebviewUrl},
545  window::WindowBuilder,
546  webview::WebviewBuilder,
547};
548use http::header::HeaderValue;
549use std::collections::HashMap;
550tauri::Builder::default()
551  .setup(|app| {
552    let window = tauri::window::WindowBuilder::new(app, "label").build()?;
553
554    let app_ = app.handle().clone();
555    let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
556      .on_new_window(move |url, features| {
557        let builder = tauri::WebviewWindowBuilder::new(
558          &app_,
559          // note: add an ID counter or random label generator to support multiple opened windows at the same time
560          "opened-window",
561          tauri::WebviewUrl::External("about:blank".parse().unwrap()),
562        )
563        .window_features(features)
564        .on_document_title_changed(|window, title| {
565          window.set_title(&title).unwrap();
566        })
567        .title(url.as_str());
568
569        let window = builder.build().unwrap();
570        tauri::webview::NewWindowResponse::Create { window }
571      });
572
573    let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
574    Ok(())
575  });
576```
577  "####
578  )]
579  ///
580  /// # Platform-specific
581  ///
582  /// - **Android / iOS**: Not supported.
583  ///
584  /// [window.open]: https://developer.mozilla.org/en-US/docs/Web/API/Window/open
585  pub fn on_new_window<F: Fn(Url, NewWindowFeatures) -> NewWindowResponse<R> + Send + 'static>(
586    mut self,
587    f: F,
588  ) -> Self {
589    self.new_window_handler.replace(Box::new(f));
590    self
591  }
592
593  /// Defines a closure to be executed when document title change.
594  pub fn on_document_title_changed<F: Fn(Webview<R>, String) + Send + 'static>(
595    mut self,
596    f: F,
597  ) -> Self {
598    self.document_title_changed_handler.replace(Box::new(f));
599    self
600  }
601
602  /// Set a download event handler to be notified when a download is requested or finished.
603  ///
604  /// Returning `false` prevents the download from happening on a [`DownloadEvent::Requested`] event.
605  ///
606  /// # Examples
607  ///
608  #[cfg_attr(
609    feature = "unstable",
610    doc = r####"
611```rust,no_run
612use tauri::{
613  utils::config::{Csp, CspDirectiveSources, WebviewUrl},
614  window::WindowBuilder,
615  webview::{DownloadEvent, WebviewBuilder},
616};
617
618tauri::Builder::default()
619  .setup(|app| {
620    let window = WindowBuilder::new(app, "label").build()?;
621    let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
622      .on_download(|webview, event| {
623        match event {
624          DownloadEvent::Requested { url, destination } => {
625            println!("downloading {}", url);
626            *destination = "/home/tauri/target/path".into();
627          }
628          DownloadEvent::Finished { url, path, success } => {
629            println!("downloaded {} to {:?}, success: {}", url, path, success);
630          }
631          _ => (),
632        }
633        // let the download start
634        true
635      });
636
637    let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
638    Ok(())
639  });
640```
641  "####
642  )]
643  pub fn on_download<F: Fn(Webview<R>, DownloadEvent<'_>) -> bool + Send + Sync + 'static>(
644    mut self,
645    f: F,
646  ) -> Self {
647    self.download_handler.replace(Arc::new(f));
648    self
649  }
650
651  /// Defines a closure to be executed when a page load event is triggered.
652  /// The event can be either [`PageLoadEvent::Started`] if the page has started loading
653  /// or [`PageLoadEvent::Finished`] when the page finishes loading.
654  ///
655  /// # Examples
656  ///
657  #[cfg_attr(
658    feature = "unstable",
659    doc = r####"
660```rust,no_run
661use tauri::{
662  utils::config::{Csp, CspDirectiveSources, WebviewUrl},
663  window::WindowBuilder,
664  webview::{PageLoadEvent, WebviewBuilder},
665};
666use http::header::HeaderValue;
667use std::collections::HashMap;
668tauri::Builder::default()
669  .setup(|app| {
670    let window = tauri::window::WindowBuilder::new(app, "label").build()?;
671    let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
672      .on_page_load(|webview, payload| {
673        match payload.event() {
674          PageLoadEvent::Started => {
675            println!("{} finished loading", payload.url());
676          }
677          PageLoadEvent::Finished => {
678            println!("{} finished loading", payload.url());
679          }
680        }
681      });
682    let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
683    Ok(())
684  });
685```
686  "####
687  )]
688  pub fn on_page_load<F: Fn(Webview<R>, PageLoadPayload<'_>) + Send + Sync + 'static>(
689    mut self,
690    f: F,
691  ) -> Self {
692    self.on_page_load_handler.replace(Box::new(f));
693    self
694  }
695
696  pub(crate) fn into_pending_webview<M: Manager<R>>(
697    mut self,
698    manager: &M,
699    window_label: &str,
700  ) -> crate::Result<PendingWebview<EventLoopMessage, R>> {
701    let mut pending = PendingWebview::new(self.webview_attributes, self.label.clone())?;
702    pending.navigation_handler = self.navigation_handler.take();
703    pending.new_window_handler = self.new_window_handler.take().map(|handler| {
704      Box::new(
705        move |url, features: NewWindowFeatures| match handler(url, features) {
706          NewWindowResponse::Allow => tauri_runtime::webview::NewWindowResponse::Allow,
707          #[cfg(mobile)]
708          NewWindowResponse::Create { window: _ } => {
709            tauri_runtime::webview::NewWindowResponse::Allow
710          }
711          #[cfg(desktop)]
712          NewWindowResponse::Create { window } => {
713            tauri_runtime::webview::NewWindowResponse::Create {
714              window_id: window.window.window.id,
715            }
716          }
717          NewWindowResponse::Deny => tauri_runtime::webview::NewWindowResponse::Deny,
718        },
719      )
720        as Box<
721          dyn Fn(Url, NewWindowFeatures) -> tauri_runtime::webview::NewWindowResponse
722            + Send
723            + 'static,
724        >
725    });
726
727    if let Some(document_title_changed_handler) = self.document_title_changed_handler.take() {
728      let label = pending.label.clone();
729      let manager = manager.manager_owned();
730      pending
731        .document_title_changed_handler
732        .replace(Box::new(move |title| {
733          if let Some(w) = manager.get_webview(&label) {
734            document_title_changed_handler(w, title);
735          }
736        }));
737    }
738    pending.web_resource_request_handler = self.web_resource_request_handler.take();
739
740    if let Some(download_handler) = self.download_handler.take() {
741      let label = pending.label.clone();
742      let manager = manager.manager_owned();
743      pending.download_handler.replace(Arc::new(move |event| {
744        if let Some(w) = manager.get_webview(&label) {
745          download_handler(
746            w,
747            match event {
748              tauri_runtime::webview::DownloadEvent::Requested { url, destination } => {
749                DownloadEvent::Requested { url, destination }
750              }
751              tauri_runtime::webview::DownloadEvent::Finished { url, path, success } => {
752                DownloadEvent::Finished { url, path, success }
753              }
754            },
755          )
756        } else {
757          false
758        }
759      }));
760    }
761
762    let label_ = pending.label.clone();
763    let manager_ = manager.manager_owned();
764    pending
765      .on_page_load_handler
766      .replace(Box::new(move |url, event| {
767        if let Some(w) = manager_.get_webview(&label_) {
768          if let Some(handler) = self.on_page_load_handler.as_ref() {
769            handler(w, PageLoadPayload { url: &url, event });
770          }
771        }
772      }));
773
774    manager
775      .manager()
776      .webview
777      .prepare_webview(manager, pending, window_label)
778  }
779
780  /// Creates a new webview on the given window.
781  #[cfg(desktop)]
782  pub(crate) fn build(
783    self,
784    window: Window<R>,
785    position: Position,
786    size: Size,
787  ) -> crate::Result<Webview<R>> {
788    let app_manager = window.manager();
789
790    let mut pending = self.into_pending_webview(&window, window.label())?;
791
792    pending.webview_attributes.bounds = Some(tauri_runtime::dpi::Rect { size, position });
793
794    let use_https_scheme = pending.webview_attributes.use_https_scheme;
795
796    let webview = match &mut window.runtime() {
797      RuntimeOrDispatch::Dispatch(dispatcher) => dispatcher.create_webview(pending),
798      _ => unimplemented!(),
799    }
800    .map(|webview| {
801      app_manager
802        .webview
803        .attach_webview(window.clone(), webview, use_https_scheme)
804    })?;
805
806    Ok(webview)
807  }
808}
809
810/// Webview attributes.
811impl<R: Runtime> WebviewBuilder<R> {
812  /// Sets whether clicking an inactive window also clicks through to the webview.
813  #[must_use]
814  pub fn accept_first_mouse(mut self, accept: bool) -> Self {
815    self.webview_attributes.accept_first_mouse = accept;
816    self
817  }
818
819  /// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created,
820  /// but before the HTML document has been parsed and before any other script included by the HTML document is run.
821  ///
822  /// Since it runs on all top-level document navigations,
823  /// it's recommended to check the `window.location` to guard your script from running on unexpected origins.
824  ///
825  /// This is executed only on the main frame.
826  /// If you only want to run it in all frames, use [`Self::initialization_script_for_all_frames`] instead.
827  ///
828  /// ## Platform-specific
829  ///
830  /// - **Windows:** scripts are always added to subframes.
831  /// - **Android:** When [addDocumentStartJavaScript] is not supported,
832  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).
833  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.
834  ///
835  /// # Examples
836  ///
837  #[cfg_attr(
838    feature = "unstable",
839    doc = r####"
840```rust
841use tauri::{WindowBuilder, Runtime};
842
843const INIT_SCRIPT: &str = r#"
844  if (window.location.origin === 'https://tauri.app') {
845    console.log("hello world from js init script");
846
847    window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };
848  }
849"#;
850
851fn main() {
852  tauri::Builder::default()
853    .setup(|app| {
854      let window = tauri::window::WindowBuilder::new(app, "label").build()?;
855      let webview_builder = tauri::webview::WebviewBuilder::new("label", tauri::WebviewUrl::App("index.html".into()))
856        .initialization_script(INIT_SCRIPT);
857      let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
858      Ok(())
859    });
860}
861```
862  "####
863  )]
864  ///
865  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)
866  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)
867  #[must_use]
868  pub fn initialization_script(mut self, script: impl Into<String>) -> Self {
869    self
870      .webview_attributes
871      .initialization_scripts
872      .push(InitializationScript {
873        script: script.into(),
874        for_main_frame_only: true,
875      });
876    self
877  }
878
879  /// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created,
880  /// but before the HTML document has been parsed and before any other script included by the HTML document is run.
881  ///
882  /// Since it runs on all top-level document navigations and also child frame page navigations,
883  /// it's recommended to check the `window.location` to guard your script from running on unexpected origins.
884  ///
885  /// This is executed on all frames (main frame and also sub frames).
886  /// If you only want to run the script in the main frame, use [`Self::initialization_script`] instead.
887  ///
888  /// ## Platform-specific
889  ///
890  /// - **Android:** When [addDocumentStartJavaScript] is not supported,
891  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).
892  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.
893  ///
894  /// # Examples
895  ///
896  #[cfg_attr(
897    feature = "unstable",
898    doc = r####"
899```rust
900use tauri::{WindowBuilder, Runtime};
901
902const INIT_SCRIPT: &str = r#"
903  if (window.location.origin === 'https://tauri.app') {
904    console.log("hello world from js init script");
905
906    window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };
907  }
908"#;
909
910fn main() {
911  tauri::Builder::default()
912    .setup(|app| {
913      let window = tauri::window::WindowBuilder::new(app, "label").build()?;
914      let webview_builder = tauri::webview::WebviewBuilder::new("label", tauri::WebviewUrl::App("index.html".into()))
915        .initialization_script_for_all_frames(INIT_SCRIPT);
916      let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
917      Ok(())
918    });
919}
920```
921  "####
922  )]
923  ///
924  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)
925  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)
926  #[must_use]
927  pub fn initialization_script_for_all_frames(mut self, script: impl Into<String>) -> Self {
928    self
929      .webview_attributes
930      .initialization_scripts
931      .push(InitializationScript {
932        script: script.into(),
933        for_main_frame_only: false,
934      });
935    self
936  }
937
938  /// Set the user agent for the webview
939  #[must_use]
940  pub fn user_agent(mut self, user_agent: &str) -> Self {
941    self.webview_attributes.user_agent = Some(user_agent.to_string());
942    self
943  }
944
945  /// Set additional arguments for the webview.
946  ///
947  /// ## Platform-specific
948  ///
949  /// - **macOS / Linux / Android / iOS**: Unsupported.
950  ///
951  /// ## Warning
952  ///
953  /// By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection`
954  /// so if you use this method, you also need to disable these components by yourself if you want.
955  #[must_use]
956  pub fn additional_browser_args(mut self, additional_args: &str) -> Self {
957    self.webview_attributes.additional_browser_args = Some(additional_args.to_string());
958    self
959  }
960
961  /// Data directory for the webview.
962  #[must_use]
963  pub fn data_directory(mut self, data_directory: PathBuf) -> Self {
964    self
965      .webview_attributes
966      .data_directory
967      .replace(data_directory);
968    self
969  }
970
971  /// Disables the drag and drop handler. This is required to use HTML5 drag and drop APIs on the frontend on Windows.
972  #[must_use]
973  pub fn disable_drag_drop_handler(mut self) -> Self {
974    self.webview_attributes.drag_drop_handler_enabled = false;
975    self
976  }
977
978  /// Enables clipboard access for the page rendered on **Linux** and **Windows**.
979  ///
980  /// **macOS** doesn't provide such method and is always enabled by default,
981  /// but you still need to add menu item accelerators to use shortcuts.
982  #[must_use]
983  pub fn enable_clipboard_access(mut self) -> Self {
984    self.webview_attributes.clipboard = true;
985    self
986  }
987
988  /// Enable or disable incognito mode for the WebView.
989  ///
990  ///  ## Platform-specific:
991  ///
992  ///  - **Windows**: Requires WebView2 Runtime version 101.0.1210.39 or higher, does nothing on older versions,
993  ///    see https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/archive?tabs=dotnetcsharp#10121039
994  ///  - **Android**: Unsupported.
995  ///  - **macOS / iOS**: Uses the nonPersistent DataStore
996  #[must_use]
997  pub fn incognito(mut self, incognito: bool) -> Self {
998    self.webview_attributes.incognito = incognito;
999    self
1000  }
1001
1002  /// Set a proxy URL for the WebView for all network requests.
1003  ///
1004  /// Must be either a `http://` or a `socks5://` URL.
1005  ///
1006  /// ## Platform-specific
1007  ///
1008  /// - **macOS**: Requires the `macos-proxy` feature flag and only compiles for macOS 14+.
1009  #[must_use]
1010  pub fn proxy_url(mut self, url: Url) -> Self {
1011    self.webview_attributes.proxy_url = Some(url);
1012    self
1013  }
1014
1015  /// Enable or disable transparency for the WebView.
1016  #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
1017  #[cfg_attr(
1018    docsrs,
1019    doc(cfg(any(not(target_os = "macos"), feature = "macos-private-api")))
1020  )]
1021  #[must_use]
1022  pub fn transparent(mut self, transparent: bool) -> Self {
1023    self.webview_attributes.transparent = transparent;
1024    self
1025  }
1026
1027  /// Whether the webview should be focused or not.
1028  #[must_use]
1029  pub fn focused(mut self, focus: bool) -> Self {
1030    self.webview_attributes.focus = focus;
1031    self
1032  }
1033
1034  /// Sets the webview to automatically grow and shrink its size and position when the parent window resizes.
1035  #[must_use]
1036  pub fn auto_resize(mut self) -> Self {
1037    self.webview_attributes.auto_resize = true;
1038    self
1039  }
1040
1041  /// Whether page zooming by hotkeys and mousewheel should be enabled or not.
1042  ///
1043  /// ## Platform-specific:
1044  ///
1045  /// - **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.
1046  /// - **MacOS / Linux**: Injects a polyfill that zooms in and out with `Ctrl/Cmd + [- = +]` hotkeys or mousewheel events,
1047  ///   20% in each step, ranging from 20% to 1000%. Requires `core:webview:allow-set-webview-zoom` permission
1048  ///
1049  /// - **Android / iOS**: Unsupported.
1050  #[must_use]
1051  pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {
1052    self.webview_attributes.zoom_hotkeys_enabled = enabled;
1053    self
1054  }
1055
1056  /// Whether browser extensions can be installed for the webview process
1057  ///
1058  /// ## Platform-specific:
1059  ///
1060  /// - **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)
1061  /// - **MacOS / Linux / iOS / Android** - Unsupported.
1062  #[must_use]
1063  pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self {
1064    self.webview_attributes.browser_extensions_enabled = enabled;
1065    self
1066  }
1067
1068  /// Set the path from which to load extensions from. Extensions stored in this path should be unpacked Chrome extensions on Windows, and compiled `.so` extensions on Linux.
1069  ///
1070  /// ## Platform-specific:
1071  ///
1072  /// - **Windows**: Browser extensions must first be enabled. See [`browser_extensions_enabled`](Self::browser_extensions_enabled)
1073  /// - **MacOS / iOS / Android** - Unsupported.
1074  #[must_use]
1075  pub fn extensions_path(mut self, path: impl AsRef<Path>) -> Self {
1076    self.webview_attributes.extensions_path = Some(path.as_ref().to_path_buf());
1077    self
1078  }
1079
1080  /// Initialize the WebView with a custom data store identifier.
1081  /// Can be used as a replacement for data_directory not being available in WKWebView.
1082  ///
1083  /// - **macOS / iOS**: Available on macOS >= 14 and iOS >= 17
1084  /// - **Windows / Linux / Android**: Unsupported.
1085  ///
1086  /// Note: Enable incognito mode to use the `nonPersistent` DataStore.
1087  #[must_use]
1088  pub fn data_store_identifier(mut self, data_store_identifier: [u8; 16]) -> Self {
1089    self.webview_attributes.data_store_identifier = Some(data_store_identifier);
1090    self
1091  }
1092
1093  /// Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.
1094  ///
1095  /// ## Note
1096  ///
1097  /// 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.
1098  ///
1099  /// ## Warning
1100  ///
1101  /// 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.
1102  #[must_use]
1103  pub fn use_https_scheme(mut self, enabled: bool) -> Self {
1104    self.webview_attributes.use_https_scheme = enabled;
1105    self
1106  }
1107
1108  /// Whether web inspector, which is usually called browser devtools, is enabled or not. Enabled by default.
1109  ///
1110  /// This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.
1111  ///
1112  /// ## Platform-specific
1113  ///
1114  /// - macOS: This will call private functions on **macOS**
1115  /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.
1116  /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.
1117  #[must_use]
1118  pub fn devtools(mut self, enabled: bool) -> Self {
1119    self.webview_attributes.devtools.replace(enabled);
1120    self
1121  }
1122
1123  /// Set the webview background color.
1124  ///
1125  /// ## Platform-specific:
1126  ///
1127  /// - **macOS / iOS**: Not implemented.
1128  /// - **Windows**: On Windows 7, alpha channel is ignored.
1129  /// - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored.
1130  #[must_use]
1131  pub fn background_color(mut self, color: Color) -> Self {
1132    self.webview_attributes.background_color = Some(color);
1133    self
1134  }
1135
1136  /// Change the default background throttling behaviour.
1137  ///
1138  /// By default, browsers use a suspend policy that will throttle timers and even unload
1139  /// the whole tab (view) to free resources after roughly 5 minutes when a view became
1140  /// minimized or hidden. This will pause all tasks until the documents visibility state
1141  /// changes back from hidden to visible by bringing the view back to the foreground.
1142  ///
1143  /// ## Platform-specific
1144  ///
1145  /// - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.
1146  /// - **iOS**: Supported since version 17.0+.
1147  /// - **macOS**: Supported since version 14.0+.
1148  ///
1149  /// see <https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578>
1150  #[must_use]
1151  pub fn background_throttling(mut self, policy: BackgroundThrottlingPolicy) -> Self {
1152    self.webview_attributes.background_throttling = Some(policy);
1153    self
1154  }
1155
1156  /// Whether JavaScript should be disabled.
1157  #[must_use]
1158  pub fn disable_javascript(mut self) -> Self {
1159    self.webview_attributes.javascript_disabled = true;
1160    self
1161  }
1162
1163  /// Specifies the native scrollbar style to use with the webview.
1164  /// CSS styles that modify the scrollbar are applied on top of the native appearance configured here.
1165  ///
1166  /// Defaults to [`ScrollBarStyle::Default`], which is the browser default.
1167  ///
1168  /// ## Platform-specific
1169  ///
1170  /// - **Windows**:
1171  ///   - [`ScrollBarStyle::FluentOverlay`] requires WebView2 Runtime version 125.0.2535.41 or higher,
1172  ///     and does nothing on older versions.
1173  ///   - This option must be given the same value for all webviews that target the same data directory. Use
1174  ///     [`WebviewBuilder::data_directory`] to change data directories if needed.
1175  /// - **Linux / Android / iOS / macOS**: Unsupported. Only supports `Default` and performs no operation.
1176  #[must_use]
1177  pub fn scroll_bar_style(mut self, style: ScrollBarStyle) -> Self {
1178    self.webview_attributes = self.webview_attributes.scroll_bar_style(style);
1179    self
1180  }
1181
1182  /// Controls the WebView's browser-level general autofill behavior.
1183  ///
1184  /// **This option does not disable password or credit card autofill.**
1185  ///
1186  /// When set to `false`, the WebView will not automatically populate
1187  /// general form fields using previously stored data such as addresses
1188  /// or contact information.
1189  ///
1190  /// By default, this is `true`.
1191  ///
1192  /// ## Platform-specific
1193  ///
1194  /// - **Windows**: Supported. WebView2's autofill feature (called
1195  ///   "Suggestions") may not honor `autocomplete="off"` on input
1196  ///   elements in some cases.
1197  /// - **Linux / Android / iOS / macOS**: Unsupported and performs no
1198  ///   operation.
1199  #[must_use]
1200  pub fn general_autofill_enabled(mut self, enabled: bool) -> Self {
1201    self.webview_attributes = self.webview_attributes.general_autofill_enabled(enabled);
1202    self
1203  }
1204
1205  /// Whether to show a link preview when long pressing on links. Available on macOS and iOS only.
1206  ///
1207  /// Default is true.
1208  ///
1209  /// See https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview
1210  ///
1211  /// ## Platform-specific
1212  ///
1213  /// - **Linux / Windows / Android:** Unsupported.
1214  #[cfg(target_os = "macos")]
1215  #[must_use]
1216  pub fn allow_link_preview(mut self, allow_link_preview: bool) -> Self {
1217    self.webview_attributes = self
1218      .webview_attributes
1219      .allow_link_preview(allow_link_preview);
1220    self
1221  }
1222
1223  /// Allows overriding the keyboard accessory view on iOS.
1224  /// Returning `None` effectively removes the view.
1225  ///
1226  /// The closure parameter is the webview instance.
1227  ///
1228  /// The accessory view is the view that appears above the keyboard when a text input element is focused.
1229  /// It usually displays a view with "Done", "Next" buttons.
1230  ///
1231  /// # Stability
1232  ///
1233  /// This relies on [`objc2_ui_kit`] which does not provide a stable API yet, so it can receive breaking changes in minor releases.
1234  #[cfg(target_os = "ios")]
1235  pub fn with_input_accessory_view_builder<
1236    F: Fn(&objc2_ui_kit::UIView) -> Option<objc2::rc::Retained<objc2_ui_kit::UIView>>
1237      + Send
1238      + Sync
1239      + 'static,
1240  >(
1241    mut self,
1242    builder: F,
1243  ) -> Self {
1244    self
1245      .webview_attributes
1246      .input_accessory_view_builder
1247      .replace(tauri_runtime::webview::InputAccessoryViewBuilder::new(
1248        Box::new(builder),
1249      ));
1250    self
1251  }
1252
1253  /// Set the environment for the webview.
1254  /// Useful if you need to share the same environment, for instance when using the [`Self::on_new_window`].
1255  #[cfg(all(feature = "wry", windows))]
1256  pub fn with_environment(
1257    mut self,
1258    environment: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment,
1259  ) -> Self {
1260    self.webview_attributes.environment.replace(environment);
1261    self
1262  }
1263
1264  /// Creates a new webview sharing the same web process with the provided webview.
1265  /// Useful if you need to link a webview to another, for instance when using the [`Self::on_new_window`].
1266  #[cfg(all(
1267    feature = "wry",
1268    any(
1269      target_os = "linux",
1270      target_os = "dragonfly",
1271      target_os = "freebsd",
1272      target_os = "netbsd",
1273      target_os = "openbsd",
1274    )
1275  ))]
1276  pub fn with_related_view(mut self, related_view: webkit2gtk::WebView) -> Self {
1277    self.webview_attributes.related_view.replace(related_view);
1278    self
1279  }
1280
1281  /// Set the webview configuration.
1282  /// Useful if you need to share the use a predefined webview configuration, for instance when using the [`Self::on_new_window`].
1283  #[cfg(target_os = "macos")]
1284  pub fn with_webview_configuration(
1285    mut self,
1286    webview_configuration: objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>,
1287  ) -> Self {
1288    self
1289      .webview_attributes
1290      .webview_configuration
1291      .replace(webview_configuration);
1292    self
1293  }
1294}
1295
1296/// Webview.
1297#[default_runtime(crate::Wry, wry)]
1298pub struct Webview<R: Runtime> {
1299  pub(crate) window: Arc<Mutex<Window<R>>>,
1300  /// The webview created by the runtime.
1301  pub(crate) webview: DetachedWebview<EventLoopMessage, R>,
1302  /// The manager to associate this webview with.
1303  pub(crate) manager: Arc<AppManager<R>>,
1304  pub(crate) app_handle: AppHandle<R>,
1305  pub(crate) resources_table: Arc<Mutex<ResourceTable>>,
1306  use_https_scheme: bool,
1307}
1308
1309impl<R: Runtime> std::fmt::Debug for Webview<R> {
1310  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1311    f.debug_struct("Window")
1312      .field("window", &self.window.lock().unwrap())
1313      .field("webview", &self.webview)
1314      .field("use_https_scheme", &self.use_https_scheme)
1315      .finish()
1316  }
1317}
1318
1319impl<R: Runtime> Clone for Webview<R> {
1320  fn clone(&self) -> Self {
1321    Self {
1322      window: self.window.clone(),
1323      webview: self.webview.clone(),
1324      manager: self.manager.clone(),
1325      app_handle: self.app_handle.clone(),
1326      resources_table: self.resources_table.clone(),
1327      use_https_scheme: self.use_https_scheme,
1328    }
1329  }
1330}
1331
1332impl<R: Runtime> Hash for Webview<R> {
1333  /// Only use the [`Webview`]'s label to represent its hash.
1334  fn hash<H: Hasher>(&self, state: &mut H) {
1335    self.webview.label.hash(state)
1336  }
1337}
1338
1339impl<R: Runtime> Eq for Webview<R> {}
1340impl<R: Runtime> PartialEq for Webview<R> {
1341  /// Only use the [`Webview`]'s label to compare equality.
1342  fn eq(&self, other: &Self) -> bool {
1343    self.webview.label.eq(&other.webview.label)
1344  }
1345}
1346
1347/// Base webview functions.
1348impl<R: Runtime> Webview<R> {
1349  /// Create a new webview that is attached to the window.
1350  pub(crate) fn new(
1351    window: Window<R>,
1352    webview: DetachedWebview<EventLoopMessage, R>,
1353    use_https_scheme: bool,
1354  ) -> Self {
1355    Self {
1356      manager: window.manager.clone(),
1357      app_handle: window.app_handle.clone(),
1358      window: Arc::new(Mutex::new(window)),
1359      webview,
1360      resources_table: Default::default(),
1361      use_https_scheme,
1362    }
1363  }
1364
1365  /// Initializes a webview builder with the given window label and URL to load on the webview.
1366  ///
1367  /// Data URLs are only supported with the `webview-data-url` feature flag.
1368  #[cfg(feature = "unstable")]
1369  #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
1370  pub fn builder<L: Into<String>>(label: L, url: WebviewUrl) -> WebviewBuilder<R> {
1371    WebviewBuilder::new(label.into(), url)
1372  }
1373
1374  /// Runs the given closure on the main thread.
1375  pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {
1376    self
1377      .webview
1378      .dispatcher
1379      .run_on_main_thread(f)
1380      .map_err(Into::into)
1381  }
1382
1383  /// The webview label.
1384  pub fn label(&self) -> &str {
1385    &self.webview.label
1386  }
1387
1388  /// Whether the webview was configured to use the HTTPS scheme or not.
1389  pub(crate) fn use_https_scheme(&self) -> bool {
1390    self.use_https_scheme
1391  }
1392
1393  /// Registers a webview event listener.
1394  pub fn on_webview_event<F: Fn(&WebviewEvent) + Send + 'static>(&self, f: F) {
1395    self
1396      .webview
1397      .dispatcher
1398      .on_webview_event(move |event| f(&event.clone().into()));
1399  }
1400
1401  /// Resolves the given command scope for this webview on the currently loaded URL.
1402  ///
1403  /// If the command is not allowed, returns None.
1404  ///
1405  /// If the scope cannot be deserialized to the given type, an error is returned.
1406  ///
1407  /// In a command context this can be directly resolved from the command arguments via [CommandScope]:
1408  ///
1409  /// ```
1410  /// use tauri::ipc::CommandScope;
1411  ///
1412  /// #[derive(Debug, serde::Deserialize)]
1413  /// struct ScopeType {
1414  ///   some_value: String,
1415  /// }
1416  /// #[tauri::command]
1417  /// fn my_command(scope: CommandScope<ScopeType>) {
1418  ///   // check scope
1419  /// }
1420  /// ```
1421  ///
1422  /// # Examples
1423  ///
1424  /// ```
1425  /// use tauri::Manager;
1426  ///
1427  /// #[derive(Debug, serde::Deserialize)]
1428  /// struct ScopeType {
1429  ///   some_value: String,
1430  /// }
1431  ///
1432  /// tauri::Builder::default()
1433  ///   .setup(|app| {
1434  ///     let webview = app.get_webview_window("main").unwrap();
1435  ///     let scope = webview.resolve_command_scope::<ScopeType>("my-plugin", "read");
1436  ///     Ok(())
1437  ///   });
1438  /// ```
1439  pub fn resolve_command_scope<T: ScopeObject>(
1440    &self,
1441    plugin: &str,
1442    command: &str,
1443  ) -> crate::Result<Option<ResolvedScope<T>>> {
1444    let current_url = self.url()?;
1445    let is_local = self.is_local_url(&current_url);
1446    let origin = if is_local {
1447      Origin::Local
1448    } else {
1449      Origin::Remote { url: current_url }
1450    };
1451
1452    let cmd_name = format!("plugin:{plugin}|{command}");
1453    let resolved_access = self
1454      .manager()
1455      .runtime_authority
1456      .lock()
1457      .unwrap()
1458      .resolve_access(&cmd_name, self.window().label(), self.label(), &origin);
1459
1460    if let Some(access) = resolved_access {
1461      let scope_ids = access
1462        .iter()
1463        .filter_map(|cmd| cmd.scope_id)
1464        .collect::<Vec<_>>();
1465
1466      let command_scope = CommandScope::resolve(self, scope_ids)?;
1467      let global_scope = GlobalScope::resolve(self, plugin)?;
1468
1469      Ok(Some(ResolvedScope {
1470        global_scope,
1471        command_scope,
1472      }))
1473    } else {
1474      Ok(None)
1475    }
1476  }
1477}
1478
1479/// Desktop webview setters and actions.
1480#[cfg(desktop)]
1481impl<R: Runtime> Webview<R> {
1482  /// Opens the dialog to prints the contents of the webview.
1483  /// Currently only supported on macOS on `wry`.
1484  /// `window.print()` works on all platforms.
1485  pub fn print(&self) -> crate::Result<()> {
1486    self.webview.dispatcher.print().map_err(Into::into)
1487  }
1488
1489  /// Get the cursor position relative to the top-left hand corner of the desktop.
1490  ///
1491  /// Note that the top-left hand corner of the desktop is not necessarily the same as the screen.
1492  /// If the user uses a desktop with multiple monitors,
1493  /// the top-left hand corner of the desktop is the top-left hand corner of the main monitor on Windows and macOS
1494  /// or the top-left of the leftmost monitor on X11.
1495  ///
1496  /// The coordinates can be negative if the top-left hand corner of the window is outside of the visible screen region.
1497  pub fn cursor_position(&self) -> crate::Result<PhysicalPosition<f64>> {
1498    self.app_handle.cursor_position()
1499  }
1500
1501  /// Closes this webview.
1502  pub fn close(&self) -> crate::Result<()> {
1503    self.webview.dispatcher.close()?;
1504    self.manager().on_webview_close(self.label());
1505    Ok(())
1506  }
1507
1508  /// Resizes this webview.
1509  pub fn set_bounds(&self, bounds: tauri_runtime::dpi::Rect) -> crate::Result<()> {
1510    self
1511      .webview
1512      .dispatcher
1513      .set_bounds(bounds)
1514      .map_err(Into::into)
1515  }
1516
1517  /// Resizes this webview.
1518  pub fn set_size<S: Into<Size>>(&self, size: S) -> crate::Result<()> {
1519    self
1520      .webview
1521      .dispatcher
1522      .set_size(size.into())
1523      .map_err(Into::into)
1524  }
1525
1526  /// Sets this webviews's position.
1527  pub fn set_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {
1528    self
1529      .webview
1530      .dispatcher
1531      .set_position(position.into())
1532      .map_err(Into::into)
1533  }
1534
1535  /// Focus the webview.
1536  pub fn set_focus(&self) -> crate::Result<()> {
1537    self.webview.dispatcher.set_focus().map_err(Into::into)
1538  }
1539
1540  /// Hide the webview.
1541  pub fn hide(&self) -> crate::Result<()> {
1542    self.webview.dispatcher.hide().map_err(Into::into)
1543  }
1544
1545  /// Show the webview.
1546  pub fn show(&self) -> crate::Result<()> {
1547    self.webview.dispatcher.show().map_err(Into::into)
1548  }
1549
1550  /// Move the webview to the given window.
1551  pub fn reparent(&self, window: &Window<R>) -> crate::Result<()> {
1552    #[cfg(not(feature = "unstable"))]
1553    {
1554      if self.window_ref().is_webview_window() || window.is_webview_window() {
1555        return Err(crate::Error::CannotReparentWebviewWindow);
1556      }
1557    }
1558
1559    *self.window.lock().unwrap() = window.clone();
1560    self.webview.dispatcher.reparent(window.window.id)?;
1561    Ok(())
1562  }
1563
1564  /// Sets whether the webview should automatically grow and shrink its size and position when the parent window resizes.
1565  pub fn set_auto_resize(&self, auto_resize: bool) -> crate::Result<()> {
1566    self
1567      .webview
1568      .dispatcher
1569      .set_auto_resize(auto_resize)
1570      .map_err(Into::into)
1571  }
1572
1573  /// Returns the bounds of the webviews's client area.
1574  pub fn bounds(&self) -> crate::Result<tauri_runtime::dpi::Rect> {
1575    self.webview.dispatcher.bounds().map_err(Into::into)
1576  }
1577
1578  /// Returns the webview position.
1579  ///
1580  /// - For child webviews, returns the position of the top-left hand corner of the webviews's client area relative to the top-left hand corner of the parent window.
1581  /// - For webview window, returns the inner position of the window.
1582  pub fn position(&self) -> crate::Result<PhysicalPosition<i32>> {
1583    self.webview.dispatcher.position().map_err(Into::into)
1584  }
1585
1586  /// Returns the physical size of the webviews's client area.
1587  pub fn size(&self) -> crate::Result<PhysicalSize<u32>> {
1588    self.webview.dispatcher.size().map_err(Into::into)
1589  }
1590}
1591
1592/// Webview APIs.
1593impl<R: Runtime> Webview<R> {
1594  /// The window that is hosting this webview.
1595  pub fn window(&self) -> Window<R> {
1596    self.window.lock().unwrap().clone()
1597  }
1598
1599  /// A reference to the window that is hosting this webview.
1600  pub fn window_ref(&self) -> MutexGuard<'_, Window<R>> {
1601    self.window.lock().unwrap()
1602  }
1603
1604  pub(crate) fn window_label(&self) -> String {
1605    self.window_ref().label().to_string()
1606  }
1607
1608  /// Executes a closure, providing it with the webview handle that is specific to the current platform.
1609  ///
1610  /// The closure is executed on the main thread.
1611  ///
1612  /// Note that `webview2-com`, `webkit2gtk`, `objc2_web_kit` and similar crates may be updated in minor releases of Tauri.
1613  /// Therefore it's recommended to pin Tauri to at least a minor version when you're using `with_webview`.
1614  ///
1615  /// # Examples
1616  ///
1617  #[cfg_attr(
1618    feature = "unstable",
1619    doc = r####"
1620```rust,no_run
1621use tauri::Manager;
1622
1623tauri::Builder::default()
1624  .setup(|app| {
1625    let main_webview = app.get_webview("main").unwrap();
1626    main_webview.with_webview(|webview| {
1627      #[cfg(target_os = "linux")]
1628      {
1629        // see <https://docs.rs/webkit2gtk/2.0.0/webkit2gtk/struct.WebView.html>
1630        // and <https://docs.rs/webkit2gtk/2.0.0/webkit2gtk/trait.WebViewExt.html>
1631        use webkit2gtk::WebViewExt;
1632        webview.inner().set_zoom_level(4.);
1633      }
1634
1635      #[cfg(windows)]
1636      unsafe {
1637        // see https://docs.rs/webview2-com/0.19.1/webview2_com/Microsoft/Web/WebView2/Win32/struct.ICoreWebView2Controller.html
1638        webview.controller().SetZoomFactor(4.).unwrap();
1639      }
1640
1641      #[cfg(target_os = "macos")]
1642      unsafe {
1643        let view: &objc2_web_kit::WKWebView = &*webview.inner().cast();
1644        let controller: &objc2_web_kit::WKUserContentController = &*webview.controller().cast();
1645        let window: &objc2_app_kit::NSWindow = &*webview.ns_window().cast();
1646
1647        view.setPageZoom(4.);
1648        controller.removeAllUserScripts();
1649        let bg_color = objc2_app_kit::NSColor::colorWithDeviceRed_green_blue_alpha(0.5, 0.2, 0.4, 1.);
1650        window.setBackgroundColor(Some(&bg_color));
1651      }
1652
1653      #[cfg(target_os = "android")]
1654      {
1655        use jni::objects::JValue;
1656        webview.jni_handle().exec(|env, _, webview| {
1657          env.call_method(webview, "zoomBy", "(F)V", &[JValue::Float(4.)]).unwrap();
1658        })
1659      }
1660    });
1661    Ok(())
1662});
1663```
1664  "####
1665  )]
1666  #[cfg(feature = "wry")]
1667  #[cfg_attr(docsrs, doc(cfg(feature = "wry")))]
1668  pub fn with_webview<F: FnOnce(PlatformWebview) + Send + 'static>(
1669    &self,
1670    f: F,
1671  ) -> crate::Result<()> {
1672    self
1673      .webview
1674      .dispatcher
1675      .with_webview(|w| f(PlatformWebview(*w.downcast().unwrap())))
1676      .map_err(Into::into)
1677  }
1678
1679  /// Returns the current url of the webview.
1680  pub fn url(&self) -> crate::Result<Url> {
1681    self
1682      .webview
1683      .dispatcher
1684      .url()
1685      .map(|url| url.parse().map_err(crate::Error::InvalidUrl))?
1686  }
1687
1688  /// Navigates the webview to the defined url.
1689  pub fn navigate(&self, url: Url) -> crate::Result<()> {
1690    self.webview.dispatcher.navigate(url).map_err(Into::into)
1691  }
1692
1693  /// Reloads the current page.
1694  pub fn reload(&self) -> crate::Result<()> {
1695    self.webview.dispatcher.reload().map_err(Into::into)
1696  }
1697
1698  fn is_local_url(&self, current_url: &Url) -> bool {
1699    let uses_https = current_url.scheme() == "https";
1700
1701    // if from `tauri://` custom protocol
1702    ({
1703      let protocol_url = self.manager().tauri_protocol_url(uses_https);
1704      current_url.scheme() == protocol_url.scheme()
1705      && current_url.domain() == protocol_url.domain()
1706    }) ||
1707
1708    // or if relative to `devUrl` or `frontendDist`
1709      self
1710          .manager()
1711          .get_app_url(uses_https)
1712          .make_relative(current_url)
1713          .is_some()
1714
1715      // or from a custom protocol registered by the user
1716      || ({
1717        let scheme = current_url.scheme();
1718        let protocols = self.manager().webview.uri_scheme_protocols.lock().unwrap();
1719
1720        #[cfg(all(not(windows), not(target_os = "android")))]
1721        let local = protocols.contains_key(scheme);
1722
1723        // on window and android, custom protocols are `http://<protocol-name>.path/to/route`
1724        // so we check using the first part of the domain
1725        #[cfg(any(windows, target_os = "android"))]
1726        let local = {
1727          let scheme = scheme == self.manager().tauri_protocol_url(uses_https).scheme();
1728          let protocol = current_url
1729            .domain()
1730            .and_then(|d| d.strip_suffix(".localhost"))
1731            .map(|protocol| protocols.contains_key(protocol))
1732            .unwrap_or_default();
1733
1734          scheme && protocol
1735        };
1736
1737        local
1738      })
1739  }
1740
1741  /// Handles this window receiving an [`InvokeRequest`].
1742  pub fn on_message(self, request: InvokeRequest, responder: Box<OwnedInvokeResponder<R>>) {
1743    let manager = self.manager_owned();
1744    let is_local = self.is_local_url(&request.url);
1745
1746    // ensure the passed key matches what our manager should have injected
1747    let expected = manager.invoke_key();
1748    if request.invoke_key != expected {
1749      #[cfg(feature = "tracing")]
1750      tracing::error!(
1751        "__TAURI_INVOKE_KEY__ expected {expected} but received {}",
1752        request.invoke_key
1753      );
1754
1755      #[cfg(not(feature = "tracing"))]
1756      eprintln!(
1757        "__TAURI_INVOKE_KEY__ expected {expected} but received {}",
1758        request.invoke_key
1759      );
1760
1761      return;
1762    }
1763
1764    let resolver = InvokeResolver::new(
1765      self.clone(),
1766      Arc::new(Mutex::new(Some(Box::new(
1767        move |webview: Webview<R>, cmd, response, callback, error| {
1768          responder(webview, cmd, response, callback, error);
1769        },
1770      )))),
1771      request.cmd.clone(),
1772      request.callback,
1773      request.error,
1774    );
1775
1776    #[cfg(mobile)]
1777    let app_handle = self.app_handle.clone();
1778
1779    let message = InvokeMessage::new(
1780      self,
1781      manager.state(),
1782      request.cmd.to_string(),
1783      request.body,
1784      request.headers,
1785    );
1786
1787    let acl_origin = if is_local {
1788      Origin::Local
1789    } else {
1790      Origin::Remote {
1791        url: request.url.clone(),
1792      }
1793    };
1794    let (resolved_acl, has_app_acl_manifest) = {
1795      let runtime_authority = manager.runtime_authority.lock().unwrap();
1796      let acl = runtime_authority.resolve_access(
1797        &request.cmd,
1798        message.webview.window_ref().label(),
1799        message.webview.label(),
1800        &acl_origin,
1801      );
1802      (acl, runtime_authority.has_app_manifest())
1803    };
1804
1805    let mut invoke = Invoke {
1806      message,
1807      resolver: resolver.clone(),
1808      acl: resolved_acl,
1809    };
1810
1811    let plugin_command = request.cmd.strip_prefix("plugin:").map(|raw_command| {
1812      let mut tokens = raw_command.split('|');
1813      // safe to unwrap: split always has a least one item
1814      let plugin = tokens.next().unwrap();
1815      let command = tokens.next().map(|c| c.to_string()).unwrap_or_default();
1816      (plugin, command)
1817    });
1818
1819    // Check ACL on plugin commands, when the app defined its ACL manifest,
1820    // or when the request comes from a non-local (remote) origin.  This
1821    // ensures remote content can never reach custom commands unless an
1822    // explicit `remote` capability has been configured for them.
1823    if (plugin_command.is_some() || has_app_acl_manifest || !is_local)
1824      // TODO: Remove this special check in v3
1825      && request.cmd != crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND
1826      && invoke.acl.is_none()
1827    {
1828      #[cfg(debug_assertions)]
1829      {
1830        let (key, command_name) = plugin_command
1831          .clone()
1832          .unwrap_or_else(|| (tauri_utils::acl::APP_ACL_KEY, request.cmd.clone()));
1833        invoke.resolver.reject(
1834          manager
1835            .runtime_authority
1836            .lock()
1837            .unwrap()
1838            .resolve_access_message(
1839              key,
1840              &command_name,
1841              invoke.message.webview.window().label(),
1842              invoke.message.webview.label(),
1843              &acl_origin,
1844            ),
1845        );
1846      }
1847      #[cfg(not(debug_assertions))]
1848      invoke
1849        .resolver
1850        .reject(format!("Command {} not allowed by ACL", request.cmd));
1851      return;
1852    }
1853
1854    if let Some((plugin, command_name)) = plugin_command {
1855      invoke.message.command = command_name;
1856
1857      let command = invoke.message.command.clone();
1858
1859      #[cfg(mobile)]
1860      let message = invoke.message.clone();
1861
1862      #[allow(unused_mut)]
1863      let mut handled = manager.extend_api(plugin, invoke);
1864
1865      #[cfg(mobile)]
1866      {
1867        if !handled {
1868          handled = true;
1869
1870          fn load_channels<R: Runtime>(payload: &serde_json::Value, webview: &Webview<R>) {
1871            use std::str::FromStr;
1872
1873            if let serde_json::Value::Object(map) = payload {
1874              for v in map.values() {
1875                if let serde_json::Value::String(s) = v {
1876                  let _ = crate::ipc::JavaScriptChannelId::from_str(s)
1877                    .map(|id| id.channel_on::<R, ()>(webview.clone()));
1878                }
1879              }
1880            }
1881          }
1882
1883          let payload = message.payload.into_json();
1884          // initialize channels
1885          load_channels(&payload, &message.webview);
1886
1887          let resolver_ = resolver.clone();
1888          if let Err(e) = crate::plugin::mobile::run_command(
1889            plugin,
1890            &app_handle,
1891            heck::AsLowerCamelCase(message.command).to_string(),
1892            payload,
1893            move |response| match response {
1894              Ok(r) => resolver_.resolve(r),
1895              Err(e) => resolver_.reject(e),
1896            },
1897          ) {
1898            resolver.reject(e.to_string());
1899            return;
1900          }
1901        }
1902      }
1903
1904      if !handled {
1905        resolver.reject(format!("Command {command} not found"));
1906      }
1907    } else {
1908      let command = invoke.message.command.clone();
1909      let handled = manager.run_invoke_handler(invoke);
1910      if !handled {
1911        resolver.reject(format!("Command {command} not found"));
1912      }
1913    }
1914  }
1915
1916  /// Evaluates JavaScript on this window.
1917  pub fn eval(&self, js: impl Into<String>) -> crate::Result<()> {
1918    self
1919      .webview
1920      .dispatcher
1921      .eval_script(js.into())
1922      .map_err(Into::into)
1923  }
1924
1925  /// Evaluate JavaScript with callback function on this webview.
1926  /// The evaluation result will be serialized into a JSON string and passed to the callback function.
1927  ///
1928  /// Exception is ignored because of the limitation on Windows. You can catch it yourself and return as string as a workaround.
1929  pub fn eval_with_callback(
1930    &self,
1931    js: impl Into<String>,
1932    callback: impl Fn(String) + Send + 'static,
1933  ) -> crate::Result<()> {
1934    self
1935      .webview
1936      .dispatcher
1937      .eval_script_with_callback(js.into(), callback)
1938      .map_err(Into::into)
1939  }
1940
1941  /// Register a JS event listener and return its identifier.
1942  pub(crate) fn listen_js(
1943    &self,
1944    event: EventName<&str>,
1945    target: EventTarget,
1946    handler: CallbackFn,
1947  ) -> crate::Result<EventId> {
1948    let listeners = self.manager().listeners();
1949
1950    let id = listeners.next_event_id();
1951
1952    self.eval(crate::event::listen_js_script(
1953      listeners.listeners_object_name(),
1954      &serde_json::to_string(&target)?,
1955      event,
1956      id,
1957      handler,
1958    ))?;
1959
1960    listeners.listen_js(event, self.label(), target, id);
1961
1962    Ok(id)
1963  }
1964
1965  /// Unregister a JS event listener.
1966  pub(crate) fn unlisten_js(&self, event: EventName<&str>, id: EventId) -> crate::Result<()> {
1967    let listeners = self.manager().listeners();
1968
1969    listeners.unlisten_js(event, id);
1970
1971    Ok(())
1972  }
1973
1974  pub(crate) fn emit_js(&self, emit_args: &EmitArgs, ids: &[u32]) -> crate::Result<()> {
1975    self.eval(crate::event::emit_js_script(
1976      self.manager().listeners().function_name(),
1977      emit_args,
1978      &serde_json::to_string(ids)?,
1979    )?)?;
1980    Ok(())
1981  }
1982
1983  /// Opens the developer tools window (Web Inspector).
1984  /// The devtools is only enabled on debug builds or with the `devtools` feature flag.
1985  ///
1986  /// ## Platform-specific
1987  ///
1988  /// - **macOS:** Only supported on macOS 10.15+.
1989  ///   This is a private API on macOS, so you cannot use this if your application will be published on the App Store.
1990  ///
1991  /// # Examples
1992  ///
1993  #[cfg_attr(
1994    feature = "unstable",
1995    doc = r####"
1996```rust,no_run
1997use tauri::Manager;
1998tauri::Builder::default()
1999  .setup(|app| {
2000    #[cfg(debug_assertions)]
2001    app.get_webview("main").unwrap().open_devtools();
2002    Ok(())
2003  });
2004```
2005  "####
2006  )]
2007  #[cfg(any(debug_assertions, feature = "devtools"))]
2008  #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = "devtools"))))]
2009  pub fn open_devtools(&self) {
2010    self.webview.dispatcher.open_devtools();
2011  }
2012
2013  /// Closes the developer tools window (Web Inspector).
2014  /// The devtools is only enabled on debug builds or with the `devtools` feature flag.
2015  ///
2016  /// ## Platform-specific
2017  ///
2018  /// - **macOS:** Only supported on macOS 10.15+.
2019  ///   This is a private API on macOS, so you cannot use this if your application will be published on the App Store.
2020  /// - **Windows:** Unsupported.
2021  ///
2022  /// # Examples
2023  ///
2024  #[cfg_attr(
2025    feature = "unstable",
2026    doc = r####"
2027```rust,no_run
2028use tauri::Manager;
2029tauri::Builder::default()
2030  .setup(|app| {
2031    #[cfg(debug_assertions)]
2032    {
2033      let webview = app.get_webview("main").unwrap();
2034      webview.open_devtools();
2035      std::thread::spawn(move || {
2036        std::thread::sleep(std::time::Duration::from_secs(10));
2037        webview.close_devtools();
2038      });
2039    }
2040    Ok(())
2041  });
2042```
2043  "####
2044  )]
2045  #[cfg(any(debug_assertions, feature = "devtools"))]
2046  #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = "devtools"))))]
2047  pub fn close_devtools(&self) {
2048    self.webview.dispatcher.close_devtools();
2049  }
2050
2051  /// Checks if the developer tools window (Web Inspector) is opened.
2052  /// The devtools is only enabled on debug builds or with the `devtools` feature flag.
2053  ///
2054  /// ## Platform-specific
2055  ///
2056  /// - **macOS:** Only supported on macOS 10.15+.
2057  ///   This is a private API on macOS, so you cannot use this if your application will be published on the App Store.
2058  /// - **Windows:** Unsupported.
2059  ///
2060  /// # Examples
2061  ///
2062  #[cfg_attr(
2063    feature = "unstable",
2064    doc = r####"
2065```rust,no_run
2066use tauri::Manager;
2067tauri::Builder::default()
2068  .setup(|app| {
2069    #[cfg(debug_assertions)]
2070    {
2071      let webview = app.get_webview("main").unwrap();
2072      if !webview.is_devtools_open() {
2073        webview.open_devtools();
2074      }
2075    }
2076    Ok(())
2077  });
2078```
2079  "####
2080  )]
2081  #[cfg(any(debug_assertions, feature = "devtools"))]
2082  #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = "devtools"))))]
2083  pub fn is_devtools_open(&self) -> bool {
2084    self
2085      .webview
2086      .dispatcher
2087      .is_devtools_open()
2088      .unwrap_or_default()
2089  }
2090
2091  /// Set the webview zoom level
2092  ///
2093  /// ## Platform-specific:
2094  ///
2095  /// - **Android**: Not supported.
2096  /// - **macOS**: available on macOS 11+ only.
2097  /// - **iOS**: available on iOS 14+ only.
2098  pub fn set_zoom(&self, scale_factor: f64) -> crate::Result<()> {
2099    self
2100      .webview
2101      .dispatcher
2102      .set_zoom(scale_factor)
2103      .map_err(Into::into)
2104  }
2105
2106  /// Specify the webview background color.
2107  ///
2108  /// ## Platform-specific:
2109  ///
2110  /// - **macOS / iOS**: Not implemented.
2111  /// - **Windows**:
2112  ///   - On Windows 7, transparency is not supported and the alpha value will be ignored.
2113  ///   - On Windows higher than 7: translucent colors are not supported so any alpha value other than `0` will be replaced by `255`
2114  pub fn set_background_color(&self, color: Option<Color>) -> crate::Result<()> {
2115    self
2116      .webview
2117      .dispatcher
2118      .set_background_color(color)
2119      .map_err(Into::into)
2120  }
2121
2122  /// Clear all browsing data for this webview.
2123  pub fn clear_all_browsing_data(&self) -> crate::Result<()> {
2124    self
2125      .webview
2126      .dispatcher
2127      .clear_all_browsing_data()
2128      .map_err(Into::into)
2129  }
2130
2131  /// Returns all cookies in the runtime's cookie store including HTTP-only and secure cookies.
2132  ///
2133  /// Note that cookies will only be returned for URLs with an http or https scheme.
2134  /// Cookies set through javascript for local files
2135  /// (such as those served from the tauri://) protocol are not currently supported.
2136  ///
2137  /// # Stability
2138  ///
2139  /// See [Self::cookies].
2140  ///
2141  /// # Known issues
2142  ///
2143  /// See [Self::cookies].
2144  pub fn cookies_for_url(&self, url: Url) -> crate::Result<Vec<Cookie<'static>>> {
2145    self
2146      .webview
2147      .dispatcher
2148      .cookies_for_url(url)
2149      .map_err(Into::into)
2150  }
2151
2152  /// Returns all cookies in the runtime's cookie store for all URLs including HTTP-only and secure cookies.
2153  ///
2154  /// Note that cookies will only be returned for URLs with an http or https scheme.
2155  /// Cookies set through javascript for local files
2156  /// (such as those served from the tauri://) protocol are not currently supported.
2157  ///
2158  /// # Stability
2159  ///
2160  /// The return value of this function leverages [`tauri_runtime::Cookie`] which re-exports the cookie crate.
2161  /// This dependency might receive updates in minor Tauri releases.
2162  ///
2163  /// # Known issues
2164  ///
2165  /// On Windows, this function deadlocks when used in a synchronous command or event handlers, see [the Webview2 issue].
2166  /// You should use `async` commands and separate threads when reading cookies.
2167  ///
2168  /// ## Platform-specific
2169  ///
2170  /// - **Android**: Unsupported, always returns an empty [`Vec`].
2171  ///
2172  /// [the Webview2 issue]: https://github.com/tauri-apps/wry/issues/583
2173  pub fn cookies(&self) -> crate::Result<Vec<Cookie<'static>>> {
2174    self.webview.dispatcher.cookies().map_err(Into::into)
2175  }
2176
2177  /// Set a cookie for the webview.
2178  ///
2179  /// # Stability
2180  ///
2181  /// See [Self::cookies].
2182  pub fn set_cookie(&self, cookie: Cookie<'_>) -> crate::Result<()> {
2183    self
2184      .webview
2185      .dispatcher
2186      .set_cookie(cookie)
2187      .map_err(Into::into)
2188  }
2189
2190  /// Delete a cookie for the webview.
2191  ///
2192  /// # Stability
2193  ///
2194  /// See [Self::cookies].
2195  pub fn delete_cookie(&self, cookie: Cookie<'_>) -> crate::Result<()> {
2196    self
2197      .webview
2198      .dispatcher
2199      .delete_cookie(cookie)
2200      .map_err(Into::into)
2201  }
2202}
2203
2204impl<R: Runtime> Listener<R> for Webview<R> {
2205  /// Listen to an event on this webview.
2206  ///
2207  /// # Examples
2208  #[cfg_attr(
2209    feature = "unstable",
2210    doc = r####"
2211```
2212use tauri::{Manager, Listener};
2213
2214tauri::Builder::default()
2215  .setup(|app| {
2216    let webview = app.get_webview("main").unwrap();
2217    webview.listen("component-loaded", move |event| {
2218      println!("webview just loaded a component");
2219    });
2220
2221    Ok(())
2222  });
2223```
2224  "####
2225  )]
2226  fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventId
2227  where
2228    F: Fn(Event) + Send + 'static,
2229  {
2230    let event = EventName::new(event.into()).unwrap();
2231    self.manager.listen(
2232      event,
2233      EventTarget::Webview {
2234        label: self.label().to_string(),
2235      },
2236      handler,
2237    )
2238  }
2239
2240  /// Listen to an event on this webview only once.
2241  ///
2242  /// See [`Self::listen`] for more information.
2243  fn once<F>(&self, event: impl Into<String>, handler: F) -> EventId
2244  where
2245    F: FnOnce(Event) + Send + 'static,
2246  {
2247    let event = EventName::new(event.into()).unwrap();
2248    self.manager.once(
2249      event,
2250      EventTarget::Webview {
2251        label: self.label().to_string(),
2252      },
2253      handler,
2254    )
2255  }
2256
2257  /// Unlisten to an event on this webview.
2258  ///
2259  /// # Examples
2260  #[cfg_attr(
2261    feature = "unstable",
2262    doc = r####"
2263```
2264use tauri::{Manager, Listener};
2265
2266tauri::Builder::default()
2267  .setup(|app| {
2268    let webview = app.get_webview("main").unwrap();
2269    let webview_ = webview.clone();
2270    let handler = webview.listen("component-loaded", move |event| {
2271      println!("webview just loaded a component");
2272
2273      // we no longer need to listen to the event
2274      // we also could have used `webview.once` instead
2275      webview_.unlisten(event.id());
2276    });
2277
2278    // stop listening to the event when you do not need it anymore
2279    webview.unlisten(handler);
2280
2281    Ok(())
2282  });
2283```
2284  "####
2285  )]
2286  fn unlisten(&self, id: EventId) {
2287    self.manager.unlisten(id)
2288  }
2289}
2290
2291impl<R: Runtime> Emitter<R> for Webview<R> {}
2292
2293impl<R: Runtime> Manager<R> for Webview<R> {
2294  fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {
2295    self
2296      .resources_table
2297      .lock()
2298      .expect("poisoned window resources table")
2299  }
2300}
2301
2302impl<R: Runtime> ManagerBase<R> for Webview<R> {
2303  fn manager(&self) -> &AppManager<R> {
2304    &self.manager
2305  }
2306
2307  fn manager_owned(&self) -> Arc<AppManager<R>> {
2308    self.manager.clone()
2309  }
2310
2311  fn runtime(&self) -> RuntimeOrDispatch<'_, R> {
2312    self.app_handle.runtime()
2313  }
2314
2315  fn managed_app_handle(&self) -> &AppHandle<R> {
2316    &self.app_handle
2317  }
2318
2319  #[cfg(target_os = "android")]
2320  fn activity_name(&self) -> Option<crate::Result<String>> {
2321    Some(self.window().activity_name())
2322  }
2323
2324  #[cfg(target_os = "ios")]
2325  fn scene_identifier(&self) -> Option<crate::Result<String>> {
2326    Some(self.window().scene_identifier())
2327  }
2328}
2329
2330impl<'de, R: Runtime> CommandArg<'de, R> for Webview<R> {
2331  /// Grabs the [`Webview`] from the [`CommandItem`]. This will never fail.
2332  fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {
2333    Ok(command.message.webview())
2334  }
2335}
2336
2337/// Resolved scope that can be obtained via [`Webview::resolve_command_scope`].
2338pub struct ResolvedScope<T: ScopeObject> {
2339  command_scope: CommandScope<T>,
2340  global_scope: GlobalScope<T>,
2341}
2342
2343impl<T: ScopeObject> ResolvedScope<T> {
2344  /// The global plugin scope.
2345  pub fn global_scope(&self) -> &GlobalScope<T> {
2346    &self.global_scope
2347  }
2348
2349  /// The command-specific scope.
2350  pub fn command_scope(&self) -> &CommandScope<T> {
2351    &self.command_scope
2352  }
2353}
2354
2355#[cfg(test)]
2356mod tests {
2357  use url::Url;
2358
2359  fn test_webview_window() -> crate::WebviewWindow<crate::test::MockRuntime> {
2360    use crate::test::{mock_builder, mock_context, noop_assets};
2361
2362    // Create a mock app with proper context
2363    let app = mock_builder().build(mock_context(noop_assets())).unwrap();
2364
2365    // Create a webview window
2366    crate::WebviewWindowBuilder::new(&app, "test", crate::WebviewUrl::default())
2367      .build()
2368      .unwrap()
2369  }
2370
2371  #[test]
2372  fn webview_is_send_sync() {
2373    crate::test_utils::assert_send::<super::Webview>();
2374    crate::test_utils::assert_sync::<super::Webview>();
2375  }
2376
2377  #[test]
2378  fn tauri_protocol_is_local() {
2379    let webview = test_webview_window().webview;
2380
2381    #[cfg(all(not(windows), not(target_os = "android")))]
2382    assert!(webview.is_local_url(&Url::parse("tauri://localhost/").unwrap()));
2383
2384    #[cfg(any(windows, target_os = "android"))]
2385    assert!(webview.is_local_url(&Url::parse("https://tauri.localhost/").unwrap()));
2386  }
2387
2388  // On Windows/Android, custom protocols are served as `https://<name>.localhost/`.
2389  // We ensure only `.localhost` domains are accepted to prevent a subdomain being able to
2390  // impersonate a protocol name.
2391  #[cfg(any(windows, target_os = "android"))]
2392  #[test]
2393  fn windows_custom_protocol_rejects_spoofed_domain() {
2394    use crate::test::{mock_builder, mock_context, noop_assets};
2395
2396    let app = mock_builder()
2397      .register_uri_scheme_protocol("myproto", |_, _| {
2398        http::Response::builder().body(Vec::new()).unwrap()
2399      })
2400      .build(mock_context(noop_assets()))
2401      .unwrap();
2402    let webview = crate::WebviewWindowBuilder::new(&app, "test", crate::WebviewUrl::default())
2403      .build()
2404      .unwrap()
2405      .webview;
2406
2407    let url = |s| Url::parse(s).unwrap();
2408
2409    // Legitimate Windows custom protocol URL
2410    assert!(webview.is_local_url(&url("https://myproto.localhost/")));
2411
2412    // Attacker domain that starts with a registered protocol name — must NOT be local.
2413    assert!(!webview.is_local_url(&url("https://myproto.evil.com/")));
2414
2415    // Subdomain of .localhost with unregistered name — must NOT be local
2416    assert!(!webview.is_local_url(&url("https://notregistered.localhost/")));
2417  }
2418
2419  /// Custom (non-plugin) commands must be rejected when the IPC request
2420  /// originates from a remote URL, even when no `AppManifest` has been
2421  /// configured.  Only local (bundled) origins should be able to reach
2422  /// custom commands.
2423  #[test]
2424  fn remote_origin_blocked_for_custom_commands_without_app_manifest() {
2425    use crate::test::{mock_builder, mock_context, noop_assets, INVOKE_KEY};
2426    use crate::webview::InvokeRequest;
2427
2428    let app = mock_builder().build(mock_context(noop_assets())).unwrap();
2429
2430    let webview = crate::WebviewWindowBuilder::new(&app, "main", Default::default())
2431      .build()
2432      .unwrap();
2433
2434    // Request from a remote origin for a custom (non-plugin) command
2435    // - should be rejected even without an AppManifest.
2436    let remote_result = crate::test::get_ipc_response(
2437      &webview,
2438      InvokeRequest {
2439        cmd: "any_custom_command".into(),
2440        callback: crate::ipc::CallbackFn(0),
2441        error: crate::ipc::CallbackFn(1),
2442        url: "https://evil.com".parse().unwrap(),
2443        body: crate::ipc::InvokeBody::default(),
2444        headers: Default::default(),
2445        invoke_key: INVOKE_KEY.to_string(),
2446      },
2447    );
2448    assert!(
2449      remote_result.is_err(),
2450      "custom command should be rejected from a remote origin"
2451    );
2452
2453    // Same command from the local origin - should NOT be rejected by the
2454    // remote-origin guard (it may still fail because the command doesn't
2455    // exist, but the error message will be different).
2456    let local_result = crate::test::get_ipc_response(
2457      &webview,
2458      InvokeRequest {
2459        cmd: "any_custom_command".into(),
2460        callback: crate::ipc::CallbackFn(0),
2461        error: crate::ipc::CallbackFn(1),
2462        url: "tauri://localhost".parse().unwrap(),
2463        body: crate::ipc::InvokeBody::default(),
2464        headers: Default::default(),
2465        invoke_key: INVOKE_KEY.to_string(),
2466      },
2467    );
2468    // The local request should either succeed or fail for a reason OTHER
2469    // than "not allowed from remote context".
2470    if let Err(e) = &local_result {
2471      let msg = e.to_string();
2472      assert!(
2473        !msg.contains("not allowed from remote context"),
2474        "local origin should not be blocked by the remote-origin guard, got: {msg}"
2475      );
2476    }
2477  }
2478
2479  #[cfg(target_os = "macos")]
2480  #[test]
2481  fn test_webview_window_has_set_simple_fullscreen_method() {
2482    let webview_window = test_webview_window();
2483
2484    // This should compile if set_simple_fullscreen exists
2485    let result = webview_window.set_simple_fullscreen(true);
2486
2487    // We expect this to work without panicking
2488    assert!(result.is_ok(), "set_simple_fullscreen should succeed");
2489  }
2490}