Skip to content

Commit 0f51d67

Browse files
fix(linux): remove harmful request loading workaround (#1605)
* 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 * lint --------- Co-authored-by: Lucas Nogueira <[email protected]>
1 parent aaafa7c commit 0f51d67

File tree

4 files changed

+33
-164
lines changed

4 files changed

+33
-164
lines changed
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/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1971,7 +1971,7 @@ impl WebView {
19711971
}
19721972

19731973
/// Returns the id of this webview.
1974-
pub fn id(&self) -> WebViewId {
1974+
pub fn id(&self) -> WebViewId<'_> {
19751975
self.webview.id()
19761976
}
19771977

src/webkitgtk/mod.rs

Lines changed: 2 additions & 3 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
}
@@ -670,7 +669,7 @@ impl InnerWebView {
670669
is_inspector_open
671670
}
672671

673-
pub fn id(&self) -> crate::WebViewId {
672+
pub fn id(&self) -> crate::WebViewId<'_> {
674673
&self.id
675674
}
676675

src/webkitgtk/web_context.rs

Lines changed: 22 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,25 @@
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::{
27-
ApplicationInfo, AutomationSessionExt, CookiePersistentStorage, DownloadExt, LoadEvent,
28-
SecurityManagerExt, URIRequest, URIRequestExt, URISchemeRequest, URISchemeRequestExt,
29-
URISchemeResponse, URISchemeResponseExt, WebContext, WebContextExt as Webkit2gtkContextExt,
30-
WebView, WebViewExt,
19+
ApplicationInfo, AutomationSessionExt, CookiePersistentStorage, DownloadExt, SecurityManagerExt,
20+
URIRequest, URIRequestExt, URISchemeRequest, URISchemeRequestExt, URISchemeResponse,
21+
URISchemeResponseExt, WebContext, WebContextExt as Webkit2gtkContextExt, WebView, WebViewExt,
3122
};
3223

3324
#[derive(Debug)]
3425
pub struct WebContextImpl {
3526
context: WebContext,
36-
webview_uri_loader: Rc<WebViewUriLoader>,
3727
automation: bool,
3828
app_info: Option<ApplicationInfo>,
3929
}
@@ -87,7 +77,6 @@ impl WebContextImpl {
8777
Self {
8878
context,
8979
automation,
90-
webview_uri_loader: Rc::default(),
9180
app_info: Some(app_info),
9281
}
9382
}
@@ -114,15 +103,8 @@ pub trait WebContextExt {
114103
where
115104
F: Fn(crate::WebViewId, Request<Vec<u8>>, RequestAsyncResponder) + 'static;
116105

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

127109
/// If the context allows automation.
128110
///
@@ -279,12 +261,23 @@ impl WebContextExt for super::WebContext {
279261
Ok(())
280262
}
281263

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

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

290283
fn allows_automation(&self) -> bool {
@@ -399,134 +392,3 @@ impl MainThreadRequest {
399392

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