Bug 1305957 part 5 - Add implementation of scroll anchor selection and invalidation. r=hiro,dbaron,dholbert

This commit implements candidate selection for a scroll frame using a frame tree
traversal. It roughly tries to follow the algorithm given in the scroll
anchoring draft specification, adapted to operate on the frame tree [1].

Some details, such as not selecting an anchor if the user hasn't scrolled are
not currently in the specification but will be to match Blink's implementation.

Once a scroll anchor has been selected, we maintain a bit on it and its ancestor
frame's states. This is used in a later commit to detect changes to position
during a reflow so the scroll frame can perform an adjustment.

A scroll anchor will be invalidated when the user scrolls the frame or the
scroll anchor is destroyed. Later commits will add logic to drive selection and
invalidation appropriately.

[1] https://drafts.csswg.org/css-scroll-anchoring/#anchor-node-selection

Differential Revision: https://phabricator.services.mozilla.com/D13268

--HG--
extra : rebase_source : e05ba48662aafef5957322def33ddc5d93f3ca5a
extra : source : bdb766faa8679386bf4e9740781262ea4bb36544
This commit is contained in:
Ryan Hunt 2018-11-27 15:45:16 -06:00
parent 91f41f3a0a
commit 47ee617d1e
7 changed files with 540 additions and 10 deletions

View File

