Bug 1943865 - Scroll visually to position:fixed element in ScrollFrameIntoView if necessary. r=dlrobertson
In the case of position:fixed frame, walking up the frame tree doesn't reach to the root scroll container, thus we need to invoke ScrollToVisual outside the walking up the tree loop. This commit has two independent tests, a web platform test and a mochitest. Unfortunately the web platform test doesn't work on Firefox, since WebDriver (GeckoDriver) doesn't support touch action yet. It works on Chrome. What the mochitest does is mostly equivalent with the web platform test, but with nsIDOMWindowUtils.setResolutionAndScaleTo and zoomToFocusedInput. Differential Revision: https://phabricator.services.mozilla.com/D236061
This commit is contained in:
@@ -3506,6 +3506,26 @@ static WhereToScroll GetApplicableWhereToScroll(
|
||||
return aOriginal;
|
||||
}
|
||||
|
||||
static ScrollMode GetScrollModeForScrollIntoView(
|
||||
const ScrollContainerFrame* aScrollContainerFrame,
|
||||
ScrollFlags aScrollFlags) {
|
||||
ScrollMode scrollMode = ScrollMode::Instant;
|
||||
// Default to an instant scroll, but if the scroll behavior given is "auto"
|
||||
// or "smooth", use that as the specified behavior. If the user has disabled
|
||||
// smooth scrolls, a given mode of "auto" or "smooth" should not result in
|
||||
// a smooth scroll.
|
||||
ScrollBehavior behavior = ScrollBehavior::Instant;
|
||||
if (aScrollFlags & ScrollFlags::ScrollSmooth) {
|
||||
behavior = ScrollBehavior::Smooth;
|
||||
} else if (aScrollFlags & ScrollFlags::ScrollSmoothAuto) {
|
||||
behavior = ScrollBehavior::Auto;
|
||||
}
|
||||
if (aScrollContainerFrame->IsSmoothScroll(behavior)) {
|
||||
scrollMode = ScrollMode::SmoothMsd;
|
||||
}
|
||||
return scrollMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function takes a scroll container frame, a rect in the coordinate system
|
||||
* of the scrolled frame, and a desired percentage-based scroll
|
||||
@@ -3514,12 +3534,12 @@ static WhereToScroll GetApplicableWhereToScroll(
|
||||
*
|
||||
* This needs to work even if aRect has a width or height of zero.
|
||||
*/
|
||||
static void ScrollToShowRect(ScrollContainerFrame* aScrollContainerFrame,
|
||||
const nsIFrame* aScrollableFrame,
|
||||
const nsIFrame* aTarget, const nsRect& aRect,
|
||||
const Sides aScrollPaddingSkipSides,
|
||||
const nsMargin& aMargin, ScrollAxis aVertical,
|
||||
ScrollAxis aHorizontal, ScrollFlags aScrollFlags) {
|
||||
static Maybe<nsPoint> ScrollToShowRect(
|
||||
ScrollContainerFrame* aScrollContainerFrame,
|
||||
const nsIFrame* aScrollableFrame, const nsIFrame* aTarget,
|
||||
const nsRect& aRect, const Sides aScrollPaddingSkipSides,
|
||||
const nsMargin& aMargin, ScrollAxis aVertical, ScrollAxis aHorizontal,
|
||||
ScrollFlags aScrollFlags) {
|
||||
nsPoint scrollPt = aScrollContainerFrame->GetVisualViewportOffset();
|
||||
const nsPoint originalScrollPt = scrollPt;
|
||||
const nsRect visibleRect(scrollPt,
|
||||
@@ -3597,24 +3617,11 @@ static void ScrollToShowRect(ScrollContainerFrame* aScrollContainerFrame,
|
||||
// If we don't need to scroll, then don't try since it might cancel
|
||||
// a current smooth scroll operation.
|
||||
if (scrollPt == originalScrollPt) {
|
||||
return;
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
ScrollMode scrollMode = ScrollMode::Instant;
|
||||
// Default to an instant scroll, but if the scroll behavior given is "auto"
|
||||
// or "smooth", use that as the specified behavior. If the user has disabled
|
||||
// smooth scrolls, a given mode of "auto" or "smooth" should not result in
|
||||
// a smooth scroll.
|
||||
ScrollBehavior behavior = ScrollBehavior::Instant;
|
||||
if (aScrollFlags & ScrollFlags::ScrollSmooth) {
|
||||
behavior = ScrollBehavior::Smooth;
|
||||
} else if (aScrollFlags & ScrollFlags::ScrollSmoothAuto) {
|
||||
behavior = ScrollBehavior::Auto;
|
||||
}
|
||||
bool smoothScroll = aScrollContainerFrame->IsSmoothScroll(behavior);
|
||||
if (smoothScroll) {
|
||||
scrollMode = ScrollMode::SmoothMsd;
|
||||
}
|
||||
ScrollMode scrollMode =
|
||||
GetScrollModeForScrollIntoView(aScrollContainerFrame, aScrollFlags);
|
||||
nsIFrame* frame = do_QueryFrame(aScrollContainerFrame);
|
||||
AutoWeakFrame weakFrame(frame);
|
||||
aScrollContainerFrame->ScrollTo(scrollPt, scrollMode, &allowedRange,
|
||||
@@ -3622,19 +3629,7 @@ static void ScrollToShowRect(ScrollContainerFrame* aScrollContainerFrame,
|
||||
aScrollFlags & ScrollFlags::TriggeredByScript
|
||||
? ScrollTriggeredByScript::Yes
|
||||
: ScrollTriggeredByScript::No);
|
||||
if (!weakFrame.IsAlive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is the RCD-RSF, also call ScrollToVisual() since we want to
|
||||
// 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 (aScrollContainerFrame->IsRootScrollFrameOfDocument() &&
|
||||
frame->PresContext()->IsRootContentDocumentCrossProcess()) {
|
||||
frame->PresShell()->ScrollToVisual(scrollPt, FrameMetrics::eMainThread,
|
||||
scrollMode);
|
||||
}
|
||||
return Some(scrollPt);
|
||||
}
|
||||
|
||||
nsresult PresShell::ScrollContentIntoView(nsIContent* aContent,
|
||||
@@ -3767,6 +3762,51 @@ void PresShell::DoScrollContentIntoView() {
|
||||
data->mContentScrollHAxis, data->mContentToScrollToFlags);
|
||||
}
|
||||
|
||||
void PresShell::ScrollFrameIntoVisualViewport(Maybe<nsPoint>& aDestination,
|
||||
const nsRect& aPositionFixedRect,
|
||||
ScrollFlags aScrollFlags) {
|
||||
PresShell* root = GetRootPresShell();
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!root->GetPresContext()->IsRootContentDocumentCrossProcess()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ScrollContainerFrame* rootScrollContainer =
|
||||
root->GetRootScrollContainerFrame();
|
||||
if (!rootScrollContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aDestination) {
|
||||
// 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
|
||||
// position:fixed subtree.
|
||||
|
||||
const nsSize visualViewportSize =
|
||||
rootScrollContainer->GetVisualViewportSize();
|
||||
// If the position:fixed element is already inside the visual viewport, we
|
||||
// don't need to scroll visually.
|
||||
if (aPositionFixedRect.y >= 0 &&
|
||||
aPositionFixedRect.YMost() <= visualViewportSize.height &&
|
||||
aPositionFixedRect.x >= 0 &&
|
||||
aPositionFixedRect.XMost() <= visualViewportSize.width) {
|
||||
return;
|
||||
}
|
||||
|
||||
aDestination = Some(aPositionFixedRect.TopLeft());
|
||||
}
|
||||
|
||||
// NOTE: It seems chrome doesn't respect the root element's
|
||||
// scroll-behavior for visual scrolling.
|
||||
ScrollMode scrollMode =
|
||||
GetScrollModeForScrollIntoView(rootScrollContainer, aScrollFlags);
|
||||
root->ScrollToVisual(*aDestination, FrameMetrics::eMainThread, scrollMode);
|
||||
}
|
||||
|
||||
bool PresShell::ScrollFrameIntoView(
|
||||
nsIFrame* aTargetFrame, const Maybe<nsRect>& aKnownRectRelativeToTarget,
|
||||
ScrollAxis aVertical, ScrollAxis aHorizontal, ScrollFlags aScrollFlags) {
|
||||
@@ -3852,10 +3892,17 @@ bool PresShell::ScrollFrameIntoView(
|
||||
}();
|
||||
|
||||
bool didScroll = false;
|
||||
bool inPositionFixedSubtree = false;
|
||||
const nsIFrame* target = aTargetFrame;
|
||||
Maybe<nsPoint> rootScrollDestination;
|
||||
// Walk up the frame hierarchy scrolling the rect into view and
|
||||
// keeping rect relative to container
|
||||
do {
|
||||
if (container->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
|
||||
nsLayoutUtils::IsReallyFixedPos(container)) {
|
||||
inPositionFixedSubtree = true;
|
||||
}
|
||||
|
||||
if (ScrollContainerFrame* sf = do_QueryFrame(container)) {
|
||||
nsPoint oldPosition = sf->GetScrollPosition();
|
||||
nsRect targetRect = rect;
|
||||
@@ -3885,11 +3932,17 @@ bool PresShell::ScrollFrameIntoView(
|
||||
|
||||
{
|
||||
AutoWeakFrame wf(container);
|
||||
ScrollToShowRect(sf, container, target, targetRect, skipPaddingSides,
|
||||
scrollMargin, aVertical, aHorizontal, aScrollFlags);
|
||||
Maybe<nsPoint> destination = ScrollToShowRect(
|
||||
sf, container, target, targetRect, skipPaddingSides, scrollMargin,
|
||||
aVertical, aHorizontal, aScrollFlags);
|
||||
if (!wf.IsAlive()) {
|
||||
return didScroll;
|
||||
}
|
||||
|
||||
if (sf->IsRootScrollFrameOfDocument() &&
|
||||
sf->PresContext()->IsRootContentDocumentCrossProcess()) {
|
||||
rootScrollDestination = destination;
|
||||
}
|
||||
}
|
||||
|
||||
nsPoint newPosition = sf->LastScrollDestination();
|
||||
@@ -3947,6 +4000,17 @@ bool PresShell::ScrollFrameIntoView(
|
||||
container = parent;
|
||||
} while (container);
|
||||
|
||||
// If this is inside the top level content document process (and a direct
|
||||
// descendant of it), also call ScrollToVisual() since we want to
|
||||
// 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) {
|
||||
return didScroll;
|
||||
}
|
||||
|
||||
ScrollFrameIntoVisualViewport(rootScrollDestination, rect, aScrollFlags);
|
||||
|
||||
return didScroll;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user