Skip to content

Commit 8188d35

Browse files
committed
fix(linux): remove harmful request loading workaround
The `WebViewUriLoader` is a workaround for an unknown concurrency bug in WebKitGTK. It is now removed because * it introduces a deadlock situation, * it disables parallel loading when initializing multiple webviews, and * the bug can no longer be reproduced using current WebKitGTK versions. Closes tauri-apps/tauri#12589 Refs #359
1 parent 4b44ebb commit 8188d35

3 files changed

Lines changed: 28 additions & 158 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
wry: patch
3+
---
4+
5+
On Linux, removed a workaround which forced inital requests for multiple webviews to be handled sequentially.
6+
The workaround was intended to fix a concurrency bug with loading multiple URIs at the same time on WebKitGTK.
7+
But it prevented parallelization and could cause a deadlock in certain situations.
8+
It is no longer needed with newer WebKitGTK versions.

src/webkitgtk/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,8 +366,7 @@ impl InnerWebView {
366366

367367
// Navigation
368368
if let Some(url) = attributes.url {
369-
web_context.queue_load_uri(w.webview.clone(), url, attributes.headers);
370-
web_context.flush_queue_loader();
369+
web_context.load_uri(w.webview.clone(), url, attributes.headers);
371370
} else if let Some(html) = attributes.html {
372371
w.webview.load_html(&html, None);
373372
}

src/webkitgtk/web_context.rs

Lines changed: 19 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,15 @@
55
//! Unix platform extensions for [`WebContext`](super::WebContext).
66
77
use crate::{Error, RequestAsyncResponder};
8-
use gtk::{
9-
glib::{self, MainContext, ObjectExt},
10-
traits::WidgetExt,
11-
};
8+
use gtk::glib::{self, MainContext, ObjectExt};
129
use http::{header::CONTENT_TYPE, HeaderName, HeaderValue, Request, Response as HttpResponse};
1310
use soup::{MessageHeaders, MessageHeadersType};
1411
use std::{
1512
borrow::Cow,
1613
cell::RefCell,
17-
collections::VecDeque,
1814
env::current_dir,
1915
path::{Path, PathBuf},
2016
rc::Rc,
21-
sync::{
22-
atomic::{AtomicBool, Ordering::SeqCst},
23-
Mutex,
24-
},
2517
};
2618
use webkit2gtk::{
2719
ApplicationInfo, AutomationSessionExt, CookiePersistentStorage, DownloadExt, LoadEvent,
@@ -33,7 +25,6 @@ use webkit2gtk::{
3325
#[derive(Debug)]
3426
pub struct WebContextImpl {
3527
context: WebContext,
36-
webview_uri_loader: Rc<WebViewUriLoader>,
3728
automation: bool,
3829
app_info: Option<ApplicationInfo>,
3930
}
@@ -87,7 +78,6 @@ impl WebContextImpl {
8778
Self {
8879
context,
8980
automation,
90-
webview_uri_loader: Rc::default(),
9181
app_info: Some(app_info),
9282
}
9383
}
@@ -114,15 +104,8 @@ pub trait WebContextExt {
114104
where
115105
F: Fn(crate::WebViewId, Request<Vec<u8>>, RequestAsyncResponder) + 'static;
116106

117-
/// Add a [`WebView`] to the queue waiting to be opened.
118-
///
119-
/// See the [`WebViewUriLoader`] for more information.
120-
fn queue_load_uri(&self, webview: WebView, url: String, headers: Option<http::HeaderMap>);
121-
122-
/// Flush all queued [`WebView`]s waiting to load a uri.
123-
///
124-
/// See the [`WebViewUriLoader`] for more information.
125-
fn flush_queue_loader(&self);
107+
/// Loads a URI for a [`WebView`].
108+
fn load_uri(&self, webview: WebView, url: String, headers: Option<http::HeaderMap>);
126109

127110
/// If the context allows automation.
128111
///
@@ -279,12 +262,23 @@ impl WebContextExt for super::WebContext {
279262
Ok(())
280263
}
281264

282-
fn queue_load_uri(&self, webview: WebView, url: String, headers: Option<http::HeaderMap>) {
283-
self.os.webview_uri_loader.push(webview, url, headers)
284-
}
265+
fn load_uri(&self, webview: WebView, uri: String, headers: Option<http::HeaderMap>) {
266+
if let Some(headers) = headers {
267+
let req = URIRequest::builder().uri(&uri).build();
285268

286-
fn flush_queue_loader(&self) {
287-
self.os.webview_uri_loader.clone().flush()
269+
if let Some(ref mut req_headers) = req.http_headers() {
270+
for (header, value) in headers.iter() {
271+
req_headers.append(
272+
header.to_string().as_str(),
273+
value.to_str().unwrap_or_default(),
274+
);
275+
}
276+
}
277+
278+
webview.load_request(&req);
279+
} else {
280+
webview.load_uri(&uri);
281+
}
288282
}
289283

290284
fn allows_automation(&self) -> bool {
@@ -399,134 +393,3 @@ impl MainThreadRequest {
399393

400394
unsafe impl Send for MainThreadRequest {}
401395
unsafe impl Sync for MainThreadRequest {}
402-
403-
#[derive(Debug)]
404-
struct WebviewUriRequest {
405-
webview: WebView,
406-
uri: String,
407-
headers: Option<http::HeaderMap>,
408-
}
409-
410-
/// Prevents an unknown concurrency bug with loading multiple URIs at the same time on webkit2gtk.
411-
///
412-
/// Using the queue prevents data race issues with loading uris for multiple [`WebView`]s in the
413-
/// same context at the same time. Occasionally, the one of the [`WebView`]s will be clobbered
414-
/// and it's content will be injected into a different [`WebView`].
415-
///
416-
/// Example of `webview-c` clobbering `webview-b` while `webview-a` is okay:
417-
/// ```text
418-
/// webview-a triggers load-change::started
419-
/// URISchemeRequestCallback triggered with webview-a
420-
/// webview-a triggers load-change::committed
421-
/// webview-a triggers load-change::finished
422-
/// webview-b triggers load-change::started
423-
/// webview-c triggers load-change::started
424-
/// URISchemeRequestCallback triggered with webview-c
425-
/// URISchemeRequestCallback triggered with webview-c
426-
/// webview-c triggers load-change::committed
427-
/// webview-c triggers load-change::finished
428-
/// ```
429-
///
430-
/// In that example, `webview-a` will load fine. `webview-b` will remain empty as the uri was
431-
/// never loaded. `webview-c` will contain the content of both `webview-b` and `webview-c`
432-
/// because it was triggered twice even through only started once. The content injected will not
433-
/// be sequential, and often is interjected in the middle of one of the other contents.
434-
///
435-
/// FIXME: We think this may be an underlying concurrency bug in webkit2gtk as the usual ways of
436-
/// fixing threading issues are not working. Ideally, the locks are not needed if we can understand
437-
/// the true cause of the bug.
438-
#[derive(Debug, Default)]
439-
struct WebViewUriLoader {
440-
lock: AtomicBool,
441-
queue: Mutex<VecDeque<WebviewUriRequest>>,
442-
}
443-
444-
impl WebViewUriLoader {
445-
/// Check if the lock is in use.
446-
fn is_locked(&self) -> bool {
447-
self.lock.swap(true, SeqCst)
448-
}
449-
450-
/// Unlock the lock.
451-
fn unlock(&self) {
452-
self.lock.store(false, SeqCst)
453-
}
454-
455-
/// Add a [`WebView`] to the queue.
456-
fn push(&self, webview: WebView, uri: String, headers: Option<http::HeaderMap>) {
457-
let mut queue = self.queue.lock().expect("poisoned load queue");
458-
queue.push_back(WebviewUriRequest {
459-
webview,
460-
uri,
461-
headers,
462-
})
463-
}
464-
465-
/// Remove a [`WebView`] from the queue and return it.
466-
fn pop(&self) -> Option<WebviewUriRequest> {
467-
let mut queue = self.queue.lock().expect("poisoned load queue");
468-
queue.pop_front()
469-
}
470-
471-
/// Load the next uri to load if the lock is not engaged.
472-
fn flush(self: Rc<Self>) {
473-
if !self.is_locked() {
474-
if let Some(WebviewUriRequest {
475-
webview,
476-
uri,
477-
headers,
478-
}) = self.pop()
479-
{
480-
// ensure that the lock is released when the webview is destroyed before LoadEvent::Finished is handled
481-
let self_ = self.clone();
482-
let destroy_id = webview.connect_destroy(move |_| {
483-
self_.unlock();
484-
self_.clone().flush();
485-
});
486-
let destroy_id_guard = Mutex::new(Some(destroy_id));
487-
488-
let load_changed_id_guard = Rc::new(Mutex::new(None));
489-
let load_changed_id_guard_ = load_changed_id_guard.clone();
490-
let self_ = self.clone();
491-
// noet: we do not need to listen to failed events because those will finish the change event anyways
492-
let load_changed_id = webview.connect_load_changed(move |w, event| {
493-
if let LoadEvent::Finished = event {
494-
self_.unlock();
495-
self_.clone().flush();
496-
497-
// unregister listeners
498-
if let Some(id) = destroy_id_guard.lock().unwrap().take() {
499-
w.disconnect(id);
500-
}
501-
if let Some(id) = load_changed_id_guard_.lock().unwrap().take() {
502-
w.disconnect(id);
503-
}
504-
};
505-
});
506-
load_changed_id_guard
507-
.lock()
508-
.unwrap()
509-
.replace(load_changed_id);
510-
511-
if let Some(headers) = headers {
512-
let req = URIRequest::builder().uri(&uri).build();
513-
514-
if let Some(ref mut req_headers) = req.http_headers() {
515-
for (header, value) in headers.iter() {
516-
req_headers.append(
517-
header.to_string().as_str(),
518-
value.to_str().unwrap_or_default(),
519-
);
520-
}
521-
}
522-
523-
webview.load_request(&req);
524-
} else {
525-
webview.load_uri(&uri);
526-
}
527-
} else {
528-
self.unlock();
529-
}
530-
}
531-
}
532-
}

0 commit comments

Comments
 (0)