Repository navigation
Keep keyboard focus off automation-controlled browser tabs - #4303
Merged
SawyerHood merged 1 commit intoSep 24, 2026
Merged
Conversation
A controlled desktop browser tab could take native keyboard focus from the operator's composer or another app. The CDP bridge forwarded Page.bringToFront to Chromium, and navigations or input driven over CDP made the tab's WebContentsView the native focus target; the view manager then reported that as a user focus. Page.bringToFront now reveals the tab through the adapter without native focus. While a CDP controller is attached, native focus reaching the tab is not reported to the renderer; on the next tick the view manager returns focus to the host webContents if the host window is focused, or defers until the window regains focus, so BB never activates itself over another app. Take over releases control before focusing, so it still works. Fixes #3558 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SawyerHood
deleted the
bb/fix-bb-browser-automation-focus-on-macos-thr_eb9gevm8sn
branch
September 24, 2026 22:47
This was referenced Sep 24, 2026
Closed
SawyerHood
pushed a commit
that referenced
this pull request
Sep 24, 2026
## Human comments ## What was wrong Electron gives a `WebContentsView` native keyboard focus when its page navigates, even while the view is hidden with `setVisible(false)`. A persisted desktop Browser tab whose page reloads on its own (a retry timer, a meta refresh, live reload) therefore took focus from the BB window every time it navigated. The operator lost the composer in every other thread, and on the New thread page. The view manager forwarded that focus to the renderer as a normal browser focus. #4303 added a guard that returns focus to the host, but only for automation-controlled tabs. See #4308 for the investigation, the live trace and a standalone Electron repro. ## What changed - `apps/desktop/src/desktop-browser-view.ts`: - A hidden entry (`!entry.visible`) now takes the same path as an automation-controlled one: its native focus is not reported, and `returnFocusToHost` gives focus back to the host on the next tick. - `controlledViewHasFocus` treats hidden entries the same way, so that return actually runs. - Visible tabs are unchanged. So is explicit focus (`focusEntryWithoutNotifying`, Take over), which still sets `suppressNextFocusNotification` first. - `apps/desktop/test/desktop-browser-view-manager.test.ts`: one regression test. A hidden tab's native focus returns to the host and does not push `bb-desktop:browser:focused`. - No wire, `HOST_DAEMON_PROTOCOL_VERSION`, CLI, SDK or documentation changes. ## How you verified - The new test fails on `main` (`focusHostWebContents` is never called) and passes with the fix. - `pnpm exec turbo run test --filter=@bb/desktop --force`: 47 files, 448 tests passed, 1 skipped. One earlier run hit a transient failure in `electron-builder-config.test.ts`, which is unrelated to this change. That file passes on its own both with and without this change, and on the rerun. - `pnpm exec turbo run typecheck lint --filter=@bb/desktop` passed. `oxfmt --check` passes on the changed source file. The only formatter findings in the test file are in pre-existing code outside this diff, and I left them untouched. - Real Electron 44.3.0 under Xvfb, driving the real `createDesktopBrowserViewManager`, using the repro script in #4308. The script attaches a tab, hides it, focuses the host, and lets the page reload itself. - `main` at `6f9d6f44d` reproduced in 3 of 3 runs. Each reload blurred the host, pushed `bb-desktop:browser:focused`, and left `host.isFocused=false` while `win.isFocused=true`. - With this change, 3 of 3 runs showed each reload's host blur followed by host focus within about 1 ms, no focus push, and `host.isFocused=true` / `document.hasFocus=true` at every poll. Fixes #4308 > AGENT GENERATED Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Human comments
What was wrong
A desktop browser tab under Browser Automation control could take native keyboard focus away from the operator. Keystrokes typed into the BB composer, or into another app, then landed in the worker's browser field (#3558). There were two causes. The scoped CDP bridge forwarded
Page.bringToFrontstraight to Chromium, which natively focuses the page. And navigations or input driven over CDP (for examplepage.goto) made the tab'sWebContentsViewthe native focus target, which the view manager reported to the renderer as a user focus. On Linux, focusing the host webContents synchronously inside that focus event had no effect. @pixexid's analysis on #3558 identified the controller-driven native focus.What changed
apps/desktop/src/desktop-browser-cdp.ts: page-sessionPage.bringToFrontnow reveals the tab throughadapter.activateTab, the same pathTarget.activateTargetalready uses, instead of focusing it natively.apps/desktop/src/desktop-browser-cdp-adapter.ts: marks a tab as automation-controlled while the CDP adapter owns its debugger attachment, and clears the mark when it detaches.apps/desktop/src/desktop-browser-view.ts: while a tab is controlled, its native focus events are no longer sent to the renderer as browser focus. On the next tick the manager returns focus to the host webContents if the host window is focused and a controlled view still holds focus. If the window isn't focused, it waits for the window's nextfocusevent, so BB never activates itself over another app. Explicit focus requests such as Take over still bypass the guard, and Take over releases control first.README.mdandSKILL.mddocument the rule.HOST_DAEMON_PROTOCOL_VERSIONchanges. Headless sessions are unaffected.How you verified
mainand pass here:desktop-browser-cdp.test.ts:Page.bringToFrontreveals the tab without a native command.desktop-browser-view-manager.test.ts: a controlled tab's native focus is not reported; focus returns on the next tick; the return waits for the window's focus event when the window is unfocused; explicit focus bypasses the guard; normal reporting resumes after detach.pnpm exec turbo run typecheck --filter=@bb/desktoppasses. The desktop Vitest suite passes exceptpreload-build.test.ts, which launches Electron and couldn't create its socket directory in my sandbox; this change doesn't touch it.dev-browser1.0.0-rc.3 connected over the desktop CDP bridge, ranpage.goto, then clicked and typed 25 numbered values, callingbringToFront()every fifth round. At the same time,xdotooltyped 360 characters as the operator using real X keystrokes. Two flows (the plugin's hidden-tab create → control → reveal-without-focus, and an explicit handoff of a visible tab), each with the operator typing into the composer and then into a separate app:mainPage.bringToFrontnatively focuses a background view, which is the first cause fixed here.Fixes #3558
🤖 Generated with Claude Code