Documentation index for AI agents (llms.txt). Markdown versions of every page are available by appending .md to the page URL. The full corpus is at /llms-full.txt.

Page Bridge & Injection

The page bridge is a fallback communication channel that allows Inspector Lab to function when the extension's background process cannot be reached. This is critical for environments like Orion on iOS and iPadOS where standard extension APIs may be unavailable or unreliable.

Overview

Inspector Lab operates in multiple isolated worlds within a web page:

  • Isolated World: Where the extension's content script runs (limited capabilities)
  • Main World: The page's own JavaScript realm (full page access, but no extension APIs)
  • Inspector UI: The devtools panel itself (in a shadow DOM)

The main world is necessary for certain operations—particularly console evaluation and network/console hooks—because only code running there can access the page's global scope and intercept its functions. Normally, the background service worker uses chrome.scripting to inject code into the main world. When that's unavailable, the page bridge takes over.

DOM Events CustomEvent Evaluation &Hook Connection Inspector UI(Isolated World) Page Bridge Client(Isolated World) Page Bridge(Main World) Page Runtime(Main World)

The page bridge carries no extension privileges. Code executed through it has exactly the same authority as the page itself, and cannot bypass the page's Content Security Policy.

Architecture

Page Bridge Installation

The page bridge is loaded lazily when needed—typically when console evaluation or network capture is first requested and the background is unreachable. The installPageBridge() function in page-bridge-client.ts handles the installation:

1
Create Configuration

Generate four unique channel names (random tokens) for bidirectional communication: connectEvent, evalRequestEvent, evalReplyEvent, and readyEvent.

2
Inject Script

Create an inline <script> element containing the page bridge source code. The configuration is passed via the script's data-inspector-lab-bridge attribute as JSON.

3
Wait for Ready

Listen for the readyEvent custom event. The page bridge fires this as soon as it runs synchronously on insertion—or a security policy violation event fires if CSP blocks the injection.

4
Clean Up

Remove the script element from the DOM (it's no longer needed after execution) and resolve with a client interface.

Timeout Behavior:

  • Install timeout: 5 seconds (INSTALL_TIMEOUT_MS)
  • Eval timeout: 10 seconds per evaluation (EVAL_TIMEOUT_MS)

If the timeout expires or a securitypolicyviolation event fires for script-src, the install fails with a PageBridgeError describing the issue.

Configuration Protocol

The bridge receives its configuration as a JSON object on the script element's dataset:

type PageBridgeConfig = {
  connectEvent: string;
  evalRequestEvent: string;
  evalReplyEvent: string;
  readyEvent: string;
};

This approach avoids any external dependencies or fetch operations—the bridge works even if the extension runtime has gone away entirely.

Communication Flow

Console Evaluation

When the inspector needs to evaluate an expression in the page's main world:

  1. Inspector UI → calls evaluateExpression()
  2. Tries background first → sends EvaluateRequest to background service worker
  3. On failure → sets evaluateViaPage = true and installs the page bridge
  4. Page bridge client → dispatches a CustomEvent on evalRequestEvent
  5. Page bridge (main world) → receives the event and calls evaluateInPage()
  6. Evaluation result → dispatched back as a custom event on evalReplyEvent
  7. Client resolves → with EvaluateResponse (success or error message)

Each evaluation is assigned a unique ID to match requests with replies:

type PageBridgeEvalRequest = {
  id: string;
  expression: string;
  limit: number;  // Character limit for the preview
};

type PageBridgeEvalResponse = {
  id: string;
  response: EvaluateResponse;
};

Three Core Services

The page bridge provides three services to replace what the background normally does in the main world:

  1. Console Hooks — Installs and connects the console prehook (via connect()) so that console.log() and related calls are captured and sent to the inspector.

  2. Network Hooks — Installs and connects the network prehook so that fetch() and XMLHttpRequest calls are captured without requiring the webRequest API.

  3. Expression Evaluation — Evaluates arbitrary JavaScript in the page's global scope, running the same evaluateInPage() function that the background would inject.

All three services are automatically initialized when the prehooks are imported; both guard against duplicate installation, so this is a no-op when the document_start registration or an earlier launch already ran them.

Hook Connection

Network and console capture relies on hooks installed in the main world (via console-prehook and network-prehook modules). When the background cannot inject these, the page bridge connects them instead:

type PageBridgeConnectRequest = {
  hook: "network" | "console";
  eventName: string;
};

The page bridge's connect() function:

  1. Locates the hook on the window object (stored as __inspectorLabNetworkHook or __inspectorLabConsoleHook)
  2. Sets its eventName property to the provided channel name
  3. Replays any events buffered while the hook was unconnected

Limitations and Fallbacks

Content Security Policy

The most common failure point: if the page's CSP forbids inline scripts (script-src directive doesn't include 'unsafe-inline'), the bridge cannot inject itself. The client detects this via a securitypolicyviolation event and immediately reports:

