ScrollAnchorContainer.h (6783B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_layout_ScrollAnchorContainer_h_ 8 #define mozilla_layout_ScrollAnchorContainer_h_ 9 10 #include "mozilla/Saturate.h" 11 #include "mozilla/TimeStamp.h" 12 #include "nsPoint.h" 13 14 class nsFrameList; 15 class nsIFrame; 16 17 namespace mozilla { 18 class ScrollContainerFrame; 19 } 20 21 namespace mozilla::layout { 22 23 /** 24 * A scroll anchor container finds a descendent element of a scroll container 25 * frame to be an anchor node. After every reflow, the scroll anchor will apply 26 * scroll adjustments to keep the anchor node in the same relative position. 27 * 28 * See: https://drafts.csswg.org/css-scroll-anchoring/ 29 */ 30 class ScrollAnchorContainer final { 31 public: 32 explicit ScrollAnchorContainer(ScrollContainerFrame* aScrollFrame); 33 ~ScrollAnchorContainer(); 34 35 /** 36 * Returns the nearest scroll anchor container that could select aFrame as an 37 * anchor node. 38 */ 39 static ScrollAnchorContainer* FindFor(nsIFrame* aFrame); 40 41 /** 42 * Returns the frame that is the selected anchor node or null if no anchor 43 * is selected. 44 */ 45 nsIFrame* AnchorNode() const { return mAnchorNode; } 46 47 // The owner of this scroll anchor container. 48 ScrollContainerFrame* Frame() const; 49 50 /** 51 * Returns the scroll container frame that owns this scroll anchor container. 52 * This is always non-null. 53 */ 54 ScrollContainerFrame* ScrollContainer() const; 55 56 /** 57 * Find a suitable anchor node among the descendants of the scroll container 58 * frame. This should only be called after the scroll anchor has been 59 * invalidated. 60 */ 61 void SelectAnchor(); 62 63 /** 64 * Whether this scroll frame can maintain an anchor node at the moment. 65 */ 66 bool CanMaintainAnchor() const; 67 68 /** 69 * Notify the scroll anchor container that its scroll frame has been 70 * scrolled by a user and should invalidate itself. 71 */ 72 void UserScrolled(); 73 74 /** 75 * Notify the scroll anchor container that a reflow has happened and it 76 * should query its anchor to see if a scroll adjustment needs to occur. 77 */ 78 void ApplyAdjustments(); 79 80 /** 81 * Notify the scroll anchor container that it should suppress any scroll 82 * adjustment that may happen after the next layout flush. 83 */ 84 void SuppressAdjustments(); 85 86 /** 87 * Notify this scroll anchor container that its anchor node should be 88 * invalidated, and recomputed at the next available opportunity if 89 * ScheduleSelection is Yes. 90 */ 91 enum class ScheduleSelection { No, Yes }; 92 void InvalidateAnchor(ScheduleSelection = ScheduleSelection::Yes); 93 94 /** 95 * Notify this scroll anchor container that it will be destroyed along with 96 * its parent frame. 97 */ 98 void Destroy(); 99 100 private: 101 // Represents an assessment of a frame's suitability as a scroll anchor, 102 // from the scroll-anchoring spec's "candidate examination algorithm": 103 // https://drafts.csswg.org/css-scroll-anchoring-1/#candidate-examination 104 enum class ExamineResult { 105 // The frame is an excluded subtree or fully clipped and should be ignored. 106 // This corresponds with step 1 in the algorithm. 107 Exclude, 108 // This frame is an anonymous or inline box and its descendants should be 109 // searched to find an anchor node. If none are found, then continue 110 // searching. This is implied by the prologue of the algorithm, and 111 // should be made explicit in the spec [1]. 112 // 113 // [1] https://github.com/w3c/csswg-drafts/issues/3489 114 PassThrough, 115 // The frame is partially visible and its descendants should be searched to 116 // find an anchor node. If none are found then this frame should be 117 // selected. This corresponds with step 3 in the algorithm. 118 Traverse, 119 // The frame is fully visible and should be selected as an anchor node. This 120 // corresponds with step 2 in the algorithm. 121 Accept, 122 }; 123 124 ExamineResult ExamineAnchorCandidate(nsIFrame* aPrimaryFrame) const; 125 126 // Search a frame's children to find an anchor node. Returns the frame for a 127 // valid anchor node, if one was found in the frames descendants, or null 128 // otherwise. 129 nsIFrame* FindAnchorIn(nsIFrame* aFrame) const; 130 131 // Search a child list to find an anchor node. Returns the frame for a valid 132 // anchor node, if one was found in this child list, or null otherwise. 133 nsIFrame* FindAnchorInList(const nsFrameList& aFrameList) const; 134 135 // Notes that a given adjustment has happened, and maybe disables scroll 136 // anchoring on this scroller altogether based on various prefs. 137 void AdjustmentMade(nscoord aAdjustment); 138 139 // The anchor node that we will scroll to keep in the same relative position 140 // after reflows. This may be null if we were not able to select a valid 141 // scroll anchor 142 nsIFrame* mAnchorNode = nullptr; 143 144 // The last offset of the scroll anchor node's scrollable overflow rect start 145 // edge relative to the scroll-port start edge, in the block axis of the 146 // scroll frame. This is used for calculating the distance to scroll to keep 147 // the anchor node in the same relative position 148 nscoord mLastAnchorOffset = 0; 149 150 struct DisablingHeuristic { 151 // The number of consecutive scroll anchoring adjustments that have happened 152 // without a user scroll. 153 SaturateUint32 mConsecutiveScrollAnchoringAdjustments{0}; 154 155 // The total length that has been adjusted by all the consecutive 156 // adjustments referenced above. Note that this is a sum, so that 157 // oscillating adjustments average towards zero. 158 nscoord mConsecutiveScrollAnchoringAdjustmentLength{0}; 159 160 // The time we started checking for adjustments. 161 TimeStamp mTimeStamp; 162 163 // Returns whether anchoring should get disabled. 164 bool AdjustmentMade(const ScrollAnchorContainer&, nscoord aAdjustment); 165 void Reset(); 166 } mHeuristic; 167 168 // True if we've been disabled by the heuristic controlled by 169 // layout.css.scroll-anchoring.max-consecutive-adjustments and 170 // layout.css.scroll-anchoring.min-adjustment-threshold. 171 bool mDisabled : 1; 172 173 // True if when we selected the current scroll anchor, there were unlaid out 174 // children that could be better anchor nodes after layout. 175 bool mAnchorMightBeSubOptimal : 1; 176 // True if we should recalculate our anchor node at the next chance 177 bool mAnchorNodeIsDirty : 1; 178 // True if we are applying a scroll anchor adjustment 179 bool mApplyingAnchorAdjustment : 1; 180 // True if we should suppress anchor adjustments 181 bool mSuppressAnchorAdjustment : 1; 182 }; 183 184 } // namespace mozilla::layout 185 186 #endif // mozilla_layout_ScrollAnchorContainer_h_