Jelajahi Sumber

refactor: enhance event system rust apis (#7996)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
Amr Bashir 1 tahun lalu
induk
melakukan
93c8a77b34

+ 12 - 0
.changes/tauri-event-system-apis.md

@@ -0,0 +1,12 @@
+---
+'tauri': 'major:breaking'
+---
+
+The event system APIS on Rust is recieving a few changes for consistency and quality of life improvements:
+
+- Renamed `Manager::emit_all` to just `Manager::emit` and will now both trigger the events on JS side as well as Rust.
+- Removed `Manager::trigger_global`, use `Manager::emit`
+- Added `Manager::emit_filter`.
+- Removed `Window::emit`, and moved the implementation to `Manager::emit`.
+- Removed `Window::emit_and_trigger` and `Window::trigger`, use `Window::emit` instead.
+- Changed `Window::emit_to` to only trigger the target window listeners so it won't be catched by `Manager::listen_global`

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

@@ -246,7 +246,7 @@ impl<R: Runtime> GlobalWindowEvent<R> {
     &self.event
   }
 
-  /// The window that the menu belongs to.
+  /// The window that the event belongs to.
   pub fn window(&self) -> &Window<R> {
     &self.window
   }

+ 75 - 38
core/tauri/src/event/listener.rs

@@ -2,7 +2,9 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::{Event, EventId};
+use crate::{Runtime, Window};
+
+use super::{EmitArgs, Event, EventId};
 
 use std::{
   boxed::Box,
@@ -15,33 +17,33 @@ use std::{
 };
 
 /// What to do with the pending handler when resolving it?
-enum Pending {
+enum Pending<R: Runtime> {
   Unlisten(EventId),
-  Listen(EventId, String, Handler),
-  Trigger(String, Option<String>, Option<String>),
+  Listen(EventId, String, Handler<R>),
+  Emit(EmitArgs),
 }
 
 /// Stored in [`Listeners`] to be called upon when the event that stored it is triggered.
-struct Handler {
-  window: Option<String>,
+struct Handler<R: Runtime> {
+  window: Option<Window<R>>,
   callback: Box<dyn Fn(Event) + Send>,
 }
 
 /// Holds event handlers and pending event handlers, along with the salts associating them.
-struct InnerListeners {
-  handlers: Mutex<HashMap<String, HashMap<EventId, Handler>>>,
-  pending: Mutex<Vec<Pending>>,
+struct InnerListeners<R: Runtime> {
+  handlers: Mutex<HashMap<String, HashMap<EventId, Handler<R>>>>,
+  pending: Mutex<Vec<Pending<R>>>,
   function_name: &'static str,
   listeners_object_name: &'static str,
   next_event_id: Arc<AtomicU32>,
 }
 
 /// A self-contained event manager.
-pub struct Listeners {
-  inner: Arc<InnerListeners>,
+pub struct Listeners<R: Runtime> {
+  inner: Arc<InnerListeners<R>>,
 }
 
-impl Default for Listeners {
+impl<R: Runtime> Default for Listeners<R> {
   fn default() -> Self {
     Self {
       inner: Arc::new(InnerListeners {
@@ -55,7 +57,7 @@ impl Default for Listeners {
   }
 }
 
-impl Clone for Listeners {
+impl<R: Runtime> Clone for Listeners<R> {
   fn clone(&self) -> Self {
     Self {
       inner: self.inner.clone(),
@@ -63,7 +65,7 @@ impl Clone for Listeners {
   }
 }
 
-impl Listeners {
+impl<R: Runtime> Listeners<R> {
   pub(crate) fn next_event_id(&self) -> EventId {
     self.inner.next_event_id.fetch_add(1, Ordering::Relaxed)
   }
@@ -79,7 +81,7 @@ impl Listeners {
   }
 
   /// Insert a pending event action to the queue.
-  fn insert_pending(&self, action: Pending) {
+  fn insert_pending(&self, action: Pending<R>) {
     self
       .inner
       .pending
@@ -89,7 +91,7 @@ impl Listeners {
   }
 
   /// Finish all pending event actions.
-  fn flush_pending(&self) {
+  fn flush_pending(&self) -> crate::Result<()> {
     let pending = {
       let mut lock = self
         .inner
@@ -102,13 +104,17 @@ impl Listeners {
     for action in pending {
       match action {
         Pending::Unlisten(id) => self.unlisten(id),
-        Pending::Listen(id, event, handler) => self.listen_(id, event, handler),
-        Pending::Trigger(ref event, window, payload) => self.trigger(event, window, payload),
+        Pending::Listen(id, event, handler) => self.listen_with_id(id, event, handler),
+        Pending::Emit(args) => {
+          self.emit(&args)?;
+        }
       }
     }
+
+    Ok(())
   }
 
-  fn listen_(&self, id: EventId, event: String, handler: Handler) {
+  fn listen_with_id(&self, id: EventId, event: String, handler: Handler<R>) {
     match self.inner.handlers.try_lock() {
       Err(_) => self.insert_pending(Pending::Listen(id, event, handler)),
       Ok(mut lock) => {
@@ -117,11 +123,11 @@ impl Listeners {
     }
   }
 
-  /// Adds an event listener for JS events.
+  /// Adds an event listener.
   pub(crate) fn listen<F: Fn(Event) + Send + 'static>(
     &self,
     event: String,
-    window: Option<String>,
+    window: Option<Window<R>>,
     handler: F,
   ) -> EventId {
     let id = self.next_event_id();
@@ -130,16 +136,16 @@ impl Listeners {
       callback: Box::new(handler),
     };
 
-    self.listen_(id, event, handler);
+    self.listen_with_id(id, event, handler);
 
     id
   }
 
-  /// Listen to a JS event and immediately unlisten.
+  /// Listen to an event and immediately unlisten.
   pub(crate) fn once<F: FnOnce(Event) + Send + 'static>(
     &self,
     event: String,
-    window: Option<String>,
+    window: Option<Window<R>>,
     handler: F,
   ) {
     let self_ = self.clone();
@@ -164,19 +170,42 @@ impl Listeners {
     }
   }
 
-  /// Triggers the given global event with its payload.
-  pub(crate) fn trigger(&self, event: &str, window: Option<String>, payload: Option<String>) {
+  /// Emits the given event with its payload based on a filter.
+  pub(crate) fn emit_filter<F>(&self, emit_args: &EmitArgs, filter: Option<F>) -> crate::Result<()>
+  where
+    F: Fn(&Window<R>) -> bool,
+  {
     let mut maybe_pending = false;
     match self.inner.handlers.try_lock() {
-      Err(_) => self.insert_pending(Pending::Trigger(event.to_owned(), window, payload)),
+      Err(_) => self.insert_pending(Pending::Emit(emit_args.clone())),
       Ok(lock) => {
-        if let Some(handlers) = lock.get(event) {
-          for (&id, handler) in handlers {
-            if handler.window.is_none() || window == handler.window {
+        if let Some(handlers) = lock.get(&emit_args.event_name) {
+          let handlers = if let Some(filter) = filter {
+            handlers
+              .iter()
+              .filter(|h| {
+                h.1
+                  .window
+                  .as_ref()
+                  .map(|w| {
+                    // clippy sees this as redundant closure but
+                    // fixing it will result in a compiler error
+                    #[allow(clippy::redundant_closure)]
+                    filter(w)
+                  })
+                  .unwrap_or(false)
+              })
+              .collect::<Vec<_>>()
+          } else {
+            handlers.iter().collect::<Vec<_>>()
+          };
+
+          if !handlers.is_empty() {
+            for (&id, handler) in handlers {
               maybe_pending = true;
               (handler.callback)(self::Event {
                 id,
-                data: payload.clone(),
+                data: emit_args.payload.clone(),
               })
             }
           }
@@ -185,14 +214,22 @@ impl Listeners {
     }
 
     if maybe_pending {
-      self.flush_pending();
+      self.flush_pending()?;
     }
+
+    Ok(())
+  }
+
+  /// Emits the given event with its payload.
+  pub(crate) fn emit(&self, emit_args: &EmitArgs) -> crate::Result<()> {
+    self.emit_filter(emit_args, None::<&dyn Fn(&Window<R>) -> bool>)
   }
 }
 
 #[cfg(test)]
 mod test {
   use super::*;
+  use crate::test::MockRuntime;
   use proptest::prelude::*;
 
   // dummy event handler function
@@ -206,7 +243,7 @@ mod test {
     // check to see if listen() is properly passing keys into the LISTENERS map
     #[test]
     fn listeners_check_key(e in "[a-z]+") {
-      let listeners: Listeners = Default::default();
+      let listeners: Listeners<MockRuntime> = Default::default();
       // clone e as the key
       let key = e.clone();
       // pass e and an dummy func into listen
@@ -222,7 +259,7 @@ mod test {
     // check to see if listen inputs a handler function properly into the LISTENERS map.
     #[test]
     fn listeners_check_fn(e in "[a-z]+") {
-       let listeners: Listeners = Default::default();
+       let listeners: Listeners<MockRuntime> = Default::default();
        // clone e as the key
        let key = e.clone();
        // pass e and an dummy func into listen
@@ -248,11 +285,11 @@ mod test {
     // check to see if on_event properly grabs the stored function from listen.
     #[test]
     fn check_on_event(key in "[a-z]+", d in "[a-z]+") {
-      let listeners: Listeners = Default::default();
-      // call listen with e and the event_fn dummy func
+      let listeners: Listeners<MockRuntime> = Default::default();
+      // call listen with key and the event_fn dummy func
       listeners.listen(key.clone(), None, event_fn);
-      // call on event with e and d.
-      listeners.trigger(&key, None, Some(d));
+      // call on event with key and d.
+      listeners.emit(&EmitArgs { event_name: key.clone(), event: serde_json::to_string(&key).unwrap(), source_window_label: "null".into(), payload: serde_json::to_string(&d).unwrap() })?;
 
       // lock the mutex
       let l = listeners.inner.handlers.lock().unwrap();

+ 82 - 20
core/tauri/src/event/mod.rs

@@ -5,6 +5,7 @@
 mod listener;
 pub(crate) mod plugin;
 pub(crate) use listener::Listeners;
+use serde::Serialize;
 
 /// Checks if an event name is valid.
 pub fn is_event_name_valid(event: &str) -> bool {
@@ -23,11 +24,39 @@ pub fn assert_event_name_is_valid(event: &str) {
 /// Unique id of an event.
 pub type EventId = u32;
 
-/// An event that was triggered.
+/// Serialized emit arguments.
+#[derive(Clone)]
+pub struct EmitArgs {
+  /// Raw event name.
+  pub event_name: String,
+  /// Serialized event name.
+  pub event: String,
+  /// Serialized source window label ("null" for global events)
+  pub source_window_label: String,
+  /// Serialized payload.
+  pub payload: String,
+}
+
+impl EmitArgs {
+  pub fn from<S: Serialize>(
+    event: &str,
+    source_window_label: Option<&str>,
+    payload: S,
+  ) -> crate::Result<Self> {
+    Ok(EmitArgs {
+      event_name: event.into(),
+      event: serde_json::to_string(event)?,
+      source_window_label: serde_json::to_string(&source_window_label)?,
+      payload: serde_json::to_string(&payload)?,
+    })
+  }
+}
+
+/// An event that was emitted.
 #[derive(Debug, Clone)]
 pub struct Event {
   id: EventId,
-  data: Option<String>,
+  data: String,
 }
 
 impl Event {
@@ -37,27 +66,11 @@ impl Event {
   }
 
   /// The event payload.
-  pub fn payload(&self) -> Option<&str> {
-    self.data.as_deref()
+  pub fn payload(&self) -> &str {
+    &self.data
   }
 }
 
-pub fn unlisten_js(listeners_object_name: &str, event_name: &str, event_id: EventId) -> String {
-  format!(
-    "
-      (function () {{
-        const listeners = (window['{listeners_object_name}'] || {{}})['{event_name}']
-        if (listeners) {{
-          const index = window['{listeners_object_name}']['{event_name}'].findIndex(e => e.id === {event_id})
-          if (index > -1) {{
-            window['{listeners_object_name}']['{event_name}'].splice(index, 1)
-          }}
-        }}
-      }})()
-    ",
-  )
-}
-
 pub fn listen_js(
   listeners_object_name: &str,
   event: &str,
@@ -92,3 +105,52 @@ pub fn listen_js(
     },
   )
 }
+
+pub fn emit_js(event_emit_function_name: &str, emit_args: &EmitArgs) -> crate::Result<String> {
+  Ok(format!(
+    "(function () {{ const fn = window['{}']; fn && fn({{event: {}, windowLabel: {}, payload: {}}}) }})()",
+    event_emit_function_name,
+    emit_args.event,
+    emit_args.source_window_label,
+    emit_args.payload
+  ))
+}
+
+pub fn unlisten_js(listeners_object_name: &str, event_name: &str, event_id: EventId) -> String {
+  format!(
+    "
+      (function () {{
+        const listeners = (window['{listeners_object_name}'] || {{}})['{event_name}']
+        if (listeners) {{
+          const index = window['{listeners_object_name}']['{event_name}'].findIndex(e => e.id === {event_id})
+          if (index > -1) {{
+            window['{listeners_object_name}']['{event_name}'].splice(index, 1)
+          }}
+        }}
+      }})()
+    ",
+  )
+}
+
+pub fn event_initialization_script(function: &str, listeners: &str) -> String {
+  format!(
+    "
+    Object.defineProperty(window, '{function}', {{
+      value: function (eventData) {{
+        const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || []
+
+        for (let i = listeners.length - 1; i >= 0; i--) {{
+          const listener = listeners[i]
+          if (
+              (listener.windowLabel && listener.windowLabel === eventData.windowLabel) ||
+              (!listener.windowLabel && (listener.windowLabel === null || eventData.windowLabel === null))
+            ) {{
+            eventData.id = listener.id
+            listener.handler(eventData)
+          }}
+        }}
+      }}
+    }});
+  "
+  )
+}

+ 9 - 18
core/tauri/src/event/plugin.rs

@@ -37,6 +37,12 @@ impl<'de> Deserialize<'de> for EventName {
 
 pub struct WindowLabel(String);
 
+impl AsRef<str> for WindowLabel {
+  fn as_ref(&self) -> &str {
+    &self.0
+  }
+}
+
 impl<'de> Deserialize<'de> for WindowLabel {
   fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
   where
@@ -75,25 +81,10 @@ pub fn emit<R: Runtime>(
   window_label: Option<WindowLabel>,
   payload: Option<JsonValue>,
 ) -> Result<()> {
-  // dispatch the event to Rust listeners
-  window.trigger(
-    &event.0,
-    payload.as_ref().and_then(|p| {
-      serde_json::to_string(&p)
-        .map_err(|e| {
-          #[cfg(debug_assertions)]
-          eprintln!("{e}");
-          e
-        })
-        .ok()
-    }),
-  );
-
-  // emit event to JS
-  if let Some(target) = window_label {
-    window.emit_to(&target.0, &event.0, payload)
+  if let Some(label) = window_label {
+    window.emit_filter(&event.0, payload, |w| label.as_ref() == w.label())
   } else {
-    window.emit_all(&event.0, payload)
+    window.emit(&event.0, payload)
   }
 }
 

+ 73 - 91
core/tauri/src/lib.rs

@@ -546,67 +546,7 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
     self.manager().package_info()
   }
 
-  /// Emits an 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 windows matching the filter critera.
-  ///
-  /// # Examples
-  /// ```
-  /// use tauri::Manager;
-  ///
-  /// #[tauri::command]
-  /// fn synchronize(app: tauri::AppHandle) {
-  ///   // emits the synchronized event to all windows
-  ///   app.emit_filter("synchronized", (), |w| w.label().starts_with("foo-"));
-  /// }
-  /// ```
-  fn emit_filter<S, F>(&self, event: &str, payload: S, filter: F) -> Result<()>
-  where
-    S: Serialize + Clone,
-    F: Fn(&Window<R>) -> bool,
-  {
-    self.manager().emit_filter(event, None, payload, filter)
-  }
-
-  /// 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 event triggered on any window ([`Window::trigger`] or [`Window::emit_and_trigger`]) or with [`Self::trigger_global`].
+  /// Listen to an event emitted on any window.
   ///
   /// # Examples
   /// ```
@@ -615,7 +555,7 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
   /// #[tauri::command]
   /// fn synchronize(window: tauri::Window) {
   ///   // emits the synchronized event to all windows
-  ///   window.emit_and_trigger("synchronized", ());
+  ///   window.emit("synchronized", ());
   /// }
   ///
   /// tauri::Builder::default()
@@ -634,6 +574,34 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
     self.manager().listen(event.into(), None, handler)
   }
 
+  /// Remove an event listener.
+  ///
+  /// # Examples
+  /// ```
+  /// use tauri::Manager;
+  ///
+  /// tauri::Builder::default()
+  ///   .setup(|app| {
+  ///     let handle = app.handle().clone();
+  ///     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, id: EventId) {
+    self.manager().unlisten(id)
+  }
+
   /// Listen to a global event only once.
   ///
   /// See [`Self::listen_global`] for more information.
@@ -644,12 +612,27 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
     self.manager().once(event.into(), None, handler)
   }
 
-  /// 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`].
+  /// Emits an event to all windows.
+  ///
+  /// If using [`Window`] to emit the event, it will be used as the source.
+  ///
+  /// # Examples
+  /// ```
+  /// use tauri::Manager;
+  ///
+  /// #[tauri::command]
+  /// fn synchronize(app: tauri::AppHandle) {
+  ///   // emits the synchronized event to all windows
+  ///   app.emit("synchronized", ());
+  /// }
+  /// ```
+  fn emit<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {
+    self.manager().emit(event, None, payload)
+  }
+
+  /// Emits an event to the window with the specified label.
   ///
-  /// A global event does not have a source or target window attached.
+  /// If using [`Window`] to emit the event, it will be used as the source.
   ///
   /// # Examples
   /// ```
@@ -659,41 +642,40 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
   /// 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()));
+  ///     // emit a download progress event to the updater window
+  ///     app.emit_to("updater", "download-progress", i);
   ///   }
   /// }
   /// ```
-  fn trigger_global(&self, event: &str, data: Option<String>) {
-    self.manager().trigger(event, None, data)
+  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())
   }
 
-  /// Remove an event listener.
+  /// Emits an event to specific windows based on a filter.
+  ///
+  /// If using [`Window`] to emit the event, it will be used as the source.
   ///
   /// # Examples
   /// ```
   /// use tauri::Manager;
   ///
-  /// tauri::Builder::default()
-  ///   .setup(|app| {
-  ///     let handle = app.handle().clone();
-  ///     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(())
-  ///   });
+  /// #[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_filter("download-progress", i, |w| w.label() == "main" );
+  ///   }
+  /// }
   /// ```
-  fn unlisten(&self, id: EventId) {
-    self.manager().unlisten(id)
+  fn emit_filter<S, F>(&self, event: &str, payload: S, filter: F) -> Result<()>
+  where
+    S: Serialize + Clone,
+    F: Fn(&Window<R>) -> bool,
+  {
+    self.manager().emit_filter(event, None, payload, filter)
   }
 
   /// Fetch a single window from the manager.

+ 240 - 61
core/tauri/src/manager.rs

@@ -27,7 +27,7 @@ use tauri_utils::{
   html::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN},
 };
 
-use crate::window::WindowEmitArgs;
+use crate::event::EmitArgs;
 use crate::{
   app::{
     AppHandle, GlobalWindowEvent, GlobalWindowEventListener, OnPageLoad, PageLoadPayload,
@@ -225,7 +225,7 @@ fn replace_csp_nonce(
 pub struct InnerWindowManager<R: Runtime> {
   pub(crate) windows: Mutex<HashMap<String, Window<R>>>,
   pub(crate) plugins: Mutex<PluginStore<R>>,
-  listeners: Listeners,
+  listeners: Listeners<R>,
   pub(crate) state: Arc<StateManager>,
 
   /// The JS message handler.
@@ -847,7 +847,10 @@ impl<R: Runtime> WindowManager<R> {
       }
       .render_default(&Default::default())?
       .into_string(),
-      event_initialization_script: &self.event_initialization_script(),
+      event_initialization_script: &crate::event::event_initialization_script(
+        self.listeners().function_name(),
+        self.listeners().listeners_object_name(),
+      ),
       plugin_initialization_script,
       freeze_prototype,
     }
@@ -856,29 +859,7 @@ impl<R: Runtime> WindowManager<R> {
     .map_err(Into::into)
   }
 
-  fn event_initialization_script(&self) -> String {
-    format!(
-      "
-      Object.defineProperty(window, '{function}', {{
-        value: function (eventData) {{
-          const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || []
-
-          for (let i = listeners.length - 1; i >= 0; i--) {{
-            const listener = listeners[i]
-            if (listener.windowLabel === null || eventData.windowLabel === null || listener.windowLabel === eventData.windowLabel) {{
-              eventData.id = listener.id
-              listener.handler(eventData)
-            }}
-          }}
-        }}
-      }});
-    ",
-      function = self.listeners().function_name(),
-      listeners = self.listeners().listeners_object_name()
-    )
-  }
-
-  pub(crate) fn listeners(&self) -> &Listeners {
+  pub(crate) fn listeners(&self) -> &Listeners<R> {
     &self.inner.listeners
   }
 }
@@ -1146,26 +1127,6 @@ impl<R: Runtime> WindowManager<R> {
     self.windows_lock().remove(label);
   }
 
-  pub fn emit_filter<S, F>(
-    &self,
-    event: &str,
-    source_window_label: Option<&str>,
-    payload: S,
-    filter: F,
-  ) -> crate::Result<()>
-  where
-    S: Serialize + Clone,
-    F: Fn(&Window<R>) -> bool,
-  {
-    let emit_args = WindowEmitArgs::from(event, source_window_label, payload)?;
-    assert_event_name_is_valid(event);
-    self
-      .windows()
-      .values()
-      .filter(|&w| filter(w))
-      .try_for_each(|window| window.emit_internal(&emit_args))
-  }
-
   pub fn eval_script_all<S: Into<String>>(&self, script: S) -> crate::Result<()> {
     let script = script.into();
     self
@@ -1186,21 +1147,20 @@ impl<R: Runtime> WindowManager<R> {
     &self.inner.package_info
   }
 
-  pub fn trigger(&self, event: &str, window: Option<String>, data: Option<String>) {
-    assert_event_name_is_valid(event);
-    self.listeners().trigger(event, window, data)
-  }
-
   pub fn listen<F: Fn(Event) + Send + 'static>(
     &self,
     event: String,
-    window: Option<String>,
+    window: Option<Window<R>>,
     handler: F,
   ) -> EventId {
     assert_event_name_is_valid(&event);
     self.listeners().listen(event, window, handler)
   }
 
+  pub fn unlisten(&self, id: EventId) {
+    self.listeners().unlisten(id)
+  }
+
   pub fn once<F: FnOnce(Event) + Send + 'static>(
     &self,
     event: String,
@@ -1208,19 +1168,63 @@ impl<R: Runtime> WindowManager<R> {
     handler: F,
   ) {
     assert_event_name_is_valid(&event);
-    self.listeners().once(event, window, handler)
+    self
+      .listeners()
+      .once(event, window.and_then(|w| self.get_window(&w)), handler)
   }
 
-  pub fn event_listeners_object_name(&self) -> &str {
-    self.inner.listeners.listeners_object_name()
-  }
+  pub fn emit_filter<S, F>(
+    &self,
+    event: &str,
+    source_window_label: Option<&str>,
+    payload: S,
+    filter: F,
+  ) -> crate::Result<()>
+  where
+    S: Serialize + Clone,
+    F: Fn(&Window<R>) -> bool,
+  {
+    assert_event_name_is_valid(event);
+
+    let emit_args = EmitArgs::from(event, source_window_label, payload)?;
+
+    self
+      .windows_lock()
+      .values()
+      .filter(|w| {
+        w.has_js_listener(None, event)
+          || w.has_js_listener(source_window_label.map(Into::into), event)
+      })
+      .filter(|w| filter(w))
+      .try_for_each(|window| window.emit_js(&emit_args))?;
+
+    self.listeners().emit_filter(&emit_args, Some(filter))?;
 
-  pub fn event_emit_function_name(&self) -> &str {
-    self.inner.listeners.function_name()
+    Ok(())
   }
 
-  pub fn unlisten(&self, id: EventId) {
-    self.listeners().unlisten(id)
+  pub fn emit<S: Serialize + Clone>(
+    &self,
+    event: &str,
+    source_window_label: Option<&str>,
+    payload: S,
+  ) -> crate::Result<()> {
+    assert_event_name_is_valid(event);
+
+    let emit_args = EmitArgs::from(event, source_window_label, payload)?;
+
+    self
+      .windows_lock()
+      .values()
+      .filter(|w| {
+        w.has_js_listener(None, event)
+          || w.has_js_listener(source_window_label.map(Into::into), event)
+      })
+      .try_for_each(|window| window.emit_js(&emit_args))?;
+
+    self.listeners().emit(&emit_args)?;
+
+    Ok(())
   }
 
   pub fn get_window(&self, label: &str) -> Option<Window<R>> {
@@ -1346,10 +1350,25 @@ mod tests {
 
 #[cfg(test)]
 mod test {
-  use crate::{generate_context, plugin::PluginStore, StateManager, Wry};
+  use std::{
+    sync::mpsc::{channel, Receiver, Sender},
+    time::Duration,
+  };
+
+  use crate::{
+    generate_context,
+    plugin::PluginStore,
+    test::{mock_app, MockRuntime},
+    App, Manager, StateManager, Window, WindowBuilder, Wry,
+  };
 
   use super::WindowManager;
 
+  const WINDOW_LISTEN_ID: &str = "Window::listen";
+  const WINDOW_LISTEN_GLOBAL_ID: &str = "Window::listen_global";
+  const APP_LISTEN_GLOBAL_ID: &str = "App::listen_global";
+  const TEST_EVENT_NAME: &str = "event";
+
   #[test]
   fn check_get_url() {
     let context = generate_context!("test/fixture/src-tauri/tauri.conf.json", crate);
@@ -1380,4 +1399,164 @@ mod test {
     #[cfg(dev)]
     assert_eq!(manager.get_url().to_string(), "http://localhost:4000/");
   }
+
+  struct EventSetup {
+    app: App<MockRuntime>,
+    window: Window<MockRuntime>,
+    tx: Sender<(&'static str, String)>,
+    rx: Receiver<(&'static str, String)>,
+  }
+
+  fn setup_events() -> EventSetup {
+    let app = mock_app();
+    let window = WindowBuilder::new(&app, "main", Default::default())
+      .build()
+      .unwrap();
+
+    let (tx, rx) = channel();
+
+    let tx_ = tx.clone();
+    window.listen(TEST_EVENT_NAME, move |evt| {
+      tx_
+        .send((
+          WINDOW_LISTEN_ID,
+          serde_json::from_str::<String>(evt.payload()).unwrap(),
+        ))
+        .unwrap();
+    });
+
+    let tx_ = tx.clone();
+    window.listen_global(TEST_EVENT_NAME, move |evt| {
+      tx_
+        .send((
+          WINDOW_LISTEN_GLOBAL_ID,
+          serde_json::from_str::<String>(evt.payload()).unwrap(),
+        ))
+        .unwrap();
+    });
+
+    let tx_ = tx.clone();
+    app.listen_global(TEST_EVENT_NAME, move |evt| {
+      tx_
+        .send((
+          APP_LISTEN_GLOBAL_ID,
+          serde_json::from_str::<String>(evt.payload()).unwrap(),
+        ))
+        .unwrap();
+    });
+
+    EventSetup {
+      app,
+      window,
+      tx,
+      rx,
+    }
+  }
+
+  fn assert_events(received: &[&str], expected: &[&str]) {
+    for e in expected {
+      assert!(received.contains(e), "{e} did not receive global event");
+    }
+    assert_eq!(
+      received.len(),
+      expected.len(),
+      "received {:?} events but expected {:?}",
+      received,
+      expected
+    );
+  }
+
+  #[test]
+  fn app_global_events() {
+    let EventSetup {
+      app,
+      window: _,
+      tx: _,
+      rx,
+    } = setup_events();
+
+    let mut received = Vec::new();
+    let payload = "global-payload";
+    app.emit(TEST_EVENT_NAME, payload).unwrap();
+    while let Ok((source, p)) = rx.recv_timeout(Duration::from_secs(1)) {
+      assert_eq!(p, payload);
+      received.push(source);
+    }
+    assert_events(
+      &received,
+      &[
+        WINDOW_LISTEN_ID,
+        WINDOW_LISTEN_GLOBAL_ID,
+        APP_LISTEN_GLOBAL_ID,
+      ],
+    );
+  }
+
+  #[test]
+  fn window_global_events() {
+    let EventSetup {
+      app: _,
+      window,
+      tx: _,
+      rx,
+    } = setup_events();
+
+    let mut received = Vec::new();
+    let payload = "global-payload";
+    window.emit(TEST_EVENT_NAME, payload).unwrap();
+    while let Ok((source, p)) = rx.recv_timeout(Duration::from_secs(1)) {
+      assert_eq!(p, payload);
+      received.push(source);
+    }
+    assert_events(
+      &received,
+      &[
+        WINDOW_LISTEN_ID,
+        WINDOW_LISTEN_GLOBAL_ID,
+        APP_LISTEN_GLOBAL_ID,
+      ],
+    );
+  }
+
+  #[test]
+  fn window_local_events() {
+    let EventSetup {
+      app,
+      window,
+      tx,
+      rx,
+    } = setup_events();
+
+    let mut received = Vec::new();
+    let payload = "global-payload";
+    window
+      .emit_to(window.label(), TEST_EVENT_NAME, payload)
+      .unwrap();
+    while let Ok((source, p)) = rx.recv_timeout(Duration::from_secs(1)) {
+      assert_eq!(p, payload);
+      received.push(source);
+    }
+    assert_events(&received, &[WINDOW_LISTEN_ID]);
+
+    received.clear();
+    let other_window_listen_id = "OtherWindow::listen";
+    let other_window = WindowBuilder::new(&app, "other", Default::default())
+      .build()
+      .unwrap();
+    other_window.listen(TEST_EVENT_NAME, move |evt| {
+      tx.send((
+        other_window_listen_id,
+        serde_json::from_str::<String>(evt.payload()).unwrap(),
+      ))
+      .unwrap();
+    });
+    window
+      .emit_to(other_window.label(), TEST_EVENT_NAME, payload)
+      .unwrap();
+    while let Ok((source, p)) = rx.recv_timeout(Duration::from_secs(1)) {
+      assert_eq!(p, payload);
+      received.push(source);
+    }
+    assert_events(&received, &[other_window_listen_id]);
+  }
 }

+ 5 - 5
core/tauri/src/scope/fs.rs

@@ -180,7 +180,7 @@ impl Scope {
     self.event_listeners.lock().unwrap().remove(&id);
   }
 
-  fn trigger(&self, event: Event) {
+  fn emit(&self, event: Event) {
     let listeners = self.event_listeners.lock().unwrap();
     let handlers = listeners.values();
     for listener in handlers {
@@ -204,7 +204,7 @@ impl Scope {
         escaped_pattern_with(p, if recursive { "**" } else { "*" })
       })?;
     }
-    self.trigger(Event::PathAllowed(path.to_path_buf()));
+    self.emit(Event::PathAllowed(path.to_path_buf()));
     Ok(())
   }
 
@@ -218,7 +218,7 @@ impl Scope {
       path,
       escaped_pattern,
     )?;
-    self.trigger(Event::PathAllowed(path.to_path_buf()));
+    self.emit(Event::PathAllowed(path.to_path_buf()));
     Ok(())
   }
 
@@ -237,7 +237,7 @@ impl Scope {
         escaped_pattern_with(p, if recursive { "**" } else { "*" })
       })?;
     }
-    self.trigger(Event::PathForbidden(path.to_path_buf()));
+    self.emit(Event::PathForbidden(path.to_path_buf()));
     Ok(())
   }
 
@@ -251,7 +251,7 @@ impl Scope {
       path,
       escaped_pattern,
     )?;
-    self.trigger(Event::PathForbidden(path.to_path_buf()));
+    self.emit(Event::PathForbidden(path.to_path_buf()));
     Ok(())
   }
 

+ 24 - 113
core/tauri/src/window/mod.rs

@@ -15,7 +15,7 @@ use crate::TitleBarStyle;
 use crate::{
   app::{AppHandle, UriSchemeResponder},
   command::{CommandArg, CommandItem},
-  event::{Event, EventId},
+  event::{EmitArgs, Event, EventId},
   ipc::{
     CallbackFn, Invoke, InvokeBody, InvokeError, InvokeMessage, InvokeResolver,
     OwnedInvokeResponder,
@@ -71,26 +71,6 @@ struct WindowCreatedEvent {
   label: String,
 }
 
-pub(crate) struct WindowEmitArgs {
-  pub event: String,
-  pub source_window_label: String,
-  pub payload: String,
-}
-
-impl WindowEmitArgs {
-  pub fn from<S: Serialize>(
-    event: &str,
-    source_window_label: Option<&str>,
-    payload: S,
-  ) -> crate::Result<Self> {
-    Ok(WindowEmitArgs {
-      event: serde_json::to_string(event)?,
-      source_window_label: serde_json::to_string(&source_window_label)?,
-      payload: serde_json::to_string(&payload)?,
-    })
-  }
-}
-
 /// Monitor descriptor.
 #[derive(Debug, Clone, Serialize)]
 #[serde(rename_all = "camelCase")]
@@ -976,6 +956,11 @@ impl<R: Runtime> PartialEq for Window<R> {
 }
 
 impl<R: Runtime> Manager<R> for Window<R> {
+  fn emit<S: Serialize + Clone>(&self, event: &str, payload: S) -> crate::Result<()> {
+    self.manager().emit(event, Some(self.label()), payload)?;
+    Ok(())
+  }
+
   fn emit_to<S: Serialize + Clone>(
     &self,
     label: &str,
@@ -987,10 +972,14 @@ impl<R: Runtime> Manager<R> for Window<R> {
       .emit_filter(event, Some(self.label()), payload, |w| label == w.label())
   }
 
-  fn emit_all<S: Serialize + Clone>(&self, event: &str, payload: S) -> crate::Result<()> {
+  fn emit_filter<S, F>(&self, event: &str, payload: S, filter: F) -> crate::Result<()>
+  where
+    S: Serialize + Clone,
+    F: Fn(&Window<R>) -> bool,
+  {
     self
       .manager()
-      .emit_filter(event, Some(self.label()), payload, |_| true)
+      .emit_filter(event, Some(self.label()), payload, filter)
   }
 }
 impl<R: Runtime> ManagerBase<R> for Window<R> {
@@ -2337,6 +2326,14 @@ impl<R: Runtime> Window<R> {
     Ok(())
   }
 
+  pub(crate) fn emit_js(&self, emit_args: &EmitArgs) -> crate::Result<()> {
+    self.eval(&crate::event::emit_js(
+      self.manager().listeners().function_name(),
+      emit_args,
+    )?)?;
+    Ok(())
+  }
+
   /// Whether this window registered a listener to an event from the given window and event name.
   pub(crate) fn has_js_listener(&self, window_label: Option<String>, event: &str) -> bool {
     self
@@ -2445,75 +2442,8 @@ 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,
-    payload: S,
-  ) -> crate::Result<()> {
-    self.trigger(event, Some(serde_json::to_string(&payload)?));
-    self.emit(event, payload)
-  }
-
-  pub(crate) fn emit_internal(&self, emit_args: &WindowEmitArgs) -> crate::Result<()> {
-    self.eval(&format!(
-      "(function () {{ const fn = window['{}']; fn && fn({{event: {}, windowLabel: {}, payload: {}}}) }})()",
-      self.manager.event_emit_function_name(),
-      emit_args.event,
-      emit_args.source_window_label,
-      emit_args.payload
-    ))?;
-    Ok(())
-  }
-
-  /// Emits an event to the JavaScript listeners on the current window or globally.
-  ///
-  /// # 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
-      .emit_filter(event, Some(self.label()), payload, |w| {
-        w.has_js_listener(None, event) || w.has_js_listener(Some(self.label().into()), event)
-      })?;
-    Ok(())
-  }
-
   /// Listen to an event on this window.
   ///
-  /// 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 `emit` function from the window plugin (`@tauri-apps/api/window` package).
-  ///
   /// # Examples
   /// ```
   /// use tauri::Manager;
@@ -2532,8 +2462,9 @@ impl<R: Runtime> Window<R> {
   where
     F: Fn(Event) + Send + 'static,
   {
-    let label = self.window.label.clone();
-    self.manager.listen(event.into(), Some(label), handler)
+    self
+      .manager
+      .listen(event.into(), Some(self.clone()), handler)
   }
 
   /// Unlisten to an event on this window.
@@ -2565,7 +2496,7 @@ impl<R: Runtime> Window<R> {
     self.manager.unlisten(id)
   }
 
-  /// Listen to an event on this window a single time.
+  /// Listen to an event on this window only once.
   ///
   /// See [`Self::listen`] for more information.
   pub fn once<F>(&self, event: impl Into<String>, handler: F)
@@ -2575,26 +2506,6 @@ impl<R: Runtime> Window<R> {
     let label = self.window.label.clone();
     self.manager.once(event.into(), Some(label), handler)
   }
-
-  /// Triggers an event to the Rust listeners on this window or global listeners.
-  ///
-  /// # 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)
-  }
 }
 
 /// The [`WindowEffectsConfig`] object builder