Branch: refs/heads/main
Home: https://github.com/WebKit/WebKit
Commit: 7799f34a1444dcbf7d2635b7cc591a70a2794c6d
https://github.com/WebKit/WebKit/commit/7799f34a1444dcbf7d2635b7cc591a70a2794c6d
Author: Matt Woodrow <[email protected]>
Date: 2026-09-24 (Thu, 24 Sep 2026)
Changed paths:
A
LayoutTests/scrollingcoordinator/scrolling-tree/overflow-node-reattached-after-ancestor-proxy-destroyed-expected.txt
A
LayoutTests/scrollingcoordinator/scrolling-tree/overflow-node-reattached-after-ancestor-proxy-destroyed.html
M Source/WebCore/page/scrolling/ScrollingTree.cpp
M Source/WebCore/rendering/LayerAncestorClippingStack.cpp
M Source/WebCore/rendering/LayerAncestorClippingStack.h
M Source/WebCore/rendering/RenderLayerBacking.cpp
M Source/WebCore/rendering/RenderLayerCompositor.cpp
M Source/WebCore/rendering/RenderLayerCompositor.h
M
Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.cpp
Log Message:
-----------
MESSAGE_CHECK in establishLayerTreeScrollingRelations because scrolling nodes
can be orphaned when an overflow scroll proxy node is destroyed.
https://bugs.webkit.org/show_bug.cgi?id=325098
rdar://184655581
Reviewed by Simon Fraser.
When a composited layer stops needing an ancestor clip,
LayerAncestorClippingStack
destroys its overflow scroll proxy nodes with unparentChildrenAndDestroyNode(),
which
moves their child nodes into the unparented set. Those nodes belong to
paint-order
descendants of the layer, and they are only reattached if the compositing
traversal
visits their layers again. It does not when a clean composited layer lies in
between:
that layer gets an empty update level, so updateBackingAndHierarchy() does not
descend
into it.
The orphaned node is then left out of every commit, while a node elsewhere in
the tree
can still refer to it: an overflow scroll proxy node refers to the overflow
scrolling
node of the scroller that clips its layer, and it is often parented outside the
destroyed subtree, because a stacking context escapes a non-stacking-context
scroller
in paint order.
detachScrollCoordinatedLayerWithRole() already marks the layers of the children
of the
node it destroys as needing a scrolling tree update, but the three
LayerAncestorClippingStack call sites did not. Route them through a new
RenderLayerCompositor::unparentChildrenAndDestroyScrollingNode() that does the
same, and
share the marking with detachScrollCoordinatedLayerWithRole(). That reattaches
them in
the same update: the dirty bit propagates to paint-order ancestors, and the
layer being
updated only decides whether to traverse its descendants after the
configuration update
that clears its clipping stack.
Overflow scroll proxy nodes were never added to m_scrollingNodeToLayerMap, even
though
detachScrollCoordinatedLayerWithRole() removes them from it, so the layer of an
orphaned
proxy node could not be marked. Register them when they are created, and remove
the
entry when the node is destroyed. There is no separate test for this: an
orphaned proxy
node's layer is clipped by a scroller inside its stacking context, and as far
as I can
tell some node on that scroller's paint-order path is always orphaned alongside
it and
is in the map, so marking that one already got the proxy node's layer visited.
Also stop killing the web process over a relation the UI process cannot resolve.
Neither a web process bug like this one nor stale state in the active node sets
is
a memory safety problem.
updateTreeFromStateNodeRecursive() could also leave stale entries in the active
node
sets. It bails out when a node's parent is not in m_nodeMap, and did a bare
m_nodeMap.remove() to do it. That strands the node: it stays in
m_nodeMapPerFrame, so a
later commit calls removeNode() for it, but removeNode() early-returns once the
node is
out of m_nodeMap, so removeFromActiveNodes() never runs for it. Worse,
commitStateBeforeChildren() still ran on the node afterwards, which adds proxy
nodes to
the active sets, and every descendant hit the same bailout. Route it through
removeNode()
instead, and skip the subtree rather than committing state into a node we just
removed;
the descendants stay in unvisitedNodes and are removed the same way when the
commit
finishes.
Test:
scrollingcoordinator/scrolling-tree/overflow-node-reattached-after-ancestor-proxy-destroyed.html
*
LayoutTests/scrollingcoordinator/scrolling-tree/overflow-node-reattached-after-ancestor-proxy-destroyed-expected.txt:
Added.
*
LayoutTests/scrollingcoordinator/scrolling-tree/overflow-node-reattached-after-ancestor-proxy-destroyed.html:
Added.
* Source/WebCore/page/scrolling/ScrollingTree.cpp:
(WebCore::ScrollingTree::updateTreeFromStateNodeRecursive):
* Source/WebCore/rendering/LayerAncestorClippingStack.cpp:
(WebCore::LayerAncestorClippingStack::clear):
(WebCore::LayerAncestorClippingStack::detachFromScrollingCoordinator):
(WebCore::LayerAncestorClippingStack::updateWithClipData):
* Source/WebCore/rendering/LayerAncestorClippingStack.h:
* Source/WebCore/rendering/RenderLayerBacking.cpp:
(WebCore::RenderLayerBacking::updateAncestorClippingStack):
(WebCore::RenderLayerBacking::ensureOverflowControlsHostLayerAncestorClippingStack):
(WebCore::RenderLayerBacking::updateAncestorClipping):
(WebCore::RenderLayerBacking::detachFromScrollingCoordinator):
* Source/WebCore/rendering/RenderLayerCompositor.cpp:
(WebCore::RenderLayerCompositor::setNeedsScrollingTreeUpdateForChildrenOfNode):
(WebCore::RenderLayerCompositor::unparentChildrenAndDestroyScrollingNode):
(WebCore::RenderLayerCompositor::detachScrollCoordinatedLayerWithRole):
(WebCore::RenderLayerCompositor::updateScrollingNodeForScrollingProxyRole):
* Source/WebCore/rendering/RenderLayerCompositor.h:
* Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.cpp:
(WebKit::RemoteScrollingCoordinatorProxy::establishLayerTreeScrollingRelations):
Canonical link: https://commits.webkit.org/321860@main
To unsubscribe from these emails, change your notification settings at
https://github.com/WebKit/WebKit/settings/notifications