Skip to content

Add BH_NO_ACTIVATE flag to suppress focus-steal (switch_tab + new_tab + daemon)#402

Open
undeemed wants to merge 3 commits into
browser-use:mainfrom
undeemed:no-activate-flag
Open

Add BH_NO_ACTIVATE flag to suppress focus-steal (switch_tab + new_tab + daemon)#402
undeemed wants to merge 3 commits into
browser-use:mainfrom
undeemed:no-activate-flag

Conversation

@undeemed

@undeemed undeemed commented Jun 2, 2026

Copy link
Copy Markdown

Problem

new_tab()switch_tab()cdp("Target.activateTarget", ...) raises the tab/window to the OS foreground on every call. When driving a visible browser (e.g. an isolated debug Chrome the user is watching), this steals focus on every single action — the window keeps popping to the front.

Change

Gate the Target.activateTarget call behind an opt-in env flag:

if os.environ.get("BH_NO_ACTIVATE") != "1":
    cdp("Target.activateTarget", targetId=target_id)
  • Default behavior unchanged — activation still happens unless BH_NO_ACTIVATE=1 is set.
  • Routing is unaffected: set_session (right below) is what makes cdp()/js() default to the tab; activation is purely cosmetic OS focus, and the 🐴 title marker already shows which tab is controlled.
  • The flag is read client-side in switch_tab, so callers just prefix runs with BH_NO_ACTIVATE=1 — no daemon restart needed.

Use case

Automated/background drivers attached to a visible isolated browser, where focus-stealing on each new_tab is disruptive.


Summary by cubic

Added BH_NO_ACTIVATE to suppress focus-steal: switch_tab() skips Target.activateTarget, and new_tab() and the daemon pass background:true to Target.createTarget so tabs open without raising the window (verified on macOS). Defaults are unchanged; when the flag is set the daemon anchors a real data: tab to keep backgrounding reliable, and you can override per call via new_tab(background=...) or switch_tab(activate=...).

Written for commit 929b4dc. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

Re-trigger cubic

@undeemed

undeemed commented Jun 2, 2026

Copy link
Copy Markdown
Author

Closing — opened prematurely, will revisit on my own terms. Apologies for the noise.

@undeemed undeemed closed this Jun 2, 2026
@aiba

aiba commented Jun 21, 2026

Copy link
Copy Markdown

Wait this seems super useful. Can we reopen this so it can be merged?

aiba added a commit to aiba/browser-harness that referenced this pull request Jun 21, 2026
…itch

When driving a visible browser, every new_tab()/switch_tab() raises the
Chrome window to the OS foreground and steals keyboard focus from whatever
the user is typing in. Two CDP calls cause this on macOS:

  - switch_tab(): Target.activateTarget raises + focuses the window.
  - new_tab(): Target.createTarget opens the tab in the foreground
    (foreground is the Mac default; createTarget takes a Mac-only
    `background` flag).

Gate both behind an opt-in BH_NO_ACTIVATE=1 env flag (default off, so the
existing watch-along behavior is unchanged):

  - switch_tab skips Target.activateTarget.
  - new_tab / the daemon's no-real-pages bootstrap pass background:true.

The flag is read client-side, so callers just prefix runs with
BH_NO_ACTIVATE=1 -- no daemon restart needed. Routing is unaffected:
set_session still makes cdp()/js() default to the controlled tab, and the
horse-emoji title marker still shows which tab the agent drives.

Note: background:true only keeps the window backgrounded when the new tab is
not the window's only tab. With an empty window the first new_tab() still
foregrounds once; keep one real loaded sentinel tab open for fully
focus-free behavior.

Completes PR browser-use#402, which gated only the switch_tab activateTarget call;
verified empirically that createTarget alone still raises the window
(frontmost: Finder -> Chrome for Testing) unless background:true is set.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
undeemed and others added 2 commits June 21, 2026 22:27
…_tab

new_tab -> switch_tab -> Target.activateTarget raises the tab/window to the OS
foreground on every call. When driving a *visible* browser that's the focus-steal
users notice on each action. The 🐴 title marker already shows which tab is
controlled, and set_session still routes cdp()/js() to the tab without activating
it — so activation is purely cosmetic OS focus.

Gate it behind an opt-in env flag (BH_NO_ACTIVATE=1) so automated/background
drivers can suppress the focus-steal. Default behavior is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…daemon)

switch_tab's activateTarget gate alone doesn't stop focus-steal: new_tab()
and the daemon's no-real-pages bootstrap call Target.createTarget, which
raises the OS window on macOS before switch_tab runs. Pass background:true
under BH_NO_ACTIVATE=1 to cover the new_tab path too. Default unchanged.

Adds unit tests for the background flag in new_tab and attach_first_page.

Co-authored-by: Aaron Iba <ai@aaroniba.net>
@undeemed undeemed changed the title Add BH_NO_ACTIVATE flag to suppress focus-steal in switch_tab Add BH_NO_ACTIVATE flag to suppress focus-steal (switch_tab + new_tab + daemon) Jun 22, 2026
@undeemed

Copy link
Copy Markdown
Author

Reopening — closed this early in June, complete now. switch_tab's gate alone left the Target.createTarget raise, so I added the background:true half for new_tab + daemon bootstrap (thanks @aiba for flagging that path), plus a per-call new_tab(url, background=…) override and a daemon keepalive anchor so it works without a user-managed sentinel (the #469 follow-up). Documented the flag, added tests (incl. an activate-skip regression), verified e2e on Chrome for Testing. Rebased onto main. Overlaps #469 (same core fix) — can be the single PR to review, no preference which lands.

@undeemed undeemed reopened this Jun 22, 2026
@browser-harness-review

browser-harness-review Bot commented Jun 22, 2026

Copy link
Copy Markdown

✅ Skill review passed

Reviewed 1 file(s) — no findings.

@undeemed

undeemed commented Jun 22, 2026

Copy link
Copy Markdown
Author

Only verified the focus-steal fix on macOS. The background:true + skip-activateTarget path is unconditional Chromium C++ (no mac-specific code), so it should hold cross-platform — but if anyone's on Windows or Linux, a quick confirm that new_tab() under BH_NO_ACTIVATE=1 doesn't raise the window would be appreciated.

… check

- new_tab(url, background=True|False) overrides BH_NO_ACTIVATE per call;
  switch_tab gains an `activate` override so backgrounding also skips the
  activateTarget raise (backgrounding createTarget alone doesn't stop it).
- daemon bootstrap anchors a real data: page (not about:blank, which Chrome
  treats as internal) when no real pages exist, so new_tab stays backgrounded
  without a user-managed sentinel (follow-up suggested in browser-use#469).
- centralize the env check in _no_activate().
- document BH_NO_ACTIVATE in SKILL.md + install.md.

Tests for the per-call override (incl. the activate-skip regression) and the
data: anchor; verified end-to-end on Chrome for Testing.
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.

2 participants