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