Skip to content

fix: Juggler request interception event handling#4

Open
void0x14 wants to merge 8 commits into
VulpineOS:mainfrom
void0x14:fix/request-interception-v2
Open

fix: Juggler request interception event handling#4
void0x14 wants to merge 8 commits into
VulpineOS:mainfrom
void0x14:fix/request-interception-v2

Conversation

@void0x14
Copy link
Copy Markdown

Problem

foxbridge could not deliver Fetch.requestPaused events to CDP clients (e.g. doggystyle engine). This blocked request interception workflows like Arkose CAPTCHA audio URL detection.

Root Causes (3 bugs found and fixed)

Bug 1: Wrong interception method

Browser.setRequestInterception was used, but in Juggler this only sets a flag — it does NOT enable actual request interception.

Fix: Use Network.setRequestInterception({enabled: true}) which calls enableRequestInterception() on the page's NetworkObserver.

Bug 2: Wrong event subscription

foxbridge subscribed to Browser.requestIntercepted, which does not exist in Juggler.

Fix: Subscribe to Network.requestWillBeSent and check the isIntercepted boolean field.

Bug 3: Headers type mismatch (silent failure)

Juggler sends headers as [{name:"Host",value:"github.com"}] array, but the handler expected map[string]string. json.Unmarshal silently failed, causing the entire handler to return without emitting Fetch.requestPaused.

Fix: Parse headers as []struct{Name, Value string} and convert to map.

Additional fixes

  • TextMessage framing: CDP WebSocket frames were sent as BinaryMessage (opcode 0x02) but recvWsTextAlloc only handles TEXT (0x01). Fixed to use TextMessage for uncompressed frames.
  • Stale context routing: latestCtx was not cleared on executionContextDestroyed, causing commands to be sent to destroyed contexts. Added mainCtx fallback and proper cleanup.
  • Runtime result typing: normalizeRuntimeResult hardcoded "undefined" for non-boolean/string/number results. Now infers type from JSON value.

Testing

  • All foxbridge unit tests pass (go test ./... -count=1)
  • Live tested with doggystyle engine: Fetch.requestPaused events are now correctly received (62 events in test run)
  • Previously: 0 events received. After fix: events flow correctly.

Commits

  1. db73abe — clear latestCtx on executionContextDestroyed
  2. 78e5bfe — use TextMessage for uncompressed CDP frames
  3. 4629a89 — mainCtx fallback when latestCtx cleared
  4. 32ac70c — normalizeRuntimeResult infers type from JSON
  5. 9c1297a — subscribe to Network.requestWillBeSent
  6. c1bf3f2 — use Network.setRequestInterception
  7. 545744e — remove browserContextId param
  8. b26fe4d — parse Juggler headers as array

void0x14 added 8 commits May 29, 2026 17:44
…ntext routing

When a subframe's execution context is destroyed, latestCtx was not cleared.
This caused Runtime.evaluate (without contextId) to keep routing to the
destroyed context, producing persistent 'Failed to find execution context'
errors that never recovered.

Fix:
- executionContextDestroyed: clear latestCtx if it matches the destroyed context
- executionContextsCleared: clear latestCtx for the session (navigation case)
- Add test assertion verifying latestCtx is cleared after context destruction
BinaryMessage (opcode 0x02) was always sent regardless of compression
state. CDP wire format is JSON text — engine recvWsTextAlloc only
handles TEXT (0x01) and CONTINUATION (0x00) frames, causing WsFrameError.

- Send: TextMessage when compress=false, BinaryMessage when compress=true
- SendBatch: same fix

SOURCE: Chrome DevTools Protocol — wire format is JSON text frames
SOURCE: RFC 6455 Section 5.2 — opcode 0x01 = text, 0x02 = binary
When a subframe's execution context is destroyed, latestCtx is cleared
(fix VulpineOS#1). But Juggler requires executionContextId in Runtime.evaluate.
Without a fallback, foxbridge sends undefined → Juggler rejects.

Add mainCtx map that tracks the main frame's execution context per
session. Unlike latestCtx, mainCtx is updated on every main frame
context creation (surviving navigation) and only cleared on
executionContextsCleared (full navigation reset).

Fallback chain: latestCtx → mainCtx → empty (Juggler error)
This ensures Runtime.evaluate always has a valid contextId.

Also fix binary framing: Send() now uses TextMessage (opcode 0x01)
for uncompressed CDP frames instead of always BinaryMessage (0x02).

Fixes VulpineOS#1
Instead of hardcoding type="undefined" when Juggler omits the type
field, infer the actual type from the JSON value:
- String value → type="string"
- Boolean value → type="boolean"
- Number value → type="number"

This fixes "Runtime.evaluate expected string, got type=undefined"
errors in CDP clients that check the type field.

SOURCE: Chrome DevTools Protocol — Runtime.evaluate returns {result:{type,value}}
…tIntercepted

Juggler does not emit Browser.requestIntercepted events. Instead, it uses
Network.requestWillBeSent with isIntercepted: true for intercepted requests.
- Remove Browser.requestIntercepted subscription (never fires)
- Add isIntercepted check in Network.requestWillBeSent handler
- Emit Fetch.requestPaused when isIntercepted is true
SOURCE: Juggler Protocol.js — Network.requestWillBeSent { isIntercepted: Boolean }
…Interception

Browser.setRequestInterception only sets a flag on the browser context
but does NOT enable request interception. Network.setRequestInterception
is a PAGE-level handler that calls enableRequestInterception() on the
page's NetworkObserver, which actually enables request interception.

- Fetch.enable → Network.setRequestInterception (was Browser.setRequestInterception)
- Fetch.disable → Network.setRequestInterception (was Browser.setRequestInterception)
- Network.setRequestInterception passes through directly to Juggler

SOURCE: Juggler PageHandler.js — Network.setRequestInterception calls enableRequestInterception
SOURCE: Juggler BrowserHandler.js — Browser.setRequestInterception only sets a flag
SOURCE: Juggler NetworkObserver.js — enableRequestInterception sets _requestInterceptionEnabled
Juggler's Network.setRequestInterception only accepts {enabled: Boolean}.
Extra parameters like browserContextId cause CDP error -32000.

SOURCE: Juggler Protocol.js — Network.setRequestInterception params: {enabled: Boolean}
Juggler sends headers as [{name:"Host",value:"github.com"}] but the
Network.requestWillBeSent handler expected map[string]string. This caused
json.Unmarshal to silently fail, preventing Fetch.requestPaused from
ever being emitted to CDP clients.

Parse headers as []struct{Name,Value string} and convert to map.

Also removes accidentally committed .opencode/ files and foxbridge-fixed
binary from earlier development.

SOURCE: Juggler NetworkObserver.js — _sendOnRequest sends headers array
SOURCE: Juggler Protocol.js — Network.requestWillBeSent type definitions
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