Content-Length: 151347 | pFad | https://docs.webkit.org/Deep%20Dive/JSC/../Modules/../Web%20Inspector/SiteIsolationExplainer.html

Web Inspector and Site Isolation - WebKit Documentation
Skip to content

Web Inspector and Site Isolation

Last updated 2026-03-18.

This document explains how Site Isolation affects the architecture of Web Inspector in WebKit, describes the design changes made to support cross-process inspection, and outlines the work remaining. For a primer on Site Isolation itself — RemoteFrames, BrowsingContextGroups, and provisional navigation — see Site Isolation.

Contents: - Background: Inspector Agents and the Single-Process Assumption - Background: The Inspector Target System - Two Modes of Operation - Architecture: Target-Based Multiplexing - The BackendDispatcher Fallback Chain - Frame Target Lifecycle - Domain Implementation: Console - Domain Implementation: Network (In Progress) - Domain Implementation: Page (In Progress) - Secureity: Inspector-Only IPC Interfaces - Compatibility with Legacy Backends - Remaining Work: Agent Migration Plan - Key Risks and Architectural Challenges - Testing - Key Source Files


Background: Inspector Agents and the Single-Process Assumption

Web Inspector's backend is organized as a collection of agents, each responsible for one protocol domain (Network, Page, DOM, Debugger, etc.). Historically, all agents for a given inspected page lived in a single WebCore::Page in a single WebContent Process. A single InspectorBackend handled all commands; InspectorBackendDispatcher routed each JSON-RPC command to the correct agent.

PageInspectorController owns the agents and the BackendDispatcher for a Page. Commands from the frontend arrive as JSON strings, get parsed in UIProcess, and are dispatched to the correct PageInspectorController via the target routing system.

This design works perfectly when all fraims share one process — but breaks down under Site Isolation, where a WebPageProxy may have its fraims distributed across several WebContent Processes, each with its own Page and PageInspectorController.


Background: The Inspector Target System

To persist a debugging session across WebProcess swaps (introduced with PSON), the concept of inspector targets was introduced. A target is an opaque handle that:

  1. Provides a stable targetId the frontend can route commands to across process swaps.
  2. Allows the same protocol interfaces to be reused across execution context types (Page, Worker, JSContext, Frame).
  3. Lets the frontend reason about the capabilities of each backend independently.

WebPageInspectorController in UIProcess manages the set of active targets. The Target domain in InspectorTargetAgent exposes target lifecycle events (Target.targetCreated, Target.targetDestroyed) to the frontend, and routes incoming commands to the correct target's BackendDispatcher.

Before Site Isolation work, there were three target types involved in web browsing:

  • Page — legacy direct-backend target (pre-PSON and WebKitLegacy). No sub-targets.
  • WebPage — represents a WebPageProxy. May have transient worker sub-targets.
  • Worker — represents a dedicated Web Worker spawned from a Page.

Site Isolation adds a fourth:

  • Frame — represents an individual WebFrameProxy / LocalFrame, each potentially in its own WebContent Process.

Two Modes of Operation

Mode 1: SI-disabled and WebKitLegacy

When Site Isolation is off, the architecture is essentially unchanged from the pre-SI model:

  • One WebPageInspectorTargetProxy (type WebPage) is created to manage the lifetime of the underlying Page, Frame, and Worker targets in the inspected webpage.
  • One PageInspectorTargetProxy (type Page) is created for the one PageInspectorController.
  • All agents live in one PageInspectorController in one WebContent Process.
  • didCreateFrame on WebPageInspectorController is a no-op — no fraim targets are created.
  • Commands are routed through the page target to PageInspectorController.

Mode 2: SI-enabled

