Bug 1777649 - Honor scroll snap properties on ScrollFrameRectIntoView call. r=emilio

Differential Revision: https://phabricator.services.mozilla.com/D151335
This commit is contained in:
Hiroyuki Ikezoe 2022-07-31 22:51:56 +00:00
parent a313ef9201
commit 507bbaf6f5
7 changed files with 151 additions and 30 deletions

View File

@ -3493,6 +3493,21 @@ static nscoord ComputeWhereToScroll(WhereToScroll aWhereToScroll,
return resultCoord;
}
static WhereToScroll GetApplicableWhereToScroll(
StyleScrollSnapAlignKeyword aAlign, WhereToScroll aOriginal) {
switch (aAlign) {
case StyleScrollSnapAlignKeyword::None:
return aOriginal;
case StyleScrollSnapAlignKeyword::Start:
return kScrollToTop;
case StyleScrollSnapAlignKeyword::Center:
return kScrollToCenter;
case StyleScrollSnapAlignKeyword::End:
return kScrollToBottom;
}
return aOriginal;
}
/**
* This function takes a scrollable frame, a rect in the coordinate system
* of the scrolled frame, and a desired percentage-based scroll
@ -3502,9 +3517,9 @@ static nscoord ComputeWhereToScroll(WhereToScroll aWhereToScroll,
* This needs to work even if aRect has a width or height of zero.
*/
static void ScrollToShowRect(nsIScrollableFrame* aFrameAsScrollable,
const nsRect& aRect, const nsMargin& aMargin,
ScrollAxis aVertical, ScrollAxis aHorizontal,
ScrollFlags aScrollFlags) {
const nsIFrame* aTarget, const nsRect& aRect,
const nsMargin& aMargin, ScrollAxis aVertical,
ScrollAxis aHorizontal, ScrollFlags aScrollFlags) {
nsPoint scrollPt = aFrameAsScrollable->GetVisualViewportOffset();
const nsPoint originalScrollPt = scrollPt;
const nsRect visibleRect(scrollPt,
@ -3540,9 +3555,14 @@ static void ScrollToShowRect(nsIScrollableFrame* aFrameAsScrollable,
if (ComputeNeedToScroll(aVertical.mWhenToScroll, lineSize.height, aRect.y,
aRect.YMost(), visibleRect.y + padding.top,
visibleRect.YMost() - padding.bottom)) {
// If the scroll-snap-align on the frame is valid, we need to respect it.
WhereToScroll whereToScroll = GetApplicableWhereToScroll(
aFrameAsScrollable->GetScrollSnapAlignFor(aTarget).second,
aVertical.mWhereToScroll);
nscoord maxHeight;
scrollPt.y = ComputeWhereToScroll(
aVertical.mWhereToScroll, scrollPt.y, rectToScrollIntoView.y,
whereToScroll, scrollPt.y, rectToScrollIntoView.y,
rectToScrollIntoView.YMost(), visibleRect.y, visibleRect.YMost(),
&allowedRange.y, &maxHeight);
allowedRange.height = maxHeight - allowedRange.y;
@ -3556,9 +3576,14 @@ static void ScrollToShowRect(nsIScrollableFrame* aFrameAsScrollable,
if (ComputeNeedToScroll(aHorizontal.mWhenToScroll, lineSize.width, aRect.x,
aRect.XMost(), visibleRect.x + padding.left,
visibleRect.XMost() - padding.right)) {
// If the scroll-snap-align on the frame is valid, we need to respect it.
WhereToScroll whereToScroll = GetApplicableWhereToScroll(
aFrameAsScrollable->GetScrollSnapAlignFor(aTarget).first,
aHorizontal.mWhereToScroll);
nscoord maxWidth;
scrollPt.x = ComputeWhereToScroll(
aHorizontal.mWhereToScroll, scrollPt.x, rectToScrollIntoView.x,
whereToScroll, scrollPt.x, rectToScrollIntoView.x,
rectToScrollIntoView.XMost(), visibleRect.x, visibleRect.XMost(),
&allowedRange.x, &maxWidth);
allowedRange.width = maxWidth - allowedRange.x;
@ -3706,6 +3731,7 @@ void PresShell::DoScrollContentIntoView() {
// over all continuation frames below.
const nsMargin scrollMargin = GetScrollMargin(mContentToScrollTo, frame);
const nsIFrame* target = frame;
// This is a two-step process.
// Step 1: Find the bounds of the rect we want to scroll into view. For
// example, for an inline frame we may want to scroll in the whole
@ -3737,14 +3763,15 @@ void PresShell::DoScrollContentIntoView() {
ScrollFrameRectIntoView(container, frameBounds, scrollMargin,
data->mContentScrollVAxis, data->mContentScrollHAxis,
data->mContentToScrollToFlags);
data->mContentToScrollToFlags, target);
}
bool PresShell::ScrollFrameRectIntoView(nsIFrame* aFrame, const nsRect& aRect,
const nsMargin& aMargin,
ScrollAxis aVertical,
ScrollAxis aHorizontal,
ScrollFlags aScrollFlags) {
ScrollFlags aScrollFlags,
const nsIFrame* aTarget) {
if (aFrame->AncestorHidesContent()) {
return false;
}
@ -3753,6 +3780,7 @@ bool PresShell::ScrollFrameRectIntoView(nsIFrame* aFrame, const nsRect& aRect,
// This function needs to work even if rect has a width or height of 0.
nsRect rect = aRect;
nsIFrame* container = aFrame;
const nsIFrame* target = aTarget ? aTarget : aFrame;
// Walk up the frame hierarchy scrolling the rect into view and
// keeping rect relative to container
do {
@ -3786,8 +3814,8 @@ bool PresShell::ScrollFrameRectIntoView(nsIFrame* aFrame, const nsRect& aRect,
{
AutoWeakFrame wf(container);
ScrollToShowRect(sf, targetRect, aMargin, aVertical, aHorizontal,
aScrollFlags);
ScrollToShowRect(sf, target, targetRect, aMargin, aVertical,
aHorizontal, aScrollFlags);
if (!wf.IsAlive()) {
return didScroll;
}
@ -3806,6 +3834,10 @@ bool PresShell::ScrollFrameRectIntoView(nsIFrame* aFrame, const nsRect& aRect,
if (aScrollFlags & ScrollFlags::ScrollFirstAncestorOnly) {
break;
}
// This scroll container will be the next target element in the nearest
// ancestor scroll container.
target = container;
}
nsIFrame* parent;
if (container->IsTransformed()) {

View File

@ -591,27 +591,25 @@ class PresShell final : public nsStubDocumentObserver,
* the rect is empty.
* @param aVertical see ScrollContentIntoView and ScrollAxis
* @param aHorizontal see ScrollContentIntoView and ScrollAxis
* @param aScrollFlags if SCROLL_FIRST_ANCESTOR_ONLY is set, only the
* @param aScrollFlags if ScrollFirstAncestorOnly is set, only the
* nearest scrollable ancestor is scrolled, otherwise all
* scrollable ancestors may be scrolled if necessary
* if SCROLL_OVERFLOW_HIDDEN is set then we may scroll in a direction
* if ScrollOverflowHidden is set then we may scroll in a direction
* even if overflow:hidden is specified in that direction; otherwise
* we will not scroll in that direction when overflow:hidden is
* set for that direction
* If SCROLL_NO_PARENT_FRAMES is set then we only scroll
* If ScrollNoParentFrames is set then we only scroll
* nodes in this document, not in any parent documents which
* contain this document in a iframe or the like.
* If SCROLL_IGNORE_SCROLL_MARGIN_AND_PADDING is set we ignore scroll-margin
* value specified for |aFrame| and scroll-padding value for the scroll
* container. This option is typically used to locate poped-up frames into
* view.
* @param aTarget optional, specifying the targe frame where we want to scroll
* if it's different from aFrame.
* @return true if any scrolling happened, false if no scrolling happened
*/
MOZ_CAN_RUN_SCRIPT
bool ScrollFrameRectIntoView(nsIFrame* aFrame, const nsRect& aRect,
const nsMargin& aMargin, ScrollAxis aVertical,
ScrollAxis aHorizontal,
ScrollFlags aScrollFlags);
ScrollAxis aHorizontal, ScrollFlags aScrollFlags,
const nsIFrame* aTarget = nullptr);
/**
* Suppress notification of the frame manager that frames are

View File

@ -8054,6 +8054,77 @@ void ScrollFrameHelper::PostPendingResnap() {
mOuter->PresShell()->PostPendingScrollResnap(sf);
}
nsIScrollableFrame::PhysicalScrollSnapAlign
ScrollFrameHelper::GetScrollSnapAlignFor(const nsIFrame* aFrame) const {
StyleScrollSnapAlignKeyword alignForY = StyleScrollSnapAlignKeyword::None;
StyleScrollSnapAlignKeyword alignForX = StyleScrollSnapAlignKeyword::None;
nsIFrame* styleFrame = GetFrameForStyle();
if (!styleFrame) {
return {alignForX, alignForY};
}
if (styleFrame->StyleDisplay()->mScrollSnapType.strictness ==
StyleScrollSnapStrictness::None) {
return {alignForX, alignForY};
}
const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
if (styleDisplay->mScrollSnapAlign.inline_ ==
StyleScrollSnapAlignKeyword::None &&
styleDisplay->mScrollSnapAlign.block ==
StyleScrollSnapAlignKeyword::None) {
return {alignForX, alignForY};
}
nsSize snapAreaSize =
ScrollSnapUtils::GetSnapAreaFor(aFrame, mScrolledFrame, GetScrolledRect())
.Size();
const WritingMode writingMode =
ScrollSnapUtils::NeedsToRespectTargetWritingMode(snapAreaSize,
GetSnapportSize())
? aFrame->GetWritingMode()
: styleFrame->GetWritingMode();
switch (styleFrame->StyleDisplay()->mScrollSnapType.axis) {
case StyleScrollSnapAxis::X:
alignForX = writingMode.IsVertical()
? styleDisplay->mScrollSnapAlign.block
: styleDisplay->mScrollSnapAlign.inline_;
break;
case StyleScrollSnapAxis::Y:
alignForY = writingMode.IsVertical()
? styleDisplay->mScrollSnapAlign.inline_
: styleDisplay->mScrollSnapAlign.block;
break;
case StyleScrollSnapAxis::Block:
if (writingMode.IsVertical()) {
alignForX = styleDisplay->mScrollSnapAlign.block;
} else {
alignForY = styleDisplay->mScrollSnapAlign.block;
}
break;
case StyleScrollSnapAxis::Inline:
if (writingMode.IsVertical()) {
alignForY = styleDisplay->mScrollSnapAlign.inline_;
} else {
alignForX = styleDisplay->mScrollSnapAlign.inline_;
}
break;
case StyleScrollSnapAxis::Both:
if (writingMode.IsVertical()) {
alignForX = styleDisplay->mScrollSnapAlign.block;
alignForY = styleDisplay->mScrollSnapAlign.inline_;
} else {
alignForX = styleDisplay->mScrollSnapAlign.inline_;
alignForY = styleDisplay->mScrollSnapAlign.block;
}
break;
}
return {alignForX, alignForY};
}
bool ScrollFrameHelper::UsesOverlayScrollbars() const {
return mOuter->PresContext()->UseOverlayScrollbars();
}

View File

@ -37,6 +37,7 @@ class AutoContainsBlendModeCapturer;
namespace mozilla {
class PresShell;
struct ScrollReflowInput;
struct StyleScrollSnapAlign;
namespace layers {
class Layer;
class WebRenderLayerManager;
@ -494,6 +495,9 @@ class ScrollFrameHelper : public nsIReflowCallback {
void PostPendingResnapIfNeeded(const nsIFrame* aFrame);
void PostPendingResnap();
using PhysicalScrollSnapAlign = nsIScrollableFrame::PhysicalScrollSnapAlign;
PhysicalScrollSnapAlign GetScrollSnapAlignFor(const nsIFrame* aFrame) const;
static bool ShouldActivateAllScrollFrames();
nsRect RestrictToRootDisplayPort(const nsRect& aDisplayportBase);
bool DecideScrollableLayer(nsDisplayListBuilder* aBuilder,
@ -1319,6 +1323,11 @@ class nsHTMLScrollFrame : public nsContainerFrame,
mHelper.PostPendingResnapIfNeeded(aFrame);
}
void PostPendingResnap() final { mHelper.PostPendingResnap(); }
using PhysicalScrollSnapAlign = nsIScrollableFrame::PhysicalScrollSnapAlign;
PhysicalScrollSnapAlign GetScrollSnapAlignFor(
const nsIFrame* aFrame) const final {
return mHelper.GetScrollSnapAlignFor(aFrame);
}
bool DragScroll(mozilla::WidgetEvent* aEvent) final {
return mHelper.DragScroll(aEvent);
@ -1806,6 +1815,11 @@ class nsXULScrollFrame final : public nsBoxFrame,
mHelper.PostPendingResnapIfNeeded(aFrame);
}
void PostPendingResnap() final { mHelper.PostPendingResnap(); }
using PhysicalScrollSnapAlign = nsIScrollableFrame::PhysicalScrollSnapAlign;
PhysicalScrollSnapAlign GetScrollSnapAlignFor(
const nsIFrame* aFrame) const final {
return mHelper.GetScrollSnapAlignFor(aFrame);
}
bool DragScroll(mozilla::WidgetEvent* aEvent) final {
return mHelper.DragScroll(aEvent);

View File

@ -36,6 +36,7 @@ class nsIContent;
namespace mozilla {
class DisplayItemClip;
class nsDisplayListBuilder;
enum class StyleScrollSnapAlignKeyword : uint8_t;
namespace layers {
struct ScrollMetadata;
@ -54,11 +55,14 @@ class ScrollAnchorContainer;
*/
class nsIScrollableFrame : public nsIScrollbarMediator {
public:
typedef mozilla::CSSIntPoint CSSIntPoint;
typedef mozilla::layers::ScrollSnapInfo ScrollSnapInfo;
typedef mozilla::layout::ScrollAnchorContainer ScrollAnchorContainer;
typedef mozilla::ScrollMode ScrollMode;
typedef mozilla::ScrollOrigin ScrollOrigin;
using CSSIntPoint = mozilla::CSSIntPoint;
using ScrollSnapInfo = mozilla::layers::ScrollSnapInfo;
using ScrollAnchorContainer = mozilla::layout::ScrollAnchorContainer;
using ScrollMode = mozilla::ScrollMode;
using ScrollOrigin = mozilla::ScrollOrigin;
using PhysicalScrollSnapAlign =
std::pair<mozilla::StyleScrollSnapAlignKeyword,
mozilla::StyleScrollSnapAlignKeyword>;
NS_DECL_QUERYFRAME_TARGET(nsIScrollableFrame)
@ -586,6 +590,15 @@ class nsIScrollableFrame : public nsIScrollbarMediator {
virtual void PostPendingResnapIfNeeded(const nsIFrame* aFrame) = 0;
virtual void PostPendingResnap() = 0;
/**
* Returns a pair of the scroll-snap-align property value both on X and Y axes
* for the given |aFrame| considering the scroll-snap-type of this scroll
* container. For example, if the scroll-snap-type is `none`, the pair of
* scroll-snap-align is also `none none`.
*/
virtual PhysicalScrollSnapAlign GetScrollSnapAlignFor(
const nsIFrame* aFrame) const = 0;
/**
* Given the drag event aEvent, determine whether the mouse is near the edge
* of the scrollable area, and scroll the view in the direction of that edge

View File

@ -1,5 +0,0 @@
[scroll-target-snap-001.html]
expected:
if (os == "linux") and swgl and not debug: [FAIL, PASS]
if (os == "android") and not debug: [FAIL, PASS]
FAIL

View File

@ -1,2 +0,0 @@
[scroll-target-snap-003.html]
expected: FAIL