Skip to content

Architecture cleanup: reduce protocol translation layers and handwritten CDP plumbing #9

Description

@TheKevinWang

Summary

Foxbridge is solving a real problem: CDP-only tools need a way to control Firefox/Camoufox.

The main issue is that the current architecture does a bit too much translation and hand-written protocol work.

Right now the BiDi path is basically:

CDP
 ↓
Foxbridge
 ↓
Juggler-style calls
 ↓
BiDi
 ↓
Firefox

So even when using BiDi, Juggler is still acting like the internal protocol.

It would probably be cleaner to make both Juggler and BiDi backends implement the same small set of browser operations directly:

                      ┌─ Juggler → Camoufox
CDP → semantic core ──┤
                      └─ BiDi    → Firefox

Main problems

1. The backend interface isn't really backend-neutral

The current backend API is basically:

Call(sessionID, method string, params json.RawMessage)

But those method strings are Juggler-style methods like:

Page.navigate
Runtime.evaluate
Browser.newPage

The BiDi backend then translates those into:

browsingContext.navigate
script.evaluate
browsingContext.create

That means BiDi has to pretend to be Juggler.

A better setup would be something more like:

Navigate(...)
Evaluate(...)
CreateContext(...)
Screenshot(...)
GetCookies(...)
SetCookies(...)
Subscribe(...)

Then Juggler and BiDi can implement those operations independently.


2. A lot of protocol plumbing is handwritten

Foxbridge currently implements its own:

  • CDP server
  • WebSocket handling
  • sessions
  • routing
  • event translation
  • BiDi client
  • Juggler client
  • request/response conversion

It also uses a lot of json.RawMessage and map[string]interface{}.

I don't think this needs a rewrite or a switch to TypeScript just to use @vscode/cdp, but generated protocol types would help a lot.

Foxbridge already reads devtools-protocol for doctor.

It could go further and generate Go types from:

browser_protocol.json
js_protocol.json

That would reduce manual protocol maintenance and catch upstream changes earlier.


3. devtools-protocol should be a direct dependency

The doctor-data script directly reads files from:

node_modules/devtools-protocol/

but devtools-protocol currently comes in through Puppeteer.

If Foxbridge directly uses it, it should probably be listed as a direct dev dependency instead of relying on Puppeteer's dependency tree.


4. No-op stubs can hide real incompatibilities

There are currently a lot more stubbed/missing CDP methods than fully implemented ones.

Some unsupported methods or whole domains return {} as if they succeeded.

That's useful for harmless client probes, but it can also cause this:

client: enable feature
foxbridge: {}
client: cool, it worked

Then something breaks later for a completely different reason.

It might be better to have modes like:

strict
puppeteer
openclaw

strict would return a proper unsupported error.

Client compatibility modes could allow specific known-safe no-ops.


5. Not all translations are actually equivalent

Some CDP/Juggler features don't map cleanly to BiDi.

For example:

  • isolated worlds
  • execution contexts vs realms
  • user-agent overrides
  • some emulation features
  • target/session behavior

Those should probably be marked as:

exact
emulated
partial
unsupported

instead of just "implemented".

foxbridge doctor would be a good place to show that.


6. Keep the CDP socket boring

The CDP endpoint should probably behave as much like Chrome as possible.

The current server has support for things like:

  • binary WebSocket frames
  • custom compression
  • batching

For a compatibility layer, standard JSON text frames are probably safer.

If compression is needed, standard WebSocket compression is better than custom payload compression.

The extra local JSON overhead is probably tiny compared with actually running a browser.


7. Some compatibility logic is too dependent on client behavior

There is quite a bit of state like:

latestCtx
lastQuery
lastQuerySkips
lastDialog
loaderMap
nodeObjects

Some of this is necessary because CDP and Firefox model things differently.

But things like tracking the last selector so Puppeteer's $eval sequence can be recognized are pretty fragile.

Those kinds of hacks should probably live in explicit client compatibility code instead of the core bridge.

Something like:

compat/puppeteer/
compat/openclaw/

would make it clearer what is generic protocol translation and what is client-specific behavior.


8. Testing should compare against real Chrome

The current Puppeteer tests are useful, but they're a Foxbridge-specific integration suite, not Puppeteer's entire upstream test suite.

The biggest testing improvement would probably be differential testing:

same CDP test
   /      \
Chrome   Foxbridge
   \      /
 compare results/events

Compare things like:

  • command results
  • errors
  • event ordering
  • frame IDs
  • session behavior
  • execution contexts
  • navigation lifecycle

Foxbridge already has record/replay support, so that could be really useful for this.


9. Repo cleanup

The repo currently appears to track things like:

node_modules/
foxbridge binary

Those should probably be ignored and binaries published through releases/CI instead.


Suggested direction

Something like this would be cleaner long-term:

CDP clients
    │
    ▼
CDP compatibility layer
    │
    ▼
semantic browser operations
    │
    ├───────────────┐
    ▼               ▼
Juggler adapter   BiDi adapter
    │               │
    ▼               ▼
Camoufox          Firefox

Juggler still makes sense for Camoufox.

BiDi makes sense for normal Firefox.

The main change is that BiDi shouldn't have to pretend to be Juggler internally.

Possible migration

This doesn't need to happen all at once.

  1. Make devtools-protocol a direct dependency.
  2. Stop tracking generated dependencies/binaries.
  3. Use normal CDP WebSocket framing by default.
  4. Add strict vs compatibility stub behavior.
  5. Generate CDP Go types from the upstream schema.
  6. Add backend capability levels like exact/emulated/partial.
  7. Introduce a small semantic backend interface.
  8. Move Juggler-specific and Puppeteer-specific behavior out of the generic core.
  9. Add Chrome-vs-Foxbridge differential tests.

The overall Foxbridge idea is good. This would mostly make the internals easier to maintain and make compatibility failures more obvious instead of hiding them behind extra translation layers or successful no-op responses.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions