Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(22)

Side by Side Diff: third_party/WebKit/Source/core/page/scrolling/StickyPositionScrollingConstraints.h

Issue 2961613002: Slightly refactor StickyPositionScrollingConstraints API and add documentation (Closed)
Patch Set: Add diagram + example Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #ifndef StickyPositionScrollingConstraints_h 5 #ifndef StickyPositionScrollingConstraints_h
6 #define StickyPositionScrollingConstraints_h 6 #define StickyPositionScrollingConstraints_h
7 7
8 #include "platform/geometry/FloatRect.h" 8 #include "platform/geometry/FloatRect.h"
9 #include "platform/geometry/FloatSize.h" 9 #include "platform/geometry/FloatSize.h"
10 #include "platform/wtf/HashMap.h" 10 #include "platform/wtf/HashMap.h"
11 11
12 namespace blink { 12 namespace blink {
13 13
14 class LayoutBoxModelObject;
15 class PaintLayer; 14 class PaintLayer;
16 class StickyPositionScrollingConstraints; 15 class StickyPositionScrollingConstraints;
17 16
18 typedef WTF::HashMap<PaintLayer*, StickyPositionScrollingConstraints> 17 typedef WTF::HashMap<PaintLayer*, StickyPositionScrollingConstraints>
19 StickyConstraintsMap; 18 StickyConstraintsMap;
20 19
21 // TODO(smcgruer): Add detailed comment explaining how 20 // Encapsulates the constraint information for a position: sticky element and
22 // StickyPositionScrollingConstraints works. 21 // does calculation of the sticky offset for a given overflow clip rectangle.
22 //
23 // To avoid slowing down scrolling we cannot make the offset calculation a
24 // layout-inducing event. Instead constraint information is cached during layout
25 // and used as the scroll position changes to determine the current offset. In
26 // most cases the only information that is needed is the sticky element's layout
27 // rectangle and its containing block rectangle (both respective to the nearest
28 // ancestor scroller which the element is sticking to), and the set of sticky
29 // edge constraints (i.e. the distance from each edge the element should stick).
30 //
31 // For a given (non-cached) overflow clip rectangle, calculating the current
32 // offset in most cases just requires sliding the (cached) sticky element
33 // rectangle until it satisfies the (cached) sticky edge constraints for the
34 // overflow clip rectangle, whilst not letting the sticky element rectangle
35 // escape its (cached) containing block rect. For example, consider the
36 // following situation (where positions are relative to the scroll container):
37 //
38 // +---------------------+ <-- Containing Block (150x70 at 10,0)
39 // | +-----------------+ |
40 // | | top: 50px; |<-- Sticky Box (130x10 at 20,0)
41 // | +-----------------+ |
42 // | |
43 // +-------------------------+ <-- Overflow Clip Rectangle (170x60 at 0,50)
44 // | | | |
45 // | | | |
46 // | +---------------------+ |
47 // | |
48 // | |
49 // | |
50 // +-------------------------+
51 //
52 // Here the cached sticky box would be moved downwards to try and be at position
53 // (20,100) - 50 pixels down from the top of the clip rectangle. However doing
54 // so would take it outside the cached containing block rectangle, so the final
55 // sticky position would be capped to (20,20).
56 //
57 // Unfortunately this approach breaks down in the presence of nested sticky
58 // elements, as the cached locations would be moved by ancestor sticky elements.
59 // Consider:
60 //
61 // +------------------------+ <-- Outer sticky (top: 10px, 150x50 at 0,0)
62 // | +------------------+ |
63 // | | | <-- Inner sticky (top: 25px, 100x20 at 20,0)
64 // | +------------------+ |
65 // | |
66 // +------------------------+
67 //
68 // Assume the overflow clip rectangle is centered perfectly over the outer
69 // sticky. We would then want to move the outer sticky element down by 10
70 // pixels, and the inner sticky element down by only 15 pixels - because it is
71 // already being shifted by its ancestor. To correctly handle such situations we
72 // apply more complicated logic which is explained in the implementation of
73 // |ComputeStickyOffset|.
23 class StickyPositionScrollingConstraints final { 74 class StickyPositionScrollingConstraints final {
24 public: 75 public:
25 enum AnchorEdgeFlags { 76 enum AnchorEdgeFlags {
26 kAnchorEdgeLeft = 1 << 0, 77 kAnchorEdgeLeft = 1 << 0,
27 kAnchorEdgeRight = 1 << 1, 78 kAnchorEdgeRight = 1 << 1,
28 kAnchorEdgeTop = 1 << 2, 79 kAnchorEdgeTop = 1 << 2,
29 kAnchorEdgeBottom = 1 << 3 80 kAnchorEdgeBottom = 1 << 3
30 }; 81 };
31 typedef unsigned AnchorEdges; 82 typedef unsigned AnchorEdges;
32 83
33 StickyPositionScrollingConstraints() 84 StickyPositionScrollingConstraints()
34 : anchor_edges_(0), 85 : anchor_edges_(0),
35 left_offset_(0), 86 left_offset_(0),
36 right_offset_(0), 87 right_offset_(0),
37 top_offset_(0), 88 top_offset_(0),
38 bottom_offset_(0), 89 bottom_offset_(0),
39 nearest_sticky_box_shifting_sticky_box_(nullptr), 90 nearest_sticky_layer_shifting_sticky_box_(nullptr),
40 nearest_sticky_box_shifting_containing_block_(nullptr) {} 91 nearest_sticky_layer_shifting_containing_block_(nullptr) {}
41 92
42 StickyPositionScrollingConstraints( 93 StickyPositionScrollingConstraints(
43 const StickyPositionScrollingConstraints& other) 94 const StickyPositionScrollingConstraints& other)
44 : anchor_edges_(other.anchor_edges_), 95 : anchor_edges_(other.anchor_edges_),
45 left_offset_(other.left_offset_), 96 left_offset_(other.left_offset_),
46 right_offset_(other.right_offset_), 97 right_offset_(other.right_offset_),
47 top_offset_(other.top_offset_), 98 top_offset_(other.top_offset_),
48 bottom_offset_(other.bottom_offset_), 99 bottom_offset_(other.bottom_offset_),
49 scroll_container_relative_containing_block_rect_( 100 scroll_container_relative_containing_block_rect_(
50 other.scroll_container_relative_containing_block_rect_), 101 other.scroll_container_relative_containing_block_rect_),
51 scroll_container_relative_sticky_box_rect_( 102 scroll_container_relative_sticky_box_rect_(
52 other.scroll_container_relative_sticky_box_rect_), 103 other.scroll_container_relative_sticky_box_rect_),
53 nearest_sticky_box_shifting_sticky_box_( 104 nearest_sticky_layer_shifting_sticky_box_(
54 other.nearest_sticky_box_shifting_sticky_box_), 105 other.nearest_sticky_layer_shifting_sticky_box_),
55 nearest_sticky_box_shifting_containing_block_( 106 nearest_sticky_layer_shifting_containing_block_(
56 other.nearest_sticky_box_shifting_containing_block_), 107 other.nearest_sticky_layer_shifting_containing_block_),
57 total_sticky_box_sticky_offset_(other.total_sticky_box_sticky_offset_), 108 total_sticky_box_sticky_offset_(other.total_sticky_box_sticky_offset_),
58 total_containing_block_sticky_offset_( 109 total_containing_block_sticky_offset_(
59 other.total_containing_block_sticky_offset_) {} 110 other.total_containing_block_sticky_offset_) {}
60 111
61 FloatSize ComputeStickyOffset( 112 // Computes the sticky offset for a given overflow clip rect.
62 const FloatRect& viewport_rect, 113 //
63 const StickyPositionScrollingConstraints* ancestor_sticky_box_constraints, 114 // This method is non-const as we cache internal state for performance; see
64 const StickyPositionScrollingConstraints* 115 // documentation in the implementation for details.
65 ancestor_containing_block_constraints); 116 FloatSize ComputeStickyOffset(const FloatRect& overflow_clip_rect,
117 const StickyConstraintsMap&);
118
119 // Returns the last-computed offset of the sticky box from its original
120 // position before scroll.
121 //
122 // This method exists for performance (to avoid recomputing the sticky offset)
123 // and must only be called when compositing inputs are clean for the sticky
124 // element. (Or after prepaint for SlimmingPaintV2).
125 FloatSize GetOffsetForStickyPosition(const StickyConstraintsMap&) const;
66 126
67 bool HasAncestorStickyElement() const { 127 bool HasAncestorStickyElement() const {
68 return nearest_sticky_box_shifting_sticky_box_ || 128 return nearest_sticky_layer_shifting_sticky_box_ ||
69 nearest_sticky_box_shifting_containing_block_; 129 nearest_sticky_layer_shifting_containing_block_;
70 } 130 }
71 131
72 AnchorEdges GetAnchorEdges() const { return anchor_edges_; } 132 AnchorEdges GetAnchorEdges() const { return anchor_edges_; }
73 bool HasAnchorEdge(AnchorEdgeFlags flag) const { 133 bool HasAnchorEdge(AnchorEdgeFlags flag) const {
74 return anchor_edges_ & flag; 134 return anchor_edges_ & flag;
75 } 135 }
76 void AddAnchorEdge(AnchorEdgeFlags edge_flag) { anchor_edges_ |= edge_flag; } 136 void AddAnchorEdge(AnchorEdgeFlags edge_flag) { anchor_edges_ |= edge_flag; }
77 void SetAnchorEdges(AnchorEdges edges) { anchor_edges_ = edges; }
78 137
79 float LeftOffset() const { return left_offset_; } 138 float LeftOffset() const { return left_offset_; }
80 float RightOffset() const { return right_offset_; } 139 float RightOffset() const { return right_offset_; }
81 float TopOffset() const { return top_offset_; } 140 float TopOffset() const { return top_offset_; }
82 float BottomOffset() const { return bottom_offset_; } 141 float BottomOffset() const { return bottom_offset_; }
83 142
84 void SetLeftOffset(float offset) { left_offset_ = offset; } 143 void SetLeftOffset(float offset) { left_offset_ = offset; }
85 void SetRightOffset(float offset) { right_offset_ = offset; } 144 void SetRightOffset(float offset) { right_offset_ = offset; }
86 void SetTopOffset(float offset) { top_offset_ = offset; } 145 void SetTopOffset(float offset) { top_offset_ = offset; }
87 void SetBottomOffset(float offset) { bottom_offset_ = offset; } 146 void SetBottomOffset(float offset) { bottom_offset_ = offset; }
88 147
89 void SetScrollContainerRelativeContainingBlockRect(const FloatRect& rect) { 148 void SetScrollContainerRelativeContainingBlockRect(const FloatRect& rect) {
90 scroll_container_relative_containing_block_rect_ = rect; 149 scroll_container_relative_containing_block_rect_ = rect;
91 } 150 }
92 const FloatRect& ScrollContainerRelativeContainingBlockRect() const { 151 const FloatRect& ScrollContainerRelativeContainingBlockRect() const {
93 return scroll_container_relative_containing_block_rect_; 152 return scroll_container_relative_containing_block_rect_;
94 } 153 }
95 154
96 void SetScrollContainerRelativeStickyBoxRect(const FloatRect& rect) { 155 void SetScrollContainerRelativeStickyBoxRect(const FloatRect& rect) {
97 scroll_container_relative_sticky_box_rect_ = rect; 156 scroll_container_relative_sticky_box_rect_ = rect;
98 } 157 }
99 const FloatRect& ScrollContainerRelativeStickyBoxRect() const { 158 const FloatRect& ScrollContainerRelativeStickyBoxRect() const {
100 return scroll_container_relative_sticky_box_rect_; 159 return scroll_container_relative_sticky_box_rect_;
101 } 160 }
102 161
103 void SetNearestStickyBoxShiftingStickyBox(LayoutBoxModelObject* layer) { 162 void SetNearestStickyLayerShiftingStickyBox(PaintLayer* layer) {
104 nearest_sticky_box_shifting_sticky_box_ = layer; 163 nearest_sticky_layer_shifting_sticky_box_ = layer;
105 } 164 }
106 LayoutBoxModelObject* NearestStickyBoxShiftingStickyBox() const { 165 PaintLayer* NearestStickyLayerShiftingStickyBox() const {
107 return nearest_sticky_box_shifting_sticky_box_; 166 return nearest_sticky_layer_shifting_sticky_box_;
108 } 167 }
109 168
110 void SetNearestStickyBoxShiftingContainingBlock(LayoutBoxModelObject* layer) { 169 void SetNearestStickyLayerShiftingContainingBlock(PaintLayer* layer) {
111 nearest_sticky_box_shifting_containing_block_ = layer; 170 nearest_sticky_layer_shifting_containing_block_ = layer;
112 } 171 }
113 LayoutBoxModelObject* NearestStickyBoxShiftingContainingBlock() const { 172 PaintLayer* NearestStickyLayerShiftingContainingBlock() const {
114 return nearest_sticky_box_shifting_containing_block_; 173 return nearest_sticky_layer_shifting_containing_block_;
115 }
116
117 const FloatSize& GetTotalStickyBoxStickyOffset() const {
118 return total_sticky_box_sticky_offset_;
119 }
120 const FloatSize& GetTotalContainingBlockStickyOffset() const {
121 return total_containing_block_sticky_offset_;
122 }
123
124 // Returns the relative position of the sticky box and its original position
125 // before scroll. This method is only safe to call if ComputeStickyOffset has
126 // been invoked.
127 FloatSize GetOffsetForStickyPosition(const StickyConstraintsMap&) const;
128
129 const LayoutBoxModelObject* NearestStickyAncestor() const {
130 // If we have one or more sticky ancestor elements between ourselves and our
131 // containing block, |m_nearestStickyBoxShiftingStickyBox| points to the
132 // closest. Otherwise, |m_nearestStickyBoxShiftingContainingBlock| points
133 // to the the first sticky ancestor between our containing block (inclusive)
134 // and our scroll ancestor (exclusive). Therefore our nearest sticky
135 // ancestor is the former if it exists, or the latter otherwise.
136 //
137 // If both are null, then we have no sticky ancestors before our scroll
138 // ancestor, so the correct action is to return null.
139 return nearest_sticky_box_shifting_sticky_box_
140 ? nearest_sticky_box_shifting_sticky_box_
141 : nearest_sticky_box_shifting_containing_block_;
142 } 174 }
143 175
144 bool operator==(const StickyPositionScrollingConstraints& other) const { 176 bool operator==(const StickyPositionScrollingConstraints& other) const {
145 return left_offset_ == other.left_offset_ && 177 return left_offset_ == other.left_offset_ &&
146 right_offset_ == other.right_offset_ && 178 right_offset_ == other.right_offset_ &&
147 top_offset_ == other.top_offset_ && 179 top_offset_ == other.top_offset_ &&
148 bottom_offset_ == other.bottom_offset_ && 180 bottom_offset_ == other.bottom_offset_ &&
149 scroll_container_relative_containing_block_rect_ == 181 scroll_container_relative_containing_block_rect_ ==
150 other.scroll_container_relative_containing_block_rect_ && 182 other.scroll_container_relative_containing_block_rect_ &&
151 scroll_container_relative_sticky_box_rect_ == 183 scroll_container_relative_sticky_box_rect_ ==
152 other.scroll_container_relative_sticky_box_rect_ && 184 other.scroll_container_relative_sticky_box_rect_ &&
153 nearest_sticky_box_shifting_sticky_box_ == 185 nearest_sticky_layer_shifting_sticky_box_ ==
154 other.nearest_sticky_box_shifting_sticky_box_ && 186 other.nearest_sticky_layer_shifting_sticky_box_ &&
155 nearest_sticky_box_shifting_containing_block_ == 187 nearest_sticky_layer_shifting_containing_block_ ==
156 other.nearest_sticky_box_shifting_containing_block_ && 188 other.nearest_sticky_layer_shifting_containing_block_ &&
157 total_sticky_box_sticky_offset_ == 189 total_sticky_box_sticky_offset_ ==
158 other.total_sticky_box_sticky_offset_ && 190 other.total_sticky_box_sticky_offset_ &&
159 total_containing_block_sticky_offset_ == 191 total_containing_block_sticky_offset_ ==
160 other.total_containing_block_sticky_offset_; 192 other.total_containing_block_sticky_offset_;
161 } 193 }
162 194
163 bool operator!=(const StickyPositionScrollingConstraints& other) const { 195 bool operator!=(const StickyPositionScrollingConstraints& other) const {
164 return !(*this == other); 196 return !(*this == other);
165 } 197 }
166 198
167 private: 199 private:
200 FloatSize AncestorStickyBoxOffset(const StickyConstraintsMap&);
201 FloatSize AncestorContainingBlockOffset(const StickyConstraintsMap&);
202
168 AnchorEdges anchor_edges_; 203 AnchorEdges anchor_edges_;
169 float left_offset_; 204 float left_offset_;
170 float right_offset_; 205 float right_offset_;
171 float top_offset_; 206 float top_offset_;
172 float bottom_offset_; 207 float bottom_offset_;
208
209 // The containing block rect and sticky box rect are the basic components
210 // for calculating the sticky offset to apply after a scroll. Consider the
211 // following setup:
212 //
213 // <scroll-container>
214 // <containing-block> (*)
215 // <sticky-element>
216 //
217 // (*) <containing-block> may be the same as <scroll-container>.
218
219 // The layout position of the containing block relative to the scroll
220 // container. When calculating the sticky offset it is used to ensure the
221 // sticky element stays bounded by its containing block.
173 FloatRect scroll_container_relative_containing_block_rect_; 222 FloatRect scroll_container_relative_containing_block_rect_;
223
224 // The layout position of the sticky element relative to the scroll container.
225 // When calculating the sticky offset it is used to determine how large the
226 // offset needs to be to satisfy the sticky constraints.
174 FloatRect scroll_container_relative_sticky_box_rect_; 227 FloatRect scroll_container_relative_sticky_box_rect_;
175 228
176 // In order to properly compute the stickyOffset, we need to know if we have 229 // In the case of nested sticky elements the layout position of the sticky
177 // any sticky ancestors both between ourselves and our containing block and 230 // element and its containing block are not accurate (as they are affected by
178 // between our containing block and the viewport. These ancestors are needed 231 // ancestor sticky offsets). To ensure a correct sticky offset calculation in
179 // to properly shift our constraining rects with regards to the containing 232 // that case we must track any sticky ancestors between the sticky element and
180 // block and viewport. 233 // its containing block, and between its containing block and the overflow
181 LayoutBoxModelObject* nearest_sticky_box_shifting_sticky_box_; 234 // clip ancestor.
182 LayoutBoxModelObject* nearest_sticky_box_shifting_containing_block_; 235 //
236 // See the implementation of |ComputeStickyOffset| for documentation on how
237 // these ancestors are used to correct the offset calculation.
238 PaintLayer* nearest_sticky_layer_shifting_sticky_box_;
239 PaintLayer* nearest_sticky_layer_shifting_containing_block_;
183 240
184 // For performance we cache our accumulated sticky offset to allow descendant 241 // For performance we cache our accumulated sticky offset to allow descendant
185 // sticky elements to offset their constraint rects. Because we can either 242 // sticky elements to offset their constraint rects. Because we can either
186 // affect the sticky box constraint rect or the containing block constraint 243 // affect a descendant element's sticky box constraint rect or containing
187 // rect, we need to accumulate both. 244 // block constraint rect, we need to accumulate two offsets.
245
246 // The sticky box offset accumulates the chain of sticky elements that are
247 // between this sticky element and its containing block. Any descendant using
248 // |total_sticky_box_sticky_offset_| has the same containing block as this
249 // element, so |total_sticky_box_sticky_offset_| does not accumulate
250 // containing block sticky offsets. For example, consider the following chain:
188 // 251 //
189 // The case where we can affect both the sticky box constraint rect and the 252 // <div style="position: sticky;">
190 // constraining block constriant rect for different sticky descendants is 253 // <div id="outerInline" style="position: sticky; display: inline;">
191 // quite complex. See the StickyPositionComplexTableNesting test in 254 // <div id="innerInline" style="position: sticky; display: inline;"><div>
192 // LayoutBoxModelObjectTest.cpp. 255 // </div>
256 // </div>
257 //
258 // In the above example, both outerInline and innerInline have the same
259 // containing block - the outermost <div>.
193 FloatSize total_sticky_box_sticky_offset_; 260 FloatSize total_sticky_box_sticky_offset_;
261
262 // The containing block offset accumulates all sticky-related offsets between
263 // this element and the ancestor scroller. If this element is a containing
264 // block shifting ancestor for some descendant, it shifts the descendant's
265 // constraint rects by its entire offset.
194 FloatSize total_containing_block_sticky_offset_; 266 FloatSize total_containing_block_sticky_offset_;
195 }; 267 };
196 268
197 } // namespace blink 269 } // namespace blink
198 270
199 #endif // StickyPositionScrollingConstraints_h 271 #endif // StickyPositionScrollingConstraints_h
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698