blob: 9326db5f45dafbce72c389488d420240003876bc [file] [log] [blame]
// 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