Skip to content

Commit b985eaf

Browse files
authored
fix(core): immediately unregister event listener on unlisten call (#13306)
* fix(core): immediately unregister event listener on unlisten call the unlisten function is currently async, but marked as `() => void` in the TypeScript definition. To avoid a breaking change, we're going to immediately unregister the listener function so it's not called. this fixes a race condition where after calling unlisten() you would still receive events if you do not `await` it and there's a new event triggering while the unlisten command is running * cleanup * fix build * fix ci
1 parent c84b162 commit b985eaf

File tree

8 files changed

+62
-20
lines changed

8 files changed

+62
-20
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@tauri-apps/api": minor:bug
3+
"tauri": minor:bug
4+
---
5+
6+
Immediately unregister event listener when the unlisten function is called.

crates/tauri/scripts/bundle.global.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.

crates/tauri/src/app.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1102,7 +1102,7 @@ impl<R: Runtime> App<R> {
11021102
)]
11031103
fn register_core_plugins(&self) -> crate::Result<()> {
11041104
self.handle.plugin(crate::path::plugin::init())?;
1105-
self.handle.plugin(crate::event::plugin::init())?;
1105+
self.handle.plugin(crate::event::plugin::init(self))?;
11061106
self.handle.plugin(crate::window::plugin::init())?;
11071107
self.handle.plugin(crate::webview::plugin::init())?;
11081108
self.handle.plugin(crate::app::plugin::init())?;

crates/tauri/src/event/init.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
// eslint-disable-next-line
6+
Object.defineProperty(window, '__TAURI_EVENT_PLUGIN_INTERNALS__', {
7+
value: {
8+
unregisterListener: __RAW_unregister_listener_function__
9+
}
10+
})

crates/tauri/src/event/mod.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ impl Event {
164164
}
165165
}
166166

167-
pub fn listen_js_script(
167+
pub(crate) fn listen_js_script(
168168
listeners_object_name: &str,
169169
serialized_target: &str,
170170
event: EventName<&str>,
@@ -191,7 +191,7 @@ pub fn listen_js_script(
191191
)
192192
}
193193

194-
pub fn emit_js_script(
194+
pub(crate) fn emit_js_script(
195195
event_emit_function_name: &str,
196196
emit_args: &EmitArgs,
197197
serialized_ids: &str,
@@ -205,23 +205,23 @@ pub fn emit_js_script(
205205
))
206206
}
207207

208-
pub fn unlisten_js_script(
208+
pub(crate) fn unlisten_js_script(
209209
listeners_object_name: &str,
210-
event_name: EventName<&str>,
211-
event_id: EventId,
210+
event_arg: &str,
211+
event_id_arg: &str,
212212
) -> String {
213213
format!(
214214
"(function () {{
215-
const listeners = (window['{listeners_object_name}'] || {{}})['{event_name}']
215+
const listeners = (window['{listeners_object_name}'] || {{}})[{event_arg}]
216216
if (listeners) {{
217-
window.__TAURI_INTERNALS__.unregisterCallback(listeners[{event_id}].handlerId)
217+
window.__TAURI_INTERNALS__.unregisterCallback(listeners[{event_id_arg}].handlerId)
218218
}}
219219
}})()
220220
",
221221
)
222222
}
223223

224-
pub fn event_initialization_script(function_name: &str, listeners: &str) -> String {
224+
pub(crate) fn event_initialization_script(function_name: &str, listeners: &str) -> String {
225225
format!(
226226
"Object.defineProperty(window, '{function_name}', {{
227227
value: function (eventData, ids) {{

crates/tauri/src/event/plugin.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
// SPDX-License-Identifier: MIT
44
use serde::{Deserialize, Deserializer};
55
use serde_json::Value as JsonValue;
6+
use serialize_to_javascript::{default_template, DefaultTemplate, Template};
67
use tauri_runtime::window::is_label_valid;
78

89
use crate::plugin::{Builder, TauriPlugin};
910
use crate::{command, ipc::CallbackFn, EventId, Result, Runtime};
10-
use crate::{AppHandle, Emitter, Webview};
11+
use crate::{AppHandle, Emitter, Manager, Webview};
1112

1213
use super::EventName;
1314
use super::EventTarget;
@@ -75,11 +76,33 @@ async fn emit_to<R: Runtime>(
7576
}
7677

7778
/// Initializes the event plugin.
78-
pub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {
79+
pub(crate) fn init<R: Runtime, M: Manager<R>>(manager: &M) -> TauriPlugin<R> {
80+
let listeners = manager.manager().listeners();
81+
82+
#[derive(Template)]
83+
#[default_template("./init.js")]
84+
struct InitJavascript {
85+
#[raw]
86+
unregister_listener_function: String,
87+
}
88+
89+
let init_script = InitJavascript {
90+
unregister_listener_function: format!(
91+
"(event, eventId) => {}",
92+
crate::event::unlisten_js_script(listeners.listeners_object_name(), "event", "eventId")
93+
),
94+
};
95+
7996
Builder::new("event")
8097
.invoke_handler(crate::generate_handler![
8198
#![plugin(event)]
8299
listen, unlisten, emit, emit_to
83100
])
101+
.js_init_script(
102+
init_script
103+
.render_default(&Default::default())
104+
.unwrap()
105+
.to_string(),
106+
)
84107
.build()
85108
}

crates/tauri/src/webview/mod.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,12 +1680,6 @@ fn main() {
16801680
pub(crate) fn unlisten_js(&self, event: EventName<&str>, id: EventId) -> crate::Result<()> {
16811681
let listeners = self.manager().listeners();
16821682

1683-
self.eval(crate::event::unlisten_js_script(
1684-
listeners.listeners_object_name(),
1685-
event,
1686-
id,
1687-
))?;
1688-
16891683
listeners.unlisten_js(event, id);
16901684

16911685
Ok(())

packages/api/src/event.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@
1111

1212
import { invoke, transformCallback } from './core'
1313

14+
declare global {
15+
interface Window {
16+
__TAURI_EVENT_PLUGIN_INTERNALS__: {
17+
unregisterListener: (event: string, eventId: number) => void
18+
}
19+
}
20+
}
21+
1422
type EventTarget =
1523
| { kind: 'Any' }
1624
| { kind: 'AnyLabel'; label: string }
@@ -30,6 +38,7 @@ interface Event<T> {
3038

3139
type EventCallback<T> = (event: Event<T>) => void
3240

41+
// TODO(v3): mark this as Promise<void>
3342
type UnlistenFn = () => void
3443

3544
type EventName = `${TauriEvent}` | (string & Record<never, never>)
@@ -72,6 +81,7 @@ enum TauriEvent {
7281
* @returns
7382
*/
7483
async function _unlisten(event: string, eventId: number): Promise<void> {
84+
window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(event, eventId)
7585
await invoke('plugin:event|unlisten', {
7686
event,
7787
eventId
@@ -152,8 +162,7 @@ async function once<T>(
152162
return listen<T>(
153163
event,
154164
(eventData) => {
155-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
156-
_unlisten(event, eventData.id)
165+
void _unlisten(event, eventData.id)
157166
handler(eventData)
158167
},
159168
options

0 commit comments

Comments
 (0)