manager.rs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use crate::{
  5. app::{AppHandle, GlobalWindowEvent, GlobalWindowEventListener},
  6. event::{Event, EventHandler, Listeners},
  7. hooks::{InvokeHandler, InvokeResponder, OnPageLoad, PageLoadPayload},
  8. plugin::PluginStore,
  9. runtime::{
  10. http::{
  11. HttpRange, MimeType, Request as HttpRequest, Response as HttpResponse,
  12. ResponseBuilder as HttpResponseBuilder,
  13. },
  14. webview::{FileDropEvent, FileDropHandler, InvokePayload, WebviewRpcHandler, WindowBuilder},
  15. window::{dpi::PhysicalSize, DetachedWindow, PendingWindow, WindowEvent},
  16. Icon, Runtime,
  17. },
  18. utils::{
  19. assets::Assets,
  20. config::{AppUrl, Config, WindowUrl},
  21. PackageInfo,
  22. },
  23. Context, Invoke, StateManager, Window,
  24. };
  25. #[cfg(any(target_os = "linux", target_os = "windows"))]
  26. use crate::api::path::{resolve_path, BaseDirectory};
  27. use crate::app::{GlobalMenuEventListener, WindowMenuEvent};
  28. use crate::{runtime::menu::Menu, MenuEvent};
  29. use serde::Serialize;
  30. use serde_json::Value as JsonValue;
  31. use std::{
  32. borrow::Cow,
  33. collections::{HashMap, HashSet},
  34. fmt,
  35. fs::create_dir_all,
  36. io::SeekFrom,
  37. sync::{Arc, Mutex, MutexGuard},
  38. };
  39. use tauri_macros::default_runtime;
  40. use tokio::io::{AsyncReadExt, AsyncSeekExt};
  41. use url::Url;
  42. const WINDOW_RESIZED_EVENT: &str = "tauri://resize";
  43. const WINDOW_MOVED_EVENT: &str = "tauri://move";
  44. const WINDOW_CLOSE_REQUESTED_EVENT: &str = "tauri://close-requested";
  45. const WINDOW_DESTROYED_EVENT: &str = "tauri://destroyed";
  46. const WINDOW_FOCUS_EVENT: &str = "tauri://focus";
  47. const WINDOW_BLUR_EVENT: &str = "tauri://blur";
  48. const WINDOW_SCALE_FACTOR_CHANGED_EVENT: &str = "tauri://scale-change";
  49. const MENU_EVENT: &str = "tauri://menu";
  50. #[default_runtime(crate::Wry, wry)]
  51. pub struct InnerWindowManager<R: Runtime> {
  52. windows: Mutex<HashMap<String, Window<R>>>,
  53. pub(crate) plugins: Mutex<PluginStore<R>>,
  54. listeners: Listeners,
  55. pub(crate) state: Arc<StateManager>,
  56. /// The JS message handler.
  57. invoke_handler: Box<InvokeHandler<R>>,
  58. /// The page load hook, invoked when the webview performs a navigation.
  59. on_page_load: Box<OnPageLoad<R>>,
  60. config: Arc<Config>,
  61. assets: Arc<dyn Assets>,
  62. default_window_icon: Option<Vec<u8>>,
  63. package_info: PackageInfo,
  64. /// The webview protocols protocols available to all windows.
  65. uri_scheme_protocols: HashMap<String, Arc<CustomProtocol<R>>>,
  66. /// The menu set to all windows.
  67. menu: Option<Menu>,
  68. /// Menu event listeners to all windows.
  69. menu_event_listeners: Arc<Vec<GlobalMenuEventListener<R>>>,
  70. /// Window event listeners to all windows.
  71. window_event_listeners: Arc<Vec<GlobalWindowEventListener<R>>>,
  72. /// Responder for invoke calls.
  73. invoke_responder: Arc<InvokeResponder<R>>,
  74. /// The script that initializes the invoke system.
  75. invoke_initialization_script: String,
  76. }
  77. impl<R: Runtime> fmt::Debug for InnerWindowManager<R> {
  78. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  79. f.debug_struct("InnerWindowManager")
  80. .field("plugins", &self.plugins)
  81. .field("state", &self.state)
  82. .field("config", &self.config)
  83. .field("default_window_icon", &self.default_window_icon)
  84. .field("package_info", &self.package_info)
  85. .field("menu", &self.menu)
  86. .finish()
  87. }
  88. }
  89. /// A resolved asset.
  90. pub struct Asset {
  91. /// The asset bytes.
  92. pub bytes: Vec<u8>,
  93. /// The asset's mime type.
  94. pub mime_type: String,
  95. }
  96. /// Uses a custom URI scheme handler to resolve file requests
  97. pub struct CustomProtocol<R: Runtime> {
  98. /// Handler for protocol
  99. #[allow(clippy::type_complexity)]
  100. pub protocol: Box<
  101. dyn Fn(&AppHandle<R>, &HttpRequest) -> Result<HttpResponse, Box<dyn std::error::Error>>
  102. + Send
  103. + Sync,
  104. >,
  105. }
  106. #[default_runtime(crate::Wry, wry)]
  107. #[derive(Debug)]
  108. pub struct WindowManager<R: Runtime> {
  109. pub inner: Arc<InnerWindowManager<R>>,
  110. invoke_keys: Arc<Mutex<Vec<u32>>>,
  111. }
  112. impl<R: Runtime> Clone for WindowManager<R> {
  113. fn clone(&self) -> Self {
  114. Self {
  115. inner: self.inner.clone(),
  116. invoke_keys: self.invoke_keys.clone(),
  117. }
  118. }
  119. }
  120. impl<R: Runtime> WindowManager<R> {
  121. #[allow(clippy::too_many_arguments)]
  122. pub(crate) fn with_handlers(
  123. context: Context<impl Assets>,
  124. plugins: PluginStore<R>,
  125. invoke_handler: Box<InvokeHandler<R>>,
  126. on_page_load: Box<OnPageLoad<R>>,
  127. uri_scheme_protocols: HashMap<String, Arc<CustomProtocol<R>>>,
  128. state: StateManager,
  129. window_event_listeners: Vec<GlobalWindowEventListener<R>>,
  130. (menu, menu_event_listeners): (Option<Menu>, Vec<GlobalMenuEventListener<R>>),
  131. (invoke_responder, invoke_initialization_script): (Arc<InvokeResponder<R>>, String),
  132. ) -> Self {
  133. Self {
  134. inner: Arc::new(InnerWindowManager {
  135. windows: Mutex::default(),
  136. plugins: Mutex::new(plugins),
  137. listeners: Listeners::default(),
  138. state: Arc::new(state),
  139. invoke_handler,
  140. on_page_load,
  141. config: Arc::new(context.config),
  142. assets: context.assets,
  143. default_window_icon: context.default_window_icon,
  144. package_info: context.package_info,
  145. uri_scheme_protocols,
  146. menu,
  147. menu_event_listeners: Arc::new(menu_event_listeners),
  148. window_event_listeners: Arc::new(window_event_listeners),
  149. invoke_responder,
  150. invoke_initialization_script,
  151. }),
  152. invoke_keys: Default::default(),
  153. }
  154. }
  155. /// Get a locked handle to the windows.
  156. pub(crate) fn windows_lock(&self) -> MutexGuard<'_, HashMap<String, Window<R>>> {
  157. self.inner.windows.lock().expect("poisoned window manager")
  158. }
  159. /// State managed by the application.
  160. pub(crate) fn state(&self) -> Arc<StateManager> {
  161. self.inner.state.clone()
  162. }
  163. /// The invoke responder.
  164. pub(crate) fn invoke_responder(&self) -> Arc<InvokeResponder<R>> {
  165. self.inner.invoke_responder.clone()
  166. }
  167. /// Get the base path to serve data from.
  168. ///
  169. /// * In dev mode, this will be based on the `devPath` configuration value.
  170. /// * Otherwise, this will be based on the `distDir` configuration value.
  171. #[cfg(custom_protocol)]
  172. fn base_path(&self) -> &AppUrl {
  173. &self.inner.config.build.dist_dir
  174. }
  175. #[cfg(dev)]
  176. fn base_path(&self) -> &AppUrl {
  177. &self.inner.config.build.dev_path
  178. }
  179. /// Get the base URL to use for webview requests.
  180. ///
  181. /// In dev mode, this will be based on the `devPath` configuration value.
  182. fn get_url(&self) -> Cow<'_, Url> {
  183. match self.base_path() {
  184. AppUrl::Url(WindowUrl::External(url)) => Cow::Borrowed(url),
  185. _ => Cow::Owned(Url::parse("tauri://localhost").unwrap()),
  186. }
  187. }
  188. fn generate_invoke_key(&self) -> u32 {
  189. let key = rand::random();
  190. self.invoke_keys.lock().unwrap().push(key);
  191. key
  192. }
  193. /// Checks whether the invoke key is valid or not.
  194. ///
  195. /// An invoke key is valid if it was generated by this manager instance.
  196. pub(crate) fn verify_invoke_key(&self, key: u32) -> bool {
  197. self.invoke_keys.lock().unwrap().contains(&key)
  198. }
  199. fn prepare_pending_window(
  200. &self,
  201. mut pending: PendingWindow<R>,
  202. label: &str,
  203. pending_labels: &[String],
  204. app_handle: AppHandle<R>,
  205. ) -> crate::Result<PendingWindow<R>> {
  206. let is_init_global = self.inner.config.build.with_global_tauri;
  207. let plugin_init = self
  208. .inner
  209. .plugins
  210. .lock()
  211. .expect("poisoned plugin store")
  212. .initialization_script();
  213. let mut webview_attributes = pending.webview_attributes;
  214. webview_attributes =
  215. webview_attributes.initialization_script(&self.inner.invoke_initialization_script);
  216. if is_init_global {
  217. webview_attributes = webview_attributes.initialization_script(&format!(
  218. "(function () {{
  219. const __TAURI_INVOKE_KEY__ = {key};
  220. {bundle_script}
  221. }})()",
  222. key = self.generate_invoke_key(),
  223. bundle_script = include_str!("../scripts/bundle.js"),
  224. ));
  225. }
  226. webview_attributes = webview_attributes
  227. .initialization_script(&format!(
  228. r#"
  229. if (!window.__TAURI__) {{
  230. window.__TAURI__ = {{}}
  231. }}
  232. window.__TAURI__.__windows = {window_labels_array}.map(function (label) {{ return {{ label: label }} }});
  233. window.__TAURI__.__currentWindow = {{ label: {current_window_label} }}
  234. "#,
  235. window_labels_array = serde_json::to_string(pending_labels)?,
  236. current_window_label = serde_json::to_string(&label)?,
  237. ))
  238. .initialization_script(&self.initialization_script(&plugin_init));
  239. #[cfg(dev)]
  240. {
  241. webview_attributes = webview_attributes.initialization_script(&format!(
  242. "window.__TAURI_INVOKE_KEY__ = {}",
  243. self.generate_invoke_key()
  244. ));
  245. }
  246. pending.webview_attributes = webview_attributes;
  247. if !pending.window_builder.has_icon() {
  248. if let Some(default_window_icon) = &self.inner.default_window_icon {
  249. let icon = Icon::Raw(default_window_icon.clone());
  250. pending.window_builder = pending.window_builder.icon(icon)?;
  251. }
  252. }
  253. if pending.window_builder.get_menu().is_none() {
  254. if let Some(menu) = &self.inner.menu {
  255. pending = pending.set_menu(menu.clone());
  256. }
  257. }
  258. let mut registered_scheme_protocols = Vec::new();
  259. for (uri_scheme, protocol) in &self.inner.uri_scheme_protocols {
  260. registered_scheme_protocols.push(uri_scheme.clone());
  261. let protocol = protocol.clone();
  262. let app_handle = Mutex::new(app_handle.clone());
  263. pending.register_uri_scheme_protocol(uri_scheme.clone(), move |p| {
  264. (protocol.protocol)(&app_handle.lock().unwrap(), p)
  265. });
  266. }
  267. if !registered_scheme_protocols.contains(&"tauri".into()) {
  268. pending.register_uri_scheme_protocol("tauri", self.prepare_uri_scheme_protocol());
  269. registered_scheme_protocols.push("tauri".into());
  270. }
  271. if !registered_scheme_protocols.contains(&"asset".into()) {
  272. let window_url = Url::parse(&pending.url).unwrap();
  273. let window_origin =
  274. if cfg!(windows) && window_url.scheme() != "http" && window_url.scheme() != "https" {
  275. format!("https://{}.localhost", window_url.scheme())
  276. } else {
  277. format!(
  278. "{}://{}{}",
  279. window_url.scheme(),
  280. window_url.host().unwrap(),
  281. if let Some(port) = window_url.port() {
  282. format!(":{}", port)
  283. } else {
  284. "".into()
  285. }
  286. )
  287. };
  288. pending.register_uri_scheme_protocol("asset", move |request| {
  289. #[cfg(target_os = "windows")]
  290. let path = request.uri().replace("asset://localhost/", "");
  291. #[cfg(not(target_os = "windows"))]
  292. let path = request.uri().replace("asset://", "");
  293. let path = percent_encoding::percent_decode(path.as_bytes())
  294. .decode_utf8_lossy()
  295. .to_string();
  296. let path_for_data = path.clone();
  297. let mut response =
  298. HttpResponseBuilder::new().header("Access-Control-Allow-Origin", &window_origin);
  299. // handle 206 (partial range) http request
  300. if let Some(range) = request.headers().get("range").cloned() {
  301. let mut status_code = 200;
  302. let path_for_data = path_for_data.clone();
  303. let (headers, status_code, data) = crate::async_runtime::safe_block_on(async move {
  304. let mut headers = HashMap::new();
  305. let mut buf = Vec::new();
  306. let mut file = tokio::fs::File::open(path_for_data.clone()).await.unwrap();
  307. // Get the file size
  308. let file_size = file.metadata().await.unwrap().len();
  309. // parse the range
  310. let range = HttpRange::parse(range.to_str().unwrap(), file_size).unwrap();
  311. // FIXME: Support multiple ranges
  312. // let support only 1 range for now
  313. let first_range = range.first();
  314. if let Some(range) = first_range {
  315. let mut real_length = range.length;
  316. // prevent max_length;
  317. // specially on webview2
  318. if range.length > file_size / 3 {
  319. // max size sent (400ko / request)
  320. // as it's local file system we can afford to read more often
  321. real_length = std::cmp::min(file_size - range.start, 1024 * 400);
  322. }
  323. // last byte we are reading, the length of the range include the last byte
  324. // who should be skipped on the header
  325. let last_byte = range.start + real_length - 1;
  326. // partial content
  327. status_code = 206;
  328. headers.insert("Connection", "Keep-Alive".into());
  329. headers.insert("Accept-Ranges", "bytes".into());
  330. headers.insert("Content-Length", real_length.to_string());
  331. headers.insert(
  332. "Content-Range",
  333. format!("bytes {}-{}/{}", range.start, last_byte, file_size),
  334. );
  335. file.seek(SeekFrom::Start(range.start)).await.unwrap();
  336. file.take(real_length).read_to_end(&mut buf).await.unwrap();
  337. }
  338. (headers, status_code, buf)
  339. });
  340. for (k, v) in headers {
  341. response = response.header(k, v);
  342. }
  343. if !data.is_empty() {
  344. let mime_type = MimeType::parse(&data, &path);
  345. return response.mimetype(&mime_type).status(status_code).body(data);
  346. }
  347. }
  348. if let Ok(data) =
  349. crate::async_runtime::safe_block_on(async move { tokio::fs::read(path_for_data).await })
  350. {
  351. let mime_type = MimeType::parse(&data, &path);
  352. response.mimetype(&mime_type).body(data)
  353. } else {
  354. response.status(404).body(Vec::new())
  355. }
  356. });
  357. }
  358. Ok(pending)
  359. }
  360. fn prepare_rpc_handler(&self, app_handle: AppHandle<R>) -> WebviewRpcHandler<R> {
  361. let manager = self.clone();
  362. Box::new(move |window, request| {
  363. let window = Window::new(manager.clone(), window, app_handle.clone());
  364. let command = request.command.clone();
  365. let arg = request
  366. .params
  367. .unwrap()
  368. .as_array_mut()
  369. .unwrap()
  370. .first_mut()
  371. .unwrap_or(&mut JsonValue::Null)
  372. .take();
  373. match serde_json::from_value::<InvokePayload>(arg) {
  374. Ok(message) => {
  375. let _ = window.on_message(command, message);
  376. }
  377. Err(e) => {
  378. let error: crate::Error = e.into();
  379. let _ = window.eval(&format!(
  380. r#"console.error({})"#,
  381. JsonValue::String(error.to_string())
  382. ));
  383. }
  384. }
  385. })
  386. }
  387. pub fn get_asset(&self, mut path: String) -> Result<Asset, Box<dyn std::error::Error>> {
  388. let assets = &self.inner.assets;
  389. if path.ends_with('/') {
  390. path.pop();
  391. }
  392. path = percent_encoding::percent_decode(path.as_bytes())
  393. .decode_utf8_lossy()
  394. .to_string();
  395. let path = if path.is_empty() {
  396. // if the url is `tauri://localhost`, we should load `index.html`
  397. "index.html".to_string()
  398. } else {
  399. // skip leading `/`
  400. path.chars().skip(1).collect::<String>()
  401. };
  402. let is_javascript = path.ends_with(".js") || path.ends_with(".cjs") || path.ends_with(".mjs");
  403. let is_html = path.ends_with(".html");
  404. let asset_response = assets
  405. .get(&path.as_str().into())
  406. .or_else(|| assets.get(&format!("{}/index.html", path.as_str()).into()))
  407. .or_else(|| {
  408. #[cfg(debug_assertions)]
  409. eprintln!("Asset `{}` not found; fallback to index.html", path); // TODO log::error!
  410. assets.get(&"index.html".into())
  411. })
  412. .ok_or_else(|| crate::Error::AssetNotFound(path.clone()))
  413. .map(Cow::into_owned);
  414. match asset_response {
  415. Ok(asset) => {
  416. let final_data = match is_javascript || is_html {
  417. true => String::from_utf8_lossy(&asset)
  418. .into_owned()
  419. .replacen(
  420. "__TAURI__INVOKE_KEY_TOKEN__",
  421. &self.generate_invoke_key().to_string(),
  422. 1,
  423. )
  424. .as_bytes()
  425. .to_vec(),
  426. false => asset,
  427. };
  428. let mime_type = MimeType::parse(&final_data, &path);
  429. Ok(Asset {
  430. bytes: final_data,
  431. mime_type,
  432. })
  433. }
  434. Err(e) => {
  435. #[cfg(debug_assertions)]
  436. eprintln!("{:?}", e); // TODO log::error!
  437. Err(Box::new(e))
  438. }
  439. }
  440. }
  441. #[allow(clippy::type_complexity)]
  442. fn prepare_uri_scheme_protocol(
  443. &self,
  444. ) -> Box<dyn Fn(&HttpRequest) -> Result<HttpResponse, Box<dyn std::error::Error>> + Send + Sync>
  445. {
  446. let manager = self.clone();
  447. Box::new(move |request| {
  448. let path = request
  449. .uri()
  450. .split(&['?', '#'][..])
  451. // ignore query string
  452. .next()
  453. .unwrap()
  454. .to_string()
  455. .replace("tauri://localhost", "");
  456. let asset = manager.get_asset(path)?;
  457. HttpResponseBuilder::new()
  458. .mimetype(&asset.mime_type)
  459. .body(asset.bytes)
  460. })
  461. }
  462. fn prepare_file_drop(&self, app_handle: AppHandle<R>) -> FileDropHandler<R> {
  463. let manager = self.clone();
  464. Box::new(move |event, window| {
  465. let window = Window::new(manager.clone(), window, app_handle.clone());
  466. let _ = match event {
  467. FileDropEvent::Hovered(paths) => window.emit_and_trigger("tauri://file-drop-hover", paths),
  468. FileDropEvent::Dropped(paths) => window.emit_and_trigger("tauri://file-drop", paths),
  469. FileDropEvent::Cancelled => window.emit_and_trigger("tauri://file-drop-cancelled", ()),
  470. _ => unimplemented!(),
  471. };
  472. true
  473. })
  474. }
  475. fn initialization_script(&self, plugin_initialization_script: &str) -> String {
  476. let key = self.generate_invoke_key();
  477. format!(
  478. r#"
  479. {core_script}
  480. {event_initialization_script}
  481. if (document.readyState === 'complete') {{
  482. window.__TAURI_INVOKE__("__initialized", {{ url: window.location.href }}, {key})
  483. }} else {{
  484. window.addEventListener('DOMContentLoaded', function () {{
  485. window.__TAURI_INVOKE__("__initialized", {{ url: window.location.href }}, {key})
  486. }})
  487. }}
  488. {plugin_initialization_script}
  489. "#,
  490. key = key,
  491. core_script = include_str!("../scripts/core.js").replace("_KEY_VALUE_", &key.to_string()),
  492. event_initialization_script = self.event_initialization_script(),
  493. plugin_initialization_script = plugin_initialization_script
  494. )
  495. }
  496. fn event_initialization_script(&self) -> String {
  497. return format!(
  498. "
  499. window['{function}'] = function (eventData) {{
  500. const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || []
  501. for (let i = listeners.length - 1; i >= 0; i--) {{
  502. const listener = listeners[i]
  503. eventData.id = listener.id
  504. listener.handler(eventData)
  505. }}
  506. }}
  507. ",
  508. function = self.inner.listeners.function_name(),
  509. listeners = self.inner.listeners.listeners_object_name()
  510. );
  511. }
  512. }
  513. #[cfg(test)]
  514. mod test {
  515. use super::WindowManager;
  516. use crate::{generate_context, plugin::PluginStore, StateManager, Wry};
  517. #[test]
  518. fn check_get_url() {
  519. let context = generate_context!("test/fixture/src-tauri/tauri.conf.json", crate);
  520. let manager: WindowManager<Wry> = WindowManager::with_handlers(
  521. context,
  522. PluginStore::default(),
  523. Box::new(|_| ()),
  524. Box::new(|_, _| ()),
  525. Default::default(),
  526. StateManager::new(),
  527. Default::default(),
  528. Default::default(),
  529. (std::sync::Arc::new(|_, _, _, _| ()), "".into()),
  530. );
  531. #[cfg(custom_protocol)]
  532. assert_eq!(manager.get_url().to_string(), "tauri://localhost");
  533. #[cfg(dev)]
  534. assert_eq!(manager.get_url().to_string(), "http://localhost:4000/");
  535. }
  536. }
  537. impl<R: Runtime> WindowManager<R> {
  538. pub fn run_invoke_handler(&self, invoke: Invoke<R>) {
  539. (self.inner.invoke_handler)(invoke);
  540. }
  541. pub fn run_on_page_load(&self, window: Window<R>, payload: PageLoadPayload) {
  542. (self.inner.on_page_load)(window.clone(), payload.clone());
  543. self
  544. .inner
  545. .plugins
  546. .lock()
  547. .expect("poisoned plugin store")
  548. .on_page_load(window, payload);
  549. }
  550. pub fn extend_api(&self, invoke: Invoke<R>) {
  551. self
  552. .inner
  553. .plugins
  554. .lock()
  555. .expect("poisoned plugin store")
  556. .extend_api(invoke);
  557. }
  558. pub fn initialize_plugins(&self, app: &AppHandle<R>) -> crate::Result<()> {
  559. self
  560. .inner
  561. .plugins
  562. .lock()
  563. .expect("poisoned plugin store")
  564. .initialize(app, &self.inner.config.plugins)
  565. }
  566. pub fn prepare_window(
  567. &self,
  568. app_handle: AppHandle<R>,
  569. mut pending: PendingWindow<R>,
  570. pending_labels: &[String],
  571. ) -> crate::Result<PendingWindow<R>> {
  572. if self.windows_lock().contains_key(&pending.label) {
  573. return Err(crate::Error::WindowLabelAlreadyExists(pending.label));
  574. }
  575. let (is_local, url) = match &pending.webview_attributes.url {
  576. WindowUrl::App(path) => {
  577. let url = self.get_url();
  578. (
  579. true,
  580. // ignore "index.html" just to simplify the url
  581. if path.to_str() != Some("index.html") {
  582. url
  583. .join(&*path.to_string_lossy())
  584. .map_err(crate::Error::InvalidUrl)?
  585. .to_string()
  586. } else {
  587. url.to_string()
  588. },
  589. )
  590. }
  591. WindowUrl::External(url) => (url.scheme() == "tauri", url.to_string()),
  592. _ => unimplemented!(),
  593. };
  594. pending.url = url;
  595. if is_local {
  596. let label = pending.label.clone();
  597. pending = self.prepare_pending_window(pending, &label, pending_labels, app_handle.clone())?;
  598. pending.rpc_handler = Some(self.prepare_rpc_handler(app_handle.clone()));
  599. }
  600. if pending.webview_attributes.file_drop_handler_enabled {
  601. pending.file_drop_handler = Some(self.prepare_file_drop(app_handle));
  602. }
  603. // in `Windows`, we need to force a data_directory
  604. // but we do respect user-specification
  605. #[cfg(any(target_os = "linux", target_os = "windows"))]
  606. if pending.webview_attributes.data_directory.is_none() {
  607. let local_app_data = resolve_path(
  608. &self.inner.config,
  609. &self.inner.package_info,
  610. &self.inner.config.tauri.bundle.identifier,
  611. Some(BaseDirectory::LocalData),
  612. );
  613. if let Ok(user_data_dir) = local_app_data {
  614. pending.webview_attributes.data_directory = Some(user_data_dir);
  615. }
  616. }
  617. // make sure the directory is created and available to prevent a panic
  618. if let Some(user_data_dir) = &pending.webview_attributes.data_directory {
  619. if !user_data_dir.exists() {
  620. create_dir_all(user_data_dir)?;
  621. }
  622. }
  623. Ok(pending)
  624. }
  625. pub fn attach_window(&self, app_handle: AppHandle<R>, window: DetachedWindow<R>) -> Window<R> {
  626. let window = Window::new(self.clone(), window, app_handle);
  627. let window_ = window.clone();
  628. let window_event_listeners = self.inner.window_event_listeners.clone();
  629. let manager = self.clone();
  630. window.on_window_event(move |event| {
  631. let _ = on_window_event(&window_, &manager, event);
  632. for handler in window_event_listeners.iter() {
  633. handler(GlobalWindowEvent {
  634. window: window_.clone(),
  635. event: event.clone(),
  636. });
  637. }
  638. });
  639. {
  640. let window_ = window.clone();
  641. let menu_event_listeners = self.inner.menu_event_listeners.clone();
  642. window.on_menu_event(move |event| {
  643. let _ = on_menu_event(&window_, &event);
  644. for handler in menu_event_listeners.iter() {
  645. handler(WindowMenuEvent {
  646. window: window_.clone(),
  647. menu_item_id: event.menu_item_id.clone(),
  648. });
  649. }
  650. });
  651. }
  652. // insert the window into our manager
  653. {
  654. self
  655. .windows_lock()
  656. .insert(window.label().to_string(), window.clone());
  657. }
  658. // let plugins know that a new window has been added to the manager
  659. {
  660. self
  661. .inner
  662. .plugins
  663. .lock()
  664. .expect("poisoned plugin store")
  665. .created(window.clone());
  666. }
  667. window
  668. }
  669. pub(crate) fn on_window_close(&self, label: &str) {
  670. self.windows_lock().remove(label);
  671. }
  672. pub fn emit_filter<S, F>(&self, event: &str, payload: S, filter: F) -> crate::Result<()>
  673. where
  674. S: Serialize + Clone,
  675. F: Fn(&Window<R>) -> bool,
  676. {
  677. self
  678. .windows_lock()
  679. .values()
  680. .filter(|&w| filter(w))
  681. .try_for_each(|window| window.emit(event, payload.clone()))
  682. }
  683. pub fn labels(&self) -> HashSet<String> {
  684. self.windows_lock().keys().cloned().collect()
  685. }
  686. pub fn config(&self) -> Arc<Config> {
  687. self.inner.config.clone()
  688. }
  689. pub fn package_info(&self) -> &PackageInfo {
  690. &self.inner.package_info
  691. }
  692. pub fn unlisten(&self, handler_id: EventHandler) {
  693. self.inner.listeners.unlisten(handler_id)
  694. }
  695. pub fn trigger(&self, event: &str, window: Option<String>, data: Option<String>) {
  696. self.inner.listeners.trigger(event, window, data)
  697. }
  698. pub fn listen<F: Fn(Event) + Send + 'static>(
  699. &self,
  700. event: String,
  701. window: Option<String>,
  702. handler: F,
  703. ) -> EventHandler {
  704. self.inner.listeners.listen(event, window, handler)
  705. }
  706. pub fn once<F: Fn(Event) + Send + 'static>(
  707. &self,
  708. event: String,
  709. window: Option<String>,
  710. handler: F,
  711. ) -> EventHandler {
  712. self.inner.listeners.once(event, window, handler)
  713. }
  714. pub fn event_listeners_object_name(&self) -> String {
  715. self.inner.listeners.listeners_object_name()
  716. }
  717. pub fn event_emit_function_name(&self) -> String {
  718. self.inner.listeners.function_name()
  719. }
  720. pub fn get_window(&self, label: &str) -> Option<Window<R>> {
  721. self.windows_lock().get(label).cloned()
  722. }
  723. pub fn windows(&self) -> HashMap<String, Window<R>> {
  724. self.windows_lock().clone()
  725. }
  726. }
  727. fn on_window_event<R: Runtime>(
  728. window: &Window<R>,
  729. manager: &WindowManager<R>,
  730. event: &WindowEvent,
  731. ) -> crate::Result<()> {
  732. match event {
  733. WindowEvent::Resized(size) => window.emit_and_trigger(WINDOW_RESIZED_EVENT, size)?,
  734. WindowEvent::Moved(position) => window.emit_and_trigger(WINDOW_MOVED_EVENT, position)?,
  735. WindowEvent::CloseRequested {
  736. label: _,
  737. signal_tx,
  738. } => {
  739. if window.has_js_listener(WINDOW_CLOSE_REQUESTED_EVENT) {
  740. signal_tx.send(true).unwrap();
  741. }
  742. window.emit_and_trigger(WINDOW_CLOSE_REQUESTED_EVENT, ())?;
  743. }
  744. WindowEvent::Destroyed => {
  745. window.emit_and_trigger(WINDOW_DESTROYED_EVENT, ())?;
  746. let label = window.label();
  747. for window in manager.inner.windows.lock().unwrap().values() {
  748. window.eval(&format!(
  749. r#"window.__TAURI__.__windows = window.__TAURI__.__windows.filter(w => w.label !== "{}");"#,
  750. label
  751. ))?;
  752. }
  753. }
  754. WindowEvent::Focused(focused) => window.emit_and_trigger(
  755. if *focused {
  756. WINDOW_FOCUS_EVENT
  757. } else {
  758. WINDOW_BLUR_EVENT
  759. },
  760. (),
  761. )?,
  762. WindowEvent::ScaleFactorChanged {
  763. scale_factor,
  764. new_inner_size,
  765. ..
  766. } => window.emit_and_trigger(
  767. WINDOW_SCALE_FACTOR_CHANGED_EVENT,
  768. ScaleFactorChanged {
  769. scale_factor: *scale_factor,
  770. size: *new_inner_size,
  771. },
  772. )?,
  773. _ => unimplemented!(),
  774. }
  775. Ok(())
  776. }
  777. #[derive(Serialize)]
  778. #[serde(rename_all = "camelCase")]
  779. struct ScaleFactorChanged {
  780. scale_factor: f64,
  781. size: PhysicalSize<u32>,
  782. }
  783. fn on_menu_event<R: Runtime>(window: &Window<R>, event: &MenuEvent) -> crate::Result<()> {
  784. window.emit_and_trigger(MENU_EVENT, event.menu_item_id.clone())
  785. }