summaryrefslogtreecommitdiffstats
path: root/layout/generic/ScrollAnchorContainer.h
diff options
context:
space:
mode:
Diffstat (limited to 'layout/generic/ScrollAnchorContainer.h')
-rw-r--r--layout/generic/ScrollAnchorContainer.h183
1 files changed, 183 insertions, 0 deletions
diff --git a/layout/generic/ScrollAnchorContainer.h b/layout/generic/ScrollAnchorContainer.h
new file mode 100644
index 0000000000..e86a937da6
--- /dev/null
+++ b/layout/generic/ScrollAnchorContainer.h
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layout_ScrollAnchorContainer_h_
+#define mozilla_layout_ScrollAnchorContainer_h_
+
+#include "nsPoint.h"
+#include "mozilla/Saturate.h"
+#include "mozilla/TimeStamp.h"
+
+class nsFrameList;
+class nsHTMLScrollFrame;
+class nsIFrame;
+class nsIScrollableFrame;
+
+namespace mozilla::layout {
+
+/**
+ * A scroll anchor container finds a descendent element of a scrollable frame
+ * to be an anchor node. After every reflow, the scroll anchor will apply
+ * scroll adjustments to keep the anchor node in the same relative position.
+ *
+ * See: https://drafts.csswg.org/css-scroll-anchoring/
+ */
+class ScrollAnchorContainer final {
+ public:
+ explicit ScrollAnchorContainer(nsHTMLScrollFrame* aScrollFrame);
+ ~ScrollAnchorContainer();
+
+ /**
+ * Returns the nearest scroll anchor container that could select aFrame as an
+ * anchor node.
+ */
+ 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; }
+
+ // The owner of this scroll anchor container.
+ nsHTMLScrollFrame* Frame() const;
+
+ /**
+ * Returns the frame that owns this scroll anchor container as a scrollable
+ * frame. This is always non-null.
+ */
+ 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();
+
+ /**
+ * Whether this scroll frame can maintain an anchor node at the moment.
+ */
+ bool CanMaintainAnchor() const;
+
+ /**
+ * Notify the scroll anchor container that its scroll frame has been
+ * scrolled by a user and should invalidate itself.
+ */
+ void UserScrolled();
+
+ /**
+ * Notify the scroll anchor container that a reflow has happened and it
+ * should query its anchor to see if a scroll adjustment needs to occur.
+ */
+ void ApplyAdjustments();
+
+ /**
+ * Notify the scroll anchor container that it should suppress any scroll
+ * adjustment that may happen after the next layout flush.
+ */
+ void SuppressAdjustments();
+
+ /**
+ * Notify this scroll anchor container that its anchor node should be
+ * invalidated, and recomputed at the next available opportunity if
+ * ScheduleSelection is Yes.
+ */
+ enum class ScheduleSelection { No, Yes };
+ void InvalidateAnchor(ScheduleSelection = ScheduleSelection::Yes);
+
+ /**
+ * 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;
+
+ // Notes that a given adjustment has happened, and maybe disables scroll
+ // anchoring on this scroller altogether based on various prefs.
+ void AdjustmentMade(nscoord aAdjustment);
+
+ // 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 = nullptr;
+
+ // The last offset of the scroll anchor node's scrollable overflow rect start
+ // edge relative to the scroll-port start edge, in the block axis of the
+ // scroll frame. This is used for calculating the distance to scroll to keep
+ // the anchor node in the same relative position
+ nscoord mLastAnchorOffset = 0;
+
+ struct DisablingHeuristic {
+ // The number of consecutive scroll anchoring adjustments that have happened
+ // without a user scroll.
+ SaturateUint32 mConsecutiveScrollAnchoringAdjustments{0};
+
+ // The total length that has been adjusted by all the consecutive
+ // adjustments referenced above. Note that this is a sum, so that
+ // oscillating adjustments average towards zero.
+ nscoord mConsecutiveScrollAnchoringAdjustmentLength{0};
+
+ // The time we started checking for adjustments.
+ TimeStamp mTimeStamp;
+
+ // Returns whether anchoring should get disabled.
+ bool AdjustmentMade(const ScrollAnchorContainer&, nscoord aAdjustment);
+ void Reset();
+ } mHeuristic;
+
+ // True if we've been disabled by the heuristic controlled by
+ // layout.css.scroll-anchoring.max-consecutive-adjustments and
+ // layout.css.scroll-anchoring.min-adjustment-threshold.
+ bool mDisabled : 1;
+
+ // True if when we selected the current scroll anchor, there were unlaid out
+ // children that could be better anchor nodes after layout.
+ bool mAnchorMightBeSubOptimal : 1;
+ // True if we should recalculate our anchor node at the next chance
+ bool mAnchorNodeIsDirty : 1;
+ // True if we are applying a scroll anchor adjustment
+ bool mApplyingAnchorAdjustment : 1;
+ // True if we should suppress anchor adjustments
+ bool mSuppressAnchorAdjustment : 1;
+};
+
+} // namespace mozilla::layout
+
+#endif // mozilla_layout_ScrollAnchorContainer_h_