This browser or this site's Content Security Policy blocks the in-page 
fallback, and the extension background is unreachable. Browser DevTools 
can bypass CSP; an in-page inspector cannot.

Browser DevTools can bypass a page's CSP because they run in a privileged context. Inspector Lab, running as a content script, cannot. This is a fundamental browser security boundary.

When cookie operations fall back to document.cookie:

  • Available: User-facing cookies (unencrypted, non-HttpOnly)
  • Unavailable: HttpOnly cookies, Secure flag, SameSite settings

The inspector's Cookies panel labels fallback cookies as "document" source and explains the limitations.

Network Capture

Network capture via the page bridge relies on fetch and XMLHttpRequest hooks. The webRequest API (which sees all network traffic) is not available, so:

  • Real-time capture during page load is limited to JavaScript-initiated requests
  • Service Worker requests and image loads may not be visible
  • Headers-only details from the background (via webRequest) are not available as a fallback

Cookie operations have a three-state flow:

let cookieStoreUnreachable: { reason: string; runtimeGone: boolean } | null = null;
  1. Initial state (null): Try the background via chrome.cookies
  2. First read fails: Note the reason, fall back to document.cookie for all subsequent reads and writes
  3. Write operations: Only use the fallback if already marked unreachable; never trigger the fallback on their own

This prevents race conditions: a write timeout does not mean the message didn't arrive—it may have succeeded but lost its reply. Replaying the write against document.cookie could corrupt data.

Timeout Optimization: Once a read has confirmed the store is unreachable, subsequent reads use a shorter timeout (1.5 seconds) instead of the default, so the panel doesn't stall on every refresh.

XML and SVG Documents

The page bridge works in any document realm, including XML and SVG. The element-picking and inspection features work via DOM traversal (safe everywhere), but console evaluation and network hooks may behave differently in non-HTML contexts due to differences in the global object and available APIs.

See XML & SVG Document Support for more details.

Error Handling

PageBridgeError

Thrown by installPageBridge() when injection fails. Always carries a user-friendly message suitable for display in the UI, since no JavaScript console is available to the end user on devices like iPad.

export class PageBridgeError extends Error {}

Silent Failures

Some failures are intentionally silent to avoid noise:

  • Invalid event details (not JSON) are ignored
  • A mismatched id in an eval reply is ignored (may belong to a timed-out request)
  • If the bridge is called on a page that never installed the prehooks, connect() is a no-op

Performance Notes

  • Script injection overhead: ~0–5ms (synchronous execution)
  • Per-evaluation overhead: 1–2ms for event dispatch and JSON serialization
  • Memory: ~12 KB of source code, removed after successful installation
  • No persistent state: Each page load gets a fresh bridge with new channel tokens

The bridge is a fallback, not a replacement: use the background path whenever possible. Chrome and Firefox with a healthy extension runtime should never hit this code path.