From e76179fc81b3fb65b33513c474304f9f009f5839 Mon Sep 17 00:00:00 2001 From: tenderdeve Date: Mon, 29 Jun 2026 15:36:46 +0530 Subject: [PATCH 1/3] fix(core): purge JS event listeners when source webview is destroyed (fix #15583) JS event listeners registered from a webview are stored in the backend Listeners::js_event_listeners map keyed by the source webview label. When the webview was destroyed the manager removed its handle but left the listener entries behind. Their JS runtime no longer exists, so the callbacks can never be delivered and the metadata leaked until app exit, forcing apps to manually unlisten() before closing a window. Add Listeners::remove_webview_events and call it from on_webview_close and on_window_close so a destroyed webview's listeners are dropped with it. --- .../fix-js-listeners-leak-on-webview-close.md | 5 +++ crates/tauri/src/event/listener.rs | 36 +++++++++++++++++++ crates/tauri/src/manager/mod.rs | 2 ++ 3 files changed, 43 insertions(+) create mode 100644 .changes/fix-js-listeners-leak-on-webview-close.md diff --git a/.changes/fix-js-listeners-leak-on-webview-close.md b/.changes/fix-js-listeners-leak-on-webview-close.md new file mode 100644 index 000000000000..dbe95ba97df0 --- /dev/null +++ b/.changes/fix-js-listeners-leak-on-webview-close.md @@ -0,0 +1,5 @@ +--- +'tauri': 'patch:bug' +--- + +Remove a webview's JS event listeners from the backend `Listeners` map when that webview is destroyed. Previously the entries keyed by the source webview label lingered after the webview was dropped, so they could never be delivered and leaked until the app exited — forcing apps to manually `unlisten()` before closing a window. diff --git a/crates/tauri/src/event/listener.rs b/crates/tauri/src/event/listener.rs index b39c8ddf8214..9b8375abc764 100644 --- a/crates/tauri/src/event/listener.rs +++ b/crates/tauri/src/event/listener.rs @@ -251,6 +251,20 @@ impl Listeners { } } + /// Removes all JS event listeners registered from the given webview. + /// + /// Called when a webview is destroyed: its JavaScript runtime no longer + /// exists, so the listeners keyed by its label can never be delivered and + /// would otherwise leak in the map until the app exits. + pub(crate) fn remove_webview_events(&self, webview_label: &str) { + self + .inner + .js_event_listeners + .lock() + .unwrap() + .remove(webview_label); + } + pub(crate) fn has_js_listener bool>( &self, event: crate::EventName<&str>, @@ -398,4 +412,26 @@ mod test { .emit(EmitArgs::new(event.as_str_event(), &()).unwrap()) .unwrap(); } + + #[test] + fn js_listeners_removed_on_webview_close() { + let listeners = Listeners::default(); + let event = crate::EventName::new("some-event".to_owned()).unwrap(); + let webview_label = "main"; + + let id = listeners.next_event_id(); + listeners.listen_js( + event.as_str_event(), + webview_label, + EventTarget::Webview { + label: webview_label.to_owned(), + }, + id, + ); + assert!(listeners.has_js_listener(event.as_str_event(), |_| true)); + + // dropping the source webview must drop its JS listeners. + listeners.remove_webview_events(webview_label); + assert!(!listeners.has_js_listener(event.as_str_event(), |_| true)); + } } diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index 12b1df2e4e8a..3e9d79880411 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -655,6 +655,7 @@ impl AppManager { if let Some(window) = window { for webview in window.webviews() { self.webview.webviews_lock().remove(webview.label()); + self.listeners().remove_webview_events(webview.label()); } } } @@ -662,6 +663,7 @@ impl AppManager { #[cfg(desktop)] pub(crate) fn on_webview_close(&self, label: &str) { self.webview.webviews_lock().remove(label); + self.listeners().remove_webview_events(label); } pub fn windows(&self) -> HashMap> { From 81cdf20d62cfba44571d2568d6054bc9222eca9f Mon Sep 17 00:00:00 2001 From: tenderdeve Date: Wed, 1 Jul 2026 14:40:19 +0530 Subject: [PATCH 2/3] refactor(core): rename remove_webview_events to remove_webview_js_listeners Clarify that this only purges JS-side listeners, matching the js_event_listeners map it operates on. Addresses @Legend-Master's review feedback. --- crates/tauri/src/event/listener.rs | 4 ++-- crates/tauri/src/manager/mod.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/tauri/src/event/listener.rs b/crates/tauri/src/event/listener.rs index 9b8375abc764..bed30b715225 100644 --- a/crates/tauri/src/event/listener.rs +++ b/crates/tauri/src/event/listener.rs @@ -256,7 +256,7 @@ impl Listeners { /// Called when a webview is destroyed: its JavaScript runtime no longer /// exists, so the listeners keyed by its label can never be delivered and /// would otherwise leak in the map until the app exits. - pub(crate) fn remove_webview_events(&self, webview_label: &str) { + pub(crate) fn remove_webview_js_listeners(&self, webview_label: &str) { self .inner .js_event_listeners @@ -431,7 +431,7 @@ mod test { assert!(listeners.has_js_listener(event.as_str_event(), |_| true)); // dropping the source webview must drop its JS listeners. - listeners.remove_webview_events(webview_label); + listeners.remove_webview_js_listeners(webview_label); assert!(!listeners.has_js_listener(event.as_str_event(), |_| true)); } } diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index 3e9d79880411..c2ebbd001a9c 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -655,7 +655,7 @@ impl AppManager { if let Some(window) = window { for webview in window.webviews() { self.webview.webviews_lock().remove(webview.label()); - self.listeners().remove_webview_events(webview.label()); + self.listeners().remove_webview_js_listeners(webview.label()); } } } @@ -663,7 +663,7 @@ impl AppManager { #[cfg(desktop)] pub(crate) fn on_webview_close(&self, label: &str) { self.webview.webviews_lock().remove(label); - self.listeners().remove_webview_events(label); + self.listeners().remove_webview_js_listeners(label); } pub fn windows(&self) -> HashMap> { From d2edb357b89695d804db3d6f04056a80af65751b Mon Sep 17 00:00:00 2001 From: tenderdeve Date: Mon, 6 Jul 2026 12:13:41 +0530 Subject: [PATCH 3/3] style(core): rustfmt remove_webview_js_listeners call site --- crates/tauri/src/manager/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index c2ebbd001a9c..ccc5ffbaf896 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -655,7 +655,9 @@ impl AppManager { if let Some(window) = window { for webview in window.webviews() { self.webview.webviews_lock().remove(webview.label()); - self.listeners().remove_webview_js_listeners(webview.label()); + self + .listeners() + .remove_webview_js_listeners(webview.label()); } } }