| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/page/scrolling/sticky_position_scrolling_constraints.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer.h" |
| #include "third_party/blink/renderer/platform/geometry/layout_unit.h" |
| |
| namespace blink { |
| |
| PhysicalOffset StickyPositionScrollingConstraints::ComputeStickyOffset( |
| const PhysicalRect& content_box_rect, |
| const StickyConstraintsMap& constraints_map) { |
| PhysicalRect sticky_box_rect = scroll_container_relative_sticky_box_rect; |
| PhysicalRect containing_block_rect = |
| scroll_container_relative_containing_block_rect; |
| PhysicalOffset ancestor_sticky_box_offset = |
| AncestorStickyBoxOffset(constraints_map); |
| PhysicalOffset ancestor_containing_block_offset = |
| AncestorContainingBlockOffset(constraints_map); |
| |
| // Adjust the cached rect locations for any sticky ancestor elements. The |
| // sticky offset applied to those ancestors affects us as follows: |
| // |
| // 1. |nearest_sticky_layer_shifting_sticky_box| is a sticky layer between |
| // ourselves and our containing block, e.g. a nested inline parent. |
| // It shifts only the sticky_box_rect and not the containing_block_rect. |
| // 2. |nearest_sticky_layer_shifting_containing_block| is a sticky layer |
| // between our containing block (inclusive) and our scroll ancestor |
| // (exclusive). As such, it shifts both the sticky_box_rect and the |
| // containing_block_rect. |
| // |
| // Note that this calculation assumes that |ComputeStickyOffset| is being |
| // called top down, e.g. it has been called on any ancestors we have before |
| // being called on us. |
| sticky_box_rect.Move(ancestor_sticky_box_offset + |
| ancestor_containing_block_offset); |
| containing_block_rect.Move(ancestor_containing_block_offset); |
| |
| // We now attempt to shift sticky_box_rect to obey the specified sticky |
| // constraints, whilst always staying within our containing block. This |
| // shifting produces the final sticky offset below. |
| // |
| // As per the spec, 'left' overrides 'right' and 'top' overrides 'bottom'. |
| PhysicalRect box_rect = sticky_box_rect; |
| |
| if (is_anchored_right) { |
| LayoutUnit right_limit = content_box_rect.Right() - right_offset; |
| LayoutUnit right_delta = right_limit - sticky_box_rect.Right(); |
| LayoutUnit available_space = |
| containing_block_rect.X() - sticky_box_rect.X(); |
| |
| right_delta = right_delta.ClampPositiveToZero(); |
| available_space = available_space.ClampPositiveToZero(); |
| |
| if (right_delta < available_space) |
| right_delta = available_space; |
| |
| box_rect.Move(PhysicalOffset(right_delta, LayoutUnit())); |
| } |
| |
| if (is_anchored_left) { |
| LayoutUnit left_limit = content_box_rect.X() + left_offset; |
| LayoutUnit left_delta = left_limit - sticky_box_rect.X(); |
| LayoutUnit available_space = |
| containing_block_rect.Right() - sticky_box_rect.Right(); |
| |
| left_delta = left_delta.ClampNegativeToZero(); |
| available_space = available_space.ClampNegativeToZero(); |
| |
| if (left_delta > available_space) |
| left_delta = available_space; |
| |
| box_rect.Move(PhysicalOffset(left_delta, LayoutUnit())); |
| } |
| |
| if (is_anchored_bottom) { |
| LayoutUnit bottom_limit = content_box_rect.Bottom() - bottom_offset; |
| LayoutUnit bottom_delta = bottom_limit - sticky_box_rect.Bottom(); |
| LayoutUnit available_space = |
| containing_block_rect.Y() - sticky_box_rect.Y(); |
| |
| bottom_delta = bottom_delta.ClampPositiveToZero(); |
| available_space = available_space.ClampPositiveToZero(); |
| |
| if (bottom_delta < available_space) |
| bottom_delta = available_space; |
| |
| box_rect.Move(PhysicalOffset(LayoutUnit(), bottom_delta)); |
| } |
| |
| if (is_anchored_top) { |
| LayoutUnit top_limit = content_box_rect.Y() + top_offset; |
| LayoutUnit top_delta = top_limit - sticky_box_rect.Y(); |
| LayoutUnit available_space = |
| containing_block_rect.Bottom() - sticky_box_rect.Bottom(); |
| |
| top_delta = top_delta.ClampNegativeToZero(); |
| available_space = available_space.ClampNegativeToZero(); |
| |
| if (top_delta > available_space) |
| top_delta = available_space; |
| |
| box_rect.Move(PhysicalOffset(LayoutUnit(), top_delta)); |
| } |
| |
| PhysicalOffset sticky_offset = box_rect.offset - sticky_box_rect.offset; |
| |
| // Now that we have computed our current sticky offset, update the cached |
| // accumulated sticky offsets. |
| total_sticky_box_sticky_offset = ancestor_sticky_box_offset + sticky_offset; |
| total_containing_block_sticky_offset = ancestor_sticky_box_offset + |
| ancestor_containing_block_offset + |
| sticky_offset; |
| |
| return sticky_offset; |
| } |
| |
| PhysicalOffset StickyPositionScrollingConstraints::GetOffsetForStickyPosition( |
| const StickyConstraintsMap& constraints_map) const { |
| return total_sticky_box_sticky_offset - |
| AncestorStickyBoxOffset(constraints_map); |
| } |
| |
| PhysicalOffset StickyPositionScrollingConstraints::AncestorStickyBoxOffset( |
| const StickyConstraintsMap& constraints_map) const { |
| if (!nearest_sticky_layer_shifting_sticky_box) |
| return PhysicalOffset(); |
| DCHECK(constraints_map.Contains(nearest_sticky_layer_shifting_sticky_box)); |
| return constraints_map.at(nearest_sticky_layer_shifting_sticky_box) |
| .total_sticky_box_sticky_offset; |
| } |
| |
| PhysicalOffset |
| StickyPositionScrollingConstraints::AncestorContainingBlockOffset( |
| const StickyConstraintsMap& constraints_map) const { |
| if (!nearest_sticky_layer_shifting_containing_block) { |
| return PhysicalOffset(); |
| } |
| DCHECK( |
| constraints_map.Contains(nearest_sticky_layer_shifting_containing_block)); |
| return constraints_map.at(nearest_sticky_layer_shifting_containing_block) |
| .total_containing_block_sticky_offset; |
| } |
| |
| } // namespace blink |