Skip to content

Browser capability audit #3

Description

@dvcolomban

Part of Design a portable contribution SDK and WebExtension runtime.

Question

Which browser capabilities can the generic extension SDK promise on Chromium and Firefox, under which permissions and lifecycle conditions, and what evidence establishes each promise? Produce a versioned capability matrix and executable experiment requirements that later contracts can use without assuming identical browser behavior.

Context and current behavior

The destination is a framework-neutral SDK with build-time contributions, shared JSON-rendered interfaces, and adapters for Devframe, DevTools, and browser extensions. Browser support must evolve by adding modules and adapters. A missing Firefox capability must not prevent the remaining extension from working.

The platforms differ materially. Chrome's declarativeNetRequest API covers request blocking, redirects, and header changes; it provides no response-body transform. Firefox exposes webRequest.filterResponseData. Chromium exposes a restricted CDP transport through chrome.debugger, while Mozilla documents that this debugger API is not implemented in Firefox.

Debugger coexistence remains unproven. Chrome's reference says opening DevTools terminates an extension debugging session, while Google's debugger sample opens DevTools before attaching. Neither reading establishes a reliable supported-version contract. The audit must test both orders.

Chrome and Firefox also differ in background execution and sidebar APIs. A shared TypeScript interface does not erase those differences.

Requirements and scope

  • Inventory content scripts, page-world execution, background execution, popup, options, DevTools pages and panels, browser sidebars, messaging, storage, injection timing, request interception, response transformation, and debugger access.
  • Record browser/version, extension context, permissions, target restrictions, activation requirements, reload behavior, and known interference for every capability.
  • Distinguish static absence from denied permission, restricted target, disconnected backend, and a temporarily conflicting owner. Capabilities must be observable when their availability changes.
  • Keep native browser objects local. A capability descriptor and its failure data may cross RPC; browser namespace objects, listeners, and live handles may not.
  • Settle minimum supported versions and the maintenance rule for advancing them. Do not infer a browser floor from whichever version happens to be installed.
  • Separate development HMR against deployed websites from production extension updates. Build-time executable contributions remain packaged; this audit must not promise production remote-code replacement.

Options and recommendation

A lowest-common-denominator API is simple but would discard Chromium debugging and Firefox response filtering. Separate unrelated SDKs preserve browser behavior but defeat contribution reuse. Recommend a common contribution contract with explicit, independently registered capabilities and browser-specific implementations. The capability catalogue should state semantic guarantees, not merely whether a property exists on chrome or browser.

An untested capability is unresolved. It cannot be promoted to supported from a type declaration or a mocked unit test. Version-specific exceptions belong in the matrix with a reproducible case and a date.

Proposed API or experiment

The following is illustrative pseudocode, not an approved public schema:

const availability = await context.capabilities.describe('http.response.transform');
const stopWatching = context.capabilities.watch(change => {
  renderCapabilityState(change);
});

await browserScenario.run({
  host: 'chromium-extension',
  target: fixturePage,
  steps: ['attach-debugger', 'open-native-devtools', 'transform-response'],
});

stopWatching();

Build a small experiment extension with packaged scripts and deterministic HTTP fixtures. Repeat attachment before and after native DevTools, navigate during attachment, and exercise frame replacement and user cancellation. Repeat applicable experiments on Firefox. Record observed results, exact browser versions, API permissions, commands, and traces. Declare unsupported cases through the public contract and test those declarations automatically.

Scenarios and acceptance criteria

  • A contribution using shared state and JSON UI operates on Chromium and Firefox even when debugger access is unavailable on Firefox.
  • Opening or closing a popup and DevTools panel cleans its listeners without stopping unrelated background work.
  • Permission denial, restricted browser pages, navigation, worker termination, reconnect, and extension reload produce defined availability transitions.
  • Native DevTools coexistence is exercised in both attachment orders, including active interception; documentation disagreement stays recorded until observed evidence resolves it.
  • Every SDK-owned API, hook, and feature has a runnable example and an automated assertion on every supported host. The overall example suite includes contributions, renderers, Devframe, DevTools, Chromium, and Firefox. Unsupported paths remain asserted cases, not silent skips.

Testing raw browser exposure means verifying the SDK's local access, ownership, lifecycle, and failure behavior. It does not mean reproducing every browser vendor's API test suite.

Dependencies

None. This audit establishes platform facts needed by later decisions. It may inspect existing upstream code and documentation without waiting for a contract decision.

Definition of Ready

  • The owner can inspect Chromium and Firefox extension documentation and the current repositories.
  • The proposed host inventory and requested extension contexts are listed.
  • Experiments can run against deterministic local fixtures without production credentials or application-specific code.
  • The owner has identified how real extensions and native DevTools interactions will be automated on each candidate browser.

Definition of Done

  • A source-linked matrix assigns every inventoried capability a supported, unsupported, or explicitly unresolved outcome with version and permission requirements.
  • High-risk claims, including debugger coexistence and response interception, have real-browser evidence or remain blocking investigations with named owners.
  • Supported browser floors, capability evolution, and unsupported-result semantics are decided.
  • The resolution defines a complete public-API-to-example-to-test mapping requirement, including failure and lifecycle paths on every supported host.
  • No stubbed behavior, compile-only example, or unobserved claim counts as feasibility proof. Shipping the SDK remains subsequent implementation work.

Resolution record

Post the accepted matrix, experiment links, chosen browser floors, unresolved blockers, and resulting contract implications in the resolution comment. Link that comment from the parent map under this issue's title; keep detailed evidence here.

Activity

  1. self-assigned this
    on Sep 22, 2026
  2. dvcolomban commented on Sep 22, 2026

    @dvcolomban
    CollaboratorAuthor

    Research checkpoint — this audit remains open.

    An isolated Chrome for Testing 151.0.7922.34 run passed 15 assertions covering packaged MAIN-world injection before the parser, world isolation, DNR header changes, restricted debugger domains, response-stage text/HTML rewriting, explicit interception cleanup, and restricted-page rejection. The companion toolchain audit also observed a basic content-to-background round trip in Firefox 156.0.

    These observations establish narrow platform behavior, not SDK conformance. Chrome debugger and Firefox response filtering require different implementations. Native DevTools coexistence still needs both attachment orders tested against the reference and official sample.

    Remaining gates: browser support floors and failure semantics; Firefox filtering and MAIN-world timing; DevTools coexistence; permission and lifecycle transitions; broader interception failure cases. The report records concrete experiment protocols and their owner. Every supported SDK API/host/mode cell still needs a working example and executed behavior assertions.

    Retained research context: local, unpushed branch dvcol/research/browser-capabilities, commit d5c2799, report docs/research/browser-capabilities.md. The full research artifact remains local; no public artifact URL is claimed. This checkpoint is not a resolution or an implementation-complete claim.

  3. dvcolomban commented on Sep 22, 2026

    @dvcolomban
    CollaboratorAuthor

    Resolution

    The Browser capability audit establishes bounded native feasibility and a source-linked capability matrix. Remaining integration and lifecycle guarantees have named acceptance gates. The SDK and its conformance suite remain subsequent work.

    The user-approved initial baseline is current stable Chromium and Firefox, advancing with stable releases. Official metadata retrieved on 2026-09-22 selected Chrome for Testing 153.0.8010.52 and Firefox 156.0.1. Both actual binaries reported those versions and ran in isolated macOS profiles. Cached Chrome 151 results remain historical. This audit adds no ESR, old-version, derivative-browser, mobile, Safari or untested operating-system guarantee. Record exact versions and rerun affected checks before advancing each release's support record. Google metadata, Mozilla metadata.

    Dated capability matrix

    Capability and execution context Chromium evidence and constraints Firefox evidence and constraints SDK status and required follow-up
    Static content script in an isolated world Observed on 153.0.8010.52. Packaged script ran with messaging access and separate globals. Chrome lists only a subset of extension APIs in content scripts. Observed on 156.0.1: isolated globals separated from MAIN, runtime messaging completed with the event background. Supported native in both bounded fixtures; contribution lifecycle unresolved. Chrome content scripts, Mozilla execution worlds.
    document_start MAIN-world script Observed before the fixture's first parser script. Manifest world dates to Chrome 111; programmatic MAIN dates to Chrome 95. Observed before the first parser script on 156.0.1, with runtime messaging absent in MAIN. Manifest world is documented from Firefox 128. Supported native for pre-registered top-level scripts. Keep execution-world and timing capabilities separate. No result yet for strict CSP, frame variants or scripts registered after navigation. Manifest compatibility, Scripting compatibility.
    Dynamic registration and one-shot injection scripting plus granted host access or activeTab; dynamic registration from Chrome 96. Restricted chrome:// injection was rejected in the experiment. Scripting registration from Firefox 102. Host grants can change during use. Registration is not retroactive parser-time execution. Permission denial, revocation and navigation races unresolved. Chrome scripting, Mozilla host permissions.
    Same-origin, cross-origin and related frames Target selection can use frame/document identity. Each matching frame needs the relevant access. Compatibility data records an empty-iframe exception to document_start, including match_about_blank; origin-fallback support starts at 128. Unresolved. Test blank, blob, sandboxed, same-process and out-of-process frames separately. Content-script compatibility, Chrome scripting.
    MV3 background execution Documented service worker. Global variables do not survive shutdown. An active debugger session keeps it alive from Chrome 118. Documented nonpersistent background scripts/event page; background.service_worker is unsupported. Browser-specific host entry points required. Recovery and state retention untested. Chrome lifecycle, Mozilla background manifest.
    Popup Documented packaged action popup. Closing it ends its document lifetime. Documented MV3 action.default_popup. Actual open/close, action routing and listener cleanup unresolved on both. Chrome popup, Mozilla action.
    Options document Documented options_page or options_ui, with tab or embedded behavior. Documented options_ui with browser-specific presentation. Rendering and persisted options round trip unresolved. Chrome options, Mozilla options.
    DevTools page, panel and DevTools sidebar Documented packaged devtools_page, which lives with the DevTools window and can create panels. Documented DevTools page, panels and inspected-window integration. Both hosts need real panel mounting and teardown tests. An extension DevTools panel is distinct from the Vite DevTools host. Chrome DevTools extensions, Mozilla DevTools extensions.
    Browser side panel/sidebar sidePanel from Chrome 114 MV3; open() from 116 requires user interaction and the sidePanel permission. sidebarAction with sidebar_action manifest entry; incompatible with Chrome's API. Separate adapters. Opening, tab switching and disposal unresolved. Chrome sidePanel, Mozilla sidebarAction.
    Messaging and ports Documented JSON serialization. A content script cannot directly access the privileged debugger namespace; observed here. Runtime message round trip observed on 156.0.1; structured-clone semantics documented. Supported native for basic messaging; ports/reconnection unresolved. Use an explicit portable wire codec. Raw browser objects and callbacks stay local. Disconnect/reconnect behavior unresolved. Chrome messaging, Mozilla sendMessage.
    Storage and change notifications Documented storage permission; local/session/sync areas have different retention. storage.session clears on extension reload/update and browser restart. Documented asynchronous extension storage and change notifications. Storage APIs exist; shared-state consistency, revisions and restoration are separate SDK contracts. Chrome storage, Mozilla storage.
    Request observation webRequest plus host access. For subresources, access to request and initiator matters. Observed onBeforeRequest for document and fetch traffic on 156.0.1; Firefox activeTab does not grant network interception. Firefox observation supported native in the fixture; Chromium observation unresolved. Observation and modification require distinct capabilities. Chrome webRequest, Mozilla incompatibilities.
    Request/response header modification DNR request and response header rules observed. Rule type and host grants determine authority. Normal MV3 extensions cannot use blocking webRequest; policy-installed extensions are an exception. Blocking webRequest is documented, including response filtering. Its permissions differ from Chromium. Chromium bounded success. Firefox semantics, conflicting rules and permission removal unresolved. Chrome DNR, Chrome webRequest, Mozilla response filtering.
    HTTP response body or HTML-byte transform DNR body transform unsupported. Debugger Fetch response-stage transformation observed for ASCII text and HTML on 153.0.8010.52. Observed text, HTML and three-chunk UTF-8 filtering on 156.0.1. MV3 needs webRequestFilterResponse, webRequest, webRequestBlocking and host access; missing filter permission rejected all three calls. Supported native for measured buffered transforms. Incremental output, compression, redirects, cancellation, cache and CSP outcomes unresolved. Debugger/Firefox implementations remain separate. DNR actions, Fetch schema, Mozilla filtering.
    Debugger commands and events Requires manifest debugger, which cannot be optional; transport exposes a restricted CDP domain set. Runtime.evaluate passed; Browser.getVersion was rejected. Flat child sessions date to Chrome 125. Chrome's debugger API is documented as unimplemented. Firefox native-CDP capability unsupported. Chromium command coverage, child sessions and CDB integration unresolved beyond the bounded native test. Chrome debugger, Mozilla incompatibilities.
    Debugger plus native DevTools Both orders passed on 153.0.8010.52 with a ready native Network frontend, extension evaluation 42, frontend evaluation 54, zero observed detach events, and response fulfillment in both active-Fetch cases. No equivalent extension debugger API. Supported native for these four measured cases. Breakpoints, cancellation, navigation, competing owners and other versions remain unresolved. Do not generalize from the bounded observation. Debugger detach reference, Google sample.
    Executable updates and HMR Packaged extension updates, unpacked development reloads and server UI HMR are different mechanisms. Companion audit records extension-page HMR on the current stable builds; its exact reload limits belong in the toolchain audit. Store policy requires self-contained code and rejects remote code execution as a generic update mechanism. Generic production replacement of privileged extension modules through a remote HMR server is unsupported as the default delivery contract. Other development contexts and watched-production-preview behavior remain unresolved. Chrome MV3 policy, Mozilla add-on policy.

    What was observed

    On stable Chromium, the original native capability fixture passed fifteen assertions across eight groups: static MAIN before the first parser script, isolated globals and messaging access, DNR request/response headers, debugger evaluation, rejection of browser-level CDP through the extension transport, buffered text and HTML-byte transformation, explicit Fetch-disable/detach restoration, and rejection of chrome:// injection. These are native operation checks; the simultaneous Playwright controller is not the native DevTools evidence.

    A separate headed-browser fixture exercised four real native Network frontend cases: extension first and frontend first, each without interception and with a response paused. It verified the native frontend document was ready and its own TargetManager contained the inspected fixture. After both clients attached, the frontend's existing RuntimeAgent evaluated 54 and the extension debugger evaluated 42. Both intercepted requests fulfilled as transformed-response, HTTP 200; no detach event was observed in these runs. The controller never autoattached or opened a CDP session to the inspected page. It controlled its own extension page and briefly inspected its own frontend document. This establishes the measured coexistence cases on 153.0.8010.52, despite the earlier documentation/sample ambiguity. It does not guarantee breakpoint behavior, cancellation, navigation, other owners or future versions. Chrome debugger, CDP Target schema.

    On stable Firefox, packaged MAIN code ran before the fixture's first parser script, isolated globals stayed separate, isolated messaging reached the event background, and browser.debugger was absent. With the required permissions, filterResponseData transformed text, HTML, a pre-parser HTML script insertion, and UTF-8 input delivered in three chunks with multibyte characters split across chunks. This fixture buffers until onstop and writes once; it does not prove incremental downstream streaming. A second manifest lacking only webRequestFilterResponse rejected all three filtering calls with the browser's missing-permission error and delivered unchanged bodies. This is absent permission, not user refusal or revocation. Mozilla response filtering.

    Contract consequences

    Retain independently registered capabilities with browser-specific implementations. DNR cannot transform response bodies; Chromium can use its restricted debugger Fetch domain. Firefox can use response filtering but has no equivalent native extension debugger API. The remaining contribution must still run when a particular capability is unsupported. Raw browser namespaces, listeners and handles stay in their eligible local context; descriptors, target IDs and serializable results can cross RPC.

    The semantic baseline distinguishes unsupported implementation, wrong execution context, missing permission, restricted target, disconnected backend, conflicting owner and stale target. Contribution and realm contract chooses concrete public names and serialization without collapsing these distinctions. The audit does not introduce SDK error types or availability watchers.

    Build-time executable contributions remain packaged. Development page HMR, content reinjection, background restart, watched production preview and installed extension update need distinct lifecycle contracts. Production remote replacement of privileged extension code is not a generic supported delivery mechanism. Chrome debugger cannot be an optional runtime permission. Chrome permission exclusions, MV3 code policy.

    Owned handoff and blocking acceptance gates

    dvcol is accountable for these existing contract tickets:

    • Debugger and CDB contract: retain the four-case native evidence, then prove breakpoint, close/reopen, cancellation, navigation, owner conflict and optional CDB behavior. No CDB integration was exercised here.
    • Injection and transform contract: gate streaming, compressed/binary/large responses, redirects, cache/service-worker paths, strict CSP, frame replacement and finite request deadlines on real evidence.
    • State scope and recovery: observe actual background termination without inspection keepalive, next activation, durable state and pending-operation outcomes on both hosts. These transitions remain unrun.
    • Permissions and trust: distinguish missing grants from real gesture-driven refusal, grant, host revocation and restoration. Only absent filtering permission was observed here.
    • Renderer and surface contract: exercise actual popup, options, native DevTools panel and browser sidebar mounting/disposal. Extension documents alone do not establish native UI lifecycle.
    • Live preview and reload contract: consume toolchain evidence and define each update mode's surviving state, listeners and ownership.
    • Examples and API coverage contract: require every public SDK export/API/hook/feature to map to a runnable example and automated assertion on every supported host, including contributions, renderers, Devframe, DevTools, Chromium and Firefox. Unsupported, failure and lifecycle paths are required cases. No stub or compile-only example counts. Raw-exposure tests cover SDK access, ownership and disposal rather than retesting every vendor method.

    The source-linked matrix explicitly leaves unobserved guarantees unresolved. The research Definition of Done permits high-risk claims to remain blocking investigations with named owners, so these gates do not require implementing the future SDK before research closure.

    Retained executable evidence is on local, unpushed branch dvcol/research/browser-capabilities, commit 8ce8658, under docs/research/. The source-linked findings above are the public resolution; no hosted artifact URL is claimed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

wayfinder:researchEvidence gathering that unblocks a design decision

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions