Skip to content

Commit 37264fe

Browse files
committed
[Scroll Anchoring] Anchoring can cause pages to scroll to negative offsets
https://bugs.webkit.org/show_bug.cgi?id=308689 rdar://171221075 Reviewed by Tim Horton and Abrar Rahman Protyasha. Scroll anchoring adjustments are issued with `ScrollClamping::Clamped`, but the path through `AsyncScrollingCoordinator::requestScrollToPosition()` that applies the ScrollUpdate synchronously does not express clamping, so we need to do a manual clamp here, using `adjustScrollPositionWithinRange()` which is made virtual, and consults `allowsUnclampedScrollPosition` on ScrollView. This was seen in a (broken) scroll anchoring scenario on reddit, and resulted in missing content. Test: fast/scrolling/scroll-anchoring-constraints.html * LayoutTests/fast/dom/elementFromPoint-relative-to-viewport.html: Make it side scrollable. * LayoutTests/fast/scrolling/scroll-anchoring-constraints-expected.txt: Added. * LayoutTests/fast/scrolling/scroll-anchoring-constraints.html: Added. * LayoutTests/platform/ios/compositing/geometry/fixed-in-composited-expected.txt: * LayoutTests/platform/ios/compositing/geometry/horizontal-scroll-composited-expected.txt: * LayoutTests/platform/ios/imported/w3c/web-platform-tests/dom/events/scrolling/scrollend-event-fired-for-scrollIntoView_include=root-inline-end-block-end-behavior-auto-expected.txt: Removed. * Source/WebCore/page/scrolling/AsyncScrollingCoordinator.cpp: (WebCore::AsyncScrollingCoordinator::requestScrollToPosition): * Source/WebCore/platform/ScrollView.h: * Source/WebCore/platform/ScrollableArea.h: (WebCore::ScrollableArea::adjustScrollPositionWithinRange const): Canonical link: https://commits.webkit.org/308320@main
1 parent 586c879 commit 37264fe

File tree

9 files changed

+80
-8
lines changed

9 files changed

+80
-8
lines changed

LayoutTests/fast/dom/elementFromPoint-relative-to-viewport.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11

22
<script src="../../resources/js-test-pre.js"></script>
33
<style>
4+
body {
5+
width: 1200px;
6+
}
47
.test {
58
width: 100px;
69
height: 100px;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
PASS window.scrollY is 0
2+
PASS successfullyParsed is true
3+
4+
TEST COMPLETE
5+
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<style>
5+
body {
6+
height: 5000px;
7+
}
8+
9+
.above-top {
10+
margin-top: -2000px;
11+
height: 2500px;
12+
border: 1px solid black;
13+
}
14+
15+
#changer {
16+
height: 1800px;
17+
width: 100%;
18+
background-color: orange;
19+
}
20+
21+
.anchor {
22+
width: 100%;
23+
height: 300px;
24+
background-color: cyan;
25+
}
26+
27+
body.changed #changer {
28+
height: 100px;
29+
}
30+
</style>
31+
<script src="../../resources/js-test.js"></script>
32+
<script src="../../resources/ui-helper.js"></script>
33+
<script>
34+
jsTestIsAsync = true;
35+
36+
window.addEventListener('load', async () => {
37+
await UIHelper.delayFor(0);
38+
// Need to scroll to trigger anchoring.
39+
window.scrollTo(0, 10);
40+
41+
await UIHelper.renderingUpdate();
42+
document.body.classList.add('changed');
43+
44+
shouldBe('window.scrollY', '0');
45+
46+
window.scrollTo(0, 0);
47+
finishJSTest();
48+
}, false);
49+
</script>
50+
</head>
51+
<body>
52+
<div class="above-top">
53+
<div id="changer"></div>
54+
<div class="anchor"></div>
55+
</div>
56+
<div id="console"></div>
57+
</body>
58+
</html>

LayoutTests/platform/ios/compositing/geometry/fixed-in-composited-expected.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ layer at (0,0) size 60x60
1616
RenderBlock {DIV} at (0,0) size 60x60 [bgcolor=#0000FF33]
1717
layer at (95,145) size 50x50
1818
RenderBlock (positioned) zI: 1 {DIV} at (95,145) size 50x50
19-
layer at (70,70) size 100x100
19+
layer at (20,70) size 100x100
2020
RenderBlock (positioned) {DIV} at (20,20) size 100x100 [bgcolor=#008000]
2121
layer at (200,100) size 50x50
2222
RenderBlock (positioned) zI: 1 {DIV} at (200,100) size 50x50
@@ -28,4 +28,4 @@ layer at (400,100) size 100x100
2828
RenderBlock {DIV} at (0,0) size 100x100
2929
layer at (420,120) size 100x100
3030
RenderBlock (positioned) {DIV} at (20,20) size 100x100 [bgcolor=#008000]
31-
scrolled to 50,50
31+
scrolled to 0,50

LayoutTests/platform/ios/compositing/geometry/horizontal-scroll-composited-expected.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ layer at (0,0) size 800x822
55
RenderBody {BODY} at (8,8) size 600x806
66
layer at (8,8) size 1006x806
77
RenderBlock {DIV} at (0,0) size 1006x806 [border: (3px solid #FF0000)]
8-
scrolled to 300,0
8+
scrolled to 214,0

LayoutTests/platform/ios/imported/w3c/web-platform-tests/dom/events/scrolling/scrollend-event-fired-for-scrollIntoView_include=root-inline-end-block-end-behavior-auto-expected.txt

Lines changed: 0 additions & 3 deletions
This file was deleted.

Source/WebCore/page/scrolling/AsyncScrollingCoordinator.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,9 +398,13 @@ bool AsyncScrollingCoordinator::requestScrollToPosition(ScrollableArea& scrollab
398398
bool inProgrammaticScroll = scrollableArea.currentScrollType() == ScrollType::Programmatic;
399399

400400
if ((inProgrammaticScroll && options.animated == ScrollIsAnimated::No) || inBackForwardCache) {
401+
auto adjustedScrollPosition = scrollPosition;
402+
if (options.clamping == ScrollClamping::Clamped)
403+
adjustedScrollPosition = scrollableArea.adjustScrollPositionWithinRange(scrollPosition);
404+
401405
auto scrollUpdate = ScrollUpdate {
402406
.nodeID = *scrollingNodeID,
403-
.scrollPosition = scrollPosition,
407+
.scrollPosition = adjustedScrollPosition,
404408
.data = ScrollUpdateData {
405409
.updateType = ScrollUpdateType::PositionUpdate,
406410
.updateLayerPositionAction = ScrollingLayerPositionAction::Set,

Source/WebCore/platform/ScrollView.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ class ScrollView : public Widget, public ScrollableArea, public CanMakeCheckedPt
255255
ScrollPosition maximumScrollPosition() const override; // The maximum position we can be scrolled to.
256256

257257
// Adjust the passed in scroll position to keep it between the minimum and maximum positions.
258-
ScrollPosition adjustScrollPositionWithinRange(const ScrollPosition&) const;
258+
ScrollPosition adjustScrollPositionWithinRange(const ScrollPosition&) const override;
259259
int scrollX() const { return scrollPosition().x(); }
260260
int scrollY() const { return scrollPosition().y(); }
261261

Source/WebCore/platform/ScrollableArea.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,11 @@ class ScrollableArea : public CanMakeWeakPtr<ScrollableArea>, public AbstractCan
276276
WEBCORE_EXPORT virtual ScrollPosition minimumScrollPosition() const;
277277
WEBCORE_EXPORT virtual ScrollPosition maximumScrollPosition() const;
278278

279+
virtual ScrollPosition adjustScrollPositionWithinRange(const ScrollPosition& position) const
280+
{
281+
return constrainedScrollPosition(position);
282+
}
283+
279284
ScrollPosition constrainedScrollPosition(const ScrollPosition& position) const
280285
{
281286
return position.constrainedBetween(minimumScrollPosition(), maximumScrollPosition());

0 commit comments

Comments
 (0)