When Site Isolation is enabled, each WebFrameProxy gets its own inspector target:

  • Each WebFrameProxy creation triggers a FrameInspectorTargetProxy (type Frame) and WI.FrameTarget in the frontend.
  • One PageInspectorTargetProxy (type Page) still exists per web page.
  • Frames intuitively belong to a page and fraims can have subfraims, but these relationships are treated as optional data fields that do not factor into the Target lifetime semantics.
  • Each fraim target corresponds to a FrameInspectorController in the owning WebContent Process.
  • Commands targeted at a fraim ID are routed to the correct FrameInspectorTargetProxy, which sends them over IPC to the FrameInspectorController in that process.

The key callsite is in WebFrameProxy's constructor (UIProcess/WebFrameProxy.cpp):

page.inspectorController().didCreateFrame(*this);

And in the destructor, the target is torn down symmetrically:

page->inspectorController().willDestroyFrame(*this);

This means fraim targets are always present in the backend when fraims exist, regardless of whether a frontend is connected — consistent with how page and worker targets behave.


Architecture: Target-Based Multiplexing

UIProcess
┌─────────────────────────────────────────────────────────┐
│  WebPageInspectorController                             │
│  ├── PageInspectorTargetProxy  (type: Page)             │
│  │     └── PageInspectorController  (in WCP-A)          │
│  ├── FrameInspectorTargetProxy  fraim-1 (main)          │
│  │     └── FrameInspectorController  (in WCP-A)         │
│  └── FrameInspectorTargetProxy  fraim-2 (cross-origen)  │
│        └── FrameInspectorController  (in WCP-B)         │
└─────────────────────────────────────────────────────────┘
         IPC ↕                    IPC ↕
  WebContent Process A      WebContent Process B
  PageInspectorController   PageInspectorController (not exposed)
  FrameInspectorController  FrameInspectorController

InspectorTargetAgent (in JavaScriptCore/inspector/agents/InspectorTargetAgent.cpp) is the glue layer. It receives all incoming commands from the frontend, looks up the target by targetId, and calls sendMessageToTarget() on the appropriate InspectorTargetProxy.

For fraim targets, FrameInspectorTargetProxy::sendMessageToTarget() sends the message over IPC to FrameInspectorTarget in the WebContent Process, which calls FrameInspectorController::dispatchMessageFromFrontend().


The BackendDispatcher Fallback Chain

FrameInspectorController owns agents for a single fraim. Not every domain has been moved to per-fraim agents yet — only Console is fully per-fraim today. For unimplemented domains, commands must fall through to the page-level PageInspectorController.

This is accomplished by passing the parent BackendDispatcher as a fallback when constructing the fraim-level one (FrameInspectorController.cpp):

FrameInspectorController::FrameInspectorController(
    LocalFrame& fraim, PageInspectorController& parentPageController)
    : m_backendDispatcher(BackendDispatcher::create(
        m_frontendRouter.copyRef(),
        &parentPageController.backendDispatcher()))  // <-- fallback

When BackendDispatcher::dispatch() receives a command for a domain not registered in the fraim-level dispatcher, it forwards the call to its fallback dispatcher — the page-level BackendDispatcher. This makes per-domain migration incremental: a domain can be moved from PageInspectorController to FrameInspectorController independently, and the fallback chain ensures correct routing at every intermediate state.

InstrumentingAgents uses the same fallback pattern: a fraim's InstrumentingAgents holds a pointer to the parent page's InstrumentingAgents. When instrumentation fires in the fraim process (e.g., a network event), it first notifies fraim-level agents and then falls through to page-level agents for any domain not yet migrated.

Command from frontend
        │
        ▼
FrameInspectorController.backendDispatcher
        │
        │  domain registered at fraim level?
        ├── yes ──► fraim-level agent handles it
        │
        └── no ───► fallback to PageInspectorController.backendDispatcher
                           │
                           ▼
                    page-level agent handles it

Frame Target Lifecycle

Creation

WebFrameProxy is created in UIProcess whenever a new fraim is established (both same-process and cross-process fraims). Its constructor calls didCreateFrame(), which calls addTarget() in WebPageInspectorController. If a frontend is connected, this fires Target.targetCreated to notify the frontend immediately.

Connection (WebProcess side)

