Repository: tauri-apps/wry (language: rust)

Issue:
IPC broken when sharing context between WebViews on Linux
**Describe the bug**
If I spawn multiple WebViews with a shared WebContext, IPC callbacks (from JS to Rust) are routed incorrectly. It is as if each window sent the same event separately.

**Steps To Reproduce**

Apply the following changes to `examples/multiwindow.rs`. It makes the windows use a shared WebContext.
<details><summary>Diff</summary>
<p>

```diff
diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs
index 1a95c09..5b50a2a 100644
--- a/examples/multiwindow.rs
+++ b/examples/multiwindow.rs
@@ -8,7 +8,7 @@ use tao::{
   event_loop::{ControlFlow, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget},
   window::{Window, WindowBuilder, WindowId},
 };
-use wry::{http::Request, WebView, WebViewBuilder};
+use wry::{http::Request, WebContext, WebView, WebViewBuilder};
 
 enum UserEvent {
   CloseWindow(WindowId),
@@ -20,11 +20,13 @@ fn main() -> wry::Result<()> {
   let event_loop = EventLoopBuilder::<UserEvent>::with_user_event().build();
   let mut webviews = HashMap::new();
   let proxy = event_loop.create_proxy();
+  let mut web_context = WebContext::new(None);
 
   let new_window = create_new_window(
     format!("Window {}", webviews.len() + 1),
     &event_loop,
     proxy.clone(),
+    &mut web_context,
   );
   webviews.insert(new_window.0.id(), (new_window.0, new_window.1));
 
@@ -47,6 +49,7 @@ fn main() -> wry::Result<()> {
           format!("Window {}", webviews.len() + 1),
           event_loop,
           proxy.clone(),
+          &mut web_context,
         );
         webviews.insert(new_window.0.id(), (new_window.0, new_window.1));
       }
@@ -69,6 +72,7 @@ fn create_new_window(
   title: String,
   event_loop: &EventLoopWindowTarget<UserEvent>,
   proxy: EventLoopProxy<UserEvent>,
+  web_context: &mut WebContext,
 ) -> (Window, WebView) {
   let window = WindowBuilder::new()
     .with_title(title)
@@ -114,6 +118,7 @@ fn create_new_window(
   };
 
   let webview = builder
+    .with_web_context(web_context)
     .with_html(
       r#"
         <button onclick="window.ipc.postMessage('new-window')">Open a new window</button>
```

</p>
</details> 

[Full example](https://gist.github.com/ollpu/76f7cea00788c0753209c95b3e6979e3)

Then:
- Start the example with `cargo run --example multiwindow`
- Open a new window using the button
- Try to change the title of either of the windows. Both windows change their title.
- Press the "new window" button again. This time two new windows open.

**Expected behavior**
As with the original example,
- only the current window changes its title based on the text field
- only one new window is opened each time the button is pressed

**Screenshots**
![image](https://github.com/tauri-apps/wry/assets/7241014/c5e902bb-6288-405f-9dd9-deae0f943ac9)


**Platform and Versions:**
OS: Ubuntu 22.04.4
Rustc: rustc 1.77.2 (25ef9e3d8 2024-04-09)

**Additional context**
I primarily want to share the WebContext between WebViews so that the `BroadcastChannel` API would work. It seems that on Linux / WebKitGTK, it doesn't work if the views are in separate contexts. (Same with live localStorage changes, etc.)

In Tauri the WebViews are currently always set up with separate contexts, **unless** `TAURI_AUTOMATION=true` (Tauri v1) or `TAURI_WEBVIEW_AUTOMATION=true` (Tauri v2) is set, in which case this bug is exhibited.


Discussion / hints:
I dug around a bit, and it seems like the underlying cause is that all of the WebViews share their `UserContentManager`, and the IPC callback is tied to that. It might make sense to use separate `UserContentManager`s for each view. Otherwise the IPC messages need to be discriminated in some other way (but then the signals would be unnecessarily fired multiple times for each event).

Edit: Init scripts are also shared within the `UserContentManager`, so I think they should be separate.

Fix patch:
diff --git a/.changes/linux-shared-webcontext-ipc.md b/.changes/linux-shared-webcontext-ipc.md
new file mode 100644
index 000000000..bcb966ffd
--- /dev/null
+++ b/.changes/linux-shared-webcontext-ipc.md
@@ -0,0 +1,5 @@
+---
+"wry": patch
+---
+
+Fix Linux IPC handler and initialization scripts when sharing a WebContext between multiple WebViews.
diff --git a/src/webkitgtk/mod.rs b/src/webkitgtk/mod.rs
index 5cb548b28..e92ee5651 100644
--- a/src/webkitgtk/mod.rs
+++ b/src/webkitgtk/mod.rs
@@ -28,7 +28,7 @@ use webkit2gtk::{
   AutoplayPolicy, InputMethodContextExt, LoadEvent, NavigationPolicyDecision,
   NavigationPolicyDecisionExt, NetworkProxyMode, NetworkProxySettings, PolicyDecisionType,
   PrintOperationExt, SettingsExt, URIRequest, URIRequestExt, UserContentInjectedFrames,
-  UserContentManagerExt, UserScript, UserScriptInjectionTime,
+  UserContentManager, UserContentManagerExt, UserScript, UserScriptInjectionTime,
   WebContextExt as Webkit2gtkWeContextExt, WebView, WebViewExt, WebsiteDataManagerExt,
   WebsiteDataManagerExtManual, WebsitePolicies,
 };
@@ -266,7 +266,7 @@ impl InnerWebView {
     Self::attach_handlers(&webview, web_context, &mut attributes);
 
     // IPC handler
-    Self::attach_ipc_handler(webview.clone(), web_context, &mut attributes);
+    Self::attach_ipc_handler(webview.clone(), &mut attributes);
 
     // Drag drop handler
     if let Some(drag_drop_handler) = attributes.drag_drop_handler.take() {
@@ -339,7 +339,7 @@ impl InnerWebView {
 
   fn create_webview(web_context: &WebContext, attributes: &WebViewAttributes) -> WebView {
     let mut builder = WebView::builder()
-      .user_content_manager(web_context.manager())
+      .user_content_manager(&UserContentManager::new())
       .web_context(web_context.context())
       .is_controlled_by_automation(web_context.allows_automation());
 
@@ -511,14 +511,12 @@ impl InnerWebView {
     is_in_fixed_parent
   }
 
-  fn attach_ipc_handler(
-    webview: WebView,
-    web_context: &WebContext,
-    attributes: &mut WebViewAttributes,
-  ) {
+  fn attach_ipc_handler(webview: WebView, attributes: &mut WebViewAttributes) {
     // Message handler
     let ipc_handler = attributes.ipc_handler.take();
-    let manager = web_context.manager();
+    let manager = webview
+      .user_content_manager()
+      .expect("WebView does not have UserContentManager");
 
     // Connect before registering as recommended by the docs
     manager.connect_script_message_received(None, move |_m, msg| {
diff --git a/src/webkitgtk/web_context.rs b/src/webkitgtk/web_context.rs
index df12dec89..90c93f9e8 100644
--- a/src/webkitgtk/web_context.rs
+++ b/src/webkitgtk/web_context.rs
@@ -22,14 +22,13 @@ use std::{
 use webkit2gtk::{
   ApplicationInfo, AutomationSessionExt, CookiePersistentStorage, DownloadExt, LoadEvent,
   SecurityManagerExt, URIRequest, URIRequestExt, URISchemeRequest, URISchemeRequestExt,
-  URISchemeResponse, URISchemeResponseExt, UserContentManager, WebContext,
-  WebContextExt as Webkit2gtkContextExt, WebView, WebViewExt,
+  URISchemeResponse, URISchemeResponseExt, WebContext, WebContextExt as Webkit2gtkContextExt,
+  WebView, WebViewExt,
 };
 
 #[derive(Debug)]
 pub struct WebContextImpl {
   context: WebContext,
-  manager: UserContentManager,
   webview_uri_loader: Rc<WebViewUriLoader>,
   registered_protocols: HashSet<String>,
   automation: bool,
@@ -90,7 +89,6 @@ impl WebContextImpl {
     Self {
       context,
       automation,
-      manager: UserContentManager::new(),
       registered_protocols: Default::default(),
       webview_uri_loader: Rc::default(),
       app_info: Some(app_info),
@@ -108,9 +106,6 @@ pub trait WebContextExt {
   /// The GTK [`WebContext`] of all webviews in the context.
   fn context(&self) -> &WebContext;
 
-  /// The GTK [`UserContentManager`] of all webviews in the context.
-  fn manager(&self) -> &UserContentManager;
-
   /// Register a custom protocol to the web context.
   ///
   /// When duplicate schemes are registered, the duplicate handler will still be submitted and the
@@ -157,10 +152,6 @@ impl WebContextExt for super::WebContext {
     &self.os.context
   }
 
-  fn manager(&self) -> &UserContentManager {
-    &self.os.manager
-  }
-
   fn register_uri_scheme<F>(&mut self, name: &str, handler: F) -> crate::Result<()>
   where
     F: Fn(Request<Vec<u8>>, RequestAsyncResponder) + 'static,


Test patch: