Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/fix-js-listeners-leak-on-webview-close.md
Original file line number Diff line number Diff line change
@@ -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.
36 changes: 36 additions & 0 deletions crates/tauri/src/event/listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_js_listeners(&self, webview_label: &str) {
self
.inner
.js_event_listeners
.lock()
.unwrap()
.remove(webview_label);
}

pub(crate) fn has_js_listener<F: Fn(&EventTarget) -> bool>(
&self,
event: crate::EventName<&str>,
Expand Down Expand Up @@ -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_js_listeners(webview_label);
assert!(!listeners.has_js_listener(event.as_str_event(), |_| true));
}
}
4 changes: 4 additions & 0 deletions crates/tauri/src/manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,13 +655,17 @@ impl<R: Runtime> AppManager<R> {
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());
}
}
}

#[cfg(desktop)]
pub(crate) fn on_webview_close(&self, label: &str) {
self.webview.webviews_lock().remove(label);
self.listeners().remove_webview_js_listeners(label);
}

pub fn windows(&self) -> HashMap<String, Window<R>> {
Expand Down
Loading