webview.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. //! A layer between raw [`Runtime`] webviews and Tauri.
  5. //!
  6. use crate::{window::is_label_valid, Rect, Runtime, UserEvent};
  7. use http::Request;
  8. use tauri_utils::config::{WebviewUrl, WindowConfig, WindowEffectsConfig};
  9. use url::Url;
  10. use std::{
  11. borrow::Cow,
  12. collections::HashMap,
  13. hash::{Hash, Hasher},
  14. path::PathBuf,
  15. sync::Arc,
  16. };
  17. type UriSchemeProtocol = dyn Fn(http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
  18. + Send
  19. + Sync
  20. + 'static;
  21. type WebResourceRequestHandler =
  22. dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
  23. type NavigationHandler = dyn Fn(&Url) -> bool + Send;
  24. type OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send;
  25. type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync;
  26. /// Download event.
  27. pub enum DownloadEvent<'a> {
  28. /// Download requested.
  29. Requested {
  30. /// The url being downloaded.
  31. url: Url,
  32. /// Represents where the file will be downloaded to.
  33. /// Can be used to set the download location by assigning a new path to it.
  34. /// The assigned path _must_ be absolute.
  35. destination: &'a mut PathBuf,
  36. },
  37. /// Download finished.
  38. Finished {
  39. /// The URL of the original download request.
  40. url: Url,
  41. /// Potentially representing the filesystem path the file was downloaded to.
  42. path: Option<PathBuf>,
  43. /// Indicates if the download succeeded or not.
  44. success: bool,
  45. },
  46. }
  47. #[cfg(target_os = "android")]
  48. pub struct CreationContext<'a, 'b> {
  49. pub env: &'a mut jni::JNIEnv<'b>,
  50. pub activity: &'a jni::objects::JObject<'b>,
  51. pub webview: &'a jni::objects::JObject<'b>,
  52. }
  53. /// Kind of event for the page load handler.
  54. #[derive(Debug, Clone, Copy, PartialEq, Eq)]
  55. pub enum PageLoadEvent {
  56. /// Page started to load.
  57. Started,
  58. /// Page finished loading.
  59. Finished,
  60. }
  61. /// A webview that has yet to be built.
  62. pub struct PendingWebview<T: UserEvent, R: Runtime<T>> {
  63. /// The label that the webview will be named.
  64. pub label: String,
  65. /// The [`WebviewAttributes`] that the webview will be created with.
  66. pub webview_attributes: WebviewAttributes,
  67. pub uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocol>>,
  68. /// How to handle IPC calls on the webview.
  69. pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
  70. /// A handler to decide if incoming url is allowed to navigate.
  71. pub navigation_handler: Option<Box<NavigationHandler>>,
  72. /// The resolved URL to load on the webview.
  73. pub url: String,
  74. #[cfg(target_os = "android")]
  75. #[allow(clippy::type_complexity)]
  76. pub on_webview_created:
  77. Option<Box<dyn Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send>>,
  78. pub web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
  79. pub on_page_load_handler: Option<Box<OnPageLoadHandler>>,
  80. pub download_handler: Option<Arc<DownloadHandler>>,
  81. }
  82. impl<T: UserEvent, R: Runtime<T>> PendingWebview<T, R> {
  83. /// Create a new [`PendingWebview`] with a label from the given [`WebviewAttributes`].
  84. pub fn new(
  85. webview_attributes: WebviewAttributes,
  86. label: impl Into<String>,
  87. ) -> crate::Result<Self> {
  88. let label = label.into();
  89. if !is_label_valid(&label) {
  90. Err(crate::Error::InvalidWindowLabel)
  91. } else {
  92. Ok(Self {
  93. webview_attributes,
  94. uri_scheme_protocols: Default::default(),
  95. label,
  96. ipc_handler: None,
  97. navigation_handler: None,
  98. url: "tauri://localhost".to_string(),
  99. #[cfg(target_os = "android")]
  100. on_webview_created: None,
  101. web_resource_request_handler: None,
  102. on_page_load_handler: None,
  103. download_handler: None,
  104. })
  105. }
  106. }
  107. pub fn register_uri_scheme_protocol<
  108. N: Into<String>,
  109. H: Fn(http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
  110. + Send
  111. + Sync
  112. + 'static,
  113. >(
  114. &mut self,
  115. uri_scheme: N,
  116. protocol: H,
  117. ) {
  118. let uri_scheme = uri_scheme.into();
  119. self
  120. .uri_scheme_protocols
  121. .insert(uri_scheme, Box::new(protocol));
  122. }
  123. #[cfg(target_os = "android")]
  124. pub fn on_webview_created<
  125. F: Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + 'static,
  126. >(
  127. mut self,
  128. f: F,
  129. ) -> Self {
  130. self.on_webview_created.replace(Box::new(f));
  131. self
  132. }
  133. }
  134. /// A webview that is not yet managed by Tauri.
  135. #[derive(Debug)]
  136. pub struct DetachedWebview<T: UserEvent, R: Runtime<T>> {
  137. /// Name of the window
  138. pub label: String,
  139. /// The [`crate::WebviewDispatch`] associated with the window.
  140. pub dispatcher: R::WebviewDispatcher,
  141. }
  142. impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWebview<T, R> {
  143. fn clone(&self) -> Self {
  144. Self {
  145. label: self.label.clone(),
  146. dispatcher: self.dispatcher.clone(),
  147. }
  148. }
  149. }
  150. impl<T: UserEvent, R: Runtime<T>> Hash for DetachedWebview<T, R> {
  151. /// Only use the [`DetachedWebview`]'s label to represent its hash.
  152. fn hash<H: Hasher>(&self, state: &mut H) {
  153. self.label.hash(state)
  154. }
  155. }
  156. impl<T: UserEvent, R: Runtime<T>> Eq for DetachedWebview<T, R> {}
  157. impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWebview<T, R> {
  158. /// Only use the [`DetachedWebview`]'s label to compare equality.
  159. fn eq(&self, other: &Self) -> bool {
  160. self.label.eq(&other.label)
  161. }
  162. }
  163. /// The attributes used to create an webview.
  164. #[derive(Debug, Clone)]
  165. pub struct WebviewAttributes {
  166. pub url: WebviewUrl,
  167. pub user_agent: Option<String>,
  168. pub initialization_scripts: Vec<String>,
  169. pub data_directory: Option<PathBuf>,
  170. pub drag_drop_handler_enabled: bool,
  171. pub clipboard: bool,
  172. pub accept_first_mouse: bool,
  173. pub additional_browser_args: Option<String>,
  174. pub window_effects: Option<WindowEffectsConfig>,
  175. pub incognito: bool,
  176. pub transparent: bool,
  177. pub bounds: Option<Rect>,
  178. pub auto_resize: bool,
  179. pub proxy_url: Option<Url>,
  180. pub zoom_hotkeys_enabled: bool,
  181. }
  182. impl From<&WindowConfig> for WebviewAttributes {
  183. fn from(config: &WindowConfig) -> Self {
  184. let mut builder = Self::new(config.url.clone());
  185. builder = builder.incognito(config.incognito);
  186. #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
  187. {
  188. builder = builder.transparent(config.transparent);
  189. }
  190. builder = builder.accept_first_mouse(config.accept_first_mouse);
  191. if !config.drag_drop_enabled {
  192. builder = builder.disable_drag_drop_handler();
  193. }
  194. if let Some(user_agent) = &config.user_agent {
  195. builder = builder.user_agent(user_agent);
  196. }
  197. if let Some(additional_browser_args) = &config.additional_browser_args {
  198. builder = builder.additional_browser_args(additional_browser_args);
  199. }
  200. if let Some(effects) = &config.window_effects {
  201. builder = builder.window_effects(effects.clone());
  202. }
  203. if let Some(url) = &config.proxy_url {
  204. builder = builder.proxy_url(url.to_owned());
  205. }
  206. builder = builder.zoom_hotkeys_enabled(config.zoom_hotkeys_enabled);
  207. builder
  208. }
  209. }
  210. impl WebviewAttributes {
  211. /// Initializes the default attributes for a webview.
  212. pub fn new(url: WebviewUrl) -> Self {
  213. Self {
  214. url,
  215. user_agent: None,
  216. initialization_scripts: Vec::new(),
  217. data_directory: None,
  218. drag_drop_handler_enabled: true,
  219. clipboard: false,
  220. accept_first_mouse: false,
  221. additional_browser_args: None,
  222. window_effects: None,
  223. incognito: false,
  224. transparent: false,
  225. bounds: None,
  226. auto_resize: false,
  227. proxy_url: None,
  228. zoom_hotkeys_enabled: false,
  229. }
  230. }
  231. /// Sets the user agent
  232. #[must_use]
  233. pub fn user_agent(mut self, user_agent: &str) -> Self {
  234. self.user_agent = Some(user_agent.to_string());
  235. self
  236. }
  237. /// Sets the init script.
  238. #[must_use]
  239. pub fn initialization_script(mut self, script: &str) -> Self {
  240. self.initialization_scripts.push(script.to_string());
  241. self
  242. }
  243. /// Data directory for the webview.
  244. #[must_use]
  245. pub fn data_directory(mut self, data_directory: PathBuf) -> Self {
  246. self.data_directory.replace(data_directory);
  247. self
  248. }
  249. /// Disables the drag and drop handler. This is required to use HTML5 drag and drop APIs on the frontend on Windows.
  250. #[must_use]
  251. pub fn disable_drag_drop_handler(mut self) -> Self {
  252. self.drag_drop_handler_enabled = false;
  253. self
  254. }
  255. /// Enables clipboard access for the page rendered on **Linux** and **Windows**.
  256. ///
  257. /// **macOS** doesn't provide such method and is always enabled by default,
  258. /// but you still need to add menu item accelerators to use shortcuts.
  259. #[must_use]
  260. pub fn enable_clipboard_access(mut self) -> Self {
  261. self.clipboard = true;
  262. self
  263. }
  264. /// Sets whether clicking an inactive window also clicks through to the webview.
  265. #[must_use]
  266. pub fn accept_first_mouse(mut self, accept: bool) -> Self {
  267. self.accept_first_mouse = accept;
  268. self
  269. }
  270. /// Sets additional browser arguments. **Windows Only**
  271. #[must_use]
  272. pub fn additional_browser_args(mut self, additional_args: &str) -> Self {
  273. self.additional_browser_args = Some(additional_args.to_string());
  274. self
  275. }
  276. /// Sets window effects
  277. #[must_use]
  278. pub fn window_effects(mut self, effects: WindowEffectsConfig) -> Self {
  279. self.window_effects = Some(effects);
  280. self
  281. }
  282. /// Enable or disable incognito mode for the WebView.
  283. #[must_use]
  284. pub fn incognito(mut self, incognito: bool) -> Self {
  285. self.incognito = incognito;
  286. self
  287. }
  288. /// Enable or disable transparency for the WebView.
  289. #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
  290. #[must_use]
  291. pub fn transparent(mut self, transparent: bool) -> Self {
  292. self.transparent = transparent;
  293. self
  294. }
  295. /// Sets the webview to automatically grow and shrink its size and position when the parent window resizes.
  296. #[must_use]
  297. pub fn auto_resize(mut self) -> Self {
  298. self.auto_resize = true;
  299. self
  300. }
  301. /// Enable proxy for the WebView
  302. #[must_use]
  303. pub fn proxy_url(mut self, url: Url) -> Self {
  304. self.proxy_url = Some(url);
  305. self
  306. }
  307. /// Whether page zooming by hotkeys is enabled
  308. ///
  309. /// ## Platform-specific:
  310. ///
  311. /// - **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.
  312. /// - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`,
  313. /// 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission
  314. ///
  315. /// - **Android / iOS**: Unsupported.
  316. #[must_use]
  317. pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {
  318. self.zoom_hotkeys_enabled = enabled;
  319. self
  320. }
  321. }
  322. /// IPC handler.
  323. pub type WebviewIpcHandler<T, R> = Box<dyn Fn(DetachedWebview<T, R>, Request<String>) + Send>;