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.

Architecture Overview

Inspector Lab is a browser extension DevTools inspector designed for web debugging across desktop and tablet browsers. This page describes the system architecture, key components, and how they interact.

System Architecture

Custom Events chrome.runtime.sendMessage chrome.runtime.sendMessage Injection Script React Render Page Context(MAIN World) Content Script(Isolated World) Background Service Worker Inspector UI(Shadow DOM)

Key Components

Background Service Worker (background.ts)

The background script runs as the extension's central hub. Its message listener is registered at module scope—before any other initialization—to ensure it remains responsive even if later APIs fail to initialize.

Core responsibilities:

  • Message routing: Handles all chrome.runtime.onMessage calls from the inspector UI and content scripts, dispatching to handlers for PING, EVALUATE, TRACK_INSPECTOR, FETCH_SOURCE, GET_COOKIES, SET_COOKIE, DELETE_COOKIE, CLEAR_SITE_COOKIES, GET_NETWORK_DETAILS, INTERCEPT_CONSOLE, and INTERCEPT_NETWORK operations
  • Prehook registration: Registers console and network prehooks via chrome.scripting.registerContentScripts at document_start in the MAIN world for each origin that has granted per-site host permission; prehooks capture console and network events before any page script runs
  • Network interception: Listens to chrome.webRequest events (onSendHeaders, onCompleted, onErrorOccurred) to maintain a headers-only log of HTTP requests for inspected tabs (capped at 500 entries per tab; oldest entries are dropped when exceeded)
  • Cookie operations: Bridges access to chrome.cookies API for read, write, and delete operations; validates cookie identity and handles permission checks
  • Session tracking: Uses chrome.storage.session with in-memory Map fallback to track which tabs have the inspector open (keyed by tab ID) and which panel is active per origin; in-memory fallback is essential on browsers like Orion for iOS that lack or partially implement the session storage API
  • Source fetching: Retrieves page sources (HTML, CSS, JavaScript) with a 60 KB per-file limit, matching the Sources panel's display cap; capped previews are shown at 2 KB
  • Cookie retrieval at scale: Fetches all cookies via chrome.cookies.getAll() when the user grants the optional all-hosts permission, or returns an empty set otherwise

The message listener is registered at the very first line to ensure it stays responsive even if later initialization fails. This prevents silent failures where panels go completely mute if APIs like chrome.storage.session or chrome.webRequest are unavailable. Each per-message handler has its own try-catch, so individual operations can fail gracefully without silencing the listener.

Storage and origin-based keys:

  • openTab:{tabId} → origin of the tab with an open inspector
  • panel:{origin} → name of the active panel tab for that origin
  • Prehook registration is keyed by inspector-lab-prehook:{origin} and is checked before re-registering to avoid duplicate content scripts

Diagnostic logging:

The background worker installs a global error handler via installGlobalDiagnostics() so uncaught errors are logged to persistent diagnostics, readable from the popup—the only console available on iPad.

Inspector Entry Point (inspector-entry.tsx)

The main React application mounted into a shadow-DOM host on every inspected page:

  • UI frame management: Maintains the inspector window as floating or docked (bottom, left, right) with resize and drag support
  • Panel coordination: Houses a six-panel tab interface (Elements, Console, Sources, Network, Cookies, Storage)
  • State synchronization: Bridges between the panel UI and the page/background via the messaging protocol
  • Device support: Handles both desktop (floating/docked windows) and tablet (sheet-like UI) layouts
  • Theme management: Applies theming from settings and syncs color-scheme preferences
  • Reload persistence: Re-injects itself after page reloads when the inspector was open (not closed)

Page Bridge

A lightweight transport for code that needs to run in the page's MAIN world when the background cannot be reached:

  • Evaluation fallback: Runs JavaScript expressions in MAIN context via injected scripts when chrome.scripting.executeScript is unavailable (e.g., under strict Content Security Policy)
  • Capture hooks: Connects console and network interceptors to the page's event system as a fallback to prehook registration

Message Flow

Typical Request–Response

  1. Panel action (user clicks "Evaluate" in Console)
  2. Panel sends runtime message via sendRuntimeMessage() to background
  3. Background receives via chrome.runtime.onMessage
  4. Background processes (e.g., calls chrome.cookies.getAll())
  5. Background sends response back to panel
  6. Panel updates UI with the result

Console & Network Capture

Early capture requires code injected before page scripts run:

  1. On inspector open, background calls ensurePrehookRegistered() to register console and network prehooks via chrome.scripting.registerContentScripts with runAt: "document_start" and world: "MAIN" (registration is skipped if the origin lacks host permission)
  2. Prehooks install hooks into window.__inspectorLabConsoleHook and window.__inspectorLabNetworkHook to capture events
  3. Hooks buffer events until the inspector sends INTERCEPT_CONSOLE_MESSAGE or INTERCEPT_NETWORK_MESSAGE with a unique event name
  4. Background flushes buffered events into the page by dispatching custom events and maintains the event channel open
  5. Page code dispatches events on document, caught by a content-script listener that forwards them back to the panel

If prehooks cannot be registered (no per-site host permission granted), console and network capture starts at the next page reload instead — an acceptable degradation since users must opt in to full functionality. Diagnostic logging records when permissions are unavailable.

Storage & Persistence

Session Storage

Used to track which tabs have the inspector open and remember the active panel per origin:

  • Key: openTab:{tabId} → value: origin
  • Key: panel:{origin} → value: active tab name
  • Implementation: Native chrome.storage.session with in-memory Map fallback for browsers that don't support it (e.g., Orion on iOS); the in-memory mirror is hydrated on service-worker start by reading all keys and re-hydrated on each message, so reload persistence survives service-worker restarts as long as the tab remains open

Network Log

A per-tab Map in the background tracks HTTP requests captured by chrome.webRequest listeners:

  • Size limit: 500 entries per tab (oldest dropped when exceeded)
  • Data: Capped to headers only (status, URL, method, request/response headers, timing); full request bodies are lazily fetched via FETCH_SOURCE_MESSAGE only when the user inspects a row
  • Lifetime: Lost on service-worker restart; panel treats this the same as pre-inspector history

Settings

User preferences (color scheme, custom theme, panel layout) are stored in chrome.storage.local and watched for changes so the UI stays in sync across tabs.

Error Handling & Fallbacks

The architecture gracefully degrades when APIs are unavailable:

FailureFallback
chrome.storage.session missing or failingIn-memory Map for session tracking; reload persistence works for the lifetime of the tab
chrome.webRequest unsupportedNetwork panel falls back to fetch/XHR hooks from page (via prehooks if registered, or page bridge if CSP-blocked)
chrome.scripting.executeScript blockedPage bridge runs evaluation via injected script in the page's MAIN world
chrome.permissions.contains unavailablePrehook registration is skipped; console/network capture starts at next reload
Background unreachableCookie operations use document.cookie (read-only); evaluation falls back to page bridge

Each degradation is logged diagnostically and, where appropriate, surfaced in the UI so users understand why functionality is limited.

Cross-World Communication

Inspector Lab operates across three JavaScript worlds:

WorldRoleCapabilities
Isolated (Content Script)Inspector UI hostAccess to chrome.* APIs; cannot touch page globals
MAIN (Page Context)Hooks & evaluationFull page access; no extension APIs
Service WorkerBackground relayAll extension APIs; long-lived relay

Communication between worlds uses:

  • chrome.runtime.sendMessage (Isolated ↔ Service Worker)
  • Custom events (Isolated ↔ MAIN via document.dispatchEvent)
  • Injected scripts (bootstrap page bridge from Isolated into MAIN)

Key Design Decisions

Why register the message listener at module scope?

Two critical APIs—chrome.storage.session and chrome.webRequest—are absent or partially implemented on some browsers (Orion for iOS). If either throws at module scope before the message listener is registered, every panel goes completely silent because there is no listener to answer. By registering the listener first, any initialization failure becomes a per-message error that the handler can catch and respond to, rather than a fatal invisible failure.

Why prehooks?

Console and network events fire before any inspector UI can attach. Prehooks installed at document_start capture them early and buffer until the inspector connects, ensuring no events are lost. Without prehooks, events that fire in the first few milliseconds are missed, leaving gaps in the console and network log.

Why in-memory session storage?

Browsers like Orion on iOS expose chrome.storage.session incompletely or not at all. An in-memory mirror lets the inspector work everywhere with a narrower (session-only) contract rather than failing silently. The mirror is hydrated on service-worker start and kept in sync by message handlers, so reload persistence survives as long as the tab is open.

Why per-tab network logging in the background?

The background's chrome.webRequest listeners run synchronously and cover all request types (images, stylesheets, fonts) that never pass through fetch/XHR. An in-memory log is fast and sufficient for debugging; full request bodies are lazily fetched only when the user inspects a row, keeping memory use low. The log is capped at 500 entries per tab to prevent unbounded growth.

Why document-scoped themes?

Inspector UI is isolated in shadow DOM but needs to respond to system dark mode and user preference changes. Theme settings are watched and re-applied on every change so the UI stays in sync even if the user toggles their system settings mid-session.

Why origin-based prehook registration?

Host permissions and prehook state are per-origin. Registering prehooks only for origins that have granted permission ensures the inspector respects user intent; deferred capture on reload (when no permission is granted) is an acceptable degradation because users must opt in to full functionality via the popup's permission request.


See also: