Skip to content

Keep keyboard focus off automation-controlled browser tabs - #4303

Merged
SawyerHood merged 1 commit into
mainfrom
bb/fix-bb-browser-automation-focus-on-macos-thr_eb9gevm8sn
Sep 24, 2026
Merged

SawyerHood merged 1 commit into
mainfrom
bb/fix-bb-browser-automation-focus-on-macos-thr_eb9gevm8sn

Conversation

@SawyerHood

Copy link
Copy Markdown
Collaborator

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.bringToFront straight to Chromium, which natively focuses the page. And navigations or input driven over CDP (for example page.goto) made the tab's WebContentsView the 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-session Page.bringToFront now reveals the tab through adapter.activateTab, the same path Target.activateTarget already 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 next focus event, 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.
  • Behaviour change: a user can't type into a tab while an agent controls it. They press Take over, which matches the existing "… is controlling this tab" banner.
  • Browser Automation README.md and SKILL.md document the rule.
  • No wire, CLI, SDK, or HOST_DAEMON_PROTOCOL_VERSION changes. Headless sessions are unaffected.

How you verified

  • New unit tests fail on main and pass here:
    • desktop-browser-cdp.test.ts: Page.bringToFront reveals 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/desktop passes. The desktop Vitest suite passes except preload-build.test.ts, which launches Electron and couldn't create its socket directory in my sandbox; this change doesn't touch it.
  • End to end on Linux (Xvfb + openbox): real Electron 44.3.0 running the desktop view manager, CDP adapter, bridge, and broker unmodified. The plugin's pinned dev-browser 1.0.0-rc.3 connected over the desktop CDP bridge, ran page.goto, then clicked and typed 25 numbered values, calling bringToFront() every fifth round. At the same time, xdotool typed 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:
Scenario main This PR
Plugin flow, typing in composer composer 0/360; keys went into the worker's field composer 360/360; worker 25/25 exact
Plugin flow, typing in other app BB took focus; other app 0/360 other app kept focus, 360/360; worker 25/25 exact
Handoff flow, typing in composer 0/360 360/360; worker 25/25 exact
Handoff flow, typing in other app BB took focus; 0/360 360/360; worker 25/25 exact
  • Not verified live on macOS: the test Mac was locked. A local Electron probe on macOS confirmed that Page.bringToFront natively focuses a background view, which is the first cause fixed here.

Fixes #3558

🤖 Generated with Claude Code

AGENT GENERATED

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
SawyerHood merged commit d25c320 into main Sep 24, 2026
18 checks passed
@SawyerHood
SawyerHood deleted the bb/fix-bb-browser-automation-focus-on-macos-thr_eb9gevm8sn branch September 24, 2026 22:47
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Desktop Browser Automation steals focus across BB sessions

1 participant