Skip to content

Add attach_tab() and new_tab(background=True) for focus-preserving automation - #504

Open
icekinder wants to merge 1 commit into
browser-use:mainfrom
icekinder:feat/attach-tab-background-new-tab
Open

Add attach_tab() and new_tab(background=True) for focus-preserving automation#504
icekinder wants to merge 1 commit into
browser-use:mainfrom
icekinder:feat/attach-tab-background-new-tab

Conversation

@icekinder

@icekinder icekinder commented Jul 10, 2026

Copy link
Copy Markdown

Motivation

When the harness drives a user's live browser (the daemon attached to their real Chrome), new_tab() and switch_tab() activate the target — which visibly yanks the user out of whatever tab they're working in every time automation runs. For background/batch workflows (CSV imports, polling a web app's UI, scraping a logged-in dashboard) the automation has no reason to be visible at all.

Changes

  • attach_tab(target) — attach a session to a tab without activating it. switch_tab() keeps its exact behavior and is now literally activate + attach_tab().
  • new_tab(url, background=False) — with background=True, the tab is created via Target.createTarget's background flag and only attached, never activated. Default behavior unchanged.
  • The reuse-a-blank-current-tab shortcut in new_tab() is skipped when background=True: the current tab may be the user's visible one, and background is an explicit request not to touch what they see.
  • Documented both in interaction-skills/tabs.md (code block + a "rules that held up" line).

Notes

Running this against a live daily-driver Chrome for a few weeks now — background CSV-import/enrichment runs no longer steal focus while the user keeps browsing.

🤖 Generated with Claude Code


Summary by cubic

Adds attach_tab() and new_tab(background=True) so automation can attach to tabs without activating them, preventing focus theft in a user’s live browser. Default behavior remains unchanged.

  • New Features
    • attach_tab(target): attach session without activating; switch_tab = activate + attach.
    • new_tab(url, background=True): create target with CDP background flag and attach only.
    • Skip “reuse current blank tab” when background=True to avoid touching the visible tab.
    • Docs: updated interaction-skills/tabs.md with examples and guidance.

Written for commit 5702b58. Summary will update on new commits.

Review in cubic

…tomation

When the harness drives a user's live browser, new_tab()/switch_tab()
activate the target and yank the user out of whatever tab they are
working in. This adds:

- attach_tab(target): attach a session without activating the tab
  (switch_tab() keeps its behavior and is now activate + attach_tab)
- new_tab(url, background=True): create via Target.createTarget's
  background flag and attach without activating

The reuse-a-blank-current-tab shortcut in new_tab() is skipped when
background=True, since the current tab may be the user's visible one.
Documented both in interaction-skills/tabs.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@browser-harness-review

Copy link
Copy Markdown

✅ Skill review passed

Reviewed 1 file(s) — no findings.

@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 2 files

Re-trigger cubic

@Kastan97 Kastan97 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ollie (CTO) Review: APPROVED

What this does: Adds attach_tab(target) for attach-without-activate and new_tab(url, background=True) for creating tabs without stealing OS focus. Refactors switch_tab to call attach_tab internally.

Verification:

  • 21+/6- across helpers.py + tabs.md docs
  • Clean separation: switch_tab = activate + attach, attach_tab = attach only
  • new_tab background=True bypasses blank-tab reuse (doesn't touch user's current tab) and passes background to CDP
  • Docs updated

Good to merge once CI passes.

@aiba

aiba commented Jul 30, 2026

Copy link
Copy Markdown

I've been running background-tab automation on macOS for a while via my own opt-in variant (#469), and hit two things downstream of background: true that aren't covered here. Sharing the measurements rather than a competing patch — this PR looks like the right shape, and I'd rather it land with these known.

1. background: true doesn't keep the window back when the new tab is the window's only tab.

Target.createTarget(background=True) backgrounds the tab, but if the window has no other real loaded tab, the window itself still comes forward and takes OS focus. about:blank doesn't count — it has to be a real loaded page.

I work around it by launching the automation browser pointed at a static local sentinel page, so the window always holds at least one real tab and background: true has something to stay behind. Without that, the very first new_tab() of a session steals focus even with everything else in place — which is easy to miss when testing against an already-running browser that happens to have tabs open.

2. Background tabs report visibilityState: 'hidden', and real sites behave differently there.

Two failure modes I hit, both silent — input is delivered and simply has no effect:

  • SPA routers defer transitions while hidden. Intuit's sign-in is a concrete case: clicking a challenge-picker option dispatches fine, the DOM is unchanged, and nothing advances. No error anywhere.
  • The 1Password extension declines to intercept navigator.credentials.* in a hidden tab, so Chromium falls through to its own native passkey dialog — which no CDP input can reach, and which then blocks every subsequent click on the page.

Emulation.setFocusEmulationEnabled on session attach fixes both without Target.activateTarget, so the window still never comes forward. #498 does this in the daemon's set_session, which is a good place for it — it covers every attach, not just the ones that go through switch_tab.

Worth noting the stated justification in #498 for that call — that a hidden RenderWidget drops Input.dispatchMouseEvent entirely — did not reproduce for me. On Brave 150 with emulation explicitly disabled and visibilityState: 'hidden', a plain click_at_xy still registered and type_text still inserted. So the emulation is worth having, but for the visibility-gated behaviour above rather than for input delivery. Same for #498's mouseMoved-before-click and click-moves-DOM-focus fixes: neither reproduced here (activeElement moved correctly, click-then-type worked).

Environment for all of the above: macOS 15, Brave 150 (Chromium 150), dedicated debugging profile on a non-default port, daemon attached over CDP.

Happy to close #469 in favour of this one — the opt-in-parameter approach here is cleaner than my env var, and there's no sense in four open PRs for the same problem.

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.

3 participants