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/webview/webkitgtk/mod.rs b/src/webview/webkitgtk/mod.rs
index e8d6a6a7a..6fc0212fd 100644
--- a/src/webview/webkitgtk/mod.rs
+++ b/src/webview/webkitgtk/mod.rs
@@ -17,7 +17,8 @@ use std::{
 use url::Url;
 use webkit2gtk::{
   traits::*, LoadEvent, NavigationPolicyDecision, PolicyDecisionType, URIRequest,
-  UserContentInjectedFrames, UserScript, UserScriptInjectionTime, WebView, WebViewBuilder,
+  UserContentInjectedFrames, UserContentManager, UserScript, UserScriptInjectionTime, WebView,
+  WebViewBuilder,
 };
 use webkit2gtk_sys::{
   webkit_get_major_version, webkit_get_micro_version, webkit_get_minor_version,
@@ -63,9 +64,11 @@ impl InnerWebView {
       }
     };
 
+    let manager = UserContentManager::new();
+
     let webview = {
       let mut webview = WebViewBuilder::new();
-      webview = webview.user_content_manager(web_context.manager());
+      webview = webview.user_content_manager(&manager);
       webview = webview.web_context(web_context.context());
       webview = webview.is_controlled_by_automation(web_context.allows_automation());
       webview.build()
@@ -77,8 +80,6 @@ impl InnerWebView {
     let webview = Rc::new(webview);
     let w = window_rc.clone();
     let ipc_handler = attributes.ipc_handler.take();
-    let manager = web_context.manager();
-
     // Use the window hash as the script handler name to prevent from conflict when sharing same
     // web context.
     let window_hash = {
@@ -99,8 +100,8 @@ impl InnerWebView {
       }
     });
 
-    // Register the handler we just connected
-    manager.register_script_message_handler(&window_hash);
+    // Register handler on JS side
+    manager.register_script_message_handler("ipc");
 
     // Allow the webview to close it's own window
     let close_window = window_rc.clone();
@@ -355,10 +356,11 @@ impl InnerWebView {
     }
 
     for (name, handler) in attributes.custom_protocols {
-      match web_context.register_uri_scheme(&name, handler) {
+      match web_context.try_register_uri_scheme(&name, handler) {
         // Swallow duplicate scheme errors to preserve current behavior.
-        // FIXME: we should log this error in the future
-        Err(Error::DuplicateCustomProtocol(_)) => (),
+        Err(Error::DuplicateCustomProtocol(_)) => {
+          log::warn!("Ignoring already registered custom protocol {name}");
+        }
         Err(e) => return Err(e),
         Ok(_) => (),
       }
diff --git a/src/webview/webkitgtk/web_context.rs b/src/webview/webkitgtk/web_context.rs
index 1b4063993..c80a9f13d 100644
--- a/src/webview/webkitgtk/web_context.rs
+++ b/src/webview/webkitgtk/web_context.rs
@@ -21,14 +21,13 @@ use std::{
 };
 use url::Url;
 use webkit2gtk::{
-  traits::*, ApplicationInfo, CookiePersistentStorage, LoadEvent, URIRequest, UserContentManager,
-  WebContext, WebContextBuilder, WebView, WebsiteDataManagerBuilder,
+  traits::*, ApplicationInfo, CookiePersistentStorage, LoadEvent, URIRequest, WebContext,
+  WebContextBuilder, WebView, WebsiteDataManagerBuilder,
 };
 
 #[derive(Debug)]
 pub struct WebContextImpl {
   context: WebContext,
-  manager: UserContentManager,
   webview_uri_loader: Rc<WebviewUriLoader>,
   registered_protocols: HashSet<String>,
   automation: bool,
@@ -82,7 +81,6 @@ impl WebContextImpl {
     Self {
       context,
       automation,
-      manager: UserContentManager::new(),
       registered_protocols: Default::default(),
       webview_uri_loader: Rc::default(),
       app_info: Some(app_info),
@@ -101,9 +99,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
@@ -150,10 +145,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>>) -> crate::Result<Response<Cow<'static, [u8]>>> + 'static,


Test patch: