Clicking, typing and dragging on a tab the user is not looking at did nothing at all. This
release makes background operation actually work.
The defect
connect_tab({background: true}) correctly declined to steal focus — that part always worked.
What did not: a tab whose visibilityState is hidden does not process dispatched mouse or
key events. The command went out, nothing errored, and the page never saw it.
A comment in the extension asserted "CDP input still reaches a background tab", citing
browser_scroll and agent sessions as evidence. Both do work — and so do Runtime.evaluate,
hover and type. Click, double-click, press_key and drag do not. The claim generalised from the
mechanisms that survive backgrounding to the ones that don't, and nothing caught it because every
check looked at return values rather than effects.
The discriminator is visibilityState, not focus. Input to an unfocused but rendered tab always
worked, which is why this reproduced only on a tab created in the background that had never been
foregrounded.
The fix
Two independent causes, found by separating them:
- The target was outside the viewport.
DOM.scrollIntoViewIfNeededis a no-op on a tab
Chrome is not rendering, and does not report that it did nothing — so the in-page fallback,
which only ran in acatch, never fired. Scrolling is now verified rather than trusted. - The real blocker: even at correct, hit-testable coordinates the click still didn't
register. Background connections now enableEmulation.setFocusEmulationEnabledand
Page.setWebLifecycleState({state:"active"}), which is what makes a non-rendered page accept
dispatched input. Both are best-effort no-ops where unsupported.
Verified against a real browser
On a genuinely hidden tab: click, double-click, hover, type, press_key, select_option, drag,
scroll and a 183KB screenshot all work — and the user's active tab never moves. Two agent sessions
drive separate tabs with independent actions, snapshots and batches. 24/24 in the new session
suite; the existing stress suite is unchanged at 29/0 and 31/0, with cold start down from 24s to
3s.
Also added
get_capabilities now reports extensionVersion and buildId. An unpacked extension does not
hot-reload, and the manifest version does not change between rebuilds within a release — so there
was no way to tell whether Chrome had loaded newly built code. That ambiguity cost a full debugging
cycle chasing a fix that had never been loaded.
tests/e2e-sessions.mjs covers background operation, multi-session isolation, tab navigation
and the full input battery, asserting throughout that the active tab never changes.
Upgrading
npx crawlio-browser@latest init. The fix is in the extension — take the build from the release
assets and reload it, then confirm buildId in get_capabilities matches.