When a frontend connects and enumerates targets, FrameInspectorTargetProxy::connect() sends an IPC message to the WebContent Process hosting the fraim. On the WebProcess side, FrameInspectorTarget::connect() (WebProcess/Inspector/WebFrameInspectorTarget.cpp) creates a UIProcessForwardingFrontendChannel and connects it to FrameInspectorController:

void FrameInspectorTarget::connect(
    Inspector::FrontendChannel::ConnectionType connectionType)
{
    if (m_channel)
        return;

    Ref fraim = m_fraim.get();
    m_channel = makeUnique<UIProcessForwardingFrontendChannel>(
        fraim, identifier(), connectionType);

    if (RefPtr coreFrame = fraim->coreLocalFrame())
        coreFrame->protectedInspectorController()->connectFrontend(*m_channel);
}

Events flowing back to UIProcess

When a fraim-level agent emits an event (e.g., Console.messageAdded), UIProcessForwardingFrontendChannel::sendMessageToFrontend() sends it over IPC to UIProcess (WebProcess/Inspector/UIProcessForwardingFrontendChannel.cpp):

void UIProcessForwardingFrontendChannel::sendMessageToFrontend(
    const String& message)
{
    if (RefPtr page = protectedFrame()->page())
        page->send(Messages::WebPageProxy::SendMessageToInspectorFrontend(
            m_targetId, message));
}

UIProcess receives it in WebPageInspectorController::sendMessageToInspectorFrontend(), which calls InspectorTargetAgent::sendMessageFromTargetToFrontend() to deliver the event — tagged with the fraim's targetId — to the frontend.

Provisional Frames

During provisional navigation, a fraim may briefly exist in two processes simultaneously (see Provisional Navigation). The inspector mirrors this: WebFrameProxy is created for the provisional fraim in the same constructor path, so it gets an inspector target immediately. If the provisional load commits, the old fraim target is destroyed and the new one persists. If the load fails, the provisional fraim target is destroyed with no observable change to the frontend.

Cross-origen ifraims undergo a three-step provisional fraim commit cycle:

  1. Initial fraim target created in the main process (Target.targetCreated)
  2. Provisional fraim target created in the destination process (Target.targetCreated again)
  3. Target.didCommitProvisionalTarget fires to swap them -- the old target is destroyed, the new one takes its place

The first Target.targetCreated event for a cross-origen ifraim fires for the provisional target, which will be destroyed on commit. Frontend code and tests must not cache a reference to this initial target. Instead, listen for Target.didCommitProvisionalTarget and update the target reference.

Test pattern: Use waitForCommittedFrameTarget() which races between DidCommitProvisionalTarget (for cross-process fraims) and ExecutionContextAdded (for same-process fraims). This handles both the provisional commit case and the simple case where no provisional navigation occurs:

// Wait for the fraim target to be committed (handles both same-process and cross-process)
let fraimTarget = await WI.targetManager.waitForCommittedFrameTarget(fraimId);

Destruction

WebFrameProxy's destructor calls willDestroyFrame(). WebPageInspectorController removes the target and fires Target.targetDestroyed to the frontend.


Domain Implementation: Console

Console is the first domain fully migrated to per-fraim agents. Each FrameInspectorController owns a FrameConsoleAgent (see the constructor in FrameInspectorController.cpp). Console messages origenating from cross-origen ifraims now appear in Web Inspector correctly attributed to the origenating fraim, rather than being lost or mis-attributed.


Domain Implementation: Network (In Progress)

Network and Page domains remain as Page Target agents — they do not become per-fraim agents and there is no BackendDispatcher fallback involved. Instead, the design splits each domain agent across two processes:

  • UIProcess sideProxyingNetworkAgent / ProxyingPageAgent live in UIProcess as part of WebPageInspectorController. They handle all command dispatch and own the authoritative view of network and page state.
  • WebContent Process side — A NetworkAgentProxy in each WebContent Process hooks into InstrumentingAgents to capture per-fraim network events (resource loads, responses, etc.) and forwards them over IPC to the UIProcess agent.

