Describe the bug
WebviewWindowBuilder::on_new_window never fires when a user clicks a real <a href="..." target="_blank"> anchor inside a window created with WebviewUrl::External(...), on Windows/WebView2.
This was found while building a desktop wrapper around web.whatsapp.com (loaded via WebviewUrl::External). Clicking any link inside a chat message does nothing at all — no new window, no navigation, no error.
Reproduction
let builder = WebviewWindowBuilder::new(app, &label, WebviewUrl::External(url))
// ... other builder calls (user_agent, initialization_script, on_navigation, on_download) ...
.on_new_window({
let app = app.clone();
move |url, _features| {
// Neither of these ever run:
unsafe {
use windows::core::PCWSTR;
use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK};
let msg: Vec<u16> = format!("on_new_window fired: {url}\0").encode_utf16().collect();
let title: Vec<u16> = "diag\0".encode_utf16().collect();
MessageBoxW(None, PCWSTR(msg.as_ptr()), PCWSTR(title.as_ptr()), MB_OK);
}
tauri::webview::NewWindowResponse::Deny
}
});
Then click any <a target="_blank"> link on the loaded external page.
Expected behavior
The on_new_window closure should be invoked, per the docs ("A new window is requested to be opened by the window.open API"), and MDN/Chromium semantics where target="_blank" anchor clicks also request a new browsing context.
Actual behavior
The closure never runs. Confirmed with escalating diagnostics, from least to most invasive:
crate::dlog::log(...) (plain file write) inside the closure — never appears in the log file.
- A raw, synchronous
MessageBoxW call inside the closure — no dialog ever appears, ruling out any async/threading/IPC confound.
Meanwhile, confirmed via a page-injected diagnostic (monkey-patched window.open + a capture-phase click listener, both using alert() so no console/IPC dependency) that:
- The click target is a real anchor:
<a href="https://google.com/" target="_blank"> (tested with multiple URLs, including plain https://google.com/, to rule out anything page/site-specific).
- The page never calls
window.open() — it relies purely on native browser handling of target="_blank", which is normal/expected behavior for any website.
So a genuine, native, user-initiated target="_blank" anchor click produces literally no event reaching on_new_window — not even a native win32 MessageBoxW invoked synchronously inside the handler appears.
Investigation notes
I reviewed the relevant source on both sides of the stack (versions below) and didn't find an obvious gap:
crates/tauri/src/webview/mod.rs: on_new_window stores the closure in new_window_handler; into_pending_webview unconditionally threads it into pending.new_window_handler — no branch that would skip this for WebviewUrl::External.
wry src/webview2/mod.rs: add_NewWindowRequested is registered unconditionally (not gated behind if let Some(new_window_req_handler) = ... the way the WebKitGTK backend does it); the handler checks if let Some(new_window_req_handler) = &new_window_req_handler { ... } else { args.SetHandled(true) }. Since a handler is configured in our case, it should reach the Some branch and invoke it via Self::dispatch_handler(hwnd, ...).
Given the else branch (args.SetHandled(true) with no further action) is exactly what silently swallows the event with no registered handler, this may hint at where a default app (no on_new_window at all) goes silent — but doesn't explain why a registered handler also never fires.
I don't yet have a minimal standalone reproduction (this was found inside a larger app — an unofficial WhatsApp Web desktop wrapper), but I can put one together if that would help triage. Happy to provide more detail (full on_navigation/on_download config, tauri.conf.json, etc.) on request.
Platform and versions
tauri: 2.11.2
tauri-runtime-wry: 2.11.2
wry: 0.55.1
webview2-com: 0.38
OS: Windows 11 Pro (10.0.26200)
WebView2 runtime: Evergreen (current)
App config relevant bits:
WebviewUrl::External("https://web.whatsapp.com/")
app.security.csp: null in tauri.conf.json (loading a remote origin we don't control)
withGlobalTauri: true
Additional context
Separately (possibly related, possibly not): the page's own CSP (connect-src sent by the remote server) blocks Tauri's http://ipc.localhost IPC channel entirely (Refused to connect to 'http://ipc.localhost/...' because it violates the Content Security Policy, then falls back to postMessage, which in our testing also never reaches Rust for any command). This is a documented/known limitation for remote pages with csp: null (e.g. #8476, #12835). I originally suspected that was the root cause of the link-opening issue too, and switched to on_new_window specifically because it should run entirely on the Rust/native side without touching page IPC — but as detailed above, it doesn't fire either, so I don't think the two issues share a root cause.
Describe the bug
WebviewWindowBuilder::on_new_windownever fires when a user clicks a real<a href="..." target="_blank">anchor inside a window created withWebviewUrl::External(...), on Windows/WebView2.This was found while building a desktop wrapper around
web.whatsapp.com(loaded viaWebviewUrl::External). Clicking any link inside a chat message does nothing at all — no new window, no navigation, no error.Reproduction
Then click any
<a target="_blank">link on the loaded external page.Expected behavior
The
on_new_windowclosure should be invoked, per the docs ("A new window is requested to be opened by the window.open API"), and MDN/Chromium semantics wheretarget="_blank"anchor clicks also request a new browsing context.Actual behavior
The closure never runs. Confirmed with escalating diagnostics, from least to most invasive:
crate::dlog::log(...)(plain file write) inside the closure — never appears in the log file.MessageBoxWcall inside the closure — no dialog ever appears, ruling out any async/threading/IPC confound.Meanwhile, confirmed via a page-injected diagnostic (monkey-patched
window.open+ a capture-phaseclicklistener, both usingalert()so no console/IPC dependency) that:<a href="https://google.com/" target="_blank">(tested with multiple URLs, including plainhttps://google.com/, to rule out anything page/site-specific).window.open()— it relies purely on native browser handling oftarget="_blank", which is normal/expected behavior for any website.So a genuine, native, user-initiated
target="_blank"anchor click produces literally no event reachingon_new_window— not even a native win32 MessageBoxW invoked synchronously inside the handler appears.Investigation notes
I reviewed the relevant source on both sides of the stack (versions below) and didn't find an obvious gap:
crates/tauri/src/webview/mod.rs:on_new_windowstores the closure innew_window_handler;into_pending_webviewunconditionally threads it intopending.new_window_handler— no branch that would skip this forWebviewUrl::External.wrysrc/webview2/mod.rs:add_NewWindowRequestedis registered unconditionally (not gated behindif let Some(new_window_req_handler) = ...the way the WebKitGTK backend does it); the handler checksif let Some(new_window_req_handler) = &new_window_req_handler { ... } else { args.SetHandled(true) }. Since a handler is configured in our case, it should reach theSomebranch and invoke it viaSelf::dispatch_handler(hwnd, ...).Given the
elsebranch (args.SetHandled(true)with no further action) is exactly what silently swallows the event with no registered handler, this may hint at where a default app (noon_new_windowat all) goes silent — but doesn't explain why a registered handler also never fires.I don't yet have a minimal standalone reproduction (this was found inside a larger app — an unofficial WhatsApp Web desktop wrapper), but I can put one together if that would help triage. Happy to provide more detail (full
on_navigation/on_downloadconfig,tauri.conf.json, etc.) on request.Platform and versions
App config relevant bits:
WebviewUrl::External("https://web.whatsapp.com/")app.security.csp: nullintauri.conf.json(loading a remote origin we don't control)withGlobalTauri: trueAdditional context
Separately (possibly related, possibly not): the page's own CSP (
connect-srcsent by the remote server) blocks Tauri'shttp://ipc.localhostIPC channel entirely (Refused to connect to 'http://ipc.localhost/...' because it violates the Content Security Policy, then falls back to postMessage, which in our testing also never reaches Rust for any command). This is a documented/known limitation for remote pages withcsp: null(e.g. #8476, #12835). I originally suspected that was the root cause of the link-opening issue too, and switched toon_new_windowspecifically because it should run entirely on the Rust/native side without touching page IPC — but as detailed above, it doesn't fire either, so I don't think the two issues share a root cause.