Skip to content

release: v0.15.0 — Firefox/BiDi engine + incognito clean session#3

Merged
hamr0 merged 4 commits into
mainfrom
feat/firefox-bidi
Jul 10, 2026
Merged

release: v0.15.0 — Firefox/BiDi engine + incognito clean session#3
hamr0 merged 4 commits into
mainfrom
feat/firefox-bidi

Conversation

@hamr0

@hamr0 hamr0 commented Jul 10, 2026

Copy link
Copy Markdown
Owner

v0.15.0 — Firefox/BiDi engine + incognito clean session

Two features, released together after a full pre-merge review pass.

Firefox support via WebDriver BiDi (connect({ engine: 'firefox' }))

Second transport (src/bidi.js) over the same ws dep — no geckodriver, no new package. AX tree reconstructed in-page (src/ax-snapshot.js) since BiDi has no getFullAXTree. prune.js/aria.js/readable.js reused verbatim. CLI --engine firefox, MCP BAREBROWSE_ENGINE=firefox.

Incognito clean session (incognito: true)

Skips all auth injection (cookies + storageState) for a logged-out session. Gate enforced at the page-object level, so it holds even when a caller injects unconditionally (MCP goto, daemon). On browse(), connect(), both engines, MCP (browse arg + BAREBROWSE_INCOGNITO=1), CLI (--incognito).

Review pass — found + fixed (each validated first)

  • HIGH: startDaemon never forwarded --incognito to the daemon child → open --incognito silently authenticated. Fixed via pure, unit-tested buildDaemonArgs().
  • HIGH: Firefox injectCookies loaded the whole cookie jar (ignored the URL). Fixed via shared scopedCookiesForUrl() used by both engines so they can't drift.
  • Bug: Firefox iframe splice leaked another tab's frame after switchTab() (POC-proven). Fixed by scoping allContexts() to the active tab (getTree({ root })). Nested-frame handling was already correct. Regression tests added.
  • Parity: Firefox stubs the 5 CDP-only daemon methods with clear "not supported" errors / empty logs instead of silent TypeErrors.

Verification

  • npm test193 pass, 0 fail
  • npm run typecheck — clean
  • /ship + /security + /diff-review gates run under /verify-done

Known gaps (Firefox engine, documented)

Consent auto-dismiss, ad-block, stealth, hybrid mode, reload({ignoreCache}), console/network capture, saveState/waitForNavigation/waitForNetworkIdle are Chromium-only.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MZpPPhwXmSfN1n7uX7RAxT

hamr0 and others added 4 commits July 10, 2026 12:48
CDP is deprecated in Firefox, so add a second transport beside cdp.js over
the same `ws` dependency — no geckodriver, no new dep. Selectable with
connect({ engine: 'firefox' }), BAREBROWSE_ENGINE=firefox (MCP), or
`open --engine firefox` (CLI).

New modules (each stands alone):
- bidi.js         — BiDi JSON-RPC transport (session.new, script.evaluate, …)
- firefox.js      — find/launch Firefox, parse BiDi banner, temp profile, reap
- ax-snapshot.js  — reconstruct a CDP-vocabulary AX tree in-page (BiDi has no
                    getFullAXTree): implicit roles, accessible-name computation,
                    aria-hidden/visibility filtering, shadow-DOM + slot walk
- firefox-page.js — page object over BiDi: goto/snapshot/click/type/press/
                    scroll/hover/select/drag/upload/back/forward/reload/
                    screenshot/pdf/tabs/switchTab/waitFor/readable/injectCookies

Reuses prune.js/aria.js/readable.js unchanged; readable.js refactored to share
EXTRACT_EXPRESSION + finalizeReadable across transports. Navigation guard
(assertNavigable) enforced on the Firefox goto() for parity. Fidelity validated
against real CDP snapshots; iframes, shadow DOM, CSP, SPA timing covered in
test/integration/firefox.test.js (13 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AtfjYZuaUhrB7ovuWC2GRD
incognito gates every auth source so the session loads logged-out:
storageState loading is skipped (CDP) and injectCookies() is a no-op on both
engines (connect page, createTab, and Firefox/BiDi page), so even callers that
inject unconditionally (MCP goto, daemon) get no auth. Exposed as
connect({ incognito }) / browse({ incognito }), CLI --incognito, MCP per-request
`incognito` on `browse` + BAREBROWSE_INCOGNITO=1 for the persistent session.
Parity + able-to-fail controls in test/integration/incognito.test.js (4 tests).

Also makes the Firefox/BiDi modules typecheck-clean under `tsc --noEmit`:
- ax-snapshot.js: // @ts-nocheck (browser-context code — document/CSS/regex
  literals run in-page via .toString(), never in Node)
- bidi.js: @type {Promise<void>} hint on the connect promise
- firefox.js: guard possibly-undefined process.pid in cleanupFirefox
- firefox-page.js / index.js: JSDoc @param for uploadDir, incognito, engine

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AtfjYZuaUhrB7ovuWC2GRD
Firefox support over WebDriver BiDi (connect({ engine: 'firefox' }), CLI
--engine firefox, MCP BAREBROWSE_ENGINE=firefox) and an incognito clean,
unauthenticated session that gates all auth injection. Docs: CHANGELOG 0.15.0,
README Firefox section + requirements, integration guide engine/incognito opts.

Pre-release review pass (/ship + /security + /diff-review under /verify-done)
found and fixed, each validated first:

- HIGH: startDaemon never forwarded --incognito to the detached daemon child,
  so `barebrowse open <url> --incognito` silently ran a fully-authenticated
  session. Extracted pure buildDaemonArgs() (unit-tested) with the forward.
- HIGH: Firefox injectCookies loaded the entire cookie jar (ignored the URL),
  vs the CDP path's host scoping. Extracted shared scopedCookiesForUrl() used
  by both engines so they cannot drift; Firefox now scopes to the target host.
- Firefox iframe splice leaked another tab's frame into the active snapshot
  after switchTab() to a non-first tab (POC-proven). Scoped allContexts() to
  the active tab via getTree({ root }); nested-frame handling was already
  correct. Regression tests added (nested + multi-tab).
- Firefox page stubs the 5 CDP-only methods the daemon dispatches: downloads/
  dialogLog return [], saveState/waitForNavigation/waitForNetworkIdle throw a
  clear "not supported on Firefox/BiDi" error instead of a silent TypeError.

Tests: 193 pass, 0 fail; typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZpPPhwXmSfN1n7uX7RAxT
Medium /code-review of the v0.15.0 branch surfaced 5 correctness issues in the
new Firefox/BiDi engine; all validated (POC + regression tests) and fixed:

- goto()/reload()/traverseHistory() ignored their timeout — a page that never
  fires load hung the call forever. Added withTimeout() around the navigate
  commands (goto now rejects at its timeout; POC: ~2s vs never).
- daemon `eval` over BiDi returned the raw serialized result.value, so object/
  array evals came back malformed vs the CDP returnByValue shape. Wrapped in an
  in-page JSON.stringify + JSON.parse (undefined → null), matching readable().
- A collapsed native <select> expanded every <option> into the tree. It now
  surfaces as a single combobox with its current value; multi/size listboxes
  still list options.
- Bare text directly under <body> was dropped (root walk used .children, not
  .childNodes). Now kept as StaticText, matching walk()/CDP.
- Firefox proxy setup ignored the URL scheme and wired everything as an HTTP
  proxy; a socks:// proxy now configures network.proxy.socks + version.

Regression tests added to test/integration/firefox.test.js (select collapse,
bare body text, goto timeout). Full suite: 196 pass, 0 fail; typecheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZpPPhwXmSfN1n7uX7RAxT
@hamr0
hamr0 merged commit 586df32 into main Jul 10, 2026
1 check passed
@hamr0
hamr0 deleted the feat/firefox-bidi branch July 10, 2026 16:12
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.

1 participant