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—with a focus on initialization order, diagnostics, fallback strategies, and how data moves between backgrounds, panels, and injected scripts.

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 is the central orchestrator of Inspector Lab, started as a service worker. Its chrome.runtime.onMessage listener is registered before any other logic runs, ensuring resilience even if essential APIs (like chrome.storage.session or chrome.webRequest) are missing or fail to initialize—such as on partial implementations like the Orion browser. This keeps the panels responsive to messages under all conditions.

Core responsibilities:

  • Message Routing: Handles all chrome.runtime.onMessage requests from the panel UI and content scripts, routing message types like PING, EVALUATE, TRACK_INSPECTOR, FETCH_SOURCE, GET_COOKIES, SET_COOKIE, DELETE_COOKIE, CLEAR_SITE_COOKIES, GET_NETWORK_DETAILS, INTERCEPT_CONSOLE, and INTERCEPT_NETWORK to the correct handler.

  • Early Prehook Registration: Registers console and network capture prehooks via chrome.scripting.registerContentScripts at document_start in the MAIN world for every origin that has a user-granted host permission; avoids duplicate registration by checking by content-script id.

  • Network Interception: Uses chrome.webRequest (onSendHeaders, onCompleted, onErrorOccurred) to build a headers-only HTTP request log per inspected tab (capped at 500 entries per tab), covering all resource types that fetch/XHR cannot reach. Log is stored in-memory and dropped on service worker restart.

  • Cookie Operations: Bridges access to chrome.cookies for read, write, and delete, with identity/permission checks.

  • Session Tracking: Uses chrome.storage.session if available, with a transparent in-memory Map fallback for browsers like Orion on iOS that lack it. This records which tabs have the inspector open (keyed by tabId) and which panel is active for each origin. The fallback guarantees persistence only for the lifetime of the service worker—sufficiently aligned with browser session intent.

  • Source Fetching: Reads source files (HTML, CSS, JS) on demand, capped at 60 KB to match the Sources panel's display cap; preview responses truncated to 2 KB for fast initial loads.

  • Cookie Retrieval at Scale: Fetches all cookies using chrome.cookies.getAll() only if the user has granted the optional all-hosts permission; otherwise, returns an empty set.

The chrome.runtime.onMessage listener is set up as the first step in the module, so that even if a later API call throws during module initialization, the extension can still respond to messages—converting a fatal silent failure into a gracefully handled per-message error. Each handler contains its own try/catch for granular recovery.

Storage and Origin-based Keys:

  • openTab:{tabId} → origin of the tab with an inspector open.
  • panel:{origin} → name of the active panel tab for that origin.
  • Prehook state is tracked as inspector-lab-prehook:{origin} and used to prevent redundant content script registration.

Diagnostic Logging:

A global error handler is installed in the background worker via installGlobalDiagnostics("background"). All uncaught errors are collected in a persistent diagnostics log, accessible from the extension popup (especially vital on platforms like iPad where console is unavailable).

Inspector Entry Point (inspector-entry.tsx)

The main React application is injected into every inspected page as a shadow-DOM root:

  • UI Frame Management: The inspector is rendered floating or docked, with resizable and draggable behavior for flexible layouts.
  • Panel Coordination: Offers a tabbed interface: Elements, Console, Sources, Network, Cookies, Storage.
  • State Synchronization: Bridges data and action flows between UI panels, the background, and injected page world using the messaging protocol.
  • Device Support: Adapts for desktop and tablet, including sheet-like UI on tablets.
  • Theme Management: Reads and synchronizes theme settings (system, custom), updating immediately on changes.
  • Reload Persistence: Detects page reloads and ensures the inspector is re-injected if previously open.

Style Collection (injected/inspector-styles.ts)

Elements panel sidebar reconstructs CSS provenance for the selected node in the MAIN world (relying on standard DOM APIs):

  • Sheet Discovery: Gathers all matching document stylesheets, adopted sheets, shadowroot sheets, and imported sheets, subject to current media queries and excluding disabled/extension-injected sheets.
  • Rule Matching: Walks through complex CSS constructs—@media, @supports, @container, @layer, @scope—tracking active/inactive conditions.
  • Cascade Resolution: Decides declaration status by importance, specificity, and order, marking ambiguous states as "uncertain" where CSSOM cannot decide.
  • Inheritance: Follows the ancestor chain across regular/shadow DOM and slot assignment, striking out properties defined nearer to the target or that aren't inherited.
  • Guardrail: Halts after 10,000 rules to avoid runaway computation, clearly flagging truncated results.

A debounced mutation observer in the inspector trigger recollection after DOM or class changes (250ms debounce) to keep the sidebar current but not overly reactive.

