mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-01-31 00:35:19 +01:00
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
This commit is contained in:
committed by
GitHub
parent
c84b162374
commit
b985eaf0a2
6
.changes/unlisten-race-condition.md
Normal file
6
.changes/unlisten-race-condition.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@tauri-apps/api": minor:bug
|
||||
"tauri": minor:bug
|
||||
---
|
||||
|
||||
Immediately unregister event listener when the unlisten function is called.
|
||||
File diff suppressed because one or more lines are too long
@@ -1102,7 +1102,7 @@ impl<R: Runtime> App<R> {
|
||||
)]
|
||||
fn register_core_plugins(&self) -> crate::Result<()> {
|
||||
self.handle.plugin(crate::path::plugin::init())?;
|
||||
self.handle.plugin(crate::event::plugin::init())?;
|
||||
self.handle.plugin(crate::event::plugin::init(self))?;
|
||||
self.handle.plugin(crate::window::plugin::init())?;
|
||||
self.handle.plugin(crate::webview::plugin::init())?;
|
||||
self.handle.plugin(crate::app::plugin::init())?;
|
||||
|
||||
10
crates/tauri/src/event/init.js
Normal file
10
crates/tauri/src/event/init.js
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// eslint-disable-next-line
|
||||
Object.defineProperty(window, '__TAURI_EVENT_PLUGIN_INTERNALS__', {
|
||||
value: {
|
||||
unregisterListener: __RAW_unregister_listener_function__
|
||||
}
|
||||
})
|
||||
@@ -164,7 +164,7 @@ impl Event {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listen_js_script(
|
||||
pub(crate) fn listen_js_script(
|
||||
listeners_object_name: &str,
|
||||
serialized_target: &str,
|
||||
event: EventName<&str>,
|
||||
@@ -191,7 +191,7 @@ pub fn listen_js_script(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn emit_js_script(
|
||||
pub(crate) fn emit_js_script(
|
||||
event_emit_function_name: &str,
|
||||
emit_args: &EmitArgs,
|
||||
serialized_ids: &str,
|
||||
@@ -205,23 +205,23 @@ pub fn emit_js_script(
|
||||
))
|
||||
}
|
||||
|
||||
pub fn unlisten_js_script(
|
||||
pub(crate) fn unlisten_js_script(
|
||||
listeners_object_name: &str,
|
||||
event_name: EventName<&str>,
|
||||
event_id: EventId,
|
||||
event_arg: &str,
|
||||
event_id_arg: &str,
|
||||
) -> String {
|
||||
format!(
|
||||
"(function () {{
|
||||
const listeners = (window['{listeners_object_name}'] || {{}})['{event_name}']
|
||||
const listeners = (window['{listeners_object_name}'] || {{}})[{event_arg}]
|
||||
if (listeners) {{
|
||||
window.__TAURI_INTERNALS__.unregisterCallback(listeners[{event_id}].handlerId)
|
||||
window.__TAURI_INTERNALS__.unregisterCallback(listeners[{event_id_arg}].handlerId)
|
||||
}}
|
||||
}})()
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
pub fn event_initialization_script(function_name: &str, listeners: &str) -> String {
|
||||
pub(crate) fn event_initialization_script(function_name: &str, listeners: &str) -> String {
|
||||
format!(
|
||||
"Object.defineProperty(window, '{function_name}', {{
|
||||
value: function (eventData, ids) {{
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use serde_json::Value as JsonValue;
|
||||
use serialize_to_javascript::{default_template, DefaultTemplate, Template};
|
||||
use tauri_runtime::window::is_label_valid;
|
||||
|
||||
use crate::plugin::{Builder, TauriPlugin};
|
||||
use crate::{command, ipc::CallbackFn, EventId, Result, Runtime};
|
||||
use crate::{AppHandle, Emitter, Webview};
|
||||
use crate::{AppHandle, Emitter, Manager, Webview};
|
||||
|
||||
use super::EventName;
|
||||
use super::EventTarget;
|
||||
@@ -75,11 +76,33 @@ async fn emit_to<R: Runtime>(
|
||||
}
|
||||
|
||||
/// Initializes the event plugin.
|
||||
pub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
pub(crate) fn init<R: Runtime, M: Manager<R>>(manager: &M) -> TauriPlugin<R> {
|
||||
let listeners = manager.manager().listeners();
|
||||
|
||||
#[derive(Template)]
|
||||
#[default_template("./init.js")]
|
||||
struct InitJavascript {
|
||||
#[raw]
|
||||
unregister_listener_function: String,
|
||||
}
|
||||
|
||||
let init_script = InitJavascript {
|
||||
unregister_listener_function: format!(
|
||||
"(event, eventId) => {}",
|
||||
crate::event::unlisten_js_script(listeners.listeners_object_name(), "event", "eventId")
|
||||
),
|
||||
};
|
||||
|
||||
Builder::new("event")
|
||||
.invoke_handler(crate::generate_handler![
|
||||
#![plugin(event)]
|
||||
listen, unlisten, emit, emit_to
|
||||
])
|
||||
.js_init_script(
|
||||
init_script
|
||||
.render_default(&Default::default())
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -1680,12 +1680,6 @@ fn main() {
|
||||
pub(crate) fn unlisten_js(&self, event: EventName<&str>, id: EventId) -> crate::Result<()> {
|
||||
let listeners = self.manager().listeners();
|
||||
|
||||
self.eval(crate::event::unlisten_js_script(
|
||||
listeners.listeners_object_name(),
|
||||
event,
|
||||
id,
|
||||
))?;
|
||||
|
||||
listeners.unlisten_js(event, id);
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -11,6 +11,14 @@
|
||||
|
||||
import { invoke, transformCallback } from './core'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__TAURI_EVENT_PLUGIN_INTERNALS__: {
|
||||
unregisterListener: (event: string, eventId: number) => void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type EventTarget =
|
||||
| { kind: 'Any' }
|
||||
| { kind: 'AnyLabel'; label: string }
|
||||
@@ -30,6 +38,7 @@ interface Event<T> {
|
||||
|
||||
type EventCallback<T> = (event: Event<T>) => void
|
||||
|
||||
// TODO(v3): mark this as Promise<void>
|
||||
type UnlistenFn = () => void
|
||||
|
||||
type EventName = `${TauriEvent}` | (string & Record<never, never>)
|
||||
@@ -72,6 +81,7 @@ enum TauriEvent {
|
||||
* @returns
|
||||
*/
|
||||
async function _unlisten(event: string, eventId: number): Promise<void> {
|
||||
window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(event, eventId)
|
||||
await invoke('plugin:event|unlisten', {
|
||||
event,
|
||||
eventId
|
||||
@@ -152,8 +162,7 @@ async function once<T>(
|
||||
return listen<T>(
|
||||
event,
|
||||
(eventData) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
_unlisten(event, eventData.id)
|
||||
void _unlisten(event, eventData.id)
|
||||
handler(eventData)
|
||||
},
|
||||
options
|
||||
|
||||
Reference in New Issue
Block a user