From 4842b9a64182a0ccea2a8412592c7b93ea603cd3 Mon Sep 17 00:00:00 2001 From: BJ Burg Date: Tue, 9 Dec 2025 16:24:10 -0800 Subject: [PATCH 1/2] Web Inspector Site Isolation Explainer Second editing session adds more details about Target system. --- .../Web Inspector/SiteIsolationExplainer.md | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 docs/Deep Dive/Web Inspector/SiteIsolationExplainer.md diff --git a/docs/Deep Dive/Web Inspector/SiteIsolationExplainer.md b/docs/Deep Dive/Web Inspector/SiteIsolationExplainer.md new file mode 100644 index 00000000..ec1ddd43 --- /dev/null +++ b/docs/Deep Dive/Web Inspector/SiteIsolationExplainer.md @@ -0,0 +1,158 @@ +# Site Isolation Plans + +_Last updated Dec 9, 2025._ + +This document aims to explain the implications of Site Isolation on the architecture +of Web Inspector. + +## Terminology + +`InspectorBackend`: represents a place that command requests can be dispatched or where instrumentation events are emitted. + +## Background: Evolution of WebKit Process Model + +20 years ago, Web Inspector was introduced in a world where all of WebKit ran in a single process, including +the `WebInspectorUI` user interface. In the single process model, Web Inspector's frontend sends text JSON-RPC commands +over a channel to a single `InspectorBackend` associated with a `WebCore::Page`, which then parses and dispatches commands to various +group command handlers called _agents_ using `InspectorBackendDispatcher`. + +WebKit2 introduced the multi-process model where multiple Web Content Processes are coordinated by a central UIProcess. +Initially this had no effect on the design of targets in Web Inspector, because frontend messages are parsed and dispatched in +one WebProcess at the WebCore level, with WebInspectorUI connected to WebProcesses directly without UIProcess involvement. + +A series of half-measures towards Site Isolation started to complicate this picture of one-frontend and one-backend. +Process Swap On Navigation (PSON) was one such effort with a simple premise: when a Page navigates to a new origin, +we should perform the load in a new WebProcess so that a potentially compromised WebProcess won't be reused across the +different-origin security boundary. For Web Inspector, this meant that the there is still one-frontend and one-backend, but +the backend instance may change as the inspected page navigates. Without further changes, the debugger (WebInspectorUI) would automatically +close its connection to InspectorBackend upon such navigations if a new WebProcess is used. + +## Background: Inspector Target system + +To enable WebInspectorUI debugging session to persist across web process swaps, the concept of Targets was introduced with +three design goals: + +1. provide an opaque handle that WebKit can use to route incoming JSON-RPC messages to the correct InspectorBackend (WebPage, Worker, and now Frame). +1. allow reuse of command interfaces across multiple types of execution contexts (e.g., `PageRuntimeAgent`, +`WorkerRuntimeAgent`, `JSGlobalObjectRuntimeAgent` all implement the `RuntimeBackendDispatcherHandler` interface created from `Runtime.json`.) +1. allow the frontend to reason about the capabilities of each InspectorBackend (e.g., send commands to all "capable" targets) + +A `Target` represents an execution context, within the overall `WebPage` debuggable, to which JSON-RPC commands can be sent. During a single debugging session, +WebInspectorUI will inspect multiple Page instances which may be hosted in different Web Processes. Each Page/WebPage/WebPageProxy for the same +`WebPageDebuggable` appears as a `PageTarget`. Therefore, WebInspectorUI is able to switch between `PageTarget` instances when a main frame load is committed, while still handling events from both targets as the old page unloads and the new page loads. + +Targets also solve the problem of getting a message dispatched to the correct `BackendDispatcher` and corresponding agents. With the introduction of Page targets, the connection to the debugger was moved to UIProcess, but most commands are parsed and handled by agents in WebProcess +after initial JSON parsing in UIProcess. The target routing system is implemented in the backend as the `Target` domain in `InspectorTargetAgent` +and instantiated as part of `WebPageInspectorController`. Targets as exposed by TargetAgent have a simplified lifecycle compared to WebPages and Workers, +but still contain essential concepts such as pausing/resuming, provisional/committed state, and target creation/destruction events. + +- Page target: represents a legacy "direct" backend target which does not support multiple Targets. This is for backwards compatibility with old (pre-PSON and WebKitLegacy) backends. +- WebPage target: represents a WebPageProxy or WKWebView. Associated with multiple transient worker and frame targets. For backwards compatibility with post-PSON backends which lack Frame targets, WebInspectorUI can also use PageTarget as the main Frame target when iterating execution contexts. +- Worker target: represents a dedicated Web Worker target. Associated with the parent Page target that spawned the worker. + +ServiceWorker, ITML, and JavaScript (i.e., `JSContext`) targets are standalone debuggables and not exposed as sub-targets of WebPage target within debuggable. +The fact that they have target types is for convenience in restricting which domains are supposed to be exposed. + +FIXME: describe proxying to workers, before and after + + +## What are the key issues motivating design changes? + +Under Site Isolation, + +- Web Inspector backend can no longer assume that all frames are in the same process, or that a Page is in the same process as its Frame. +- One backend in one Web Process only has partial resource data. +- WebInspectorUI needs to connect to multiple web processes to receive events and fetch resource data. + +Concurrently to Site Isolation, WebKit is also gaining support for WebDriver BiDi. The implementation for WebDriver BiDi is +automation-focused, but ultimately it is just another JSON-RPC debug protocol that we have to support in WebKit. One of the main +differences is that WebDriver is UIProcess-based, and it includes the ability to target commands to specific `browsing contexts`. +This is basically the same thing as targeting a command to a specific frame. Similarly, FrameTargetProxy in UIProcess routes its message to the +correct `InspectorBackend` using `sendMessageToProcessContainingFrame()`. + +The rest of this document motivates a modified architectural design based on Frame targets. + +## What are the main changes are being proposed? + +- [X] Add new `Frame` target type which represents a place that commands can be targeted at. A Page target has one or more Frame targets. +- [X] Introduce `FrameInspectorController` which owns per-frame agents in WebProcess. +- [X] Deprecate the 'Page' debuggable type (i.e. remove direct backend support). +- [X] Transition WebKitLegacy to use in-process proxying backend. +- [/] Move per-page agents to UIProcess; use 'octopus' design for instrumentation forwarding from WebProcess `InspectorInstrumentation`. +- [/] Keep per-execution-context agents in WebProcess; dispatch happens from `FrameInspectorController`. +- [ ] Update resource/frame identifier scheme to work with Site Isolation (currently uses a process-local static uint64...) +- [ ] Fix uses of `WI.assumingMainTarget()` in WebInspetorUI + +FIXME: add diagrams for DirectTargetBackend, ProxyingTargetBackend with Pages, and ProxyingTargetBackend with Frames. +FIXME: need illustration of octopus hybrid page/network agent and more details + +--- + +BJB> Everything below this has not yet been rewritten. + +## Why do we need Frame target? + +- Each frame has its own execution context (Document), but it may or may not co-locate in the same process as other frames. +- Helps the backend to route agent commands to the appropriate Webcontent process. +- Frame targets in the frontend abstracts away which actual process is hosting the frame. +- Reuse existing target routing mechanism for Frame-targeted commands. +- Backend needs to know for each frame which process that data resides in. + +## Can't we use more than one Page target for OOPIF? + +- This violates the expectation that WebInspectorUI should not be overly familiar with + the process model of WebKit and specifically the policy choices for Site Isolation. +- Frontend code designed to iterate execution contexts (frames), not processes. +- Reasoning about multiple Page targets, which may or may not be a WKWebView, is error-prone. + +## What's the difference between multiple Frame targets vs multiple Page targets? + +- WebKitLegacy Frame Target adapter (already done) +- Moving Network and Page agent to UIProcess (this might be required anyway to provide global page identifiers) +- Add IPC support for Page and Network events and commands + +## What about WebKitLegacy? + +- Unable to stop supporting it any time soon, still has 1st party clients that we can't break. +- Easier to add frame target adapter for single-process mode, + than to continue supporting direct backend target implementations + (i.e., have IPC (new) and no-IPC (existing) versions of Page and NetworkAgent) +- Qianlang added this adapter as part of the Console work, it uses different frame lifecycle instrumentation. + +## What about backwards compatibility with shipping Page targets? + +- Frontend needs to continue to support shipping Page, Worker, and ServiceWorker targets w/o any frame targets. +- For Page targets without Frame targets, the frontend assumes it's older and send commands to the Page target. +- Target iteration assumes Page w/ 1+ Frame target -> send frame messages to frames, otherwise send to Page target. + +Example: + +For Page.getResourceTree, in shipping Web Inspector, all frame tree nodes are included in one payload. + +With Site Isolation, not all resources will be in the same process, so each payload will contain a +subset of frame tree nodes and their resources. Whether or not we use Frame or Page targets, we still need +to rework the frontend to merge results from multiple targets. Since Frames are the unit of process isolation, +it's easier to think about the correct behavior of Inspector backend commands in terms of frames. + +The alternative would be to send commands to every sub-page of a page, but this is difficult to reason about because +the policy for which frames should go into which processes is variable across platforms and releases. + + +What is the plan for each domain? + +- Domains to transition to per-frame agents: + Debugger, Runtime, CSS, DOM, Audit, Animation, DOM, DOMStorage, DOMDebugger, Canvas +- Domains to be moved to UIProcess: + Network, Page +- Domains to stay as-is: + Target, Browser, Worker, ServiceWorker +- Domains TBD: + LayerTree, Heap, Memory, Storage, IndexedDB +- Domains removed: + Database, AppCache + + +## Other Deprecations + +- AugmentableInspectorController +- ITML debuggable type From dd1b912445384067bf6a6f08e80604e5bf4e3a64 Mon Sep 17 00:00:00 2001 From: BJ Burg Date: Wed, 25 Feb 2026 15:27:13 -0800 Subject: [PATCH 2/2] Web Inspector Site Isolation: rewrite explainer with accurate architecture Rewrites SiteIsolationExplainer.md with a complete, accurate description of the Web Inspector SI design: frame target lifecycle, BackendDispatcher fallback chain, the split UIProcess/WebProcess design for Network and Page domains (ProxyingNetworkAgent + NetworkAgentProxy), and open questions. Also adds a Web Inspector cross-reference section to SiteIsolation.md. --- docs/Deep Dive/SiteIsolation.md | 9 + .../Web Inspector/SiteIsolationExplainer.md | 397 +++++++++++++----- 2 files changed, 296 insertions(+), 110 deletions(-) diff --git a/docs/Deep Dive/SiteIsolation.md b/docs/Deep Dive/SiteIsolation.md index 846e3bf8..cddb062c 100644 --- a/docs/Deep Dive/SiteIsolation.md +++ b/docs/Deep Dive/SiteIsolation.md @@ -67,5 +67,14 @@ Site isolation was originally planned as a simple 3 step project: 3. Fix the performance regression from step 2 As of January 2025 are currently on step 2 and looking forward to step 3. In order to get there, we need to fix all subtasks of rdar://99665363 which is organized from a code-centric perspective. QA has also been helping find things to fix from a user-centric perspective, and they are subtasks of rdar://138794978. Those that don't have access to radar can reach out on the WebKit Slack. +## Web Inspector + +Web Inspector's architecture is also affected by site isolation. With content split across +multiple WebContent Processes, the inspector must observe and aggregate protocol data from each +process. The approach uses the existing inspector target multiplexing mechanism +(`InspectorTargetAgent`) to create per-frame inspector targets that each connect to a +`FrameInspectorController` in their respective WebContent Process. For a detailed explanation of +this architecture, see [Web Inspector and Site Isolation](Web%20Inspector/SiteIsolationExplainer.md). + ## Strategies for fixing bugs Most of the functionality bugs remaining can be described as “we used to follow a pointer to another frame and now we can’t.” A handful of strategies continue to be quite effective. The first is maybe we can refactor the code to send a message to the frame via IPC instead of calling a function and operating on the frame directly when we need to do something. The second is maybe see if we can proactively send state to all processes so when they need to do something they already have the necessary information. This should only be done with information that is not sensitive because it creates a side channel for speculative execution attacks to read information other sites should not have access to. The third is maybe we can do something in a privileged process such as the UI process or the GPU process that doesn’t have web content in it but can communicate and know state from multiple sites. And the fourth is maybe it is correct to do nothing if a frame is in another process. This last option is rare, but sometimes if access is gated on an origin check it is correct to skip a frame in another process. diff --git a/docs/Deep Dive/Web Inspector/SiteIsolationExplainer.md b/docs/Deep Dive/Web Inspector/SiteIsolationExplainer.md index ec1ddd43..f0a612ee 100644 --- a/docs/Deep Dive/Web Inspector/SiteIsolationExplainer.md +++ b/docs/Deep Dive/Web Inspector/SiteIsolationExplainer.md @@ -1,158 +1,335 @@ -# Site Isolation Plans +# Web Inspector and Site Isolation -_Last updated Dec 9, 2025._ +_Last updated 2026-02-25._ -This document aims to explain the implications of Site Isolation on the architecture -of Web Inspector. +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](../SiteIsolation.md). -## Terminology +--- + +## 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 frames share one process — but breaks down under Site +Isolation, where a `WebPageProxy` may have its frames 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, 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: + +- **`Page`** — legacy direct-backend target (pre-PSON and WebKitLegacy). No sub-targets. +- **`WebPage`** — represents a `WebPageProxy`. May have transient worker and frame 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 (or WebKitLegacy) -`InspectorBackend`: represents a place that command requests can be dispatched or where instrumentation events are emitted. +When Site Isolation is off, the architecture is essentially unchanged from the pre-SI model: -## Background: Evolution of WebKit Process Model +- One `WebPageInspectorTargetProxy` (type `WebPage`) is created for the `WebPageProxy`. +- All agents live in one `PageInspectorController` in one WebContent Process. +- `didCreateFrame` on `WebPageInspectorController` is a no-op — no frame targets are created. +- Commands are routed through the page target to `PageInspectorController`. -20 years ago, Web Inspector was introduced in a world where all of WebKit ran in a single process, including -the `WebInspectorUI` user interface. In the single process model, Web Inspector's frontend sends text JSON-RPC commands -over a channel to a single `InspectorBackend` associated with a `WebCore::Page`, which then parses and dispatches commands to various -group command handlers called _agents_ using `InspectorBackendDispatcher`. +### Mode 2: SI-enabled -WebKit2 introduced the multi-process model where multiple Web Content Processes are coordinated by a central UIProcess. -Initially this had no effect on the design of targets in Web Inspector, because frontend messages are parsed and dispatched in -one WebProcess at the WebCore level, with WebInspectorUI connected to WebProcesses directly without UIProcess involvement. +When Site Isolation is enabled, each `WebFrameProxy` gets its own inspector target: -A series of half-measures towards Site Isolation started to complicate this picture of one-frontend and one-backend. -Process Swap On Navigation (PSON) was one such effort with a simple premise: when a Page navigates to a new origin, -we should perform the load in a new WebProcess so that a potentially compromised WebProcess won't be reused across the -different-origin security boundary. For Web Inspector, this meant that the there is still one-frontend and one-backend, but -the backend instance may change as the inspected page navigates. Without further changes, the debugger (WebInspectorUI) would automatically -close its connection to InspectorBackend upon such navigations if a new WebProcess is used. +- One `WebPageInspectorTargetProxy` still exists for page-level agents. +- Each `WebFrameProxy` creation triggers a `WebFrameInspectorTargetProxy` (type `Frame`). +- Each frame target connects to a `FrameInspectorController` in the owning WebContent Process. +- Commands targeted at a frame ID are routed to the correct `WebFrameInspectorTargetProxy`, + which sends them over IPC to the `FrameInspectorController` in that process. -## Background: Inspector Target system +The key callsite is in `WebFrameProxy`'s constructor +(`UIProcess/WebFrameProxy.cpp`): -To enable WebInspectorUI debugging session to persist across web process swaps, the concept of Targets was introduced with -three design goals: +```cpp +page.inspectorController().createWebFrameInspectorTarget( + *this, WebFrameInspectorTarget::toTargetID(frameID)); +``` -1. provide an opaque handle that WebKit can use to route incoming JSON-RPC messages to the correct InspectorBackend (WebPage, Worker, and now Frame). -1. allow reuse of command interfaces across multiple types of execution contexts (e.g., `PageRuntimeAgent`, -`WorkerRuntimeAgent`, `JSGlobalObjectRuntimeAgent` all implement the `RuntimeBackendDispatcherHandler` interface created from `Runtime.json`.) -1. allow the frontend to reason about the capabilities of each InspectorBackend (e.g., send commands to all "capable" targets) +And in the destructor, the target is torn down symmetrically: -A `Target` represents an execution context, within the overall `WebPage` debuggable, to which JSON-RPC commands can be sent. During a single debugging session, -WebInspectorUI will inspect multiple Page instances which may be hosted in different Web Processes. Each Page/WebPage/WebPageProxy for the same -`WebPageDebuggable` appears as a `PageTarget`. Therefore, WebInspectorUI is able to switch between `PageTarget` instances when a main frame load is committed, while still handling events from both targets as the old page unloads and the new page loads. +```cpp +page->inspectorController().destroyInspectorTarget( + WebFrameInspectorTarget::toTargetID(frameID())); +``` -Targets also solve the problem of getting a message dispatched to the correct `BackendDispatcher` and corresponding agents. With the introduction of Page targets, the connection to the debugger was moved to UIProcess, but most commands are parsed and handled by agents in WebProcess -after initial JSON parsing in UIProcess. The target routing system is implemented in the backend as the `Target` domain in `InspectorTargetAgent` -and instantiated as part of `WebPageInspectorController`. Targets as exposed by TargetAgent have a simplified lifecycle compared to WebPages and Workers, -but still contain essential concepts such as pausing/resuming, provisional/committed state, and target creation/destruction events. +This means frame targets are always present when frames exist, regardless of whether a +frontend is connected — consistent with how page and worker targets behave. -- Page target: represents a legacy "direct" backend target which does not support multiple Targets. This is for backwards compatibility with old (pre-PSON and WebKitLegacy) backends. -- WebPage target: represents a WebPageProxy or WKWebView. Associated with multiple transient worker and frame targets. For backwards compatibility with post-PSON backends which lack Frame targets, WebInspectorUI can also use PageTarget as the main Frame target when iterating execution contexts. -- Worker target: represents a dedicated Web Worker target. Associated with the parent Page target that spawned the worker. +--- + +## Architecture: Target-Based Multiplexing + +``` +UIProcess +┌─────────────────────────────────────────────────────────┐ +│ WebPageInspectorController │ +│ ├── WebPageInspectorTargetProxy (type: WebPage) │ +│ │ └── PageInspectorController (in WCP-A) │ +│ ├── WebFrameInspectorTargetProxy frame-1 (main) │ +│ │ └── FrameInspectorController (in WCP-A) │ +│ └── WebFrameInspectorTargetProxy frame-2 (cross-origin) +│ └── FrameInspectorController (in WCP-B) │ +└─────────────────────────────────────────────────────────┘ + IPC ↕ IPC ↕ + WebContent Process A WebContent Process B + PageInspectorController (no PageInspectorController) + 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 frame targets, `WebFrameInspectorTargetProxy::sendMessageToTarget()` sends the message over +IPC to `WebFrameInspectorTarget` in the WebContent Process, which calls +`FrameInspectorController::dispatchMessageFromFrontend()`. + +--- + +## The BackendDispatcher Fallback Chain + +`FrameInspectorController` owns agents for a single frame. Not every domain has been moved to +per-frame agents yet — only `Console` is fully per-frame 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 frame-level one (`FrameInspectorController.cpp`): + +```cpp +FrameInspectorController::FrameInspectorController( + LocalFrame& frame, 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 frame-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 frame's `InstrumentingAgents` holds a +pointer to the parent page's `InstrumentingAgents`. When instrumentation fires in the frame +process (e.g., a network event), it first notifies frame-level agents and then falls through to +page-level agents for any domain not yet migrated. + +``` +Command from frontend + │ + ▼ +FrameInspectorController.backendDispatcher + │ + │ domain registered at frame level? + ├── yes ──► frame-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 frame is established (both same-process +and cross-process frames). Its constructor calls `createWebFrameInspectorTarget()`, which calls +`addTarget()` in `WebPageInspectorController`. If a frontend is connected, this fires +`Target.targetCreated` to notify the frontend immediately. -ServiceWorker, ITML, and JavaScript (i.e., `JSContext`) targets are standalone debuggables and not exposed as sub-targets of WebPage target within debuggable. -The fact that they have target types is for convenience in restricting which domains are supposed to be exposed. +### Connection (WebProcess side) -FIXME: describe proxying to workers, before and after +When a frontend connects and enumerates targets, `WebFrameInspectorTargetProxy::connect()` +sends an IPC message to the WebContent Process hosting the frame. On the WebProcess side, +`WebFrameInspectorTarget::connect()` (`WebProcess/Inspector/WebFrameInspectorTarget.cpp`) +creates a `WebFrameInspectorTargetFrontendChannel` and connects it to `FrameInspectorController`: +```cpp +void WebFrameInspectorTarget::connect( + Inspector::FrontendChannel::ConnectionType connectionType) +{ + if (m_channel) + return; -## What are the key issues motivating design changes? + Ref frame = m_frame.get(); + m_channel = makeUnique( + frame, identifier(), connectionType); -Under Site Isolation, + if (RefPtr coreFrame = frame->coreLocalFrame()) + coreFrame->protectedInspectorController()->connectFrontend(*m_channel); +} +``` -- Web Inspector backend can no longer assume that all frames are in the same process, or that a Page is in the same process as its Frame. -- One backend in one Web Process only has partial resource data. -- WebInspectorUI needs to connect to multiple web processes to receive events and fetch resource data. +### Events flowing back to UIProcess -Concurrently to Site Isolation, WebKit is also gaining support for WebDriver BiDi. The implementation for WebDriver BiDi is -automation-focused, but ultimately it is just another JSON-RPC debug protocol that we have to support in WebKit. One of the main -differences is that WebDriver is UIProcess-based, and it includes the ability to target commands to specific `browsing contexts`. -This is basically the same thing as targeting a command to a specific frame. Similarly, FrameTargetProxy in UIProcess routes its message to the -correct `InspectorBackend` using `sendMessageToProcessContainingFrame()`. +When a frame-level agent emits an event (e.g., `Console.messageAdded`), +`WebFrameInspectorTargetFrontendChannel::sendMessageToFrontend()` sends it over IPC to UIProcess +(`WebProcess/Inspector/WebFrameInspectorTargetFrontendChannel.cpp`): -The rest of this document motivates a modified architectural design based on Frame targets. +```cpp +void WebFrameInspectorTargetFrontendChannel::sendMessageToFrontend( + const String& message) +{ + if (RefPtr page = protectedFrame()->page()) + page->send(Messages::WebPageProxy::SendMessageToInspectorFrontend( + m_targetId, message)); +} +``` -## What are the main changes are being proposed? +UIProcess receives it in `WebPageInspectorController::sendMessageToInspectorFrontend()`, which +calls `InspectorTargetAgent::sendMessageFromTargetToFrontend()` to deliver the event — tagged +with the frame's `targetId` — to the frontend. -- [X] Add new `Frame` target type which represents a place that commands can be targeted at. A Page target has one or more Frame targets. -- [X] Introduce `FrameInspectorController` which owns per-frame agents in WebProcess. -- [X] Deprecate the 'Page' debuggable type (i.e. remove direct backend support). -- [X] Transition WebKitLegacy to use in-process proxying backend. -- [/] Move per-page agents to UIProcess; use 'octopus' design for instrumentation forwarding from WebProcess `InspectorInstrumentation`. -- [/] Keep per-execution-context agents in WebProcess; dispatch happens from `FrameInspectorController`. -- [ ] Update resource/frame identifier scheme to work with Site Isolation (currently uses a process-local static uint64...) -- [ ] Fix uses of `WI.assumingMainTarget()` in WebInspetorUI +### Provisional Frames -FIXME: add diagrams for DirectTargetBackend, ProxyingTargetBackend with Pages, and ProxyingTargetBackend with Frames. -FIXME: need illustration of octopus hybrid page/network agent and more details +During provisional navigation, a frame may briefly exist in two processes simultaneously (see +[Provisional Navigation](../SiteIsolation.md#provisional-navigation)). The inspector mirrors this: +`WebFrameProxy` is created for the provisional frame in the same constructor path, so it gets an +inspector target immediately. If the provisional load commits, the old frame target is destroyed +and the new one persists. If the load fails, the provisional frame target is destroyed with no +observable change to the frontend. + +### Destruction + +`WebFrameProxy`'s destructor calls `destroyInspectorTarget()`. `WebPageInspectorController` +removes the target and fires `Target.targetDestroyed` to the frontend. --- -BJB> Everything below this has not yet been rewritten. +## Domain Implementation: Console -## Why do we need Frame target? +`Console` is the first domain fully migrated to per-frame agents. Each `FrameInspectorController` +owns a `FrameConsoleAgent` (see the constructor in `FrameInspectorController.cpp`). Console +messages originating from cross-origin iframes now appear in Web Inspector correctly attributed +to the originating frame, rather than being lost or mis-attributed. -- Each frame has its own execution context (Document), but it may or may not co-locate in the same process as other frames. -- Helps the backend to route agent commands to the appropriate Webcontent process. -- Frame targets in the frontend abstracts away which actual process is hosting the frame. -- Reuse existing target routing mechanism for Frame-targeted commands. -- Backend needs to know for each frame which process that data resides in. +--- -## Can't we use more than one Page target for OOPIF? +## Domain Implementation: Network (In Progress) -- This violates the expectation that WebInspectorUI should not be overly familiar with - the process model of WebKit and specifically the policy choices for Site Isolation. -- Frontend code designed to iterate execution contexts (frames), not processes. -- Reasoning about multiple Page targets, which may or may not be a WKWebView, is error-prone. +Network and Page domains remain as **Page Target agents** — they do not become per-frame agents +and there is no `BackendDispatcher` fallback involved. Instead, the design splits each domain +agent across two processes: -## What's the difference between multiple Frame targets vs multiple Page targets? +- **UIProcess side** — `ProxyingNetworkAgent` / `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-frame network events (resource loads, responses, etc.) + and forwards them over IPC to the UIProcess agent. -- WebKitLegacy Frame Target adapter (already done) -- Moving Network and Page agent to UIProcess (this might be required anyway to provide global page identifiers) -- Add IPC support for Page and Network events and commands +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-frame data is needed (e.g., `Network.getResponseBody`). -## What about WebKitLegacy? +--- -- Unable to stop supporting it any time soon, still has 1st party clients that we can't break. -- Easier to add frame target adapter for single-process mode, - than to continue supporting direct backend target implementations - (i.e., have IPC (new) and no-IPC (existing) versions of Page and NetworkAgent) -- Qianlang added this adapter as part of the Console work, it uses different frame lifecycle instrumentation. +## Domain Implementation: Page (In Progress) -## What about backwards compatibility with shipping Page targets? +`Page` domain adaptation mirrors Network. `Page.getResourceTree` must collect and merge frame +subtrees from each WebContent Process. The merged result presents the frontend with a unified +frame tree even though resources are distributed across processes. -- Frontend needs to continue to support shipping Page, Worker, and ServiceWorker targets w/o any frame targets. -- For Page targets without Frame targets, the frontend assumes it's older and send commands to the Page target. -- Target iteration assumes Page w/ 1+ Frame target -> send frame messages to frames, otherwise send to Page target. +Phases: +- **Phase 1** — `getResourceTree` aggregation across frame targets (in progress) +- **Phase 2** — `searchInResources` across all frame targets +- **Phase 3** — `getResourceContent` with correct process routing +- **Phase 4** — Resource load events aggregated from all processes -Example: +--- -For Page.getResourceTree, in shipping Web Inspector, all frame tree nodes are included in one payload. +## Compatibility with Legacy Backends -With Site Isolation, not all resources will be in the same process, so each payload will contain a -subset of frame tree nodes and their resources. Whether or not we use Frame or Page targets, we still need -to rework the frontend to merge results from multiple targets. Since Frames are the unit of process isolation, -it's easier to think about the correct behavior of Inspector backend commands in terms of frames. +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: -The alternative would be to send commands to every sub-page of a page, but this is difficult to reason about because -the policy for which frames should go into which processes is variable across platforms and releases. +- If a `WebPage` target has one or more `Frame` sub-targets → send per-frame commands to the + frame targets. +- If a `WebPage` target has no `Frame` sub-targets (older backend) → treat the page target as + the single frame 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 frame target abstraction provides uniform addressing. -What is the plan for each domain? +--- + +## Open Questions -- Domains to transition to per-frame agents: - Debugger, Runtime, CSS, DOM, Audit, Animation, DOM, DOMStorage, DOMDebugger, Canvas -- Domains to be moved to UIProcess: - Network, Page -- Domains to stay as-is: - Target, Browser, Worker, ServiceWorker -- Domains TBD: - LayerTree, Heap, Memory, Storage, IndexedDB -- Domains removed: - Database, AppCache +1. **`getResponseBody` routing** — Response body data lives in `NetworkResourcesData` in the + process that loaded the resource. When a frontend requests a body for a cross-origin iframe + resource, how does the proxy agent locate and fetch it from the correct process? Current + thinking: embed process identity in the resource identifier, or introduce a UIProcess-side + cache. +2. **Shared `InjectedScriptManager`** — `FrameInspectorController` currently shares the + parent page's `InjectedScriptManager`. Is this correct? Injected scripts run in a specific + frame's JS context; a shared manager may cause leakage of script handles across origins. -## Other Deprecations +3. **DOM domain across process boundaries** — DOM `nodeId` values are process-local integers. + Under Site Isolation, nodes from different processes may have colliding IDs. A global + identifier scheme (possibly an extension of `InspectorIdentifierRegistry`) is needed before + DOM can be migrated to per-frame agents. + +--- -- AugmentableInspectorController -- ITML debuggable type +## Key Source Files + +| File | Role | +|------|------| +| `UIProcess/Inspector/WebPageInspectorController.h/.cpp` | Manages all targets for a `WebPageProxy` | +| `UIProcess/Inspector/WebFrameInspectorTargetProxy.h/.cpp` | Frame target proxy in UIProcess | +| `UIProcess/Inspector/WebPageInspectorTargetProxy.h/.cpp` | Page target proxy in UIProcess | +| `UIProcess/Inspector/InspectorTargetProxy.h` | Base class for all target proxies | +| `UIProcess/WebFrameProxy.cpp` | Creates/destroys frame inspector targets on frame lifecycle | +| `WebProcess/Inspector/WebFrameInspectorTarget.h/.cpp` | Frame target in WebContent Process | +| `WebProcess/Inspector/WebFrameInspectorTargetFrontendChannel.cpp` | IPC: WebProcess → UIProcess for events | +| `WebCore/inspector/FrameInspectorController.h/.cpp` | Per-frame agent controller with fallback chain (frame-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 | +| `JavaScriptCore/inspector/agents/InspectorTargetAgent.cpp` | Target multiplexing and command routing | +| `JavaScriptCore/inspector/InspectorBackendDispatcher.cpp` | `BackendDispatcher` with fallback dispatcher |