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