fix(windows)!: restore webview focus for unstable feature - #15625
Conversation
Package Changes Through 4f1c912There are 14 changes which include tauri with minor, tauri-cli with minor, @tauri-apps/cli with minor, tauri-runtime with minor, tauri-runtime-wry with minor, tauri-utils with minor, tauri-bundler with minor, tauri-build with minor, tauri-macos-sign with minor, tauri-codegen with minor, tauri-macros with minor, tauri-plugin with minor, tauri-driver with minor, @tauri-apps/api with minor Planned Package VersionsThe following package releases are the planned based on the context of changes in this pull request.
Add another change file through the GitHub UI by following this link. Read about change files or the docs at github.com/jbolda/covector |
| let focused_webview = Arc::new(Mutex::new(FocusState::default())); | ||
|
|
||
| #[cfg(feature = "unstable")] | ||
| let has_children = webview.is_some(); |
There was a problem hiding this comment.
@lucasfernog You might want to double check if this is correct
|
does this PR in wry tauri-apps/wry#1755 tries to fix the same issue? also should we maybe fix it in wry or not? |
tauri-apps/wry#1755 doesn't make any sense to me at least at a glance, it just tries to attach to the subclass to the first webview in the window.
If |
|
We hit this exact bug in our app ( The restore here runs on tao's That's why our wry-side fix (tauri-apps/wry#1755) hooks Has this been tested against a WebView2 runtime new enough to show the direct-to-child focus restore behavior? If |
|
@zendy00 This is tested on Webview2 149, and alt-tab does give the focus to the tao window for me |
|
Thanks for checking this. I re-tested in our app by applying this PR through With that setup, Alt+Tab focus restoration works correctly for our Windows Really glad to see this fixed on the Tauri side. Thanks for the quick clarification and for working on this. |
Backport of upstream 08acfb3 ("fix(windows)!: restore webview focus for unstable feature", tauri-apps#15625), merged for 2.12 while 2.11.5 is still the latest release. Cherry-picked rather than waited for because a downstream consumer needs it now: a window with a child webview could not tell "the keyboard moved into my own webview" from "the user switched application". Both look the same from the parent. Windows sends WM_KILLFOCUS to a top-level whenever the keyboard leaves it -- including into a child of that very window -- and tao turns that into Focused(false). So the first click into web content reported the window as unfocused, and an app that dismisses itself on blur dismissed itself the instant it was first clicked. The check that survived that reported the opposite: a thread's focus state outlives it losing the foreground, so once a click had put the keyboard in the WebView2 child the panel's own thread went on naming that child even after another application owned the desktop, and the dismissal never fired at all. Neither read can be fixed downstream -- at WM_KILLFOCUS time the activation change has not completed, so a synchronous GetGUIThreadInfo(0) either still reports us or answers a null hwndFocus mid-transition. Upstream's answer is to stop asking the parent. With a child webview present the raw parent blur is suppressed and WebView2's own add_LostFocus becomes the one blur producer, which fires after focus has genuinely moved away. Focus gains are deduplicated through a three-state FocusState, which also restores focus to the last focused webview on Alt+Tab back and stops a window drag emitting Focused. CONSEQUENCE FOR CALLERS, and it is not optional: a window whose child webview has never held focus now emits no blur at all, because the parent's is suppressed and the webview has none to lose. Anything relying on aggregate blur must focus the child explicitly when it reveals the window. The three .changes/ fragments are deliberately not carried. No commit on this patch branch has ever carried one, covector never runs downstream, and upstream will have consumed and deleted those files by the release that makes this backport redundant -- so keeping them would only make the rebase that drops this commit conflict over files that no longer exist.
WebView2 raises `LostFocus` whenever the webview stops holding the keyboard, and that includes the keyboard moving to the webview's OWN top-level window. Every `SetWindowPos` that moves or resizes the webview does exactly that. The `add_LostFocus` handler took "this webview lost focus" to mean "this window lost focus", so a window which repositions itself while open raises a spurious `WindowEvent::Focused(false)` per frame it applies. MEASURED, on a downstream tray panel that sizes itself to its content: 30 of 31 blur events landed within 50ms of a window move and 15 of them exactly 1ms after, one pair per show, with the window closing itself 27-37ms after every open. The previous commit is what surfaced it -- before it, the raw parent blur was the one an app saw and this one was redundant -- so the two belong together even though only the other is upstream. Upstream is aware of the phenomenon in the other direction: the same PR carries "Fix `WindowEvent::Focused` events emitted when dragging the window on Windows", which dedupes the focus GAIN. The loss still fires. `GetFocus` is the right call here and would be wrong almost anywhere else. It answers about the CALLING THREAD's message queue, and a WebView2 event handler runs on the thread that owns the controller, so this is the one place that can ask it and get a meaningful answer. It is also correctly timed: `LostFocus` is raised after the focus change has completed, unlike `WM_KILLFOCUS`, where the activation change is still in flight -- which is exactly why the same question cannot be answered from a window-blur handler, and why downstream attempts to do so failed. A null answer is not "unknown": it means our thread holds no focus at all, which is the real departure this event exists to report. Rooted with `GA_ROOT` because the focus may be on any descendant and the container is itself a child. The check runs BEFORE the state machine, so a move leaves `FocusState` alone. Recording `Blured` there would emit a blur nobody caused and make the focus gain that follows look like a fresh activation rather than the continuation it is. Focus moving to a different top-level of the same process still reads as a real blur, correctly: that window's root is not ours. NOT AN UPSTREAM CHERRY-PICK, unlike the commit below it, so it does not disappear when this branch rebases onto a release containing tauri-apps#15625. It wants to be a PR against `tauri-apps/tauri`; until then it is a carried patch.
Fix #15624
Fix #15626
Closes tauri-apps/wry#1755
unstablefeature is enabled on WindowsWindowEvent::Focusedevents emitted when dragging the window on WindowsCreateWebviewOptions::focused_webviewnow takesArc<Mutex<FocusState>>instead ofArc<Mutex<Option<String>>>