plugin.rs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. //! The Tauri plugin extension to expand Tauri functionality.
  5. use crate::{
  6. app::UriSchemeResponder,
  7. ipc::{Invoke, InvokeHandler, ScopeObject, ScopeValue},
  8. manager::webview::UriSchemeProtocol,
  9. utils::config::PluginConfig,
  10. webview::PageLoadPayload,
  11. AppHandle, Error, RunEvent, Runtime, Webview, Window,
  12. };
  13. use serde::de::DeserializeOwned;
  14. use serde_json::Value as JsonValue;
  15. use tauri_macros::default_runtime;
  16. use url::Url;
  17. use std::{
  18. borrow::Cow,
  19. collections::HashMap,
  20. fmt::{self, Debug},
  21. sync::Arc,
  22. };
  23. /// Mobile APIs.
  24. #[cfg(mobile)]
  25. pub mod mobile;
  26. /// The plugin interface.
  27. pub trait Plugin<R: Runtime>: Send {
  28. /// The plugin name. Used as key on the plugin config object.
  29. fn name(&self) -> &'static str;
  30. /// Initializes the plugin.
  31. #[allow(unused_variables)]
  32. fn initialize(
  33. &mut self,
  34. app: &AppHandle<R>,
  35. config: JsonValue,
  36. ) -> Result<(), Box<dyn std::error::Error>> {
  37. Ok(())
  38. }
  39. /// Add the provided JavaScript to a list of scripts that should be run after the global object has been created,
  40. /// but before the HTML document has been parsed and before any other script included by the HTML document is run.
  41. ///
  42. /// Since it runs on all top-level document and child frame page navigations,
  43. /// it's recommended to check the `window.location` to guard your script from running on unexpected origins.
  44. ///
  45. /// The script is wrapped into its own context with `(function () { /* your script here */ })();`,
  46. /// so global variables must be assigned to `window` instead of implicitly declared.
  47. fn initialization_script(&self) -> Option<String> {
  48. None
  49. }
  50. /// Callback invoked when the window is created.
  51. #[allow(unused_variables)]
  52. fn window_created(&mut self, window: Window<R>) {}
  53. /// Callback invoked when the webview is created.
  54. #[allow(unused_variables)]
  55. fn webview_created(&mut self, webview: Webview<R>) {}
  56. /// Callback invoked when webview tries to navigate to the given Url. Returning falses cancels navigation.
  57. #[allow(unused_variables)]
  58. fn on_navigation(&mut self, webview: &Webview<R>, url: &Url) -> bool {
  59. true
  60. }
  61. /// Callback invoked when the webview performs a navigation to a page.
  62. #[allow(unused_variables)]
  63. fn on_page_load(&mut self, webview: &Webview<R>, payload: &PageLoadPayload<'_>) {}
  64. /// Callback invoked when the event loop receives a new event.
  65. #[allow(unused_variables)]
  66. fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {}
  67. /// Extend commands to [`crate::Builder::invoke_handler`].
  68. #[allow(unused_variables)]
  69. fn extend_api(&mut self, invoke: Invoke<R>) -> bool {
  70. false
  71. }
  72. }
  73. type SetupHook<R, C> =
  74. dyn FnOnce(&AppHandle<R>, PluginApi<R, C>) -> Result<(), Box<dyn std::error::Error>> + Send;
  75. type OnWindowReady<R> = dyn FnMut(Window<R>) + Send;
  76. type OnWebviewReady<R> = dyn FnMut(Webview<R>) + Send;
  77. type OnEvent<R> = dyn FnMut(&AppHandle<R>, &RunEvent) + Send;
  78. type OnNavigation<R> = dyn Fn(&Webview<R>, &Url) -> bool + Send;
  79. type OnPageLoad<R> = dyn FnMut(&Webview<R>, &PageLoadPayload<'_>) + Send;
  80. type OnDrop<R> = dyn FnOnce(AppHandle<R>) + Send;
  81. /// A handle to a plugin.
  82. #[derive(Debug)]
  83. #[allow(dead_code)]
  84. pub struct PluginHandle<R: Runtime> {
  85. name: &'static str,
  86. handle: AppHandle<R>,
  87. }
  88. impl<R: Runtime> Clone for PluginHandle<R> {
  89. fn clone(&self) -> Self {
  90. Self {
  91. name: self.name,
  92. handle: self.handle.clone(),
  93. }
  94. }
  95. }
  96. impl<R: Runtime> PluginHandle<R> {
  97. /// Returns the application handle.
  98. pub fn app(&self) -> &AppHandle<R> {
  99. &self.handle
  100. }
  101. }
  102. /// Api exposed to the plugin setup hook.
  103. #[derive(Clone)]
  104. #[allow(dead_code)]
  105. pub struct PluginApi<R: Runtime, C: DeserializeOwned> {
  106. handle: AppHandle<R>,
  107. name: &'static str,
  108. raw_config: Arc<JsonValue>,
  109. config: C,
  110. }
  111. impl<R: Runtime, C: DeserializeOwned> PluginApi<R, C> {
  112. /// Returns the plugin configuration.
  113. pub fn config(&self) -> &C {
  114. &self.config
  115. }
  116. /// Returns the application handle.
  117. pub fn app(&self) -> &AppHandle<R> {
  118. &self.handle
  119. }
  120. /// Gets the global scope defined on the permissions that are part of the app ACL.
  121. pub fn scope<T: ScopeObject>(&self) -> crate::Result<ScopeValue<T>> {
  122. self
  123. .handle
  124. .manager
  125. .runtime_authority
  126. .lock()
  127. .unwrap()
  128. .scope_manager
  129. .get_global_scope_typed(&self.handle, self.name)
  130. }
  131. }
  132. /// Builds a [`TauriPlugin`].
  133. ///
  134. /// This Builder offers a more concise way to construct Tauri plugins than implementing the Plugin trait directly.
  135. ///
  136. /// # Conventions
  137. ///
  138. /// When using the Builder Pattern it is encouraged to export a function called `init` that constructs and returns the plugin.
  139. /// While plugin authors can provide every possible way to construct a plugin,
  140. /// sticking to the `init` function convention helps users to quickly identify the correct function to call.
  141. ///
  142. /// ```rust
  143. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  144. ///
  145. /// pub fn init<R: Runtime>() -> TauriPlugin<R> {
  146. /// Builder::new("example")
  147. /// .build()
  148. /// }
  149. /// ```
  150. ///
  151. /// When plugins expose more complex configuration options, it can be helpful to provide a Builder instead:
  152. ///
  153. /// ```rust
  154. /// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, Runtime};
  155. ///
  156. /// pub struct Builder {
  157. /// option_a: String,
  158. /// option_b: String,
  159. /// option_c: bool
  160. /// }
  161. ///
  162. /// impl Default for Builder {
  163. /// fn default() -> Self {
  164. /// Self {
  165. /// option_a: "foo".to_string(),
  166. /// option_b: "bar".to_string(),
  167. /// option_c: false
  168. /// }
  169. /// }
  170. /// }
  171. ///
  172. /// impl Builder {
  173. /// pub fn new() -> Self {
  174. /// Default::default()
  175. /// }
  176. ///
  177. /// pub fn option_a(mut self, option_a: String) -> Self {
  178. /// self.option_a = option_a;
  179. /// self
  180. /// }
  181. ///
  182. /// pub fn option_b(mut self, option_b: String) -> Self {
  183. /// self.option_b = option_b;
  184. /// self
  185. /// }
  186. ///
  187. /// pub fn option_c(mut self, option_c: bool) -> Self {
  188. /// self.option_c = option_c;
  189. /// self
  190. /// }
  191. ///
  192. /// pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
  193. /// PluginBuilder::new("example")
  194. /// .setup(move |app_handle, api| {
  195. /// // use the options here to do stuff
  196. /// println!("a: {}, b: {}, c: {}", self.option_a, self.option_b, self.option_c);
  197. ///
  198. /// Ok(())
  199. /// })
  200. /// .build()
  201. /// }
  202. /// }
  203. /// ```
  204. pub struct Builder<R: Runtime, C: DeserializeOwned = ()> {
  205. name: &'static str,
  206. invoke_handler: Box<InvokeHandler<R>>,
  207. setup: Option<Box<SetupHook<R, C>>>,
  208. js_init_script: Option<String>,
  209. on_navigation: Box<OnNavigation<R>>,
  210. on_page_load: Box<OnPageLoad<R>>,
  211. on_window_ready: Box<OnWindowReady<R>>,
  212. on_webview_ready: Box<OnWebviewReady<R>>,
  213. on_event: Box<OnEvent<R>>,
  214. on_drop: Option<Box<OnDrop<R>>>,
  215. uri_scheme_protocols: HashMap<String, Arc<UriSchemeProtocol<R>>>,
  216. }
  217. impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
  218. /// Creates a new Plugin builder.
  219. pub fn new(name: &'static str) -> Self {
  220. Self {
  221. name,
  222. setup: None,
  223. js_init_script: None,
  224. invoke_handler: Box::new(|_| false),
  225. on_navigation: Box::new(|_, _| true),
  226. on_page_load: Box::new(|_, _| ()),
  227. on_window_ready: Box::new(|_| ()),
  228. on_webview_ready: Box::new(|_| ()),
  229. on_event: Box::new(|_, _| ()),
  230. on_drop: None,
  231. uri_scheme_protocols: Default::default(),
  232. }
  233. }
  234. /// Defines the JS message handler callback.
  235. /// It is recommended you use the [tauri::generate_handler] to generate the input to this method, as the input type is not considered stable yet.
  236. ///
  237. /// # Examples
  238. ///
  239. /// ```rust
  240. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  241. ///
  242. /// #[tauri::command]
  243. /// async fn foobar<R: Runtime>(app: tauri::AppHandle<R>, window: tauri::Window<R>) -> Result<(), String> {
  244. /// println!("foobar");
  245. ///
  246. /// Ok(())
  247. /// }
  248. ///
  249. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  250. /// Builder::new("example")
  251. /// .invoke_handler(tauri::generate_handler![foobar])
  252. /// .build()
  253. /// }
  254. ///
  255. /// ```
  256. /// [tauri::generate_handler]: ../macro.generate_handler.html
  257. #[must_use]
  258. pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
  259. where
  260. F: Fn(Invoke<R>) -> bool + Send + Sync + 'static,
  261. {
  262. self.invoke_handler = Box::new(invoke_handler);
  263. self
  264. }
  265. /// Sets the provided JavaScript to be run after the global object has been created,
  266. /// but before the HTML document has been parsed and before any other script included by the HTML document is run.
  267. ///
  268. /// Since it runs on all top-level document and child frame page navigations,
  269. /// it's recommended to check the `window.location` to guard your script from running on unexpected origins.
  270. ///
  271. /// The script is wrapped into its own context with `(function () { /* your script here */ })();`,
  272. /// so global variables must be assigned to `window` instead of implicitly declared.
  273. ///
  274. /// Note that calling this function multiple times overrides previous values.
  275. ///
  276. /// # Examples
  277. ///
  278. /// ```rust
  279. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  280. ///
  281. /// const INIT_SCRIPT: &str = r#"
  282. /// if (window.location.origin === 'https://tauri.app') {
  283. /// console.log("hello world from js init script");
  284. ///
  285. /// window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };
  286. /// }
  287. /// "#;
  288. ///
  289. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  290. /// Builder::new("example")
  291. /// .js_init_script(INIT_SCRIPT.to_string())
  292. /// .build()
  293. /// }
  294. /// ```
  295. #[must_use]
  296. pub fn js_init_script(mut self, js_init_script: String) -> Self {
  297. self.js_init_script = Some(js_init_script);
  298. self
  299. }
  300. /// Define a closure that runs when the plugin is registered.
  301. ///
  302. /// # Examples
  303. ///
  304. /// ```rust
  305. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime, Manager};
  306. /// use std::path::PathBuf;
  307. ///
  308. /// #[derive(Debug, Default)]
  309. /// struct PluginState {
  310. /// dir: Option<PathBuf>
  311. /// }
  312. ///
  313. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  314. /// Builder::new("example")
  315. /// .setup(|app, api| {
  316. /// app.manage(PluginState::default());
  317. ///
  318. /// Ok(())
  319. /// })
  320. /// .build()
  321. /// }
  322. /// ```
  323. #[must_use]
  324. pub fn setup<F>(mut self, setup: F) -> Self
  325. where
  326. F: FnOnce(&AppHandle<R>, PluginApi<R, C>) -> Result<(), Box<dyn std::error::Error>>
  327. + Send
  328. + 'static,
  329. {
  330. self.setup.replace(Box::new(setup));
  331. self
  332. }
  333. /// Callback invoked when the webview tries to navigate to a URL. Returning false cancels the navigation.
  334. ///
  335. /// #Example
  336. ///
  337. /// ```
  338. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  339. ///
  340. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  341. /// Builder::new("example")
  342. /// .on_navigation(|webview, url| {
  343. /// // allow the production URL or localhost on dev
  344. /// url.scheme() == "tauri" || (cfg!(dev) && url.host_str() == Some("localhost"))
  345. /// })
  346. /// .build()
  347. /// }
  348. /// ```
  349. #[must_use]
  350. pub fn on_navigation<F>(mut self, on_navigation: F) -> Self
  351. where
  352. F: Fn(&Webview<R>, &Url) -> bool + Send + 'static,
  353. {
  354. self.on_navigation = Box::new(on_navigation);
  355. self
  356. }
  357. /// Callback invoked when the webview performs a navigation to a page.
  358. ///
  359. /// # Examples
  360. ///
  361. /// ```rust
  362. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  363. ///
  364. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  365. /// Builder::new("example")
  366. /// .on_page_load(|webview, payload| {
  367. /// println!("{:?} URL {} in webview {}", payload.event(), payload.url(), webview.label());
  368. /// })
  369. /// .build()
  370. /// }
  371. /// ```
  372. #[must_use]
  373. pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
  374. where
  375. F: FnMut(&Webview<R>, &PageLoadPayload<'_>) + Send + 'static,
  376. {
  377. self.on_page_load = Box::new(on_page_load);
  378. self
  379. }
  380. /// Callback invoked when the window is created.
  381. ///
  382. /// # Examples
  383. ///
  384. /// ```rust
  385. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  386. ///
  387. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  388. /// Builder::new("example")
  389. /// .on_window_ready(|window| {
  390. /// println!("created window {}", window.label());
  391. /// })
  392. /// .build()
  393. /// }
  394. /// ```
  395. #[must_use]
  396. pub fn on_window_ready<F>(mut self, on_window_ready: F) -> Self
  397. where
  398. F: FnMut(Window<R>) + Send + 'static,
  399. {
  400. self.on_window_ready = Box::new(on_window_ready);
  401. self
  402. }
  403. /// Callback invoked when the webview is created.
  404. ///
  405. /// # Examples
  406. ///
  407. /// ```rust
  408. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  409. ///
  410. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  411. /// Builder::new("example")
  412. /// .on_webview_ready(|webview| {
  413. /// println!("created webview {}", webview.label());
  414. /// })
  415. /// .build()
  416. /// }
  417. /// ```
  418. #[must_use]
  419. pub fn on_webview_ready<F>(mut self, on_webview_ready: F) -> Self
  420. where
  421. F: FnMut(Webview<R>) + Send + 'static,
  422. {
  423. self.on_webview_ready = Box::new(on_webview_ready);
  424. self
  425. }
  426. /// Callback invoked when the event loop receives a new event.
  427. ///
  428. /// # Examples
  429. ///
  430. /// ```rust
  431. /// use tauri::{plugin::{Builder, TauriPlugin}, RunEvent, Runtime};
  432. ///
  433. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  434. /// Builder::new("example")
  435. /// .on_event(|app_handle, event| {
  436. /// match event {
  437. /// RunEvent::ExitRequested { api, .. } => {
  438. /// // Prevents the app from exiting.
  439. /// // This will cause the core thread to continue running in the background even without any open windows.
  440. /// api.prevent_exit();
  441. /// }
  442. /// // Ignore all other cases.
  443. /// _ => {}
  444. /// }
  445. /// })
  446. /// .build()
  447. /// }
  448. /// ```
  449. #[must_use]
  450. pub fn on_event<F>(mut self, on_event: F) -> Self
  451. where
  452. F: FnMut(&AppHandle<R>, &RunEvent) + Send + 'static,
  453. {
  454. self.on_event = Box::new(on_event);
  455. self
  456. }
  457. /// Callback invoked when the plugin is dropped.
  458. ///
  459. /// # Examples
  460. ///
  461. /// ```rust
  462. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  463. ///
  464. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  465. /// Builder::new("example")
  466. /// .on_drop(|app| {
  467. /// println!("plugin has been dropped and is no longer running");
  468. /// // you can run cleanup logic here
  469. /// })
  470. /// .build()
  471. /// }
  472. /// ```
  473. #[must_use]
  474. pub fn on_drop<F>(mut self, on_drop: F) -> Self
  475. where
  476. F: FnOnce(AppHandle<R>) + Send + 'static,
  477. {
  478. self.on_drop.replace(Box::new(on_drop));
  479. self
  480. }
  481. /// Registers a URI scheme protocol available to all webviews.
  482. /// Leverages [setURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/2875766-seturlschemehandler) on macOS,
  483. /// [AddWebResourceRequestedFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.addwebresourcerequestedfilter?view=webview2-dotnet-1.0.774.44) on Windows
  484. /// and [webkit-web-context-register-uri-scheme](https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#webkit-web-context-register-uri-scheme) on Linux.
  485. ///
  486. /// # Known limitations
  487. ///
  488. /// URI scheme protocols are registered when the webview is created. Due to this limitation, if the plugin is registered after a webview has been created, this protocol won't be available.
  489. ///
  490. /// # Arguments
  491. ///
  492. /// * `uri_scheme` The URI scheme to register, such as `example`.
  493. /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`.
  494. ///
  495. /// # Examples
  496. ///
  497. /// ```rust
  498. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  499. ///
  500. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  501. /// Builder::new("myplugin")
  502. /// .register_uri_scheme_protocol("myscheme", |app, req| {
  503. /// http::Response::builder().body(Vec::new()).unwrap()
  504. /// })
  505. /// .build()
  506. /// }
  507. /// ```
  508. #[must_use]
  509. pub fn register_uri_scheme_protocol<
  510. N: Into<String>,
  511. T: Into<Cow<'static, [u8]>>,
  512. H: Fn(&AppHandle<R>, http::Request<Vec<u8>>) -> http::Response<T> + Send + Sync + 'static,
  513. >(
  514. mut self,
  515. uri_scheme: N,
  516. protocol: H,
  517. ) -> Self {
  518. self.uri_scheme_protocols.insert(
  519. uri_scheme.into(),
  520. Arc::new(UriSchemeProtocol {
  521. protocol: Box::new(move |app, request, responder| {
  522. responder.respond(protocol(app, request))
  523. }),
  524. }),
  525. );
  526. self
  527. }
  528. /// Similar to [`Self::register_uri_scheme_protocol`] but with an asynchronous responder that allows you
  529. /// to process the request in a separate thread and respond asynchronously.
  530. ///
  531. /// # Arguments
  532. ///
  533. /// * `uri_scheme` The URI scheme to register, such as `example`.
  534. /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`.
  535. ///
  536. /// # Examples
  537. ///
  538. /// ```rust
  539. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  540. ///
  541. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  542. /// Builder::new("myplugin")
  543. /// .register_asynchronous_uri_scheme_protocol("app-files", |_app, request, responder| {
  544. /// // skip leading `/`
  545. /// let path = request.uri().path()[1..].to_string();
  546. /// std::thread::spawn(move || {
  547. /// if let Ok(data) = std::fs::read(path) {
  548. /// responder.respond(
  549. /// http::Response::builder()
  550. /// .body(data)
  551. /// .unwrap()
  552. /// );
  553. /// } else {
  554. /// responder.respond(
  555. /// http::Response::builder()
  556. /// .status(http::StatusCode::BAD_REQUEST)
  557. /// .header(http::header::CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())
  558. /// .body("failed to read file".as_bytes().to_vec())
  559. /// .unwrap()
  560. /// );
  561. /// }
  562. /// });
  563. /// })
  564. /// .build()
  565. /// }
  566. /// ```
  567. #[must_use]
  568. pub fn register_asynchronous_uri_scheme_protocol<
  569. N: Into<String>,
  570. H: Fn(&AppHandle<R>, http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync + 'static,
  571. >(
  572. mut self,
  573. uri_scheme: N,
  574. protocol: H,
  575. ) -> Self {
  576. self.uri_scheme_protocols.insert(
  577. uri_scheme.into(),
  578. Arc::new(UriSchemeProtocol {
  579. protocol: Box::new(protocol),
  580. }),
  581. );
  582. self
  583. }
  584. /// Builds the [TauriPlugin].
  585. pub fn build(self) -> TauriPlugin<R, C> {
  586. TauriPlugin {
  587. name: self.name,
  588. app: None,
  589. invoke_handler: self.invoke_handler,
  590. setup: self.setup,
  591. js_init_script: self.js_init_script,
  592. on_navigation: self.on_navigation,
  593. on_page_load: self.on_page_load,
  594. on_window_ready: self.on_window_ready,
  595. on_webview_ready: self.on_webview_ready,
  596. on_event: self.on_event,
  597. on_drop: self.on_drop,
  598. uri_scheme_protocols: self.uri_scheme_protocols,
  599. }
  600. }
  601. }
  602. /// Plugin struct that is returned by the [`Builder`]. Should only be constructed through the builder.
  603. pub struct TauriPlugin<R: Runtime, C: DeserializeOwned = ()> {
  604. name: &'static str,
  605. app: Option<AppHandle<R>>,
  606. invoke_handler: Box<InvokeHandler<R>>,
  607. setup: Option<Box<SetupHook<R, C>>>,
  608. js_init_script: Option<String>,
  609. on_navigation: Box<OnNavigation<R>>,
  610. on_page_load: Box<OnPageLoad<R>>,
  611. on_window_ready: Box<OnWindowReady<R>>,
  612. on_webview_ready: Box<OnWebviewReady<R>>,
  613. on_event: Box<OnEvent<R>>,
  614. on_drop: Option<Box<OnDrop<R>>>,
  615. uri_scheme_protocols: HashMap<String, Arc<UriSchemeProtocol<R>>>,
  616. }
  617. impl<R: Runtime, C: DeserializeOwned> Drop for TauriPlugin<R, C> {
  618. fn drop(&mut self) {
  619. if let (Some(on_drop), Some(app)) = (self.on_drop.take(), self.app.take()) {
  620. on_drop(app);
  621. }
  622. }
  623. }
  624. impl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> {
  625. fn name(&self) -> &'static str {
  626. self.name
  627. }
  628. fn initialize(
  629. &mut self,
  630. app: &AppHandle<R>,
  631. config: JsonValue,
  632. ) -> Result<(), Box<dyn std::error::Error>> {
  633. self.app.replace(app.clone());
  634. if let Some(s) = self.setup.take() {
  635. (s)(
  636. app,
  637. PluginApi {
  638. name: self.name,
  639. handle: app.clone(),
  640. raw_config: Arc::new(config.clone()),
  641. config: serde_json::from_value(config)?,
  642. },
  643. )?;
  644. }
  645. for (uri_scheme, protocol) in &self.uri_scheme_protocols {
  646. app
  647. .manager
  648. .webview
  649. .register_uri_scheme_protocol(uri_scheme, protocol.clone())
  650. }
  651. Ok(())
  652. }
  653. fn initialization_script(&self) -> Option<String> {
  654. self.js_init_script.clone()
  655. }
  656. fn window_created(&mut self, window: Window<R>) {
  657. (self.on_window_ready)(window)
  658. }
  659. fn webview_created(&mut self, webview: Webview<R>) {
  660. (self.on_webview_ready)(webview)
  661. }
  662. fn on_navigation(&mut self, webview: &Webview<R>, url: &Url) -> bool {
  663. (self.on_navigation)(webview, url)
  664. }
  665. fn on_page_load(&mut self, webview: &Webview<R>, payload: &PageLoadPayload<'_>) {
  666. (self.on_page_load)(webview, payload)
  667. }
  668. fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {
  669. (self.on_event)(app, event)
  670. }
  671. fn extend_api(&mut self, invoke: Invoke<R>) -> bool {
  672. (self.invoke_handler)(invoke)
  673. }
  674. }
  675. /// Plugin collection type.
  676. #[default_runtime(crate::Wry, wry)]
  677. pub(crate) struct PluginStore<R: Runtime> {
  678. store: Vec<Box<dyn Plugin<R>>>,
  679. }
  680. impl<R: Runtime> fmt::Debug for PluginStore<R> {
  681. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  682. let plugins: Vec<&str> = self.store.iter().map(|plugins| plugins.name()).collect();
  683. f.debug_struct("PluginStore")
  684. .field("plugins", &plugins)
  685. .finish()
  686. }
  687. }
  688. impl<R: Runtime> Default for PluginStore<R> {
  689. fn default() -> Self {
  690. Self { store: Vec::new() }
  691. }
  692. }
  693. impl<R: Runtime> PluginStore<R> {
  694. /// Adds a plugin to the store.
  695. ///
  696. /// Returns `true` if a plugin with the same name is already in the store.
  697. pub fn register(&mut self, plugin: Box<dyn Plugin<R>>) -> bool {
  698. let len = self.store.len();
  699. self.store.retain(|p| p.name() != plugin.name());
  700. let result = len != self.store.len();
  701. self.store.push(plugin);
  702. result
  703. }
  704. /// Removes the plugin with the given name from the store.
  705. pub fn unregister(&mut self, plugin: &'static str) -> bool {
  706. let len = self.store.len();
  707. self.store.retain(|p| p.name() != plugin);
  708. len != self.store.len()
  709. }
  710. /// Initializes the given plugin.
  711. pub(crate) fn initialize(
  712. &self,
  713. plugin: &mut Box<dyn Plugin<R>>,
  714. app: &AppHandle<R>,
  715. config: &PluginConfig,
  716. ) -> crate::Result<()> {
  717. initialize(plugin, app, config)
  718. }
  719. /// Initializes all plugins in the store.
  720. pub(crate) fn initialize_all(
  721. &mut self,
  722. app: &AppHandle<R>,
  723. config: &PluginConfig,
  724. ) -> crate::Result<()> {
  725. self
  726. .store
  727. .iter_mut()
  728. .try_for_each(|plugin| initialize(plugin, app, config))
  729. }
  730. /// Generates an initialization script from all plugins in the store.
  731. pub(crate) fn initialization_script(&self) -> String {
  732. self
  733. .store
  734. .iter()
  735. .filter_map(|p| p.initialization_script())
  736. .fold(String::new(), |acc, script| {
  737. format!("{acc}\n(function () {{ {script} }})();")
  738. })
  739. }
  740. /// Runs the created hook for all plugins in the store.
  741. pub(crate) fn window_created(&mut self, window: Window<R>) {
  742. self.store.iter_mut().for_each(|plugin| {
  743. #[cfg(feature = "tracing")]
  744. let _span = tracing::trace_span!("plugin::hooks::created", name = plugin.name()).entered();
  745. plugin.window_created(window.clone())
  746. })
  747. }
  748. /// Runs the webview created hook for all plugins in the store.
  749. pub(crate) fn webview_created(&mut self, webview: Webview<R>) {
  750. self
  751. .store
  752. .iter_mut()
  753. .for_each(|plugin| plugin.webview_created(webview.clone()))
  754. }
  755. pub(crate) fn on_navigation(&mut self, webview: &Webview<R>, url: &Url) -> bool {
  756. for plugin in self.store.iter_mut() {
  757. #[cfg(feature = "tracing")]
  758. let _span =
  759. tracing::trace_span!("plugin::hooks::on_navigation", name = plugin.name()).entered();
  760. if !plugin.on_navigation(webview, url) {
  761. return false;
  762. }
  763. }
  764. true
  765. }
  766. /// Runs the on_page_load hook for all plugins in the store.
  767. pub(crate) fn on_page_load(&mut self, webview: &Webview<R>, payload: &PageLoadPayload<'_>) {
  768. self.store.iter_mut().for_each(|plugin| {
  769. #[cfg(feature = "tracing")]
  770. let _span =
  771. tracing::trace_span!("plugin::hooks::on_page_load", name = plugin.name()).entered();
  772. plugin.on_page_load(webview, payload)
  773. })
  774. }
  775. /// Runs the on_event hook for all plugins in the store.
  776. pub(crate) fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {
  777. self
  778. .store
  779. .iter_mut()
  780. .for_each(|plugin| plugin.on_event(app, event))
  781. }
  782. /// Runs the plugin `extend_api` hook if it exists. Returns whether the invoke message was handled or not.
  783. ///
  784. /// The message is not handled when the plugin exists **and** the command does not.
  785. pub(crate) fn extend_api(&mut self, plugin: &str, invoke: Invoke<R>) -> bool {
  786. for p in self.store.iter_mut() {
  787. if p.name() == plugin {
  788. #[cfg(feature = "tracing")]
  789. let _span = tracing::trace_span!("plugin::hooks::ipc", name = plugin).entered();
  790. return p.extend_api(invoke);
  791. }
  792. }
  793. invoke.resolver.reject(format!("plugin {plugin} not found"));
  794. true
  795. }
  796. }
  797. #[cfg_attr(feature = "tracing", tracing::instrument(name = "plugin::hooks::initialize", skip(plugin, app), fields(name = plugin.name())))]
  798. fn initialize<R: Runtime>(
  799. plugin: &mut Box<dyn Plugin<R>>,
  800. app: &AppHandle<R>,
  801. config: &PluginConfig,
  802. ) -> crate::Result<()> {
  803. plugin
  804. .initialize(
  805. app,
  806. config.0.get(plugin.name()).cloned().unwrap_or_default(),
  807. )
  808. .map_err(|e| Error::PluginInitialization(plugin.name().to_string(), e.to_string()))
  809. }