Contributing to Foxbridge
Repository Structure
foxbridge/
├── cmd/foxbridge/main.go # CLI entry point
├── pkg/
│ ├── backend/
│ │ ├── backend.go # Backend interface
│ │ ├── juggler/ # Juggler pipe transport + client
│ │ └── bidi/ # BiDi WebSocket client
│ ├── bridge/
│ │ ├── bridge.go # Main router + shared state
│ │ ├── target.go # Target domain handler
│ │ ├── page.go # Page domain handler
│ │ ├── runtime.go # Runtime domain handler
│ │ ├── input.go # Input domain handler
│ │ ├── network.go # Network domain handler
│ │ ├── emulation.go # Emulation domain handler
│ │ ├── dom.go # DOM domain handler
│ │ ├── fetch.go # Fetch (request interception) handler
│ │ ├── events.go # Event subscriptions + translation
│ │ ├── stub.go # Stub domains (no-op)
│ │ └── *_test.go # Unit tests per domain
│ ├── cdp/
│ │ ├── server.go # WebSocket server + HTTP endpoints
│ │ └── session.go # Session manager (3-way index)
│ └── firefox/ # Firefox process management
├── tests/ # Puppeteer integration tests
└── docs/ # Documentation siteAdding a New CDP Domain
-
Create the handler file
pkg/bridge/yourdomain.gowith ahandleYourDomain()function following the same pattern as existing handlers (switch on method, translate params, callcallJuggler(), return result). -
Register the route in
HandleMessageinbridge.gowithstrings.HasPrefix(method, "YourDomain."). -
Write tests in
pkg/bridge/yourdomain_test.gousing the mock backend.
Adding Event Translation
Add a subscription in SetupEventSubscriptions() in events.go. Use GetByJugglerSession to find the CDP session, then emitEvent to broadcast. Include the sessionID to scope events to a specific session.
Test Infrastructure
mockBackend
All unit tests use mockBackend from testutil_test.go. It implements backend.Backend and records all calls:
func TestSomething(t *testing.T) {
mb := newMockBackend()
sessions := cdp.NewSessionManager()
server := cdp.NewServer(0, nil, sessions)
b := New(mb, sessions, server)
// Preconfigure a response
mb.SetResponse("session-1", "Juggler.method", json.RawMessage(`{"result": "ok"}`), nil)
// Send a CDP message
result, err := b.handleYourDomain(nil, &cdp.Message{
Method: "YourDomain.someMethod",
SessionID: "cdp-session-1",
Params: json.RawMessage(`{}`),
})
// Verify the Juggler call was made
call := mb.LastCall()
assert(call.Method == "Juggler.method")
}Use SetResponse("", method, ...) for wildcard matching. The mock records all calls in mb.calls for inspection.
Running Tests
go test ./... # All unit tests
go test -race ./... # With race detector
go test ./pkg/bridge/ -v # Single package
cd tests && npm test # Integration tests (requires browser)Style Guidelines
- Each domain handler is a single file with a single
handle*function - Use
callJuggler()for all backend calls (handles session resolution) - Use
marshalResult()to build JSON responses - Return
json.RawMessage("{}"), nilfor no-op and success-with-no-data methods - Return
nil, &cdp.Error{Code: -32000, Message: err.Error()}for errors - All shared state maps must be mutex-protected
See also
- Architecture — How CDP-to-Firefox translation works under the hood
- Testing — Test suite and Puppeteer compatibility
- CLI Reference — All foxbridge command line options