[bug] WebView2 custom protocol can clone DispatcherMainThreadContext off the main thread
#15408 opened on May 18, 2026
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 containsmain_thread: DispatcherMainThreadContext<T>.DispatcherMainThreadContext<T>is#[derive(Debug, Clone)]and containswindow_target: EventLoopWindowTarget<Message<T>>.DispatcherMainThreadContext<T>has unsafeSendandSyncimpls with the commentwe ensure this type is only used on the main thread.- The adjacent field
windowsis already anArc<WindowsStore>with the commentchanging this to an Rc will cause frequent app crashes, butwindow_targetstill contains tao's internalRcstate.
tauri-runtime 2.11.2,src/webview.rs:DetachedWebview::cloneclonesdispatcher.
tauri 2.11.2,src/webview/mod.rs:Webview::cloneclones theDetachedWebview.
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 currentWebResourceRequestedcallback 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:
- Dispatch the wry
custom_protocol_handlerinvocation itself onto the WebView/main thread on Windows, not only the async response setter. - Avoid deriving
CloneforDispatcherMainThreadContext/Contextin a way that clonesEventLoopWindowTargetfrom arbitrary threads. - Split main-thread-only state out of dispatcher handles so cloning a
Webview/dispatcher from protocol callbacks does not clone taoRcstate.
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.