commit f332ba95d3b0e36ea8ed118c22aae713be785c8d
parent b0768a1f51b4f61e102c24e2b695812e33f24457
Author: Hiroyuki Ikezoe <hikezoe.birchill@mozilla.com>
Date: Tue, 2 Dec 2025 02:03:30 +0000
Bug 1999429 - Respect block and inline options for visual scrollIntoView. r=layout-reviewers,dholbert
Differential Revision: https://phabricator.services.mozilla.com/D272107
Diffstat:
4 files changed, 92 insertions(+), 12 deletions(-)
diff --git a/gfx/layers/apz/test/mochitest/helper_scrollIntoView_block_center.html b/gfx/layers/apz/test/mochitest/helper_scrollIntoView_block_center.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width,initial-scale=1">
+ <title>Tests that Element.scrollIntoView respects block option for visual scroll</title>
+ <script src="apz_test_native_event_utils.js"></script>
+ <script src="apz_test_utils.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+</head>
+<style>
+body {
+ margin: 0px;
+ padding: 0px;
+}
+#target {
+ position: fixed;
+ width: 100%;
+ height: 10px;
+ top: 50%;
+ bottom: 50%;
+ background-color: green;
+}
+</style>
+<body>
+<div id="target"></div>
+<script>
+async function test() {
+ is(window.scrollY, 0, "The initial scroll offset should be 0");
+ is(visualViewport.scale, 2.0, "The document should get scaled by 2.0");
+ is(visualViewport.pageTop, 0, "The initial visual viewport pageTop should be 0");
+
+ const target = document.querySelector("#target");
+ const targetRect = target.getBoundingClientRect();
+
+ const scrollPromise =
+ new Promise(resolve => visualViewport.addEventListener("scroll", resolve));
+ target.scrollIntoView({ block: "center" });
+ await scrollPromise;
+
+ await promiseApzFlushedRepaints();
+
+ // Calculate the expected position.
+ const centerOfTarget = targetRect.y + targetRect.height * 0.5;
+ const expected = centerOfTarget - visualViewport.height * 0.5;
+
+ isfuzzy(visualViewport.offsetTop, expected, 1.0,
+ "The position:fixed element is aligned at the center of visual viewport");
+}
+
+SpecialPowers.getDOMWindowUtils(window).setResolutionAndScaleTo(2.0);
+waitUntilApzStable()
+.then(test)
+.then(subtestDone, subtestFailed);
+</script>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_group_scrollIntoView.html b/gfx/layers/apz/test/mochitest/test_group_scrollIntoView.html
@@ -31,6 +31,10 @@ var subtests = [
"prefs": [...prefs,
["layout.scroll_fixed_content_into_view_visually", true]]
},
+ {"file": "helper_scrollIntoView_block_center.html",
+ "prefs": [...prefs,
+ ["layout.scroll_fixed_content_into_view_visually", true]]
+ },
];
if (isApzEnabled()) {
diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp
@@ -3741,9 +3741,10 @@ static bool NeedToVisuallyScroll(const nsSize& aLayoutViewportSize,
return true;
}
-void PresShell::ScrollFrameIntoVisualViewport(Maybe<nsPoint>& aDestination,
- const nsRect& aPositionFixedRect,
- ScrollFlags aScrollFlags) {
+void PresShell::ScrollFrameIntoVisualViewport(
+ Maybe<nsPoint>& aDestination, const nsRect& aPositionFixedRect,
+ const nsIFrame* aPositionFixedFrame, ScrollAxis aVertical,
+ ScrollAxis aHorizontal, ScrollFlags aScrollFlags) {
PresShell* root = GetRootPresShell();
if (!root) {
return;
@@ -3760,6 +3761,7 @@ void PresShell::ScrollFrameIntoVisualViewport(Maybe<nsPoint>& aDestination,
}
if (!aDestination) {
+ MOZ_ASSERT(aPositionFixedFrame);
// If we have in the top level content document but we didn't reach to
// the root scroll container in the frame tree walking up loop in
// ScrollFrameIntoView, it means the target element is inside a
@@ -3802,10 +3804,23 @@ void PresShell::ScrollFrameIntoVisualViewport(Maybe<nsPoint>& aDestination,
// position because the position:fixed origin (0, 0) is the layout scroll
// position. Otherwise if we've already scrolled, this scrollIntoView
// operation will jump back to near (0, 0) position.
- // Bug 1947470: We need to calculate the destination with `WhereToScroll`
- // options.
- const nsPoint layoutOffset = rootScrollContainer->GetScrollPosition();
- aDestination = Some(aPositionFixedRect.TopLeft() + layoutOffset);
+ nsPoint layoutOffset = rootScrollContainer->GetScrollPosition();
+
+ const nsRect visibleRect(layoutOffset, visualViewportSize);
+ nscoord unusedRangeMinOutparam;
+ nscoord unusedRangeMaxOutparam;
+ nscoord x = ComputeWhereToScroll(
+ aHorizontal.mWhereToScroll, layoutOffset.x, aPositionFixedRect.x,
+ aPositionFixedRect.XMost(), visibleRect.x, visibleRect.XMost(),
+ &unusedRangeMinOutparam, &unusedRangeMaxOutparam);
+ nscoord y = ComputeWhereToScroll(
+ aVertical.mWhereToScroll, layoutOffset.y, aPositionFixedRect.y,
+ aPositionFixedRect.YMost(), visibleRect.y, visibleRect.YMost(),
+ &unusedRangeMinOutparam, &unusedRangeMaxOutparam);
+
+ layoutOffset.x += x;
+ layoutOffset.y += y;
+ aDestination = Some(layoutOffset);
}
// NOTE: It seems chrome doesn't respect the root element's
@@ -3888,7 +3903,7 @@ bool PresShell::ScrollFrameIntoView(
nsIFrame* container = aTargetFrame;
- bool inPositionFixedSubtree = false;
+ const nsIFrame* positionFixedFrame = nullptr;
auto isPositionFixed = [&](const nsIFrame* aFrame) -> bool {
return aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
nsLayoutUtils::IsReallyFixedPos(aFrame);
@@ -3901,7 +3916,7 @@ bool PresShell::ScrollFrameIntoView(
MaybeSkipPaddingSides(aTargetFrame);
while (nsIFrame* parent = container->GetParent()) {
if (isPositionFixed(container)) {
- inPositionFixedSubtree = true;
+ positionFixedFrame = container;
}
container = parent;
if (container->IsScrollContainerOrSubclass()) {
@@ -3947,7 +3962,7 @@ bool PresShell::ScrollFrameIntoView(
// keeping rect relative to container
do {
if (isPositionFixed(container)) {
- inPositionFixedSubtree = true;
+ positionFixedFrame = container;
}
if (ScrollContainerFrame* sf = do_QueryFrame(container)) {
@@ -4029,11 +4044,12 @@ bool PresShell::ScrollFrameIntoView(
// scroll the rect into view visually, and that may require scrolling
// the visual viewport in scenarios where there is not enough layout
// scroll range.
- if (!rootScrollDestination && !inPositionFixedSubtree) {
+ if (!rootScrollDestination && !positionFixedFrame) {
return didScroll;
}
- ScrollFrameIntoVisualViewport(rootScrollDestination, rect, aScrollFlags);
+ ScrollFrameIntoVisualViewport(rootScrollDestination, rect, positionFixedFrame,
+ aVertical, aHorizontal, aScrollFlags);
return didScroll;
}
diff --git a/layout/base/PresShell.h b/layout/base/PresShell.h
@@ -407,6 +407,9 @@ class PresShell final : public nsStubDocumentObserver,
void ScrollFrameIntoVisualViewport(Maybe<nsPoint>& aDestination,
const nsRect& aPositionFixedRect,
+ const nsIFrame* aPositionFixedFrame,
+ ScrollAxis aVertical,
+ ScrollAxis aHorizontal,
ScrollFlags aScrollFlags);
public: