Skip to content

Commit 9b34055

Browse files
authored
fix(core): window-specific event delivery, closes #3302 (#3344)
1 parent 6330b66 commit 9b34055

18 files changed

Lines changed: 311 additions & 168 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
The `tauri::Window#emit` functiow now correctly sends the event to all windows that has a registered listener.
6+
**Breaking change:** `Window#emit_and_trigger` and `Window#emit` now requires the payload to be cloneable.

core/tauri-runtime-wry/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use tauri_runtime::{
1414
webview::{FileDropEvent, FileDropHandler, WebviewIpcHandler, WindowBuilder, WindowBuilderBase},
1515
window::{
1616
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
17-
DetachedWindow, PendingWindow, WindowEvent,
17+
DetachedWindow, JsEventListenerKey, PendingWindow, WindowEvent,
1818
},
1919
ClipboardManager, Dispatch, Error, ExitRequestedEventAction, GlobalShortcutManager, Icon, Result,
2020
RunEvent, RunIteration, Runtime, RuntimeHandle, UserAttentionType,
@@ -2700,7 +2700,7 @@ fn create_ipc_handler(
27002700
context: Context,
27012701
label: String,
27022702
menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
2703-
js_event_listeners: Arc<Mutex<HashMap<String, HashSet<u64>>>>,
2703+
js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,
27042704
handler: WebviewIpcHandler<Wry>,
27052705
) -> Box<dyn Fn(&Window, String) + 'static> {
27062706
Box::new(move |window, request| {
@@ -2724,7 +2724,7 @@ fn create_file_drop_handler(
27242724
context: Context,
27252725
label: String,
27262726
menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
2727-
js_event_listeners: Arc<Mutex<HashMap<String, HashSet<u64>>>>,
2727+
js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,
27282728
handler: FileDropHandler<Wry>,
27292729
) -> Box<dyn Fn(&Window, WryFileDropEvent) -> bool + 'static> {
27302730
Box::new(move |window, event| {

core/tauri-runtime/src/window.rs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,20 @@ pub struct PendingWindow<R: Runtime> {
105105
/// Maps runtime id to a string menu id.
106106
pub menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
107107

108-
/// A HashMap mapping JS event names with listener ids associated.
109-
pub js_event_listeners: Arc<Mutex<HashMap<String, HashSet<u64>>>>,
108+
/// A HashMap mapping JS event names with associated listener ids.
109+
pub js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,
110110
}
111111

112-
fn validate_label(label: &str) {
112+
pub fn is_label_valid(label: &str) -> bool {
113+
label
114+
.chars()
115+
.all(|c| char::is_alphanumeric(c) || c == '-' || c == '/' || c == ':' || c == '_')
116+
}
117+
118+
pub fn assert_label_is_valid(label: &str) {
113119
assert!(
114-
label.chars().all(char::is_alphanumeric),
115-
"Window label must be alphanumeric"
120+
is_label_valid(label),
121+
"Window label must include only alphanumeric characters, `-`, `/`, `:` and `_`."
116122
);
117123
}
118124

@@ -128,7 +134,7 @@ impl<R: Runtime> PendingWindow<R> {
128134
get_menu_ids(&mut menu_ids, menu);
129135
}
130136
let label = label.into();
131-
validate_label(&label);
137+
assert_label_is_valid(&label);
132138
Self {
133139
window_builder,
134140
webview_attributes,
@@ -154,7 +160,7 @@ impl<R: Runtime> PendingWindow<R> {
154160
get_menu_ids(&mut menu_ids, menu);
155161
}
156162
let label = label.into();
157-
validate_label(&label);
163+
assert_label_is_valid(&label);
158164
Self {
159165
window_builder,
160166
webview_attributes,
@@ -192,6 +198,15 @@ impl<R: Runtime> PendingWindow<R> {
192198
}
193199
}
194200

201+
/// Key for a JS event listener.
202+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
203+
pub struct JsEventListenerKey {
204+
/// The associated window label.
205+
pub window_label: Option<String>,
206+
/// The event name.
207+
pub event: String,
208+
}
209+
195210
/// A webview window that is not yet managed by Tauri.
196211
#[derive(Debug)]
197212
pub struct DetachedWindow<R: Runtime> {
@@ -204,8 +219,8 @@ pub struct DetachedWindow<R: Runtime> {
204219
/// Maps runtime id to a string menu id.
205220
pub menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
206221

207-
/// A HashMap mapping JS event names with listener ids associated.
208-
pub js_event_listeners: Arc<Mutex<HashMap<String, HashSet<u64>>>>,
222+
/// A HashMap mapping JS event names with associated listener ids.
223+
pub js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,
209224
}
210225

211226
impl<R: Runtime> Clone for DetachedWindow<R> {

core/tauri/scripts/bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/tauri/src/endpoints/event.rs

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::{
77
api::ipc::CallbackFn,
88
event::is_event_name_valid,
99
event::{listen_js, unlisten_js},
10+
runtime::window::is_label_valid,
1011
sealed::ManagerBase,
1112
Manager, Runtime,
1213
};
@@ -31,12 +32,35 @@ impl<'de> Deserialize<'de> for EventId {
3132
}
3233
}
3334

35+
pub struct WindowLabel(String);
36+
37+
impl<'de> Deserialize<'de> for WindowLabel {
38+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
39+
where
40+
D: Deserializer<'de>,
41+
{
42+
let event_id = String::deserialize(deserializer)?;
43+
if is_label_valid(&event_id) {
44+
Ok(WindowLabel(event_id))
45+
} else {
46+
Err(serde::de::Error::custom(
47+
"Window label must include only alphanumeric characters, `-`, `/`, `:` and `_`.",
48+
))
49+
}
50+
}
51+
}
52+
3453
/// The API descriptor.
3554
#[derive(Deserialize, CommandModule)]
3655
#[serde(tag = "cmd", rename_all = "camelCase")]
3756
pub enum Cmd {
3857
/// Listen to an event.
39-
Listen { event: EventId, handler: CallbackFn },
58+
#[serde(rename_all = "camelCase")]
59+
Listen {
60+
event: EventId,
61+
window_label: Option<WindowLabel>,
62+
handler: CallbackFn,
63+
},
4064
/// Unlisten to an event.
4165
#[serde(rename_all = "camelCase")]
4266
Unlisten { event_id: u64 },
@@ -45,7 +69,7 @@ pub enum Cmd {
4569
#[serde(rename_all = "camelCase")]
4670
Emit {
4771
event: EventId,
48-
window_label: Option<String>,
72+
window_label: Option<WindowLabel>,
4973
payload: Option<String>,
5074
},
5175
}
@@ -54,16 +78,25 @@ impl Cmd {
5478
fn listen<R: Runtime>(
5579
context: InvokeContext<R>,
5680
event: EventId,
81+
window_label: Option<WindowLabel>,
5782
handler: CallbackFn,
5883
) -> crate::Result<u64> {
5984
let event_id = rand::random();
85+
86+
let window_label = window_label.map(|l| l.0);
87+
6088
context.window.eval(&listen_js(
6189
context.window.manager().event_listeners_object_name(),
6290
format!("'{}'", event.0),
6391
event_id,
92+
window_label.clone(),
6493
format!("window['_{}']", handler.0),
6594
))?;
66-
context.window.register_js_listener(event.0, event_id);
95+
96+
context
97+
.window
98+
.register_js_listener(window_label, event.0, event_id);
99+
67100
Ok(event_id)
68101
}
69102

@@ -79,14 +112,14 @@ impl Cmd {
79112
fn emit<R: Runtime>(
80113
context: InvokeContext<R>,
81114
event: EventId,
82-
window_label: Option<String>,
115+
window_label: Option<WindowLabel>,
83116
payload: Option<String>,
84117
) -> crate::Result<()> {
85118
// dispatch the event to Rust listeners
86119
context.window.trigger(&event.0, payload.clone());
87120

88121
if let Some(target) = window_label {
89-
context.window.emit_to(&target, &event.0, payload)?;
122+
context.window.emit_to(&target.0, &event.0, payload)?;
90123
} else {
91124
context.window.emit_all(&event.0, payload)?;
92125
}

core/tauri/src/event.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ pub fn is_event_name_valid(event: &str) -> bool {
1818
.all(|c| c.is_alphanumeric() || c == '-' || c == '/' || c == ':' || c == '_')
1919
}
2020

21+
pub fn assert_event_name_is_valid(event: &str) {
22+
assert!(
23+
is_event_name_valid(event),
24+
"Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`."
25+
);
26+
}
27+
2128
/// Represents an event handler.
2229
#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
2330
pub struct EventHandler(Uuid);
@@ -306,23 +313,31 @@ pub fn listen_js(
306313
listeners_object_name: String,
307314
event: String,
308315
event_id: u64,
316+
window_label: Option<String>,
309317
handler: String,
310318
) -> String {
311319
format!(
312320
"if (window['{listeners}'] === void 0) {{
313-
window['{listeners}'] = Object.create(null)
321+
Object.defineProperty(window, '{listeners}', {{ value: Object.create(null) }});
314322
}}
315323
if (window['{listeners}'][{event}] === void 0) {{
316-
window['{listeners}'][{event}] = []
324+
Object.defineProperty(window['{listeners}'], {event}, {{ value: [] }});
317325
}}
318326
window['{listeners}'][{event}].push({{
319327
id: {event_id},
328+
windowLabel: {window_label},
320329
handler: {handler}
321330
}});
322331
",
323332
listeners = listeners_object_name,
324333
event = event,
325334
event_id = event_id,
335+
window_label = if let Some(l) = window_label {
336+
crate::runtime::window::assert_label_is_valid(&l);
337+
format!("'{}'", l)
338+
} else {
339+
"null".to_owned()
340+
},
326341
handler = handler
327342
)
328343
}

core/tauri/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -404,14 +404,14 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
404404

405405
/// Emits a event to all windows.
406406
fn emit_all<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {
407-
self.manager().emit_filter(event, payload, |_| true)
407+
self.manager().emit_filter(event, None, payload, |_| true)
408408
}
409409

410410
/// Emits an event to a window with the specified label.
411411
fn emit_to<S: Serialize + Clone>(&self, label: &str, event: &str, payload: S) -> Result<()> {
412412
self
413413
.manager()
414-
.emit_filter(event, payload, |w| label == w.label())
414+
.emit_filter(event, None, payload, |w| label == w.label())
415415
}
416416

417417
/// Listen to a global event.
@@ -552,6 +552,7 @@ pub(crate) mod sealed {
552552

553553
self.manager().emit_filter(
554554
"tauri://window-created",
555+
None,
555556
Some(WindowCreatedEvent {
556557
label: window.label().into(),
557558
}),

core/tauri/src/manager.rs

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use crate::hooks::IsolationJavascript;
3030
use crate::pattern::{format_real_schema, PatternJavascript};
3131
use crate::{
3232
app::{AppHandle, GlobalWindowEvent, GlobalWindowEventListener},
33-
event::{is_event_name_valid, Event, EventHandler, Listeners},
33+
event::{assert_event_name_is_valid, Event, EventHandler, Listeners},
3434
hooks::{InvokeHandler, InvokePayload, InvokeResponder, OnPageLoad, PageLoadPayload},
3535
plugin::PluginStore,
3636
runtime::{
@@ -850,6 +850,7 @@ impl<R: Runtime> WindowManager<R> {
850850
self.event_listeners_object_name(),
851851
"eventName".into(),
852852
0,
853+
None,
853854
"window['_' + window.__TAURI__.transformCallback(cb) ]".into()
854855
)
855856
),
@@ -865,18 +866,22 @@ impl<R: Runtime> WindowManager<R> {
865866
fn event_initialization_script(&self) -> String {
866867
return format!(
867868
"
868-
window['{function}'] = function (eventData) {{
869-
const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || []
870-
871-
for (let i = listeners.length - 1; i >= 0; i--) {{
872-
const listener = listeners[i]
873-
eventData.id = listener.id
874-
listener.handler(eventData)
875-
}}
876-
}}
869+
Object.defineProperty(window, '{function}', {{
870+
value: function (eventData) {{
871+
const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || []
872+
873+
for (let i = listeners.length - 1; i >= 0; i--) {{
874+
const listener = listeners[i]
875+
if (listener.windowLabel === null || listener.windowLabel === eventData.windowLabel) {{
876+
eventData.id = listener.id
877+
listener.handler(eventData)
878+
}}
879+
}}
880+
}}
881+
}});
877882
",
878-
function = self.inner.listeners.function_name(),
879-
listeners = self.inner.listeners.listeners_object_name()
883+
function = self.event_emit_function_name(),
884+
listeners = self.event_listeners_object_name()
880885
);
881886
}
882887
}
@@ -1088,17 +1093,23 @@ impl<R: Runtime> WindowManager<R> {
10881093
self.windows_lock().remove(label);
10891094
}
10901095

1091-
pub fn emit_filter<S, F>(&self, event: &str, payload: S, filter: F) -> crate::Result<()>
1096+
pub fn emit_filter<S, F>(
1097+
&self,
1098+
event: &str,
1099+
source_window_label: Option<&str>,
1100+
payload: S,
1101+
filter: F,
1102+
) -> crate::Result<()>
10921103
where
10931104
S: Serialize + Clone,
10941105
F: Fn(&Window<R>) -> bool,
10951106
{
1096-
assert!(is_event_name_valid(event));
1107+
assert_event_name_is_valid(event);
10971108
self
10981109
.windows_lock()
10991110
.values()
11001111
.filter(|&w| filter(w))
1101-
.try_for_each(|window| window.emit(event, payload.clone()))
1112+
.try_for_each(|window| window.emit_internal(event, source_window_label, payload.clone()))
11021113
}
11031114

11041115
pub fn labels(&self) -> HashSet<String> {
@@ -1118,7 +1129,7 @@ impl<R: Runtime> WindowManager<R> {
11181129
}
11191130

11201131
pub fn trigger(&self, event: &str, window: Option<String>, data: Option<String>) {
1121-
assert!(is_event_name_valid(event));
1132+
assert_event_name_is_valid(event);
11221133
self.inner.listeners.trigger(event, window, data)
11231134
}
11241135

@@ -1128,7 +1139,7 @@ impl<R: Runtime> WindowManager<R> {
11281139
window: Option<String>,
11291140
handler: F,
11301141
) -> EventHandler {
1131-
assert!(is_event_name_valid(&event));
1142+
assert_event_name_is_valid(&event);
11321143
self.inner.listeners.listen(event, window, handler)
11331144
}
11341145

@@ -1138,7 +1149,7 @@ impl<R: Runtime> WindowManager<R> {
11381149
window: Option<String>,
11391150
handler: F,
11401151
) -> EventHandler {
1141-
assert!(is_event_name_valid(&event));
1152+
assert_event_name_is_valid(&event);
11421153
self.inner.listeners.once(event, window, handler)
11431154
}
11441155

@@ -1171,7 +1182,7 @@ fn on_window_event<R: Runtime>(
11711182
label: _,
11721183
signal_tx,
11731184
} => {
1174-
if window.has_js_listener(WINDOW_CLOSE_REQUESTED_EVENT) {
1185+
if window.has_js_listener(Some(window.label().into()), WINDOW_CLOSE_REQUESTED_EVENT) {
11751186
signal_tx.send(true).unwrap();
11761187
}
11771188
window.emit_and_trigger(WINDOW_CLOSE_REQUESTED_EVENT, ())?;
@@ -1210,7 +1221,7 @@ fn on_window_event<R: Runtime>(
12101221
Ok(())
12111222
}
12121223

1213-
#[derive(Serialize)]
1224+
#[derive(Clone, Serialize)]
12141225
#[serde(rename_all = "camelCase")]
12151226
struct ScaleFactorChanged {
12161227
scale_factor: f64,

0 commit comments

Comments
 (0)