Skip to content

Commit ca0c1b2

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 ca0c1b2

3 files changed

Lines changed: 27 additions & 155 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: 18 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,15 @@
77
use crate::{Error, RequestAsyncResponder};
88
use gtk::{
99
glib::{self, MainContext, ObjectExt},
10-
traits::WidgetExt,
1110
};
1211
use http::{header::CONTENT_TYPE, HeaderName, HeaderValue, Request, Response as HttpResponse};
1312
use soup::{MessageHeaders, MessageHeadersType};
1413
use std::{
1514
borrow::Cow,
1615
cell::RefCell,
17-
collections::VecDeque,
1816
env::current_dir,
1917
path::{Path, PathBuf},
2018
rc::Rc,
21-
sync::{
22-
atomic::{AtomicBool, Ordering::SeqCst},
23-
Mutex,
24-
},
2519
};
2620
use webkit2gtk::{
2721
ApplicationInfo, AutomationSessionExt, CookiePersistentStorage, DownloadExt, LoadEvent,
@@ -33,7 +27,6 @@ use webkit2gtk::{
3327
#[derive(Debug)]
3428
pub struct WebContextImpl {
3529
context: WebContext,
36-
webview_uri_loader: Rc<WebViewUriLoader>,
3730
automation: bool,
3831
app_info: Option<ApplicationInfo>,
3932
}
@@ -87,7 +80,6 @@ impl WebContextImpl {
8780
Self {
8881
context,
8982
automation,
90-
webview_uri_loader: Rc::default(),
9183
app_info: Some(app_info),
9284
}
9385
}
@@ -114,15 +106,8 @@ pub trait WebContextExt {
114106
where
115107
F: Fn(crate::WebViewId, Request<Vec<u8>>, RequestAsyncResponder) + 'static;
116108

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);
109+
/// Loads a URI for a [`WebView`].
110+
fn load_uri(&self, webview: WebView, url: String, headers: Option<http::HeaderMap>);
126111

127112
/// If the context allows automation.
128113
///
@@ -279,12 +264,23 @@ impl WebContextExt for super::WebContext {
279264
Ok(())
280265
}
281266

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-
}
267+
fn load_uri(&self, webview: WebView, uri: String, headers: Option<http::HeaderMap>) {
268+
if let Some(headers) = headers {
269+
let req = URIRequest::builder().uri(&uri).build();
285270

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

290286
fn allows_automation(&self) -> bool {
@@ -399,134 +395,3 @@ impl MainThreadRequest {
399395

400396
unsafe impl Send for MainThreadRequest {}
401397
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)