Skip to content

fix(windows): restore keyboard focus for child webviews on window (re)activation#1755

Open
zendy00 wants to merge 1 commit into
tauri-apps:devfrom
zendy00:fix-windows-child-webview-focus
Open

fix(windows): restore keyboard focus for child webviews on window (re)activation#1755
zendy00 wants to merge 1 commit into
tauri-apps:devfrom
zendy00:fix-windows-child-webview-focus

Conversation

@zendy00

@zendy00 zendy00 commented Jun 24, 2026

Copy link
Copy Markdown

Addresses the is_child=true / multi-webview case of tauri-apps/tauri#15624.

Problem

On Windows, after the host window loses and regains activation (Alt+Tab, clicking another window or the taskbar), keyboard input no longer reaches WebView2 content until the user clicks inside the page — for webviews created with build_as_child (is_child=true), which is how Tauri's unstable multi-webview feature hosts even the main webview.

Two compounding causes:

  1. The focus-restoration subclass (parent_subclass_procMoveFocus(PROGRAMMATIC)) is attached only if !is_child, so child webviews get no focus restoration at all.
  2. On Alt+Tab-back, Windows routes focus straight to the WebView2 child window (Chrome_WidgetWin_1), so the top-level's WM_SETFOCUS never fires and even the existing MoveFocus remedy never runs (the failure-mode-B path described in Windows accessibility regression: WebView2 content becomes unreadable and keyboard-unfocusable after the host window loses and regains activation tauri#15624).

Fix

Single file (src/webview2/mod.rs):

  1. Attach the subclass for the first webview per parent (via a GetWindowSubclass guard), regardless of is_child. The main webview gets the handler; later children (e.g. additional build_as_child webviews / browser tabs) don't overwrite it; a standard single webview (!is_child) is also the first attach, so its behavior is unchanged.
  2. Add a WM_ACTIVATE arm that defers MoveFocus via a self-posted message. Calling MoveFocus immediately in WM_ACTIVATE is overridden by Windows' subsequent focus routing to the child; deferring it until the message is dispatched re-seeds focus correctly. (App-side attempts to do this loop at ~140–200 Hz, as noted in Windows accessibility regression: WebView2 content becomes unreadable and keyboard-unfocusable after the host window loses and regains activation tauri#15624 — doing it natively in the subclass avoids that.)
  3. Gate the Drop-time detach so only the webview that installed the subclass removes it, and re-seed focus when a non-owning child webview closes (so closing a build_as_child webview / browser tab no longer tears down the main webview's focus handler or leaves focus unseeded).

Verification

Manually verified on a Tauri 2.x app (wry 0.55.1, applied via [patch.crates-io]) that uses the unstable multi-webview feature — a terminal main webview plus build_as_child browser-tab webviews:

  • Alt+Tab away/back, and clicking another app / the taskbar and back → keyboard restored without a click.
  • Opening a child (browser tab), then Alt+Tab → main webview still restores.
  • Closing the child webview, then Alt+Tab → main webview still restores; no dangling-controller crash.
  • No focus loops and no crashes.

Notes

  • The reparent() detach/attach path is left if !self.is_child (unchanged, out of scope here).
  • WM_ACTIVATE's LOWORD == WA_INACTIVE (0) is skipped, so this only re-seeds on activation.

@zendy00 zendy00 requested a review from a team as a code owner June 24, 2026 07:25
@Legend-Master Legend-Master added the ai-slop Low effort content, see https://github.com/tauri-apps/tauri?tab=contributing-ov-file#ai-tool-policy label Jun 24, 2026
@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Package Changes Through 2caef22

There are 1 changes which include wry with minor

Planned Package Versions

The following package releases are the planned based on the context of changes in this pull request.

package current next
wry 0.55.1 0.56.0

Add another change file through the GitHub UI by following this link.


Read about change files or the docs at github.com/jbolda/covector

…)activation

After the host window loses and regains activation on Windows (Alt+Tab, clicking
another window or the taskbar), keyboard input no longer reached WebView2 content
until the user clicked inside the page — for webviews created with `build_as_child`
(`is_child=true`), which is how Tauri's `unstable` multi-webview feature hosts even
the main webview.

Two compounding causes:
1. The focus-restoration subclass (`parent_subclass_proc` -> `MoveFocus`) was
   attached only `if !is_child`, so child webviews got no focus restoration at all.
2. On Alt+Tab-back, Windows routes focus directly to the WebView2 child window
   (`Chrome_WidgetWin_1`), so the top-level `WM_SETFOCUS` never fires and the
   existing `MoveFocus` remedy never runs.

Fix:
- Attach the subclass for the first webview per parent (`GetWindowSubclass` guard),
  regardless of `is_child`. Later children (e.g. browser tabs) don't overwrite it,
  and standard single-webview behavior is unchanged.
- Add a `WM_ACTIVATE` arm that defers `MoveFocus` via a self-posted message (an
  immediate call is overridden by Windows' subsequent focus routing to the child).
- Gate the `Drop`-time detach so only the installing webview removes the subclass,
  and re-seed focus when a non-owning child webview closes.

Refs #1754

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-slop Low effort content, see https://github.com/tauri-apps/tauri?tab=contributing-ov-file#ai-tool-policy platform: Windows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants