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.
- Make
devtools-protocol a direct dependency.
- Stop tracking generated dependencies/binaries.
- Use normal CDP WebSocket framing by default.
- Add strict vs compatibility stub behavior.
- Generate CDP Go types from the upstream schema.
- Add backend capability levels like exact/emulated/partial.
- Introduce a small semantic backend interface.
- Move Juggler-specific and Puppeteer-specific behavior out of the generic core.
- 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.
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:
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:
Main problems
1. The backend interface isn't really backend-neutral
The current backend API is basically:
But those method strings are Juggler-style methods like:
The BiDi backend then translates those into:
That means BiDi has to pretend to be Juggler.
A better setup would be something more like:
Then Juggler and BiDi can implement those operations independently.
2. A lot of protocol plumbing is handwritten
Foxbridge currently implements its own:
It also uses a lot of
json.RawMessageandmap[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-protocolfordoctor.It could go further and generate Go types from:
That would reduce manual protocol maintenance and catch upstream changes earlier.
3.
devtools-protocolshould be a direct dependencyThe doctor-data script directly reads files from:
but
devtools-protocolcurrently 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:
Then something breaks later for a completely different reason.
It might be better to have modes like:
strictwould 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:
Those should probably be marked as:
instead of just "implemented".
foxbridge doctorwould 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:
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:
Some of this is necessary because CDP and Firefox model things differently.
But things like tracking the last selector so Puppeteer's
$evalsequence 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:
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:
Compare things like:
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:
Those should probably be ignored and binaries published through releases/CI instead.
Suggested direction
Something like this would be cleaner long-term:
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.
devtools-protocola direct dependency.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.