Skip to content

feat(firefox): dashboard live view, bidi module and docs - #2208

Open
lahfir wants to merge 94 commits into
laf/bidi-network-emulationfrom
laf/bidi-live-view-docs
Open

lahfir wants to merge 94 commits into
laf/bidi-network-emulationfrom
laf/bidi-live-view-docs

Conversation

@lahfir

@lahfir lahfir commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor
  1. Firefox sessions did not show in the dashboard live view, and the engine had no documentation.
  2. This adds Firefox live view and documentation, moves handlers into bidi, and defines their routing once.
  3. Builds on feat(firefox): network control and emulation on Firefox #2207.

Demo

The terminal drives a Firefox session over WebDriver BiDi; the right side shows that Firefox page as each command runs.

demo.mp4

Architecture

flowchart TB
  subgraph Clients
    CLI[CLI]
    MCP[MCP server]
    DASH[Dashboard]
  end

  CLI & MCP & DASH -->|JSON over daemon socket| EX

  subgraph Daemon["Daemon (native/actions.rs)"]
    EX[execute_command]
    GATES[Policy + confirmation gates<br/>implicit launch / relaunch]
    ROUTE{engine == firefox?}
    CDPH[Existing CDP handlers<br/>Chrome, Lightpanda, Obscura,<br/>--cdp, providers]
    WD[WebDriver path<br/>Safari / iOS]
    EX --> GATES --> ROUTE
    ROUTE -->|no| CDPH
    ROUTE -->|no, provider ios/safari| WD
  end

  ROUTE -->|yes| BA

  subgraph Bidi["native/bidi/ (BiDi protocol module)"]
    BA[actions.rs<br/>One handler declaration / Chrome-only errors<br/>dialog guard, dispatch]
    subgraph Handlers["Handlers (any BiDi browser)"]
      PAGE[page: navigate, history, waits, content]
      TABS[tabs: t1..tN ids, new/switch/close]
      FORMS[forms: fill, type, press, select, check, upload, inserttext]
      INTER[interact: click, hover, drag, scroll, tap, dialog]
      QUERY[query: get/is, find/getby*, frame]
      CAP[capture: screenshot, annotate, pdf]
      STOR[storage: cookies, storage, state save/load]
      NET[network: route, HAR, requests, headers, auth, downloads]
      EMU[emulation: viewport, device, geo, media, offline]
      SNAP[snapshot: tree + refs]
      TGT[target: ref / selector resolver]
      INP[input: key + pointer action builders]
      STRM[stream: dashboard polling + input]
    end
    SESS[session.rs FirefoxSession<br/>active/top context, tab ids,<br/>ref side table i64 to sharedId,<br/>event pump]
    CLIENT[client.rs BiDi WebSocket client<br/>send, send_typed, events]
    TYPES[types.rs LocalValue, RemoteValue,<br/>BytesValue, Cookie, events]
    FF[firefox.rs find, validate, launch<br/>temp profile, prefs, process group]
    DEVT[firefox_devtools.rs RDP client<br/>accessibility tree, media simulation]
    BA --> Handlers
    Handlers --> SESS
    SESS --> CLIENT
    CLIENT --- TYPES
    SESS --> DEVT
    FF --> SESS
  end

  subgraph Shared["Shared with Chrome (reused, not copied)"]
    JS[Page JS constants<br/>element.rs / interaction.rs]
    RENDER[Snapshot renderer + RefMap<br/>build_tree / render_tree]
    POLICY[DomainFilter, route matching,<br/>state format, screenshot/pdf/upload helpers]
  end

  FORMS & INTER & QUERY -.-> JS
  SNAP -.-> RENDER
  NET & STOR & CAP -.-> POLICY
  CDPH -.-> JS & RENDER & POLICY

  subgraph Firefox["Firefox 150+ (owned temp profile)"]
    RA[Remote Agent<br/>WebDriver BiDi]
    DS[DevTools server<br/>accessibility actor]
  end

  CLIENT <-->|ws://127.0.0.1:random/session| RA
  DEVT <-->|unix socket in 0700 dir<br/>or loopback port on Windows| DS
  FF -->|spawn| Firefox
Loading

Launch sequence

sequenceDiagram
  participant C as CLI
  participant D as Daemon
  participant FF as firefox.rs
  participant S as FirefoxSession
  participant B as Firefox BiDi
  participant R as Firefox DevTools
  C->>D: open --engine firefox URL
  D->>FF: validate options (reject --cdp, --profile, --args, ...)
  FF->>FF: temp profile + user.js prefs, socket dir 0700
  FF->>B: spawn firefox --headless --remote-debugging-port 0 --start-debugger-server sock
  B-->>FF: stderr "WebDriver BiDi listening on ws://..."
  FF->>S: create session
  S->>B: session.new (proxy, prompts: accept alert/beforeunload, ignore others)
  B-->>S: browserVersion (must be >= 150)
  S->>R: connect, parentAccessibilityActor.enable
  S->>B: session.subscribe (log, network, prompts, downloads, contexts)
  D->>B: browsingContext.navigate URL (wait complete)
  D-->>C: title + url
  Note over D,B: close / idle / crash: end session, kill process group, delete profile
Loading

@lahfir
lahfir added this pull request to stack #2209 October 23, 2026 16:35
@vercel

vercel Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
agent-browser Ready Ready Preview, v0 Nov 9, 2026 10:46pm UTC
agent-browser-env-demo Ready Ready Preview, v0 Nov 9, 2026 10:46pm UTC

@vercel-internal-security-reviewer

Copy link
Copy Markdown

Security review details

Comment thread cli/src/native/actions.rs Resolved
@lahfir
lahfir force-pushed the laf/bidi-live-view-docs branch from e9a1eef to c9c59be Compare October 26, 2026 20:33
Comment thread cli/src/native/bidi/input.rs Resolved
38 tests were lost in the move from firefox/ to bidi/. Restore them, porting the touch tap and stale-ref recovery tests to the new Origin and document-identity APIs.
Keep the opener-based active tab fallback and clear the ids, labels, refs and prompts of a destroyed tab. The tab-closed test now checks that cleanup.
The refactored Firefox modules land in bidi/: navigation and daemon own loading and page resets, pointer and text entry own their policies, and network owns event recording scope and the allowlist entry point.
The page already lists firefox, so the edit no longer applies and the docs contract test fails on the missing anchor.
Comment thread docs/tests/fixtures/docs-edits.json Resolved
The fixture listed the same replacement twice, so the second pass failed. Keep a single entry and leave the baseline untouched, so the route contract sees the Firefox engine list.
Comment thread docs/tests/fixtures/docs-edits.json Resolved
The env table row changed with the engine list, so the route contract needs the matching edit.
The rendered sample and the table markup carry the engine list in different forms, so both need the edit.
Comment thread cli/src/native/bidi/network.rs Resolved
Comment thread cli/src/native/bidi/stream.rs Resolved
Comment thread cli/src/native/actions.rs Resolved
Comment thread cli/src/native/actions.rs
}
}
if state.browser.is_none() && state.firefox.is_some() {
state.broadcast_firefox_tabs().await;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

With streaming enabled this runs after every Firefox command and issues a getTree plus one document.title eval per tab even when no dashboard client is connected, so a stalled page adds up to ~1.25 s to each CLI command. Not reproduced.

};
let size = live
.bidi
.call_function(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The poll loop awaits this call and the screenshot before it can select again, both with the 30 s default timeout, so one unresponsive tab freezes frame publishing and prompt-pause tracking for up to 30 s. Not reproduced.

This branch was successfully deployed

2 active deployments
Preview – agent-browser — ff5e03ed Deployed Nov 9, 2026 by vercel[bot]
Preview – agent-browser-env-demo — ff5e03ed Deployed Nov 9, 2026 by vercel[bot]
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.

4 participants