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
This commit is contained in:
chip 2021-04-15 17:22:47 -07:00 committed by GitHub
parent ece243d17c
commit e447b8e0e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 135 additions and 86 deletions

View File

@ -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.

View File

@ -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<E: Tag, L: Tag> Listeners<E, L> {
impl<Event: Tag, Window: Tag> Clone for Listeners<Event, Window> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
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)
}
/// 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: 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
}
/// 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
})
}
/// 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));

View File

@ -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.