Repository navigation
build: release 0.3.0 - #3
Merged
Merged
Conversation
A daily workflow (and on demand) asks Chromium Dash which devtools-frontend commit stable Chrome ships, as build.ts --latest does, and compares it with REVISION. When they differ, minor Chrome updates included, it opens an issue with the update command, once per version, and closes an older one not done yet. It builds nothing; a Chromium Dash answer without a version or a commit fails the run. DEV.md: the frontend follows every stable update, not once per milestone.
…ase/x.y.0 main now holds what is released. Work for the next minor goes on dev/x.y.0, pushed freely, and reaches main through release/x.y.0 (dev -> release -> main); a fix that cannot wait still goes from main to main as a patch, and main is then merged into the dev branch. CI also runs on pushes to dev/**, so that the development branch is checked without a pull request.
With one developer, work in progress on dev/x.y.0 need not queue a run on every push: the release/x.y.0 pull request checks it all, and CI can be run on the branch by hand (gh workflow run ci.yml --ref dev/0.3.0). DEV.md says so.
A roadmap in both languages, linked from DEV.md, for the directions discussed so far: the missing accessibility tree (what dom-accessibility-api covers and what is left), real CDP on Android through ADB wireless debugging, the gaps in Emulation (no setDeviceMetricsOverride, no-ops that succeed), patches from an older release line, and what is shelved (dragging the divider in shell mode, touch screens).
iframes: what the agent assumes now (one frame, one execution context), Chrome's model (same-origin iframes as more frames and contexts in the target, cross-origin ones as child targets), what each domain needs, the realm problem (about 166 instanceof checks and 180 uses of the global document or window), and a step 0 that changes no behavior but keeps new code from adding to it. What else can be done in the page and match Chrome, by value for effort: animations, vision deficiencies, network blocking and offline, the browser's default styles, layout overlays, emulated media, Application panel updates, service workers, violations.
…the top realm The first step towards same-origin iframes (docs/roadmap.md, "iframes"), changing no behavior. - core/frames.ts: a FrameRegistry, looked up by id, execution context, window or node. Only the top document is registered for now. Domains take frame, loader and execution context ids from Context.frames instead of the FRAME_ID, LOADER_ID and EXECUTION_CONTEXT_ID constants; DOM.describeNode, DOM.getNodeForLocation and style sheet headers use the frame of their node. - core/realm.ts: node checks by nodeType (isElement, isDocument, isShadowRoot, ...) and a node's own document and window (documentOf, realmOf), which hold for an iframe's nodes too. - scripts/check-realms.ts, in `pnpm lint`: counts per file the `instanceof Element`-like checks and uses of the global document, window and getComputedStyle in the agent, against scripts/check-realms.json. A file may have fewer, not more.
FrameRegistry now finds the page's same-origin iframes (and <frame>s) as child frames, each with its own id, loader id, execution context id, parent, owner element, window and current document. Cross-origin iframes, whose contentDocument is out of reach, are not registered. - sync() walks the documents and registers, updates or unregisters frames. Like Chrome, a frame keeps its id when it loads another document but gets a new loader id and execution context id; a moved iframe gets a new browsing context, so it becomes a new frame. - onChange(listener) reports 'attached', 'navigated' and 'detached'. While anyone listens, the registry watches each frame document for iframes being added or removed and for their load events, and syncs by itself. - ofOwner, ofDocument, childrenOf and iteration in tree order serve the DOM domain now and Page/Runtime later. realm.ts gains isHtml(node, name), isNode (a node of any realm, through the top page's Node.prototype.nodeType getter, which Web IDL accepts across realms) and isFrameOwner.
An <iframe> or <frame> of a registered frame is serialized with its frameId and contentDocument, and the nodes in it work with the DOM commands: getDocument, requestChildNodes, describeNode, resolveNode, querySelector(All), getOuterHTML, performSearch and pushNodeByPathToFrontend. As checked against Chrome: - contentDocument is not one of the iframe's children: childNodeCount and children are the iframe's fallback content, and the document has no parentId. - The document's children follow only with pierce, at the iframe's own depth (like shadow roots): describeNode(iframe, depth 2, pierce) lists the document with its <html>. - The document element of each frame carries the frame's frameId. - Pushing a node inside an iframe sends setChildNodes for the iframe (its fallback content), then for its document, and down; the iframe document's parent in the path is the iframe. - performSearch goes through the documents of all frames, the top one first, for each step (text, then XPath, then selectors). - The frontend's node path names an iframe document `d`. The node checks these need use core/realm.ts instead of the top page's instanceof (resolveNode accepts a node of any realm; nodes.ts and outerHTML check node types), lowering their counts in check-realms.json. Cross-origin iframes stay as they were, without frameId.
The DOM domain's observer also observes the document of each iframe it sends (one observer for all documents, as for shadow roots, so that the order of changes across documents is kept), and listens to the frame registry while the client follows the tree. As checked against Chrome: - Attribute, text and child changes inside an iframe document are reported like any others. - An inserted iframe is first sent without frameId and contentDocument (Chrome reports it before its frame exists), then removed and inserted again with its document: right away for one without src (or about:blank), at its load for one that loads a src or srcdoc. The same holds for a moved iframe, which gets a new browsing context. - An iframe that loads another document (src or srcdoc changed, or its own location.reload()) is removed and inserted again with the new document; the old document's node ids become invalid. - A removed iframe takes its document's node ids with it. Chrome sends the new document as it commits, empty, and then counts its children with DOM.childNodeCountUpdated as they are parsed; ours is sent at the iframe's load, complete. The client ends up with the same tree. Frame changes are sent in a microtask after pending mutations and only if the document differs from the one sent, since an about:blank iframe loads synchronously while being inserted, before its mutation is reported.
…etOuterHTML The rest of the DOM domain and core/history.ts (DOM.setOuterHTML and undo) check node types and use the node's own document and window instead of the top page's instanceof, document and getComputedStyle, so that nodes of same-origin iframes work too: - setOuterHTML parses in the node's own document and, for a document element, patches that document; it patched the top one before. - setAttributesAsText and setNodeName create their nodes in the element's document; getContentQuads of a text node uses its document's range; box models and getNodesForSubtreeByStyle use the element's window's getComputedStyle. - getNodeForLocation stays on the top document, now said explicitly: coordinates inside iframes are for Overlay and geometry to come. realm.ts gains isSvg, isSvgElement and isMathMlElement. dom.ts no longer uses the top realm and history.ts once (a string); check-realms.json is lowered. A conformance test edits nodes of an iframe document.
The layout mapping read computed styles through the top window's getComputedStyle, checked elements with instanceof HTMLElement / SVG*Element and took the document, its scrolling element and scrollX/scrollY from the top page. For an element of a same-origin iframe all of these are wrong: instanceof fails across realms, so every iframe element looked like "not HTML" and fell back to the bounding rectangle, and the root elements were compared with the top document. The checks now go through core/realm.ts (isHtmlElement, isSvg) and the element's own window and document; SVGGraphicsElement and SVGGeometryElement are recognised by their methods, replaced elements by their local name. Behaviour in the top document is unchanged.
The rules and sheets of a same-origin iframe belong to the iframe's realm: they are not instances of the top page's CSSStyleRule, CSSMediaRule, ... so every `instanceof CSS*` in the CSS domain would take an iframe's style rule for something else and skip it. core/realm.ts gets isCssom(value, name), which walks the prototype chain for an interface prototype whose Symbol.toStringTag is `name`. That covers inheritance too (a CSSMediaRule is a CSSGroupingRule), as instanceof did. The CSS domain and core/adopted.ts use it instead of instanceof; behaviour for the top page is unchanged.
…k.ts, per window The fetch, XHR and WebSocket hooks and the Resource Timing observer move out of probe/index.ts into probe/network.ts, behind hookNetwork(probe, sink, window, listen). Every global they use (fetch, XMLHttpRequest, WebSocket, Request, Blob, FormData, ArrayBuffer, DOMException, PerformanceObserver, location) is now read from the window given, and instanceof checks use that window's constructors, so that the same hooks can later be installed in a same-origin iframe, whose objects are not instances of the top page's. The probe still hooks only the top window, with the same behavior. Its record keeping (ids, ring buffers, listeners) is passed in as a NetworkSink. Kept apart from the console and error hooks, which stay in probe/index.ts. Realm uses drop by 4 (check-realms).
…d events Page.getFrameTree (new) and Page.getResourceTree now list the same-origin iframes of the frame registry as childFrames, nested like the frames. Each frame is described like Chrome describes it: parentId, a new loaderId per document, name from the window's name else the <iframe>'s id, the URL without its fragment plus urlFragment, securityOrigin "://" for about:blank and about:srcdoc documents, mimeType from the document, and the secure context and cross-origin isolation of the frame's own window. The top page's resources no longer list iframe documents (Resource Timing's "iframe"/"frame" initiators): Chrome counts them as the frames' own. Page.enable now follows the registry and sends, measured against Chrome: - frameAttached (with parentFrameId), then for an iframe that has a src or srcdoc frameStartedLoading, and frameNavigated plus frameStoppedLoading once its document is there; Chrome reports nothing for the initial about:blank such an iframe leaves (core/frames.ts awaitsFirstDocument). - for an empty iframe, frameNavigated to about:blank before frameStartedLoading and frameStoppedLoading, in Chrome's order. - for a later navigation, frameStartedLoading, frameNavigated, frameStoppedLoading. - frameDetached with reason "remove" for each removed frame, descendants first. Page.disable and the end of the session stop them. The registry notices a navigation on the iframe's load event, so the events come later than Chrome's (which reports the commit). Chrome's experimental frameRequestedNavigation, frameStartedNavigating and frameSubtreeWillBeDetached are not sent: their loader id and timing are not known in the page.
… window Probe.hookNetwork(win, frame) installs the fetch, XHR and WebSocket hooks and the Resource Timing observer in another window (a same-origin iframe's), recording into the same lists. Each request record started there gets a FrameTag (frameId, loaderId, documentURL), from the `frame` callback called when the request starts: like Chrome, a request keeps the document that made it even if its frame navigates later. WebSocket records are not tagged: Chrome's webSocketCreated has no frame either. A window is hooked once per global object, marked with a symbol: when a navigation keeps the global (the initial about:blank document), calling again only replaces `frame`. Resource Timing in such a window: - entries are shifted from the iframe's time origin to the top page's, so timestamps of every frame are on one clock; - fetch() and XHR entries that started before the hooks are now reported (as Fetch / XHR), only later ones are left to the hooks. In an iframe these are the requests of its first scripts, which run before the agent learns of the new document; Chrome reports them too. The probe view recognizes Headers of any realm (an iframe's fetch() returns its own), and passes hookNetwork through. Optional in the Probe type: probes from before 0.3.0 lack it.
The CSS domain only looked at the top document. The sheet registry now takes the documents of all registered frames (and the shadow roots in them) as trees with sheets of their own, so CSS.enable announces the sheets of each same-origin iframe, and the domain watches every frame's document for <style>/<link> changes, load events and adopted sheets. - styleSheetAdded carries the frame the sheet belongs to: that of its owner node, or for a constructed sheet the realm it was made in (only that realm's trees can adopt it), found by realmOfSheet(). - Like Chrome, an inline sheet's sourceURL is its document's base URL: for a srcdoc iframe that is the parent's URL, not about:srcdoc, and a <base> in the iframe wins (checked against Chrome). Sheets a script inserts and constructed ones stay without a URL and not inline. - The registry's frame changes drive the domain: an attached iframe, or one that loads another document, has its document watched and its sheets added; the sheets of a document left behind (navigation or a removed iframe) are reported removed, as Chrome does. - core/adopted.ts hooks adoptedStyleSheets on the prototypes of each realm whose trees are watched (watchTrees), as an iframe has its own Document and ShadowRoot prototypes. - <style> elements of an iframe's document present when the agent first sees it count as the parser's, like those of the top document. Adds css-iframe.conformance.test.ts with a styled iframe fixture.
…ion context Runtime.enable used to announce one context, the top page's. It now announces the default context of every frame in the registry, and while enabled follows the frames, as Chrome does (measured against it): - an iframe's context is created once its document is there; none is announced for the initial about:blank an iframe with a src or srcdoc is about to leave, like Chrome; - a navigated frame's context is destroyed, then the new document's created; - a removed frame's context is destroyed (descendants first, following the registry); - on enable, the contexts come newest first: Chrome lists them in that order. Each context has the frame's id in auxData, isDefault true, the origin of its document (inherited by about:blank and about:srcdoc, so the parent's, as in Chrome) and a uniqueId of its own, which executionContextDestroyed carries too. Runtime.disable and the end of the session stop following. The bookkeeping lives in domains/runtime/contexts.ts, which also resolves contextId / uniqueContextId to a frame for the commands that take one.
…s, quads and scrolling
DOM.getBoxModel and DOM.getContentQuads gave the quads of a node in a
same-origin iframe in the iframe's own viewport. Chrome gives them in the
top frame's viewport, so DevTools drew and measured them off by where the
iframe is.
core/geometry gains frameToTop(doc): the mapping from a frame document's
viewport to the top one, composed over every iframe holding it. Each step
is the iframe's content box (border plus padding), placed by its bounding
rectangle, or through localToViewport when the iframe or an ancestor is
transformed, so rotated and scaled iframes map like Chrome's quads. The
iframe's own scrolling is already in the inner viewport coordinates.
Width and height of the box model stay the layout size, like Chrome's.
Text node quads go through the same mapping.
DOM.scrollIntoViewIfNeeded followed scrollIntoView({block: 'nearest'}).
Chrome uses Blink's "center if needed" alignment for each scroller on its
own: no scroll when the node is visible (or covers the visible part), the
nearest edge when partly visible, centred when hidden; then the viewport,
then the documents of the iframes holding it. scrollIntoViewIfNeeded in
core/geometry does that, which also centres in the top document like
Chrome rather than scrolling to the nearest edge.
New frame-geometry conformance test (fixture test-support/
frame-geometry.html) compares box models, content quads and scroll
positions with Chrome's for a plain iframe with border and padding, a
rotated and scaled one, scrolled documents and scrollers, inline and text
nodes, and a nested iframe.
…ocation DOM.getNodeForLocation only looked at the top document, so a point on an iframe found the <iframe> element. Chrome's hit testing goes into the iframe's document and returns the node there, with that frame's id; on the iframe's border or padding (outside its document) it returns the iframe itself. core/geometry's hitTest does the same: the topmost accepted element at a point, and for a same-origin frame owner, the hit in its document at the point mapped through the owner's content box (inverse of the mapping frameToTop uses, so transformed iframes work too), falling back to the owner where the inner document has nothing. Measured against Chrome: its point is in the top document, not the top viewport (scrolling the top page does not move what a point hits, while scrolling inside an iframe does). The agent took viewport coordinates; it now subtracts the top window's scroll position first.
…se URL fetch() and XHR resolve a relative URL against the document's base URL, not its location: in a srcdoc iframe the location is about:srcdoc, which is no base at all, so the hooks failed to parse the URL and let every such request through unrecorded. They now use document.baseURI, which also follows a <base href> like the browser does. The Resource Timing navigation entry of an about:blank or about:srcdoc document is skipped: Chrome reports no request for those documents, only for http(s) ones.
…ame and loader While Network is enabled, the domain follows the frame registry and has the probe hook the network APIs of each same-origin iframe document: those there at Network.enable, and each new one as a frame is attached or navigates (a new document has a new global object, without the hooks). Following stops at Network.disable; hooks stay, like the top page's. requestWillBeSent and responseReceived carry the request's own frameId and loaderId, and requestWillBeSent the documentURL of the document that made it (about:srcdoc for a srcdoc iframe), as Chrome does. They are taken when the request starts, so a response that arrives after its frame navigated keeps the old loader. A document's first scripts run before the registry hears of it (on load): when a request's window shows a document other than the registered one, the registry is synced first, so the request gets the new loader rather than the previous document's. An iframe's document request is reported once, from the iframe's own Resource Timing navigation entry: frameId of the iframe, a loaderId of the document it loaded, documentURL its own URL, as in Chrome. The parent's Resource Timing entry for it (initiatorType iframe/frame, same origin) is dropped; it used to be reported as a Document of the top frame. WebSocket payloads from an iframe are its own realm's Blobs and ArrayBuffers: told apart by their class, not instanceof. getResponseBody and the other requestId commands are unchanged: records of every frame share the probe's lists.
Compared with Chrome on the same page, ids by how they relate (frame and loader ids differ between the two sides): - an iframe's document request: type Document, its own frame and a new loader, documentURL its own URL; fetch, XHR and sendBeacon from it carry that frame, loader and documentURL, in requestWillBeSent and responseReceived; - getResponseBody of an iframe's request; - a navigated iframe keeps its frame and gets a new loader, also for later requests; - requests of an iframe document's first scripts and its parser (fixture test-support/frame-network.html) carry the document's frame and loader; - a srcdoc iframe: documentURL about:srcdoc, and no request for the about:srcdoc document itself; - a WebSocket opened in an iframe.
Overlay.highlightNode draws from DOM's box model, which now places a node of an iframe in the top viewport, so the highlight lands where Chrome draws it; the layer stays in the top document. Scrolling inside an iframe moves its nodes but its scroll events do not reach the top window: the highlighter also listens on the windows of the iframes holding the highlighted element and redraws on the next frame, like it does for the top page. Inspect mode listened on the top window only, where input over an iframe arrives as nothing (it goes to the iframe's window), so hovering or clicking an iframe picked nothing inside it. Chrome's inspect mode picks the node under the mouse in same-process frames. The listeners are now added to the window of every registered frame, and to frames attached or navigated while inspecting (through FrameRegistry.onChange); the event target check works across realms (isNode/isElement instead of instanceof Element). The new overlay-iframe conformance test checks the drawn quads of a node in a rotated, scrolled iframe against Chrome's box model before and after scrolling the iframe, and that a click in inspect mode picks the same node in the iframe as Chrome's inspect mode, without the page seeing it.
…r own document A node in a same-origin iframe now gets its styles the way Chrome computes them: from the rules of its own document, in its own viewport. - The cascade looks up the candidates of the node's own tree: its shadow root, or else its document (no longer always the top one), so the top page's rules never match inside an iframe and the other way round. The implicit @scope root falls back to the node's own document element. - Media queries are evaluated in the window of the sheet they are in (realmOfSheet, moved to rules.ts): a sheet's media, @import media, active @media rules and the `active` flags of mediaList. An iframe narrower than the top page matches its own (max-width) rules, as in Chrome. CSSMedia.sourceURL is the sheet's source URL rather than the top page's location. - getComputedStyleForNode and getBackgroundColors use the node's window getComputedStyle, as do the background walk (color.ts) and container queries (core/container-query.ts), whose vw/vh units and rem now come from the node's own viewport and root element. - getMatchedStylesForNode syncs the frame registry when it does not know the node's document yet (no DOM listener running). - source.ts and cascade.ts check node types with core/realm.ts. Tested against Chrome: matched, inherited and inline styles of nodes in a src and a srcdoc iframe, @media/@container rules holding only in the narrow iframe, computed styles, inline styles and background colors.
…frame Runtime commands that name a context now run in that frame's realm, as in Chrome; without one, they run in the top page's as before: - evaluate takes contextId or uniqueContextId; console input runs through the frame window's own indirect eval, so its globals, `var`s and top-level await are the frame's. Eager evaluation (throwOnSideEffect) reads the frame's globals. - REPL-mode `let`/`const`/`class` are bound per frame document (a new document is a new realm), on the frame's window, with the frame's ReferenceError/TypeError/SyntaxError. - the console utilities ($, $$, $x, clear) work on the frame's document. - callFunctionOn runs in executionContextId / uniqueContextId if given, else in the realm of the object it is called on (Chrome's rule): a node's frame, a frame's window, or the frame whose Object.prototype is in the object's prototype chain. - compileScript and globalLexicalScopeNames take executionContextId; the lexical names of an iframe come from the scripts of its document. - exceptionDetails carry the frame's executionContextId, and errors of the frame's realm are recognised (stack cleanup, "Uncaught (in promise) Error: …"). - an unknown or gone context is answered with Chrome's error "Cannot find context with specified id". To keep runtime.ts small, the REPL bindings move to runtime/repl.ts and the global lexical names to runtime/lexical-names.ts, both taking the realm (window and its eval) instead of using the globals; their logic is otherwise unchanged.
… style sheet CSS.createStyleSheet ignored its frameId and always made one sheet in the top document. Like Chrome, it now creates the inspector <style> in the document of the frame asked for, each frame (document) reusing its own one, and with `force` a new one every call; an unknown frame fails with "Frame not found", Chrome's message. The registry keeps inspector sheets as a set plus the reused one per document, instead of a single inspectorSheet, and its id survives setStyleSheetText as before. The editing commands work on iframe nodes and sheets: inline style edits (setStyleTexts, setEffectivePropertyValueForNode) and setStyleSheetText check the element / <style> with core/realm.ts instead of instanceof of the top page, so an iframe's elements are no longer rejected. Existing tests passed a made-up frameId to createStyleSheet; they now pass the top frame's. New tests, against Chrome where both sides can act on the page: per-frame reuse and `force`, headers of inspector sheets in an iframe (frame, URL, isInline), the error for an unknown frame; and on our side addRule, setStyleTexts, setRuleSelector and an inline edit applying inside the iframe. Not changed: as for the top frame, our inspector sheets are announced without ownerNode and as mutable, where Chrome gives the <style> as owner and isMutable false.
Input.dispatchMouseEvent (and touch emulation) hit tested the top document only, so input over an iframe went to the <iframe> element with top-page coordinates, and keys went to the top document's activeElement (the iframe itself when something inside it had focus). Mouse and touch input now follow core/geometry's hitPath: the element in each document from the top one in, into same-origin iframes at the point mapped through the iframe's content box. Events go to the innermost element, with clientX/Y in its document's viewport (pageX/Y from that window's scroll), view set to its window, and are built with that realm's MouseEvent, PointerEvent, WheelEvent, Touch and TouchEvent, so the iframe's own instanceof checks hold. Measured against Chrome: - Hover is tracked per document. Entering an iframe, the top document gets the out/over pair for the <iframe> element, then the iframe's document the over for its element; leaving, the inner document gets its out first (with the point mapped into it, possibly negative), then the top document's pair. Within a document Chrome fires pointerout, pointerover, mouseout, mouseover in that order; the agent fired each pointer event with its mouse event, now it follows Chrome. - A tap's compatibility mouseover moves the hover the same way, mouse events only. - Focusing on press, blurring, the default editing actions and insertText use the target's own document; keys and text go to the focused element of the focused frame (the activeElement of the top document, then of the document of each focused iframe). Chrome's window blur/focus pair on moving focus into the iframe comes from focus() itself. - The wheel scrolls the scrollers of the iframe's document, then its viewport, and passes on to the parent document once a frame cannot scroll further. isEditable, the scroll checks and the focus handling no longer use instanceof or the global document/window (core/realm.ts). The input-iframe conformance test compares the events both documents see (type, target, point, view and realm), the field value, focus and scroll positions with Chrome's for moving in and out of an iframe and clicking there, typing and insertText into its field, a tap with touch emulation and the wheel over its scroller and document.
CSS.forcePseudoState copies the rules using a forced pseudo-class into a constructed companion sheet adopted by each tree. For the tree of an iframe that sheet has to come from the iframe's realm (a tree only adopts sheets of its own realm), and its adoptedStyleSheets accessors are those of the iframe's Document/ShadowRoot prototypes. Both now come from the tree's window. The media queries the companion depends on (whether a rule's @media holds, and the queries watched for layer order changes) are evaluated in the window of the tree being built and keyed by that window, so a rule in an @media holding only in a narrow iframe is replayed there. Like Chrome, which forgets a removed node's forced states, elements of a document left without a window (its iframe removed, or navigated away) are let go: removing the iframe does not disconnect them, so being connected is not enough. Tested against Chrome (each side forcing in turn): :hover and :focus on an iframe element, with a :focus rule inside a narrow-only @media of the iframe, matched rules and computed styles while forced and after.
…y work core/geometry.ts and domains/input/input.ts no longer use instanceof, the global document/window or getComputedStyle (36 and 28 uses before); domains/overlay/overlay.ts is down from 16 to 8, the rest being the overlay layer, which belongs to the top document on purpose. Written by node scripts/check-realms.ts --update.
…al window SheetRegistry typed its fetch as `typeof window.fetch`, two of the remaining uses of the top page's realm that scripts/check-realms.ts counts in the CSS domain. `typeof globalThis.fetch` is the same type. The counts are lowered accordingly; the one left in the CSS domain (style.ts parses declarations with a top-page <div>'s style, which does not depend on the realm) stays on purpose.
…dParams Chrome replies -32602 to a uniqueContextId that names no context (read off its raw reply); frameOf threw CdpError's default -32000. The conformance tests compared messages only, so they also pin the codes now, including callFunctionOn's target checks, which Chrome answers with -32602 as well.
withCommandLineApi's release callback reads and deletes the utilities on the frame's window before resolving `released`. A frame that committed a cross-origin navigation in between throws SecurityError there, so `released` never resolved and Runtime.evaluate or callFunctionOn hung. The cleanup now runs in try/finally: the utilities map is cleared and `released` resolves either way.
highlightMerged awaited the merged frame's box model and description, then drew unconditionally, so an Overlay.hideHighlight or a new highlight that arrived meanwhile was overdrawn by the stale one, which then stayed on the page. The highlighter now counts every highlight and hide; the merged highlight draws only if the count is unchanged after the await, so commands take effect in order as in Chrome. Adds a test sending highlightNode and hideHighlight together.
The set-up of both sides was made by the first test only, so a later test run alone, or after the first failed midway, failed on the missing set-up instead of its own behavior. The set-up is now made lazily, once per side, by whichever test asks first.
… search isFrameDocument repeated opaqueFrameOf's checks and called it, and emit then called opaqueFrameOf again, searching the page's frames twice for a sandboxed frame's document. childDocumentOf now returns both answers from a single search, and emit uses it once per record.
…drop mergedDocument's pierce The id-base helpers were inserted between NodeRegistry's JSDoc and the class, leaving the comment attached to idBase; they now come before it. The pierce argument of nodes.mergedDocument was never read (pierce is already folded into the depth passed), so it is removed from the type and the call.
…endNodeId DOM.pushNodesByBackendIdsToFrontend wrote each merged frame's answer back at backendNodeIds.indexOf(id), so a repeated id only filled its first position and the others stayed 0. The frame's ids are now grouped with their original index and written back by it, as Chrome resolves each position on its own.
…nd destroy its contexts A merged sandboxed iframe kept its namespace across documents (nsFor keyed by element), so a reloaded document's agent numbered its node ids and execution context ids from the same base again: old ids were routed to the new document, and the new context was announced with the id of the old one, which nobody had destroyed (the old agent unloads, and the page's Runtime only follows FrameRegistry frames). Chrome destroys a navigated subframe's context, then creates the next with a new id, and never reuses ids. The handshake now takes a fresh namespace for each document (nextNs). The router follows the contexts each frame's agent announces on a client's session; when that session goes because the link or session closed, it posts Runtime.executionContextDestroyed for them to clients with Runtime enabled (not on the client's own reset). The channel's message handler now parses each message once instead of up to three times. Tests cover the above plus the fan-out timeout and late answers, a page handler failing a fanned-out command, and the replay of the last params of each kept command in first-sent order.
…n argument The route table said an argument of the other side is unknown "as across processes in Chrome", the implementation that the sandboxed iframe "shares the page's process" in Chrome. The rule is V8's JavaScript world check, not about processes: both comments now say so.
… context The same-origin iframe test only checked that the iframe script's executionContextId differed from the top one, so a stale or foreign id passed. It now finds the iframe's default context in Runtime.executionContextCreated by frameId and requires scriptParsed to name it, on both sides. The sandboxed console test took the first consoleAPICalled after the evaluation, whatever logged it, and only compared the context flags between sides. It now matches the frame's own message and asserts it is in the frame's context. The exception is matched by context: ours reports it with a null exception, so its text cannot be matched.
…ble and navigate opaque frames by id withMergedUrls chains each opaque frame change on a queue that may wait up to twice URL_TIMEOUT for a merged frame's agent to tell its URL, then called the listener unconditionally. Page.disable only unsubscribed from new changes, so a navigation already queued was still reported on the disabled session. withMergedUrls now returns a stop function alongside the listener, and queued changes are dropped once Page is disabled or the session resets, as Chrome reports no Page event after disable. Page.navigate looked the frame up among same-origin frames only, so a sandboxed iframe's opaque frame, listed in the frame tree under the page's id as in Chrome, failed with 'No frame with given id found'. Chrome navigates it. The opaque frame is now found in the tree and its window's location set, which the parent may do cross-origin; the events match Chrome's (one frameNavigated, no src change). The sandboxed-frames conformance test gains both cases, and its weak spots are tightened: the own-navigation case asserts frameStoppedLoading was seen and frameNavigated came exactly once; the pushState case polls for its event instead of a fixed 300 ms sleep; the non-merged case turns auto-attach on for our side too and asserts no child target there.
…s in one history Edits of a merged frame's nodes were routed to its agent and recorded in its own history, while DOM.undo/redo/markUndoableState only touched the page's, so Ctrl+Z after editing inside a sandboxed iframe did nothing yet succeeded. Chrome keeps such a frame in the page's target with a single InspectorHistory (checked: page and frame edits undo and redo in the order they were made). merged-frames gains onRouted, called after a routed command succeeds. For DOM and CSS edits (and DOM.moveTo/copyTo sent by dom.ts) the DOM domain marks a step in the frame's agent and records in the page's history an action that asks that agent for DOM.undo/DOM.redo; DOM.undo/redo await those replays. Also: asks of merged.ts to frame agents now time out (FAN_OUT_TIMEOUT), so a frame that stops answering no longer hangs getDocument, describeNode, requestChildNodes or getNodeForLocation; a timed-out frame is treated as one that answered an error. A frame's cached root is dropped when its link closes, as namespaces are no longer reused. The quadsIn comment names its real fields.
…th Chrome Covers setAttributeValue/removeAttribute/setOuterHTML and removeNode events on the page's session and their node ids, pushNodesByBackendIdsToFrontend with a repeated frame id, pushNodeByPathToFrontend into the frame, DOM.disable then enable, and DOM.undo/redo across page and frame edits.
…ith Chrome Chrome destroys a reloading sandboxed iframe's execution context before creating the next document's, with an id not seen before. The new conformance test reloads the merged frame on both sides and compares that order and the new id; it fails on the code before the namespace fix.
…nks again With a fresh namespace per handshake, a document that links again without reloading (its parent pinged after linking to its own parent, or back from the back/forward cache) got another namespace. Its node ids and contexts are renumbered or reset with the session, but ids it already handed out and keeps for the document's life (backend node ids in NodeRegistry, stylesheet ids cached per sheet) stayed in the old namespace, which no link serves any more: commands carrying them went to the page's own handlers as unknown. A merged document now tells, in its next hellos, the namespace it was given; the parent gives it again only if it is the one that element's frame was given last (MergedFrames.nsFor(owner, previous)). A new document's fresh JS tells none and still gets a new namespace.
… its namespace A pinged sandboxed iframe links again with the same document and must keep its namespace, while its next document takes another; the test fails without the namespace told in the hello. The merge test no longer expects namespace 1: the page's first ping may cross the iframe's first hello, which then takes the next one.
Describe per-document namespaces and destroyed contexts, the shared undo history, the 1 s fan-out timeout, and that a sandboxed iframe with a src of another origin (same-site too) is refused. Add roadmap items for same-site sandboxed iframes and Debugger breakpoints.
…ndboxed iframe A sandboxed document reports the null origin, so the parent trusts it by the iframe's src only. After the iframe navigates or is redirected to another origin, a document there that speaks the handshake is merged as well. Document this limitation instead of tightening the handshake, which could not stop redirects and would break in-frame reloads.
…breakpoints row Breakpoint panels are not bundled, so resolving breakpoint locations has no user-visible effect; remove the roadmap row. Sandboxed iframes with a cross-origin src stay refused: trusting them rests on an unverifiable src and the cases are rare, so move them from the roadmap to the shelved list.
Measured: errors thrown by the sandboxed document's own scripts carry their exception object; only code run from the Console, evaluated through the probe script, is muted as cross-origin by the opaque document, leaving exception null.
… loaderId pushState in a merged sandboxed iframe already reports navigatedWithinDocument, and the frame URL is refreshed before any getFrameTree answer, so drop that limitation. The initial about:blank context and a new loaderId per navigation have no small, reliable fix; move them from the roadmap to the shelved list.
On Linux CI the sandboxed iframe's body starts at y 71, not the 75 measured on macOS, so the Chrome-side spot check keeps only the event, target and x; ours is still compared with Chrome in full. The Watch prompt could have a bounding box with no size and fail to be clicked, so wait until it has one.
The public API reports gained only the probe's data-frame-origins and data-parent-origins attributes and the Vite plugin's frameOrigins and parentOrigins options. Also note the CDP commands that now fail instead of succeeding, and the new internal relay methods that tie the probe and the remote server to the same version.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release 0.3.0, based on Chrome 156 (156.0.8078.80).
Unreleasedchangelog entries under## 0.3.0(both languages);node scripts/release.ts check 0.3.0passes.Verified: full test suite, typecheck and lint pass locally on dev/0.3.0 (the last commits only touch docs). Not checked by hand in Safari or on real devices.