@ -1490,11 +1490,11 @@ bool nsLayoutUtils::IsAncestorFrameCrossDoc(const nsIFrame* aAncestorFrame,
}
// static
bool nsLayoutUtils::IsProperAncestorFrame(nsIFrame* aAncestorFrame,
nsIFrame* aFrame,
nsIFrame* aCommonAncestor) {
bool nsLayoutUtils::IsProperAncestorFrame(const nsIFrame* aAncestorFrame,
const nsIFrame* aFrame,
const nsIFrame* aCommonAncestor) {
if (aFrame == aAncestorFrame) return false;
for (nsIFrame* f = aFrame; f != aCommonAncestor; f = f->GetParent()) {
for (const nsIFrame* f = aFrame; f != aCommonAncestor; f = f->GetParent()) {
if (f == aAncestorFrame) return true;
}
return aCommonAncestor == aAncestorFrame;

View File

@ -528,8 +528,9 @@ class nsLayoutUtils {
* aAncestorFrame. If non-null, this can bound the search and speed up
* the function
*/
static bool IsProperAncestorFrame(nsIFrame* aAncestorFrame, nsIFrame* aFrame,
nsIFrame* aCommonAncestor = nullptr);
static bool IsProperAncestorFrame(const nsIFrame* aAncestorFrame,
const nsIFrame* aFrame,
const nsIFrame* aCommonAncestor = nullptr);
/**
* Like IsProperAncestorFrame, but looks across document boundaries.

View File

@ -6,6 +6,7 @@
#include "ScrollAnchorContainer.h"
#include "mozilla/StaticPrefs.h"
#include "nsGfxScrollFrame.h"
#include "nsLayoutUtils.h"
@ -16,7 +17,10 @@ namespace mozilla {
namespace layout {
ScrollAnchorContainer::ScrollAnchorContainer(ScrollFrameHelper* aScrollFrame)
: mScrollFrame(aScrollFrame) {}
: mScrollFrame(aScrollFrame),
mAnchorNode(nullptr),
mLastAnchorPos(0, 0),
mAnchorNodeIsDirty(true) {}
ScrollAnchorContainer::~ScrollAnchorContainer() {}
@ -40,5 +44,393 @@ nsIScrollableFrame* ScrollAnchorContainer::ScrollableFrame() const {
return Frame()->GetScrollTargetFrame();
}
/**
* Set the appropriate frame flags for a frame that has become or is no longer
* an anchor node.
*/
static void SetAnchorFlags(const nsIFrame* aScrolledFrame,
nsIFrame* aAnchorNode, bool aInScrollAnchorChain) {
nsIFrame* frame = aAnchorNode;
while (frame && frame != aScrolledFrame) {
MOZ_ASSERT(
frame == aAnchorNode || !frame->IsScrollFrame(),
"We shouldn't select an anchor node inside a nested scroll frame.");
frame->SetInScrollAnchorChain(aInScrollAnchorChain);
frame = frame->GetParent();
}
MOZ_ASSERT(frame,
"The anchor node should be a descendant of the scroll frame");
}
/**
* Compute the scrollable overflow rect [1] of aCandidate relative to
* aScrollFrame with all transforms applied. The specification is also
* ambiguous about what can be selected as a scroll anchor, which makes
* the scroll anchoring bounding rect partially undefined [2]. This code
* attempts to match the implementation in Blink.
*
* [1]
* https://drafts.csswg.org/css-scroll-anchoring-1/#scroll-anchoring-bounding-rect
* [2] https://github.com/w3c/csswg-drafts/issues/3478
*/
static nsRect FindScrollAnchoringBoundingRect(const nsIFrame* aScrollFrame,
nsIFrame* aCandidate) {
MOZ_ASSERT(nsLayoutUtils::IsProperAncestorFrame(aScrollFrame, aCandidate));
if (aCandidate->GetContent()->IsText()) {
nsRect bounding;
for (nsIFrame* continuation = aCandidate->FirstContinuation(); continuation;
continuation = continuation->GetNextContinuation()) {
nsRect localRect =
continuation->GetScrollableOverflowRectRelativeToSelf();
nsRect transformed = nsLayoutUtils::TransformFrameRectToAncestor(
continuation, localRect, aScrollFrame);
bounding = bounding.Union(transformed);
}
return bounding;
}
nsRect localRect = aCandidate->GetScrollableOverflowRectRelativeToSelf();
nsRect transformed = nsLayoutUtils::TransformFrameRectToAncestor(
aCandidate, localRect, aScrollFrame);
return transformed;
}
void ScrollAnchorContainer::SelectAnchor() {
MOZ_ASSERT(mScrollFrame->mScrolledFrame);
MOZ_ASSERT(mAnchorNodeIsDirty);
if (!StaticPrefs::layout_css_scroll_anchoring_enabled()) {
return;
}
ANCHOR_LOG("Selecting anchor for %p with scroll-port [%d %d x %d %d].\n",
this, mScrollFrame->mScrollPort.x, mScrollFrame->mScrollPort.y,
mScrollFrame->mScrollPort.width, mScrollFrame->mScrollPort.height);
const nsStyleDisplay* disp = Frame()->StyleDisplay();
// Don't select a scroll anchor if the scroll frame has `overflow-anchor:
// none`.
bool overflowAnchor =
disp->mOverflowAnchor == mozilla::StyleOverflowAnchor::Auto;
// Or if the scroll frame has not been scrolled from the logical origin. This
// is not in the specification [1], but Blink does this.
//
// [1] https://github.com/w3c/csswg-drafts/issues/3319
bool isScrolled = mScrollFrame->GetLogicalScrollPosition() != nsPoint();
// Or if there is perspective that could affect the scrollable overflow rect
// for descendant frames. This is not in the specification as Blink doesn't
// share this behavior with perspective [1].
//
// [1] https://github.com/w3c/csswg-drafts/issues/3322
bool hasPerspective = Frame()->ChildrenHavePerspective();
// Select a new scroll anchor
nsIFrame* oldAnchor = mAnchorNode;
if (overflowAnchor && isScrolled && !hasPerspective) {
ANCHOR_LOG("Beginning candidate selection.\n");
mAnchorNode = FindAnchorIn(mScrollFrame->mScrolledFrame);
} else {
if (!overflowAnchor) {
ANCHOR_LOG("Skipping candidate selection for `overflow-anchor: none`\n");
}
if (!isScrolled) {
ANCHOR_LOG("Skipping candidate selection for not being scrolled\n");
}
if (hasPerspective) {
ANCHOR_LOG(
"Skipping candidate selection for scroll frame with perspective\n");
}
mAnchorNode = nullptr;
}
// Update the anchor flags if needed
if (oldAnchor != mAnchorNode) {
ANCHOR_LOG("Anchor node has changed from (%p) to (%p).\n", oldAnchor,
mAnchorNode);
// Unset all flags for the old scroll anchor
if (oldAnchor) {
SetAnchorFlags(mScrollFrame->mScrolledFrame, oldAnchor, false);
}
// Set all flags for the new scroll anchor
if (mAnchorNode) {
// Anchor selection will never select a descendant of a different scroll
// frame, so we can set flags without conflicting with other scroll
// anchor containers.
SetAnchorFlags(mScrollFrame->mScrolledFrame, mAnchorNode, true);
}
} else {
ANCHOR_LOG("Anchor node has remained (%p).\n", mAnchorNode);
}
// Calculate the position to use for scroll adjustments
if (mAnchorNode) {
mLastAnchorPos =
FindScrollAnchoringBoundingRect(Frame(), mAnchorNode).TopLeft();
ANCHOR_LOG("Using last anchor position = [%d, %d].\n", mLastAnchorPos.x,
mLastAnchorPos.y);
} else {
mLastAnchorPos = nsPoint();
}
mAnchorNodeIsDirty = false;
}
void ScrollAnchorContainer::UserScrolled() { InvalidateAnchor(); }
void ScrollAnchorContainer::InvalidateAnchor() {
if (!StaticPrefs::layout_css_scroll_anchoring_enabled()) {
return;
}
ANCHOR_LOG("Invalidating scroll anchor %p for %p.\n", mAnchorNode, this);
if (mAnchorNode) {
SetAnchorFlags(mScrollFrame->mScrolledFrame, mAnchorNode, false);
}
mAnchorNode = nullptr;
mAnchorNodeIsDirty = true;
mLastAnchorPos = nsPoint();
}
void ScrollAnchorContainer::Destroy() {
if (mAnchorNode) {
SetAnchorFlags(mScrollFrame->mScrolledFrame, mAnchorNode, false);
}
mAnchorNode = nullptr;
mAnchorNodeIsDirty = false;
mLastAnchorPos = nsPoint();
}
ScrollAnchorContainer::ExamineResult
ScrollAnchorContainer::ExamineAnchorCandidate(nsIFrame* aFrame) const {
#ifdef DEBUG_FRAME_DUMP
nsCString tag = aFrame->ListTag();
ANCHOR_LOG("\tVisiting frame=%s (%p).\n", tag.get(), aFrame);
#else
ANCHOR_LOG("\t\tVisiting frame=%p.\n", aFrame);
#endif
// Check if the author has opted out of scroll anchoring for this frame
// and its descendants.
const nsStyleDisplay* disp = aFrame->StyleDisplay();
if (disp->mOverflowAnchor == mozilla::StyleOverflowAnchor::None) {
ANCHOR_LOG("\t\tExcluding `overflow-anchor: none`.\n");
return ExamineResult::Exclude;
}
// Sticky positioned elements can move with the scroll frame, making them
// unsuitable scroll anchors. This isn't in the specification yet [1], but
// matches Blink's implementation.
//
// [1] https://github.com/w3c/csswg-drafts/issues/3319
if (aFrame->IsStickyPositioned()) {
ANCHOR_LOG("\t\tExcluding `position: sticky`.\n");
return ExamineResult::Exclude;
}
// The frame for a <br> element has a non-zero area, but Blink treats them
// as if they have no area, so exclude them specially.
if (aFrame->IsBrFrame()) {
ANCHOR_LOG("\t\tExcluding <br>.\n");
return ExamineResult::Exclude;
}
// Exclude frames that aren't accessible to content.
bool isChrome =
aFrame->GetContent() && aFrame->GetContent()->ChromeOnlyAccess();
bool isPseudo = aFrame->Style()->IsPseudoElement();
if (isChrome && !isPseudo) {
ANCHOR_LOG("\t\tExcluding chrome only content.\n");
return ExamineResult::Exclude;
}
// See if this frame could have its own anchor node. We could check
// IsScrollFrame(), but that would miss nsListControlFrame which is not a
// scroll frame, but still inherits from nsHTMLScrollFrame.
nsIScrollableFrame* scrollable = do_QueryFrame(aFrame);
// We don't allow scroll anchors to be selected inside of scrollable frames as
// it's not clear how an anchor adjustment should apply to multiple scrollable
// frames. Blink allows this to happen, but they're not sure why [1].
//
// We also don't allow scroll anchors to be selected inside of SVG as it uses
// a different layout model than CSS, and the specification doesn't say it
// should apply.
//
// [1] https://github.com/w3c/csswg-drafts/issues/3477
bool canDescend = !scrollable && !aFrame->IsSVGOuterSVGFrame();
// Check what kind of frame this is
bool isBlockOutside = aFrame->IsBlockOutside();
bool isText = aFrame->GetContent()->IsText();
bool isAnonBox = aFrame->Style()->IsAnonBox() && !isText;
bool isInlineOutside = aFrame->IsInlineOutside() && !isText;
bool isContinuation = !!aFrame->GetPrevContinuation();
// If the frame is anonymous or inline-outside, search its descendants for a
// scroll anchor.
if ((isAnonBox || isInlineOutside) && canDescend) {
ANCHOR_LOG(
"\t\tSearching descendants of anon or inline box (a=%d, i=%d).\n",
isAnonBox, isInlineOutside);
return ExamineResult::PassThrough;
}
// If the frame is not block-outside or a text node then exclude it.
if (!isBlockOutside && !isText) {
ANCHOR_LOG("\t\tExcluding non block-outside or text node (b=%d, t=%d).\n",
isBlockOutside, isText);
return ExamineResult::Exclude;
}
// Find the scroll anchoring bounding rect.
nsRect rect = FindScrollAnchoringBoundingRect(Frame(), aFrame);
ANCHOR_LOG("\t\trect = [%d %d x %d %d].\n", rect.x, rect.y, rect.width,
rect.height);
// Check if this frame is visible in the scroll port. This will exclude rects
// with zero sized area. The specification is ambiguous about this [1], but
// this matches Blink's implementation.
//
// [1] https://github.com/w3c/csswg-drafts/issues/3483
nsRect visibleRect;
if (!visibleRect.IntersectRect(rect, mScrollFrame->mScrollPort)) {
return ExamineResult::Exclude;
}
// At this point, if canDescend is true, we should only have visible
// non-anonymous frames that are either:
// 1. block-outside
// 2. text nodes
//
// It's not clear what the scroll anchoring bounding rect of elements that are
// block-outside should be when they are fragmented. For text nodes that are
// fragmented, it's specified that we need to consider the union of its line
// boxes.
//
// So for text nodes we handle them by including the union of line boxes in
// the bounding rect of the primary frame, and not selecting any
// continuations.
//
// For block-outside elements we choose to consider the bounding rect of each
// frame individually, allowing ourselves to descend into any frame, but only
// selecting a frame if it's not a continuation.
if (canDescend && isContinuation) {
ANCHOR_LOG("\t\tSearching descendants of a continuation.\n");
return ExamineResult::PassThrough;
}
// If this frame is fully visible, then select it as the scroll anchor.
if (visibleRect.IsEqualEdges(rect)) {
ANCHOR_LOG("\t\tFully visible, taking.\n");
return ExamineResult::Accept;
}
// If we can't descend into this frame, then select it as the scroll anchor.
if (!canDescend) {
ANCHOR_LOG("\t\tIntersects a frame that we can't descend into, taking.\n");
return ExamineResult::Accept;
}
// It must be partially visible and we can descend into this frame. Examine
// its children for a better scroll anchor or fall back to this one.
ANCHOR_LOG("\t\tIntersects valid candidate, checking descendants.\n");
return ExamineResult::Traverse;
}
nsIFrame* ScrollAnchorContainer::FindAnchorIn(nsIFrame* aFrame) const {
// Visit the child lists of this frame
for (nsIFrame::ChildListIterator lists(aFrame); !lists.IsDone();
lists.Next()) {
// Skip child lists that contain out-of-flow frames, we'll visit them by
// following placeholders in the in-flow lists so that we visit these
// frames in DOM order.
// XXX do we actually need to exclude kOverflowOutOfFlowList too?
if (lists.CurrentID() == FrameChildListID::kAbsoluteList ||
lists.CurrentID() == FrameChildListID::kFixedList ||
lists.CurrentID() == FrameChildListID::kFloatList ||
lists.CurrentID() == FrameChildListID::kOverflowOutOfFlowList) {
continue;
}
// Search the child list, and return if we selected an anchor
if (nsIFrame* anchor = FindAnchorInList(lists.CurrentList())) {
return anchor;
}
}
// The spec requires us to do an extra pass to visit absolutely positioned
// frames a second time after all the children of their containing block have
// been visited.
//
// It's not clear why this is needed [1], but it matches Blink's
// implementation, and is needed for a WPT test.
//
// [1] https://github.com/w3c/csswg-drafts/issues/3465
const nsFrameList& absPosList =
aFrame->GetChildList(FrameChildListID::kAbsoluteList);
if (nsIFrame* anchor = FindAnchorInList(absPosList)) {
return anchor;
}
return nullptr;
}
nsIFrame* ScrollAnchorContainer::FindAnchorInList(
const nsFrameList& aFrameList) const {
for (nsIFrame* child : aFrameList) {
// If this is a placeholder, try to follow it to the out of flow frame.
nsIFrame* realFrame = nsPlaceholderFrame::GetRealFrameFor(child);
if (child != realFrame) {
// If the out of flow frame is not a descendant of our scroll frame,
// then it must have a different containing block and cannot be an
// anchor node.
if (!nsLayoutUtils::IsProperAncestorFrame(Frame(), realFrame)) {
ANCHOR_LOG(
"\t\tSkipping out of flow frame that is not a descendant of the "
"scroll frame.\n");
continue;
}
ANCHOR_LOG("\t\tFollowing placeholder to out of flow frame.\n");
child = realFrame;
}
// Perform the candidate examination algorithm
ExamineResult examine = ExamineAnchorCandidate(child);
// See the comment before the definition of `ExamineResult` in
// `ScrollAnchorContainer.h` for an explanation of this behavior.
switch (examine) {
case ExamineResult::Exclude: {
continue;
}
case ExamineResult::PassThrough: {
nsIFrame* candidate = FindAnchorIn(child);
if (!candidate) {
continue;
}
return candidate;
}
case ExamineResult::Traverse: {
nsIFrame* candidate = FindAnchorIn(child);
if (!candidate) {
return child;
}
return candidate;
}
case ExamineResult::Accept: {
return child;
}
}
}
return nullptr;
}
} // namespace layout
} // namespace mozilla

View File

@ -7,6 +7,9 @@
#ifndef mozilla_layout_ScrollAnchorContainer_h_
#define mozilla_layout_ScrollAnchorContainer_h_
#include "nsPoint.h"
class nsIFrame;
namespace mozilla {
class ScrollFrameHelper;
} // namespace mozilla
@ -32,6 +35,12 @@ class ScrollAnchorContainer final {
*/
static ScrollAnchorContainer* FindFor(nsIFrame* aFrame);
/**
* Returns the frame that is the selected anchor node or null if no anchor
* is selected.
*/
nsIFrame* AnchorNode() const { return mAnchorNode; }
/**
* Returns the frame that owns this scroll anchor container. This is always
* non-null.
@ -44,9 +53,80 @@ class ScrollAnchorContainer final {
*/
nsIScrollableFrame* ScrollableFrame() const;
/**
* Find a suitable anchor node among the descendants of the scrollable frame.
* This should only be called after the scroll anchor has been invalidated.
*/
void SelectAnchor();
/**
* Notify the scroll anchor container that its scroll frame has been
* scrolled by a user and should invalidate itself.
*/
void UserScrolled();
/**
* Notify this scroll anchor container that its anchor node should be
* invalidated and recomputed at the next available opportunity.
*/
void InvalidateAnchor();
/**
* Notify this scroll anchor container that it will be destroyed along with
* its parent frame.
*/
void Destroy();
private:
// Represents an assessment of a frame's suitability as a scroll anchor,
// from the scroll-anchoring spec's "candidate examination algorithm":
// https://drafts.csswg.org/css-scroll-anchoring-1/#candidate-examination
enum class ExamineResult {
// The frame is an excluded subtree or fully clipped and should be ignored.
// This corresponds with step 1 in the algorithm.
Exclude,
// This frame is an anonymous or inline box and its descendants should be
// searched to find an anchor node. If none are found, then continue
// searching. This is implied by the prologue of the algorithm, and
// should be made explicit in the spec [1].
//
// [1] https://github.com/w3c/csswg-drafts/issues/3489
PassThrough,
// The frame is partially visible and its descendants should be searched to
// find an anchor node. If none are found then this frame should be
// selected. This corresponds with step 3 in the algorithm.
Traverse,
// The frame is fully visible and should be selected as an anchor node. This
// corresponds with step 2 in the algorithm.
Accept,
};
ExamineResult ExamineAnchorCandidate(nsIFrame* aPrimaryFrame) const;
// Search a frame's children to find an anchor node. Returns the frame for a
// valid anchor node, if one was found in the frames descendants, or null
// otherwise.
nsIFrame* FindAnchorIn(nsIFrame* aFrame) const;
// Search a child list to find an anchor node. Returns the frame for a valid
// anchor node, if one was found in this child list, or null otherwise.
nsIFrame* FindAnchorInList(const nsFrameList& aFrameList) const;
// The owner of this scroll anchor container
ScrollFrameHelper* mScrollFrame;
// The anchor node that we will scroll to keep in the same relative position
// after reflows. This may be null if we were not able to select a valid
// scroll anchor
nsIFrame* mAnchorNode;
// The last position of the scroll anchor node relative to the scrollable
// frame. This is used for calculating the distance to scroll to keep the
// anchor node in the same relative position
nsPoint mLastAnchorPos;
// True if we should recalculate our anchor node at the next chance
bool mAnchorNodeIsDirty : 1;
};
} // namespace layout

View File

@ -111,6 +111,7 @@
#include "mozilla/dom/TouchEvent.h"
#include "mozilla/gfx/Tools.h"
#include "mozilla/layers/WebRenderUserData.h"
#include "mozilla/layout/ScrollAnchorContainer.h"
#include "nsPrintfCString.h"
#include "ActiveLayerTracker.h"
@ -727,6 +728,11 @@ void nsFrame::DestroyFrom(nsIFrame* aDestructRoot,
ActiveLayerTracker::TransferActivityToContent(this, mContent);
}
ScrollAnchorContainer* anchor = nullptr;
if (IsScrollAnchor(&anchor)) {
anchor->InvalidateAnchor();
}
if (HasCSSAnimations() || HasCSSTransitions() ||
EffectSet::GetEffectSet(this)) {
// If no new frame for this element is created by the end of the
@ -9143,6 +9149,28 @@ void nsIFrame::ComputePreserve3DChildrenOverflow(
}
}
bool nsIFrame::IsScrollAnchor(ScrollAnchorContainer** aOutContainer) {
if (!mInScrollAnchorChain) {
return false;
}
ScrollAnchorContainer* container = ScrollAnchorContainer::FindFor(this);
if (container->AnchorNode() != this) {
return false;
}
if (aOutContainer) {
*aOutContainer = container;
}
return true;
}
bool nsIFrame::IsInScrollAnchorChain() const { return mInScrollAnchorChain; }
void nsIFrame::SetInScrollAnchorChain(bool aInChain) {
mInScrollAnchorChain = aInChain;
}
uint32_t nsIFrame::GetDepthInFrameTree() const {
uint32_t result = 0;
for (nsContainerFrame* ancestor = GetParent(); ancestor;

View File

@ -2741,6 +2741,7 @@ void ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange,
}
ScrollVisual();
mAnchor.UserScrolled();
bool schedulePaint = true;
if (nsLayoutUtils::AsyncPanZoomEnabled(mOuter) &&
@ -4711,6 +4712,8 @@ void ScrollFrameHelper::AppendAnonymousContentTo(
}
void ScrollFrameHelper::Destroy(PostDestroyData& aPostDestroyData) {
mAnchor.Destroy();
if (mScrollbarActivity) {
mScrollbarActivity->Destroy();
mScrollbarActivity = nullptr;

View File

@ -115,6 +115,10 @@ class Layer;
class LayerManager;
} // namespace layers
namespace layout {
class ScrollAnchorContainer;
} // namespace layout
namespace dom {
class Selection;
} // namespace dom
@ -566,7 +570,8 @@ class nsIFrame : public nsQueryFrame {
mIsPrimaryFrame(false),
mMayHaveTransformAnimation(false),
mMayHaveOpacityAnimation(false),
mAllDescendantsAreInvisible(false) {
mAllDescendantsAreInvisible(false),
mInScrollAnchorChain(false) {
mozilla::PodZero(&mOverflow);
}
@ -1846,6 +1851,24 @@ class nsIFrame : public nsQueryFrame {
void RecomputePerspectiveChildrenOverflow(const nsIFrame* aStartFrame);
/**
* Returns whether this frame is the anchor of some ancestor scroll frame. As
* this frame is moved, the scroll frame will apply adjustments to keep this
* scroll frame in the same relative position.
*
* aOutContainer will optionally be set to the scroll anchor container for
* this frame if this frame is an anchor.
*/
bool IsScrollAnchor(
mozilla::layout::ScrollAnchorContainer** aOutContainer = nullptr);
/**
* Returns whether this frame is the anchor of some ancestor scroll frame, or
* has a descendant which is the scroll anchor.
*/
bool IsInScrollAnchorChain() const;
void SetInScrollAnchorChain(bool aInChain);
/**
* Returns the number of ancestors between this and the root of our frame tree
*/
@ -4293,9 +4316,12 @@ class nsIFrame : public nsQueryFrame {
*/
bool mAllDescendantsAreInvisible : 1;
protected:
// There is a 1-bit gap left here.
/**
* True if we are or contain the scroll anchor for a scrollable frame.
*/
bool mInScrollAnchorChain : 1;
protected:
// Helpers
/**
* Can we stop inside this frame when we're skipping non-rendered whitespace?