1pub(crate) mod plugin;
8mod webview_window;
9
10pub use webview_window::{WebviewWindow, WebviewWindowBuilder};
11
12use http::HeaderMap;
13use serde::Serialize;
14use tauri_macros::default_runtime;
15pub use tauri_runtime::webview::PageLoadEvent;
16pub use tauri_runtime::Cookie;
17#[cfg(desktop)]
18use tauri_runtime::{
19 dpi::{PhysicalPosition, PhysicalSize, Position, Size},
20 WindowDispatch,
21};
22use tauri_runtime::{
23 webview::{DetachedWebview, InitializationScript, PendingWebview, WebviewAttributes},
24 WebviewDispatch,
25};
26pub use tauri_utils::config::Color;
27use tauri_utils::config::{BackgroundThrottlingPolicy, WebviewUrl, WindowConfig};
28pub use url::Url;
29
30use crate::{
31 app::{UriSchemeResponder, WebviewEvent},
32 event::{EmitArgs, EventTarget},
33 ipc::{
34 CallbackFn, CommandArg, CommandItem, CommandScope, GlobalScope, Invoke, InvokeBody,
35 InvokeError, InvokeMessage, InvokeResolver, Origin, OwnedInvokeResponder, ScopeObject,
36 },
37 manager::AppManager,
38 sealed::{ManagerBase, RuntimeOrDispatch},
39 AppHandle, Emitter, Event, EventId, EventLoopMessage, EventName, Listener, Manager,
40 ResourceTable, Runtime, Window,
41};
42
43use std::{
44 borrow::Cow,
45 hash::{Hash, Hasher},
46 path::{Path, PathBuf},
47 sync::{Arc, Mutex, MutexGuard},
48};
49
50pub(crate) type WebResourceRequestHandler =
51 dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
52pub(crate) type NavigationHandler = dyn Fn(&Url) -> bool + Send;
53pub(crate) type UriSchemeProtocolHandler =
54 Box<dyn Fn(&str, http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync>;
55pub(crate) type OnPageLoad<R> = dyn Fn(Webview<R>, PageLoadPayload<'_>) + Send + Sync + 'static;
56
57pub(crate) type DownloadHandler<R> = dyn Fn(Webview<R>, DownloadEvent<'_>) -> bool + Send + Sync;
58
59#[derive(Clone, Serialize)]
60pub(crate) struct CreatedEvent {
61 pub(crate) label: String,
62}
63
64#[non_exhaustive]
66pub enum DownloadEvent<'a> {
67 Requested {
69 url: Url,
71 destination: &'a mut PathBuf,
75 },
76 Finished {
78 url: Url,
80 path: Option<PathBuf>,
91 success: bool,
93 },
94}
95
96#[derive(Debug, Clone)]
98pub struct PageLoadPayload<'a> {
99 pub(crate) url: &'a Url,
100 pub(crate) event: PageLoadEvent,
101}
102
103impl<'a> PageLoadPayload<'a> {
104 pub fn url(&self) -> &'a Url {
106 self.url
107 }
108
109 pub fn event(&self) -> PageLoadEvent {
111 self.event
112 }
113}
114
115#[derive(Debug)]
122pub struct InvokeRequest {
123 pub cmd: String,
125 pub callback: CallbackFn,
127 pub error: CallbackFn,
129 pub url: Url,
131 pub body: InvokeBody,
133 pub headers: HeaderMap,
135 pub invoke_key: String,
137}
138
139#[cfg(feature = "wry")]
141#[cfg_attr(docsrs, doc(cfg(feature = "wry")))]
142pub struct PlatformWebview(tauri_runtime_wry::Webview);
143
144#[cfg(feature = "wry")]
145impl PlatformWebview {
146 #[cfg(any(
148 target_os = "linux",
149 target_os = "dragonfly",
150 target_os = "freebsd",
151 target_os = "netbsd",
152 target_os = "openbsd"
153 ))]
154 #[cfg_attr(
155 docsrs,
156 doc(cfg(any(
157 target_os = "linux",
158 target_os = "dragonfly",
159 target_os = "freebsd",
160 target_os = "netbsd",
161 target_os = "openbsd"
162 )))
163 )]
164 pub fn inner(&self) -> webkit2gtk::WebView {
165 self.0.clone()
166 }
167
168 #[cfg(windows)]
170 #[cfg_attr(docsrs, doc(cfg(windows)))]
171 pub fn controller(
172 &self,
173 ) -> webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Controller {
174 self.0.controller.clone()
175 }
176
177 #[cfg(any(target_os = "macos", target_os = "ios"))]
181 #[cfg_attr(docsrs, doc(cfg(any(target_os = "macos", target_os = "ios"))))]
182 pub fn inner(&self) -> *mut std::ffi::c_void {
183 self.0.webview
184 }
185
186 #[cfg(any(target_os = "macos", target_os = "ios"))]
190 #[cfg_attr(docsrs, doc(cfg(any(target_os = "macos", target_os = "ios"))))]
191 pub fn controller(&self) -> *mut std::ffi::c_void {
192 self.0.manager
193 }
194
195 #[cfg(target_os = "macos")]
199 #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
200 pub fn ns_window(&self) -> *mut std::ffi::c_void {
201 self.0.ns_window
202 }
203
204 #[cfg(target_os = "ios")]
208 #[cfg_attr(docsrs, doc(cfg(target_os = "ios")))]
209 pub fn view_controller(&self) -> *mut std::ffi::c_void {
210 self.0.view_controller
211 }
212
213 #[cfg(target_os = "android")]
215 pub fn jni_handle(&self) -> tauri_runtime_wry::wry::JniHandle {
216 self.0
217 }
218}
219
220macro_rules! unstable_struct {
221 (#[doc = $doc:expr] $($tokens:tt)*) => {
222 #[cfg(any(test, feature = "unstable"))]
223 #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
224 #[doc = $doc]
225 pub $($tokens)*
226
227 #[cfg(not(any(test, feature = "unstable")))]
228 pub(crate) $($tokens)*
229 }
230}
231
232unstable_struct!(
233 #[doc = "A builder for a webview."]
234 struct WebviewBuilder<R: Runtime> {
235 pub(crate) label: String,
236 pub(crate) webview_attributes: WebviewAttributes,
237 pub(crate) web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
238 pub(crate) navigation_handler: Option<Box<NavigationHandler>>,
239 pub(crate) on_page_load_handler: Option<Box<OnPageLoad<R>>>,
240 pub(crate) download_handler: Option<Arc<DownloadHandler<R>>>,
241 }
242);
243
244#[cfg_attr(not(feature = "unstable"), allow(dead_code))]
245impl<R: Runtime> WebviewBuilder<R> {
246 #[cfg_attr(
258 feature = "unstable",
259 doc = r####"
260```
261tauri::Builder::default()
262 .setup(|app| {
263 let window = tauri::window::WindowBuilder::new(app, "label").build()?;
264 let webview_builder = tauri::webview::WebviewBuilder::new("label", tauri::WebviewUrl::App("index.html".into()));
265 let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap());
266 Ok(())
267 });
268```
269 "####
270 )]
271 #[cfg_attr(
275 feature = "unstable",
276 doc = r####"
277```
278tauri::Builder::default()
279 .setup(|app| {
280 let handle = app.handle().clone();
281 std::thread::spawn(move || {
282 let window = tauri::window::WindowBuilder::new(&handle, "label").build().unwrap();
283 let webview_builder = tauri::webview::WebviewBuilder::new("label", tauri::WebviewUrl::App("index.html".into()));
284 window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap());
285 });
286 Ok(())
287 });
288```
289 "####
290 )]
291 #[cfg_attr(
295 feature = "unstable",
296 doc = r####"
297```
298#[tauri::command]
299async fn create_window(app: tauri::AppHandle) {
300 let window = tauri::window::WindowBuilder::new(&app, "label").build().unwrap();
301 let webview_builder = tauri::webview::WebviewBuilder::new("label", tauri::WebviewUrl::External("https://tauri.app/".parse().unwrap()));
302 window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap());
303}
304```
305 "####
306 )]
307 pub fn new<L: Into<String>>(label: L, url: WebviewUrl) -> Self {
310 Self {
311 label: label.into(),
312 webview_attributes: WebviewAttributes::new(url),
313 web_resource_request_handler: None,
314 navigation_handler: None,
315 on_page_load_handler: None,
316 download_handler: None,
317 }
318 }
319
320 #[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::from_config(&app.config().app.windows.get(0).unwrap().clone());
341 window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap());
342}
343```
344 "####
345 )]
346 pub fn from_config(config: &WindowConfig) -> Self {
349 Self {
350 label: config.label.clone(),
351 webview_attributes: WebviewAttributes::from(config),
352 web_resource_request_handler: None,
353 navigation_handler: None,
354 on_page_load_handler: None,
355 download_handler: None,
356 }
357 }
358
359 #[cfg_attr(
369 feature = "unstable",
370 doc = r####"
371```rust,no_run
372use tauri::{
373 utils::config::{Csp, CspDirectiveSources, WebviewUrl},
374 window::WindowBuilder,
375 webview::WebviewBuilder,
376};
377use http::header::HeaderValue;
378use std::collections::HashMap;
379tauri::Builder::default()
380 .setup(|app| {
381 let window = tauri::window::WindowBuilder::new(app, "label").build()?;
382
383 let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
384 .on_web_resource_request(|request, response| {
385 if request.uri().scheme_str() == Some("tauri") {
386 // if we have a CSP header, Tauri is loading an HTML file
387 // for this example, let's dynamically change the CSP
388 if let Some(csp) = response.headers_mut().get_mut("Content-Security-Policy") {
389 // use the tauri helper to parse the CSP policy to a map
390 let mut csp_map: HashMap<String, CspDirectiveSources> = Csp::Policy(csp.to_str().unwrap().to_string()).into();
391 csp_map.entry("script-src".to_string()).or_insert_with(Default::default).push("'unsafe-inline'");
392 // use the tauri helper to get a CSP string from the map
393 let csp_string = Csp::from(csp_map).to_string();
394 *csp = HeaderValue::from_str(&csp_string).unwrap();
395 }
396 }
397 });
398
399 let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
400
401 Ok(())
402 });
403```
404 "####
405 )]
406 pub fn on_web_resource_request<
407 F: Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync + 'static,
408 >(
409 mut self,
410 f: F,
411 ) -> Self {
412 self.web_resource_request_handler.replace(Box::new(f));
413 self
414 }
415
416 #[cfg_attr(
421 feature = "unstable",
422 doc = r####"
423```rust,no_run
424use tauri::{
425 utils::config::{Csp, CspDirectiveSources, WebviewUrl},
426 window::WindowBuilder,
427 webview::WebviewBuilder,
428};
429use http::header::HeaderValue;
430use std::collections::HashMap;
431tauri::Builder::default()
432 .setup(|app| {
433 let window = tauri::window::WindowBuilder::new(app, "label").build()?;
434
435 let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
436 .on_navigation(|url| {
437 // allow the production URL or localhost on dev
438 url.scheme() == "tauri" || (cfg!(dev) && url.host_str() == Some("localhost"))
439 });
440
441 let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
442 Ok(())
443 });
444```
445 "####
446 )]
447 pub fn on_navigation<F: Fn(&Url) -> bool + Send + 'static>(mut self, f: F) -> Self {
448 self.navigation_handler.replace(Box::new(f));
449 self
450 }
451
452 #[cfg_attr(
459 feature = "unstable",
460 doc = r####"
461```rust,no_run
462use tauri::{
463 utils::config::{Csp, CspDirectiveSources, WebviewUrl},
464 window::WindowBuilder,
465 webview::{DownloadEvent, WebviewBuilder},
466};
467
468tauri::Builder::default()
469 .setup(|app| {
470 let window = WindowBuilder::new(app, "label").build()?;
471 let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
472 .on_download(|webview, event| {
473 match event {
474 DownloadEvent::Requested { url, destination } => {
475 println!("downloading {}", url);
476 *destination = "/home/tauri/target/path".into();
477 }
478 DownloadEvent::Finished { url, path, success } => {
479 println!("downloaded {} to {:?}, success: {}", url, path, success);
480 }
481 _ => (),
482 }
483 // let the download start
484 true
485 });
486
487 let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
488 Ok(())
489 });
490```
491 "####
492 )]
493 pub fn on_download<F: Fn(Webview<R>, DownloadEvent<'_>) -> bool + Send + Sync + 'static>(
494 mut self,
495 f: F,
496 ) -> Self {
497 self.download_handler.replace(Arc::new(f));
498 self
499 }
500
501 #[cfg_attr(
508 feature = "unstable",
509 doc = r####"
510```rust,no_run
511use tauri::{
512 utils::config::{Csp, CspDirectiveSources, WebviewUrl},
513 window::WindowBuilder,
514 webview::{PageLoadEvent, WebviewBuilder},
515};
516use http::header::HeaderValue;
517use std::collections::HashMap;
518tauri::Builder::default()
519 .setup(|app| {
520 let window = tauri::window::WindowBuilder::new(app, "label").build()?;
521 let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
522 .on_page_load(|webview, payload| {
523 match payload.event() {
524 PageLoadEvent::Started => {
525 println!("{} finished loading", payload.url());
526 }
527 PageLoadEvent::Finished => {
528 println!("{} finished loading", payload.url());
529 }
530 }
531 });
532 let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
533 Ok(())
534 });
535```
536 "####
537 )]
538 pub fn on_page_load<F: Fn(Webview<R>, PageLoadPayload<'_>) + Send + Sync + 'static>(
539 mut self,
540 f: F,
541 ) -> Self {
542 self.on_page_load_handler.replace(Box::new(f));
543 self
544 }
545
546 pub(crate) fn into_pending_webview<M: Manager<R>>(
547 mut self,
548 manager: &M,
549 window_label: &str,
550 ) -> crate::Result<PendingWebview<EventLoopMessage, R>> {
551 let mut pending = PendingWebview::new(self.webview_attributes, self.label.clone())?;
552 pending.navigation_handler = self.navigation_handler.take();
553 pending.web_resource_request_handler = self.web_resource_request_handler.take();
554
555 if let Some(download_handler) = self.download_handler.take() {
556 let label = pending.label.clone();
557 let manager = manager.manager_owned();
558 pending.download_handler.replace(Arc::new(move |event| {
559 if let Some(w) = manager.get_webview(&label) {
560 download_handler(
561 w,
562 match event {
563 tauri_runtime::webview::DownloadEvent::Requested { url, destination } => {
564 DownloadEvent::Requested { url, destination }
565 }
566 tauri_runtime::webview::DownloadEvent::Finished { url, path, success } => {
567 DownloadEvent::Finished { url, path, success }
568 }
569 },
570 )
571 } else {
572 false
573 }
574 }));
575 }
576
577 let label_ = pending.label.clone();
578 let manager_ = manager.manager_owned();
579 pending
580 .on_page_load_handler
581 .replace(Box::new(move |url, event| {
582 if let Some(w) = manager_.get_webview(&label_) {
583 if let Some(handler) = self.on_page_load_handler.as_ref() {
584 handler(w, PageLoadPayload { url: &url, event });
585 }
586 }
587 }));
588
589 manager
590 .manager()
591 .webview
592 .prepare_webview(manager, pending, window_label)
593 }
594
595 #[cfg(desktop)]
597 pub(crate) fn build(
598 self,
599 window: Window<R>,
600 position: Position,
601 size: Size,
602 ) -> crate::Result<Webview<R>> {
603 let app_manager = window.manager();
604
605 let mut pending = self.into_pending_webview(&window, window.label())?;
606
607 pending.webview_attributes.bounds = Some(tauri_runtime::Rect { size, position });
608
609 let use_https_scheme = pending.webview_attributes.use_https_scheme;
610
611 let webview = match &mut window.runtime() {
612 RuntimeOrDispatch::Dispatch(dispatcher) => dispatcher.create_webview(pending),
613 _ => unimplemented!(),
614 }
615 .map(|webview| {
616 app_manager
617 .webview
618 .attach_webview(window.clone(), webview, use_https_scheme)
619 })?;
620
621 Ok(webview)
622 }
623}
624
625impl<R: Runtime> WebviewBuilder<R> {
627 #[must_use]
629 pub fn accept_first_mouse(mut self, accept: bool) -> Self {
630 self.webview_attributes.accept_first_mouse = accept;
631 self
632 }
633
634 #[cfg_attr(
653 feature = "unstable",
654 doc = r####"
655```rust
656use tauri::{WindowBuilder, Runtime};
657
658const INIT_SCRIPT: &str = r#"
659 if (window.location.origin === 'https://tauri.app') {
660 console.log("hello world from js init script");
661
662 window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };
663 }
664"#;
665
666fn main() {
667 tauri::Builder::default()
668 .setup(|app| {
669 let window = tauri::window::WindowBuilder::new(app, "label").build()?;
670 let webview_builder = tauri::webview::WebviewBuilder::new("label", tauri::WebviewUrl::App("index.html".into()))
671 .initialization_script(INIT_SCRIPT);
672 let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
673 Ok(())
674 });
675}
676```
677 "####
678 )]
679 #[must_use]
683 pub fn initialization_script(mut self, script: impl Into<String>) -> Self {
684 self
685 .webview_attributes
686 .initialization_scripts
687 .push(InitializationScript {
688 script: script.into(),
689 for_main_frame_only: true,
690 });
691 self
692 }
693
694 #[cfg_attr(
712 feature = "unstable",
713 doc = r####"
714```rust
715use tauri::{WindowBuilder, Runtime};
716
717const INIT_SCRIPT: &str = r#"
718 if (window.location.origin === 'https://tauri.app') {
719 console.log("hello world from js init script");
720
721 window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };
722 }
723"#;
724
725fn main() {
726 tauri::Builder::default()
727 .setup(|app| {
728 let window = tauri::window::WindowBuilder::new(app, "label").build()?;
729 let webview_builder = tauri::webview::WebviewBuilder::new("label", tauri::WebviewUrl::App("index.html".into()))
730 .initialization_script_for_all_frames(INIT_SCRIPT);
731 let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
732 Ok(())
733 });
734}
735```
736 "####
737 )]
738 #[must_use]
742 pub fn initialization_script_for_all_frames(mut self, script: impl Into<String>) -> Self {
743 self
744 .webview_attributes
745 .initialization_scripts
746 .push(InitializationScript {
747 script: script.into(),
748 for_main_frame_only: false,
749 });
750 self
751 }
752
753 #[must_use]
755 pub fn user_agent(mut self, user_agent: &str) -> Self {
756 self.webview_attributes.user_agent = Some(user_agent.to_string());
757 self
758 }
759
760 #[must_use]
771 pub fn additional_browser_args(mut self, additional_args: &str) -> Self {
772 self.webview_attributes.additional_browser_args = Some(additional_args.to_string());
773 self
774 }
775
776 #[must_use]
778 pub fn data_directory(mut self, data_directory: PathBuf) -> Self {
779 self
780 .webview_attributes
781 .data_directory
782 .replace(data_directory);
783 self
784 }
785
786 #[must_use]
788 pub fn disable_drag_drop_handler(mut self) -> Self {
789 self.webview_attributes.drag_drop_handler_enabled = false;
790 self
791 }
792
793 #[must_use]
798 pub fn enable_clipboard_access(mut self) -> Self {
799 self.webview_attributes.clipboard = true;
800 self
801 }
802
803 #[must_use]
812 pub fn incognito(mut self, incognito: bool) -> Self {
813 self.webview_attributes.incognito = incognito;
814 self
815 }
816
817 #[must_use]
825 pub fn proxy_url(mut self, url: Url) -> Self {
826 self.webview_attributes.proxy_url = Some(url);
827 self
828 }
829
830 #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
832 #[cfg_attr(
833 docsrs,
834 doc(cfg(any(not(target_os = "macos"), feature = "macos-private-api")))
835 )]
836 #[must_use]
837 pub fn transparent(mut self, transparent: bool) -> Self {
838 self.webview_attributes.transparent = transparent;
839 self
840 }
841
842 #[must_use]
844 pub fn focused(mut self, focus: bool) -> Self {
845 self.webview_attributes.focus = focus;
846 self
847 }
848
849 #[must_use]
851 pub fn auto_resize(mut self) -> Self {
852 self.webview_attributes.auto_resize = true;
853 self
854 }
855
856 #[must_use]
866 pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {
867 self.webview_attributes.zoom_hotkeys_enabled = enabled;
868 self
869 }
870
871 #[must_use]
878 pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self {
879 self.webview_attributes.browser_extensions_enabled = enabled;
880 self
881 }
882
883 #[must_use]
890 pub fn extensions_path(mut self, path: impl AsRef<Path>) -> Self {
891 self.webview_attributes.extensions_path = Some(path.as_ref().to_path_buf());
892 self
893 }
894
895 #[must_use]
903 pub fn data_store_identifier(mut self, data_store_identifier: [u8; 16]) -> Self {
904 self.webview_attributes.data_store_identifier = Some(data_store_identifier);
905 self
906 }
907
908 #[must_use]
918 pub fn use_https_scheme(mut self, enabled: bool) -> Self {
919 self.webview_attributes.use_https_scheme = enabled;
920 self
921 }
922
923 #[must_use]
933 pub fn devtools(mut self, enabled: bool) -> Self {
934 self.webview_attributes.devtools.replace(enabled);
935 self
936 }
937
938 #[must_use]
946 pub fn background_color(mut self, color: Color) -> Self {
947 self.webview_attributes.background_color = Some(color);
948 self
949 }
950
951 #[must_use]
966 pub fn background_throttling(mut self, policy: BackgroundThrottlingPolicy) -> Self {
967 self.webview_attributes.background_throttling = Some(policy);
968 self
969 }
970
971 #[must_use]
973 pub fn disable_javascript(mut self) -> Self {
974 self.webview_attributes.javascript_disabled = true;
975 self
976 }
977
978 #[cfg(target_os = "macos")]
988 #[must_use]
989 pub fn allow_link_preview(mut self, allow_link_preview: bool) -> Self {
990 self.webview_attributes = self
991 .webview_attributes
992 .allow_link_preview(allow_link_preview);
993 self
994 }
995
996 #[cfg(target_os = "ios")]
1008 pub fn with_input_accessory_view_builder<
1009 F: Fn(&objc2_ui_kit::UIView) -> Option<objc2::rc::Retained<objc2_ui_kit::UIView>>
1010 + Send
1011 + Sync
1012 + 'static,
1013 >(
1014 mut self,
1015 builder: F,
1016 ) -> Self {
1017 self
1018 .webview_attributes
1019 .input_accessory_view_builder
1020 .replace(tauri_runtime::webview::InputAccessoryViewBuilder::new(
1021 Box::new(builder),
1022 ));
1023 self
1024 }
1025}
1026
1027#[default_runtime(crate::Wry, wry)]
1029pub struct Webview<R: Runtime> {
1030 pub(crate) window: Arc<Mutex<Window<R>>>,
1031 pub(crate) webview: DetachedWebview<EventLoopMessage, R>,
1033 pub(crate) manager: Arc<AppManager<R>>,
1035 pub(crate) app_handle: AppHandle<R>,
1036 pub(crate) resources_table: Arc<Mutex<ResourceTable>>,
1037 use_https_scheme: bool,
1038}
1039
1040impl<R: Runtime> std::fmt::Debug for Webview<R> {
1041 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1042 f.debug_struct("Window")
1043 .field("window", &self.window.lock().unwrap())
1044 .field("webview", &self.webview)
1045 .field("use_https_scheme", &self.use_https_scheme)
1046 .finish()
1047 }
1048}
1049
1050impl<R: Runtime> Clone for Webview<R> {
1051 fn clone(&self) -> Self {
1052 Self {
1053 window: self.window.clone(),
1054 webview: self.webview.clone(),
1055 manager: self.manager.clone(),
1056 app_handle: self.app_handle.clone(),
1057 resources_table: self.resources_table.clone(),
1058 use_https_scheme: self.use_https_scheme,
1059 }
1060 }
1061}
1062
1063impl<R: Runtime> Hash for Webview<R> {
1064 fn hash<H: Hasher>(&self, state: &mut H) {
1066 self.webview.label.hash(state)
1067 }
1068}
1069
1070impl<R: Runtime> Eq for Webview<R> {}
1071impl<R: Runtime> PartialEq for Webview<R> {
1072 fn eq(&self, other: &Self) -> bool {
1074 self.webview.label.eq(&other.webview.label)
1075 }
1076}
1077
1078impl<R: Runtime> Webview<R> {
1080 pub(crate) fn new(
1082 window: Window<R>,
1083 webview: DetachedWebview<EventLoopMessage, R>,
1084 use_https_scheme: bool,
1085 ) -> Self {
1086 Self {
1087 manager: window.manager.clone(),
1088 app_handle: window.app_handle.clone(),
1089 window: Arc::new(Mutex::new(window)),
1090 webview,
1091 resources_table: Default::default(),
1092 use_https_scheme,
1093 }
1094 }
1095
1096 #[cfg(feature = "unstable")]
1100 #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
1101 pub fn builder<L: Into<String>>(label: L, url: WebviewUrl) -> WebviewBuilder<R> {
1102 WebviewBuilder::new(label.into(), url)
1103 }
1104
1105 pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {
1107 self
1108 .webview
1109 .dispatcher
1110 .run_on_main_thread(f)
1111 .map_err(Into::into)
1112 }
1113
1114 pub fn label(&self) -> &str {
1116 &self.webview.label
1117 }
1118
1119 pub(crate) fn use_https_scheme(&self) -> bool {
1121 self.use_https_scheme
1122 }
1123
1124 pub fn on_webview_event<F: Fn(&WebviewEvent) + Send + 'static>(&self, f: F) {
1126 self
1127 .webview
1128 .dispatcher
1129 .on_webview_event(move |event| f(&event.clone().into()));
1130 }
1131
1132 pub fn resolve_command_scope<T: ScopeObject>(
1171 &self,
1172 plugin: &str,
1173 command: &str,
1174 ) -> crate::Result<Option<ResolvedScope<T>>> {
1175 let current_url = self.url()?;
1176 let is_local = self.is_local_url(¤t_url);
1177 let origin = if is_local {
1178 Origin::Local
1179 } else {
1180 Origin::Remote { url: current_url }
1181 };
1182
1183 let cmd_name = format!("plugin:{plugin}|{command}");
1184 let resolved_access = self
1185 .manager()
1186 .runtime_authority
1187 .lock()
1188 .unwrap()
1189 .resolve_access(&cmd_name, self.window().label(), self.label(), &origin);
1190
1191 if let Some(access) = resolved_access {
1192 let scope_ids = access
1193 .iter()
1194 .filter_map(|cmd| cmd.scope_id)
1195 .collect::<Vec<_>>();
1196
1197 let command_scope = CommandScope::resolve(self, scope_ids)?;
1198 let global_scope = GlobalScope::resolve(self, plugin)?;
1199
1200 Ok(Some(ResolvedScope {
1201 global_scope,
1202 command_scope,
1203 }))
1204 } else {
1205 Ok(None)
1206 }
1207 }
1208}
1209
1210#[cfg(desktop)]
1212impl<R: Runtime> Webview<R> {
1213 pub fn print(&self) -> crate::Result<()> {
1217 self.webview.dispatcher.print().map_err(Into::into)
1218 }
1219
1220 pub fn cursor_position(&self) -> crate::Result<PhysicalPosition<f64>> {
1229 self.app_handle.cursor_position()
1230 }
1231
1232 pub fn close(&self) -> crate::Result<()> {
1234 self.webview.dispatcher.close()?;
1235 self.manager().on_webview_close(self.label());
1236 Ok(())
1237 }
1238
1239 pub fn set_bounds(&self, bounds: tauri_runtime::Rect) -> crate::Result<()> {
1241 self
1242 .webview
1243 .dispatcher
1244 .set_bounds(bounds)
1245 .map_err(Into::into)
1246 }
1247
1248 pub fn set_size<S: Into<Size>>(&self, size: S) -> crate::Result<()> {
1250 self
1251 .webview
1252 .dispatcher
1253 .set_size(size.into())
1254 .map_err(Into::into)
1255 }
1256
1257 pub fn set_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {
1259 self
1260 .webview
1261 .dispatcher
1262 .set_position(position.into())
1263 .map_err(Into::into)
1264 }
1265
1266 pub fn set_focus(&self) -> crate::Result<()> {
1268 self.webview.dispatcher.set_focus().map_err(Into::into)
1269 }
1270
1271 pub fn hide(&self) -> crate::Result<()> {
1273 self.webview.dispatcher.hide().map_err(Into::into)
1274 }
1275
1276 pub fn show(&self) -> crate::Result<()> {
1278 self.webview.dispatcher.show().map_err(Into::into)
1279 }
1280
1281 pub fn reparent(&self, window: &Window<R>) -> crate::Result<()> {
1283 #[cfg(not(feature = "unstable"))]
1284 {
1285 if self.window_ref().is_webview_window() || window.is_webview_window() {
1286 return Err(crate::Error::CannotReparentWebviewWindow);
1287 }
1288 }
1289
1290 *self.window.lock().unwrap() = window.clone();
1291 self.webview.dispatcher.reparent(window.window.id)?;
1292 Ok(())
1293 }
1294
1295 pub fn set_auto_resize(&self, auto_resize: bool) -> crate::Result<()> {
1297 self
1298 .webview
1299 .dispatcher
1300 .set_auto_resize(auto_resize)
1301 .map_err(Into::into)
1302 }
1303
1304 pub fn bounds(&self) -> crate::Result<tauri_runtime::Rect> {
1306 self.webview.dispatcher.bounds().map_err(Into::into)
1307 }
1308
1309 pub fn position(&self) -> crate::Result<PhysicalPosition<i32>> {
1314 self.webview.dispatcher.position().map_err(Into::into)
1315 }
1316
1317 pub fn size(&self) -> crate::Result<PhysicalSize<u32>> {
1319 self.webview.dispatcher.size().map_err(Into::into)
1320 }
1321}
1322
1323impl<R: Runtime> Webview<R> {
1325 pub fn window(&self) -> Window<R> {
1327 self.window.lock().unwrap().clone()
1328 }
1329
1330 pub fn window_ref(&self) -> MutexGuard<'_, Window<R>> {
1332 self.window.lock().unwrap()
1333 }
1334
1335 pub(crate) fn window_label(&self) -> String {
1336 self.window_ref().label().to_string()
1337 }
1338
1339 #[cfg_attr(
1349 feature = "unstable",
1350 doc = r####"
1351```rust,no_run
1352use tauri::Manager;
1353
1354fn main() {
1355 tauri::Builder::default()
1356 .setup(|app| {
1357 let main_webview = app.get_webview("main").unwrap();
1358 main_webview.with_webview(|webview| {
1359 #[cfg(target_os = "linux")]
1360 {
1361 // see <https://docs.rs/webkit2gtk/2.0.0/webkit2gtk/struct.WebView.html>
1362 // and <https://docs.rs/webkit2gtk/2.0.0/webkit2gtk/trait.WebViewExt.html>
1363 use webkit2gtk::WebViewExt;
1364 webview.inner().set_zoom_level(4.);
1365 }
1366
1367 #[cfg(windows)]
1368 unsafe {
1369 // see https://docs.rs/webview2-com/0.19.1/webview2_com/Microsoft/Web/WebView2/Win32/struct.ICoreWebView2Controller.html
1370 webview.controller().SetZoomFactor(4.).unwrap();
1371 }
1372
1373 #[cfg(target_os = "macos")]
1374 unsafe {
1375 let view: &objc2_web_kit::WKWebView = &*webview.inner().cast();
1376 let controller: &objc2_web_kit::WKUserContentController = &*webview.controller().cast();
1377 let window: &objc2_app_kit::NSWindow = &*webview.ns_window().cast();
1378
1379 view.setPageZoom(4.);
1380 controller.removeAllUserScripts();
1381 let bg_color = objc2_app_kit::NSColor::colorWithDeviceRed_green_blue_alpha(0.5, 0.2, 0.4, 1.);
1382 window.setBackgroundColor(Some(&bg_color));
1383 }
1384
1385 #[cfg(target_os = "android")]
1386 {
1387 use jni::objects::JValue;
1388 webview.jni_handle().exec(|env, _, webview| {
1389 env.call_method(webview, "zoomBy", "(F)V", &[JValue::Float(4.)]).unwrap();
1390 })
1391 }
1392 });
1393 Ok(())
1394 });
1395}
1396```
1397 "####
1398 )]
1399 #[cfg(feature = "wry")]
1400 #[cfg_attr(docsrs, doc(feature = "wry"))]
1401 pub fn with_webview<F: FnOnce(PlatformWebview) + Send + 'static>(
1402 &self,
1403 f: F,
1404 ) -> crate::Result<()> {
1405 self
1406 .webview
1407 .dispatcher
1408 .with_webview(|w| f(PlatformWebview(*w.downcast().unwrap())))
1409 .map_err(Into::into)
1410 }
1411
1412 pub fn url(&self) -> crate::Result<Url> {
1414 self
1415 .webview
1416 .dispatcher
1417 .url()
1418 .map(|url| url.parse().map_err(crate::Error::InvalidUrl))?
1419 }
1420
1421 pub fn navigate(&self, url: Url) -> crate::Result<()> {
1423 self.webview.dispatcher.navigate(url).map_err(Into::into)
1424 }
1425
1426 pub fn reload(&self) -> crate::Result<()> {
1428 self.webview.dispatcher.reload().map_err(Into::into)
1429 }
1430
1431 fn is_local_url(&self, current_url: &Url) -> bool {
1432 let uses_https = current_url.scheme() == "https";
1433
1434 ({
1436 let protocol_url = self.manager().protocol_url(uses_https);
1437 current_url.scheme() == protocol_url.scheme()
1438 && current_url.domain() == protocol_url.domain()
1439 }) ||
1440
1441 self
1443 .manager()
1444 .get_url(uses_https)
1445 .make_relative(current_url)
1446 .is_some()
1447
1448 || ({
1450 let scheme = current_url.scheme();
1451 let protocols = self.manager().webview.uri_scheme_protocols.lock().unwrap();
1452
1453 #[cfg(all(not(windows), not(target_os = "android")))]
1454 let local = protocols.contains_key(scheme);
1455
1456 #[cfg(any(windows, target_os = "android"))]
1459 let local = {
1460 let protocol_url = self.manager().protocol_url(uses_https);
1461 let maybe_protocol = current_url
1462 .domain()
1463 .and_then(|d| d .split_once('.'))
1464 .unwrap_or_default()
1465 .0;
1466
1467 protocols.contains_key(maybe_protocol) && scheme == protocol_url.scheme()
1468 };
1469
1470 local
1471 })
1472 }
1473
1474 pub fn on_message(self, request: InvokeRequest, responder: Box<OwnedInvokeResponder<R>>) {
1476 let manager = self.manager_owned();
1477 let is_local = self.is_local_url(&request.url);
1478
1479 let expected = manager.invoke_key();
1481 if request.invoke_key != expected {
1482 #[cfg(feature = "tracing")]
1483 tracing::error!(
1484 "__TAURI_INVOKE_KEY__ expected {expected} but received {}",
1485 request.invoke_key
1486 );
1487
1488 #[cfg(not(feature = "tracing"))]
1489 eprintln!(
1490 "__TAURI_INVOKE_KEY__ expected {expected} but received {}",
1491 request.invoke_key
1492 );
1493
1494 return;
1495 }
1496
1497 let resolver = InvokeResolver::new(
1498 self.clone(),
1499 Arc::new(Mutex::new(Some(Box::new(
1500 move |webview: Webview<R>, cmd, response, callback, error| {
1501 responder(webview, cmd, response, callback, error);
1502 },
1503 )))),
1504 request.cmd.clone(),
1505 request.callback,
1506 request.error,
1507 );
1508
1509 #[cfg(mobile)]
1510 let app_handle = self.app_handle.clone();
1511
1512 let message = InvokeMessage::new(
1513 self,
1514 manager.state(),
1515 request.cmd.to_string(),
1516 request.body,
1517 request.headers,
1518 );
1519
1520 let acl_origin = if is_local {
1521 Origin::Local
1522 } else {
1523 Origin::Remote {
1524 url: request.url.clone(),
1525 }
1526 };
1527 let (resolved_acl, has_app_acl_manifest) = {
1528 let runtime_authority = manager.runtime_authority.lock().unwrap();
1529 let acl = runtime_authority.resolve_access(
1530 &request.cmd,
1531 message.webview.window_ref().label(),
1532 message.webview.label(),
1533 &acl_origin,
1534 );
1535 (acl, runtime_authority.has_app_manifest())
1536 };
1537
1538 let mut invoke = Invoke {
1539 message,
1540 resolver: resolver.clone(),
1541 acl: resolved_acl,
1542 };
1543
1544 let plugin_command = request.cmd.strip_prefix("plugin:").map(|raw_command| {
1545 let mut tokens = raw_command.split('|');
1546 let plugin = tokens.next().unwrap();
1548 let command = tokens.next().map(|c| c.to_string()).unwrap_or_default();
1549 (plugin, command)
1550 });
1551
1552 if (plugin_command.is_some() || has_app_acl_manifest)
1554 && request.cmd != crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND
1556 && invoke.acl.is_none()
1557 {
1558 #[cfg(debug_assertions)]
1559 {
1560 let (key, command_name) = plugin_command
1561 .clone()
1562 .unwrap_or_else(|| (tauri_utils::acl::APP_ACL_KEY, request.cmd.clone()));
1563 invoke.resolver.reject(
1564 manager
1565 .runtime_authority
1566 .lock()
1567 .unwrap()
1568 .resolve_access_message(
1569 key,
1570 &command_name,
1571 invoke.message.webview.window().label(),
1572 invoke.message.webview.label(),
1573 &acl_origin,
1574 ),
1575 );
1576 }
1577 #[cfg(not(debug_assertions))]
1578 invoke
1579 .resolver
1580 .reject(format!("Command {} not allowed by ACL", request.cmd));
1581 return;
1582 }
1583
1584 if let Some((plugin, command_name)) = plugin_command {
1585 invoke.message.command = command_name;
1586
1587 let command = invoke.message.command.clone();
1588
1589 #[cfg(mobile)]
1590 let message = invoke.message.clone();
1591
1592 #[allow(unused_mut)]
1593 let mut handled = manager.extend_api(plugin, invoke);
1594
1595 #[cfg(mobile)]
1596 {
1597 if !handled {
1598 handled = true;
1599
1600 fn load_channels<R: Runtime>(payload: &serde_json::Value, webview: &Webview<R>) {
1601 use std::str::FromStr;
1602
1603 if let serde_json::Value::Object(map) = payload {
1604 for v in map.values() {
1605 if let serde_json::Value::String(s) = v {
1606 let _ = crate::ipc::JavaScriptChannelId::from_str(s)
1607 .map(|id| id.channel_on::<R, ()>(webview.clone()));
1608 }
1609 }
1610 }
1611 }
1612
1613 let payload = message.payload.into_json();
1614 load_channels(&payload, &message.webview);
1616
1617 let resolver_ = resolver.clone();
1618 if let Err(e) = crate::plugin::mobile::run_command(
1619 plugin,
1620 &app_handle,
1621 heck::AsLowerCamelCase(message.command).to_string(),
1622 payload,
1623 move |response| match response {
1624 Ok(r) => resolver_.resolve(r),
1625 Err(e) => resolver_.reject(e),
1626 },
1627 ) {
1628 resolver.reject(e.to_string());
1629 return;
1630 }
1631 }
1632 }
1633
1634 if !handled {
1635 resolver.reject(format!("Command {command} not found"));
1636 }
1637 } else {
1638 let command = invoke.message.command.clone();
1639 let handled = manager.run_invoke_handler(invoke);
1640 if !handled {
1641 resolver.reject(format!("Command {command} not found"));
1642 }
1643 }
1644 }
1645
1646 pub fn eval(&self, js: impl Into<String>) -> crate::Result<()> {
1648 self
1649 .webview
1650 .dispatcher
1651 .eval_script(js.into())
1652 .map_err(Into::into)
1653 }
1654
1655 pub(crate) fn listen_js(
1657 &self,
1658 event: EventName<&str>,
1659 target: EventTarget,
1660 handler: CallbackFn,
1661 ) -> crate::Result<EventId> {
1662 let listeners = self.manager().listeners();
1663
1664 let id = listeners.next_event_id();
1665
1666 self.eval(crate::event::listen_js_script(
1667 listeners.listeners_object_name(),
1668 &serde_json::to_string(&target)?,
1669 event,
1670 id,
1671 &format!("window['_{}']", handler.0),
1672 ))?;
1673
1674 listeners.listen_js(event, self.label(), target, id);
1675
1676 Ok(id)
1677 }
1678
1679 pub(crate) fn unlisten_js(&self, event: EventName<&str>, id: EventId) -> crate::Result<()> {
1681 let listeners = self.manager().listeners();
1682
1683 self.eval(crate::event::unlisten_js_script(
1684 listeners.listeners_object_name(),
1685 event,
1686 id,
1687 ))?;
1688
1689 listeners.unlisten_js(event, id);
1690
1691 Ok(())
1692 }
1693
1694 pub(crate) fn emit_js(&self, emit_args: &EmitArgs, ids: &[u32]) -> crate::Result<()> {
1695 self.eval(crate::event::emit_js_script(
1696 self.manager().listeners().function_name(),
1697 emit_args,
1698 &serde_json::to_string(ids)?,
1699 )?)?;
1700 Ok(())
1701 }
1702
1703 #[cfg_attr(
1714 feature = "unstable",
1715 doc = r####"
1716```rust,no_run
1717use tauri::Manager;
1718tauri::Builder::default()
1719 .setup(|app| {
1720 #[cfg(debug_assertions)]
1721 app.get_webview("main").unwrap().open_devtools();
1722 Ok(())
1723 });
1724```
1725 "####
1726 )]
1727 #[cfg(any(debug_assertions, feature = "devtools"))]
1728 #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = "devtools"))))]
1729 pub fn open_devtools(&self) {
1730 self.webview.dispatcher.open_devtools();
1731 }
1732
1733 #[cfg_attr(
1745 feature = "unstable",
1746 doc = r####"
1747```rust,no_run
1748use tauri::Manager;
1749tauri::Builder::default()
1750 .setup(|app| {
1751 #[cfg(debug_assertions)]
1752 {
1753 let webview = app.get_webview("main").unwrap();
1754 webview.open_devtools();
1755 std::thread::spawn(move || {
1756 std::thread::sleep(std::time::Duration::from_secs(10));
1757 webview.close_devtools();
1758 });
1759 }
1760 Ok(())
1761 });
1762```
1763 "####
1764 )]
1765 #[cfg(any(debug_assertions, feature = "devtools"))]
1766 #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = "devtools"))))]
1767 pub fn close_devtools(&self) {
1768 self.webview.dispatcher.close_devtools();
1769 }
1770
1771 #[cfg_attr(
1783 feature = "unstable",
1784 doc = r####"
1785```rust,no_run
1786use tauri::Manager;
1787tauri::Builder::default()
1788 .setup(|app| {
1789 #[cfg(debug_assertions)]
1790 {
1791 let webview = app.get_webview("main").unwrap();
1792 if !webview.is_devtools_open() {
1793 webview.open_devtools();
1794 }
1795 }
1796 Ok(())
1797 });
1798```
1799 "####
1800 )]
1801 #[cfg(any(debug_assertions, feature = "devtools"))]
1802 #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = "devtools"))))]
1803 pub fn is_devtools_open(&self) -> bool {
1804 self
1805 .webview
1806 .dispatcher
1807 .is_devtools_open()
1808 .unwrap_or_default()
1809 }
1810
1811 pub fn set_zoom(&self, scale_factor: f64) -> crate::Result<()> {
1819 self
1820 .webview
1821 .dispatcher
1822 .set_zoom(scale_factor)
1823 .map_err(Into::into)
1824 }
1825
1826 pub fn set_background_color(&self, color: Option<Color>) -> crate::Result<()> {
1835 self
1836 .webview
1837 .dispatcher
1838 .set_background_color(color)
1839 .map_err(Into::into)
1840 }
1841
1842 pub fn clear_all_browsing_data(&self) -> crate::Result<()> {
1844 self
1845 .webview
1846 .dispatcher
1847 .clear_all_browsing_data()
1848 .map_err(Into::into)
1849 }
1850
1851 pub fn cookies_for_url(&self, url: Url) -> crate::Result<Vec<Cookie<'static>>> {
1869 self
1870 .webview
1871 .dispatcher
1872 .cookies_for_url(url)
1873 .map_err(Into::into)
1874 }
1875
1876 pub fn cookies(&self) -> crate::Result<Vec<Cookie<'static>>> {
1898 self.webview.dispatcher.cookies().map_err(Into::into)
1899 }
1900}
1901
1902impl<R: Runtime> Listener<R> for Webview<R> {
1903 #[cfg_attr(
1907 feature = "unstable",
1908 doc = r####"
1909```
1910use tauri::{Manager, Listener};
1911
1912tauri::Builder::default()
1913 .setup(|app| {
1914 let webview = app.get_webview("main").unwrap();
1915 webview.listen("component-loaded", move |event| {
1916 println!("webview just loaded a component");
1917 });
1918
1919 Ok(())
1920 });
1921```
1922 "####
1923 )]
1924 fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventId
1925 where
1926 F: Fn(Event) + Send + 'static,
1927 {
1928 let event = EventName::new(event.into()).unwrap();
1929 self.manager.listen(
1930 event,
1931 EventTarget::Webview {
1932 label: self.label().to_string(),
1933 },
1934 handler,
1935 )
1936 }
1937
1938 fn once<F>(&self, event: impl Into<String>, handler: F) -> EventId
1942 where
1943 F: FnOnce(Event) + Send + 'static,
1944 {
1945 let event = EventName::new(event.into()).unwrap();
1946 self.manager.once(
1947 event,
1948 EventTarget::Webview {
1949 label: self.label().to_string(),
1950 },
1951 handler,
1952 )
1953 }
1954
1955 #[cfg_attr(
1959 feature = "unstable",
1960 doc = r####"
1961```
1962use tauri::{Manager, Listener};
1963
1964tauri::Builder::default()
1965 .setup(|app| {
1966 let webview = app.get_webview("main").unwrap();
1967 let webview_ = webview.clone();
1968 let handler = webview.listen("component-loaded", move |event| {
1969 println!("webview just loaded a component");
1970
1971 // we no longer need to listen to the event
1972 // we also could have used `webview.once` instead
1973 webview_.unlisten(event.id());
1974 });
1975
1976 // stop listening to the event when you do not need it anymore
1977 webview.unlisten(handler);
1978
1979 Ok(())
1980 });
1981```
1982 "####
1983 )]
1984 fn unlisten(&self, id: EventId) {
1985 self.manager.unlisten(id)
1986 }
1987}
1988
1989impl<R: Runtime> Emitter<R> for Webview<R> {}
1990
1991impl<R: Runtime> Manager<R> for Webview<R> {
1992 fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {
1993 self
1994 .resources_table
1995 .lock()
1996 .expect("poisoned window resources table")
1997 }
1998}
1999
2000impl<R: Runtime> ManagerBase<R> for Webview<R> {
2001 fn manager(&self) -> &AppManager<R> {
2002 &self.manager
2003 }
2004
2005 fn manager_owned(&self) -> Arc<AppManager<R>> {
2006 self.manager.clone()
2007 }
2008
2009 fn runtime(&self) -> RuntimeOrDispatch<'_, R> {
2010 self.app_handle.runtime()
2011 }
2012
2013 fn managed_app_handle(&self) -> &AppHandle<R> {
2014 &self.app_handle
2015 }
2016}
2017
2018impl<'de, R: Runtime> CommandArg<'de, R> for Webview<R> {
2019 fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {
2021 Ok(command.message.webview())
2022 }
2023}
2024
2025pub struct ResolvedScope<T: ScopeObject> {
2027 command_scope: CommandScope<T>,
2028 global_scope: GlobalScope<T>,
2029}
2030
2031impl<T: ScopeObject> ResolvedScope<T> {
2032 pub fn global_scope(&self) -> &GlobalScope<T> {
2034 &self.global_scope
2035 }
2036
2037 pub fn command_scope(&self) -> &CommandScope<T> {
2039 &self.command_scope
2040 }
2041}
2042
2043#[cfg(test)]
2044mod tests {
2045 #[test]
2046 fn webview_is_send_sync() {
2047 crate::test_utils::assert_send::<super::Webview>();
2048 crate::test_utils::assert_sync::<super::Webview>();
2049 }
2050}