Chromium Code Reviews| 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 it's containing block rectangle (both respective to the nearest | |
|
chrishtr
2017/07/27 00:50:32
s/it's/its/
smcgruer
2017/07/27 14:06:52
Done. TIL I am bad at English grammar. It wasn't t
| |
| 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 it's (cached) containing block rect. | |
| 36 // | |
| 37 // Unfortunately this approach breaks down in the presence of nested sticky | |
| 38 // elements. For those we apply more complicated logic, which is explained in | |
| 39 // the implementation of |ComputeStickyOffset|. | |
| 23 class StickyPositionScrollingConstraints final { | 40 class StickyPositionScrollingConstraints final { |
| 24 public: | 41 public: |
| 25 enum AnchorEdgeFlags { | 42 enum AnchorEdgeFlags { |
| 26 kAnchorEdgeLeft = 1 << 0, | 43 kAnchorEdgeLeft = 1 << 0, |
| 27 kAnchorEdgeRight = 1 << 1, | 44 kAnchorEdgeRight = 1 << 1, |
| 28 kAnchorEdgeTop = 1 << 2, | 45 kAnchorEdgeTop = 1 << 2, |
| 29 kAnchorEdgeBottom = 1 << 3 | 46 kAnchorEdgeBottom = 1 << 3 |
| 30 }; | 47 }; |
| 31 typedef unsigned AnchorEdges; | 48 typedef unsigned AnchorEdges; |
| 32 | 49 |
| 33 StickyPositionScrollingConstraints() | 50 StickyPositionScrollingConstraints() |
| 34 : anchor_edges_(0), | 51 : anchor_edges_(0), |
| 35 left_offset_(0), | 52 left_offset_(0), |
| 36 right_offset_(0), | 53 right_offset_(0), |
| 37 top_offset_(0), | 54 top_offset_(0), |
| 38 bottom_offset_(0), | 55 bottom_offset_(0), |
| 39 nearest_sticky_box_shifting_sticky_box_(nullptr), | 56 nearest_sticky_layer_shifting_sticky_box_(nullptr), |
| 40 nearest_sticky_box_shifting_containing_block_(nullptr) {} | 57 nearest_sticky_layer_shifting_containing_block_(nullptr) {} |
| 41 | 58 |
| 42 StickyPositionScrollingConstraints( | 59 StickyPositionScrollingConstraints( |
| 43 const StickyPositionScrollingConstraints& other) | 60 const StickyPositionScrollingConstraints& other) |
| 44 : anchor_edges_(other.anchor_edges_), | 61 : anchor_edges_(other.anchor_edges_), |
| 45 left_offset_(other.left_offset_), | 62 left_offset_(other.left_offset_), |
| 46 right_offset_(other.right_offset_), | 63 right_offset_(other.right_offset_), |
| 47 top_offset_(other.top_offset_), | 64 top_offset_(other.top_offset_), |
| 48 bottom_offset_(other.bottom_offset_), | 65 bottom_offset_(other.bottom_offset_), |
| 49 scroll_container_relative_containing_block_rect_( | 66 scroll_container_relative_containing_block_rect_( |
| 50 other.scroll_container_relative_containing_block_rect_), | 67 other.scroll_container_relative_containing_block_rect_), |
| 51 scroll_container_relative_sticky_box_rect_( | 68 scroll_container_relative_sticky_box_rect_( |
| 52 other.scroll_container_relative_sticky_box_rect_), | 69 other.scroll_container_relative_sticky_box_rect_), |
| 53 nearest_sticky_box_shifting_sticky_box_( | 70 nearest_sticky_layer_shifting_sticky_box_( |
| 54 other.nearest_sticky_box_shifting_sticky_box_), | 71 other.nearest_sticky_layer_shifting_sticky_box_), |
| 55 nearest_sticky_box_shifting_containing_block_( | 72 nearest_sticky_layer_shifting_containing_block_( |
| 56 other.nearest_sticky_box_shifting_containing_block_), | 73 other.nearest_sticky_layer_shifting_containing_block_), |
| 57 total_sticky_box_sticky_offset_(other.total_sticky_box_sticky_offset_), | 74 total_sticky_box_sticky_offset_(other.total_sticky_box_sticky_offset_), |
| 58 total_containing_block_sticky_offset_( | 75 total_containing_block_sticky_offset_( |
| 59 other.total_containing_block_sticky_offset_) {} | 76 other.total_containing_block_sticky_offset_) {} |
| 60 | 77 |
| 61 FloatSize ComputeStickyOffset( | 78 // Computes the sticky offset for a given overflow clip rect. |
| 62 const FloatRect& viewport_rect, | 79 // |
| 63 const StickyPositionScrollingConstraints* ancestor_sticky_box_constraints, | 80 // This method is non-const as we cache internal state for performance; see |
| 64 const StickyPositionScrollingConstraints* | 81 // documentation in the implementation for details. |
| 65 ancestor_containing_block_constraints); | 82 FloatSize ComputeStickyOffset(const FloatRect& overflow_clip_rect, |
| 83 const StickyConstraintsMap&); | |
| 84 | |
| 85 // Returns the last-computed offset of the sticky box from its original | |
| 86 // position before scroll. | |
| 87 // | |
| 88 // This method exists for performance (to avoid recomputing the sticky offset) | |
| 89 // and must only be called when compositing inputs are clean for the sticky | |
| 90 // element. (Or after prepaint for SlimmingPaintV2). | |
| 91 FloatSize GetOffsetForStickyPosition(const StickyConstraintsMap&) const; | |
| 66 | 92 |
| 67 bool HasAncestorStickyElement() const { | 93 bool HasAncestorStickyElement() const { |
| 68 return nearest_sticky_box_shifting_sticky_box_ || | 94 return nearest_sticky_layer_shifting_sticky_box_ || |
| 69 nearest_sticky_box_shifting_containing_block_; | 95 nearest_sticky_layer_shifting_containing_block_; |
| 70 } | 96 } |
| 71 | 97 |
| 72 AnchorEdges GetAnchorEdges() const { return anchor_edges_; } | 98 AnchorEdges GetAnchorEdges() const { return anchor_edges_; } |
| 73 bool HasAnchorEdge(AnchorEdgeFlags flag) const { | 99 bool HasAnchorEdge(AnchorEdgeFlags flag) const { |
| 74 return anchor_edges_ & flag; | 100 return anchor_edges_ & flag; |
| 75 } | 101 } |
| 76 void AddAnchorEdge(AnchorEdgeFlags edge_flag) { anchor_edges_ |= edge_flag; } | 102 void AddAnchorEdge(AnchorEdgeFlags edge_flag) { anchor_edges_ |= edge_flag; } |
| 77 void SetAnchorEdges(AnchorEdges edges) { anchor_edges_ = edges; } | |
| 78 | 103 |
| 79 float LeftOffset() const { return left_offset_; } | 104 float LeftOffset() const { return left_offset_; } |
| 80 float RightOffset() const { return right_offset_; } | 105 float RightOffset() const { return right_offset_; } |
| 81 float TopOffset() const { return top_offset_; } | 106 float TopOffset() const { return top_offset_; } |
| 82 float BottomOffset() const { return bottom_offset_; } | 107 float BottomOffset() const { return bottom_offset_; } |
| 83 | 108 |
| 84 void SetLeftOffset(float offset) { left_offset_ = offset; } | 109 void SetLeftOffset(float offset) { left_offset_ = offset; } |
| 85 void SetRightOffset(float offset) { right_offset_ = offset; } | 110 void SetRightOffset(float offset) { right_offset_ = offset; } |
| 86 void SetTopOffset(float offset) { top_offset_ = offset; } | 111 void SetTopOffset(float offset) { top_offset_ = offset; } |
| 87 void SetBottomOffset(float offset) { bottom_offset_ = offset; } | 112 void SetBottomOffset(float offset) { bottom_offset_ = offset; } |
| 88 | 113 |
| 89 void SetScrollContainerRelativeContainingBlockRect(const FloatRect& rect) { | 114 void SetScrollContainerRelativeContainingBlockRect(const FloatRect& rect) { |
| 90 scroll_container_relative_containing_block_rect_ = rect; | 115 scroll_container_relative_containing_block_rect_ = rect; |
| 91 } | 116 } |
| 92 const FloatRect& ScrollContainerRelativeContainingBlockRect() const { | 117 const FloatRect& ScrollContainerRelativeContainingBlockRect() const { |
| 93 return scroll_container_relative_containing_block_rect_; | 118 return scroll_container_relative_containing_block_rect_; |
| 94 } | 119 } |
| 95 | 120 |
| 96 void SetScrollContainerRelativeStickyBoxRect(const FloatRect& rect) { | 121 void SetScrollContainerRelativeStickyBoxRect(const FloatRect& rect) { |
| 97 scroll_container_relative_sticky_box_rect_ = rect; | 122 scroll_container_relative_sticky_box_rect_ = rect; |
| 98 } | 123 } |
| 99 const FloatRect& ScrollContainerRelativeStickyBoxRect() const { | 124 const FloatRect& ScrollContainerRelativeStickyBoxRect() const { |
| 100 return scroll_container_relative_sticky_box_rect_; | 125 return scroll_container_relative_sticky_box_rect_; |
| 101 } | 126 } |
| 102 | 127 |
| 103 void SetNearestStickyBoxShiftingStickyBox(LayoutBoxModelObject* layer) { | 128 void SetNearestStickyLayerShiftingStickyBox(PaintLayer* layer) { |
| 104 nearest_sticky_box_shifting_sticky_box_ = layer; | 129 nearest_sticky_layer_shifting_sticky_box_ = layer; |
| 105 } | 130 } |
| 106 LayoutBoxModelObject* NearestStickyBoxShiftingStickyBox() const { | 131 PaintLayer* NearestStickyLayerShiftingStickyBox() const { |
| 107 return nearest_sticky_box_shifting_sticky_box_; | 132 return nearest_sticky_layer_shifting_sticky_box_; |
| 108 } | 133 } |
| 109 | 134 |
| 110 void SetNearestStickyBoxShiftingContainingBlock(LayoutBoxModelObject* layer) { | 135 void SetNearestStickyLayerShiftingContainingBlock(PaintLayer* layer) { |
| 111 nearest_sticky_box_shifting_containing_block_ = layer; | 136 nearest_sticky_layer_shifting_containing_block_ = layer; |
| 112 } | 137 } |
| 113 LayoutBoxModelObject* NearestStickyBoxShiftingContainingBlock() const { | 138 PaintLayer* NearestStickyLayerShiftingContainingBlock() const { |
| 114 return nearest_sticky_box_shifting_containing_block_; | 139 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 } | 140 } |
| 143 | 141 |
| 144 bool operator==(const StickyPositionScrollingConstraints& other) const { | 142 bool operator==(const StickyPositionScrollingConstraints& other) const { |
| 145 return left_offset_ == other.left_offset_ && | 143 return left_offset_ == other.left_offset_ && |
| 146 right_offset_ == other.right_offset_ && | 144 right_offset_ == other.right_offset_ && |
| 147 top_offset_ == other.top_offset_ && | 145 top_offset_ == other.top_offset_ && |
| 148 bottom_offset_ == other.bottom_offset_ && | 146 bottom_offset_ == other.bottom_offset_ && |
| 149 scroll_container_relative_containing_block_rect_ == | 147 scroll_container_relative_containing_block_rect_ == |
| 150 other.scroll_container_relative_containing_block_rect_ && | 148 other.scroll_container_relative_containing_block_rect_ && |
| 151 scroll_container_relative_sticky_box_rect_ == | 149 scroll_container_relative_sticky_box_rect_ == |
| 152 other.scroll_container_relative_sticky_box_rect_ && | 150 other.scroll_container_relative_sticky_box_rect_ && |
| 153 nearest_sticky_box_shifting_sticky_box_ == | 151 nearest_sticky_layer_shifting_sticky_box_ == |
| 154 other.nearest_sticky_box_shifting_sticky_box_ && | 152 other.nearest_sticky_layer_shifting_sticky_box_ && |
| 155 nearest_sticky_box_shifting_containing_block_ == | 153 nearest_sticky_layer_shifting_containing_block_ == |
| 156 other.nearest_sticky_box_shifting_containing_block_ && | 154 other.nearest_sticky_layer_shifting_containing_block_ && |
| 157 total_sticky_box_sticky_offset_ == | 155 total_sticky_box_sticky_offset_ == |
| 158 other.total_sticky_box_sticky_offset_ && | 156 other.total_sticky_box_sticky_offset_ && |
| 159 total_containing_block_sticky_offset_ == | 157 total_containing_block_sticky_offset_ == |
| 160 other.total_containing_block_sticky_offset_; | 158 other.total_containing_block_sticky_offset_; |
| 161 } | 159 } |
| 162 | 160 |
| 163 bool operator!=(const StickyPositionScrollingConstraints& other) const { | 161 bool operator!=(const StickyPositionScrollingConstraints& other) const { |
| 164 return !(*this == other); | 162 return !(*this == other); |
| 165 } | 163 } |
| 166 | 164 |
| 167 private: | 165 private: |
| 166 FloatSize AncestorStickyBoxOffset(const StickyConstraintsMap&); | |
| 167 FloatSize AncestorContainingBlockOffset(const StickyConstraintsMap&); | |
| 168 | |
| 168 AnchorEdges anchor_edges_; | 169 AnchorEdges anchor_edges_; |
| 169 float left_offset_; | 170 float left_offset_; |
| 170 float right_offset_; | 171 float right_offset_; |
| 171 float top_offset_; | 172 float top_offset_; |
| 172 float bottom_offset_; | 173 float bottom_offset_; |
| 174 | |
| 175 // The layout position of the sticky element's containing block rectangle, | |
| 176 // relative to the scroll container. When calculating the sticky offset it is | |
| 177 // used to ensure the sticky element stays bounded by it's containing block. | |
| 173 FloatRect scroll_container_relative_containing_block_rect_; | 178 FloatRect scroll_container_relative_containing_block_rect_; |
| 179 | |
| 180 // The layout position of the sticky element. When calculating the sticky | |
| 181 // offset it is used to determine how large the offset needs to be to satisfy | |
| 182 // the sticky constraints. | |
| 174 FloatRect scroll_container_relative_sticky_box_rect_; | 183 FloatRect scroll_container_relative_sticky_box_rect_; |
| 175 | 184 |
| 176 // In order to properly compute the stickyOffset, we need to know if we have | 185 // 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 | 186 // element and it's containing block are not accurate (as they are affected by |
| 178 // between our containing block and the viewport. These ancestors are needed | 187 // ancestor sticky offsets). To ensure a correct sticky offset calculation in |
| 179 // to properly shift our constraining rects with regards to the containing | 188 // that case we must track any sticky ancestors between the sticky element and |
| 180 // block and viewport. | 189 // it's containing block, and between it's containing block and the overflow |
| 181 LayoutBoxModelObject* nearest_sticky_box_shifting_sticky_box_; | 190 // clip ancestor. |
| 182 LayoutBoxModelObject* nearest_sticky_box_shifting_containing_block_; | 191 // |
| 192 // See the implementation of |ComputeStickyOffset| for documentation on how | |
| 193 // these ancestors are used to correct the offset calculation. | |
| 194 PaintLayer* nearest_sticky_layer_shifting_sticky_box_; | |
| 195 PaintLayer* nearest_sticky_layer_shifting_containing_block_; | |
| 183 | 196 |
| 184 // For performance we cache our accumulated sticky offset to allow descendant | 197 // For performance we cache our accumulated sticky offset to allow descendant |
| 185 // sticky elements to offset their constraint rects. Because we can either | 198 // sticky elements to offset their constraint rects. Because we can either |
| 186 // affect the sticky box constraint rect or the containing block constraint | 199 // affect a descendant element's sticky box constraint rect or containing |
| 187 // rect, we need to accumulate both. | 200 // block constraint rect, we need to accumulate two offsets. |
| 188 // | 201 // |
| 189 // The case where we can affect both the sticky box constraint rect and the | 202 // The sticky box offset accumulates the chain of sticky elements that are |
| 190 // constraining block constriant rect for different sticky descendants is | 203 // between this sticky element and it's containing block. Any descendant using |
| 191 // quite complex. See the StickyPositionComplexTableNesting test in | 204 // |total_sticky_box_sticky_offset_| has the same containing block as this |
| 192 // LayoutBoxModelObjectTest.cpp. | 205 // element, so |total_sticky_box_sticky_offset_| does not accumulate |
| 206 // containing block sticky offsets. | |
| 207 // | |
| 208 // The containing block offset accumulates all sticky-related offsets between | |
| 209 // this element and the ancestor scroller. If this element is a containing | |
| 210 // block shifting ancestor for some descendant, it shifts the descendant's | |
| 211 // constraint rects by it's entire offset. | |
| 193 FloatSize total_sticky_box_sticky_offset_; | 212 FloatSize total_sticky_box_sticky_offset_; |
| 194 FloatSize total_containing_block_sticky_offset_; | 213 FloatSize total_containing_block_sticky_offset_; |
| 195 }; | 214 }; |
| 196 | 215 |
| 197 } // namespace blink | 216 } // namespace blink |
| 198 | 217 |
| 199 #endif // StickyPositionScrollingConstraints_h | 218 #endif // StickyPositionScrollingConstraints_h |
| OLD | NEW |