Kaynağa Gözat

refactor(core)!: Window::close triggers RunEvent::CloseRequested (#8710)

* refactor(core): Window::close triggers RunEvent::CloseRequested

* Update .changes/runtime-wry-window-close-event.md

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>

* add destroy [skip ci]

* change files

* delete files

* fix tests

* fix tests

* fix test impl of the close flow

* fmt

* build bundle

---------

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
Lucas Fernandes Nogueira 1 yıl önce
ebeveyn
işleme
af61023273

+ 8 - 0
.changes/destroy-api.md

@@ -0,0 +1,8 @@
+---
+"tauri": patch:feat
+"@tauri-apps/api": patch:feat
+"tauri-runtime": patch:feat
+"tauri-runtime-wry": patch:feat
+---
+
+Added `Window::destroy` to force close a window.

+ 5 - 0
.changes/runtime-wry-window-close-event.md

@@ -0,0 +1,5 @@
+---
+"tauri-runtime-wry": patch:breaking
+---
+
+`WindowDispatch::close` now triggers the `CloseRequested` flow.

+ 6 - 0
.changes/window-close-requested.md

@@ -0,0 +1,6 @@
+---
+"tauri": patch:breaking
+"@tauri-apps/api": patch:breaking
+---
+
+`Window::close` now triggers a close requested event instead of forcing the window to be closed.

+ 16 - 0
core/tauri-runtime-wry/src/lib.rs

@@ -1131,6 +1131,7 @@ pub enum WindowMessage {
   Show,
   Hide,
   Close,
+  Destroy,
   SetDecorations(bool),
   SetShadow(bool),
   SetAlwaysOnBottom(bool),
@@ -1645,6 +1646,15 @@ impl<T: UserEvent> WindowDispatch<T> for WryWindowDispatcher<T> {
       .map_err(|_| Error::FailedToSendMessage)
   }
 
+  fn destroy(&self) -> Result<()> {
+    // NOTE: destroy cannot use the `send_user_message` function because it accesses the event loop callback
+    self
+      .context
+      .proxy
+      .send_event(Message::Window(self.window_id, WindowMessage::Destroy))
+      .map_err(|_| Error::FailedToSendMessage)
+  }
+
   fn set_decorations(&self, decorations: bool) -> Result<()> {
     send_user_message(
       &self.context,
@@ -2543,6 +2553,9 @@ fn handle_user_message<T: UserEvent>(
           WindowMessage::Close => {
             panic!("cannot handle `WindowMessage::Close` on the main thread")
           }
+          WindowMessage::Destroy => {
+            panic!("cannot handle `WindowMessage::Destroy` on the main thread")
+          }
           WindowMessage::SetDecorations(decorations) => window.set_decorations(decorations),
           WindowMessage::SetShadow(_enable) => {
             #[cfg(windows)]
@@ -3018,6 +3031,9 @@ fn handle_event_loop<T: UserEvent>(
         }
       }
       Message::Window(id, WindowMessage::Close) => {
+        on_close_requested(callback, id, windows.clone());
+      }
+      Message::Window(id, WindowMessage::Destroy) => {
         on_window_close(id, windows.clone());
       }
       Message::UserEvent(t) => callback(RunEvent::UserEvent(t)),

+ 3 - 0
core/tauri-runtime/src/lib.rs

@@ -597,6 +597,9 @@ pub trait WindowDispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 's
   /// Closes the window.
   fn close(&self) -> Result<()>;
 
+  /// Destroys the window.
+  fn destroy(&self) -> Result<()>;
+
   /// Updates the decorations flag.
   fn set_decorations(&self, decorations: bool) -> Result<()>;
 

+ 1 - 0
core/tauri/build.rs

@@ -74,6 +74,7 @@ const PLUGINS: &[(&str, &[(&str, bool)])] = &[
       ("show", false),
       ("hide", false),
       ("close", false),
+      ("destroy", false),
       ("set_decorations", false),
       ("set_shadow", false),
       ("set_effects", false),

+ 16 - 0
core/tauri/permissions/window/autogenerated/commands/destroy.toml

@@ -0,0 +1,16 @@
+# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-License-Identifier: MIT
+# Automatically generated - DO NOT EDIT!
+
+"$schema" = "../../../schemas/schema.json"
+
+[[permission]]
+identifier = "allow-destroy"
+description = "Enables the destroy command without any pre-configured scope."
+commands.allow = ["destroy"]
+
+[[permission]]
+identifier = "deny-destroy"
+description = "Denies the destroy command without any pre-configured scope."
+commands.deny = ["destroy"]

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
core/tauri/scripts/bundle.global.js


+ 62 - 16
core/tauri/src/test/mock_runtime.rs

@@ -42,11 +42,13 @@ type ShortcutMap = HashMap<String, Box<dyn Fn() + Send + 'static>>;
 enum Message {
   Task(Box<dyn FnOnce() + Send>),
   CloseWindow(WindowId),
+  DestroyWindow(WindowId),
 }
 
 struct Webview;
 
 struct Window {
+  label: String,
   webviews: Vec<Webview>,
 }
 
@@ -79,7 +81,7 @@ impl RuntimeContext {
     } else {
       match message {
         Message::Task(task) => task(),
-        Message::CloseWindow(id) => {
+        Message::CloseWindow(id) | Message::DestroyWindow(id) => {
           self.windows.borrow_mut().remove(&id);
         }
       }
@@ -136,11 +138,13 @@ impl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {
       (None, Vec::new())
     };
 
-    self
-      .context
-      .windows
-      .borrow_mut()
-      .insert(id, Window { webviews });
+    self.context.windows.borrow_mut().insert(
+      id,
+      Window {
+        label: pending.label.clone(),
+        webviews,
+      },
+    );
 
     let webview = webview_id.map(|id| DetachedWebview {
       label: pending.label.clone(),
@@ -666,11 +670,13 @@ impl<T: UserEvent> WindowDispatch<T> for MockWindowDispatcher {
       (None, Vec::new())
     };
 
-    self
-      .context
-      .windows
-      .borrow_mut()
-      .insert(id, Window { webviews });
+    self.context.windows.borrow_mut().insert(
+      id,
+      Window {
+        label: pending.label.clone(),
+        webviews,
+      },
+    );
 
     let webview = webview_id.map(|id| DetachedWebview {
       label: pending.label.clone(),
@@ -763,6 +769,11 @@ impl<T: UserEvent> WindowDispatch<T> for MockWindowDispatcher {
     Ok(())
   }
 
+  fn destroy(&self) -> Result<()> {
+    self.context.send_message(Message::DestroyWindow(self.id))?;
+    Ok(())
+  }
+
   fn set_decorations(&self, decorations: bool) -> Result<()> {
     Ok(())
   }
@@ -927,11 +938,13 @@ impl<T: UserEvent> Runtime<T> for MockRuntime {
       (None, Vec::new())
     };
 
-    self
-      .context
-      .windows
-      .borrow_mut()
-      .insert(id, Window { webviews });
+    self.context.windows.borrow_mut().insert(
+      id,
+      Window {
+        label: pending.label.clone(),
+        webviews,
+      },
+    );
 
     let webview = webview_id.map(|id| DetachedWebview {
       label: pending.label.clone(),
@@ -1018,6 +1031,39 @@ impl<T: UserEvent> Runtime<T> for MockRuntime {
         match m {
           Message::Task(p) => p(),
           Message::CloseWindow(id) => {
+            let label = self
+              .context
+              .windows
+              .borrow()
+              .get(&id)
+              .map(|w| w.label.clone());
+            if let Some(label) = label {
+              let (tx, rx) = channel();
+              callback(RunEvent::WindowEvent {
+                label,
+                event: WindowEvent::CloseRequested { signal_tx: tx },
+              });
+
+              let should_prevent = matches!(rx.try_recv(), Ok(true));
+              if !should_prevent {
+                self.context.windows.borrow_mut().remove(&id);
+
+                let is_empty = self.context.windows.borrow().is_empty();
+                if is_empty {
+                  let (tx, rx) = channel();
+                  callback(RunEvent::ExitRequested { code: None, tx });
+
+                  let recv = rx.try_recv();
+                  let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent));
+
+                  if !should_prevent {
+                    break;
+                  }
+                }
+              }
+            }
+          }
+          Message::DestroyWindow(id) => {
             let removed = self.context.windows.borrow_mut().remove(&id).is_some();
             if removed {
               let is_empty = self.context.windows.borrow().is_empty();

+ 6 - 7
core/tauri/src/webview/webview_window.rs

@@ -1301,17 +1301,16 @@ impl<R: Runtime> WebviewWindow<R> {
     self.webview.window().hide()
   }
 
-  /// Closes this window.
-  /// # Panics
-  ///
-  /// - Panics if the event loop is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
-  /// - Panics when called on the main thread, usually on the [`run`](crate::App#method.run) closure.
-  ///
-  /// You can spawn a task to use the API using [`crate::async_runtime::spawn`] or [`std::thread::spawn`] to prevent the panic.
+  /// Closes this window. It emits [`crate::RunEvent::CloseRequested`] first like a user-initiated close request so you can intercept it.
   pub fn close(&self) -> crate::Result<()> {
     self.webview.window().close()
   }
 
+  /// Destroys this window. Similar to [`Self::close`] but does not emit any events and force close the window instead.
+  pub fn destroy(&self) -> crate::Result<()> {
+    self.webview.window().destroy()
+  }
+
   /// Determines if this window should be [decorated].
   ///
   /// [decorated]: https://en.wikipedia.org/wiki/Window_(computing)#Window_decoration

+ 6 - 1
core/tauri/src/window/mod.rs

@@ -1676,11 +1676,16 @@ impl<R: Runtime> Window<R> {
     self.window.dispatcher.hide().map_err(Into::into)
   }
 
-  /// Closes this window.
+  /// Closes this window. It emits [`crate::RunEvent::CloseRequested`] first like a user-initiated close request so you can intercept it.
   pub fn close(&self) -> crate::Result<()> {
     self.window.dispatcher.close().map_err(Into::into)
   }
 
+  /// Destroys this window. Similar to [`Self::close`] but does not emit any events and force close the window instead.
+  pub fn destroy(&self) -> crate::Result<()> {
+    self.window.dispatcher.destroy().map_err(Into::into)
+  }
+
   /// Determines if this window should be [decorated].
   ///
   /// [decorated]: https://en.wikipedia.org/wiki/Window_(computing)#Window_decoration

+ 2 - 0
core/tauri/src/window/plugin.rs

@@ -142,6 +142,7 @@ mod desktop_commands {
   setter!(show);
   setter!(hide);
   setter!(close);
+  setter!(destroy);
   setter!(set_decorations, bool);
   setter!(set_shadow, bool);
   setter!(set_effects, Option<WindowEffectsConfig>);
@@ -394,6 +395,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
             desktop_commands::show,
             desktop_commands::hide,
             desktop_commands::close,
+            desktop_commands::destroy,
             desktop_commands::set_decorations,
             desktop_commands::set_shadow,
             desktop_commands::set_effects,

+ 24 - 6
examples/api/src-tauri/src/lib.rs

@@ -153,13 +153,31 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
 
   app.run(move |_app_handle, _event| {
     #[cfg(all(desktop, not(test)))]
-    if let RunEvent::ExitRequested { api, code, .. } = &_event {
-      // Keep the event loop running even if all windows are closed
-      // This allow us to catch tray icon events when there is no window
-      // if we manually requested an exit (code is Some(_)) we will let it go through
-      if code.is_none() {
-        api.prevent_exit();
+    match &_event {
+      RunEvent::ExitRequested { api, code, .. } => {
+        // Keep the event loop running even if all windows are closed
+        // This allow us to catch tray icon events when there is no window
+        // if we manually requested an exit (code is Some(_)) we will let it go through
+        if code.is_none() {
+          api.prevent_exit();
+        }
+      }
+      RunEvent::WindowEvent {
+        event: tauri::WindowEvent::CloseRequested { api, .. },
+        label,
+        ..
+      } => {
+        println!("closing window...");
+        // run the window destroy manually just for fun :)
+        // usually you'd show a dialog here to ask for confirmation or whatever
+        api.prevent_close();
+        _app_handle
+          .get_webview_window(label)
+          .unwrap()
+          .destroy()
+          .unwrap();
       }
+      _ => (),
     }
   })
 }

+ 19 - 1
tooling/api/src/window.ts

@@ -1041,6 +1041,8 @@ class Window {
 
   /**
    * Closes the window.
+   *
+   * Note this emits a closeRequested event so you can intercept it. To force window close, use {@link Window.destroy}.
    * @example
    * ```typescript
    * import { getCurrent } from '@tauri-apps/api/window';
@@ -1055,6 +1057,22 @@ class Window {
     })
   }
 
+  /**
+   * Destroys the window. Behaves like {@link Window.close} but forces the window close instead of emitting a closeRequested event.
+   * @example
+   * ```typescript
+   * import { getCurrent } from '@tauri-apps/api/window';
+   * await getCurrent().destroy();
+   * ```
+   *
+   * @returns A promise indicating the success or failure of the operation.
+   */
+  async destroy(): Promise<void> {
+    return invoke('plugin:window|destroy', {
+      label: this.label
+    })
+  }
+
   /**
    * Whether the window should have borders and bars.
    * @example
@@ -1663,7 +1681,7 @@ class Window {
       const evt = new CloseRequestedEvent(event)
       void Promise.resolve(handler(evt)).then(() => {
         if (!evt.isPreventDefault()) {
-          return this.close()
+          return this.destroy()
         }
       })
     })

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor