Skip to content

Commit 2f33fa5

Browse files
committed
A fixed header inside overflow scroll with a transformed ancestor stutters on scrolling (affects Libby app)
https://bugs.webkit.org/show_bug.cgi?id=250652 rdar://104095908 Reviewed by Alan Baradlay. The Libby app has content which uses a stacking-context overflow:scroll with a position:fixed descendant, and a CSS transform on an ancestor of the scroller. In this situation, we treat the position:fixed as if it has position:absolute, but still need to use the correct containing block for it (which is the enclosing transformed box). Two fixes are required here. First, `RenderLayerCompositor::computeCoordinatedPositioningForLayer()` needs to check if the position:fixed layer actually has fixed behavior (i.e. no transformed ancestor) before the early return. Second, the `traverseAncestorLayers()` helper only handled position:absolute containing block logic; we need to fix it to also compute the correct containing block for position:fixed (which allows it to find transformed ancestors of fixed). Fixing `traverseAncestorLayers()` to have the correct containingBlock behavior for fixed layers revealed a surprising behavior, which is that the deprecated CSS `clip` property on a position:absolute element clips position:fixed descendants, which is odd because it's different from how overflow works (w3c/csswg-drafts#8336). So RenderLayerCompositor::computeAncestorClippingStack() needs some special case code to detect this case. This is tested by imported/blink/fast/css/fixed-overlaps-absolute-in-clip.html. * LayoutTests/platform/ios-wk2/scrollingcoordinator/scrolling-tree/fixed-inside-stacking-overflow-inside-transformed-expected.txt: Added. * LayoutTests/scrollingcoordinator/scrolling-tree/fixed-inside-stacking-overflow-inside-transformed-expected.txt: Added. * LayoutTests/scrollingcoordinator/scrolling-tree/fixed-inside-stacking-overflow-inside-transformed.html: Added. * Source/WebCore/rendering/RenderLayerCompositor.cpp: (WebCore::traverseAncestorLayers): (WebCore::RenderLayerCompositor::computeAncestorClippingStack const): (WebCore::RenderLayerCompositor::computeCoordinatedPositioningForLayer const): Canonical link: https://commits.webkit.org/259175@main
1 parent 485c3b2 commit 2f33fa5

File tree

4 files changed

+183
-7
lines changed

4 files changed

+183
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
Fixed inside scroller
2+
3+
(Frame scrolling node
4+
(scrollable area size 800 600)
5+
(contents size 800 5021)
6+
(scrollable area parameters
7+
(horizontal scroll elasticity 1)
8+
(vertical scroll elasticity 1)
9+
(horizontal scrollbar mode 0)
10+
(vertical scrollbar mode 0))
11+
(layout viewport at (0,0) size 800x600)
12+
(min layout viewport origin (0,0))
13+
(max layout viewport origin (0,4421))
14+
(behavior for fixed 1)
15+
(children 1
16+
(Overflow scrolling node
17+
(scroll position 0 50)
18+
(scrollable area size 440 440)
19+
(contents size 440 2040)
20+
(requested scroll position 0 50)
21+
(requested scroll position represents programmatic scroll 1)
22+
(scrollable area parameters
23+
(horizontal scroll elasticity 1)
24+
(vertical scroll elasticity 1)
25+
(horizontal scrollbar mode 0)
26+
(vertical scrollbar mode 0)
27+
(allows vertical scrolling 1))
28+
(children 1
29+
(Positioned node
30+
(layout constraints
31+
(layer-position-at-last-layout (10,160)))
32+
(related overflow nodes 1)
33+
)
34+
)
35+
)
36+
)
37+
)
38+
39+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Fixed inside scroller
2+
3+
(Frame scrolling node
4+
(scrollable area size 785 600)
5+
(contents size 785 5021)
6+
(scrollable area parameters
7+
(horizontal scroll elasticity 2)
8+
(vertical scroll elasticity 2)
9+
(horizontal scrollbar mode 0)
10+
(vertical scrollbar mode 0)
11+
(allows vertical scrolling 1))
12+
(layout viewport at (0,0) size 785x600)
13+
(min layout viewport origin (0,0))
14+
(max layout viewport origin (0,4421))
15+
(behavior for fixed 1)
16+
(children 1
17+
(Overflow scrolling node
18+
(scroll position 0 50)
19+
(scrollable area size 425 425)
20+
(contents size 425 1965)
21+
(requested scroll position 0 50)
22+
(requested scroll position represents programmatic scroll 1)
23+
(scrollable area parameters
24+
(horizontal scroll elasticity 0)
25+
(vertical scroll elasticity 0)
26+
(horizontal scrollbar mode 0)
27+
(vertical scrollbar mode 0)
28+
(allows vertical scrolling 1))
29+
(children 1
30+
(Positioned node
31+
(layout constraints
32+
(layer-position-at-last-layout (10,160)))
33+
(related overflow nodes 1)
34+
)
35+
)
36+
)
37+
)
38+
)
39+
40+
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<!DOCTYPE html> <!-- webkit-test-runner [ AsyncOverflowScrollingEnabled=true ] -->
2+
<html>
3+
<head>
4+
<style>
5+
body {
6+
height: 5000px;
7+
}
8+
9+
.container {
10+
position: absolute;
11+
margin: 20px;
12+
transform: translateX(0);
13+
padding: 10px;
14+
border: 1px solid gray;
15+
}
16+
17+
.scroller {
18+
position: relative;
19+
z-index: 0;
20+
margin: 20px;
21+
width: 400px;
22+
height: 400px;
23+
overflow: scroll;
24+
padding: 20px;
25+
border: 10px solid gray;
26+
}
27+
28+
.inner {
29+
height: 500%;
30+
}
31+
32+
.inner-fixed {
33+
position: fixed;
34+
top: 150px;
35+
left: 50px;
36+
width: 400px;
37+
height: 50px;
38+
background-color: orange;
39+
}
40+
41+
.box {
42+
width: 100px;
43+
height: 100px;
44+
background-color: blue;
45+
}
46+
</style>
47+
<script src="../../resources/ui-helper.js"></script>
48+
<script>
49+
if (window.testRunner) {
50+
testRunner.dumpAsText();
51+
testRunner.waitUntilDone();
52+
}
53+
54+
window.addEventListener('load', async () => {
55+
const scroller = document.getElementsByClassName('scroller')[0];
56+
scroller.scrollTo(0, 50);
57+
await UIHelper.renderingUpdate();
58+
if (window.internals)
59+
document.getElementById('scrollingTree').innerText = window.internals.scrollingStateTreeAsText() + "\n";
60+
61+
if (window.testRunner)
62+
testRunner.notifyDone();
63+
}, false);
64+
</script>
65+
</head>
66+
<body>
67+
<div class="container">
68+
<div class="scroller">
69+
<div class="inner"></div>
70+
<div class="inner-fixed">
71+
Fixed inside scroller
72+
</div>
73+
</div>
74+
</div>
75+
<pre id="scrollingTree"></pre>
76+
</body>
77+
</html>