Page Bridge

Handles code evaluation and event capture inside the MAIN world for cases where background APIs or direct extension script execution is unavailable:

  • Evaluation Fallback: Runs JS expressions from the panel in the page context when chrome.scripting.executeScript can't be used (e.g. if blocked by CSP).
  • Hook Bridging: Connects console and network capturing prehooks to panel-side listeners via custom page events.

Message Flow

Typical Request–Response

  1. Panel action: (e.g., Console "Evaluate") sends a message via sendRuntimeMessage() to the background.
  2. Background receives the message through chrome.runtime.onMessage.
  3. Background processes the request and performs the associated API actions.
  4. Background returns a result or error.
  5. Panel receives and updates its UI accordingly.

Console & Network Capture

Early capture guarantees no events are lost on fresh page loads:

  1. On Inspector Open: The background calls ensurePrehookRegistered() and registers prehooks for the current origin in MAIN world at document_start (if per-site host permission exists).
  2. Prehooks: Inject hooks into the page for window.__inspectorLabConsoleHook and window.__inspectorLabNetworkHook.
  3. Event Buffering: Hooks buffer messages until the inspector connects.
  4. Channel Open: INTERCEPT_ messages establish message channels; buffered events are flushed, and further events flow live.
  5. Panel Forwarding: Content script listens for custom page events and pipes them to the UI panel.

If prehooks can't be registered (no host permission), capture falls back to only working after the next page reload. Diagnostic logging notes these situations.

Storage & Persistence

Session Storage

Tracks which tabs have the inspector open and the active panel per origin:

  • openTab:{tabId} → origin
  • panel:{origin} → active panel name

Backed by chrome.storage.session with an in-memory Map as a fallback for browsers lacking full support. The Map is hydrated on worker startup and stays in sync across message handlers for robust reload persistence within worker lifespan.

Network Log

Per-tab, in-memory Map records headers-only log (URL, method, status, headers, timing) using chrome.webRequest. Up to 500 entries per tab; oldest are evicted on overflow. Lost on service worker restart (as expected).

Settings

User preferences (color scheme, themes, layout) are stored in chrome.storage.local, watched for changes and synchronized across tabs for a consistent UI.

Error Handling & Fallbacks

Backward-compatible, defensive error handling enables graceful degradation:

FailureFallback
No chrome.storage.sessionIn-memory Map for session; persists as long as the worker lives
No chrome.webRequestNetwork capture falls back to fetch/XHR via prehooks or bridge
Blocked chrome.scripting.executeScriptPage bridge runs evaluation via injected code in MAIN world
chrome.permissions.contains absentSkips prehook registration; capture begins on next reload
Background unreachableCookie ops fall back to document.cookie (read-only); eval via page bridge
No chrome.tabs.queryqueryTabs() resolves null, UI signals unavailability

Each degradation is logged for diagnostics, and surfaced in the UI where relevant, so limitations are clear to the user.

Cross-World Communication

Inspector Lab spans three browser JavaScript contexts:

WorldRoleCapabilities
Isolated (Content)Inspector UI hostchrome.* APIs; blocks direct page access
MAIN (Page Context)Hooks, evaluationFull DOM/window; no extension APIs
Service WorkerBackground relayExtension APIs; long-lived message hub

Bridges and communication:

  • chrome.runtime.sendMessage (between Content/Panel and Service Worker)
  • Custom document events (between Content and MAIN)
  • Page bridge (injects code/handlers into the page when needed)

Key Design Decisions

Why register the message listener first?

Because module-scope failures on certain browsers (e.g., missing chrome.storage.session) would prevent the listener from ever being registered, muting the entire inspector. Registering the listener first ensures subsequent failures are recoverable per-message, keeping diagnostics and UI alive.

Why prehooks?

Only code injected into the page MAIN world at document_start can reliably capture console/network events that occur before the inspector UI connects, especially on reloads. Without prehooks, you miss these early events.

Why an in-memory session mirror?

Browsers with poor support for session storage can't guarantee persistence. A local Map keeps session behavior working as long as possible, aligning with the intended contract.

Why per-tab network logs?

chrome.webRequest (in background, per tab) observes all network activity, including resource types that fetch/XHR don't cover (e.g., images, stylesheets). Storing results in-memory is efficient; logs drop off at worker restart, but that's acceptable for a live inspector tool.

Why document-scoped themes?

Panels must reflect both system and user preferences. Settings changes propagate instantly, ensuring UI is always aligned to chosen or system themes.

Why origin-based prehook registration?

Permissions and security boundaries are origin-specific. The extension registers prehooks only for origins where the user has granted per-site permission. Functional capture is deferred until reload where permission is missing.


See also: