From 88cbb019a8d38fbcf7ad015d5f34f9a838a88f98 Mon Sep 17 00:00:00 2001 From: Daniel Gerhardt Date: Sat, 16 Aug 2025 15:20:42 +0200 Subject: [PATCH] fix(linux): deadlock in WebViewUriLoader (#1561) * fix(linux): deadlock in WebViewUriLoader A deadlock could occur, when a WebView was destroyed before handling `LoadEvent::Finished`. Refs https://github.com/tauri-apps/tauri/issues/12589 * unregister listeners --------- Co-authored-by: Lucas Nogueira --- ...fix-webkitgtk-webviewuriloader-deadlock.md | 5 +++ src/webkitgtk/web_context.rs | 36 ++++++++++++++++--- 2 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 .changes/fix-webkitgtk-webviewuriloader-deadlock.md diff --git a/.changes/fix-webkitgtk-webviewuriloader-deadlock.md b/.changes/fix-webkitgtk-webviewuriloader-deadlock.md new file mode 100644 index 0000000..1c33a51 --- /dev/null +++ b/.changes/fix-webkitgtk-webviewuriloader-deadlock.md @@ -0,0 +1,5 @@ +--- +wry: patch +--- + +On Linux, fix a deadlock, which could occur when destroying a WebView before loading has finished. diff --git a/src/webkitgtk/web_context.rs b/src/webkitgtk/web_context.rs index 901ceb8..e802733 100644 --- a/src/webkitgtk/web_context.rs +++ b/src/webkitgtk/web_context.rs @@ -5,7 +5,10 @@ //! Unix platform extensions for [`WebContext`](super::WebContext). use crate::{Error, RequestAsyncResponder}; -use gtk::glib::{self, MainContext, ObjectExt}; +use gtk::{ + glib::{self, MainContext, ObjectExt}, + traits::WidgetExt, +}; use http::{header::CONTENT_TYPE, HeaderName, HeaderValue, Request, Response as HttpResponse}; use soup::{MessageHeaders, MessageHeadersType}; use std::{ @@ -474,13 +477,36 @@ impl WebViewUriLoader { headers, }) = self.pop() { - // we do not need to listen to failed events because those will finish the change event anyways - webview.connect_load_changed(move |_, event| { + // ensure that the lock is released when the webview is destroyed before LoadEvent::Finished is handled + let self_ = self.clone(); + let destroy_id = webview.connect_destroy(move |_| { + self_.unlock(); + self_.clone().flush(); + }); + let destroy_id_guard = Mutex::new(Some(destroy_id)); + + let load_changed_id_guard = Rc::new(Mutex::new(None)); + let load_changed_id_guard_ = load_changed_id_guard.clone(); + let self_ = self.clone(); + // noet: we do not need to listen to failed events because those will finish the change event anyways + let load_changed_id = webview.connect_load_changed(move |w, event| { if let LoadEvent::Finished = event { - self.unlock(); - self.clone().flush(); + self_.unlock(); + self_.clone().flush(); + + // unregister listeners + if let Some(id) = destroy_id_guard.lock().unwrap().take() { + w.disconnect(id); + } + if let Some(id) = load_changed_id_guard_.lock().unwrap().take() { + w.disconnect(id); + } }; }); + load_changed_id_guard + .lock() + .unwrap() + .replace(load_changed_id); if let Some(headers) = headers { let req = URIRequest::builder().uri(&uri).build();