← WebKit — 2026-W35
364b06df0efb23bab2ae49198c6b6df89c043172 marketwatch.com: 'Subscribe now' drop-down does not render anything
severity low
class LogicError
confidence 0.25
signal score 8.56
Source/WebCore/dom
Primitive: hitTest could operate on a render tree torn down during layout of display:none subframes
Triage note: Implements lazy render-tree build/teardown for display:none iframe content documents and adds renderTreeState()!=Built guards in Document::hitTest before and after updateLayout (plus enqueuePaintTimingEntryIfNeeded). The post-layout re-check guards against using a torn-down renderView, but this is predominantly a rendering-lifecycle feature with no demonstrated exploitable bug.
Triage note: Implements lazy render-tree build/teardown for display:none iframe content documents and adds renderTreeState()!=Built guards in Document::hitTest before and after updateLayout (plus enqueuePaintTimingEntryIfNeeded). The post-layout re-check guards against using a torn-down renderView, but this is predominantly a rendering-lifecycle feature with no demonstrated exploitable bug.
Structural signals: smartptr_member_added (+6.47), null_guard (+2.59), msg_noise (-2.5), area:Source/WebCore/dom (+2.0)
Commit message
marketwatch.com: "Subscribe now" drop-down does not render anything
https://bugs.webkit.org/show_bug.cgi?id=321146
<rdar://181639379>
Reviewed by Antti Koivisto.
<div style="display: none; width: 300px">
<iframe srcdoc="<div style='width: 250px; height: 169px'></div>"></iframe>
</div>
The box inside should measure 0x0 while the ancestor is display:none. Instead it was 250x169. We build a render tree for the content of such a
frame so script can ask for geometry, but nothing gives that content document's view a size, so it lays out against a 0x0 viewport and the
geometry answers describe nothing. Build no render tree at all under a frame whose owner element generates no box, and build it back when the
owner does.
Tests: imported/w3c/web-platform-tests/css/css-animations/display-none-ancestor-does-not-cancel-subframe-animations.html
imported/w3c/web-platform-tests/css/cssom/computed-style-in-subframe-reflects-owner-document.html
imported/w3c/web-platform-tests/html/rendering/replaced-elements/embedded-content/iframe-display-animated-to-none-content-not-laid-out.html
imported/w3c/web-platform-tests/html/rendering/replaced-elements/embedded-content/iframe-display-none-content-not-laid-out.html
imported/w3c/web-platform-tests/html/rendering/replaced-elements/embedded-content/iframe-display-none-content-not-laid-out-while-parent-restyles.html
imported/w3c/web-platform-tests/html/rendering/replaced-elements/embedded-content/iframe-display-none-preserves-content-scroll-offset.tentative.html
imported/w3c/web-platform-tests/html/semantics/embedded-content/the-iframe-element/iframe-display-none-content-stays-editable.html
imported/w3c/web-platform-tests/html/semantics/embedded-content/the-iframe-element/iframe-display-none-still-loads-images.html
* Source/WebCore/dom/Document.h:
* Source/WebCore/dom/Document.cpp:
(WebCore::Document::ownerElementGeneratesBox const):
The question, answered from the owner element having a renderer rather than from its
display value, since it also generates no box inside a display:none subtree and when a shadow root leaves it unslotted.
An owner element in a document with no render tree has no renderer whatever its display is,
so that case recurses, which is what keeps nested unrendered frames hidden.
A missing renderer settles nothing until the owner element itself has been styled, since every subframe is created
while the parser is still building the tree around its owner element. Without that an iframe's initial empty
document never gets a tree, so it never resolves style and never reports what it should. Asked of the owner
element rather than of its document, so an unrelated restyle elsewhere in the parent does not make a settled
frame look undecided and build a tree only to take it down again.
(WebCore::Document::updateRenderTreeForOwnerElementBox):
Builds or tears down this document's render tree to match. Called from the owner
document's style update rather than from the display change itself: an owner element
losing its renderer may be having it replaced rather than
removed, and nothing at the time of the change can tell those apart, so the tree is settled from state afterwards. That also makes it idempotent.
(WebCore::Document::updateRenderTreesForDescendantFrames):
Runs the above over every descendant frame once this document has resolved style.
Collects the documents first, because tearing a tree down can detach a frame mid-walk.
(WebCore::Document::createRenderTree):
Withholds the tree for a frame whose owner generates no box. Needed here as well because a subframe's
first document arrives through setDocument(), before any style update of the owner.
(WebCore::Document::destroyRenderTree):
Takes whether the document is going away. A frame becoming unrendered is still here, so its renderers
come down with RendererUpdate rather than a Full teardown, keeping animations and hover state.
(WebCore::Document::updateStyleIfNeeded):
(WebCore::Document::enqueuePaintTimingEntryIfNeeded):
A frame that stops being rendered keeps a view that is still visually non-empty from
before, so ContentfulPaintChecker was reached with no RenderView.
(WebCore::Document::hitTest):
Checked for a render tree, flushed layout - which resolves owner documents and can take that tree away - then
used it. Re-checks after the flush.
* Source/WebCore/html/parser/HTMLResourcePreloader.cpp:
(WebCore::HTMLResourcePreloader::preload): Asserted that a document with a frame has a render view, which a
frame whose owner element generates no box no longer does. Nothing below it needs one: the media query
evaluator checks view() and documentElement(), and takes renderView() as a pointer that may be null.
* Source/WebCore/page/LocalFrame.cpp:
(WebCore::LocalFrame::setPrinting):
Printing a display:none frame still works, so the tree is reconciled when printing starts and stops.
* Source/WebCore/rendering/updating/RenderTreeUpdater.cpp:
(WebCore::RenderTreeUpdater::tearDownRenderersForDisplayNoneFrame): TeardownType is private, so the RendererUpdate teardown gets a named entry
point like the others.
* Source/WebCore/rendering/updating/RenderTreeUpdater.h:
* Source/WebCore/style/StyleExtractor.cpp:
(WebCore::Style::Extractor::updateStyleIfNeededForProperty): A child's tree is decided by the parent's style update, and this only flushed the
child, so a resolved value read after the owner was shown came back as the computed value.
* LayoutTests/fast/dynamic/display-none-iframe-async-layout-expected.html: Updated for the new behavior.
Canonical link: https://commits.webkit.org/319910@main
Before / after
Loading diff…
On This Page