Skip to content

Commit be4bb39

Browse files
authored
feat: add AppHandle::remove_plugin and plugin on_drop, closes #4361 (#4443)
1 parent 4b5291d commit be4bb39

File tree

4 files changed

+160
-5
lines changed

4 files changed

+160
-5
lines changed

.changes/plugin-on-drop.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Added `on_drop` hook to the `plugin::Builder`.

.changes/remove-plugin.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Added `AppHandle::remove_plugin`.

core/tauri/src/app.rs

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,31 @@ impl<R: Runtime> AppHandle<R> {
410410
self.runtime_handle.remove_system_tray().map_err(Into::into)
411411
}
412412

413-
/// Adds a plugin to the runtime.
413+
/// Adds a Tauri application plugin.
414+
/// This function can be used to register a plugin that is loaded dynamically e.g. after login.
415+
/// For plugins that are created when the app is started, prefer [`Builder::plugin`].
416+
///
417+
/// See [`Builder::plugin`] for more information.
418+
///
419+
/// # Examples
420+
///
421+
/// ```
422+
/// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, Runtime};
423+
///
424+
/// fn init_plugin<R: Runtime>() -> TauriPlugin<R> {
425+
/// PluginBuilder::new("dummy").build()
426+
/// }
427+
///
428+
/// tauri::Builder::default()
429+
/// .setup(move |app| {
430+
/// let handle = app.handle();
431+
/// std::thread::spawn(move || {
432+
/// handle.plugin(init_plugin());
433+
/// });
434+
///
435+
/// Ok(())
436+
/// });
437+
/// ```
414438
pub fn plugin<P: Plugin<R> + 'static>(&self, mut plugin: P) -> crate::Result<()> {
415439
plugin
416440
.initialize(
@@ -434,6 +458,41 @@ impl<R: Runtime> AppHandle<R> {
434458
Ok(())
435459
}
436460

461+
/// Removes the plugin with the given name.
462+
///
463+
/// # Examples
464+
///
465+
/// ```
466+
/// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin, Plugin}, Runtime};
467+
///
468+
/// fn init_plugin<R: Runtime>() -> TauriPlugin<R> {
469+
/// PluginBuilder::new("dummy").build()
470+
/// }
471+
///
472+
/// let plugin = init_plugin();
473+
/// // `.name()` requires the `PLugin` trait import
474+
/// let plugin_name = plugin.name();
475+
/// tauri::Builder::default()
476+
/// .plugin(plugin)
477+
/// .setup(move |app| {
478+
/// let handle = app.handle();
479+
/// std::thread::spawn(move || {
480+
/// handle.remove_plugin(plugin_name);
481+
/// });
482+
///
483+
/// Ok(())
484+
/// });
485+
/// ```
486+
pub fn remove_plugin(&self, plugin: &'static str) -> bool {
487+
self
488+
.manager()
489+
.inner
490+
.plugins
491+
.lock()
492+
.unwrap()
493+
.unregister(plugin)
494+
}
495+
437496
/// Exits the app. This is the same as [`std::process::exit`], but it performs cleanup on this application.
438497
pub fn exit(&self, exit_code: i32) {
439498
self.cleanup_before_exit();
@@ -940,7 +999,47 @@ impl<R: Runtime> Builder<R> {
940999
self
9411000
}
9421001

943-
/// Adds a plugin to the runtime.
1002+
/// Adds a Tauri application plugin.
1003+
///
1004+
/// A plugin is created using the [`crate::plugin::Builder`] struct.Check its documentation for more information.
1005+
///
1006+
/// # Examples
1007+
///
1008+
/// ```
1009+
/// mod plugin {
1010+
/// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, RunEvent, Runtime};
1011+
///
1012+
/// // this command can be called in the frontend using `invoke('plugin:window|do_something')`.
1013+
/// #[tauri::command]
1014+
/// async fn do_something<R: Runtime>(app: tauri::AppHandle<R>, window: tauri::Window<R>) -> Result<(), String> {
1015+
/// println!("command called");
1016+
/// Ok(())
1017+
/// }
1018+
/// pub fn init<R: Runtime>() -> TauriPlugin<R> {
1019+
/// PluginBuilder::new("window")
1020+
/// .setup(|app| {
1021+
/// // initialize the plugin here
1022+
/// Ok(())
1023+
/// })
1024+
/// .on_event(|app, event| {
1025+
/// match event {
1026+
/// RunEvent::Ready => {
1027+
/// println!("app is ready");
1028+
/// }
1029+
/// RunEvent::WindowEvent { label, event, .. } => {
1030+
/// println!("window {} received an event: {:?}", label, event);
1031+
/// }
1032+
/// _ => (),
1033+
/// }
1034+
/// })
1035+
/// .invoke_handler(tauri::generate_handler![do_something])
1036+
/// .build()
1037+
/// }
1038+
/// }
1039+
///
1040+
/// tauri::Builder::default()
1041+
/// .plugin(plugin::init());
1042+
/// ```
9441043
#[must_use]
9451044
pub fn plugin<P: Plugin<R> + 'static>(mut self, plugin: P) -> Self {
9461045
self.plugins.register(plugin);

core/tauri/src/plugin.rs

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type SetupWithConfigHook<R, T> = dyn FnOnce(&AppHandle<R>, T) -> Result<()> + Se
5959
type OnWebviewReady<R> = dyn FnMut(Window<R>) + Send;
6060
type OnEvent<R> = dyn FnMut(&AppHandle<R>, &RunEvent) + Send;
6161
type OnPageLoad<R> = dyn FnMut(Window<R>, PageLoadPayload) + Send;
62+
type OnDrop<R> = dyn FnOnce(AppHandle<R>) + Send;
6263

6364
/// Builds a [`TauriPlugin`].
6465
///
@@ -141,6 +142,7 @@ pub struct Builder<R: Runtime, C: DeserializeOwned = ()> {
141142
on_page_load: Box<OnPageLoad<R>>,
142143
on_webview_ready: Box<OnWebviewReady<R>>,
143144
on_event: Box<OnEvent<R>>,
145+
on_drop: Option<Box<OnDrop<R>>>,
144146
}
145147

146148
impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
@@ -155,6 +157,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
155157
on_page_load: Box::new(|_, _| ()),
156158
on_webview_ready: Box::new(|_| ()),
157159
on_event: Box::new(|_, _| ()),
160+
on_drop: None,
158161
}
159162
}
160163

