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};
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  sealed::{ManagerBase, RuntimeOrDispatch},
46  AppHandle, Emitter, Event, EventId, EventLoopMessage, EventName, Listener, Manager,
47  ResourceTable, Runtime, Window,
48};
49
50use std::{
51  borrow::Cow,
52  hash::{Hash, Hasher},
53  path::{Path, PathBuf},
54  sync::{Arc, Mutex, MutexGuard},
55};
56
57pub(crate) type WebResourceRequestHandler =
58  dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
59pub(crate) type NavigationHandler = dyn Fn(&Url) -> bool + Send;
60pub(crate) type NewWindowHandler<R> =
61  dyn Fn(Url, NewWindowFeatures) -> NewWindowResponse<R> + Send + Sync;
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::related_view`].
247  /// **Windows**: The webview must use the same environment as the caller webview. See [`WebviewBuilder::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    Self {
391      label: config.label.clone(),
392      webview_attributes: WebviewAttributes::from(config),
393      web_resource_request_handler: None,
394      navigation_handler: None,
395      new_window_handler: None,
396      on_page_load_handler: None,
397      document_title_changed_handler: None,
398      download_handler: None,
399    }
400  }
401
402  /// Defines a closure to be executed when the webview makes an HTTP request for a web resource, allowing you to modify the response.
403  ///
404  /// Currently only implemented for the `tauri` URI protocol.
405  ///
406  /// **NOTE:** Currently this is **not** executed when using external URLs such as a development server,
407  /// but it might be implemented in the future. **Always** check the request URL.
408  ///
409  /// # Examples
410  ///
411  #[cfg_attr(
412    feature = "unstable",
413    doc = r####"
414```rust,no_run
415use tauri::{
416  utils::config::{Csp, CspDirectiveSources, WebviewUrl},
417  window::WindowBuilder,
418  webview::WebviewBuilder,
419};
420use http::header::HeaderValue;
421use std::collections::HashMap;
422tauri::Builder::default()
423  .setup(|app| {
424    let window = tauri::window::WindowBuilder::new(app, "label").build()?;
425
426    let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
427      .on_web_resource_request(|request, response| {
428        if request.uri().scheme_str() == Some("tauri") {
429          // if we have a CSP header, Tauri is loading an HTML file
430          //  for this example, let's dynamically change the CSP
431          if let Some(csp) = response.headers_mut().get_mut("Content-Security-Policy") {
432            // use the tauri helper to parse the CSP policy to a map
433            let mut csp_map: HashMap<String, CspDirectiveSources> = Csp::Policy(csp.to_str().unwrap().to_string()).into();
434            csp_map.entry("script-src".to_string()).or_insert_with(Default::default).push("'unsafe-inline'");
435            // use the tauri helper to get a CSP string from the map
436            let csp_string = Csp::from(csp_map).to_string();
437            *csp = HeaderValue::from_str(&csp_string).unwrap();
438          }
439        }
440      });
441
442    let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
443
444    Ok(())
445  });
446```
447  "####
448  )]
449  pub fn on_web_resource_request<
450    F: Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync + 'static,
451  >(
452    mut self,
453    f: F,
454  ) -> Self {
455    self.web_resource_request_handler.replace(Box::new(f));
456    self
457  }
458
459  /// Defines a closure to be executed when the webview navigates to a URL. Returning `false` cancels the navigation.
460  ///
461  /// # Examples
462  ///
463  #[cfg_attr(
464    feature = "unstable",
465    doc = r####"
466```rust,no_run
467use tauri::{
468  utils::config::{Csp, CspDirectiveSources, WebviewUrl},
469  window::WindowBuilder,
470  webview::WebviewBuilder,
471};
472use http::header::HeaderValue;
473use std::collections::HashMap;
474tauri::Builder::default()
475  .setup(|app| {
476    let window = tauri::window::WindowBuilder::new(app, "label").build()?;
477
478    let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
479      .on_navigation(|url| {
480        // allow the production URL or localhost on dev
481        url.scheme() == "tauri" || (cfg!(dev) && url.host_str() == Some("localhost"))
482      });
483
484    let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
485    Ok(())
486  });
487```
488  "####
489  )]
490  pub fn on_navigation<F: Fn(&Url) -> bool + Send + 'static>(mut self, f: F) -> Self {
491    self.navigation_handler.replace(Box::new(f));
492    self
493  }
494
495  /// Set a new window request handler to decide if incoming url is allowed to be opened.
496  ///
497  /// A new window is requested to be opened by the [window.open] API.
498  ///
499  /// The closure take the URL to open and the window features object and returns [`NewWindowResponse`] to determine whether the window should open.
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 app_ = app.handle().clone();
517    let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
518      .on_new_window(move |url, features| {
519        let builder = tauri::WebviewWindowBuilder::new(
520          &app_,
521          // note: add an ID counter or random label generator to support multiple opened windows at the same time
522          "opened-window",
523          tauri::WebviewUrl::External("about:blank".parse().unwrap()),
524        )
525        .window_features(features)
526        .on_document_title_changed(|window, title| {
527          window.set_title(&title).unwrap();
528        })
529        .title(url.as_str());
530
531        let window = builder.build().unwrap();
532        tauri::webview::NewWindowResponse::Create { window }
533      });
534
535    let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
536    Ok(())
537  });
538```
539  "####
540  )]
541  ///
542  /// # Platform-specific
543  ///
544  /// - **Android / iOS**: Not supported.
545  /// - **Windows**: The closure is executed on a separate thread to prevent a deadlock.
546  ///
547  /// [window.open]: https://developer.mozilla.org/en-US/docs/Web/API/Window/open
548  pub fn on_new_window<
549    F: Fn(Url, NewWindowFeatures) -> NewWindowResponse<R> + Send + Sync + 'static,
550  >(
551    mut self,
552    f: F,
553  ) -> Self {
554    self.new_window_handler.replace(Box::new(f));
555    self
556  }
557
558  /// Defines a closure to be executed when document title change.
559  pub fn on_document_title_changed<F: Fn(Webview<R>, String) + Send + 'static>(
560    mut self,
561    f: F,
562  ) -> Self {
563    self.document_title_changed_handler.replace(Box::new(f));
564    self
565  }
566
567  /// Set a download event handler to be notified when a download is requested or finished.
568  ///
569  /// Returning `false` prevents the download from happening on a [`DownloadEvent::Requested`] event.
570  ///
571  /// # Examples
572  ///
573  #[cfg_attr(
574    feature = "unstable",
575    doc = r####"
576```rust,no_run
577use tauri::{
578  utils::config::{Csp, CspDirectiveSources, WebviewUrl},
579  window::WindowBuilder,
580  webview::{DownloadEvent, WebviewBuilder},
581};
582
583tauri::Builder::default()
584  .setup(|app| {
585    let window = WindowBuilder::new(app, "label").build()?;
586    let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
587      .on_download(|webview, event| {
588        match event {
589          DownloadEvent::Requested { url, destination } => {
590            println!("downloading {}", url);
591            *destination = "/home/tauri/target/path".into();
592          }
593          DownloadEvent::Finished { url, path, success } => {
594            println!("downloaded {} to {:?}, success: {}", url, path, success);
595          }
596          _ => (),
597        }
598        // let the download start
599        true
600      });
601
602    let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
603    Ok(())
604  });
605```
606  "####
607  )]
608  pub fn on_download<F: Fn(Webview<R>, DownloadEvent<'_>) -> bool + Send + Sync + 'static>(
609    mut self,
610    f: F,
611  ) -> Self {
612    self.download_handler.replace(Arc::new(f));
613    self
614  }
615
616  /// Defines a closure to be executed when a page load event is triggered.
617  /// The event can be either [`PageLoadEvent::Started`] if the page has started loading
618  /// or [`PageLoadEvent::Finished`] when the page finishes loading.
619  ///
620  /// # Examples
621  ///
622  #[cfg_attr(
623    feature = "unstable",
624    doc = r####"
625```rust,no_run
626use tauri::{
627  utils::config::{Csp, CspDirectiveSources, WebviewUrl},
628  window::WindowBuilder,
629  webview::{PageLoadEvent, WebviewBuilder},
630};
631use http::header::HeaderValue;
632use std::collections::HashMap;
633tauri::Builder::default()
634  .setup(|app| {
635    let window = tauri::window::WindowBuilder::new(app, "label").build()?;
636    let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
637      .on_page_load(|webview, payload| {
638        match payload.event() {
639          PageLoadEvent::Started => {
640            println!("{} finished loading", payload.url());
641          }
642          PageLoadEvent::Finished => {
643            println!("{} finished loading", payload.url());
644          }
645        }
646      });
647    let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
648    Ok(())
649  });
650```
651  "####
652  )]
653  pub fn on_page_load<F: Fn(Webview<R>, PageLoadPayload<'_>) + Send + Sync + 'static>(
654    mut self,
655    f: F,
656  ) -> Self {
657    self.on_page_load_handler.replace(Box::new(f));
658    self
659  }
660
661  pub(crate) fn into_pending_webview<M: Manager<R>>(
662    mut self,
663    manager: &M,
664    window_label: &str,
665  ) -> crate::Result<PendingWebview<EventLoopMessage, R>> {
666    let mut pending = PendingWebview::new(self.webview_attributes, self.label.clone())?;
667    pending.navigation_handler = self.navigation_handler.take();
668    pending.new_window_handler = self.new_window_handler.take().map(|handler| {
669      Box::new(
670        move |url, features: NewWindowFeatures| match handler(url, features) {
671          NewWindowResponse::Allow => tauri_runtime::webview::NewWindowResponse::Allow,
672          #[cfg(mobile)]
673          NewWindowResponse::Create { window: _ } => {
674            tauri_runtime::webview::NewWindowResponse::Allow
675          }
676          #[cfg(desktop)]
677          NewWindowResponse::Create { window } => {
678            tauri_runtime::webview::NewWindowResponse::Create {
679              window_id: window.window.window.id,
680            }
681          }
682          NewWindowResponse::Deny => tauri_runtime::webview::NewWindowResponse::Deny,
683        },
684      )
685        as Box<
686          dyn Fn(Url, NewWindowFeatures) -> tauri_runtime::webview::NewWindowResponse
687            + Send
688            + Sync
689            + 'static,
690        >
691    });
692
693    if let Some(document_title_changed_handler) = self.document_title_changed_handler.take() {
694      let label = pending.label.clone();
695      let manager = manager.manager_owned();
696      pending
697        .document_title_changed_handler
698        .replace(Box::new(move |title| {
699          if let Some(w) = manager.get_webview(&label) {
700            document_title_changed_handler(w, title);
701          }
702        }));
703    }
704    pending.web_resource_request_handler = self.web_resource_request_handler.take();
705
706    if let Some(download_handler) = self.download_handler.take() {
707      let label = pending.label.clone();
708      let manager = manager.manager_owned();
709      pending.download_handler.replace(Arc::new(move |event| {
710        if let Some(w) = manager.get_webview(&label) {
711          download_handler(
712            w,
713            match event {
714              tauri_runtime::webview::DownloadEvent::Requested { url, destination } => {
715                DownloadEvent::Requested { url, destination }
716              }
717              tauri_runtime::webview::DownloadEvent::Finished { url, path, success } => {
718                DownloadEvent::Finished { url, path, success }
719              }
720            },
721          )
722        } else {
723          false
724        }
725      }));
726    }
727
728    let label_ = pending.label.clone();
729    let manager_ = manager.manager_owned();
730    pending
731      .on_page_load_handler
732      .replace(Box::new(move |url, event| {
733        if let Some(w) = manager_.get_webview(&label_) {
734          if let Some(handler) = self.on_page_load_handler.as_ref() {
735            handler(w, PageLoadPayload { url: &url, event });
736          }
737        }
738      }));
739
740    manager
741      .manager()
742      .webview
743      .prepare_webview(manager, pending, window_label)
744  }
745
746  /// Creates a new webview on the given window.
747  #[cfg(desktop)]
748  pub(crate) fn build(
749    self,
750    window: Window<R>,
751    position: Position,
752    size: Size,
753  ) -> crate::Result<Webview<R>> {
754    let app_manager = window.manager();
755
756    let mut pending = self.into_pending_webview(&window, window.label())?;
757
758    pending.webview_attributes.bounds = Some(tauri_runtime::dpi::Rect { size, position });
759
760    let use_https_scheme = pending.webview_attributes.use_https_scheme;
761
762    let webview = match &mut window.runtime() {
763      RuntimeOrDispatch::Dispatch(dispatcher) => dispatcher.create_webview(pending),
764      _ => unimplemented!(),
765    }
766    .map(|webview| {
767      app_manager
768        .webview
769        .attach_webview(window.clone(), webview, use_https_scheme)
770    })?;
771
772    Ok(webview)
773  }
774}
775
776/// Webview attributes.
777impl<R: Runtime> WebviewBuilder<R> {
778  /// Sets whether clicking an inactive window also clicks through to the webview.
779  #[must_use]
780  pub fn accept_first_mouse(mut self, accept: bool) -> Self {
781    self.webview_attributes.accept_first_mouse = accept;
782    self
783  }
784
785  /// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created,
786  /// but before the HTML document has been parsed and before any other script included by the HTML document is run.
787  ///
788  /// Since it runs on all top-level document navigations,
789  /// it's recommended to check the `window.location` to guard your script from running on unexpected origins.
790  ///
791  /// This is executed only on the main frame.
792  /// If you only want to run it in all frames, use [`Self::initialization_script_for_all_frames`] instead.
793  ///
794  /// ## Platform-specific
795  ///
796  /// - **Windows:** scripts are always added to subframes.
797  /// - **Android:** When [addDocumentStartJavaScript] is not supported,
798  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).
799  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.
800  ///
801  /// # Examples
802  ///
803  #[cfg_attr(
804    feature = "unstable",
805    doc = r####"
806```rust
807use tauri::{WindowBuilder, Runtime};
808
809const INIT_SCRIPT: &str = r#"
810  if (window.location.origin === 'https://tauri.app') {
811    console.log("hello world from js init script");
812
813    window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };
814  }
815"#;
816
817fn main() {
818  tauri::Builder::default()
819    .setup(|app| {
820      let window = tauri::window::WindowBuilder::new(app, "label").build()?;
821      let webview_builder = tauri::webview::WebviewBuilder::new("label", tauri::WebviewUrl::App("index.html".into()))
822        .initialization_script(INIT_SCRIPT);
823      let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
824      Ok(())
825    });
826}
827```
828  "####
829  )]
830  ///
831  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)
832  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)
833  #[must_use]
834  pub fn initialization_script(mut self, script: impl Into<String>) -> Self {
835    self
836      .webview_attributes
837      .initialization_scripts
838      .push(InitializationScript {
839        script: script.into(),
840        for_main_frame_only: true,
841      });
842    self
843  }
844
845  /// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created,
846  /// but before the HTML document has been parsed and before any other script included by the HTML document is run.
847  ///
848  /// Since it runs on all top-level document navigations and also child frame page navigations,
849  /// it's recommended to check the `window.location` to guard your script from running on unexpected origins.
850  ///
851  /// This is executed on all frames (main frame and also sub frames).
852  /// If you only want to run the script in the main frame, use [`Self::initialization_script`] instead.
853  ///
854  /// ## Platform-specific
855  ///
856  /// - **Android:** When [addDocumentStartJavaScript] is not supported,
857  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).
858  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.
859  ///
860  /// # Examples
861  ///
862  #[cfg_attr(
863    feature = "unstable",
864    doc = r####"
865```rust
866use tauri::{WindowBuilder, Runtime};
867
868const INIT_SCRIPT: &str = r#"
869  if (window.location.origin === 'https://tauri.app') {
870    console.log("hello world from js init script");
871
872    window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };
873  }
874"#;
875
876fn main() {
877  tauri::Builder::default()
878    .setup(|app| {
879      let window = tauri::window::WindowBuilder::new(app, "label").build()?;
880      let webview_builder = tauri::webview::WebviewBuilder::new("label", tauri::WebviewUrl::App("index.html".into()))
881        .initialization_script_for_all_frames(INIT_SCRIPT);
882      let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
883      Ok(())
884    });
885}
886```
887  "####
888  )]
889  ///
890  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)
891  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)
892  #[must_use]
893  pub fn initialization_script_for_all_frames(mut self, script: impl Into<String>) -> Self {
894    self
895      .webview_attributes
896      .initialization_scripts
897      .push(InitializationScript {
898        script: script.into(),
899        for_main_frame_only: false,
900      });
901    self
902  }
903
904  /// Set the user agent for the webview
905  #[must_use]
906  pub fn user_agent(mut self, user_agent: &str) -> Self {
907    self.webview_attributes.user_agent = Some(user_agent.to_string());
908    self
909  }
910
911  /// Set additional arguments for the webview.
912  ///
913  /// ## Platform-specific
914  ///
915  /// - **macOS / Linux / Android / iOS**: Unsupported.
916  ///
917  /// ## Warning
918  ///
919  /// By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection`
920  /// so if you use this method, you also need to disable these components by yourself if you want.
921  #[must_use]
922  pub fn additional_browser_args(mut self, additional_args: &str) -> Self {
923    self.webview_attributes.additional_browser_args = Some(additional_args.to_string());
924    self
925  }
926
927  /// Data directory for the webview.
928  #[must_use]
929  pub fn data_directory(mut self, data_directory: PathBuf) -> Self {
930    self
931      .webview_attributes
932      .data_directory
933      .replace(data_directory);
934    self
935  }
936
937  /// Disables the drag and drop handler. This is required to use HTML5 drag and drop APIs on the frontend on Windows.
938  #[must_use]
939  pub fn disable_drag_drop_handler(mut self) -> Self {
940    self.webview_attributes.drag_drop_handler_enabled = false;
941    self
942  }
943
944  /// Enables clipboard access for the page rendered on **Linux** and **Windows**.
945  ///
946  /// **macOS** doesn't provide such method and is always enabled by default,
947  /// but you still need to add menu item accelerators to use shortcuts.
948  #[must_use]
949  pub fn enable_clipboard_access(mut self) -> Self {
950    self.webview_attributes.clipboard = true;
951    self
952  }
953
954  /// Enable or disable incognito mode for the WebView.
955  ///
956  ///  ## Platform-specific:
957  ///
958  ///  - **Windows**: Requires WebView2 Runtime version 101.0.1210.39 or higher, does nothing on older versions,
959  ///    see https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/archive?tabs=dotnetcsharp#10121039
960  ///  - **Android**: Unsupported.
961  ///  - **macOS / iOS**: Uses the nonPersistent DataStore
962  #[must_use]
963  pub fn incognito(mut self, incognito: bool) -> Self {
964    self.webview_attributes.incognito = incognito;
965    self
966  }
967
968  /// Set a proxy URL for the WebView for all network requests.
969  ///
970  /// Must be either a `http://` or a `socks5://` URL.
971  ///
972  /// ## Platform-specific
973  ///
974  /// - **macOS**: Requires the `macos-proxy` feature flag and only compiles for macOS 14+.
975  #[must_use]
976  pub fn proxy_url(mut self, url: Url) -> Self {
977    self.webview_attributes.proxy_url = Some(url);
978    self
979  }
980
981  /// Enable or disable transparency for the WebView.
982  #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
983  #[cfg_attr(
984    docsrs,
985    doc(cfg(any(not(target_os = "macos"), feature = "macos-private-api")))
986  )]
987  #[must_use]
988  pub fn transparent(mut self, transparent: bool) -> Self {
989    self.webview_attributes.transparent = transparent;
990    self
991  }
992
993  /// Whether the webview should be focused or not.
994  #[must_use]
995  pub fn focused(mut self, focus: bool) -> Self {
996    self.webview_attributes.focus = focus;
997    self
998  }
999
1000  /// Sets the webview to automatically grow and shrink its size and position when the parent window resizes.
1001  #[must_use]
1002  pub fn auto_resize(mut self) -> Self {
1003    self.webview_attributes.auto_resize = true;
1004    self
1005  }
1006
1007  /// Whether page zooming by hotkeys and mousewheel should be enabled or not.
1008  ///
1009  /// ## Platform-specific:
1010  ///
1011  /// - **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.
1012  /// - **MacOS / Linux**: Injects a polyfill that zooms in and out with `Ctrl/Cmd + [- = +]` hotkeys or mousewheel events,
1013  ///   20% in each step, ranging from 20% to 1000%. Requires `core:webview:allow-set-webview-zoom` permission
1014  ///
1015  /// - **Android / iOS**: Unsupported.
1016  #[must_use]
1017  pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {
1018    self.webview_attributes.zoom_hotkeys_enabled = enabled;
1019    self
1020  }
1021
1022  /// Whether browser extensions can be installed for the webview process
1023  ///
1024  /// ## Platform-specific:
1025  ///
1026  /// - **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)
1027  /// - **MacOS / Linux / iOS / Android** - Unsupported.
1028  #[must_use]
1029  pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self {
1030    self.webview_attributes.browser_extensions_enabled = enabled;
1031    self
1032  }
1033
1034  /// 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.
1035  ///
1036  /// ## Platform-specific:
1037  ///
1038  /// - **Windows**: Browser extensions must first be enabled. See [`browser_extensions_enabled`](Self::browser_extensions_enabled)
1039  /// - **MacOS / iOS / Android** - Unsupported.
1040  #[must_use]
1041  pub fn extensions_path(mut self, path: impl AsRef<Path>) -> Self {
1042    self.webview_attributes.extensions_path = Some(path.as_ref().to_path_buf());
1043    self
1044  }
1045
1046  /// Initialize the WebView with a custom data store identifier.
1047  /// Can be used as a replacement for data_directory not being available in WKWebView.
1048  ///
1049  /// - **macOS / iOS**: Available on macOS >= 14 and iOS >= 17
1050  /// - **Windows / Linux / Android**: Unsupported.
1051  ///
1052  /// Note: Enable incognito mode to use the `nonPersistent` DataStore.
1053  #[must_use]
1054  pub fn data_store_identifier(mut self, data_store_identifier: [u8; 16]) -> Self {
1055    self.webview_attributes.data_store_identifier = Some(data_store_identifier);
1056    self
1057  }
1058
1059  /// Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.
1060  ///
1061  /// ## Note
1062  ///
1063  /// 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.
1064  ///
1065  /// ## Warning
1066  ///
1067  /// 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.
1068  #[must_use]
1069  pub fn use_https_scheme(mut self, enabled: bool) -> Self {
1070    self.webview_attributes.use_https_scheme = enabled;
1071    self
1072  }
1073
1074  /// Whether web inspector, which is usually called browser devtools, is enabled or not. Enabled by default.
1075  ///
1076  /// This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.
1077  ///
1078  /// ## Platform-specific
1079  ///
1080  /// - macOS: This will call private functions on **macOS**
1081  /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.
1082  /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.
1083  #[must_use]
1084  pub fn devtools(mut self, enabled: bool) -> Self {
1085    self.webview_attributes.devtools.replace(enabled);
1086    self
1087  }
1088
1089  /// Set the webview background color.
1090  ///
1091  /// ## Platform-specific:
1092  ///
1093  /// - **macOS / iOS**: Not implemented.
1094  /// - **Windows**: On Windows 7, alpha channel is ignored.
1095  /// - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored.
1096  #[must_use]
1097  pub fn background_color(mut self, color: Color) -> Self {
1098    self.webview_attributes.background_color = Some(color);
1099    self
1100  }
1101
1102  /// Change the default background throttling behaviour.
1103  ///
1104  /// By default, browsers use a suspend policy that will throttle timers and even unload
1105  /// the whole tab (view) to free resources after roughly 5 minutes when a view became
1106  /// minimized or hidden. This will pause all tasks until the documents visibility state
1107  /// changes back from hidden to visible by bringing the view back to the foreground.
1108  ///
1109  /// ## Platform-specific
1110  ///
1111  /// - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.
1112  /// - **iOS**: Supported since version 17.0+.
1113  /// - **macOS**: Supported since version 14.0+.
1114  ///
1115  /// see https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578
1116  #[must_use]
1117  pub fn background_throttling(mut self, policy: BackgroundThrottlingPolicy) -> Self {
1118    self.webview_attributes.background_throttling = Some(policy);
1119    self
1120  }
1121
1122  /// Whether JavaScript should be disabled.
1123  #[must_use]
1124  pub fn disable_javascript(mut self) -> Self {
1125    self.webview_attributes.javascript_disabled = true;
1126    self
1127  }
1128
1129  /// Whether to show a link preview when long pressing on links. Available on macOS and iOS only.
1130  ///
1131  /// Default is true.
1132  ///
1133  /// See https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview
1134  ///
1135  /// ## Platform-specific
1136  ///
1137  /// - **Linux / Windows / Android:** Unsupported.
1138  #[cfg(target_os = "macos")]
1139  #[must_use]
1140  pub fn allow_link_preview(mut self, allow_link_preview: bool) -> Self {
1141    self.webview_attributes = self
1142      .webview_attributes
1143      .allow_link_preview(allow_link_preview);
1144    self
1145  }
1146
1147  /// Allows overriding the the keyboard accessory view on iOS.
1148  /// Returning `None` effectively removes the view.
1149  ///
1150  /// The closure parameter is the webview instance.
1151  ///
1152  /// The accessory view is the view that appears above the keyboard when a text input element is focused.
1153  /// It usually displays a view with "Done", "Next" buttons.
1154  ///
1155  /// # Stability
1156  ///
1157  /// This relies on [`objc2_ui_kit`] which does not provide a stable API yet, so it can receive breaking changes in minor releases.
1158  #[cfg(target_os = "ios")]
1159  pub fn with_input_accessory_view_builder<
1160    F: Fn(&objc2_ui_kit::UIView) -> Option<objc2::rc::Retained<objc2_ui_kit::UIView>>
1161      + Send
1162      + Sync
1163      + 'static,
1164  >(
1165    mut self,
1166    builder: F,
1167  ) -> Self {
1168    self
1169      .webview_attributes
1170      .input_accessory_view_builder
1171      .replace(tauri_runtime::webview::InputAccessoryViewBuilder::new(
1172        Box::new(builder),
1173      ));
1174    self
1175  }
1176
1177  /// Set the environment for the webview.
1178  /// Useful if you need to share the same environment, for instance when using the [`Self::on_new_window`].
1179  #[cfg(all(feature = "wry", windows))]
1180  pub fn with_environment(
1181    mut self,
1182    environment: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment,
1183  ) -> Self {
1184    self.webview_attributes.environment.replace(environment);
1185    self
1186  }
1187
1188  /// Creates a new webview sharing the same web process with the provided webview.
1189  /// Useful if you need to link a webview to another, for instance when using the [`Self::on_new_window`].
1190  #[cfg(all(
1191    feature = "wry",
1192    any(
1193      target_os = "linux",
1194      target_os = "dragonfly",
1195      target_os = "freebsd",
1196      target_os = "netbsd",
1197      target_os = "openbsd",
1198    )
1199  ))]
1200  pub fn with_related_view(mut self, related_view: webkit2gtk::WebView) -> Self {
1201    self.webview_attributes.related_view.replace(related_view);
1202    self
1203  }
1204
1205  /// Set the webview configuration.
1206  /// Useful if you need to share the use a predefined webview configuration, for instance when using the [`Self::on_new_window`].
1207  #[cfg(target_os = "macos")]
1208  pub fn with_webview_configuration(
1209    mut self,
1210    webview_configuration: objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>,
1211  ) -> Self {
1212    self
1213      .webview_attributes
1214      .webview_configuration
1215      .replace(webview_configuration);
1216    self
1217  }
1218}
1219
1220/// Webview.
1221#[default_runtime(crate::Wry, wry)]
1222pub struct Webview<R: Runtime> {
1223  pub(crate) window: Arc<Mutex<Window<R>>>,
1224  /// The webview created by the runtime.
1225  pub(crate) webview: DetachedWebview<EventLoopMessage, R>,
1226  /// The manager to associate this webview with.
1227  pub(crate) manager: Arc<AppManager<R>>,
1228  pub(crate) app_handle: AppHandle<R>,
1229  pub(crate) resources_table: Arc<Mutex<ResourceTable>>,
1230  use_https_scheme: bool,
1231}
1232
1233impl<R: Runtime> std::fmt::Debug for Webview<R> {
1234  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1235    f.debug_struct("Window")
1236      .field("window", &self.window.lock().unwrap())
1237      .field("webview", &self.webview)
1238      .field("use_https_scheme", &self.use_https_scheme)
1239      .finish()
1240  }
1241}
1242
1243impl<R: Runtime> Clone for Webview<R> {
1244  fn clone(&self) -> Self {
1245    Self {
1246      window: self.window.clone(),
1247      webview: self.webview.clone(),
1248      manager: self.manager.clone(),
1249      app_handle: self.app_handle.clone(),
1250      resources_table: self.resources_table.clone(),
1251      use_https_scheme: self.use_https_scheme,
1252    }
1253  }
1254}
1255
1256impl<R: Runtime> Hash for Webview<R> {
1257  /// Only use the [`Webview`]'s label to represent its hash.
1258  fn hash<H: Hasher>(&self, state: &mut H) {
1259    self.webview.label.hash(state)
1260  }
1261}
1262
1263impl<R: Runtime> Eq for Webview<R> {}
1264impl<R: Runtime> PartialEq for Webview<R> {
1265  /// Only use the [`Webview`]'s label to compare equality.
1266  fn eq(&self, other: &Self) -> bool {
1267    self.webview.label.eq(&other.webview.label)
1268  }
1269}
1270
1271/// Base webview functions.
1272impl<R: Runtime> Webview<R> {
1273  /// Create a new webview that is attached to the window.
1274  pub(crate) fn new(
1275    window: Window<R>,
1276    webview: DetachedWebview<EventLoopMessage, R>,
1277    use_https_scheme: bool,
1278  ) -> Self {
1279    Self {
1280      manager: window.manager.clone(),
1281      app_handle: window.app_handle.clone(),
1282      window: Arc::new(Mutex::new(window)),
1283      webview,
1284      resources_table: Default::default(),
1285      use_https_scheme,
1286    }
1287  }
1288
1289  /// Initializes a webview builder with the given window label and URL to load on the webview.
1290  ///
1291  /// Data URLs are only supported with the `webview-data-url` feature flag.
1292  #[cfg(feature = "unstable")]
1293  #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
1294  pub fn builder<L: Into<String>>(label: L, url: WebviewUrl) -> WebviewBuilder<R> {
1295    WebviewBuilder::new(label.into(), url)
1296  }
1297
1298  /// Runs the given closure on the main thread.
1299  pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {
1300    self
1301      .webview
1302      .dispatcher
1303      .run_on_main_thread(f)
1304      .map_err(Into::into)
1305  }
1306
1307  /// The webview label.
1308  pub fn label(&self) -> &str {
1309    &self.webview.label
1310  }
1311
1312  /// Whether the webview was configured to use the HTTPS scheme or not.
1313  pub(crate) fn use_https_scheme(&self) -> bool {
1314    self.use_https_scheme
1315  }
1316
1317  /// Registers a webview event listener.
1318  pub fn on_webview_event<F: Fn(&WebviewEvent) + Send + 'static>(&self, f: F) {
1319    self
1320      .webview
1321      .dispatcher
1322      .on_webview_event(move |event| f(&event.clone().into()));
1323  }
1324
1325  /// Resolves the given command scope for this webview on the currently loaded URL.
1326  ///
1327  /// If the command is not allowed, returns None.
1328  ///
1329  /// If the scope cannot be deserialized to the given type, an error is returned.
1330  ///
1331  /// In a command context this can be directly resolved from the command arguments via [CommandScope]:
1332  ///
1333  /// ```
1334  /// use tauri::ipc::CommandScope;
1335  ///
1336  /// #[derive(Debug, serde::Deserialize)]
1337  /// struct ScopeType {
1338  ///   some_value: String,
1339  /// }
1340  /// #[tauri::command]
1341  /// fn my_command(scope: CommandScope<ScopeType>) {
1342  ///   // check scope
1343  /// }
1344  /// ```
1345  ///
1346  /// # Examples
1347  ///
1348  /// ```
1349  /// use tauri::Manager;
1350  ///
1351  /// #[derive(Debug, serde::Deserialize)]
1352  /// struct ScopeType {
1353  ///   some_value: String,
1354  /// }
1355  ///
1356  /// tauri::Builder::default()
1357  ///   .setup(|app| {
1358  ///     let webview = app.get_webview_window("main").unwrap();
1359  ///     let scope = webview.resolve_command_scope::<ScopeType>("my-plugin", "read");
1360  ///     Ok(())
1361  ///   });
1362  /// ```
1363  pub fn resolve_command_scope<T: ScopeObject>(
1364    &self,
1365    plugin: &str,
1366    command: &str,
1367  ) -> crate::Result<Option<ResolvedScope<T>>> {
1368    let current_url = self.url()?;
1369    let is_local = self.is_local_url(&current_url);
1370    let origin = if is_local {
1371      Origin::Local
1372    } else {
1373      Origin::Remote { url: current_url }
1374    };
1375
1376    let cmd_name = format!("plugin:{plugin}|{command}");
1377    let resolved_access = self
1378      .manager()
1379      .runtime_authority
1380      .lock()
1381      .unwrap()
1382      .resolve_access(&cmd_name, self.window().label(), self.label(), &origin);
1383
1384    if let Some(access) = resolved_access {
1385      let scope_ids = access
1386        .iter()
1387        .filter_map(|cmd| cmd.scope_id)
1388        .collect::<Vec<_>>();
1389
1390      let command_scope = CommandScope::resolve(self, scope_ids)?;
1391      let global_scope = GlobalScope::resolve(self, plugin)?;
1392
1393      Ok(Some(ResolvedScope {
1394        global_scope,
1395        command_scope,
1396      }))
1397    } else {
1398      Ok(None)
1399    }
1400  }
1401}
1402
1403/// Desktop webview setters and actions.
1404#[cfg(desktop)]
1405impl<R: Runtime> Webview<R> {
1406  /// Opens the dialog to prints the contents of the webview.
1407  /// Currently only supported on macOS on `wry`.
1408  /// `window.print()` works on all platforms.
1409  pub fn print(&self) -> crate::Result<()> {
1410    self.webview.dispatcher.print().map_err(Into::into)
1411  }
1412
1413  /// Get the cursor position relative to the top-left hand corner of the desktop.
1414  ///
1415  /// Note that the top-left hand corner of the desktop is not necessarily the same as the screen.
1416  /// If the user uses a desktop with multiple monitors,
1417  /// the top-left hand corner of the desktop is the top-left hand corner of the main monitor on Windows and macOS
1418  /// or the top-left of the leftmost monitor on X11.
1419  ///
1420  /// The coordinates can be negative if the top-left hand corner of the window is outside of the visible screen region.
1421  pub fn cursor_position(&self) -> crate::Result<PhysicalPosition<f64>> {
1422    self.app_handle.cursor_position()
1423  }
1424
1425  /// Closes this webview.
1426  pub fn close(&self) -> crate::Result<()> {
1427    self.webview.dispatcher.close()?;
1428    self.manager().on_webview_close(self.label());
1429    Ok(())
1430  }
1431
1432  /// Resizes this webview.
1433  pub fn set_bounds(&self, bounds: tauri_runtime::dpi::Rect) -> crate::Result<()> {
1434    self
1435      .webview
1436      .dispatcher
1437      .set_bounds(bounds)
1438      .map_err(Into::into)
1439  }
1440
1441  /// Resizes this webview.
1442  pub fn set_size<S: Into<Size>>(&self, size: S) -> crate::Result<()> {
1443    self
1444      .webview
1445      .dispatcher
1446      .set_size(size.into())
1447      .map_err(Into::into)
1448  }
1449
1450  /// Sets this webviews's position.
1451  pub fn set_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {
1452    self
1453      .webview
1454      .dispatcher
1455      .set_position(position.into())
1456      .map_err(Into::into)
1457  }
1458
1459  /// Focus the webview.
1460  pub fn set_focus(&self) -> crate::Result<()> {
1461    self.webview.dispatcher.set_focus().map_err(Into::into)
1462  }
1463
1464  /// Hide the webview.
1465  pub fn hide(&self) -> crate::Result<()> {
1466    self.webview.dispatcher.hide().map_err(Into::into)
1467  }
1468
1469  /// Show the webview.
1470  pub fn show(&self) -> crate::Result<()> {
1471    self.webview.dispatcher.show().map_err(Into::into)
1472  }
1473
1474  /// Move the webview to the given window.
1475  pub fn reparent(&self, window: &Window<R>) -> crate::Result<()> {
1476    #[cfg(not(feature = "unstable"))]
1477    {
1478      if self.window_ref().is_webview_window() || window.is_webview_window() {
1479        return Err(crate::Error::CannotReparentWebviewWindow);
1480      }
1481    }
1482
1483    *self.window.lock().unwrap() = window.clone();
1484    self.webview.dispatcher.reparent(window.window.id)?;
1485    Ok(())
1486  }
1487
1488  /// Sets whether the webview should automatically grow and shrink its size and position when the parent window resizes.
1489  pub fn set_auto_resize(&self, auto_resize: bool) -> crate::Result<()> {
1490    self
1491      .webview
1492      .dispatcher
1493      .set_auto_resize(auto_resize)
1494      .map_err(Into::into)
1495  }
1496
1497  /// Returns the bounds of the webviews's client area.
1498  pub fn bounds(&self) -> crate::Result<tauri_runtime::dpi::Rect> {
1499    self.webview.dispatcher.bounds().map_err(Into::into)
1500  }
1501
1502  /// Returns the webview position.
1503  ///
1504  /// - 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.
1505  /// - For webview window, returns the inner position of the window.
1506  pub fn position(&self) -> crate::Result<PhysicalPosition<i32>> {
1507    self.webview.dispatcher.position().map_err(Into::into)
1508  }
1509
1510  /// Returns the physical size of the webviews's client area.
1511  pub fn size(&self) -> crate::Result<PhysicalSize<u32>> {
1512    self.webview.dispatcher.size().map_err(Into::into)
1513  }
1514}
1515
1516/// Webview APIs.
1517impl<R: Runtime> Webview<R> {
1518  /// The window that is hosting this webview.
1519  pub fn window(&self) -> Window<R> {
1520    self.window.lock().unwrap().clone()
1521  }
1522
1523  /// A reference to the window that is hosting this webview.
1524  pub fn window_ref(&self) -> MutexGuard<'_, Window<R>> {
1525    self.window.lock().unwrap()
1526  }
1527
1528  pub(crate) fn window_label(&self) -> String {
1529    self.window_ref().label().to_string()
1530  }
1531
1532  /// Executes a closure, providing it with the webview handle that is specific to the current platform.
1533  ///
1534  /// The closure is executed on the main thread.
1535  ///
1536  /// Note that `webview2-com`, `webkit2gtk`, `objc2_web_kit` and similar crates may be updated in minor releases of Tauri.
1537  /// Therefore it's recommended to pin Tauri to at least a minor version when you're using `with_webview`.
1538  ///
1539  /// # Examples
1540  ///
1541  #[cfg_attr(
1542    feature = "unstable",
1543    doc = r####"
1544```rust,no_run
1545use tauri::Manager;
1546
1547tauri::Builder::default()
1548  .setup(|app| {
1549    let main_webview = app.get_webview("main").unwrap();
1550    main_webview.with_webview(|webview| {
1551      #[cfg(target_os = "linux")]
1552      {
1553        // see <https://docs.rs/webkit2gtk/2.0.0/webkit2gtk/struct.WebView.html>
1554        // and <https://docs.rs/webkit2gtk/2.0.0/webkit2gtk/trait.WebViewExt.html>
1555        use webkit2gtk::WebViewExt;
1556        webview.inner().set_zoom_level(4.);
1557      }
1558
1559      #[cfg(windows)]
1560      unsafe {
1561        // see https://docs.rs/webview2-com/0.19.1/webview2_com/Microsoft/Web/WebView2/Win32/struct.ICoreWebView2Controller.html
1562        webview.controller().SetZoomFactor(4.).unwrap();
1563      }
1564
1565      #[cfg(target_os = "macos")]
1566      unsafe {
1567        let view: &objc2_web_kit::WKWebView = &*webview.inner().cast();
1568        let controller: &objc2_web_kit::WKUserContentController = &*webview.controller().cast();
1569        let window: &objc2_app_kit::NSWindow = &*webview.ns_window().cast();
1570
1571        view.setPageZoom(4.);
1572        controller.removeAllUserScripts();
1573        let bg_color = objc2_app_kit::NSColor::colorWithDeviceRed_green_blue_alpha(0.5, 0.2, 0.4, 1.);
1574        window.setBackgroundColor(Some(&bg_color));
1575      }
1576
1577      #[cfg(target_os = "android")]
1578      {
1579        use jni::objects::JValue;
1580        webview.jni_handle().exec(|env, _, webview| {
1581          env.call_method(webview, "zoomBy", "(F)V", &[JValue::Float(4.)]).unwrap();
1582        })
1583      }
1584    });
1585    Ok(())
1586});
1587```
1588  "####
1589  )]
1590  #[cfg(feature = "wry")]
1591  #[cfg_attr(docsrs, doc(feature = "wry"))]
1592  pub fn with_webview<F: FnOnce(PlatformWebview) + Send + 'static>(
1593    &self,
1594    f: F,
1595  ) -> crate::Result<()> {
1596    self
1597      .webview
1598      .dispatcher
1599      .with_webview(|w| f(PlatformWebview(*w.downcast().unwrap())))
1600      .map_err(Into::into)
1601  }
1602
1603  /// Returns the current url of the webview.
1604  pub fn url(&self) -> crate::Result<Url> {
1605    self
1606      .webview
1607      .dispatcher
1608      .url()
1609      .map(|url| url.parse().map_err(crate::Error::InvalidUrl))?
1610  }
1611
1612  /// Navigates the webview to the defined url.
1613  pub fn navigate(&self, url: Url) -> crate::Result<()> {
1614    self.webview.dispatcher.navigate(url).map_err(Into::into)
1615  }
1616
1617  /// Reloads the current page.
1618  pub fn reload(&self) -> crate::Result<()> {
1619    self.webview.dispatcher.reload().map_err(Into::into)
1620  }
1621
1622  fn is_local_url(&self, current_url: &Url) -> bool {
1623    let uses_https = current_url.scheme() == "https";
1624
1625    // if from `tauri://` custom protocol
1626    ({
1627      let protocol_url = self.manager().protocol_url(uses_https);
1628      current_url.scheme() == protocol_url.scheme()
1629      && current_url.domain() == protocol_url.domain()
1630    }) ||
1631
1632    // or if relative to `devUrl` or `frontendDist`
1633      self
1634          .manager()
1635          .get_url(uses_https)
1636          .make_relative(current_url)
1637          .is_some()
1638
1639      // or from a custom protocol registered by the user
1640      || ({
1641        let scheme = current_url.scheme();
1642        let protocols = self.manager().webview.uri_scheme_protocols.lock().unwrap();
1643
1644        #[cfg(all(not(windows), not(target_os = "android")))]
1645        let local = protocols.contains_key(scheme);
1646
1647        // on window and android, custom protocols are `http://<protocol-name>.path/to/route`
1648        // so we check using the first part of the domain
1649        #[cfg(any(windows, target_os = "android"))]
1650        let local = {
1651          let protocol_url = self.manager().protocol_url(uses_https);
1652          let maybe_protocol = current_url
1653            .domain()
1654            .and_then(|d| d .split_once('.'))
1655            .unwrap_or_default()
1656            .0;
1657
1658          protocols.contains_key(maybe_protocol) && scheme == protocol_url.scheme()
1659        };
1660
1661        local
1662      })
1663  }
1664
1665  /// Handles this window receiving an [`InvokeRequest`].
1666  pub fn on_message(self, request: InvokeRequest, responder: Box<OwnedInvokeResponder<R>>) {
1667    let manager = self.manager_owned();
1668    let is_local = self.is_local_url(&request.url);
1669
1670    // ensure the passed key matches what our manager should have injected
1671    let expected = manager.invoke_key();
1672    if request.invoke_key != expected {
1673      #[cfg(feature = "tracing")]
1674      tracing::error!(
1675        "__TAURI_INVOKE_KEY__ expected {expected} but received {}",
1676        request.invoke_key
1677      );
1678
1679      #[cfg(not(feature = "tracing"))]
1680      eprintln!(
1681        "__TAURI_INVOKE_KEY__ expected {expected} but received {}",
1682        request.invoke_key
1683      );
1684
1685      return;
1686    }
1687
1688    let resolver = InvokeResolver::new(
1689      self.clone(),
1690      Arc::new(Mutex::new(Some(Box::new(
1691        move |webview: Webview<R>, cmd, response, callback, error| {
1692          responder(webview, cmd, response, callback, error);
1693        },
1694      )))),
1695      request.cmd.clone(),
1696      request.callback,
1697      request.error,
1698    );
1699
1700    #[cfg(mobile)]
1701    let app_handle = self.app_handle.clone();
1702
1703    let message = InvokeMessage::new(
1704      self,
1705      manager.state(),
1706      request.cmd.to_string(),
1707      request.body,
1708      request.headers,
1709    );
1710
1711    let acl_origin = if is_local {
1712      Origin::Local
1713    } else {
1714      Origin::Remote {
1715        url: request.url.clone(),
1716      }
1717    };
1718    let (resolved_acl, has_app_acl_manifest) = {
1719      let runtime_authority = manager.runtime_authority.lock().unwrap();
1720      let acl = runtime_authority.resolve_access(
1721        &request.cmd,
1722        message.webview.window_ref().label(),
1723        message.webview.label(),
1724        &acl_origin,
1725      );
1726      (acl, runtime_authority.has_app_manifest())
1727    };
1728
1729    let mut invoke = Invoke {
1730      message,
1731      resolver: resolver.clone(),
1732      acl: resolved_acl,
1733    };
1734
1735    let plugin_command = request.cmd.strip_prefix("plugin:").map(|raw_command| {
1736      let mut tokens = raw_command.split('|');
1737      // safe to unwrap: split always has a least one item
1738      let plugin = tokens.next().unwrap();
1739      let command = tokens.next().map(|c| c.to_string()).unwrap_or_default();
1740      (plugin, command)
1741    });
1742
1743    // we only check ACL on plugin commands or if the app defined its ACL manifest
1744    if (plugin_command.is_some() || has_app_acl_manifest)
1745      // TODO: Remove this special check in v3
1746      && request.cmd != crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND
1747      && invoke.acl.is_none()
1748    {
1749      #[cfg(debug_assertions)]
1750      {
1751        let (key, command_name) = plugin_command
1752          .clone()
1753          .unwrap_or_else(|| (tauri_utils::acl::APP_ACL_KEY, request.cmd.clone()));
1754        invoke.resolver.reject(
1755          manager
1756            .runtime_authority
1757            .lock()
1758            .unwrap()
1759            .resolve_access_message(
1760              key,
1761              &command_name,
1762              invoke.message.webview.window().label(),
1763              invoke.message.webview.label(),
1764              &acl_origin,
1765            ),
1766        );
1767      }
1768      #[cfg(not(debug_assertions))]
1769      invoke
1770        .resolver
1771        .reject(format!("Command {} not allowed by ACL", request.cmd));
1772      return;
1773    }
1774
1775    if let Some((plugin, command_name)) = plugin_command {
1776      invoke.message.command = command_name;
1777
1778      let command = invoke.message.command.clone();
1779
1780      #[cfg(mobile)]
1781      let message = invoke.message.clone();
1782
1783      #[allow(unused_mut)]
1784      let mut handled = manager.extend_api(plugin, invoke);
1785
1786      #[cfg(mobile)]
1787      {
1788        if !handled {
1789          handled = true;
1790
1791          fn load_channels<R: Runtime>(payload: &serde_json::Value, webview: &Webview<R>) {
1792            use std::str::FromStr;
1793
1794            if let serde_json::Value::Object(map) = payload {
1795              for v in map.values() {
1796                if let serde_json::Value::String(s) = v {
1797                  let _ = crate::ipc::JavaScriptChannelId::from_str(s)
1798                    .map(|id| id.channel_on::<R, ()>(webview.clone()));
1799                }
1800              }
1801            }
1802          }
1803
1804          let payload = message.payload.into_json();
1805          // initialize channels
1806          load_channels(&payload, &message.webview);
1807
1808          let resolver_ = resolver.clone();
1809          if let Err(e) = crate::plugin::mobile::run_command(
1810            plugin,
1811            &app_handle,
1812            heck::AsLowerCamelCase(message.command).to_string(),
1813            payload,
1814            move |response| match response {
1815              Ok(r) => resolver_.resolve(r),
1816              Err(e) => resolver_.reject(e),
1817            },
1818          ) {
1819            resolver.reject(e.to_string());
1820            return;
1821          }
1822        }
1823      }
1824
1825      if !handled {
1826        resolver.reject(format!("Command {command} not found"));
1827      }
1828    } else {
1829      let command = invoke.message.command.clone();
1830      let handled = manager.run_invoke_handler(invoke);
1831      if !handled {
1832        resolver.reject(format!("Command {command} not found"));
1833      }
1834    }
1835  }
1836
1837  /// Evaluates JavaScript on this window.
1838  pub fn eval(&self, js: impl Into<String>) -> crate::Result<()> {
1839    self
1840      .webview
1841      .dispatcher
1842      .eval_script(js.into())
1843      .map_err(Into::into)
1844  }
1845
1846  /// Register a JS event listener and return its identifier.
1847  pub(crate) fn listen_js(
1848    &self,
1849    event: EventName<&str>,
1850    target: EventTarget,
1851    handler: CallbackFn,
1852  ) -> crate::Result<EventId> {
1853    let listeners = self.manager().listeners();
1854
1855    let id = listeners.next_event_id();
1856
1857    self.eval(crate::event::listen_js_script(
1858      listeners.listeners_object_name(),
1859      &serde_json::to_string(&target)?,
1860      event,
1861      id,
1862      handler,
1863    ))?;
1864
1865    listeners.listen_js(event, self.label(), target, id);
1866
1867    Ok(id)
1868  }
1869
1870  /// Unregister a JS event listener.
1871  pub(crate) fn unlisten_js(&self, event: EventName<&str>, id: EventId) -> crate::Result<()> {
1872    let listeners = self.manager().listeners();
1873
1874    listeners.unlisten_js(event, id);
1875
1876    Ok(())
1877  }
1878
1879  pub(crate) fn emit_js(&self, emit_args: &EmitArgs, ids: &[u32]) -> crate::Result<()> {
1880    self.eval(crate::event::emit_js_script(
1881      self.manager().listeners().function_name(),
1882      emit_args,
1883      &serde_json::to_string(ids)?,
1884    )?)?;
1885    Ok(())
1886  }
1887
1888  /// Opens the developer tools window (Web Inspector).
1889  /// The devtools is only enabled on debug builds or with the `devtools` feature flag.
1890  ///
1891  /// ## Platform-specific
1892  ///
1893  /// - **macOS:** Only supported on macOS 10.15+.
1894  ///   This is a private API on macOS, so you cannot use this if your application will be published on the App Store.
1895  ///
1896  /// # Examples
1897  ///
1898  #[cfg_attr(
1899    feature = "unstable",
1900    doc = r####"
1901```rust,no_run
1902use tauri::Manager;
1903tauri::Builder::default()
1904  .setup(|app| {
1905    #[cfg(debug_assertions)]
1906    app.get_webview("main").unwrap().open_devtools();
1907    Ok(())
1908  });
1909```
1910  "####
1911  )]
1912  #[cfg(any(debug_assertions, feature = "devtools"))]
1913  #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = "devtools"))))]
1914  pub fn open_devtools(&self) {
1915    self.webview.dispatcher.open_devtools();
1916  }
1917
1918  /// Closes the developer tools window (Web Inspector).
1919  /// The devtools is only enabled on debug builds or with the `devtools` feature flag.
1920  ///
1921  /// ## Platform-specific
1922  ///
1923  /// - **macOS:** Only supported on macOS 10.15+.
1924  ///   This is a private API on macOS, so you cannot use this if your application will be published on the App Store.
1925  /// - **Windows:** Unsupported.
1926  ///
1927  /// # Examples
1928  ///
1929  #[cfg_attr(
1930    feature = "unstable",
1931    doc = r####"
1932```rust,no_run
1933use tauri::Manager;
1934tauri::Builder::default()
1935  .setup(|app| {
1936    #[cfg(debug_assertions)]
1937    {
1938      let webview = app.get_webview("main").unwrap();
1939      webview.open_devtools();
1940      std::thread::spawn(move || {
1941        std::thread::sleep(std::time::Duration::from_secs(10));
1942        webview.close_devtools();
1943      });
1944    }
1945    Ok(())
1946  });
1947```
1948  "####
1949  )]
1950  #[cfg(any(debug_assertions, feature = "devtools"))]
1951  #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = "devtools"))))]
1952  pub fn close_devtools(&self) {
1953    self.webview.dispatcher.close_devtools();
1954  }
1955
1956  /// Checks if the developer tools window (Web Inspector) is opened.
1957  /// The devtools is only enabled on debug builds or with the `devtools` feature flag.
1958  ///
1959  /// ## Platform-specific
1960  ///
1961  /// - **macOS:** Only supported on macOS 10.15+.
1962  ///   This is a private API on macOS, so you cannot use this if your application will be published on the App Store.
1963  /// - **Windows:** Unsupported.
1964  ///
1965  /// # Examples
1966  ///
1967  #[cfg_attr(
1968    feature = "unstable",
1969    doc = r####"
1970```rust,no_run
1971use tauri::Manager;
1972tauri::Builder::default()
1973  .setup(|app| {
1974    #[cfg(debug_assertions)]
1975    {
1976      let webview = app.get_webview("main").unwrap();
1977      if !webview.is_devtools_open() {
1978        webview.open_devtools();
1979      }
1980    }
1981    Ok(())
1982  });
1983```
1984  "####
1985  )]
1986  #[cfg(any(debug_assertions, feature = "devtools"))]
1987  #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = "devtools"))))]
1988  pub fn is_devtools_open(&self) -> bool {
1989    self
1990      .webview
1991      .dispatcher
1992      .is_devtools_open()
1993      .unwrap_or_default()
1994  }
1995
1996  /// Set the webview zoom level
1997  ///
1998  /// ## Platform-specific:
1999  ///
2000  /// - **Android**: Not supported.
2001  /// - **macOS**: available on macOS 11+ only.
2002  /// - **iOS**: available on iOS 14+ only.
2003  pub fn set_zoom(&self, scale_factor: f64) -> crate::Result<()> {
2004    self
2005      .webview
2006      .dispatcher
2007      .set_zoom(scale_factor)
2008      .map_err(Into::into)
2009  }
2010
2011  /// Specify the webview background color.
2012  ///
2013  /// ## Platfrom-specific:
2014  ///
2015  /// - **macOS / iOS**: Not implemented.
2016  /// - **Windows**:
2017  ///   - On Windows 7, transparency is not supported and the alpha value will be ignored.
2018  ///   - On Windows higher than 7: translucent colors are not supported so any alpha value other than `0` will be replaced by `255`
2019  pub fn set_background_color(&self, color: Option<Color>) -> crate::Result<()> {
2020    self
2021      .webview
2022      .dispatcher
2023      .set_background_color(color)
2024      .map_err(Into::into)
2025  }
2026
2027  /// Clear all browsing data for this webview.
2028  pub fn clear_all_browsing_data(&self) -> crate::Result<()> {
2029    self
2030      .webview
2031      .dispatcher
2032      .clear_all_browsing_data()
2033      .map_err(Into::into)
2034  }
2035
2036  /// Returns all cookies in the runtime's cookie store including HTTP-only and secure cookies.
2037  ///
2038  /// Note that cookies will only be returned for URLs with an http or https scheme.
2039  /// Cookies set through javascript for local files
2040  /// (such as those served from the tauri://) protocol are not currently supported.
2041  ///
2042  /// # Stability
2043  ///
2044  /// See [Self::cookies].
2045  ///
2046  /// # Known issues
2047  ///
2048  /// See [Self::cookies].
2049  pub fn cookies_for_url(&self, url: Url) -> crate::Result<Vec<Cookie<'static>>> {
2050    self
2051      .webview
2052      .dispatcher
2053      .cookies_for_url(url)
2054      .map_err(Into::into)
2055  }
2056
2057  /// Returns all cookies in the runtime's cookie store for all URLs including HTTP-only and secure cookies.
2058  ///
2059  /// Note that cookies will only be returned for URLs with an http or https scheme.
2060  /// Cookies set through javascript for local files
2061  /// (such as those served from the tauri://) protocol are not currently supported.
2062  ///
2063  /// # Stability
2064  ///
2065  /// The return value of this function leverages [`tauri_runtime::Cookie`] which re-exports the cookie crate.
2066  /// This dependency might receive updates in minor Tauri releases.
2067  ///
2068  /// # Known issues
2069  ///
2070  /// On Windows, this function deadlocks when used in a synchronous command or event handlers, see [the Webview2 issue].
2071  /// You should use `async` commands and separate threads when reading cookies.
2072  ///
2073  /// ## Platform-specific
2074  ///
2075  /// - **Android**: Unsupported, always returns an empty [`Vec`].
2076  ///
2077  /// [the Webview2 issue]: https://github.com/tauri-apps/wry/issues/583
2078  pub fn cookies(&self) -> crate::Result<Vec<Cookie<'static>>> {
2079    self.webview.dispatcher.cookies().map_err(Into::into)
2080  }
2081
2082  /// Set a cookie for the webview.
2083  ///
2084  /// # Stability
2085  ///
2086  /// See [Self::cookies].
2087  pub fn set_cookie(&self, cookie: Cookie<'_>) -> crate::Result<()> {
2088    self
2089      .webview
2090      .dispatcher
2091      .set_cookie(cookie)
2092      .map_err(Into::into)
2093  }
2094
2095  /// Delete a cookie for the webview.
2096  ///
2097  /// # Stability
2098  ///
2099  /// See [Self::cookies].
2100  pub fn delete_cookie(&self, cookie: Cookie<'_>) -> crate::Result<()> {
2101    self
2102      .webview
2103      .dispatcher
2104      .delete_cookie(cookie)
2105      .map_err(Into::into)
2106  }
2107}
2108
2109impl<R: Runtime> Listener<R> for Webview<R> {
2110  /// Listen to an event on this webview.
2111  ///
2112  /// # Examples
2113  #[cfg_attr(
2114    feature = "unstable",
2115    doc = r####"
2116```
2117use tauri::{Manager, Listener};
2118
2119tauri::Builder::default()
2120  .setup(|app| {
2121    let webview = app.get_webview("main").unwrap();
2122    webview.listen("component-loaded", move |event| {
2123      println!("webview just loaded a component");
2124    });
2125
2126    Ok(())
2127  });
2128```
2129  "####
2130  )]
2131  fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventId
2132  where
2133    F: Fn(Event) + Send + 'static,
2134  {
2135    let event = EventName::new(event.into()).unwrap();
2136    self.manager.listen(
2137      event,
2138      EventTarget::Webview {
2139        label: self.label().to_string(),
2140      },
2141      handler,
2142    )
2143  }
2144
2145  /// Listen to an event on this webview only once.
2146  ///
2147  /// See [`Self::listen`] for more information.
2148  fn once<F>(&self, event: impl Into<String>, handler: F) -> EventId
2149  where
2150    F: FnOnce(Event) + Send + 'static,
2151  {
2152    let event = EventName::new(event.into()).unwrap();
2153    self.manager.once(
2154      event,
2155      EventTarget::Webview {
2156        label: self.label().to_string(),
2157      },
2158      handler,
2159    )
2160  }
2161
2162  /// Unlisten to an event on this webview.
2163  ///
2164  /// # Examples
2165  #[cfg_attr(
2166    feature = "unstable",
2167    doc = r####"
2168```
2169use tauri::{Manager, Listener};
2170
2171tauri::Builder::default()
2172  .setup(|app| {
2173    let webview = app.get_webview("main").unwrap();
2174    let webview_ = webview.clone();
2175    let handler = webview.listen("component-loaded", move |event| {
2176      println!("webview just loaded a component");
2177
2178      // we no longer need to listen to the event
2179      // we also could have used `webview.once` instead
2180      webview_.unlisten(event.id());
2181    });
2182
2183    // stop listening to the event when you do not need it anymore
2184    webview.unlisten(handler);
2185
2186    Ok(())
2187  });
2188```
2189  "####
2190  )]
2191  fn unlisten(&self, id: EventId) {
2192    self.manager.unlisten(id)
2193  }
2194}
2195
2196impl<R: Runtime> Emitter<R> for Webview<R> {}
2197
2198impl<R: Runtime> Manager<R> for Webview<R> {
2199  fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {
2200    self
2201      .resources_table
2202      .lock()
2203      .expect("poisoned window resources table")
2204  }
2205}
2206
2207impl<R: Runtime> ManagerBase<R> for Webview<R> {
2208  fn manager(&self) -> &AppManager<R> {
2209    &self.manager
2210  }
2211
2212  fn manager_owned(&self) -> Arc<AppManager<R>> {
2213    self.manager.clone()
2214  }
2215
2216  fn runtime(&self) -> RuntimeOrDispatch<'_, R> {
2217    self.app_handle.runtime()
2218  }
2219
2220  fn managed_app_handle(&self) -> &AppHandle<R> {
2221    &self.app_handle
2222  }
2223}
2224
2225impl<'de, R: Runtime> CommandArg<'de, R> for Webview<R> {
2226  /// Grabs the [`Webview`] from the [`CommandItem`]. This will never fail.
2227  fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {
2228    Ok(command.message.webview())
2229  }
2230}
2231
2232/// Resolved scope that can be obtained via [`Webview::resolve_command_scope`].
2233pub struct ResolvedScope<T: ScopeObject> {
2234  command_scope: CommandScope<T>,
2235  global_scope: GlobalScope<T>,
2236}
2237
2238impl<T: ScopeObject> ResolvedScope<T> {
2239  /// The global plugin scope.
2240  pub fn global_scope(&self) -> &GlobalScope<T> {
2241    &self.global_scope
2242  }
2243
2244  /// The command-specific scope.
2245  pub fn command_scope(&self) -> &CommandScope<T> {
2246    &self.command_scope
2247  }
2248}
2249
2250#[cfg(test)]
2251mod tests {
2252  #[test]
2253  fn webview_is_send_sync() {
2254    crate::test_utils::assert_send::<super::Webview>();
2255    crate::test_utils::assert_sync::<super::Webview>();
2256  }
2257}