From b64de610626fb3ef9e21cf407c8ee3baa6e6494a Mon Sep 17 00:00:00 2001 From: Daniel Gerhardt Date: Tue, 3 Jun 2025 14:34:52 +0200 Subject: [PATCH 1/2] 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 --- .../fix-webkitgtk-webviewuriloader-deadlock.md | 5 +++++ src/webkitgtk/web_context.rs | 16 +++++++++++++--- 2 files changed, 18 insertions(+), 3 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 000000000..1c33a51d6 --- /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 42321ace0..19f958738 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::{ @@ -451,11 +454,18 @@ impl WebViewUriLoader { headers, }) = self.pop() { + // ensure that the lock is released when the webview is destroyed before LoadEvent::Finished is handled + let self_c = self.clone(); + webview.connect_destroy(move |_| { + self_c.unlock(); + self_c.clone().flush(); + }); // we do not need to listen to failed events because those will finish the change event anyways + let self_c = self.clone(); webview.connect_load_changed(move |_, event| { if let LoadEvent::Finished = event { - self.unlock(); - self.clone().flush(); + self_c.unlock(); + self_c.clone().flush(); }; }); From c33b46c7b46e1577f4f3d2e186c75f2681060c87 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Sat, 16 Aug 2025 10:13:37 -0300 Subject: [PATCH 2/2] unregister listeners --- src/webkitgtk/web_context.rs | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/webkitgtk/web_context.rs b/src/webkitgtk/web_context.rs index 1d9321dd2..e802733ab 100644 --- a/src/webkitgtk/web_context.rs +++ b/src/webkitgtk/web_context.rs @@ -478,19 +478,35 @@ impl WebViewUriLoader { }) = self.pop() { // ensure that the lock is released when the webview is destroyed before LoadEvent::Finished is handled - let self_c = self.clone(); - webview.connect_destroy(move |_| { - self_c.unlock(); - self_c.clone().flush(); + let self_ = self.clone(); + let destroy_id = webview.connect_destroy(move |_| { + self_.unlock(); + self_.clone().flush(); }); - // we do not need to listen to failed events because those will finish the change event anyways - let self_c = self.clone(); - webview.connect_load_changed(move |_, event| { + 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_c.unlock(); - self_c.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();