1pub(crate) mod plugin;
8mod webview_window;
9
10pub use webview_window::{WebviewWindow, WebviewWindowBuilder};
11
12pub use cookie;
18use http::HeaderMap;
19use serde::Serialize;
20use tauri_macros::default_runtime;
21pub use tauri_runtime::webview::{NewWindowFeatures, PageLoadEvent, ScrollBarStyle};
22pub use tauri_runtime::Cookie;
24#[cfg(desktop)]
25use tauri_runtime::{
26 dpi::{PhysicalPosition, PhysicalSize, Position, Size},
27 WindowDispatch,
28};
29use tauri_runtime::{
30 webview::{DetachedWebview, InitializationScript, PendingWebview, WebviewAttributes},
31 WebviewDispatch,
32};
33pub use tauri_utils::config::Color;
34use tauri_utils::config::{BackgroundThrottlingPolicy, WebviewUrl, WindowConfig};
35pub use url::Url;
36
37use crate::{
38 app::{UriSchemeResponder, WebviewEvent},
39 event::{EmitArgs, EventTarget},
40 ipc::{
41 CallbackFn, CommandArg, CommandItem, CommandScope, GlobalScope, Invoke, InvokeBody,
42 InvokeError, InvokeMessage, InvokeResolver, Origin, OwnedInvokeResponder, ScopeObject,
43 },
44 manager::AppManager,
45 path::SafePathBuf,
46 sealed::{ManagerBase, RuntimeOrDispatch},
47 AppHandle, Emitter, Event, EventId, EventLoopMessage, EventName, Listener, Manager,
48 ResourceTable, Runtime, Window,
49};
50
51use std::{
52 borrow::Cow,
53 hash::{Hash, Hasher},
54 path::{Path, PathBuf},
55 sync::{Arc, Mutex, MutexGuard},
56};
57
58pub(crate) type WebResourceRequestHandler =
59 dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
60pub(crate) type NavigationHandler = dyn Fn(&Url) -> bool + Send;
61pub(crate) type NewWindowHandler<R> = dyn Fn(Url, NewWindowFeatures) -> NewWindowResponse<R> + Send;
62pub(crate) type UriSchemeProtocolHandler =
63 Box<dyn Fn(&str, http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync>;
64pub(crate) type OnPageLoad<R> = dyn Fn(Webview<R>, PageLoadPayload<'_>) + Send + Sync + 'static;
65pub(crate) type OnDocumentTitleChanged<R> = dyn Fn(Webview<R>, String) + Send + 'static;
66pub(crate) type DownloadHandler<R> = dyn Fn(Webview<R>, DownloadEvent<'_>) -> bool + Send + Sync;
67
68#[derive(Clone, Serialize)]
69pub(crate) struct CreatedEvent {
70 pub(crate) label: String,
71}
72
73#[non_exhaustive]
75pub enum DownloadEvent<'a> {
76 Requested {
78 url: Url,
80 destination: &'a mut PathBuf,
84 },
85 Finished {
87 url: Url,
89 path: Option<PathBuf>,
100 success: bool,
102 },
103}
104
105#[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 pub fn url(&self) -> &'a Url {
115 self.url
116 }
117
118 pub fn event(&self) -> PageLoadEvent {
120 self.event
121 }
122}
123
124#[derive(Debug)]
131pub struct InvokeRequest {
132 pub cmd: String,
134 pub callback: CallbackFn,
136 pub error: CallbackFn,
138 pub url: Url,
140 pub body: InvokeBody,
142 pub headers: HeaderMap,
144 pub invoke_key: String,
146}
147
148#[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[cfg(target_os = "android")]
233 pub fn jni_handle(&self) -> tauri_runtime_wry::wry::JniHandle {
234 self.0
235 }
236}
237
238pub enum NewWindowResponse<R: Runtime> {
240 Allow,
242 Create {
250 window: crate::WebviewWindow<R>,
252 },
253 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 #[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 #[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 #[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 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 #[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 pub fn from_config(config: &WindowConfig) -> Self {
390 let mut config = config.to_owned();
391
392 if let Some(data_directory) = &config.data_directory {
393 let resolve_data_dir_res = dirs::data_local_dir()
394 .or({
395 #[cfg(feature = "tracing")]
396 tracing::error!("failed to resolve data directory");
397 None
398 })
399 .and_then(|local_dir| {
400 SafePathBuf::new(data_directory.clone())
401 .inspect_err(|_err| {
402 #[cfg(feature = "tracing")]
403 tracing::error!(
404 "data_directory `{}` is not a safe path, ignoring config. Validation error was: {_err}",
405 data_directory.display()
406 );
407 })
408 .map(|p| (local_dir, p))
409 .ok()
410 })
411 .and_then(|(local_dir, data_directory)| {
412 if data_directory.as_ref().is_relative() {
413 Some(local_dir.join(&config.label).join(data_directory.as_ref()))
414 } else {
415 #[cfg(feature = "tracing")]
416 tracing::error!(
417 "data_directory `{}` is not a relative path, ignoring config.",
418 data_directory.display()
419 );
420 None
421 }
422 });
423 if let Some(resolved_data_directory) = resolve_data_dir_res {
424 config.data_directory = Some(resolved_data_directory);
425 }
426 }
427
428 Self {
429 label: config.label.clone(),
430 webview_attributes: WebviewAttributes::from(&config),
431 web_resource_request_handler: None,
432 navigation_handler: None,
433 new_window_handler: None,
434 on_page_load_handler: None,
435 document_title_changed_handler: None,
436 download_handler: None,
437 }
438 }
439
440 #[cfg_attr(
450 feature = "unstable",
451 doc = r####"
452```rust,no_run
453use tauri::{
454 utils::config::{Csp, CspDirectiveSources, WebviewUrl},
455 window::WindowBuilder,
456 webview::WebviewBuilder,
457};
458use http::header::HeaderValue;
459use std::collections::HashMap;
460tauri::Builder::default()
461 .setup(|app| {
462 let window = tauri::window::WindowBuilder::new(app, "label").build()?;
463
464 let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
465 .on_web_resource_request(|request, response| {
466 if request.uri().scheme_str() == Some("tauri") {
467 // if we have a CSP header, Tauri is loading an HTML file
468 // for this example, let's dynamically change the CSP
469 if let Some(csp) = response.headers_mut().get_mut("Content-Security-Policy") {
470 // use the tauri helper to parse the CSP policy to a map
471 let mut csp_map: HashMap<String, CspDirectiveSources> = Csp::Policy(csp.to_str().unwrap().to_string()).into();
472 csp_map.entry("script-src".to_string()).or_insert_with(Default::default).push("'unsafe-inline'");
473 // use the tauri helper to get a CSP string from the map
474 let csp_string = Csp::from(csp_map).to_string();
475 *csp = HeaderValue::from_str(&csp_string).unwrap();
476 }
477 }
478 });
479
480 let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
481
482 Ok(())
483 });
484```
485 "####
486 )]
487 pub fn on_web_resource_request<
488 F: Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync + 'static,
489 >(
490 mut self,
491 f: F,
492 ) -> Self {
493 self.web_resource_request_handler.replace(Box::new(f));
494 self
495 }
496
497 #[cfg_attr(
502 feature = "unstable",
503 doc = r####"
504```rust,no_run
505use tauri::{
506 utils::config::{Csp, CspDirectiveSources, WebviewUrl},
507 window::WindowBuilder,
508 webview::WebviewBuilder,
509};
510use http::header::HeaderValue;
511use std::collections::HashMap;
512tauri::Builder::default()
513 .setup(|app| {
514 let window = tauri::window::WindowBuilder::new(app, "label").build()?;
515
516 let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
517 .on_navigation(|url| {
518 // allow the production URL or localhost on dev
519 url.scheme() == "tauri" || (cfg!(dev) && url.host_str() == Some("localhost"))
520 });
521
522 let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
523 Ok(())
524 });
525```
526 "####
527 )]
528 pub fn on_navigation<F: Fn(&Url) -> bool + Send + 'static>(mut self, f: F) -> Self {
529 self.navigation_handler.replace(Box::new(f));
530 self
531 }
532
533 #[cfg_attr(
540 feature = "unstable",
541 doc = r####"
542```rust,no_run
543use tauri::{
544 utils::config::{Csp, CspDirectiveSources, WebviewUrl},
545 window::WindowBuilder,
546 webview::WebviewBuilder,
547};
548use http::header::HeaderValue;
549use std::collections::HashMap;
550tauri::Builder::default()
551 .setup(|app| {
552 let window = tauri::window::WindowBuilder::new(app, "label").build()?;
553
554 let app_ = app.handle().clone();
555 let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
556 .on_new_window(move |url, features| {
557 let builder = tauri::WebviewWindowBuilder::new(
558 &app_,
559 // note: add an ID counter or random label generator to support multiple opened windows at the same time
560 "opened-window",
561 tauri::WebviewUrl::External("about:blank".parse().unwrap()),
562 )
563 .window_features(features)
564 .on_document_title_changed(|window, title| {
565 window.set_title(&title).unwrap();
566 })
567 .title(url.as_str());
568
569 let window = builder.build().unwrap();
570 tauri::webview::NewWindowResponse::Create { window }
571 });
572
573 let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
574 Ok(())
575 });
576```
577 "####
578 )]
579 pub fn on_new_window<F: Fn(Url, NewWindowFeatures) -> NewWindowResponse<R> + Send + 'static>(
586 mut self,
587 f: F,
588 ) -> Self {
589 self.new_window_handler.replace(Box::new(f));
590 self
591 }
592
593 pub fn on_document_title_changed<F: Fn(Webview<R>, String) + Send + 'static>(
595 mut self,
596 f: F,
597 ) -> Self {
598 self.document_title_changed_handler.replace(Box::new(f));
599 self
600 }
601
602 #[cfg_attr(
609 feature = "unstable",
610 doc = r####"
611```rust,no_run
612use tauri::{
613 utils::config::{Csp, CspDirectiveSources, WebviewUrl},
614 window::WindowBuilder,
615 webview::{DownloadEvent, WebviewBuilder},
616};
617
618tauri::Builder::default()
619 .setup(|app| {
620 let window = WindowBuilder::new(app, "label").build()?;
621 let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
622 .on_download(|webview, event| {
623 match event {
624 DownloadEvent::Requested { url, destination } => {
625 println!("downloading {}", url);
626 *destination = "/home/tauri/target/path".into();
627 }
628 DownloadEvent::Finished { url, path, success } => {
629 println!("downloaded {} to {:?}, success: {}", url, path, success);
630 }
631 _ => (),
632 }
633 // let the download start
634 true
635 });
636
637 let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
638 Ok(())
639 });
640```
641 "####
642 )]
643 pub fn on_download<F: Fn(Webview<R>, DownloadEvent<'_>) -> bool + Send + Sync + 'static>(
644 mut self,
645 f: F,
646 ) -> Self {
647 self.download_handler.replace(Arc::new(f));
648 self
649 }
650
651 #[cfg_attr(
658 feature = "unstable",
659 doc = r####"
660```rust,no_run
661use tauri::{
662 utils::config::{Csp, CspDirectiveSources, WebviewUrl},
663 window::WindowBuilder,
664 webview::{PageLoadEvent, WebviewBuilder},
665};
666use http::header::HeaderValue;
667use std::collections::HashMap;
668tauri::Builder::default()
669 .setup(|app| {
670 let window = tauri::window::WindowBuilder::new(app, "label").build()?;
671 let webview_builder = WebviewBuilder::new("core", WebviewUrl::App("index.html".into()))
672 .on_page_load(|webview, payload| {
673 match payload.event() {
674 PageLoadEvent::Started => {
675 println!("{} finished loading", payload.url());
676 }
677 PageLoadEvent::Finished => {
678 println!("{} finished loading", payload.url());
679 }
680 }
681 });
682 let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
683 Ok(())
684 });
685```
686 "####
687 )]
688 pub fn on_page_load<F: Fn(Webview<R>, PageLoadPayload<'_>) + Send + Sync + 'static>(
689 mut self,
690 f: F,
691 ) -> Self {
692 self.on_page_load_handler.replace(Box::new(f));
693 self
694 }
695
696 pub(crate) fn into_pending_webview<M: Manager<R>>(
697 mut self,
698 manager: &M,
699 window_label: &str,
700 ) -> crate::Result<PendingWebview<EventLoopMessage, R>> {
701 let mut pending = PendingWebview::new(self.webview_attributes, self.label.clone())?;
702 pending.navigation_handler = self.navigation_handler.take();
703 pending.new_window_handler = self.new_window_handler.take().map(|handler| {
704 Box::new(
705 move |url, features: NewWindowFeatures| match handler(url, features) {
706 NewWindowResponse::Allow => tauri_runtime::webview::NewWindowResponse::Allow,
707 #[cfg(mobile)]
708 NewWindowResponse::Create { window: _ } => {
709 tauri_runtime::webview::NewWindowResponse::Allow
710 }
711 #[cfg(desktop)]
712 NewWindowResponse::Create { window } => {
713 tauri_runtime::webview::NewWindowResponse::Create {
714 window_id: window.window.window.id,
715 }
716 }
717 NewWindowResponse::Deny => tauri_runtime::webview::NewWindowResponse::Deny,
718 },
719 )
720 as Box<
721 dyn Fn(Url, NewWindowFeatures) -> tauri_runtime::webview::NewWindowResponse
722 + Send
723 + 'static,
724 >
725 });
726
727 if let Some(document_title_changed_handler) = self.document_title_changed_handler.take() {
728 let label = pending.label.clone();
729 let manager = manager.manager_owned();
730 pending
731 .document_title_changed_handler
732 .replace(Box::new(move |title| {
733 if let Some(w) = manager.get_webview(&label) {
734 document_title_changed_handler(w, title);
735 }
736 }));
737 }
738 pending.web_resource_request_handler = self.web_resource_request_handler.take();
739
740 if let Some(download_handler) = self.download_handler.take() {
741 let label = pending.label.clone();
742 let manager = manager.manager_owned();
743 pending.download_handler.replace(Arc::new(move |event| {
744 if let Some(w) = manager.get_webview(&label) {
745 download_handler(
746 w,
747 match event {
748 tauri_runtime::webview::DownloadEvent::Requested { url, destination } => {
749 DownloadEvent::Requested { url, destination }
750 }
751 tauri_runtime::webview::DownloadEvent::Finished { url, path, success } => {
752 DownloadEvent::Finished { url, path, success }
753 }
754 },
755 )
756 } else {
757 false
758 }
759 }));
760 }
761
762 let label_ = pending.label.clone();
763 let manager_ = manager.manager_owned();
764 pending
765 .on_page_load_handler
766 .replace(Box::new(move |url, event| {
767 if let Some(w) = manager_.get_webview(&label_) {
768 if let Some(handler) = self.on_page_load_handler.as_ref() {
769 handler(w, PageLoadPayload { url: &url, event });
770 }
771 }
772 }));
773
774 manager
775 .manager()
776 .webview
777 .prepare_webview(manager, pending, window_label)
778 }
779
780 #[cfg(desktop)]
782 pub(crate) fn build(
783 self,
784 window: Window<R>,
785 position: Position,
786 size: Size,
787 ) -> crate::Result<Webview<R>> {
788 let app_manager = window.manager();
789
790 let mut pending = self.into_pending_webview(&window, window.label())?;
791
792 pending.webview_attributes.bounds = Some(tauri_runtime::dpi::Rect { size, position });
793
794 let use_https_scheme = pending.webview_attributes.use_https_scheme;
795
796 let webview = match &mut window.runtime() {
797 RuntimeOrDispatch::Dispatch(dispatcher) => dispatcher.create_webview(pending),
798 _ => unimplemented!(),
799 }
800 .map(|webview| {
801 app_manager
802 .webview
803 .attach_webview(window.clone(), webview, use_https_scheme)
804 })?;
805
806 Ok(webview)
807 }
808}
809
810impl<R: Runtime> WebviewBuilder<R> {
812 #[must_use]
814 pub fn accept_first_mouse(mut self, accept: bool) -> Self {
815 self.webview_attributes.accept_first_mouse = accept;
816 self
817 }
818
819 #[cfg_attr(
838 feature = "unstable",
839 doc = r####"
840```rust
841use tauri::{WindowBuilder, Runtime};
842
843const INIT_SCRIPT: &str = r#"
844 if (window.location.origin === 'https://tauri.app') {
845 console.log("hello world from js init script");
846
847 window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };
848 }
849"#;
850
851fn main() {
852 tauri::Builder::default()
853 .setup(|app| {
854 let window = tauri::window::WindowBuilder::new(app, "label").build()?;
855 let webview_builder = tauri::webview::WebviewBuilder::new("label", tauri::WebviewUrl::App("index.html".into()))
856 .initialization_script(INIT_SCRIPT);
857 let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
858 Ok(())
859 });
860}
861```
862 "####
863 )]
864 #[must_use]
868 pub fn initialization_script(mut self, script: impl Into<String>) -> Self {
869 self
870 .webview_attributes
871 .initialization_scripts
872 .push(InitializationScript {
873 script: script.into(),
874 for_main_frame_only: true,
875 });
876 self
877 }
878
879 #[cfg_attr(
897 feature = "unstable",
898 doc = r####"
899```rust
900use tauri::{WindowBuilder, Runtime};
901
902const INIT_SCRIPT: &str = r#"
903 if (window.location.origin === 'https://tauri.app') {
904 console.log("hello world from js init script");
905
906 window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };
907 }
908"#;
909
910fn main() {
911 tauri::Builder::default()
912 .setup(|app| {
913 let window = tauri::window::WindowBuilder::new(app, "label").build()?;
914 let webview_builder = tauri::webview::WebviewBuilder::new("label", tauri::WebviewUrl::App("index.html".into()))
915 .initialization_script_for_all_frames(INIT_SCRIPT);
916 let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
917 Ok(())
918 });
919}
920```
921 "####
922 )]
923 #[must_use]
927 pub fn initialization_script_for_all_frames(mut self, script: impl Into<String>) -> Self {
928 self
929 .webview_attributes
930 .initialization_scripts
931 .push(InitializationScript {
932 script: script.into(),
933 for_main_frame_only: false,
934 });
935 self
936 }
937
938 #[must_use]
940 pub fn user_agent(mut self, user_agent: &str) -> Self {
941 self.webview_attributes.user_agent = Some(user_agent.to_string());
942 self
943 }
944
945 #[must_use]
956 pub fn additional_browser_args(mut self, additional_args: &str) -> Self {
957 self.webview_attributes.additional_browser_args = Some(additional_args.to_string());
958 self
959 }
960
961 #[must_use]
963 pub fn data_directory(mut self, data_directory: PathBuf) -> Self {
964 self
965 .webview_attributes
966 .data_directory
967 .replace(data_directory);
968 self
969 }
970
971 #[must_use]
973 pub fn disable_drag_drop_handler(mut self) -> Self {
974 self.webview_attributes.drag_drop_handler_enabled = false;
975 self
976 }
977
978 #[must_use]
983 pub fn enable_clipboard_access(mut self) -> Self {
984 self.webview_attributes.clipboard = true;
985 self
986 }
987
988 #[must_use]
997 pub fn incognito(mut self, incognito: bool) -> Self {
998 self.webview_attributes.incognito = incognito;
999 self
1000 }
1001
1002 #[must_use]
1010 pub fn proxy_url(mut self, url: Url) -> Self {
1011 self.webview_attributes.proxy_url = Some(url);
1012 self
1013 }
1014
1015 #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
1017 #[cfg_attr(
1018 docsrs,
1019 doc(cfg(any(not(target_os = "macos"), feature = "macos-private-api")))
1020 )]
1021 #[must_use]
1022 pub fn transparent(mut self, transparent: bool) -> Self {
1023 self.webview_attributes.transparent = transparent;
1024 self
1025 }
1026
1027 #[must_use]
1029 pub fn focused(mut self, focus: bool) -> Self {
1030 self.webview_attributes.focus = focus;
1031 self
1032 }
1033
1034 #[must_use]
1036 pub fn auto_resize(mut self) -> Self {
1037 self.webview_attributes.auto_resize = true;
1038 self
1039 }
1040
1041 #[must_use]
1051 pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {
1052 self.webview_attributes.zoom_hotkeys_enabled = enabled;
1053 self
1054 }
1055
1056 #[must_use]
1063 pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self {
1064 self.webview_attributes.browser_extensions_enabled = enabled;
1065 self
1066 }
1067
1068 #[must_use]
1075 pub fn extensions_path(mut self, path: impl AsRef<Path>) -> Self {
1076 self.webview_attributes.extensions_path = Some(path.as_ref().to_path_buf());
1077 self
1078 }
1079
1080 #[must_use]
1088 pub fn data_store_identifier(mut self, data_store_identifier: [u8; 16]) -> Self {
1089 self.webview_attributes.data_store_identifier = Some(data_store_identifier);
1090 self
1091 }
1092
1093 #[must_use]
1103 pub fn use_https_scheme(mut self, enabled: bool) -> Self {
1104 self.webview_attributes.use_https_scheme = enabled;
1105 self
1106 }
1107
1108 #[must_use]
1118 pub fn devtools(mut self, enabled: bool) -> Self {
1119 self.webview_attributes.devtools.replace(enabled);
1120 self
1121 }
1122
1123 #[must_use]
1131 pub fn background_color(mut self, color: Color) -> Self {
1132 self.webview_attributes.background_color = Some(color);
1133 self
1134 }
1135
1136 #[must_use]
1151 pub fn background_throttling(mut self, policy: BackgroundThrottlingPolicy) -> Self {
1152 self.webview_attributes.background_throttling = Some(policy);
1153 self
1154 }
1155
1156 #[must_use]
1158 pub fn disable_javascript(mut self) -> Self {
1159 self.webview_attributes.javascript_disabled = true;
1160 self
1161 }
1162
1163 #[must_use]
1177 pub fn scroll_bar_style(mut self, style: ScrollBarStyle) -> Self {
1178 self.webview_attributes = self.webview_attributes.scroll_bar_style(style);
1179 self
1180 }
1181
1182 #[must_use]
1200 pub fn general_autofill_enabled(mut self, enabled: bool) -> Self {
1201 self.webview_attributes = self.webview_attributes.general_autofill_enabled(enabled);
1202 self
1203 }
1204
1205 #[cfg(target_os = "macos")]
1215 #[must_use]
1216 pub fn allow_link_preview(mut self, allow_link_preview: bool) -> Self {
1217 self.webview_attributes = self
1218 .webview_attributes
1219 .allow_link_preview(allow_link_preview);
1220 self
1221 }
1222
1223 #[cfg(target_os = "ios")]
1235 pub fn with_input_accessory_view_builder<
1236 F: Fn(&objc2_ui_kit::UIView) -> Option<objc2::rc::Retained<objc2_ui_kit::UIView>>
1237 + Send
1238 + Sync
1239 + 'static,
1240 >(
1241 mut self,
1242 builder: F,
1243 ) -> Self {
1244 self
1245 .webview_attributes
1246 .input_accessory_view_builder
1247 .replace(tauri_runtime::webview::InputAccessoryViewBuilder::new(
1248 Box::new(builder),
1249 ));
1250 self
1251 }
1252
1253 #[cfg(all(feature = "wry", windows))]
1256 pub fn with_environment(
1257 mut self,
1258 environment: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment,
1259 ) -> Self {
1260 self.webview_attributes.environment.replace(environment);
1261 self
1262 }
1263
1264 #[cfg(all(
1267 feature = "wry",
1268 any(
1269 target_os = "linux",
1270 target_os = "dragonfly",
1271 target_os = "freebsd",
1272 target_os = "netbsd",
1273 target_os = "openbsd",
1274 )
1275 ))]
1276 pub fn with_related_view(mut self, related_view: webkit2gtk::WebView) -> Self {
1277 self.webview_attributes.related_view.replace(related_view);
1278 self
1279 }
1280
1281 #[cfg(target_os = "macos")]
1284 pub fn with_webview_configuration(
1285 mut self,
1286 webview_configuration: objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>,
1287 ) -> Self {
1288 self
1289 .webview_attributes
1290 .webview_configuration
1291 .replace(webview_configuration);
1292 self
1293 }
1294}
1295
1296#[default_runtime(crate::Wry, wry)]
1298pub struct Webview<R: Runtime> {
1299 pub(crate) window: Arc<Mutex<Window<R>>>,
1300 pub(crate) webview: DetachedWebview<EventLoopMessage, R>,
1302 pub(crate) manager: Arc<AppManager<R>>,
1304 pub(crate) app_handle: AppHandle<R>,
1305 pub(crate) resources_table: Arc<Mutex<ResourceTable>>,
1306 use_https_scheme: bool,
1307}
1308
1309impl<R: Runtime> std::fmt::Debug for Webview<R> {
1310 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1311 f.debug_struct("Window")
1312 .field("window", &self.window.lock().unwrap())
1313 .field("webview", &self.webview)
1314 .field("use_https_scheme", &self.use_https_scheme)
1315 .finish()
1316 }
1317}
1318
1319impl<R: Runtime> Clone for Webview<R> {
1320 fn clone(&self) -> Self {
1321 Self {
1322 window: self.window.clone(),
1323 webview: self.webview.clone(),
1324 manager: self.manager.clone(),
1325 app_handle: self.app_handle.clone(),
1326 resources_table: self.resources_table.clone(),
1327 use_https_scheme: self.use_https_scheme,
1328 }
1329 }
1330}
1331
1332impl<R: Runtime> Hash for Webview<R> {
1333 fn hash<H: Hasher>(&self, state: &mut H) {
1335 self.webview.label.hash(state)
1336 }
1337}
1338
1339impl<R: Runtime> Eq for Webview<R> {}
1340impl<R: Runtime> PartialEq for Webview<R> {
1341 fn eq(&self, other: &Self) -> bool {
1343 self.webview.label.eq(&other.webview.label)
1344 }
1345}
1346
1347impl<R: Runtime> Webview<R> {
1349 pub(crate) fn new(
1351 window: Window<R>,
1352 webview: DetachedWebview<EventLoopMessage, R>,
1353 use_https_scheme: bool,
1354 ) -> Self {
1355 Self {
1356 manager: window.manager.clone(),
1357 app_handle: window.app_handle.clone(),
1358 window: Arc::new(Mutex::new(window)),
1359 webview,
1360 resources_table: Default::default(),
1361 use_https_scheme,
1362 }
1363 }
1364
1365 #[cfg(feature = "unstable")]
1369 #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
1370 pub fn builder<L: Into<String>>(label: L, url: WebviewUrl) -> WebviewBuilder<R> {
1371 WebviewBuilder::new(label.into(), url)
1372 }
1373
1374 pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {
1376 self
1377 .webview
1378 .dispatcher
1379 .run_on_main_thread(f)
1380 .map_err(Into::into)
1381 }
1382
1383 pub fn label(&self) -> &str {
1385 &self.webview.label
1386 }
1387
1388 pub(crate) fn use_https_scheme(&self) -> bool {
1390 self.use_https_scheme
1391 }
1392
1393 pub fn on_webview_event<F: Fn(&WebviewEvent) + Send + 'static>(&self, f: F) {
1395 self
1396 .webview
1397 .dispatcher
1398 .on_webview_event(move |event| f(&event.clone().into()));
1399 }
1400
1401 pub fn resolve_command_scope<T: ScopeObject>(
1440 &self,
1441 plugin: &str,
1442 command: &str,
1443 ) -> crate::Result<Option<ResolvedScope<T>>> {
1444 let current_url = self.url()?;
1445 let is_local = self.is_local_url(¤t_url);
1446 let origin = if is_local {
1447 Origin::Local
1448 } else {
1449 Origin::Remote { url: current_url }
1450 };
1451
1452 let cmd_name = format!("plugin:{plugin}|{command}");
1453 let resolved_access = self
1454 .manager()
1455 .runtime_authority
1456 .lock()
1457 .unwrap()
1458 .resolve_access(&cmd_name, self.window().label(), self.label(), &origin);
1459
1460 if let Some(access) = resolved_access {
1461 let scope_ids = access
1462 .iter()
1463 .filter_map(|cmd| cmd.scope_id)
1464 .collect::<Vec<_>>();
1465
1466 let command_scope = CommandScope::resolve(self, scope_ids)?;
1467 let global_scope = GlobalScope::resolve(self, plugin)?;
1468
1469 Ok(Some(ResolvedScope {
1470 global_scope,
1471 command_scope,
1472 }))
1473 } else {
1474 Ok(None)
1475 }
1476 }
1477}
1478
1479#[cfg(desktop)]
1481impl<R: Runtime> Webview<R> {
1482 pub fn print(&self) -> crate::Result<()> {
1486 self.webview.dispatcher.print().map_err(Into::into)
1487 }
1488
1489 pub fn cursor_position(&self) -> crate::Result<PhysicalPosition<f64>> {
1498 self.app_handle.cursor_position()
1499 }
1500
1501 pub fn close(&self) -> crate::Result<()> {
1503 self.webview.dispatcher.close()?;
1504 self.manager().on_webview_close(self.label());
1505 Ok(())
1506 }
1507
1508 pub fn set_bounds(&self, bounds: tauri_runtime::dpi::Rect) -> crate::Result<()> {
1510 self
1511 .webview
1512 .dispatcher
1513 .set_bounds(bounds)
1514 .map_err(Into::into)
1515 }
1516
1517 pub fn set_size<S: Into<Size>>(&self, size: S) -> crate::Result<()> {
1519 self
1520 .webview
1521 .dispatcher
1522 .set_size(size.into())
1523 .map_err(Into::into)
1524 }
1525
1526 pub fn set_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {
1528 self
1529 .webview
1530 .dispatcher
1531 .set_position(position.into())
1532 .map_err(Into::into)
1533 }
1534
1535 pub fn set_focus(&self) -> crate::Result<()> {
1537 self.webview.dispatcher.set_focus().map_err(Into::into)
1538 }
1539
1540 pub fn hide(&self) -> crate::Result<()> {
1542 self.webview.dispatcher.hide().map_err(Into::into)
1543 }
1544
1545 pub fn show(&self) -> crate::Result<()> {
1547 self.webview.dispatcher.show().map_err(Into::into)
1548 }
1549
1550 pub fn reparent(&self, window: &Window<R>) -> crate::Result<()> {
1552 #[cfg(not(feature = "unstable"))]
1553 {
1554 if self.window_ref().is_webview_window() || window.is_webview_window() {
1555 return Err(crate::Error::CannotReparentWebviewWindow);
1556 }
1557 }
1558
1559 *self.window.lock().unwrap() = window.clone();
1560 self.webview.dispatcher.reparent(window.window.id)?;
1561 Ok(())
1562 }
1563
1564 pub fn set_auto_resize(&self, auto_resize: bool) -> crate::Result<()> {
1566 self
1567 .webview
1568 .dispatcher
1569 .set_auto_resize(auto_resize)
1570 .map_err(Into::into)
1571 }
1572
1573 pub fn bounds(&self) -> crate::Result<tauri_runtime::dpi::Rect> {
1575 self.webview.dispatcher.bounds().map_err(Into::into)
1576 }
1577
1578 pub fn position(&self) -> crate::Result<PhysicalPosition<i32>> {
1583 self.webview.dispatcher.position().map_err(Into::into)
1584 }
1585
1586 pub fn size(&self) -> crate::Result<PhysicalSize<u32>> {
1588 self.webview.dispatcher.size().map_err(Into::into)
1589 }
1590}
1591
1592impl<R: Runtime> Webview<R> {
1594 pub fn window(&self) -> Window<R> {
1596 self.window.lock().unwrap().clone()
1597 }
1598
1599 pub fn window_ref(&self) -> MutexGuard<'_, Window<R>> {
1601 self.window.lock().unwrap()
1602 }
1603
1604 pub(crate) fn window_label(&self) -> String {
1605 self.window_ref().label().to_string()
1606 }
1607
1608 #[cfg_attr(
1618 feature = "unstable",
1619 doc = r####"
1620```rust,no_run
1621use tauri::Manager;
1622
1623tauri::Builder::default()
1624 .setup(|app| {
1625 let main_webview = app.get_webview("main").unwrap();
1626 main_webview.with_webview(|webview| {
1627 #[cfg(target_os = "linux")]
1628 {
1629 // see <https://docs.rs/webkit2gtk/2.0.0/webkit2gtk/struct.WebView.html>
1630 // and <https://docs.rs/webkit2gtk/2.0.0/webkit2gtk/trait.WebViewExt.html>
1631 use webkit2gtk::WebViewExt;
1632 webview.inner().set_zoom_level(4.);
1633 }
1634
1635 #[cfg(windows)]
1636 unsafe {
1637 // see https://docs.rs/webview2-com/0.19.1/webview2_com/Microsoft/Web/WebView2/Win32/struct.ICoreWebView2Controller.html
1638 webview.controller().SetZoomFactor(4.).unwrap();
1639 }
1640
1641 #[cfg(target_os = "macos")]
1642 unsafe {
1643 let view: &objc2_web_kit::WKWebView = &*webview.inner().cast();
1644 let controller: &objc2_web_kit::WKUserContentController = &*webview.controller().cast();
1645 let window: &objc2_app_kit::NSWindow = &*webview.ns_window().cast();
1646
1647 view.setPageZoom(4.);
1648 controller.removeAllUserScripts();
1649 let bg_color = objc2_app_kit::NSColor::colorWithDeviceRed_green_blue_alpha(0.5, 0.2, 0.4, 1.);
1650 window.setBackgroundColor(Some(&bg_color));
1651 }
1652
1653 #[cfg(target_os = "android")]
1654 {
1655 use jni::objects::JValue;
1656 webview.jni_handle().exec(|env, _, webview| {
1657 env.call_method(webview, "zoomBy", "(F)V", &[JValue::Float(4.)]).unwrap();
1658 })
1659 }
1660 });
1661 Ok(())
1662});
1663```
1664 "####
1665 )]
1666 #[cfg(feature = "wry")]
1667 #[cfg_attr(docsrs, doc(cfg(feature = "wry")))]
1668 pub fn with_webview<F: FnOnce(PlatformWebview) + Send + 'static>(
1669 &self,
1670 f: F,
1671 ) -> crate::Result<()> {
1672 self
1673 .webview
1674 .dispatcher
1675 .with_webview(|w| f(PlatformWebview(*w.downcast().unwrap())))
1676 .map_err(Into::into)
1677 }
1678
1679 pub fn url(&self) -> crate::Result<Url> {
1681 self
1682 .webview
1683 .dispatcher
1684 .url()
1685 .map(|url| url.parse().map_err(crate::Error::InvalidUrl))?
1686 }
1687
1688 pub fn navigate(&self, url: Url) -> crate::Result<()> {
1690 self.webview.dispatcher.navigate(url).map_err(Into::into)
1691 }
1692
1693 pub fn reload(&self) -> crate::Result<()> {
1695 self.webview.dispatcher.reload().map_err(Into::into)
1696 }
1697
1698 fn is_local_url(&self, current_url: &Url) -> bool {
1699 let uses_https = current_url.scheme() == "https";
1700
1701 ({
1703 let protocol_url = self.manager().tauri_protocol_url(uses_https);
1704 current_url.scheme() == protocol_url.scheme()
1705 && current_url.domain() == protocol_url.domain()
1706 }) ||
1707
1708 self
1710 .manager()
1711 .get_app_url(uses_https)
1712 .make_relative(current_url)
1713 .is_some()
1714
1715 || ({
1717 let scheme = current_url.scheme();
1718 let protocols = self.manager().webview.uri_scheme_protocols.lock().unwrap();
1719
1720 #[cfg(all(not(windows), not(target_os = "android")))]
1721 let local = protocols.contains_key(scheme);
1722
1723 #[cfg(any(windows, target_os = "android"))]
1726 let local = {
1727 let scheme = scheme == self.manager().tauri_protocol_url(uses_https).scheme();
1728 let protocol = current_url
1729 .domain()
1730 .and_then(|d| d.strip_suffix(".localhost"))
1731 .map(|protocol| protocols.contains_key(protocol))
1732 .unwrap_or_default();
1733
1734 scheme && protocol
1735 };
1736
1737 local
1738 })
1739 }
1740
1741 pub fn on_message(self, request: InvokeRequest, responder: Box<OwnedInvokeResponder<R>>) {
1743 let manager = self.manager_owned();
1744 let is_local = self.is_local_url(&request.url);
1745
1746 let expected = manager.invoke_key();
1748 if request.invoke_key != expected {
1749 #[cfg(feature = "tracing")]
1750 tracing::error!(
1751 "__TAURI_INVOKE_KEY__ expected {expected} but received {}",
1752 request.invoke_key
1753 );
1754
1755 #[cfg(not(feature = "tracing"))]
1756 eprintln!(
1757 "__TAURI_INVOKE_KEY__ expected {expected} but received {}",
1758 request.invoke_key
1759 );
1760
1761 return;
1762 }
1763
1764 let resolver = InvokeResolver::new(
1765 self.clone(),
1766 Arc::new(Mutex::new(Some(Box::new(
1767 move |webview: Webview<R>, cmd, response, callback, error| {
1768 responder(webview, cmd, response, callback, error);
1769 },
1770 )))),
1771 request.cmd.clone(),
1772 request.callback,
1773 request.error,
1774 );
1775
1776 #[cfg(mobile)]
1777 let app_handle = self.app_handle.clone();
1778
1779 let message = InvokeMessage::new(
1780 self,
1781 manager.state(),
1782 request.cmd.to_string(),
1783 request.body,
1784 request.headers,
1785 );
1786
1787 let acl_origin = if is_local {
1788 Origin::Local
1789 } else {
1790 Origin::Remote {
1791 url: request.url.clone(),
1792 }
1793 };
1794 let (resolved_acl, has_app_acl_manifest) = {
1795 let runtime_authority = manager.runtime_authority.lock().unwrap();
1796 let acl = runtime_authority.resolve_access(
1797 &request.cmd,
1798 message.webview.window_ref().label(),
1799 message.webview.label(),
1800 &acl_origin,
1801 );
1802 (acl, runtime_authority.has_app_manifest())
1803 };
1804
1805 let mut invoke = Invoke {
1806 message,
1807 resolver: resolver.clone(),
1808 acl: resolved_acl,
1809 };
1810
1811 let plugin_command = request.cmd.strip_prefix("plugin:").map(|raw_command| {
1812 let mut tokens = raw_command.split('|');
1813 let plugin = tokens.next().unwrap();
1815 let command = tokens.next().map(|c| c.to_string()).unwrap_or_default();
1816 (plugin, command)
1817 });
1818
1819 if (plugin_command.is_some() || has_app_acl_manifest || !is_local)
1824 && request.cmd != crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND
1826 && invoke.acl.is_none()
1827 {
1828 #[cfg(debug_assertions)]
1829 {
1830 let (key, command_name) = plugin_command
1831 .clone()
1832 .unwrap_or_else(|| (tauri_utils::acl::APP_ACL_KEY, request.cmd.clone()));
1833 invoke.resolver.reject(
1834 manager
1835 .runtime_authority
1836 .lock()
1837 .unwrap()
1838 .resolve_access_message(
1839 key,
1840 &command_name,
1841 invoke.message.webview.window().label(),
1842 invoke.message.webview.label(),
1843 &acl_origin,
1844 ),
1845 );
1846 }
1847 #[cfg(not(debug_assertions))]
1848 invoke
1849 .resolver
1850 .reject(format!("Command {} not allowed by ACL", request.cmd));
1851 return;
1852 }
1853
1854 if let Some((plugin, command_name)) = plugin_command {
1855 invoke.message.command = command_name;
1856
1857 let command = invoke.message.command.clone();
1858
1859 #[cfg(mobile)]
1860 let message = invoke.message.clone();
1861
1862 #[allow(unused_mut)]
1863 let mut handled = manager.extend_api(plugin, invoke);
1864
1865 #[cfg(mobile)]
1866 {
1867 if !handled {
1868 handled = true;
1869
1870 fn load_channels<R: Runtime>(payload: &serde_json::Value, webview: &Webview<R>) {
1871 use std::str::FromStr;
1872
1873 if let serde_json::Value::Object(map) = payload {
1874 for v in map.values() {
1875 if let serde_json::Value::String(s) = v {
1876 let _ = crate::ipc::JavaScriptChannelId::from_str(s)
1877 .map(|id| id.channel_on::<R, ()>(webview.clone()));
1878 }
1879 }
1880 }
1881 }
1882
1883 let payload = message.payload.into_json();
1884 load_channels(&payload, &message.webview);
1886
1887 let resolver_ = resolver.clone();
1888 if let Err(e) = crate::plugin::mobile::run_command(
1889 plugin,
1890 &app_handle,
1891 heck::AsLowerCamelCase(message.command).to_string(),
1892 payload,
1893 move |response| match response {
1894 Ok(r) => resolver_.resolve(r),
1895 Err(e) => resolver_.reject(e),
1896 },
1897 ) {
1898 resolver.reject(e.to_string());
1899 return;
1900 }
1901 }
1902 }
1903
1904 if !handled {
1905 resolver.reject(format!("Command {command} not found"));
1906 }
1907 } else {
1908 let command = invoke.message.command.clone();
1909 let handled = manager.run_invoke_handler(invoke);
1910 if !handled {
1911 resolver.reject(format!("Command {command} not found"));
1912 }
1913 }
1914 }
1915
1916 pub fn eval(&self, js: impl Into<String>) -> crate::Result<()> {
1918 self
1919 .webview
1920 .dispatcher
1921 .eval_script(js.into())
1922 .map_err(Into::into)
1923 }
1924
1925 pub fn eval_with_callback(
1930 &self,
1931 js: impl Into<String>,
1932 callback: impl Fn(String) + Send + 'static,
1933 ) -> crate::Result<()> {
1934 self
1935 .webview
1936 .dispatcher
1937 .eval_script_with_callback(js.into(), callback)
1938 .map_err(Into::into)
1939 }
1940
1941 pub(crate) fn listen_js(
1943 &self,
1944 event: EventName<&str>,
1945 target: EventTarget,
1946 handler: CallbackFn,
1947 ) -> crate::Result<EventId> {
1948 let listeners = self.manager().listeners();
1949
1950 let id = listeners.next_event_id();
1951
1952 self.eval(crate::event::listen_js_script(
1953 listeners.listeners_object_name(),
1954 &serde_json::to_string(&target)?,
1955 event,
1956 id,
1957 handler,
1958 ))?;
1959
1960 listeners.listen_js(event, self.label(), target, id);
1961
1962 Ok(id)
1963 }
1964
1965 pub(crate) fn unlisten_js(&self, event: EventName<&str>, id: EventId) -> crate::Result<()> {
1967 let listeners = self.manager().listeners();
1968
1969 listeners.unlisten_js(event, id);
1970
1971 Ok(())
1972 }
1973
1974 pub(crate) fn emit_js(&self, emit_args: &EmitArgs, ids: &[u32]) -> crate::Result<()> {
1975 self.eval(crate::event::emit_js_script(
1976 self.manager().listeners().function_name(),
1977 emit_args,
1978 &serde_json::to_string(ids)?,
1979 )?)?;
1980 Ok(())
1981 }
1982
1983 #[cfg_attr(
1994 feature = "unstable",
1995 doc = r####"
1996```rust,no_run
1997use tauri::Manager;
1998tauri::Builder::default()
1999 .setup(|app| {
2000 #[cfg(debug_assertions)]
2001 app.get_webview("main").unwrap().open_devtools();
2002 Ok(())
2003 });
2004```
2005 "####
2006 )]
2007 #[cfg(any(debug_assertions, feature = "devtools"))]
2008 #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = "devtools"))))]
2009 pub fn open_devtools(&self) {
2010 self.webview.dispatcher.open_devtools();
2011 }
2012
2013 #[cfg_attr(
2025 feature = "unstable",
2026 doc = r####"
2027```rust,no_run
2028use tauri::Manager;
2029tauri::Builder::default()
2030 .setup(|app| {
2031 #[cfg(debug_assertions)]
2032 {
2033 let webview = app.get_webview("main").unwrap();
2034 webview.open_devtools();
2035 std::thread::spawn(move || {
2036 std::thread::sleep(std::time::Duration::from_secs(10));
2037 webview.close_devtools();
2038 });
2039 }
2040 Ok(())
2041 });
2042```
2043 "####
2044 )]
2045 #[cfg(any(debug_assertions, feature = "devtools"))]
2046 #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = "devtools"))))]
2047 pub fn close_devtools(&self) {
2048 self.webview.dispatcher.close_devtools();
2049 }
2050
2051 #[cfg_attr(
2063 feature = "unstable",
2064 doc = r####"
2065```rust,no_run
2066use tauri::Manager;
2067tauri::Builder::default()
2068 .setup(|app| {
2069 #[cfg(debug_assertions)]
2070 {
2071 let webview = app.get_webview("main").unwrap();
2072 if !webview.is_devtools_open() {
2073 webview.open_devtools();
2074 }
2075 }
2076 Ok(())
2077 });
2078```
2079 "####
2080 )]
2081 #[cfg(any(debug_assertions, feature = "devtools"))]
2082 #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = "devtools"))))]
2083 pub fn is_devtools_open(&self) -> bool {
2084 self
2085 .webview
2086 .dispatcher
2087 .is_devtools_open()
2088 .unwrap_or_default()
2089 }
2090
2091 pub fn set_zoom(&self, scale_factor: f64) -> crate::Result<()> {
2099 self
2100 .webview
2101 .dispatcher
2102 .set_zoom(scale_factor)
2103 .map_err(Into::into)
2104 }
2105
2106 pub fn set_background_color(&self, color: Option<Color>) -> crate::Result<()> {
2115 self
2116 .webview
2117 .dispatcher
2118 .set_background_color(color)
2119 .map_err(Into::into)
2120 }
2121
2122 pub fn clear_all_browsing_data(&self) -> crate::Result<()> {
2124 self
2125 .webview
2126 .dispatcher
2127 .clear_all_browsing_data()
2128 .map_err(Into::into)
2129 }
2130
2131 pub fn cookies_for_url(&self, url: Url) -> crate::Result<Vec<Cookie<'static>>> {
2145 self
2146 .webview
2147 .dispatcher
2148 .cookies_for_url(url)
2149 .map_err(Into::into)
2150 }
2151
2152 pub fn cookies(&self) -> crate::Result<Vec<Cookie<'static>>> {
2174 self.webview.dispatcher.cookies().map_err(Into::into)
2175 }
2176
2177 pub fn set_cookie(&self, cookie: Cookie<'_>) -> crate::Result<()> {
2183 self
2184 .webview
2185 .dispatcher
2186 .set_cookie(cookie)
2187 .map_err(Into::into)
2188 }
2189
2190 pub fn delete_cookie(&self, cookie: Cookie<'_>) -> crate::Result<()> {
2196 self
2197 .webview
2198 .dispatcher
2199 .delete_cookie(cookie)
2200 .map_err(Into::into)
2201 }
2202}
2203
2204impl<R: Runtime> Listener<R> for Webview<R> {
2205 #[cfg_attr(
2209 feature = "unstable",
2210 doc = r####"
2211```
2212use tauri::{Manager, Listener};
2213
2214tauri::Builder::default()
2215 .setup(|app| {
2216 let webview = app.get_webview("main").unwrap();
2217 webview.listen("component-loaded", move |event| {
2218 println!("webview just loaded a component");
2219 });
2220
2221 Ok(())
2222 });
2223```
2224 "####
2225 )]
2226 fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventId
2227 where
2228 F: Fn(Event) + Send + 'static,
2229 {
2230 let event = EventName::new(event.into()).unwrap();
2231 self.manager.listen(
2232 event,
2233 EventTarget::Webview {
2234 label: self.label().to_string(),
2235 },
2236 handler,
2237 )
2238 }
2239
2240 fn once<F>(&self, event: impl Into<String>, handler: F) -> EventId
2244 where
2245 F: FnOnce(Event) + Send + 'static,
2246 {
2247 let event = EventName::new(event.into()).unwrap();
2248 self.manager.once(
2249 event,
2250 EventTarget::Webview {
2251 label: self.label().to_string(),
2252 },
2253 handler,
2254 )
2255 }
2256
2257 #[cfg_attr(
2261 feature = "unstable",
2262 doc = r####"
2263```
2264use tauri::{Manager, Listener};
2265
2266tauri::Builder::default()
2267 .setup(|app| {
2268 let webview = app.get_webview("main").unwrap();
2269 let webview_ = webview.clone();
2270 let handler = webview.listen("component-loaded", move |event| {
2271 println!("webview just loaded a component");
2272
2273 // we no longer need to listen to the event
2274 // we also could have used `webview.once` instead
2275 webview_.unlisten(event.id());
2276 });
2277
2278 // stop listening to the event when you do not need it anymore
2279 webview.unlisten(handler);
2280
2281 Ok(())
2282 });
2283```
2284 "####
2285 )]
2286 fn unlisten(&self, id: EventId) {
2287 self.manager.unlisten(id)
2288 }
2289}
2290
2291impl<R: Runtime> Emitter<R> for Webview<R> {}
2292
2293impl<R: Runtime> Manager<R> for Webview<R> {
2294 fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {
2295 self
2296 .resources_table
2297 .lock()
2298 .expect("poisoned window resources table")
2299 }
2300}
2301
2302impl<R: Runtime> ManagerBase<R> for Webview<R> {
2303 fn manager(&self) -> &AppManager<R> {
2304 &self.manager
2305 }
2306
2307 fn manager_owned(&self) -> Arc<AppManager<R>> {
2308 self.manager.clone()
2309 }
2310
2311 fn runtime(&self) -> RuntimeOrDispatch<'_, R> {
2312 self.app_handle.runtime()
2313 }
2314
2315 fn managed_app_handle(&self) -> &AppHandle<R> {
2316 &self.app_handle
2317 }
2318
2319 #[cfg(target_os = "android")]
2320 fn activity_name(&self) -> Option<crate::Result<String>> {
2321 Some(self.window().activity_name())
2322 }
2323
2324 #[cfg(target_os = "ios")]
2325 fn scene_identifier(&self) -> Option<crate::Result<String>> {
2326 Some(self.window().scene_identifier())
2327 }
2328}
2329
2330impl<'de, R: Runtime> CommandArg<'de, R> for Webview<R> {
2331 fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {
2333 Ok(command.message.webview())
2334 }
2335}
2336
2337pub struct ResolvedScope<T: ScopeObject> {
2339 command_scope: CommandScope<T>,
2340 global_scope: GlobalScope<T>,
2341}
2342
2343impl<T: ScopeObject> ResolvedScope<T> {
2344 pub fn global_scope(&self) -> &GlobalScope<T> {
2346 &self.global_scope
2347 }
2348
2349 pub fn command_scope(&self) -> &CommandScope<T> {
2351 &self.command_scope
2352 }
2353}
2354
2355#[cfg(test)]
2356mod tests {
2357 use url::Url;
2358
2359 fn test_webview_window() -> crate::WebviewWindow<crate::test::MockRuntime> {
2360 use crate::test::{mock_builder, mock_context, noop_assets};
2361
2362 let app = mock_builder().build(mock_context(noop_assets())).unwrap();
2364
2365 crate::WebviewWindowBuilder::new(&app, "test", crate::WebviewUrl::default())
2367 .build()
2368 .unwrap()
2369 }
2370
2371 #[test]
2372 fn webview_is_send_sync() {
2373 crate::test_utils::assert_send::<super::Webview>();
2374 crate::test_utils::assert_sync::<super::Webview>();
2375 }
2376
2377 #[test]
2378 fn tauri_protocol_is_local() {
2379 let webview = test_webview_window().webview;
2380
2381 #[cfg(all(not(windows), not(target_os = "android")))]
2382 assert!(webview.is_local_url(&Url::parse("tauri://localhost/").unwrap()));
2383
2384 #[cfg(any(windows, target_os = "android"))]
2385 assert!(webview.is_local_url(&Url::parse("https://tauri.localhost/").unwrap()));
2386 }
2387
2388 #[cfg(any(windows, target_os = "android"))]
2392 #[test]
2393 fn windows_custom_protocol_rejects_spoofed_domain() {
2394 use crate::test::{mock_builder, mock_context, noop_assets};
2395
2396 let app = mock_builder()
2397 .register_uri_scheme_protocol("myproto", |_, _| {
2398 http::Response::builder().body(Vec::new()).unwrap()
2399 })
2400 .build(mock_context(noop_assets()))
2401 .unwrap();
2402 let webview = crate::WebviewWindowBuilder::new(&app, "test", crate::WebviewUrl::default())
2403 .build()
2404 .unwrap()
2405 .webview;
2406
2407 let url = |s| Url::parse(s).unwrap();
2408
2409 assert!(webview.is_local_url(&url("https://myproto.localhost/")));
2411
2412 assert!(!webview.is_local_url(&url("https://myproto.evil.com/")));
2414
2415 assert!(!webview.is_local_url(&url("https://notregistered.localhost/")));
2417 }
2418
2419 #[test]
2424 fn remote_origin_blocked_for_custom_commands_without_app_manifest() {
2425 use crate::test::{mock_builder, mock_context, noop_assets, INVOKE_KEY};
2426 use crate::webview::InvokeRequest;
2427
2428 let app = mock_builder().build(mock_context(noop_assets())).unwrap();
2429
2430 let webview = crate::WebviewWindowBuilder::new(&app, "main", Default::default())
2431 .build()
2432 .unwrap();
2433
2434 let remote_result = crate::test::get_ipc_response(
2437 &webview,
2438 InvokeRequest {
2439 cmd: "any_custom_command".into(),
2440 callback: crate::ipc::CallbackFn(0),
2441 error: crate::ipc::CallbackFn(1),
2442 url: "https://evil.com".parse().unwrap(),
2443 body: crate::ipc::InvokeBody::default(),
2444 headers: Default::default(),
2445 invoke_key: INVOKE_KEY.to_string(),
2446 },
2447 );
2448 assert!(
2449 remote_result.is_err(),
2450 "custom command should be rejected from a remote origin"
2451 );
2452
2453 let local_result = crate::test::get_ipc_response(
2457 &webview,
2458 InvokeRequest {
2459 cmd: "any_custom_command".into(),
2460 callback: crate::ipc::CallbackFn(0),
2461 error: crate::ipc::CallbackFn(1),
2462 url: "tauri://localhost".parse().unwrap(),
2463 body: crate::ipc::InvokeBody::default(),
2464 headers: Default::default(),
2465 invoke_key: INVOKE_KEY.to_string(),
2466 },
2467 );
2468 if let Err(e) = &local_result {
2471 let msg = e.to_string();
2472 assert!(
2473 !msg.contains("not allowed from remote context"),
2474 "local origin should not be blocked by the remote-origin guard, got: {msg}"
2475 );
2476 }
2477 }
2478
2479 #[cfg(target_os = "macos")]
2480 #[test]
2481 fn test_webview_window_has_set_simple_fullscreen_method() {
2482 let webview_window = test_webview_window();
2483
2484 let result = webview_window.set_simple_fullscreen(true);
2486
2487 assert!(result.is_ok(), "set_simple_fullscreen should succeed");
2489 }
2490}