plugin.rs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. // Copyright 2019-2022 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. #[cfg(target_os = "android")]
  6. use crate::{
  7. runtime::RuntimeHandle,
  8. sealed::{ManagerBase, RuntimeOrDispatch},
  9. };
  10. use crate::{
  11. utils::config::PluginConfig, AppHandle, Invoke, InvokeHandler, PageLoadPayload, RunEvent,
  12. Runtime, Window,
  13. };
  14. use serde::de::DeserializeOwned;
  15. use serde_json::Value as JsonValue;
  16. use tauri_macros::default_runtime;
  17. use std::{collections::HashMap, fmt, result::Result as StdResult};
  18. /// The result type of Tauri plugin module.
  19. pub type Result<T> = StdResult<T, Box<dyn std::error::Error>>;
  20. /// The plugin interface.
  21. pub trait Plugin<R: Runtime>: Send {
  22. /// The plugin name. Used as key on the plugin config object.
  23. fn name(&self) -> &'static str;
  24. /// Initializes the plugin.
  25. #[allow(unused_variables)]
  26. fn initialize(&mut self, app: &AppHandle<R>, config: JsonValue) -> Result<()> {
  27. Ok(())
  28. }
  29. /// Add the provided JavaScript to a list of scripts that should be run after the global object has been created,
  30. /// but before the HTML document has been parsed and before any other script included by the HTML document is run.
  31. ///
  32. /// Since it runs on all top-level document and child frame page navigations,
  33. /// it's recommended to check the `window.location` to guard your script from running on unexpected origins.
  34. ///
  35. /// The script is wrapped into its own context with `(function () { /* your script here */ })();`,
  36. /// so global variables must be assigned to `window` instead of implicitly declared.
  37. fn initialization_script(&self) -> Option<String> {
  38. None
  39. }
  40. /// Callback invoked when the webview is created.
  41. #[allow(unused_variables)]
  42. fn created(&mut self, window: Window<R>) {}
  43. /// Callback invoked when the webview performs a navigation to a page.
  44. #[allow(unused_variables)]
  45. fn on_page_load(&mut self, window: Window<R>, payload: PageLoadPayload) {}
  46. /// Callback invoked when the event loop receives a new event.
  47. #[allow(unused_variables)]
  48. fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {}
  49. /// Extend commands to [`crate::Builder::invoke_handler`].
  50. #[allow(unused_variables)]
  51. fn extend_api(&mut self, invoke: Invoke<R>) -> bool {
  52. false
  53. }
  54. }
  55. type SetupHook<R, C> = dyn FnOnce(&AppHandle<R>, PluginApi<R, C>) -> Result<()> + Send;
  56. type OnWebviewReady<R> = dyn FnMut(Window<R>) + Send;
  57. type OnEvent<R> = dyn FnMut(&AppHandle<R>, &RunEvent) + Send;
  58. type OnPageLoad<R> = dyn FnMut(Window<R>, PageLoadPayload) + Send;
  59. type OnDrop<R> = dyn FnOnce(AppHandle<R>) + Send;
  60. #[cfg(mobile)]
  61. type PendingPluginCallHandler =
  62. Box<dyn FnOnce(StdResult<serde_json::Value, serde_json::Value>) + Send + 'static>;
  63. #[cfg(mobile)]
  64. static PENDING_PLUGIN_CALLS: once_cell::sync::OnceCell<
  65. std::sync::Mutex<HashMap<i32, PendingPluginCallHandler>>,
  66. > = once_cell::sync::OnceCell::new();
  67. #[cfg(target_os = "android")]
  68. #[doc(hidden)]
  69. pub fn handle_android_plugin_response(
  70. env: jni::JNIEnv<'_>,
  71. id: i32,
  72. success: jni::objects::JString<'_>,
  73. error: jni::objects::JString<'_>,
  74. ) {
  75. let (payload, is_ok): (serde_json::Value, bool) = match (
  76. env
  77. .is_same_object(success, jni::objects::JObject::default())
  78. .unwrap_or_default(),
  79. env
  80. .is_same_object(error, jni::objects::JObject::default())
  81. .unwrap_or_default(),
  82. ) {
  83. // both null
  84. (true, true) => (serde_json::Value::Null, true),
  85. // error null
  86. (false, true) => (
  87. serde_json::from_str(env.get_string(success).unwrap().to_str().unwrap()).unwrap(),
  88. true,
  89. ),
  90. // success null
  91. (true, false) => (
  92. serde_json::from_str(env.get_string(error).unwrap().to_str().unwrap()).unwrap(),
  93. false,
  94. ),
  95. // both are set - impossible in the Kotlin code
  96. (false, false) => unreachable!(),
  97. };
  98. if let Some(handler) = PENDING_PLUGIN_CALLS
  99. .get_or_init(Default::default)
  100. .lock()
  101. .unwrap()
  102. .remove(&id)
  103. {
  104. handler(if is_ok { Ok(payload) } else { Err(payload) });
  105. }
  106. }
  107. /// A handle to a plugin.
  108. #[derive(Clone)]
  109. #[allow(dead_code)]
  110. pub struct PluginHandle<R: Runtime> {
  111. name: &'static str,
  112. handle: AppHandle<R>,
  113. }
  114. impl<R: Runtime> PluginHandle<R> {
  115. /// Executes the given mobile method.
  116. #[cfg(mobile)]
  117. pub fn run_mobile_plugin<T: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned>(
  118. &self,
  119. method: impl AsRef<str>,
  120. payload: impl serde::Serialize,
  121. ) -> crate::Result<StdResult<T, E>> {
  122. #[cfg(target_os = "ios")]
  123. {
  124. Ok(self.run_ios_plugin(method, payload))
  125. }
  126. #[cfg(target_os = "android")]
  127. {
  128. self.run_android_plugin(method, payload).map_err(Into::into)
  129. }
  130. }
  131. /// Executes the given iOS method.
  132. #[cfg(target_os = "ios")]
  133. fn run_ios_plugin<T: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned>(
  134. &self,
  135. method: impl AsRef<str>,
  136. payload: impl serde::Serialize,
  137. ) -> StdResult<T, E> {
  138. use std::{
  139. ffi::CStr,
  140. os::raw::{c_char, c_int},
  141. sync::mpsc::channel,
  142. };
  143. let id: i32 = rand::random();
  144. let (tx, rx) = channel();
  145. PENDING_PLUGIN_CALLS
  146. .get_or_init(Default::default)
  147. .lock()
  148. .unwrap()
  149. .insert(
  150. id,
  151. Box::new(move |arg| {
  152. tx.send(arg).unwrap();
  153. }),
  154. );
  155. unsafe {
  156. extern "C" fn plugin_method_response_handler(
  157. id: c_int,
  158. success: c_int,
  159. payload: *const c_char,
  160. ) {
  161. let payload = unsafe {
  162. assert!(!payload.is_null());
  163. CStr::from_ptr(payload)
  164. };
  165. if let Some(handler) = PENDING_PLUGIN_CALLS
  166. .get_or_init(Default::default)
  167. .lock()
  168. .unwrap()
  169. .remove(&id)
  170. {
  171. let payload = serde_json::from_str(payload.to_str().unwrap()).unwrap();
  172. handler(if success == 1 {
  173. Ok(payload)
  174. } else {
  175. Err(payload)
  176. });
  177. }
  178. }
  179. crate::ios::run_plugin_method(
  180. id,
  181. &self.name.into(),
  182. &method.as_ref().into(),
  183. crate::ios::json_to_dictionary(serde_json::to_value(payload).unwrap()),
  184. plugin_method_response_handler,
  185. );
  186. }
  187. rx.recv()
  188. .unwrap()
  189. .map(|r| serde_json::from_value(r).unwrap())
  190. .map_err(|e| serde_json::from_value(e).unwrap())
  191. }
  192. /// Executes the given Android method.
  193. #[cfg(target_os = "android")]
  194. fn run_android_plugin<T: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned>(
  195. &self,
  196. method: impl AsRef<str>,
  197. payload: impl serde::Serialize,
  198. ) -> StdResult<StdResult<T, E>, jni::errors::Error> {
  199. use jni::{errors::Error as JniError, objects::JObject, JNIEnv};
  200. fn run<R: Runtime>(
  201. id: i32,
  202. plugin: &'static str,
  203. method: String,
  204. payload: serde_json::Value,
  205. runtime_handle: &R::Handle,
  206. env: JNIEnv<'_>,
  207. activity: JObject<'_>,
  208. ) -> StdResult<(), JniError> {
  209. let data = crate::jni_helpers::to_jsobject::<R>(env, activity, runtime_handle, payload)?;
  210. let plugin_manager = env
  211. .call_method(
  212. activity,
  213. "getPluginManager",
  214. "()Lapp/tauri/plugin/PluginManager;",
  215. &[],
  216. )?
  217. .l()?;
  218. env.call_method(
  219. plugin_manager,
  220. "runPluginMethod",
  221. "(ILjava/lang/String;Ljava/lang/String;Lapp/tauri/plugin/JSObject;)V",
  222. &[
  223. id.into(),
  224. env.new_string(plugin)?.into(),
  225. env.new_string(&method)?.into(),
  226. data.into(),
  227. ],
  228. )?;
  229. Ok(())
  230. }
  231. let handle = match self.handle.runtime() {
  232. RuntimeOrDispatch::Runtime(r) => r.handle(),
  233. RuntimeOrDispatch::RuntimeHandle(h) => h,
  234. _ => unreachable!(),
  235. };
  236. let id: i32 = rand::random();
  237. let plugin_name = self.name;
  238. let method = method.as_ref().to_string();
  239. let payload = serde_json::to_value(payload).unwrap();
  240. let handle_ = handle.clone();
  241. let (tx, rx) = std::sync::mpsc::channel();
  242. let tx_ = tx.clone();
  243. PENDING_PLUGIN_CALLS
  244. .get_or_init(Default::default)
  245. .lock()
  246. .unwrap()
  247. .insert(
  248. id,
  249. Box::new(move |arg| {
  250. tx.send(Ok(arg)).unwrap();
  251. }),
  252. );
  253. handle.run_on_android_context(move |env, activity, _webview| {
  254. if let Err(e) = run::<R>(id, plugin_name, method, payload, &handle_, env, activity) {
  255. tx_.send(Err(e)).unwrap();
  256. }
  257. });
  258. rx.recv().unwrap().map(|response| {
  259. response
  260. .map(|r| serde_json::from_value(r).unwrap())
  261. .map_err(|e| serde_json::from_value(e).unwrap())
  262. })
  263. }
  264. }
  265. /// Api exposed to the plugin setup hook.
  266. #[derive(Clone)]
  267. #[allow(dead_code)]
  268. pub struct PluginApi<R: Runtime, C: DeserializeOwned> {
  269. handle: AppHandle<R>,
  270. name: &'static str,
  271. config: C,
  272. }
  273. impl<R: Runtime, C: DeserializeOwned> PluginApi<R, C> {
  274. /// Returns the plugin configuration.
  275. pub fn config(&self) -> &C {
  276. &self.config
  277. }
  278. /// Registers an iOS plugin.
  279. #[cfg(target_os = "ios")]
  280. pub fn register_ios_plugin(
  281. &self,
  282. init_fn: unsafe extern "C" fn(cocoa::base::id),
  283. ) -> crate::Result<PluginHandle<R>> {
  284. if let Some(window) = self.handle.manager.windows().values().next() {
  285. window.with_webview(move |w| {
  286. unsafe { init_fn(w.inner()) };
  287. })?;
  288. } else {
  289. unsafe { init_fn(cocoa::base::nil) };
  290. }
  291. Ok(PluginHandle {
  292. name: self.name,
  293. handle: self.handle.clone(),
  294. })
  295. }
  296. /// Registers an Android plugin.
  297. #[cfg(target_os = "android")]
  298. pub fn register_android_plugin(
  299. &self,
  300. plugin_identifier: &str,
  301. class_name: &str,
  302. ) -> crate::Result<PluginHandle<R>> {
  303. use jni::{errors::Error as JniError, objects::JObject, JNIEnv};
  304. fn initialize_plugin<'a, R: Runtime>(
  305. env: JNIEnv<'a>,
  306. activity: JObject<'a>,
  307. webview: JObject<'a>,
  308. runtime_handle: &R::Handle,
  309. plugin_name: &'static str,
  310. plugin_class: String,
  311. ) -> StdResult<(), JniError> {
  312. let plugin_manager = env
  313. .call_method(
  314. activity,
  315. "getPluginManager",
  316. format!("()Lapp/tauri/plugin/PluginManager;"),
  317. &[],
  318. )?
  319. .l()?;
  320. // instantiate plugin
  321. let plugin_class = runtime_handle.find_class(env, activity, plugin_class)?;
  322. let plugin = env.new_object(
  323. plugin_class,
  324. "(Landroid/app/Activity;)V",
  325. &[activity.into()],
  326. )?;
  327. // load plugin
  328. env.call_method(
  329. plugin_manager,
  330. "load",
  331. format!("(Landroid/webkit/WebView;Ljava/lang/String;Lapp/tauri/plugin/Plugin;)V"),
  332. &[
  333. webview.into(),
  334. env.new_string(plugin_name)?.into(),
  335. plugin.into(),
  336. ],
  337. )?;
  338. Ok(())
  339. }
  340. let plugin_class = format!("{}/{}", plugin_identifier.replace(".", "/"), class_name);
  341. let plugin_name = self.name;
  342. let runtime_handle = self.handle.runtime_handle.clone();
  343. self
  344. .handle
  345. .runtime_handle
  346. .run_on_android_context(move |env, activity, webview| {
  347. let _ = initialize_plugin::<R>(
  348. env,
  349. activity,
  350. webview,
  351. &runtime_handle,
  352. plugin_name,
  353. plugin_class,
  354. );
  355. });
  356. Ok(PluginHandle {
  357. name: self.name,
  358. handle: self.handle.clone(),
  359. })
  360. }
  361. }
  362. /// Builds a [`TauriPlugin`].
  363. ///
  364. /// This Builder offers a more concise way to construct Tauri plugins than implementing the Plugin trait directly.
  365. ///
  366. /// # Conventions
  367. ///
  368. /// When using the Builder Pattern it is encouraged to export a function called `init` that constructs and returns the plugin.
  369. /// While plugin authors can provide every possible way to construct a plugin,
  370. /// sticking to the `init` function convention helps users to quickly identify the correct function to call.
  371. ///
  372. /// ```rust
  373. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  374. ///
  375. /// pub fn init<R: Runtime>() -> TauriPlugin<R> {
  376. /// Builder::new("example")
  377. /// .build()
  378. /// }
  379. /// ```
  380. ///
  381. /// When plugins expose more complex configuration options, it can be helpful to provide a Builder instead:
  382. ///
  383. /// ```rust
  384. /// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, Runtime};
  385. ///
  386. /// pub struct Builder {
  387. /// option_a: String,
  388. /// option_b: String,
  389. /// option_c: bool
  390. /// }
  391. ///
  392. /// impl Default for Builder {
  393. /// fn default() -> Self {
  394. /// Self {
  395. /// option_a: "foo".to_string(),
  396. /// option_b: "bar".to_string(),
  397. /// option_c: false
  398. /// }
  399. /// }
  400. /// }
  401. ///
  402. /// impl Builder {
  403. /// pub fn new() -> Self {
  404. /// Default::default()
  405. /// }
  406. ///
  407. /// pub fn option_a(mut self, option_a: String) -> Self {
  408. /// self.option_a = option_a;
  409. /// self
  410. /// }
  411. ///
  412. /// pub fn option_b(mut self, option_b: String) -> Self {
  413. /// self.option_b = option_b;
  414. /// self
  415. /// }
  416. ///
  417. /// pub fn option_c(mut self, option_c: bool) -> Self {
  418. /// self.option_c = option_c;
  419. /// self
  420. /// }
  421. ///
  422. /// pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
  423. /// PluginBuilder::new("example")
  424. /// .setup(move |app_handle, api| {
  425. /// // use the options here to do stuff
  426. /// println!("a: {}, b: {}, c: {}", self.option_a, self.option_b, self.option_c);
  427. ///
  428. /// Ok(())
  429. /// })
  430. /// .build()
  431. /// }
  432. /// }
  433. /// ```
  434. pub struct Builder<R: Runtime, C: DeserializeOwned = ()> {
  435. name: &'static str,
  436. invoke_handler: Box<InvokeHandler<R>>,
  437. setup: Option<Box<SetupHook<R, C>>>,
  438. js_init_script: Option<String>,
  439. on_page_load: Box<OnPageLoad<R>>,
  440. on_webview_ready: Box<OnWebviewReady<R>>,
  441. on_event: Box<OnEvent<R>>,
  442. on_drop: Option<Box<OnDrop<R>>>,
  443. }
  444. impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
  445. /// Creates a new Plugin builder.
  446. pub fn new(name: &'static str) -> Self {
  447. Self {
  448. name,
  449. setup: None,
  450. js_init_script: None,
  451. invoke_handler: Box::new(|_| false),
  452. on_page_load: Box::new(|_, _| ()),
  453. on_webview_ready: Box::new(|_| ()),
  454. on_event: Box::new(|_, _| ()),
  455. on_drop: None,
  456. }
  457. }
  458. /// Defines the JS message handler callback.
  459. /// 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.
  460. ///
  461. /// # Examples
  462. ///
  463. /// ```rust
  464. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  465. ///
  466. /// #[tauri::command]
  467. /// async fn foobar<R: Runtime>(app: tauri::AppHandle<R>, window: tauri::Window<R>) -> Result<(), String> {
  468. /// println!("foobar");
  469. ///
  470. /// Ok(())
  471. /// }
  472. ///
  473. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  474. /// Builder::new("example")
  475. /// .invoke_handler(tauri::generate_handler![foobar])
  476. /// .build()
  477. /// }
  478. ///
  479. /// ```
  480. /// [tauri::generate_handler]: ../macro.generate_handler.html
  481. #[must_use]
  482. pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
  483. where
  484. F: Fn(Invoke<R>) -> bool + Send + Sync + 'static,
  485. {
  486. self.invoke_handler = Box::new(invoke_handler);
  487. self
  488. }
  489. /// Sets the provided JavaScript to be run after the global object has been created,
  490. /// but before the HTML document has been parsed and before any other script included by the HTML document is run.
  491. ///
  492. /// Since it runs on all top-level document and child frame page navigations,
  493. /// it's recommended to check the `window.location` to guard your script from running on unexpected origins.
  494. ///
  495. /// The script is wrapped into its own context with `(function () { /* your script here */ })();`,
  496. /// so global variables must be assigned to `window` instead of implicitly declared.
  497. ///
  498. /// Note that calling this function multiple times overrides previous values.
  499. ///
  500. /// # Examples
  501. ///
  502. /// ```rust
  503. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  504. ///
  505. /// const INIT_SCRIPT: &str = r#"
  506. /// if (window.location.origin === 'https://tauri.app') {
  507. /// console.log("hello world from js init script");
  508. ///
  509. /// window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };
  510. /// }
  511. /// "#;
  512. ///
  513. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  514. /// Builder::new("example")
  515. /// .js_init_script(INIT_SCRIPT.to_string())
  516. /// .build()
  517. /// }
  518. /// ```
  519. #[must_use]
  520. pub fn js_init_script(mut self, js_init_script: String) -> Self {
  521. self.js_init_script = Some(js_init_script);
  522. self
  523. }
  524. /// Define a closure that runs when the plugin is registered.
  525. ///
  526. /// # Examples
  527. ///
  528. /// ```rust
  529. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime, Manager};
  530. /// use std::path::PathBuf;
  531. ///
  532. /// #[derive(Debug, Default)]
  533. /// struct PluginState {
  534. /// dir: Option<PathBuf>
  535. /// }
  536. ///
  537. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  538. /// Builder::new("example")
  539. /// .setup(|app, api| {
  540. /// app.manage(PluginState::default());
  541. ///
  542. /// Ok(())
  543. /// })
  544. /// .build()
  545. /// }
  546. /// ```
  547. #[must_use]
  548. pub fn setup<F>(mut self, setup: F) -> Self
  549. where
  550. F: FnOnce(&AppHandle<R>, PluginApi<R, C>) -> Result<()> + Send + 'static,
  551. {
  552. self.setup.replace(Box::new(setup));
  553. self
  554. }
  555. /// Callback invoked when the webview performs a navigation to a page.
  556. ///
  557. /// # Examples
  558. ///
  559. /// ```rust
  560. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  561. ///
  562. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  563. /// Builder::new("example")
  564. /// .on_page_load(|window, payload| {
  565. /// println!("Loaded URL {} in window {}", payload.url(), window.label());
  566. /// })
  567. /// .build()
  568. /// }
  569. /// ```
  570. #[must_use]
  571. pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
  572. where
  573. F: FnMut(Window<R>, PageLoadPayload) + Send + 'static,
  574. {
  575. self.on_page_load = Box::new(on_page_load);
  576. self
  577. }
  578. /// Callback invoked when the webview is created.
  579. ///
  580. /// # Examples
  581. ///
  582. /// ```rust
  583. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  584. ///
  585. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  586. /// Builder::new("example")
  587. /// .on_webview_ready(|window| {
  588. /// println!("created window {}", window.label());
  589. /// })
  590. /// .build()
  591. /// }
  592. /// ```
  593. #[must_use]
  594. pub fn on_webview_ready<F>(mut self, on_webview_ready: F) -> Self
  595. where
  596. F: FnMut(Window<R>) + Send + 'static,
  597. {
  598. self.on_webview_ready = Box::new(on_webview_ready);
  599. self
  600. }
  601. /// Callback invoked when the event loop receives a new event.
  602. ///
  603. /// # Examples
  604. ///
  605. /// ```rust
  606. /// use tauri::{plugin::{Builder, TauriPlugin}, RunEvent, Runtime};
  607. ///
  608. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  609. /// Builder::new("example")
  610. /// .on_event(|app_handle, event| {
  611. /// match event {
  612. /// RunEvent::ExitRequested { api, .. } => {
  613. /// // Prevents the app from exiting.
  614. /// // This will cause the core thread to continue running in the background even without any open windows.
  615. /// api.prevent_exit();
  616. /// }
  617. /// // Ignore all other cases.
  618. /// _ => {}
  619. /// }
  620. /// })
  621. /// .build()
  622. /// }
  623. /// ```
  624. #[must_use]
  625. pub fn on_event<F>(mut self, on_event: F) -> Self
  626. where
  627. F: FnMut(&AppHandle<R>, &RunEvent) + Send + 'static,
  628. {
  629. self.on_event = Box::new(on_event);
  630. self
  631. }
  632. /// Callback invoked when the plugin is dropped.
  633. ///
  634. /// # Examples
  635. ///
  636. /// ```rust
  637. /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
  638. ///
  639. /// fn init<R: Runtime>() -> TauriPlugin<R> {
  640. /// Builder::new("example")
  641. /// .on_drop(|app| {
  642. /// println!("plugin has been dropped and is no longer running");
  643. /// // you can run cleanup logic here
  644. /// })
  645. /// .build()
  646. /// }
  647. /// ```
  648. #[must_use]
  649. pub fn on_drop<F>(mut self, on_drop: F) -> Self
  650. where
  651. F: FnOnce(AppHandle<R>) + Send + 'static,
  652. {
  653. self.on_drop.replace(Box::new(on_drop));
  654. self
  655. }
  656. /// Builds the [TauriPlugin].
  657. pub fn build(self) -> TauriPlugin<R, C> {
  658. TauriPlugin {
  659. name: self.name,
  660. app: None,
  661. invoke_handler: self.invoke_handler,
  662. setup: self.setup,
  663. js_init_script: self.js_init_script,
  664. on_page_load: self.on_page_load,
  665. on_webview_ready: self.on_webview_ready,
  666. on_event: self.on_event,
  667. on_drop: self.on_drop,
  668. }
  669. }
  670. }
  671. /// Plugin struct that is returned by the [`Builder`]. Should only be constructed through the builder.
  672. pub struct TauriPlugin<R: Runtime, C: DeserializeOwned = ()> {
  673. name: &'static str,
  674. app: Option<AppHandle<R>>,
  675. invoke_handler: Box<InvokeHandler<R>>,
  676. setup: Option<Box<SetupHook<R, C>>>,
  677. js_init_script: Option<String>,
  678. on_page_load: Box<OnPageLoad<R>>,
  679. on_webview_ready: Box<OnWebviewReady<R>>,
  680. on_event: Box<OnEvent<R>>,
  681. on_drop: Option<Box<OnDrop<R>>>,
  682. }
  683. impl<R: Runtime, C: DeserializeOwned> Drop for TauriPlugin<R, C> {
  684. fn drop(&mut self) {
  685. if let (Some(on_drop), Some(app)) = (self.on_drop.take(), self.app.take()) {
  686. on_drop(app);
  687. }
  688. }
  689. }
  690. impl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> {
  691. fn name(&self) -> &'static str {
  692. self.name
  693. }
  694. fn initialize(&mut self, app: &AppHandle<R>, config: JsonValue) -> Result<()> {
  695. self.app.replace(app.clone());
  696. if let Some(s) = self.setup.take() {
  697. (s)(
  698. app,
  699. PluginApi {
  700. name: self.name,
  701. handle: app.clone(),
  702. config: serde_json::from_value(config)?,
  703. },
  704. )?;
  705. }
  706. Ok(())
  707. }
  708. fn initialization_script(&self) -> Option<String> {
  709. self.js_init_script.clone()
  710. }
  711. fn created(&mut self, window: Window<R>) {
  712. (self.on_webview_ready)(window)
  713. }
  714. fn on_page_load(&mut self, window: Window<R>, payload: PageLoadPayload) {
  715. (self.on_page_load)(window, payload)
  716. }
  717. fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {
  718. (self.on_event)(app, event)
  719. }
  720. fn extend_api(&mut self, invoke: Invoke<R>) -> bool {
  721. (self.invoke_handler)(invoke)
  722. }
  723. }
  724. /// Plugin collection type.
  725. #[default_runtime(crate::Wry, wry)]
  726. pub(crate) struct PluginStore<R: Runtime> {
  727. store: HashMap<&'static str, Box<dyn Plugin<R>>>,
  728. }
  729. impl<R: Runtime> fmt::Debug for PluginStore<R> {
  730. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  731. f.debug_struct("PluginStore")
  732. .field("plugins", &self.store.keys())
  733. .finish()
  734. }
  735. }
  736. impl<R: Runtime> Default for PluginStore<R> {
  737. fn default() -> Self {
  738. Self {
  739. store: HashMap::new(),
  740. }
  741. }
  742. }
  743. impl<R: Runtime> PluginStore<R> {
  744. /// Adds a plugin to the store.
  745. ///
  746. /// Returns `true` if a plugin with the same name is already in the store.
  747. pub fn register<P: Plugin<R> + 'static>(&mut self, plugin: P) -> bool {
  748. self.store.insert(plugin.name(), Box::new(plugin)).is_some()
  749. }
  750. /// Removes the plugin with the given name from the store.
  751. pub fn unregister(&mut self, plugin: &'static str) -> bool {
  752. self.store.remove(plugin).is_some()
  753. }
  754. /// Initializes all plugins in the store.
  755. pub(crate) fn initialize(
  756. &mut self,
  757. app: &AppHandle<R>,
  758. config: &PluginConfig,
  759. ) -> crate::Result<()> {
  760. self.store.values_mut().try_for_each(|plugin| {
  761. plugin
  762. .initialize(
  763. app,
  764. config.0.get(plugin.name()).cloned().unwrap_or_default(),
  765. )
  766. .map_err(|e| crate::Error::PluginInitialization(plugin.name().to_string(), e.to_string()))
  767. })
  768. }
  769. /// Generates an initialization script from all plugins in the store.
  770. pub(crate) fn initialization_script(&self) -> String {
  771. self
  772. .store
  773. .values()
  774. .filter_map(|p| p.initialization_script())
  775. .fold(String::new(), |acc, script| {
  776. format!("{acc}\n(function () {{ {script} }})();")
  777. })
  778. }
  779. /// Runs the created hook for all plugins in the store.
  780. pub(crate) fn created(&mut self, window: Window<R>) {
  781. self
  782. .store
  783. .values_mut()
  784. .for_each(|plugin| plugin.created(window.clone()))
  785. }
  786. /// Runs the on_page_load hook for all plugins in the store.
  787. pub(crate) fn on_page_load(&mut self, window: Window<R>, payload: PageLoadPayload) {
  788. self
  789. .store
  790. .values_mut()
  791. .for_each(|plugin| plugin.on_page_load(window.clone(), payload.clone()))
  792. }
  793. /// Runs the on_event hook for all plugins in the store.
  794. pub(crate) fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {
  795. self
  796. .store
  797. .values_mut()
  798. .for_each(|plugin| plugin.on_event(app, event))
  799. }
  800. /// Runs the plugin `extend_api` hook if it exists. Returns whether the invoke message was handled or not.
  801. ///
  802. /// The message is not handled when the plugin exists **and** the command does not.
  803. pub(crate) fn extend_api(&mut self, plugin: &str, invoke: Invoke<R>) -> bool {
  804. if let Some(plugin) = self.store.get_mut(plugin) {
  805. plugin.extend_api(invoke)
  806. } else {
  807. invoke.resolver.reject(format!("plugin {plugin} not found"));
  808. true
  809. }
  810. }
  811. }