| OLD | NEW |
| 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 |
| OLD | NEW |