// Copyright 2019-2024 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT //! A layer between raw [`Runtime`] webviews and Tauri. //! use crate::{window::is_label_valid, Rect, Runtime, UserEvent}; use http::Request; use tauri_utils::config::{WebviewUrl, WindowConfig, WindowEffectsConfig}; use url::Url; use std::{ borrow::Cow, collections::HashMap, hash::{Hash, Hasher}, path::PathBuf, sync::Arc, }; type UriSchemeProtocol = dyn Fn(http::Request>, Box>) + Send>) + Send + Sync + 'static; type WebResourceRequestHandler = dyn Fn(http::Request>, &mut http::Response>) + Send + Sync; type NavigationHandler = dyn Fn(&Url) -> bool + Send; type OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send; type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync; /// Download event. pub enum DownloadEvent<'a> { /// Download requested. Requested { /// The url being downloaded. url: Url, /// Represents where the file will be downloaded to. /// Can be used to set the download location by assigning a new path to it. /// The assigned path _must_ be absolute. destination: &'a mut PathBuf, }, /// Download finished. Finished { /// The URL of the original download request. url: Url, /// Potentially representing the filesystem path the file was downloaded to. path: Option, /// Indicates if the download succeeded or not. success: bool, }, } #[cfg(target_os = "android")] pub struct CreationContext<'a, 'b> { pub env: &'a mut jni::JNIEnv<'b>, pub activity: &'a jni::objects::JObject<'b>, pub webview: &'a jni::objects::JObject<'b>, } /// Kind of event for the page load handler. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PageLoadEvent { /// Page started to load. Started, /// Page finished loading. Finished, } /// A webview that has yet to be built. pub struct PendingWebview> { /// The label that the webview will be named. pub label: String, /// The [`WebviewAttributes`] that the webview will be created with. pub webview_attributes: WebviewAttributes, pub uri_scheme_protocols: HashMap>, /// How to handle IPC calls on the webview. pub ipc_handler: Option>, /// A handler to decide if incoming url is allowed to navigate. pub navigation_handler: Option>, /// The resolved URL to load on the webview. pub url: String, #[cfg(target_os = "android")] #[allow(clippy::type_complexity)] pub on_webview_created: Option) -> Result<(), jni::errors::Error> + Send>>, pub web_resource_request_handler: Option>, pub on_page_load_handler: Option>, pub download_handler: Option>, } impl> PendingWebview { /// Create a new [`PendingWebview`] with a label from the given [`WebviewAttributes`]. pub fn new( webview_attributes: WebviewAttributes, label: impl Into, ) -> crate::Result { let label = label.into(); if !is_label_valid(&label) { Err(crate::Error::InvalidWindowLabel) } else { Ok(Self { webview_attributes, uri_scheme_protocols: Default::default(), label, ipc_handler: None, navigation_handler: None, url: "tauri://localhost".to_string(), #[cfg(target_os = "android")] on_webview_created: None, web_resource_request_handler: None, on_page_load_handler: None, download_handler: None, }) } } pub fn register_uri_scheme_protocol< N: Into, H: Fn(http::Request>, Box>) + Send>) + Send + Sync + 'static, >( &mut self, uri_scheme: N, protocol: H, ) { let uri_scheme = uri_scheme.into(); self .uri_scheme_protocols .insert(uri_scheme, Box::new(protocol)); } #[cfg(target_os = "android")] pub fn on_webview_created< F: Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + 'static, >( mut self, f: F, ) -> Self { self.on_webview_created.replace(Box::new(f)); self } } /// A webview that is not yet managed by Tauri. #[derive(Debug)] pub struct DetachedWebview> { /// Name of the window pub label: String, /// The [`crate::WebviewDispatch`] associated with the window. pub dispatcher: R::WebviewDispatcher, } impl> Clone for DetachedWebview { fn clone(&self) -> Self { Self { label: self.label.clone(), dispatcher: self.dispatcher.clone(), } } } impl> Hash for DetachedWebview { /// Only use the [`DetachedWebview`]'s label to represent its hash. fn hash(&self, state: &mut H) { self.label.hash(state) } } impl> Eq for DetachedWebview {} impl> PartialEq for DetachedWebview { /// Only use the [`DetachedWebview`]'s label to compare equality. fn eq(&self, other: &Self) -> bool { self.label.eq(&other.label) } } /// The attributes used to create an webview. #[derive(Debug, Clone)] pub struct WebviewAttributes { pub url: WebviewUrl, pub user_agent: Option, pub initialization_scripts: Vec, pub data_directory: Option, pub drag_drop_handler_enabled: bool, pub clipboard: bool, pub accept_first_mouse: bool, pub additional_browser_args: Option, pub window_effects: Option, pub incognito: bool, pub transparent: bool, pub bounds: Option, pub auto_resize: bool, pub proxy_url: Option, pub zoom_hotkeys_enabled: bool, } impl From<&WindowConfig> for WebviewAttributes { fn from(config: &WindowConfig) -> Self { let mut builder = Self::new(config.url.clone()); builder = builder.incognito(config.incognito); #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] { builder = builder.transparent(config.transparent); } builder = builder.accept_first_mouse(config.accept_first_mouse); if !config.drag_drop_enabled { builder = builder.disable_drag_drop_handler(); } if let Some(user_agent) = &config.user_agent { builder = builder.user_agent(user_agent); } if let Some(additional_browser_args) = &config.additional_browser_args { builder = builder.additional_browser_args(additional_browser_args); } if let Some(effects) = &config.window_effects { builder = builder.window_effects(effects.clone()); } if let Some(url) = &config.proxy_url { builder = builder.proxy_url(url.to_owned()); } builder = builder.zoom_hotkeys_enabled(config.zoom_hotkeys_enabled); builder } } impl WebviewAttributes { /// Initializes the default attributes for a webview. pub fn new(url: WebviewUrl) -> Self { Self { url, user_agent: None, initialization_scripts: Vec::new(), data_directory: None, drag_drop_handler_enabled: true, clipboard: false, accept_first_mouse: false, additional_browser_args: None, window_effects: None, incognito: false, transparent: false, bounds: None, auto_resize: false, proxy_url: None, zoom_hotkeys_enabled: false, } } /// Sets the user agent #[must_use] pub fn user_agent(mut self, user_agent: &str) -> Self { self.user_agent = Some(user_agent.to_string()); self } /// Sets the init script. #[must_use] pub fn initialization_script(mut self, script: &str) -> Self { self.initialization_scripts.push(script.to_string()); self } /// Data directory for the webview. #[must_use] pub fn data_directory(mut self, data_directory: PathBuf) -> Self { self.data_directory.replace(data_directory); self } /// Disables the drag and drop handler. This is required to use HTML5 drag and drop APIs on the frontend on Windows. #[must_use] pub fn disable_drag_drop_handler(mut self) -> Self { self.drag_drop_handler_enabled = false; self } /// Enables clipboard access for the page rendered on **Linux** and **Windows**. /// /// **macOS** doesn't provide such method and is always enabled by default, /// but you still need to add menu item accelerators to use shortcuts. #[must_use] pub fn enable_clipboard_access(mut self) -> Self { self.clipboard = true; self } /// Sets whether clicking an inactive window also clicks through to the webview. #[must_use] pub fn accept_first_mouse(mut self, accept: bool) -> Self { self.accept_first_mouse = accept; self } /// Sets additional browser arguments. **Windows Only** #[must_use] pub fn additional_browser_args(mut self, additional_args: &str) -> Self { self.additional_browser_args = Some(additional_args.to_string()); self } /// Sets window effects #[must_use] pub fn window_effects(mut self, effects: WindowEffectsConfig) -> Self { self.window_effects = Some(effects); self } /// Enable or disable incognito mode for the WebView. #[must_use] pub fn incognito(mut self, incognito: bool) -> Self { self.incognito = incognito; self } /// Enable or disable transparency for the WebView. #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] #[must_use] pub fn transparent(mut self, transparent: bool) -> Self { self.transparent = transparent; self } /// Sets the webview to automatically grow and shrink its size and position when the parent window resizes. #[must_use] pub fn auto_resize(mut self) -> Self { self.auto_resize = true; self } /// Enable proxy for the WebView #[must_use] pub fn proxy_url(mut self, url: Url) -> Self { self.proxy_url = Some(url); self } /// Whether page zooming by hotkeys is enabled /// /// ## Platform-specific: /// /// - **Windows**: Controls WebView2's [`IsZoomControlEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings?view=webview2-winrt-1.0.2420.47#iszoomcontrolenabled) setting. /// - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`, /// 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission /// /// - **Android / iOS**: Unsupported. #[must_use] pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self { self.zoom_hotkeys_enabled = enabled; self } } /// IPC handler. pub type WebviewIpcHandler = Box, Request) + Send>;