webview.rs 9.9 KB

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