This note records the WebKit DOM model behavior that WebInspectorCore is
comparing against.
The earlier debugging direction was too flow-oriented. The useful evidence is at the model boundary: protocol payloads, target ownership, document generation, and frame projection. This section validates the current hypotheses against WebKit source and captured WebInspector logs from cross-origin iframe pages.
DOM.Node.frameIdis not the iframe owner's child-frame id. The protocol defines it as the "Identifier of the containing frame" andInspectorDOMAgent::buildObjectForNodefills it from the node's own document/frame. For a page-target iframe element, that value is the page document's frame, not the out-of-process child document.Target.TargetInfoin current WebKit protocol containstargetId,type,isProvisional, andisPaused; it does not exposeframeIdorparentFrameIdas protocol fields. The captured logs also show frame targets arriving withframe=nil parentFrame=nil.- Frame target id decoding is not a model contract. Checked WebKit source formats
frame target ids as
frame-<frameID>-<processID>, while captured runtime logs contain ids such asframe-8589934597. Treat target-id parsing only as a compatibility hint. Runtime.ExecutionContextDescription.frameIdis "Id of the owning frame". It can supplement target/frame mapping, but it is not an iframe owner relation by itself.DOM.Node.contentDocumentis the protocol field for frame owner elements, but Site Isolation means a cross-origin frame document often arrives through the frame target'sDOM.getDocument, not as the page-target iframe payload'scontentDocument.- WebInspectorUI keeps frame-target DOM nodes target-scoped by prefixing the raw node id with the frame target id. It does not let raw protocol node ids collide across page/frame targets.
- The earlier "frame target created -> send
DOM.getDocument" hypothesis was incomplete. WebInspectorUI first checkstarget.hasDomain("DOM").DOM.jsonin checked WebKit source hastargetTypes: ["itml", "frame", "page"], but the checked WebInspectorUI frontend metadata registersDOMonly for["itml", "page"]. This separates target capability from target type; target type alone is not permission to sendDOMcommands. Captured WebInspector iframe-page logs confirm the failure mode: 61 frame targets, 15 frameDOM.getDocumentsends, 15 wrapper acknowledgements, 0 frameDOM.getDocumentreplies, and 11 frame hydrate failures. - WebInspectorUI does not use page reload as iframe recovery. A frame-target
DOM.documentUpdatedcalls_frameTargetDocumentUpdated, which cleans up only that frame target and reinitializes that target's document. - WebInspectorUI also does not synchronously reload the page document inside
_documentUpdated. It calls_setDocument(null), clears page node bindings, resets_hasRequestedDocument, and lets a laterrequestDocument/ensureDocumentpath fetch a fresh document. The later fetch is driven by normal frontend lifecycle paths such as target initialization and main resource changes;DOM.documentUpdateditself is not a synchronous reload hook. WebInspector logs that showdocumentUpdated.reload.startfollowed bymodel.replaceDocumentRootare evidence of a WebInspector-specific race: a new shallow page root is being mixed with in-flight/stale page DOM events. _ensurePageBodyChildrenLoadedis not a generic missing-parent repair mechanism. It exists because_unsplicedFrameDocumentsneed iframe owner nodes to be present in_idToDOMNode; it requests<html>children if needed, then<body>children, and then retries frame document splice.InspectorDOMAgentonly emits mutation events for nodes it has already bound for the frontend, but the meaning of WebInspectormissingParentdepends on the event:childNodeInserted: backend only sends this when the parent is bound and children were requested. Missing the parent in the same document epoch is a WebInspector target/document/node mirror invariant breach.childNodeCountUpdated: backend sends this when the parent is bound but children were not requested. A missing node afterDOM.documentUpdatedcan be stale epoch fallout, not proof that iframe projection itself is wrong.setChildNodes: this can be the response toDOM.requestChildNodesor a path push fromDOM.requestNode. A missing parent is only meaningful after correlating the outgoing request with target id, document generation, and raw node id. The same captured logs show 169childNodeCountUpdateddrops, 371setChildNodesdrops, and 73childNodeInserteddrops after page document replacement. That points at document-generation/request correlation failure, not a picker-only problem and not something to solve by blindly reloading the page document.
- The current WebInspectorUI frame DOM code is transitional. It is guarded by target capability metadata, relies on a fragile URL fallback to find iframe owners, and still has FIXME markers for routing most frame-target DOM mutation events. The stable signal is the invariant shape, not the current URL splice fallback.
The derived invariant summary is:
Target-scoped document identity is primary.
Target capabilities are explicit before target-scoped DOM commands are sent.
DOM.documentUpdated invalidates a target document generation.
DOM requests and DOM events are correlated to the active target generation.
Frame document projection is explicit state.
Page DOM mutation handling preserves the current target/document mirror.
Iframe owner discovery may request page html/body children, but only as part of
pending frame-document splice, not as a standalone event-drop workaround.
WebKit is the reference for protocol semantics and stable frontend invariants, not a blanket source-level template. The source evidence falls into two groups.
Stable WebInspectorUI contracts:
- target-scoped protocol delivery via
Target.dispatchMessageFromTarget; - current page switching through
WI.pageTarget, withWI.mainTargetresolving toWI.pageTarget || WI.backendTarget; - current page frame-tree refresh through
NetworkManager.mainFrameand_frameIdentifierMap; - per-target domain availability through
target.hasDomain("DOM"); - page-target and frame-target documents are separate state;
- frame target document updates do not reset the parent page document;
- raw DOM node ids are target-local and are scoped before entering the frontend model;
DOM.documentUpdatedclears/invalidates the target document instead of synchronously repairing another target;DOM.requestNodepath materialization arrives assetChildNodes;- iframe projected documents are not erased by later ordinary
setChildNodeson the owner element.
Transitional or incomplete upstream areas:
- iframe owner discovery: upstream currently uses fragile URL matching and has a FIXME to replace it with frame/target identity;
- frame-target DOM mutation routing: upstream currently handles only part of the frame-target event surface and returns early for many mutations;
- frame-target backend sharing:
FrameDOMAgentduplicates large parts ofInspectorDOMAgentand has upstream FIXME notes to extract shared tree-building/binding logic and avoid traversing child frames that have their own frame agent; - frame target identity threading: current
TargetInfodoes not carryframeId,parentFrameId, or owner node id even though the model relation exists conceptually; - ad iframe refresh and provisional frame commit behavior: upstream has target commit plumbing, while document generation/projection state remains the relevant invariant for parent page DOM integrity.
The research separates WebKit's proven boundaries from temporary fallback code. Upstream FIXME markers are evidence that the surrounding invariant is more stable than the current fallback code.
| Area | WebKit source | Relevant fact |
|---|---|---|
| DOM protocol node identity | Source/JavaScriptCore/inspector/protocol/DOM.json |
DOM.Node.frameId is the containing frame; contentDocument is the frame owner document field; requestChildNodes(depth: -1) means entire subtree but default is depth 1. |
| DOM target availability | Source/JavaScriptCore/inspector/protocol/DOM.json, Source/WebInspectorUI/UserInterface/Protocol/Legacy/iOS/26.4/InspectorBackendCommands.js, Source/WebInspectorUI/UserInterface/Protocol/Target.js |
Local current protocol lists frame in DOM.targetTypes, but the checked WebInspectorUI frontend metadata registers DOM only for itml and page. WebInspectorUI builds each target's agents from InspectorBackend.supportedDomainsForTargetType(target.type), so runtime capability comes from active frontend protocol metadata, not from target kind. |
| Backend node payload | Source/WebCore/inspector/agents/InspectorDOMAgent.cpp |
buildObjectForNode sets frameId from the node's document frame and sets contentDocument only when a frame owner has an accessible content document. |
| Backend mutation contract | Source/WebCore/inspector/agents/InspectorDOMAgent.cpp |
didInsertDOMNode returns when parent is not bound; otherwise it emits childNodeCountUpdated or childNodeInserted depending on whether children were requested. |
| Target protocol | Source/JavaScriptCore/inspector/protocol/Target.json |
TargetInfo has no protocol-level frame id fields; didCommitProvisionalTarget swaps old/new target ids. |
| Frame target id implementation | Source/WebKit/WebProcess/Inspector/FrameInspectorTarget.cpp, captured runtime logs |
Checked source formats frame target ids as frame-<frameID>-<processID>, while runtime logs show frame-<integer>; target id shape is useful evidence, not a substitute for explicit model fields. |
| Main target globals | Source/WebInspectorUI/UserInterface/Base/Main.js |
WebInspectorUI has WI.backendTarget and WI.pageTarget. WI.mainTarget is a getter returning `WI.pageTarget |
| Target frontend | Source/WebInspectorUI/UserInterface/Controllers/TargetManager.js |
WebInspectorUI creates WI.PageTarget / WI.FrameTarget directly from TargetInfo.targetId. On provisional commit, the old target is destroyed, the new target is marked committed, and page transition managers run. |
| Target transport | Source/WebInspectorUI/UserInterface/Protocol/TargetObserver.js and Connection.js |
Target.dispatchMessageFromTarget is routed to the target connection before domain dispatch. |
| Page frame tree | Source/WebInspectorUI/UserInterface/Controllers/NetworkManager.js, Source/WebInspectorUI/UserInterface/Models/Frame.js |
Page navigation refreshes the main frame resource tree through Page.getResourceTree; frames are keyed by protocol frame id in _frameIdentifierMap. |
| Frame target DOM flow | Source/WebInspectorUI/UserInterface/Controllers/DOMManager.js |
initializeTarget returns unless target.hasDomain("DOM"); only DOM-capable frame targets run _initializeFrameTarget, store a frame document separately, and attempt splice. |
| Pending splice | Source/WebInspectorUI/UserInterface/Controllers/DOMManager.js |
_unsplicedFrameDocuments holds frame documents until owner iframes are available; _ensurePageBodyChildrenLoaded requests page html/body children. |
| Projection attach | Source/WebInspectorUI/UserInterface/Controllers/DOMManager.js |
_trySpliceFrameDocumentIntoNode attaches the frame document as iframe _contentDocument; current fallback is exact/resolved URL match with a WebKit FIXME to use frame/target identity later. |
| Frame target document refresh | Source/WebInspectorUI/UserInterface/Controllers/DOMManager.js |
_frameTargetDocumentUpdated cleans up and reinitializes only that frame target. |
| Node scoping/projection | Source/WebInspectorUI/UserInterface/Models/DOMNode.js |
Frame-target node ids are scoped as <target id>:<raw node id>; hydration commands such as requestChildNodes use backendNodeId against the owning target; _setChildrenPayload returns if _contentDocument exists. |
| DOM event dispatch | Source/WebInspectorUI/UserInterface/Protocol/DOMObserver.js |
DOM events are target-aware, but current frame-target routing is limited: documentUpdated and setChildNodes route to frame handlers, while many mutations still return early with a FIXME. |
| Frame target lifecycle | Source/WebKit/UIProcess/Inspector/WebPageInspectorController.cpp, Source/WebKit/WebProcess/Inspector/FrameInspectorTarget.cpp |
Checked source creates frame targets only when Site Isolation frame target management is enabled. Target ids are currently formatted from frame id plus process id, and provisional frame commit sends Target.didCommitProvisionalTarget from old process target id to new process target id. This is implementation evidence, not a protocol-level DOM identity contract. |
| Frame DOM backend state | Source/WebCore/inspector/agents/frame/FrameDOMAgent.cpp |
FrameDOMAgent exists as a frame-scoped backend DOM agent, but upstream FIXME comments still call out duplicated tree-building/binding logic, child-frame handling marked for refinement, and missing frameId on frame-target document payloads. |
The DOM research was rechecked against WebKit source. No source-backed
conclusion changed, but two overbroad notes were
narrowed: frame-target hydration uses backendNodeId, not every DOM command;
and FrameDOMAgent still lacks frameId on frame-target document payloads.
Source/JavaScriptCore/inspector/protocol/DOM.jsonstill declarestargetTypes: ["itml", "frame", "page"], while the checked iOS/macOS 26.4 frontend metadata still registersDOMfor["itml", "page"]and activates it for["itml", "web-page"].Source/JavaScriptCore/inspector/protocol/Target.jsonstill exposesTargetInfo.targetId,type,isProvisional, andisPaused; it still has no protocolframeId,parentFrameId, or owner-node field.InspectorDOMAgent::buildObjectForNodestill setsDOM.Node.frameIdfrom the containing frame and setscontentDocumentonly for accessible frame owner content documents.InspectorDOMAgent::didInsertDOMNodestill returns when the parent is not bound, otherwise emittingchildNodeCountUpdatedorchildNodeInserteddepending on whether children were requested.WI.DOMManager.initializeTargetstill returns unlesstarget.hasDomain("DOM"). Frame target DOM state still lives in_frameTargetDOMData, pending frame documents still live in_unsplicedFrameDocuments, and owner discovery still uses the URL fallback guarded by the upstream FIXME.WI.DOMNodestill scopes frame-target frontend ids as<target id>:<raw node id>. Hydration commands such asrequestChildNodesroute toowningTargetand usebackendNodeId;_setChildrenPayloadstill returns early when_contentDocumentis already present.WI.DOMObserverstill routes frame-targetdocumentUpdatedandsetChildNodes, while many other frame-target mutation events still return early with thewebkit.org/b/298980FIXME.FrameDOMAgent::buildObjectForNodestill has a FIXME to setframeIdfor frame targets, andFrameInspectorTarget::toTargetIDstill formats target ids asframe-<frameID>-<processID>.
The target/page/network container shape belongs in TransportResearch.md.
This DOM note keeps only the WebKit DOM ownership shape.
WI.DOMManager is not one physical DOM tree. It owns both the page document
bucket and the frame-target document bucket. Projection connects those buckets
for display, but it does not move the frame document into the page document's
ordinary children.
flowchart TB
subgraph DM["WI.domManager"]
direction TB
subgraph PageDoc["_document<br/>page #document DOMNode"]
direction TB
pageDoctype["DOCTYPE"]
subgraph PageHTML["html DOMNode"]
direction TB
subgraph PageHead["head DOMNode"]
pageHeadChildren["head children"]
end
subgraph PageBody["body DOMNode"]
direction TB
pageBodyChildren["ordinary page children"]
subgraph IframeOwner["iframe/frame owner DOMNode"]
direction TB
ownerChildren["_children<br/>page-target children"]
contentDocumentSlot["_contentDocument<br/>reference to frame document"]
end
end
end
end
subgraph FrameData["_frameTargetDOMData"]
direction TB
subgraph FrameEntry["entry keyed by WI.FrameTarget"]
direction TB
frameTargetRef["target"]
subgraph FrameDoc["document: frame #document DOMNode"]
direction TB
frameDocumentRoot["#document root"]
subgraph FrameHTML["html DOMNode"]
frameHead["head DOMNode"]
frameBody["body DOMNode"]
end
end
end
end
subgraph Indexes["side indexes"]
direction TB
nodeIndex["_idToDOMNode"]
pending["_unsplicedFrameDocuments"]
end
end
This is the core containment answer:
WI.domManager
page document DOMNode
page DOMNode tree
iframe owner DOMNode
_contentDocument pointer to frame document after projection
frame target DOM data
WI.FrameTarget object
frame document DOMNode
frame DOMNode tree
node index
page nodes and frame-target nodes
pending frame documents
frame documents waiting for owner discovery
The frame document is displayed under the iframe owner, but the frame document is stored in the frame-target data bucket. Displayed nesting is not the same thing as ownership.
The source evidence implies these separate concepts:
ProtocolTarget: protocol routing endpoint. Owns target-local agent commands and target-local DOM events.TargetCapabilities: per-target domain/command/event availability. AFrameTargetwithoutDOMdomain support does not receiveDOM.getDocument.FrameIdentity: WebKit frame id when known. It may come from document payloadframeId, execution contextframeId, or implementation-specific target id decoding only when deliberately supported.DOMDocument: target-scoped document generation and root node. Page target and frame target documents are never substitutes for each other.DOMDocumentEpoch: invalidation boundary created byDOM.documentUpdated. In-flightrequestChildNodes,requestNode, and DOM events are meaningful only relative to the active target/document generation.DOMNode: node mirror scoped by target document generation plus raw protocol node id.ownerFrameIDmeans containing frame, not child frame.DOMHydrationRequest: outgoing DOM request state keyed by target, document generation, and raw node id.setChildNodesis interpreted through this request context instead of as an unqualified tree mutation.FrameDocumentProjection: relation between one iframe/frame owner node and one frame-target document root. This is not a regularchildrenarray.PendingFrameDocument: frame-target document that exists but has not yet found its iframe owner in the page DOM.PageOwnerHydration: the WebKit_ensurePageBodyChildrenLoadedequivalent. It is keyed by page document generation and exists only to make pending projections discover owner iframe nodes.
Contradicted interpretations:
DOM.Node.frameId == child frame id
or
frame target type == DOM-capable target
or
frame target DOM.getDocument replaces/repairs page target document
or
page DOM.documentUpdated -> synchronous page DOM reload while old events/requests
are still in flight
or
any missing page mutation parent -> reload page DOM
Source-backed interpretation:
ProtocolTarget(page)
capabilities include DOM and other domains advertised for this target
currentDocument -> DOMDocument(page generation N)
node ids scoped to page target/generation
iframe owner node
projection -> DOMDocument(frame target generation M)
ProtocolTarget(frame)
capabilities may or may not include DOM
currentDocument? -> DOMDocument(frame target generation M)
node ids scoped to frame target/generation, only if DOM-capable
FrameDocumentProjection(owner node, frame document root, state: pending|attached)
DOMManagerowns the frontend node index and current document state.DOMNodeinstances are created from WebKit DOM protocol payloads and are indexed by protocol node identity.- Frame targets are handled separately from the page document. A frame target document is fetched from that target and then spliced into the matching iframe as
contentDocument. - If the iframe owner is not yet available, WebKit keeps the frame document unspliced and retries when more page nodes are loaded.
In Site Isolation mode, WebKit treats a frame as an inspector target, not as just another subtree inside the page target:
- Each
WebFrameProxycreation creates aFrameInspectorTargetProxyin UIProcess and aWI.FrameTargetin WebInspectorUI. - The page still has a
PageInspectorTargetProxy, but cross-origin frame content can live behind a differentFrameInspectorControllerin another WebContent process. - Frame-to-page and frame-to-frame relationships are metadata. They are not the lifetime owner of protocol targets.
- Commands for frame-scoped domains are routed through the target system to the
frame target. The frontend receives frame events as
Target.dispatchMessageFromTarget(targetId, message). - Cross-origin iframe navigation can produce a provisional frame target before
commit. WebInspectorUI handles
Target.didCommitProvisionalTargetby destroying the old target and marking the new target as committed.
For DOM specifically, current WebKit source expresses the important model split
through separate page and frame target plumbing: page documents are handled by
the page target, while out-of-process frame documents can be fetched from
frame targets through FrameDOMAgent. The source does not expose an
out-of-process frame document as ordinary page-target children.
This means a cross-origin iframe is represented by two related but separate things:
Page target DOM
#document
html
body
iframe/frame owner node
Frame target DOM
#document
html
body
...
The connection between those two documents is frame/target ownership, not a
single page-target children array.
WebInspectorUI's current DOM code is a bridge between old page-DOM assumptions and Site Isolation frame targets:
DOMManager.initializeTarget(target)first returns unlesstarget.hasDomain("DOM"). AWI.FrameTargetis not automatically a DOM-capable target.DOMObserver.documentUpdated()dispatches by target type.- For
WI.FrameTarget, it callsDOMManager._frameTargetDocumentUpdated. - For the page target, it calls
DOMManager._documentUpdated.
- For
DOMManager._documentUpdated()clears the current page document by calling_setDocument(null). A new page document is requested later throughrequestDocument/ensureDocument, not by treating every DOM update as an iframe recovery reload._mainResourceDidChangeis one lifecycle path that callsensureDocument()after main-frame resource changes.DOMManager._initializeFrameTarget(target)callstarget.DOMAgent.getDocument, creates aDOMNodewith{frameTarget: target}, stores it in_frameTargetDOMData, and then tries to splice that frame document into the page tree.DOMNodestores frame-target nodes under a scoped frontend id:<frame target id>:<raw node id>. The raw node id remains target-local.DOMManager._spliceFrameDocumentIntoPageTreetries to attach the frame document to the matching iframe owner. WebKit currently does this with URL matching and explicitly marks that as fragile; the desired identity is frame/target metadata threaded throughTarget.targetCreated.- If the iframe owner is not yet known, the document is kept in
_unsplicedFrameDocuments.DOMManager._ensurePageBodyChildrenLoadedrequests page body children, and_trySpliceUnsplicedFrameDocumentsis retried from both_setChildNodesand_childNodeInserted. DOMManager._frameTargetDocumentUpdated(target)cleans up only the previous frame-target document and then reinitializes that frame target. It does not reset the parent page document.- Current
DOMObserverframe-target support is partial. Frame-targetdocumentUpdatedandsetChildNodesare handled, but attribute, character data, child insertion/removal, pseudo-element, and shadow-root events still return early with FIXME comments. So even the latest WebInspectorUI source is a transitional source state, not the final shape of frame DOM mutation routing. DOMNode._setChildrenPayloadreturns early when the node already has acontentDocument. A laterDOM.setChildNodeson an iframe owner does not replace or erase the frame document projection.
DOM.requestNode explicitly promises that the node path up to the root is sent
as setChildNodes notifications. DOM.setChildNodes(parentId: 0) is the
detached-root variant used by pushNodePathToFrontend when the pushed node has
no parent. For normal inspect selection, WebKit walks up to the nearest bound
ancestor and then emits setChildNodes for real parent ids. The observed
contract means missing parents in this path are request/document correlation
evidence, not a generic reason to reload or replace the page document.
The upstream FIXME around _trySpliceFrameDocumentIntoNode is central source
evidence:
URL-based matching is fragile (breaks with redirects, blob: URLs,
about:srcdoc, query strings). Use frame identity information (frame ID or
target ID) threaded through Target.targetCreated to directly look up the parent
iframe element.
The current WebInspectorUI code loops over known page nodes, finds the first
IFRAME or FRAME node without _contentDocument, reads its src attribute,
and compares it against the frame document's documentURL, first as an exact
string and then as a resolved URL. That is a discovery fallback, not identity.
Important consequences:
- It can attach the wrong document when multiple ad iframes share the same
src, when redirects change the final document URL, when the owner isabout:blankbefore script navigation, whensrcdocorblob:is used, or when query strings are rewritten. - It has no stable answer for ad iframe refreshes where the frame target navigates or commits in another process while the page owner node remains the same.
- It is order-sensitive: current WebInspectorUI attaches the first matching node. That behavior is not a stable identity contract; ambiguity maps to the existing pending-frame-document state.
DOM.Node.frameIddoes not solve this. It is the containing frame id of the node that the payload belongs to, not the child frame id owned by an iframe element.- Current
Target.TargetInfoalso does not solve this. The protocol payload exposestargetId,type,isProvisional, andisPaused, but noframeId,parentFrameId, or iframe owner node id. - Checked WebKit source encodes frame id and process id into the frame target
id (
frame-<frameID>-<processID>), and provisional frame commit maps old and new target ids throughTarget.didCommitProvisionalTarget. That is useful evidence for a compatibility adapter, but it is not enough to identify the owner iframe node unless the owner relation is also exposed or derived from a trusted protocol path.
The missing relation can be represented conceptually as:
FrameIdentity
frameID?
targetID
processID?
parentFrameID?
ownerNodeID?
FrameDocumentProjection
ownerNodeID?
frameTargetID
frameDocumentID
state: pending | attached | ambiguous
Research implication: explicit identity or one unambiguous URL fallback is needed before attaching a frame document to an owner. Otherwise the existing pending-frame-document category is the safer interpretation than corrupting the parent page tree.
- The transport/session root owns protocol target records and dispatch. It is distinct from page DOM state.
- Page-scoped DOM state is bounded by the page boundary that corresponds
to the active
WI.pageTarget/NetworkManager.mainFramelifetime. When that page boundary is discarded, its documents, nodes, hydration requests, projections, revisions, and selection state form one lifetime group. - Target capabilities are known before target-scoped DOM commands are sent.
A target kind of
frameis insufficient. DOMNode.IDistargetID + documentGeneration + nodeID, so the same protocolnodeIDin different targets or document generations cannot collide.DOMNodeCurrentKeyis only a current mirror lookup key:targetID + nodeID.DOMNode.ownerFrameIDis the containing frame id from protocolDOM.Node.frameId, not the iframe owner node's child frame id.DOM.documentUpdatedadvances or invalidates the target's document generation. Pending DOM command replies and DOM events from the old generation belong to the old document root.DOM.requestChildNodesandDOM.requestNodeare tracked as hydration requests against a target/document generation.requestChildNodessetChildNodesmatches the requested parent.requestNodesetChildNodesmay arrive as ordered path fragments; unknown parents are retained as request-node path fragments within the active document generation.- iframe documents are not stored as regular DOM children. Projection renders
DOMFrame.currentDocumentIDunder the iframe owner node. - Frame document refresh updates only that frame's current document generation and does not mutate the parent page document.
- Selection stores node identifiers and request generation, not stale node object references.
flowchart TD
session["Transport/session root<br/>protocol target dispatch"]
pageBoundary["Page boundary<br/>active page target + main frame tree"]
pageTarget["ProtocolTarget(page)<br/>capabilities from frontend metadata"]
frameTarget["ProtocolTarget(frame)<br/>capabilities from frontend metadata"]
pageState["Page-scoped DOM state<br/>documents, nodes, requests, projections, selection"]
pageDoc["DOMDocument(page target, generation N)"]
frameDoc["DOMDocument(frame target, generation M)"]
iframe["iframe/frame owner node<br/>ownerFrameID = containing frame"]
projection["FrameDocumentProjection<br/>pending | attached | ambiguous"]
session --> pageBoundary
session --> pageTarget
session --> frameTarget
pageBoundary --> pageState
pageState --> pageDoc
pageState --> frameDoc
pageDoc --> iframe
iframe --> projection
projection --> frameDoc
ProtocolTarget.ID: WebKit target identifier.- Page boundary: the current page target lifetime plus current main frame tree. WebKit frontend does not expose a separate stable protocol page id.
DOMFrame.ID: WebKit frame identifier when known. Do not assume currentTargetInfoexposes this directly.DOMDocument.ID:targetID + documentGeneration.DOMNode.ID:documentID + nodeID.DOMNodeCurrentKey:targetID + nodeIDfor resolving the current node mirror within one target.
backendNodeId, URL strings, raw node ids, and page-* naming are not global
DOM identity. Frame target-id shape is implementation/version evidence only;
local source and captured runtime logs do not agree on a single string form. It
may be decoded deliberately as a WebKit compatibility signal, but this research
treats target, frame, document, node, and projection as separate fields.
Frame documents are owned by the frame target's current document and projected
through DOMFrame.currentDocumentID / FrameDocumentProjection.
An iframe node does not get its child frame identity from protocol
DOM.Node.frameId. That value is the iframe node's containing frame. A future
explicit child-frame field can directly bind the owner; until then, owner
discovery is a separate projection step. The projected document is not stored
as an iframe regular child.
This preserves the parent page DOM when an iframe ad refreshes or navigates.
For cross-origin frames, a page-target iframe node may initially be known
before the frame-target document arrives, or the frame-target document may
arrive before the iframe owner is loaded. WebKit represents this gap with
pending frame documents and owner-path hydration, not by collapsing the frame
document into the page document or rebuilding the page document from a shallow
DOM.getDocument response.
The observed inspect-selection flow is transaction based:
- Resolve
RemoteObject.injectedScriptIDto the owning protocol target. - Return a
DOM.requestNodecommand intent for that target. - Accept the result only if the selection request, target, and document generation still match.
- On failure or stale result, update selection failure state only. Do not mutate the DOM tree.
WebInspectorUI projection uses the WebKit DOM tree order:
templateContent -> ::before -> effective children -> ::after
For frame owner nodes, effective children prefer the projected frame document. Regular children are not used to store the projected document.
- URL matching appears only as iframe owner discovery fallback. It is not document or node identity.
backendNodeId, URL strings, and target name prefixes are not global DOM identity.- Frame document updates are target-scoped in WebInspectorUI; they are not parent page document recovery.
- Display rows are projection output, not source-of-truth DOM model objects.
When a cross-origin iframe disappears from the DOM tree view, the useful signals are:
- Target capabilities: whether the frame target actually has the
DOMdomain in the activeInspectorBackendfrontend metadata before sendingDOM.getDocument. In the checked WebInspectorUI frontend metadata,DOMis registered foritmlandpage, notframe. - Target lifecycle: whether the frame target survived provisional commit and the model points at the committed target id.
- Frame document ownership: whether
DOM.getDocumentresult for the frame target is stored as a frame-owned document, not as a page document replacement. - Splice state: whether the frame document is either projected under the iframe owner or kept pending until the owner node is hydrated.
- Page document updates: whether page-target
DOM.documentUpdatedinvalidates the page document generation and does not synchronously replace an existing expanded page tree with a shallowDOM.getDocumentresult while old DOM events/requests are still in flight. - Request/event correlation: for
setChildNodesandchildNodeInserteddrops, the target id, document generation, and originating DOM request determine whether the event belongs to the active tree. - Selection hydration: if
DOM.requestNodeemitssetChildNodesfor missing parents, those fragments belong to the request-node path for the active document generation. Missing-parent logs alone are not evidence for a page document reload.
The core invariant is: a frame document is target-scoped state. Page DOM events may reveal or remove the iframe owner, but they do not own the frame target document.
Source/WebInspectorUI/UserInterface/Controllers/DOMManager.jsSource/WebInspectorUI/UserInterface/Models/DOMNode.jsSource/WebInspectorUI/UserInterface/Controllers/TargetManager.jsSource/WebInspectorUI/UserInterface/Protocol/Connection.jsSource/WebInspectorUI/UserInterface/Protocol/DOMObserver.jsSource/WebKit/UIProcess/Inspector/WebPageInspectorController.cppSource/WebKit/UIProcess/Inspector/FrameInspectorTargetProxy.cppSource/WebKit/WebProcess/Inspector/FrameInspectorTarget.cppSource/WebCore/inspector/agents/InspectorDOMAgent.cppSource/WebCore/inspector/agents/frame/FrameDOMAgent.cppSource/JavaScriptCore/inspector/protocol/DOM.jsonSource/JavaScriptCore/inspector/protocol/Target.jsonSource/WebInspectorUI/UserInterface/Protocol/Legacy/iOS/26.4/InspectorBackendCommands.jsSource/WebInspectorUI/UserInterface/Protocol/Legacy/macOS/26.4/InspectorBackendCommands.js
Element styles are intentionally documented separately in CSSModelResearch.md.
The DOM-side contract is only the boundary:
- DOM owns target/document/node identity, selection, and frame-document projection.
- CSS owns style refresh, cascade ordering, computed properties, stylesheet invalidation, and style-specific protocol events.
- The handoff from DOM to CSS is the selected live DOM node plus its command identity: owning target, active document generation, and raw protocol node id for that target.
- CSS does not repair DOM selection or mutate DOM projection. If the selected node is not an element, is stale, or belongs to a target without CSS support, CSS reports an unavailable state.
See CSSModelResearch.md for the WebKit WI.DOMNodeStyles / CSSManager
research, protocol payloads, cascade order, and property toggle behavior.