Source/WebCore/rendering/RenderLayerCompositor.cpp

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2060,24 +2060,34 @@ void RenderLayerCompositor::computeExtent(const LayerOverlapMap& overlapMap, con
20602060

20612061
enum class AncestorTraversal { Continue, Stop };
20622062

2063-
// This is a simplified version of containing block walking that only handles absolute position.
2063+
// This is a simplified version of containing block walking that only handles absolute and fixed position.
20642064
template <typename Function>
20652065
static AncestorTraversal traverseAncestorLayers(const RenderLayer& layer, Function&& function)
20662066
{
2067-
bool containingBlockCanSkipLayers = layer.renderer().isAbsolutelyPositioned();
2067+
auto positioningBehavior = layer.renderer().style().position();
20682068
RenderLayer* nextPaintOrderParent = layer.paintOrderParent();
2069-
2069+
20702070
for (const auto* ancestorLayer = layer.parent(); ancestorLayer; ancestorLayer = ancestorLayer->parent()) {
20712071
bool inContainingBlockChain = true;
20722072

2073-
if (containingBlockCanSkipLayers)
2073+
switch (positioningBehavior) {
2074+
case PositionType::Static:
2075+
case PositionType::Relative:
2076+
case PositionType::Sticky:
2077+
break;
2078+
case PositionType::Absolute:
20742079
inContainingBlockChain = ancestorLayer->renderer().canContainAbsolutelyPositionedObjects();
2080+
break;
2081+
case PositionType::Fixed:
2082+
inContainingBlockChain = ancestorLayer->renderer().canContainFixedPositionObjects();
2083+
break;
2084+
}
20752085

20762086
if (function(*ancestorLayer, inContainingBlockChain, ancestorLayer == nextPaintOrderParent) == AncestorTraversal::Stop)
20772087
return AncestorTraversal::Stop;
20782088

20792089
if (inContainingBlockChain)
2080-
containingBlockCanSkipLayers = ancestorLayer->renderer().isAbsolutelyPositioned();
2090+
positioningBehavior = ancestorLayer->renderer().style().position();
20812091

20822092
if (ancestorLayer == nextPaintOrderParent)
20832093
nextPaintOrderParent = ancestorLayer->paintOrderParent();
@@ -3001,6 +3011,9 @@ Vector<CompositedClipData> RenderLayerCompositor::computeAncestorClippingStack(c
30013011
newStack.insert(0, WTFMove(clipData));
30023012
};
30033013

3014+
// Surprisingly, the deprecated CSS "clip" property on abspos ancestors of fixedpos elements clips them <https://github.com/w3c/csswg-drafts/issues/8336>.
3015+
bool checkAbsoluteAncestorForClip = layer.renderer().isFixedPositioned();
3016+
30043017
traverseAncestorLayers(layer, [&](const RenderLayer& ancestorLayer, bool isContainingBlockChain, bool /*isPaintOrderAncestor*/) {
30053018
if (&ancestorLayer == compositingAncestor) {
30063019
bool canUseDescendantClip = canUseDescendantClippingLayer(ancestorLayer);
@@ -3012,7 +3025,14 @@ Vector<CompositedClipData> RenderLayerCompositor::computeAncestorClippingStack(c
30123025
return AncestorTraversal::Stop;
30133026
}
30143027

3015-
if (isContainingBlockChain && ancestorLayer.renderer().hasClipOrNonVisibleOverflow()) {
3028+
auto ancestorLayerMayClip = [&]() {
3029+
if (checkAbsoluteAncestorForClip && ancestorLayer.renderer().hasClip())
3030+
return true;
3031+
3032+
return isContainingBlockChain && ancestorLayer.renderer().hasClipOrNonVisibleOverflow();
3033+
};
3034+
3035+
if (ancestorLayerMayClip()) {
30163036
auto* box = ancestorLayer.renderBox();
30173037
if (!box)
30183038
return AncestorTraversal::Continue;
@@ -3616,7 +3636,7 @@ ScrollPositioningBehavior RenderLayerCompositor::computeCoordinatedPositioningFo
36163636
if (layer.isRenderViewLayer())
36173637
return ScrollPositioningBehavior::None;
36183638

3619-
if (layer.renderer().isFixedPositioned())
3639+
if (layer.renderer().isFixedPositioned() && layer.behavesAsFixed())
36203640
return ScrollPositioningBehavior::None;
36213641

36223642
if (!layer.hasCompositedScrollingAncestor())

0 commit comments

Comments
 (0)