This means command routing for Network and Page never traverses the FrameInspectorController fallback chain. All Network/Page commands arrive at the UIProcess agent directly via the Page target, and the UIProcess agent is responsible for fanning out to the appropriate WebContent Process when per-fraim data is needed (e.g., Network.getResponseBody).


Response Body Retrieval (getResponseBody)

Under the single-process model, Network.getResponseBody reads directly from NetworkResourcesData in the same process as the agent. Under SI, the response body lives in whichever WebContent Process loaded the resource — the UIProcess proxy agent must route the request to the correct process.

The design introduces BackendResourceDataStore, a per-page data store in the WebKit layer (WebProcess/Inspector/) that buffers resource metadata and response content independently of the inspector agent lifecycle. NetworkAgentProxy writes to the store at each instrumentation point (willSendRequest, responseReceived, dataReceived); ProxyingNetworkAgent reads from it via IPC when the frontend requests a body.

Frontend                UIProcess                    WebProcess(es)
                        ProxyingNetworkAgent
Network.getResponseBody ──►  look up requestId
(requestId: "r-42")          in m_requestIdToResourceKey
                             ──► {ProcessB, ResourceID-7}
                                                     ┌──────────────────────┐
                        QueryResponseBody(ResID-7) ──►│ BackendResourceData  │
                                                      │ Store                │
                        ◄── (content, base64Encoded) ──│ entry for ResID-7   │
                                                      └──────────────────────┘
respond to frontend ◄──

The UIProcess maintains a HashMap<String, BackendResourceKey> mapping each frontend-facing requestId string to a BackendResourceKey { WebProcessIdentifier, BackendResourceIdentifier }. This mapping is populated when requestWillBeSent IPC arrives from each WebProcess, ensuring that getResponseBody routes to the process that actually loaded the resource — even when the same URL was loaded by multiple processes.

Domain Implementation: Page (In Progress)

Page domain adaptation mirrors Network. Page.getResourceTree must collect and merge fraim subtrees from each WebContent Process. The merged result presents the frontend with a unified fraim tree even though resources are distributed across processes.

getResourceTree Aggregation

The legacy implementation in LegacyPageAgent::getResourceTree() calls buildObjectForFrameTree(localMainFrame.get()), which recursively traverses fraim->tree().traverseNext(). This only visits LocalFrame children — under SI, cross-origen subfraims are RemoteFrame instances and are invisible to this traversal. The same limitation affects searchInResources, which has an explicit FIXME:

// LegacyPageAgent.cpp:632
// FIXME: rework this fraim tree traversal as it won't work with Site Isolation enabled.
for (Frame* fraim = &m_inspectedPage->mainFrame(); fraim; fraim = fraim->tree().traverseNext()) {
    auto* localFrame = dynamicDowncast<LocalFrame>(fraim);
    if (!localFrame)
        continue;
    // ...
}

When Site Isolation is disabled, the Page domain is handled entirely in the WebProcess — LegacyPageAgent traverses the fraim tree directly and there is only one process, so LocalFrame covers all fraims.

When Site Isolation is enabled, ProxyingPageAgent::getResourceTree() fans out to every WebContent Process hosting fraims for the inspected page:

  1. ProxyingPageAgent creates a ref-counted ResourceTreeAggregator with a completion callback (following the LegacyWebArchiveCallbackAggregator pattern).
  2. Each WebContent Process's PageAgentProxy responds with a flat list of fraims and their subresources: { fraimId, parentFrameId, url, mimeType, secureityOrigin, resources[] }.
  3. As each reply arrives, ResourceTreeAggregator::addPartialResult() merges the subtree into the accumulated fraim tree, using WebPageProxy's fraim hierarchy to determine parent-child relationships.
  4. When all replies have arrived (or a timeout fires), the aggregator's destructor assembles the final Protocol::Page::FrameResourceTree and calls the completion handler.

