ソースを参照

fix(core): global events now reaches window listeners, closes #4493 (#7163)

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
Lucas Fernandes Nogueira 2 年 前
コミット
696d77c3ce

+ 5 - 0
.changes/fix-global-event-on-local-listener.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch:bug
+---
+
+Fixes global events not being received on window-specific event listeners.

+ 95 - 3
core/tauri/src/lib.rs

@@ -611,18 +611,66 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
   }
 
   /// Emits a event to all windows.
+  ///
+  /// Only the webviews receives this event.
+  /// To trigger Rust listeners, use [`Self::trigger_global`], [`Window::trigger`] or [`Window::emit_and_trigger`].
+  ///
+  /// # Examples
+  /// ```
+  /// use tauri::Manager;
+  ///
+  /// #[tauri::command]
+  /// fn synchronize(app: tauri::AppHandle) {
+  ///   // emits the synchronized event to all windows
+  ///   app.emit_all("synchronized", ());
+  /// }
+  /// ```
   fn emit_all<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {
     self.manager().emit_filter(event, None, payload, |_| true)
   }
 
-  /// Emits an event to a window with the specified label.
+  /// Emits an event to the window with the specified label.
+  ///
+  /// # Examples
+  /// ```
+  /// use tauri::Manager;
+  ///
+  /// #[tauri::command]
+  /// fn download(app: tauri::AppHandle) {
+  ///   for i in 1..100 {
+  ///     std::thread::sleep(std::time::Duration::from_millis(150));
+  ///     // emit a download progress event to the updater window
+  ///     app.emit_to("updater", "download-progress", i);
+  ///   }
+  /// }
+  /// ```
   fn emit_to<S: Serialize + Clone>(&self, label: &str, event: &str, payload: S) -> Result<()> {
     self
       .manager()
       .emit_filter(event, None, payload, |w| label == w.label())
   }
 
-  /// Listen to a global event.
+  /// Listen to a event triggered on any window ([`Window::trigger`] or [`Window::emit_and_trigger`]) or with [`Self::trigger_global`].
+  ///
+  /// # Examples
+  /// ```
+  /// use tauri::Manager;
+  ///
+  /// #[tauri::command]
+  /// fn synchronize(window: tauri::Window) {
+  ///   // emits the synchronized event to all windows
+  ///   window.emit_and_trigger("synchronized", ());
+  /// }
+  ///
+  /// tauri::Builder::default()
+  ///   .setup(|app| {
+  ///     app.listen_global("synchronized", |event| {
+  ///       println!("app is in sync");
+  ///     });
+  ///     Ok(())
+  ///   })
+  ///   .invoke_handler(tauri::generate_handler![synchronize]);
+  /// ```
   fn listen_global<F>(&self, event: impl Into<String>, handler: F) -> EventHandler
   where
     F: Fn(Event) + Send + 'static,
@@ -631,6 +679,8 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
   }
 
   /// Listen to a global event only once.
+  ///
+  /// See [`Self::listen_global`] for more information.
   fn once_global<F>(&self, event: impl Into<String>, handler: F) -> EventHandler
   where
     F: FnOnce(Event) + Send + 'static,
@@ -638,12 +688,54 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
     self.manager().once(event.into(), None, handler)
   }
 
-  /// Trigger a global event.
+  /// Trigger a global event to Rust listeners.
+  /// To send the events to the webview, see [`Self::emit_all`] and [`Self::emit_to`].
+  /// To trigger listeners registed on an specific window, see [`Window::trigger`].
+  /// To trigger all listeners, see [`Window::emit_and_trigger`].
+  ///
+  /// A global event does not have a source or target window attached.
+  ///
+  /// # Examples
+  /// ```
+  /// use tauri::Manager;
+  ///
+  /// #[tauri::command]
+  /// fn download(app: tauri::AppHandle) {
+  ///   for i in 1..100 {
+  ///     std::thread::sleep(std::time::Duration::from_millis(150));
+  ///     // emit a download progress event to all listeners registed in Rust
+  ///     app.trigger_global("download-progress", Some(i.to_string()));
+  ///   }
+  /// }
+  /// ```
   fn trigger_global(&self, event: &str, data: Option<String>) {
     self.manager().trigger(event, None, data)
   }
 
   /// Remove an event listener.
+  ///
+  /// # Examples
+  /// ```
+  /// use tauri::Manager;
+  ///
+  /// tauri::Builder::default()
+  ///   .setup(|app| {
+  ///     let handle = app.handle();
+  ///     let handler = app.listen_global("ready", move |event| {
+  ///       println!("app is ready");
+  ///
+  ///       // we no longer need to listen to the event
+  ///       // we also could have used `app.once_global` instead
+  ///       handle.unlisten(event.id());
+  ///     });
+  ///
+  ///     // stop listening to the event when you do not need it anymore
+  ///     app.unlisten(handler);
+  ///
+  ///
+  ///     Ok(())
+  ///   });
+  /// ```
   fn unlisten(&self, handler_id: EventHandler) {
     self.manager().unlisten(handler_id)
   }

+ 1 - 1
core/tauri/src/manager.rs

@@ -839,7 +839,7 @@ impl<R: Runtime> WindowManager<R> {
 
           for (let i = listeners.length - 1; i >= 0; i--) {{
             const listener = listeners[i]
-            if (listener.windowLabel === null || listener.windowLabel === eventData.windowLabel) {{
+            if (listener.windowLabel === null || eventData.windowLabel === null || listener.windowLabel === eventData.windowLabel) {{
               eventData.id = listener.id
               listener.handler(eventData)
             }}

+ 87 - 4
core/tauri/src/window.rs

@@ -1737,6 +1737,24 @@ impl<R: Runtime> Window<R> {
 /// Event system APIs.
 impl<R: Runtime> Window<R> {
   /// Emits an event to both the JavaScript and the Rust listeners.
+  ///
+  /// This API is a combination of [`Self::trigger`] and [`Self::emit`].
+  ///
+  /// # Examples
+  /// ```
+  /// use tauri::Manager;
+  ///
+  /// #[tauri::command]
+  /// fn download(window: tauri::Window) {
+  ///   window.emit_and_trigger("download-started", ());
+  ///
+  ///   for i in 1..100 {
+  ///     std::thread::sleep(std::time::Duration::from_millis(150));
+  ///     // emit a download progress event to all listeners
+  ///     window.emit_and_trigger("download-progress", i);
+  ///   }
+  /// }
+  /// ```
   pub fn emit_and_trigger<S: Serialize + Clone>(
     &self,
     event: &str,
@@ -1762,9 +1780,21 @@ impl<R: Runtime> Window<R> {
     Ok(())
   }
 
-  /// Emits an event to the JavaScript listeners on the current window.
+  /// Emits an event to the JavaScript listeners on the current window or globally.
   ///
-  /// The event is only delivered to listeners that used the `WebviewWindow#listen` method on the @tauri-apps/api `window` module.
+  /// # Examples
+  /// ```
+  /// use tauri::Manager;
+  ///
+  /// #[tauri::command]
+  /// fn download(window: tauri::Window) {
+  ///   for i in 1..100 {
+  ///     std::thread::sleep(std::time::Duration::from_millis(150));
+  ///     // emit a download progress event to all listeners registed in the webview
+  ///     window.emit("download-progress", i);
+  ///   }
+  /// }
+  /// ```
   pub fn emit<S: Serialize + Clone>(&self, event: &str, payload: S) -> crate::Result<()> {
     self
       .manager
@@ -1779,6 +1809,21 @@ impl<R: Runtime> Window<R> {
   /// This listener only receives events that are triggered using the
   /// [`trigger`](Window#method.trigger) and [`emit_and_trigger`](Window#method.emit_and_trigger) methods or
   /// the `appWindow.emit` function from the @tauri-apps/api `window` module.
+  ///
+  /// # Examples
+  /// ```
+  /// use tauri::Manager;
+  ///
+  /// tauri::Builder::default()
+  ///   .setup(|app| {
+  ///     let window = app.get_window("main").unwrap();
+  ///     window.listen("component-loaded", move |event| {
+  ///       println!("window just loaded a component");
+  ///     });
+  ///
+  ///     Ok(())
+  ///   });
+  /// ```
   pub fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventHandler
   where
     F: Fn(Event) + Send + 'static,
@@ -1788,11 +1833,37 @@ impl<R: Runtime> Window<R> {
   }
 
   /// Unlisten to an event on this window.
+  ///
+  /// # Examples
+  /// ```
+  /// use tauri::Manager;
+  ///
+  /// tauri::Builder::default()
+  ///   .setup(|app| {
+  ///     let window = app.get_window("main").unwrap();
+  ///     let window_ = window.clone();
+  ///     let handler = window.listen("component-loaded", move |event| {
+  ///       println!("window just loaded a component");
+  ///
+  ///       // we no longer need to listen to the event
+  ///       // we also could have used `window.once` instead
+  ///       window_.unlisten(event.id());
+  ///     });
+  ///
+  ///     // stop listening to the event when you do not need it anymore
+  ///     window.unlisten(handler);
+  ///
+  ///
+  ///     Ok(())
+  ///   });
+  /// ```
   pub fn unlisten(&self, handler_id: EventHandler) {
     self.manager.unlisten(handler_id)
   }
 
   /// Listen to an event on this window a single time.
+  ///
+  /// See [`Self::listen`] for more information.
   pub fn once<F>(&self, event: impl Into<String>, handler: F) -> EventHandler
   where
     F: FnOnce(Event) + Send + 'static,
@@ -1801,9 +1872,21 @@ impl<R: Runtime> Window<R> {
     self.manager.once(event.into(), Some(label), handler)
   }
 
-  /// Triggers an event to the Rust listeners on this window.
+  /// Triggers an event to the Rust listeners on this window or global listeners.
   ///
-  /// The event is only delivered to listeners that used the [`listen`](Window#method.listen) method.
+  /// # Examples
+  /// ```
+  /// use tauri::Manager;
+  ///
+  /// #[tauri::command]
+  /// fn download(window: tauri::Window) {
+  ///   for i in 1..100 {
+  ///     std::thread::sleep(std::time::Duration::from_millis(150));
+  ///     // emit a download progress event to all listeners registed on `window` in Rust
+  ///     window.trigger("download-progress", Some(i.to_string()));
+  ///   }
+  /// }
+  /// ```
   pub fn trigger(&self, event: &str, data: Option<String>) {
     let label = self.window.label.clone();
     self.manager.trigger(event, Some(label), data)

ファイルの差分が大きいため隠しています
+ 0 - 0
tooling/api/docs/js-api.json


+ 4 - 3
tooling/api/src/event.ts

@@ -39,7 +39,8 @@ export enum TauriEvent {
 }
 
 /**
- * Listen to an event from the backend.
+ * Listen to an event. The event can be either global or window-specific.
+ * See {@link Event.windowLabel} to check the event source.
  *
  * @example
  * ```typescript
@@ -67,7 +68,7 @@ async function listen<T>(
 }
 
 /**
- * Listen to an one-off event from the backend.
+ * Listen to an one-off event. See {@link listen} for more information.
  *
  * @example
  * ```typescript
@@ -98,7 +99,7 @@ async function once<T>(
 }
 
 /**
- * Emits an event to the backend.
+ * Emits an event to the backend and all Tauri windows.
  * @example
  * ```typescript
  * import { emit } from '@tauri-apps/api/event';

+ 33 - 5
tooling/api/src/window.ts

@@ -326,7 +326,10 @@ class WebviewWindowHandle {
   }
 
   /**
-   * Listen to an event emitted by the backend that is tied to the webview window.
+   * Listen to an event emitted by the backend or webview.
+   * The event must either be a global event or an event targetting this window.
+   *
+   * See {@link WebviewWindow.emit | `emit`} for more information.
    *
    * @example
    * ```typescript
@@ -339,10 +342,11 @@ class WebviewWindowHandle {
    * unlisten();
    * ```
    *
+   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
+   *
    * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
    * @param handler Event handler.
    * @returns A promise resolving to a function to unlisten to the event.
-   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
    */
   async listen<T>(
     event: EventName,
@@ -359,7 +363,8 @@ class WebviewWindowHandle {
   }
 
   /**
-   * Listen to an one-off event emitted by the backend that is tied to the webview window.
+   * Listen to an one-off event.
+   * See {@link WebviewWindow.listen | `listen`} for more information.
    *
    * @example
    * ```typescript
@@ -372,10 +377,11 @@ class WebviewWindowHandle {
    * unlisten();
    * ```
    *
+   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
+   *
    * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
    * @param handler Event handler.
    * @returns A promise resolving to a function to unlisten to the event.
-   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
    */
   async once<T>(event: string, handler: EventCallback<T>): Promise<UnlistenFn> {
     if (this._handleTauriEvent(event, handler)) {
@@ -389,13 +395,35 @@ class WebviewWindowHandle {
   }
 
   /**
-   * Emits an event to the backend, tied to the webview window.
+   * Emits an event to the backend and all Tauri windows.
+   * The event will have this window's {@link WebviewWindow.label | label} as {@link Event.windowLabel | source window label}.
+   *
    * @example
    * ```typescript
    * import { appWindow } from '@tauri-apps/api/window';
    * await appWindow.emit('window-loaded', { loggedIn: true, token: 'authToken' });
    * ```
    *
+   * This function can also be used to communicate between windows:
+   * ```typescript
+   * import { appWindow } from '@tauri-apps/api/window';
+   * await appWindow.listen('sync-data', (event) => { });
+   *
+   * // on another window...
+   * import { WebviewWindow } from '@tauri-apps/api/window';
+   * const otherWindow = WebviewWindow.getByLabel('other')
+   * await otherWindow.emit('sync-data');
+   * ```
+   *
+   * Global listeners are also triggered:
+   * ```typescript
+   * import { appWindow } from '@tauri-apps/api/window';
+   * import { listen } from '@tauri-apps/api/event';
+   * await listen('ping', (event) => { });
+   *
+   * await appWindow.emit('ping');
+   * ```
+   *
    * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
    * @param payload Event payload.
    */

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません