tauri-apps/tauri

[bug] WebView2 custom protocol can clone DispatcherMainThreadContext off the main thread

Open

#15408 opened on May 18, 2026

View on GitHub
 (0 comments) (0 reactions) (0 assignees)Rust (106,642 stars) (3,597 forks)batch import
help wantedpriority: 0 crashtype: bug

Description

Describe the bug

A Windows WebView2 custom protocol request can call Tauri's custom protocol handler from a non-main thread. If the handler path looks up and clones a Webview, this can clone tauri_runtime_wry::DispatcherMainThreadContext off the main thread and trip the unsafe Rc precondition inside tao::EventLoopWindowTarget.

In our app this was observed as an unsafe precondition violation around Rc::inc_strong / strong_count == 0 while cloning a Tauri webview during custom protocol handling.

The suspected chain is:

text Tauri::Webview::clone -> DetachedWebview::clone -> WryWebviewDispatcher::clone -> Context::clone -> DispatcherMainThreadContext::clone -> EventLoopWindowTarget::clone -> Rc<EventLoopRunner>::clone

DispatcherMainThreadContext has unsafe Send / Sync impls with the safety comment saying the type is only used on the main thread, but the derived Clone can still be reached from non-main-thread custom protocol callbacks.

Source locations checked

This is still present in the latest published crates at the time of filing:

  • tauri-runtime-wry 2.11.2, src/lib.rs:
    • Context<T> is #[derive(Clone)] and contains main_thread: DispatcherMainThreadContext<T>.
    • DispatcherMainThreadContext<T> is #[derive(Debug, Clone)] and contains window_target: EventLoopWindowTarget<Message<T>>.
    • DispatcherMainThreadContext<T> has unsafe Send and Sync impls with the comment we ensure this type is only used on the main thread.
    • The adjacent field windows is already an Arc<WindowsStore> with the comment changing this to an Rc will cause frequent app crashes, but window_target still contains tao's internal Rc state.
  • tauri-runtime 2.11.2, src/webview.rs:
    • DetachedWebview::clone clones dispatcher.
  • tauri 2.11.2, src/webview/mod.rs:
    • Webview::clone clones the DetachedWebview.
  • wry 0.55.1, src/webview2/mod.rs, custom protocol handling:
    • The async response setter dispatches back to the main thread when needed.
    • But custom_protocol_handler(&webview_id, request, RequestAsyncResponder { ... }) itself is invoked directly on the current WebResourceRequested callback thread.

Why this seems unsafe

The Tauri runtime safety invariant appears to be that DispatcherMainThreadContext is only used on the main thread. However, on Windows/WebView2, WebResourceRequested can be observed during shutdown/restart or internal WebView2 work from a non-main thread. In that case, the wry custom protocol handler is called without being dispatched to the main thread first.

If app/framework code inside that handler resolves a webview and clones it, DispatcherMainThreadContext::clone clones EventLoopWindowTarget, which ultimately clones non-atomic Rc state from the wrong thread. This violates the safety invariant.

There is an older related issue, #10001, also titled around DispatcherMainThreadContext unsafe precondition violations, but the path described here is different: WebView2 custom protocol handling -> webview clone -> window_target clone.

Expected behavior

Custom protocol handling should not be able to clone or otherwise touch DispatcherMainThreadContext / EventLoopWindowTarget off the main thread.

Possible fixes

A few possible directions, depending on intended ownership between Tauri/wry/tao:

  1. Dispatch the wry custom_protocol_handler invocation itself onto the WebView/main thread on Windows, not only the async response setter.
  2. Avoid deriving Clone for DispatcherMainThreadContext / Context in a way that clones EventLoopWindowTarget from arbitrary threads.
  3. Split main-thread-only state out of dispatcher handles so cloning a Webview/dispatcher from protocol callbacks does not clone tao Rc state.

Versions

Our affected lockfile currently has:

text tauri = 2.10.3 tauri-runtime-wry = 2.10.1 wry = 0.54.4 tao = 0.34.8

I also checked the latest published crates and the relevant code shape still appears unchanged:

text tauri = 2.11.2 tauri-runtime-wry = 2.11.2 wry = 0.55.1 tao = 0.35.x

Platform

Windows, WebView2 backend.

Additional context

I do not yet have a minimal public reproduction project. This issue is filed from a crash investigation plus source-level tracing. The important part is that custom_protocol_handler can run outside the main thread while Tauri's Webview::clone path can clone main-thread-only tao state.

Contributor guide