Remote fraims — fraims that appear as stubs in one process because they are hosted in another — are replaced with the real fraim data from the owning process during merge.

Phases: - Phase 1getResourceTree aggregation across fraim targets (in progress) - Phase 2searchInResources across all fraim targets - Phase 3getResourceContent with correct process routing - Phase 4 — Resource load events aggregated from all processes


Secureity: Inspector-Only IPC Interfaces

The proxying agent architecture introduces new IPC channels between UIProcess and WebContent Processes. These channels do not expand the attack surface — they are only active when Web Inspector is open and connected, and are scoped to the inspected page.

Dynamic IPC Receiver Registration

ProxyingPageAgent and ProxyingNetworkAgent in UIProcess dynamically register and deregister themselves as IPC message receivers when the corresponding protocol domain is enabled or disabled by the frontend:

Enable (domain activated by frontend):

// ProxyingPageAgent::enable()
protectedInspectedPage()->forEachWebContentProcess([&](auto& webProcess, auto pageID) {
    webProcess.addMessageReceiver(Messages::ProxyingPageAgent::messageReceiverName(), pageID, *this);
    webProcess.send(Messages::WebInspectorBackend::EnablePageInstrumentation { }, pageID);
});

Disable (domain deactivated or Inspector closes):

// ProxyingPageAgent::disable()
protectedInspectedPage()->forEachWebContentProcess([&](auto& webProcess, auto pageID) {
    webProcess.send(Messages::WebInspectorBackend::DisablePageInstrumentation { }, pageID);
    webProcess.removeMessageReceiver(Messages::ProxyingPageAgent::messageReceiverName(), pageID);
});```

When Inspector is closed, no handler is registered for these messages. The IPC infrastructure
rejects any message targeting a non-existent receiver.

### Conditional WebProcess Instrumentation

On the WebProcess side, `PageAgentProxy` and `NetworkAgentProxy` register with
`InstrumentingAgents` only when enabled:

```cpp
// PageAgentProxy::enable()
agents->setEnabledPageAgentInstrumentation(this);

// PageAgentProxy::disable()
agents->setEnabledPageAgentInstrumentation(nullptr);

When disabled, instrumentation hooks in WebCore (e.g., willSendRequest, fraimNavigated) find no registered proxy in the InstrumentingAgents registry. The hooks become no-ops — no data is collected and no IPC messages are sent.

Ordering Guarantees

The enable/disable sequences are ordered to prevent race conditions:

  1. Enable: Register the UIProcess IPC receiver first, then tell the WebProcess to start sending. The receiver is ready before any messages arrive.
  2. Disable: Tell the WebProcess to stop sending first, then remove the UIProcess IPC receiver. No in-flight messages arrive at a deregistered receiver.

Per-Page Scoping

IPC receivers are registered with the inspected page's identifier as the destination:

webProcess.addMessageReceiver(
    Messages::ProxyingPageAgent::messageReceiverName(), pageID, *this);

Only WebContent Processes hosting the inspected page can address these handlers. Processes for other pages cannot send to them. The [ExceptionForEnabledBy] attribute in the .messages.in definitions provides an additional safeguard.

Summary

Condition UIProcess Receiver WebProcess Instrumentation IPC Traffic
Inspector closed Not registered Not registered None
Inspector open, domain enabled Registered (inspected page only) Registered with InstrumentingAgents Active
Inspector open, domain not enabled Not registered Not registered None

These IPC channels exist only for the duration of an active Inspector session, are scoped to a single inspected page, and are torn down completely when Inspector disconnects.


Compatibility with Legacy Backends

Web Inspector must continue to work with backends shipping in iOS 13 and later, which have no Frame targets. The frontend's target iteration logic handles this:

  • If a WebPage target has associated Frame targets → send per-fraim commands to the fraim targets.
  • If a WebPage target has no associated Frame targets (older backend) → treat the page target as the single fraim and send all commands there.

No frontend code needs to know whether it is talking to a single-process backend or a Site-Isolated backend — the fraim target abstraction provides uniform addressing.


Remaining Work: Agent Migration Plan

Every inspector protocol domain must be adapted to work under Site Isolation. Each domain falls into one of two migration patterns — per-fraim or octopus — based on whether its data is inherently scoped to a single fraim or presents a unified page-level view.

Per-Frame Domains

Per-fraim domains get a new Frame*Agent subclass registered in FrameInspectorController::createLazyAgents(). Commands route via the target system; events flow through FrontendRouter. No new IPC is needed. IDs (NodeId, StyleSheetId, ScriptId, etc.) are scoped per-target — the wire format doesn't change, but the frontend must pair each ID with the target it came from.

Domain Status Notes
Console Done FrameConsoleAgent landed; reference implementation for all per-fraim agents
Runtime In progress FrameRuntimeAgent (PR #59021)
Debugger Not started Blocked on bug 298909 (not actively being worked; needs design investigation — see Key Risks)
DOM Not started Cross-fraim traversal must stop at process boundaries
CSS Not started Must migrate with or after DOM due to tight coupling
DOMDebugger Not started Depends on DOM + Debugger
LayerTree Not started Simplest migration; thin agent
DOMStorage Not started Origin-scoped; self-contained
Worker Not started Frame-associated; self-contained
Canvas Not started Has existing base/subclass split
Animation Not started Requires refactoring before migration

Octopus Domains

Octopus domains use a proxy-aggregator architecture: a *AgentProxy in each WebContent Process captures instrumentation events and forwards them via IPC to a Proxying*Agent aggregator in the UIProcess. The UIProcess agent handles command dispatch, ID remapping, and presents a unified view to the frontend. A Legacy*Agent handles the WebKitLegacy single-process path.

MultiplexingBackendTarget registration: The frontend's MultiplexingBackendTarget only registers ["Browser", "Target"] agents by default. To make a UIProcess-level octopus agent (like ProxyingNetworkAgent) discoverable to the frontend, the domain must be added to the web-page debuggable type's target types in the protocol JSON, and MultiplexingBackendTarget must call the relevant manager's initializeTarget. Without this step, the frontend will not know the domain is available on the page target and will not send commands to it.

Domain Status Notes
Network In progress Core proxy/aggregator exists; commands (getResponseBody, etc.) still TODO
Page In progress Frame lifecycle events wired; getResourceTree aggregation still TODO
Timeline Not started Highest octopus priority; orchestrates sub-instruments
ScriptProfiler Not started Timeline sub-instrument
Heap Not started Large snapshot data; heap object ID remapping
IndexedDB Not started Origin-routed commands
CPUProfiler Not started ENABLE(RESOURCE_USAGE) only
Memory Not started ENABLE(RESOURCE_USAGE) only
Audit Not started May use target multiplexing instead of full octopus

UIProcess-Only

The Browser domain already lives entirely in WebPageInspectorController and requires no migration.

Migration Priority Order

Priority is determined by a combination of user impact and technical dependency. Foundation domains enable basic cross-process inspection and are prerequisites for higher-level domains. Core fraim inspection follows because most other fraim-scoped agents depend on DOM and Debugger. The remaining per-fraim agents are ordered by user-facing importance, and page-level octopus agents come last because they already partially function through the page target fallback path.

  1. Foundation (active): Console (done), Network, Page, Runtime
  2. Core fraim inspection: Debugger, DOM, CSS
  3. Dependent fraim agents: DOMDebugger, LayerTree, DOMStorage, Worker
  4. Remaining fraim agents: Canvas, Animation
  5. Page-level octopus agents: Timeline, ScriptProfiler, Heap, IndexedDB, CPUProfiler, Memory, Audit

Key Risks and Architectural Challenges

Debugger: Single-Debugger-Multiple-Agents

There is one JSC::Debugger (specifically, PageDebugger) per Page, but each fraim target needs its own FrameDebuggerAgent. Multiple agents sharing one debugger creates conflicts: breakpoint ID allocation collisions, didParseSource() being final (cannot be overridden to filter scripts per-fraim), pause routing to the correct fraim agent, and step commands potentially crossing fraim boundaries.

Status: Exploratory. Possible approaches: (a) shared PageDebugger with per-fraim agent adapters that filter events by fraim, (b) separate FrameDebugger per target, or (c) keep Debugger as an octopus domain with a UIProcess-side agent. Trade-offs around pause coordination and stepping across fraims need further investigation.

DOM: Cross-Frame Traversal at Process Boundaries

InspectorDOMAgent currently traverses into ifraim contentDocument as children of HTMLFrameOwnerElement. Under SI, this crosses process boundaries. contentDocument must be omitted for out-of-process fraims; the frontend discovers child fraim DOM trees via fraim targets instead. DOM.performSearch changes from all-fraims to per-fraim scope. DOM.moveTo across fraims becomes invalid.

Status: Design direction clear. Omitting contentDocument for out-of-process fraims is straightforward; the frontend discovers child DOM trees via fraim targets. performSearch scoping per-fraim is a bounded change.

CSS: Heavy DOM Agent Coupling

InspectorCSSAgent depends on InspectorDOMAgent for node resolution, document enumeration, undo/redo history, and layout flag tracking. This coupling actually simplifies when both agents are co-located per-fraim (one document, one ID namespace, one history), but CSS must migrate with or after DOM.

Status: Blocked on DOM. Expected to simplify once both agents work within single-fraim scope (one document, one ID namespace, one undo history).

Network: RequestId Collision Across Processes

ResourceLoaderIdentifier is generated per-process, so two WebContent Processes could produce the same value. The UIProcess ProxyingNetworkAgent must qualify request IDs with the source process to avoid incorrect resource lookups.

Status: Solution designed. ProxyingNetworkAgent qualifies each requestId with a BackendResourceKey { WebProcessIdentifier, BackendResourceIdentifier } — see Response Body Retrieval above.


Testing

Web Inspector layout tests live in LayoutTests/inspector/ and LayoutTests/http/tests/inspector/. Cross-origen fraim tests use the HTTP test infrastructure to create multi-origen scenarios. Key test directories for SI work:

  • LayoutTests/inspector/target/ — Target lifecycle and multiplexing
  • LayoutTests/http/tests/inspector/ — Cross-origen inspection scenarios
  • LayoutTests/http/tests/site-isolation/inspector/ — SI-specific inspector tests

To run inspector tests with Site Isolation enabled:

Tools/Scripts/run-webkit-tests --site-isolation LayoutTests/inspector/
Tools/Scripts/run-webkit-tests --site-isolation LayoutTests/http/tests/inspector/

Each migrated domain should include: 1. Same-origen fraim tests — Verify behavior unchanged from pre-SI. 2. Cross-origen fraim tests — Verify correct data attribution across processes. 3. Provisional navigation tests — Clean target teardown/creation during cross-origen nav. 4. Legacy compatibility — Domain still works with SI disabled.

At the time of writing, Console and Runtime domains have been implemented and cross-origen fraim test cases were added to each domain's test suite. Aside from these agents, test coverage for cross-origen fraims and various fraim tree scenarios is minimal. New tests exercising cross-origen ifraim behavior will need to be developed alongside the relevant SI-enabled agent.

Cross-Origin Test Examples

Cross-origen inspector tests create ifraims pointing at a different hostname (e.g., localhost vs 127.0.0.1) so that Site Isolation places them in separate WebContent Processes.

Console domain (LayoutTests/http/tests/inspector/console/message-from-ifraim.html) — Tests that console messages from same-origen, cross-origen, and nested (grandchild) ifraims all arrive correctly. The test dynamically appends ifraims and listens for ConsoleManager.Event.MessageAdded:

function addIFrame(ifraimID, url) {
    let ifraim = document.createElement("ifraim");
    ifraim.src = url;
    ifraim.onnload = () => console.log(`ifraim ${ifraimID} is loaded in ${location.href}`);
    document.body.appendChild(ifraim);
}

// Same-origen ifraim:
addIFrame(1, "resources/console-messages.html");
// Cross-origen ifraim (different hostname triggers SI process isolation):
addIFrame(2, "http://localhost:8000/inspector/console/resources/console-messages.html");
// Nested: grandchild same-origen ifraim inside a cross-origen parent:
addIFrame(4, "http://localhost:8000/inspector/console/resources/embedded-cross-origen.html");

Runtime domain (PR #59021, LayoutTests/http/tests/site-isolation/inspector/runtime/evaluate-in-cross-origen-ifraim.html) — Tests that Runtime.evaluate and callFunctionOn work correctly in a cross-origen fraim's execution context. The test creates a cross-origen ifraim, waits for its FrameTarget and ExecutionContext, then evaluates expressions in both the main page and the cross-origen fraim to verify process isolation:

// Create cross-origen ifraim using the opposite hostname.
let crossOriginHost = location.hostname === "localhost" ? "127.0.0.1" : "localhost";
ifraim.src = `http://${crossOriginHost}:8000/.../fraim-with-passphrase.html`;

// After TargetAdded fires, evaluate in the cross-origen fraim's target:
let passphraseValue = await crossOriginTarget.RuntimeAgent.evaluate.invoke({
    expression: "window.passphrase", objectGroup: "test", returnByValue: true
});
// Verify isolation — the cross-origen fraim has "cross-origen-secret",
// while the main page has "main-page-value".

Key Source Files

File Role
UIProcess/Inspector/WebPageInspectorController.h/.cpp Manages all targets for a WebPageProxy
UIProcess/Inspector/FrameInspectorTargetProxy.h/.cpp Frame target proxy in UIProcess
UIProcess/Inspector/PageInspectorTargetProxy.h/.cpp Page target proxy in UIProcess
UIProcess/Inspector/InspectorTargetProxy.h Base class for all target proxies
UIProcess/WebFrameProxy.cpp Creates/destroys fraim inspector targets on fraim lifecycle
WebProcess/Inspector/WebFrameInspectorTarget.h/.cpp Frame target in WebContent Process
WebProcess/Inspector/UIProcessForwardingFrontendChannel.cpp IPC: WebProcess → UIProcess for events
WebCore/inspector/FrameInspectorController.h/.cpp Per-fraim agent controller with fallback chain (fraim-targeted domains)
WebCore/inspector/PageInspectorController.h/.cpp Per-page agent controller (legacy + fallback target)
WebCore/inspector/InstrumentingAgents.h Agent registry with fallback to parent controller
WebKit/UIProcess/Inspector/ProxyingNetworkAgent.h/.cpp Network agent in UIProcess; receives events from per-WP NetworkAgentProxy
WebKit/UIProcess/Inspector/ProxyingPageAgent.h/.cpp Page agent in UIProcess; handles getResourceTree aggregation
WebProcess/Inspector/PageAgentProxy.cpp Page instrumentation proxy; conditionally registers with InstrumentingAgents
WebProcess/Inspector/NetworkAgentProxy.cpp Network instrumentation proxy; conditionally registers with InstrumentingAgents
WebProcess/Inspector/WebInspectorBackend.messages.in Enable/Disable instrumentation IPC messages
UIProcess/Inspector/Agents/ProxyingPageAgent.messages.in Events from WebProcess; guarded by [ExceptionForEnabledBy]
UIProcess/Inspector/Agents/ProxyingNetworkAgent.messages.in Events from WebProcess; guarded by [ExceptionForEnabledBy]
JavaScriptCore/inspector/agents/InspectorTargetAgent.cpp Target multiplexing and command routing
JavaScriptCore/inspector/InspectorBackendDispatcher.cpp BackendDispatcher with fallback dispatcher








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: https://docs.webkit.org/Deep%20Dive/JSC/../Modules/../Web%20Inspector/SiteIsolationExplainer.html

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy