Skip to content

Commit e447b8e

Browse files
authored
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
1 parent ece243d commit e447b8e

File tree

3 files changed

+133
-84
lines changed

3 files changed

+133
-84
lines changed

Diff for: .changes/nested-events.md

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

Diff for: core/tauri/src/event.rs

+117-77
Original file line numberDiff line numberDiff line change
@@ -41,137 +41,177 @@ impl Event {
4141
}
4242
}
4343

44-
/// What happens after the handler is called?
45-
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
46-
enum AfterHandle {
47-
/// The handler is removed (once).
48-
Remove,
49-
50-
/// Nothing is done (regular).
51-
DoNothing,
44+
/// What to do with the pending handler when resolving it?
45+
enum Pending<Event: Tag, Window: Tag> {
46+
Unlisten(EventHandler),
47+
Listen(EventHandler, Event, Handler<Window>),
48+
Trigger(Event, Option<Window>, Option<String>),
5249
}
5350

5451
/// Stored in [`Listeners`] to be called upon when the event that stored it is triggered.
5552
struct Handler<Window: Tag> {
5653
window: Option<Window>,
57-
callback: Box<dyn Fn(Event) -> AfterHandle + Send>,
54+
callback: Box<dyn Fn(Event) + Send>,
5855
}
5956

6057
/// A collection of handlers. Multiple handlers can represent the same event.
6158
type Handlers<Event, Window> = HashMap<Event, HashMap<EventHandler, Handler<Window>>>;
6259

63-
#[derive(Clone)]
64-
pub(crate) struct Listeners<Event: Tag, Window: Tag> {
65-
inner: Arc<Mutex<Handlers<Event, Window>>>,
60+
/// Holds event handlers and pending event handlers, along with the salts associating them.
61+
struct InnerListeners<Event: Tag, Window: Tag> {
62+
handlers: Mutex<Handlers<Event, Window>>,
63+
pending: Mutex<Vec<Pending<Event, Window>>>,
6664
function_name: Uuid,
6765
listeners_object_name: Uuid,
6866
queue_object_name: Uuid,
6967
}
7068

71-
impl<E: Tag, L: Tag> Default for Listeners<E, L> {
69+
/// A self-contained event manager.
70+
pub(crate) struct Listeners<Event: Tag, Window: Tag> {
71+
inner: Arc<InnerListeners<Event, Window>>,
72+
}
73+
74+
impl<Event: Tag, Window: Tag> Default for Listeners<Event, Window> {
7275
fn default() -> Self {
7376
Self {
74-
inner: Arc::new(Mutex::default()),
75-
function_name: Uuid::new_v4(),
76-
listeners_object_name: Uuid::new_v4(),
77-
queue_object_name: Uuid::new_v4(),
77+
inner: Arc::new(InnerListeners {
78+
handlers: Mutex::default(),
79+
pending: Mutex::default(),
80+
function_name: Uuid::new_v4(),
81+
listeners_object_name: Uuid::new_v4(),
82+
queue_object_name: Uuid::new_v4(),
83+
}),
84+
}
85+
}
86+
}
87+
88+
impl<Event: Tag, Window: Tag> Clone for Listeners<Event, Window> {
89+
fn clone(&self) -> Self {
90+
Self {
91+
inner: self.inner.clone(),
7892
}
7993
}
8094
}
8195

82-
impl<E: Tag, L: Tag> Listeners<E, L> {
96+
impl<Event: Tag, Window: Tag> Listeners<Event, Window> {
8397
/// Randomly generated function name to represent the JavaScript event function.
8498
pub(crate) fn function_name(&self) -> String {
85-
self.function_name.to_string()
99+
self.inner.function_name.to_string()
86100
}
87101

88102
/// Randomly generated listener object name to represent the JavaScript event listener object.
89103
pub(crate) fn listeners_object_name(&self) -> String {
90-
self.function_name.to_string()
104+
self.inner.listeners_object_name.to_string()
91105
}
92106

93107
/// Randomly generated queue object name to represent the JavaScript event queue object.
94108
pub(crate) fn queue_object_name(&self) -> String {
95-
self.queue_object_name.to_string()
109+
self.inner.queue_object_name.to_string()
96110
}
97111

98-
fn listen_internal<F>(&self, event: E, window: Option<L>, handler: F) -> EventHandler
99-
where
100-
F: Fn(Event) -> AfterHandle + Send + 'static,
101-
{
102-
let id = EventHandler(Uuid::new_v4());
103-
112+
/// Insert a pending event action to the queue.
113+
fn insert_pending(&self, action: Pending<Event, Window>) {
104114
self
105115
.inner
116+
.pending
106117
.lock()
107-
.expect("poisoned event mutex")
108-
.entry(event)
109-
.or_default()
110-
.insert(
111-
id,
112-
Handler {
113-
window,
114-
callback: Box::new(handler),
115-
},
116-
);
118+
.expect("poisoned pending event queue")
119+
.push(action)
120+
}
117121

118-
id
122+
/// Finish all pending event actions.
123+
fn flush_pending(&self) {
124+
let pending = {
125+
let mut lock = self
126+
.inner
127+
.pending
128+
.lock()
129+
.expect("poisoned pending event queue");
130+
std::mem::take(&mut *lock)
131+
};
132+
133+
for action in pending {
134+
match action {
135+
Pending::Unlisten(id) => self.unlisten(id),
136+
Pending::Listen(id, event, handler) => self.listen_(id, event, handler),
137+
Pending::Trigger(event, window, payload) => self.trigger(event, window, payload),
138+
}
139+
}
140+
}
141+
142+
fn listen_(&self, id: EventHandler, event: Event, handler: Handler<Window>) {
143+
match self.inner.handlers.try_lock() {
144+
Err(_) => self.insert_pending(Pending::Listen(id, event, handler)),
145+
Ok(mut lock) => {
146+
lock.entry(event).or_default().insert(id, handler);
147+
}
148+
}
119149
}
120150

121151
/// Adds an event listener for JS events.
122-
pub(crate) fn listen<F>(&self, event: E, window: Option<L>, handler: F) -> EventHandler
123-
where
124-
F: Fn(Event) + Send + 'static,
125-
{
126-
self.listen_internal(event, window, move |event| {
127-
handler(event);
128-
AfterHandle::DoNothing
129-
})
152+
pub(crate) fn listen<F: Fn(self::Event) + Send + 'static>(
153+
&self,
154+
event: Event,
155+
window: Option<Window>,
156+
handler: F,
157+
) -> EventHandler {
158+
let id = EventHandler(Uuid::new_v4());
159+
let handler = Handler {
160+
window,
161+
callback: Box::new(handler),
162+
};
163+
164+
self.listen_(id, event, handler);
165+
166+
id
130167
}
131168

132169
/// Listen to a JS event and immediately unlisten.
133-
pub(crate) fn once<F: Fn(Event) + Send + 'static>(
170+
pub(crate) fn once<F: Fn(self::Event) + Send + 'static>(
134171
&self,
135-
event: E,
136-
window: Option<L>,
172+
event: Event,
173+
window: Option<Window>,
137174
handler: F,
138175
) -> EventHandler {
139-
self.listen_internal(event, window, move |event| {
176+
let self_ = self.clone();
177+
self.listen(event, window, move |event| {
178+
self_.unlisten(event.id);
140179
handler(event);
141-
AfterHandle::Remove
142180
})
143181
}
144182

145183
/// Removes an event listener.
146184
pub(crate) fn unlisten(&self, handler_id: EventHandler) {
147-
self
148-
.inner
149-
.lock()
150-
.expect("poisoned event mutex")
151-
.values_mut()
152-
.for_each(|handler| {
185+
match self.inner.handlers.try_lock() {
186+
Err(_) => self.insert_pending(Pending::Unlisten(handler_id)),
187+
Ok(mut lock) => lock.values_mut().for_each(|handler| {
153188
handler.remove(&handler_id);
154-
})
189+
}),
190+
}
155191
}
156192

157193
/// Triggers the given global event with its payload.
158-
pub(crate) fn trigger(&self, event: E, window: Option<L>, data: Option<String>) {
159-
if let Some(handlers) = self
160-
.inner
161-
.lock()
162-
.expect("poisoned event mutex")
163-
.get_mut(&event)
164-
{
165-
handlers.retain(|&id, handler| {
166-
if window.is_none() || window == handler.window {
167-
let data = data.clone();
168-
let payload = Event { id, data };
169-
(handler.callback)(payload) != AfterHandle::Remove
170-
} else {
171-
// skip and retain all handlers specifying a different window
172-
true
194+
pub(crate) fn trigger(&self, event: Event, window: Option<Window>, payload: Option<String>) {
195+
let mut maybe_pending = false;
196+
match self.inner.handlers.try_lock() {
197+
Err(_) => self.insert_pending(Pending::Trigger(event, window, payload)),
198+
Ok(lock) => {
199+
if let Some(handlers) = lock.get(&event) {
200+
for (&id, handler) in handlers {
201+
if window.is_none() || window == handler.window {
202+
maybe_pending = true;
203+
(handler.callback)(self::Event {
204+
id,
205+
data: payload.clone(),
206+
})
207+
}
208+
}
173209
}
174-
})
210+
}
211+
}
212+
213+
if maybe_pending {
214+
self.flush_pending();
175215
}
176216
}
177217
}
@@ -199,7 +239,7 @@ mod test {
199239
listeners.listen(e, None, event_fn);
200240

201241
// lock mutex
202-
let l = listeners.inner.lock().unwrap();
242+
let l = listeners.inner.handlers.lock().unwrap();
203243

204244
// check if the generated key is in the map
205245
assert_eq!(l.contains_key(&key), true);
@@ -215,7 +255,7 @@ mod test {
215255
listeners.listen(e, None, event_fn);
216256

217257
// lock mutex
218-
let mut l = listeners.inner.lock().unwrap();
258+
let mut l = listeners.inner.handlers.lock().unwrap();
219259

220260
// check if l contains key
221261
if l.contains_key(&key) {
@@ -243,7 +283,7 @@ mod test {
243283
listeners.trigger(e, None, Some(d));
244284

245285
// lock the mutex
246-
let l = listeners.inner.lock().unwrap();
286+
let l = listeners.inner.handlers.lock().unwrap();
247287

248288
// assert that the key is contained in the listeners map
249289
assert!(l.contains_key(&key));

Diff for: core/tauri/src/runtime/manager.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,19 @@ use std::{
3131
};
3232
use uuid::Uuid;
3333

34-
pub struct InnerWindowManager<M: Params> {
35-
windows: Mutex<HashMap<M::Label, Window<M>>>,
36-
plugins: Mutex<PluginStore<M>>,
37-
listeners: Listeners<M::Event, M::Label>,
34+
pub struct InnerWindowManager<P: Params> {
35+
windows: Mutex<HashMap<P::Label, Window<P>>>,
36+
plugins: Mutex<PluginStore<P>>,
37+
listeners: Listeners<P::Event, P::Label>,
3838

3939
/// The JS message handler.
40-
invoke_handler: Box<InvokeHandler<M>>,
40+
invoke_handler: Box<InvokeHandler<P>>,
4141

4242
/// The page load hook, invoked when the webview performs a navigation.
43-
on_page_load: Box<OnPageLoad<M>>,
43+
on_page_load: Box<OnPageLoad<P>>,
4444

4545
config: Config,
46-
assets: Arc<M::Assets>,
46+
assets: Arc<P::Assets>,
4747
default_window_icon: Option<Vec<u8>>,
4848

4949
/// A list of salts that are valid for the current application.

0 commit comments

Comments
 (0)