소스 검색

allow event listeners to be nested (#1513)

* allow event listeners to be nested

* finish event comment

* remove extraneous into_iter()

* use tag instead of params on event for easier testing
chip 4 년 전
부모
커밋
e447b8e0e6
3개의 변경된 파일133개의 추가작업 그리고 84개의 파일을 삭제
  1. 9 0
      .changes/nested-events.md
  2. 117 77
      core/tauri/src/event.rs
  3. 7 7
      core/tauri/src/runtime/manager.rs

+ 9 - 0
.changes/nested-events.md

@@ -0,0 +1,9 @@
+---
+"tauri": patch
+---
+
+Window and global events can now be nested inside event handlers. They will run as soon
+as the event handler closure is finished in the order they were called. Previously, calling
+events inside an event handler would produce a deadlock.
+
+Note: The order that event handlers are called when triggered is still non-deterministic.

+ 117 - 77
core/tauri/src/event.rs

@@ -41,137 +41,177 @@ impl Event {
   }
 }
 
-/// What happens after the handler is called?
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-enum AfterHandle {
-  /// The handler is removed (once).
-  Remove,
-
-  /// Nothing is done (regular).
-  DoNothing,
+/// What to do with the pending handler when resolving it?
+enum Pending<Event: Tag, Window: Tag> {
+  Unlisten(EventHandler),
+  Listen(EventHandler, Event, Handler<Window>),
+  Trigger(Event, Option<Window>, Option<String>),
 }
 
 /// Stored in [`Listeners`] to be called upon when the event that stored it is triggered.
 struct Handler<Window: Tag> {
   window: Option<Window>,
-  callback: Box<dyn Fn(Event) -> AfterHandle + Send>,
+  callback: Box<dyn Fn(Event) + Send>,
 }
 
 /// A collection of handlers. Multiple handlers can represent the same event.
 type Handlers<Event, Window> = HashMap<Event, HashMap<EventHandler, Handler<Window>>>;
 
-#[derive(Clone)]
-pub(crate) struct Listeners<Event: Tag, Window: Tag> {
-  inner: Arc<Mutex<Handlers<Event, Window>>>,
+/// Holds event handlers and pending event handlers, along with the salts associating them.
+struct InnerListeners<Event: Tag, Window: Tag> {
+  handlers: Mutex<Handlers<Event, Window>>,
+  pending: Mutex<Vec<Pending<Event, Window>>>,
   function_name: Uuid,
   listeners_object_name: Uuid,
   queue_object_name: Uuid,
 }
 
-impl<E: Tag, L: Tag> Default for Listeners<E, L> {
+/// A self-contained event manager.
+pub(crate) struct Listeners<Event: Tag, Window: Tag> {
+  inner: Arc<InnerListeners<Event, Window>>,
+}
+
+impl<Event: Tag, Window: Tag> Default for Listeners<Event, Window> {
   fn default() -> Self {
     Self {
-      inner: Arc::new(Mutex::default()),
-      function_name: Uuid::new_v4(),
-      listeners_object_name: Uuid::new_v4(),
-      queue_object_name: Uuid::new_v4(),
+      inner: Arc::new(InnerListeners {
+        handlers: Mutex::default(),
+        pending: Mutex::default(),
+        function_name: Uuid::new_v4(),
+        listeners_object_name: Uuid::new_v4(),
+        queue_object_name: Uuid::new_v4(),
+      }),
+    }
+  }
+}
+
+impl<Event: Tag, Window: Tag> Clone for Listeners<Event, Window> {
+  fn clone(&self) -> Self {
+    Self {
+      inner: self.inner.clone(),
     }
   }
 }
 
-impl<E: Tag, L: Tag> Listeners<E, L> {
+impl<Event: Tag, Window: Tag> Listeners<Event, Window> {
   /// Randomly generated function name to represent the JavaScript event function.
   pub(crate) fn function_name(&self) -> String {
-    self.function_name.to_string()
+    self.inner.function_name.to_string()
   }
 
   /// Randomly generated listener object name to represent the JavaScript event listener object.
   pub(crate) fn listeners_object_name(&self) -> String {
-    self.function_name.to_string()
+    self.inner.listeners_object_name.to_string()
   }
 
   /// Randomly generated queue object name to represent the JavaScript event queue object.
   pub(crate) fn queue_object_name(&self) -> String {
-    self.queue_object_name.to_string()
+    self.inner.queue_object_name.to_string()
   }
 
-  fn listen_internal<F>(&self, event: E, window: Option<L>, handler: F) -> EventHandler
-  where
-    F: Fn(Event) -> AfterHandle + Send + 'static,
-  {
-    let id = EventHandler(Uuid::new_v4());
-
+  /// Insert a pending event action to the queue.
+  fn insert_pending(&self, action: Pending<Event, Window>) {
     self
       .inner
+      .pending
       .lock()
-      .expect("poisoned event mutex")
-      .entry(event)
-      .or_default()
-      .insert(
-        id,
-        Handler {
-          window,
-          callback: Box::new(handler),
-        },
-      );
+      .expect("poisoned pending event queue")
+      .push(action)
+  }
 
-    id
+  /// Finish all pending event actions.
+  fn flush_pending(&self) {
+    let pending = {
+      let mut lock = self
+        .inner
+        .pending
+        .lock()
+        .expect("poisoned pending event queue");
+      std::mem::take(&mut *lock)
+    };
+
+    for action in pending {
+      match action {
+        Pending::Unlisten(id) => self.unlisten(id),
+        Pending::Listen(id, event, handler) => self.listen_(id, event, handler),
+        Pending::Trigger(event, window, payload) => self.trigger(event, window, payload),
+      }
+    }
+  }
+
+  fn listen_(&self, id: EventHandler, event: Event, handler: Handler<Window>) {
+    match self.inner.handlers.try_lock() {
+      Err(_) => self.insert_pending(Pending::Listen(id, event, handler)),
+      Ok(mut lock) => {
+        lock.entry(event).or_default().insert(id, handler);
+      }
+    }
   }
 
   /// Adds an event listener for JS events.
-  pub(crate) fn listen<F>(&self, event: E, window: Option<L>, handler: F) -> EventHandler
-  where
-    F: Fn(Event) + Send + 'static,
-  {
-    self.listen_internal(event, window, move |event| {
-      handler(event);
-      AfterHandle::DoNothing
-    })
+  pub(crate) fn listen<F: Fn(self::Event) + Send + 'static>(
+    &self,
+    event: Event,
+    window: Option<Window>,
+    handler: F,
+  ) -> EventHandler {
+    let id = EventHandler(Uuid::new_v4());
+    let handler = Handler {
+      window,
+      callback: Box::new(handler),
+    };
+
+    self.listen_(id, event, handler);
+
+    id
   }
 
   /// Listen to a JS event and immediately unlisten.
-  pub(crate) fn once<F: Fn(Event) + Send + 'static>(
+  pub(crate) fn once<F: Fn(self::Event) + Send + 'static>(
     &self,
-    event: E,
-    window: Option<L>,
+    event: Event,
+    window: Option<Window>,
     handler: F,
   ) -> EventHandler {
-    self.listen_internal(event, window, move |event| {
+    let self_ = self.clone();
+    self.listen(event, window, move |event| {
+      self_.unlisten(event.id);
       handler(event);
-      AfterHandle::Remove
     })
   }
 
   /// Removes an event listener.
   pub(crate) fn unlisten(&self, handler_id: EventHandler) {
-    self
-      .inner
-      .lock()
-      .expect("poisoned event mutex")
-      .values_mut()
-      .for_each(|handler| {
+    match self.inner.handlers.try_lock() {
+      Err(_) => self.insert_pending(Pending::Unlisten(handler_id)),
+      Ok(mut lock) => lock.values_mut().for_each(|handler| {
         handler.remove(&handler_id);
-      })
+      }),
+    }
   }
 
   /// Triggers the given global event with its payload.
-  pub(crate) fn trigger(&self, event: E, window: Option<L>, data: Option<String>) {
-    if let Some(handlers) = self
-      .inner
-      .lock()
-      .expect("poisoned event mutex")
-      .get_mut(&event)
-    {
-      handlers.retain(|&id, handler| {
-        if window.is_none() || window == handler.window {
-          let data = data.clone();
-          let payload = Event { id, data };
-          (handler.callback)(payload) != AfterHandle::Remove
-        } else {
-          // skip and retain all handlers specifying a different window
-          true
+  pub(crate) fn trigger(&self, event: Event, window: Option<Window>, payload: Option<String>) {
+    let mut maybe_pending = false;
+    match self.inner.handlers.try_lock() {
+      Err(_) => self.insert_pending(Pending::Trigger(event, window, payload)),
+      Ok(lock) => {
+        if let Some(handlers) = lock.get(&event) {
+          for (&id, handler) in handlers {
+            if window.is_none() || window == handler.window {
+              maybe_pending = true;
+              (handler.callback)(self::Event {
+                id,
+                data: payload.clone(),
+              })
+            }
+          }
         }
-      })
+      }
+    }
+
+    if maybe_pending {
+      self.flush_pending();
     }
   }
 }
@@ -199,7 +239,7 @@ mod test {
       listeners.listen(e, None, event_fn);
 
       // lock mutex
-      let l = listeners.inner.lock().unwrap();
+      let l = listeners.inner.handlers.lock().unwrap();
 
       // check if the generated key is in the map
       assert_eq!(l.contains_key(&key), true);
@@ -215,7 +255,7 @@ mod test {
        listeners.listen(e, None, event_fn);
 
        // lock mutex
-       let mut l = listeners.inner.lock().unwrap();
+       let mut l = listeners.inner.handlers.lock().unwrap();
 
        // check if l contains key
        if l.contains_key(&key) {
@@ -243,7 +283,7 @@ mod test {
       listeners.trigger(e, None, Some(d));
 
       // lock the mutex
-      let l = listeners.inner.lock().unwrap();
+      let l = listeners.inner.handlers.lock().unwrap();
 
       // assert that the key is contained in the listeners map
       assert!(l.contains_key(&key));

+ 7 - 7
core/tauri/src/runtime/manager.rs

@@ -31,19 +31,19 @@ use std::{
 };
 use uuid::Uuid;
 
-pub struct InnerWindowManager<M: Params> {
-  windows: Mutex<HashMap<M::Label, Window<M>>>,
-  plugins: Mutex<PluginStore<M>>,
-  listeners: Listeners<M::Event, M::Label>,
+pub struct InnerWindowManager<P: Params> {
+  windows: Mutex<HashMap<P::Label, Window<P>>>,
+  plugins: Mutex<PluginStore<P>>,
+  listeners: Listeners<P::Event, P::Label>,
 
   /// The JS message handler.
-  invoke_handler: Box<InvokeHandler<M>>,
+  invoke_handler: Box<InvokeHandler<P>>,
 
   /// The page load hook, invoked when the webview performs a navigation.
-  on_page_load: Box<OnPageLoad<M>>,
+  on_page_load: Box<OnPageLoad<P>>,
 
   config: Config,
-  assets: Arc<M::Assets>,
+  assets: Arc<P::Assets>,
   default_window_icon: Option<Vec<u8>>,
 
   /// A list of salts that are valid for the current application.