@@ -311,7 +314,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
311314
#[must_use]
312315
pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
313316
where
314-
F: FnMut(Window<R>, PageLoadPayload) + Send + Sync + 'static,
317+
F: FnMut(Window<R>, PageLoadPayload) + Send + 'static,
315318
{
316319
self.on_page_load = Box::new(on_page_load);
317320
self
@@ -335,7 +338,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
335338
#[must_use]
336339
pub fn on_webview_ready<F>(mut self, on_webview_ready: F) -> Self
337340
where
338-
F: FnMut(Window<R>) + Send + Sync + 'static,
341+
F: FnMut(Window<R>) + Send + 'static,
339342
{
340343
self.on_webview_ready = Box::new(on_webview_ready);
341344
self
@@ -367,37 +370,74 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
367370
#[must_use]
368371
pub fn on_event<F>(mut self, on_event: F) -> Self
369372
where
370-
F: FnMut(&AppHandle<R>, &RunEvent) + Send + Sync + 'static,
373+
F: FnMut(&AppHandle<R>, &RunEvent) + Send + 'static,
371374
{
372375
self.on_event = Box::new(on_event);
373376
self
374377
}
375378

379+
/// Callback invoked when the plugin is dropped.
380+
///
381+
/// # Examples
382+
///
383+
/// ```rust
384+
/// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
385+
///
386+
/// fn init<R: Runtime>() -> TauriPlugin<R> {
387+
/// Builder::new("example")
388+
/// .on_drop(|app| {
389+
/// println!("plugin has been dropped and is no longer running");
390+
/// // you can run cleanup logic here
391+
/// })
392+
/// .build()
393+
/// }
394+
/// ```
395+
#[must_use]
396+
pub fn on_drop<F>(mut self, on_drop: F) -> Self
397+
where
398+
F: FnOnce(AppHandle<R>) + Send + 'static,
399+
{
400+
self.on_drop.replace(Box::new(on_drop));
401+
self
402+
}
403+
376404
/// Builds the [TauriPlugin].
377405
pub fn build(self) -> TauriPlugin<R, C> {
378406
TauriPlugin {
379407
name: self.name,
408+
app: None,
380409
invoke_handler: self.invoke_handler,
381410
setup: self.setup,
382411
setup_with_config: self.setup_with_config,
383412
js_init_script: self.js_init_script,
384413
on_page_load: self.on_page_load,
385414
on_webview_ready: self.on_webview_ready,
386415
on_event: self.on_event,
416+
on_drop: self.on_drop,
387417
}
388418
}
389419
}
390420

391421
/// Plugin struct that is returned by the [`Builder`]. Should only be constructed through the builder.
392422
pub struct TauriPlugin<R: Runtime, C: DeserializeOwned = ()> {
393423
name: &'static str,
424+
app: Option<AppHandle<R>>,
394425
invoke_handler: Box<InvokeHandler<R>>,
395426
setup: Option<Box<SetupHook<R>>>,
396427
setup_with_config: Option<Box<SetupWithConfigHook<R, C>>>,
397428
js_init_script: Option<String>,
398429
on_page_load: Box<OnPageLoad<R>>,
399430
on_webview_ready: Box<OnWebviewReady<R>>,
400431
on_event: Box<OnEvent<R>>,
432+
on_drop: Option<Box<OnDrop<R>>>,
433+
}
434+
435+
impl<R: Runtime, C: DeserializeOwned> Drop for TauriPlugin<R, C> {
436+
fn drop(&mut self) {
437+
if let (Some(on_drop), Some(app)) = (self.on_drop.take(), self.app.take()) {
438+
on_drop(app);
439+
}
440+
}
401441
}
402442

403443
impl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> {
@@ -406,6 +446,7 @@ impl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> {
406446
}
407447

408448
fn initialize(&mut self, app: &AppHandle<R>, config: JsonValue) -> Result<()> {
449+
self.app.replace(app.clone());
409450
if let Some(s) = self.setup.take() {
410451
(s)(app)?;
411452
}
@@ -466,6 +507,11 @@ impl<R: Runtime> PluginStore<R> {
466507
self.store.insert(plugin.name(), Box::new(plugin)).is_some()
467508
}
468509

510+
/// Removes the plugin with the given name from the store.
511+
pub fn unregister(&mut self, plugin: &'static str) -> bool {
512+
self.store.remove(plugin).is_some()
513+
}
514+
469515
/// Initializes all plugins in the store.
470516
pub(crate) fn initialize(
471517
&mut self,

0 commit comments

Comments
 (0)