Skip to content

Bug: latestCtx not cleared on Runtime.executionContextDestroyed — stale context causes persistent evaluate failures #1

@void0x14

Description

@void0x14

Description

When a subframe's execution context is destroyed (e.g., iframe removed from DOM), foxbridge continues to route subsequent Runtime.evaluate calls to the destroyed context via latestCtx. This results in persistent "Failed to find execution context" errors that never recover — even after multiple retries with delays.

Environment

  • foxbridge version: VulpineOS embedded (CDP server on :9222)
  • Backend: juggler
  • Camoufox: Firefox 146
  • Platform: 7.0.9-1-cachyos Linux x86_64

Steps to Reproduce

  1. Connect to foxbridge CDP endpoint via WebSocket
  2. Navigate to a page that creates subframe iframes (e.g., Arkose Labs captcha enforcement iframe)
  3. Enable Target.setAutoAttach with flatten:true
  4. Subframe loads → Runtime.executionContextCreated fires with a new context (e.g., id:104, uniqueId:"id-5")
  5. Subframe is removed from DOM → Runtime.executionContextDestroyed fires for the same context
  6. Send Runtime.evaluate without contextId parameter (expecting foxbridge to use the default/main frame context)
  7. foxbridge returns: "Failed to find execution context with id = id-5"

Expected Behavior

After Runtime.executionContextDestroyed fires for a context, foxbridge should either:

  • Clear latestCtx[session] for that session, OR
  • Fall back to the main frame's default context when the stored latestCtx points to a destroyed context

Actual Behavior

foxbridge routes Runtime.evaluate (with no contextId) to the destroyed context stored in latestCtx, producing a persistent error. Retries do not help because no new context is created to overwrite latestCtx.

Evidence

Log Timeline (from foxbridge CDP wire capture)

T+0ms — Subframe context created:

{
  "method": "Runtime.executionContextCreated",
  "params": {
    "context": {
      "auxData": {
        "frameId": "subframe-12884901889",
        "isDefault": true,
        "type": "default"
      },
      "id": 104,
      "name": "",
      "origin": "",
      "uniqueId": "id-5"
    }
  },
  "sessionId": "005711cd-a7ec-49cf-8063-992307f5deb0"
}

T+0ms (same millisecond) — Subframe context destroyed:

{
  "method": "Runtime.executionContextDestroyed",
  "params": {
    "executionContextId": 104,
    "executionContextUniqueId": "id-5"
  },
  "sessionId": "005711cd-a7ec-49cf-8063-992307f5deb0"
}

T+2000ms onwards — Every Runtime.evaluate fails:

[runtime] evaluate error: error in channel "content::10/12/2":
exception while running method "evaluate" in namespace "page":
Failed to find execution context with id = id-5
findExecutionContext@chrome://juggler/content/content/Runtime.js:319:13

This error repeats every ~6 seconds for minutes — the stale context is never recovered.

Additional Evidence (from our application logs)

[22:43:07] Runtime.executionContextDestroyed: main frame context id=104 destroyed!
[22:43:09] Foxbridge Runtime.evaluate missing result object: CDP error -32000:
  Failed to find execution context with id = id-5
[22:43:15] Foxbridge Runtime.evaluate missing result object: CDP error -32000:
  Failed to find execution context with id = id-5
[22:43:21] Foxbridge Runtime.evaluate missing result object: CDP error -32000:
  Failed to find execution context with id = id-5
... (continues every ~6 seconds, never recovers)

Analysis

Per the Context Management documentation:

When a Runtime.executionContextCreated event arrives, foxbridge updates latestCtx[jugglerSessionID] to the new context. All subsequent evaluate and callFunctionOn calls prefer this latest context over the caller's requested context ID.
The latestCtx map is updated on creation but not cleared on destruction. The documentation describes cleanup during navigation (executionContextsCleared), but not during subframe destruction (executionContextDestroyed).
The problematic sequence:

  1. Context created → latestCtx[session] = "id-5"
  2. Context destroyed → ctxMap entry removed, but latestCtx[session] still = "id-5"
  3. Runtime.evaluate without contextId → foxbridge reads latestCtx[session] → tries to use "id-5" → Juggler throws

Suggested Fix

In the Runtime.executionContextDestroyed handler, when the destroyed context matches latestCtx[session], either:
Option A: Clear latestCtx[session] (set to empty/null), causing foxbridge to use Juggler's default context selection.
Option B: Fall back to the session's default/main frame context when latestCtx points to a destroyed context.
Option C: During evaluate with no explicit contextId, validate that latestCtx[session] still exists in ctxMap before using it. If not, fall back to default.

Workaround (client-side)

We currently work around this by:

  1. Explicitly passing the main frame's contextId in Runtime.evaluate calls (bypasses latestCtx)
  2. Retry logic (3 attempts, 200ms delay) to catch the window between context destruction and latestCtx update
    Neither fix addresses the root cause in foxbridge.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions