Skip to content

Fetch.enable ignores urlPattern patterns — intercepts every request and stalls navigation #5

Open

Description

@lgwacker

Bug description

Fetch.enable with a patterns array of urlPatterns is translated to Juggler's Browser.setRequestInterception with only {enabled: true} — the URL patterns are dropped entirely. As a result, every request in the browser is intercepted (paused) instead of only the requests matching the requested patterns.

Root cause

In pkg/bridge/fetch.go, the Fetch.enable handler parses params.Patterns but never uses it; it only forwards {"enabled": true} to Juggler. Juggler has no pattern support of its own, so the bridge must implement the pattern filtering — but it currently doesn't, and the Browser.requestIntercepted → Fetch.requestPaused handler in pkg/bridge/events.go emits a paused event for every intercepted request.

Impact

Any client that scopes Fetch.enable to a single URL (e.g. a dialog-bridge supervisor intercepting only http://hermes-dialog-bridge.invalid/*) gets all navigation requests paused forever:

  • Page.navigate returns a navigationId, but the page never commits — no frameNavigated is ever emitted.
  • Every request waits for a Fetch.continueRequest that the client will never send (it only handles requests matching its pattern).
  • Navigation stalls indefinitely; the only way out is disabling interception entirely.

Repro steps

  1. Start foxbridge and connect a CDP client (e.g. Puppeteer) to a Firefox/Camoufox instance.
  2. Call Fetch.enable with a single pattern, e.g. {"patterns": [{"urlPattern": "http://hermes-dialog-bridge.invalid/*"}]}.
  3. Call Page.navigate to any other URL (e.g. https://example.com).
  4. Observe: the navigation request is paused and Fetch.requestPaused is emitted for it (matching the pattern or not), Page.navigate returns a navigationId, but frameNavigated never fires and the page never loads.

Expected vs actual

  • Expected (Chrome semantics): only requests matching at least one urlPattern are paused; all other requests are transparently continued.
  • Actual: every request is paused and waits for a continuation that never comes.

Fix approach

  • Keep the per-CDP-session urlPattern list from Fetch.enable (and clear it on Fetch.disable).
  • In the Browser.requestIntercepted handler, auto-continue (Browser.continueInterceptedRequest) any request whose URL matches no pattern, and only emit Network.requestWillBeSent / Fetch.requestPaused for matching requests.
  • Pattern matching mirrors Chrome: glob where * matches any sequence, case-insensitive, anchored to the full URL.
  • Fetch.enable with no patterns keeps the current behavior (intercept everything).

Activity

  1. lgwacker commented on Aug 12, 2026

    @lgwacker
    Author

    Follow-up: root cause is deeper than urlPattern handling — PR #6 now carries the complete fix

    While validating the pattern fix, we found that the interception stall is caused by two protocol-level bugs, not just the dropped patterns:

    1. foxbridge uses Juggler methods/events that do not exist

    Verified against the bundled protocol (omni.ja → chrome/juggler/content/protocol/):

    • Browser.requestIntercepted (subscribed in events.go) is not emitted by Juggler — the per-page NetworkObserver pauses requests and emits Network.requestWillBeSent with an isIntercepted flag (PageNetwork.Events.Request).
    • Browser.continueInterceptedRequest / Browser.setRequestInterception (browser-level) don't drive the page's interception — the real page-session methods are Network.setRequestInterception, Network.resumeInterceptedRequest, Network.fulfillInterceptedRequest, Network.abortInterceptedRequest.

    So with Fetch.enable the request is genuinely paused in the browser (visible: Page.navigate returns navigationId but no frameNavigated ever commits) while the bridge never receives any interception event → deadlock.

    2. Camoufox PageHandler buffers network events forever (omni.ja)

    PageHandler._handleNetworkEvent(event, details, frameId) holds network events in _networkEventsForUnreportedFrameIds until _reportedFrameIds.has(frameId). The helper.on dispatcher passes only (eventDetails) (frameId is never the 3rd argument), and the frame IDs themselves mismatch: Page.frameAttached reports a UUID (e90bdc68-…) while network events carry mainframe-N (from browsingContextToFrameId). The set lookup never matches → every Network.requestWillBeSent is buffered indefinitely, even without interception.

    Fixes shipped in PR #6 (commit 57ebbbc)

    • Fetch.enable/disable → Network.setRequestInterception on the page session
    • Fetch.continue/fulfill/failRequest → Network.resume/fulfill/abortInterceptedRequest
    • Fetch.requestPaused emitted from Network.requestWillBeSent when isIntercepted is set
    • urlPattern scoping retained: requests outside the pattern are auto-resumed

    The Camoufox-side frameId/buffer issue (omni.ja) is a separate bug in the browser itself — on our side we patch PageHandler._handleNetworkEvent to emit events directly. Happy to open a separate issue for that one if useful.

  2. lgwacker commented on Aug 12, 2026

    @lgwacker
    Author

    Final root cause + fix update (commit a41f9e6)

    Further verification against the Camoufox 135 bundle (omni.ja) proved the stall is not the dropped urlPatterns — it's that Juggler never delivers interception events at all:

    1. Network.setRequestInterception({enabled: true}) (page session) makes the NetworkObserver pause requests (channelIntercepted → stored in _interceptedRequests awaiting resumeInterceptedRequest).
    2. But PageNetwork.Events.Request is never delivered to the PageHandler (browser process): no Network.requestWillBeSent ever reaches the bridge — confirmed by zero occurrences in the full bridge log and by instrumenting the omni.ja handlers (the PageHandler's _handleNetworkEvent never fires; even the Camoufox-internal channelIntercepted probe stays silent while the navigation hangs).
    3. Result: with Fetch enabled, every request is paused forever, nobody emits Fetch.requestPaused, nothing calls resumeInterceptedRequest → Page.navigate returns navigationId, frameNavigated never arrives. Fetch.disable (which resumes all _interceptedRequests) unblocks navigation — proving the pause is real.

    Fix shipped: Fetch.enable is now a no-op (patterns still parsed/stored for a future Juggler fix). Navigation flows; dialog interception via Fetch is degraded but pages load. This unblocks every CDP client that enables Fetch (e.g. the Hermes dialog-bridge supervisor) and is verified end-to-end: Fetch.enable → Page.navigate → frameNavigated → page loads.

    The earlier commits (patterns + Network.* rename) remain valid groundwork; the no-op is the safe behavior until Juggler starts delivering interception events.

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