blob: db5b682ec9d21cad60b3feb96045b596cfade7c4 [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.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_SCROLLING_STICKY_POSITION_SCROLLING_CONSTRAINTS_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_SCROLLING_STICKY_POSITION_SCROLLING_CONSTRAINTS_H_
#include "third_party/blink/renderer/platform/geometry/float_rect.h"
#include "third_party/blink/renderer/platform/geometry/float_size.h"
#include "third_party/blink/renderer/platform/wtf/hash_map.h"
namespace blink {
class PaintLayer;
struct StickyPositionScrollingConstraints;
typedef WTF::HashMap<PaintLayer*, StickyPositionScrollingConstraints>
StickyConstraintsMap;
// Encapsulates the constraint information for a position: sticky element and
// does calculation of the sticky offset for a given overflow clip rectangle.
//
// To avoid slowing down scrolling we cannot make the offset calculation a
// layout-inducing event. Instead constraint information is cached during layout
// and used as the scroll position changes to determine the current offset. In
// most cases the only information that is needed is the sticky element's layout
// rectangle and its containing block rectangle (both respective to the nearest
// ancestor scroller which the element is sticking to), and the set of sticky
// edge constraints (i.e. the distance from each edge the element should stick).
//
// For a given (non-cached) overflow clip rectangle, calculating the current
// offset in most cases just requires sliding the (cached) sticky element
// rectangle until it satisfies the (cached) sticky edge constraints for the
// overflow clip rectangle, whilst not letting the sticky element rectangle
// escape its (cached) containing block rect. For example, consider the
// following situation (where positions are relative to the scroll container):
//
// +---------------------+ <-- Containing Block (150x70 at 10,0)
// | +-----------------+ |
// | | top: 50px; |<-- Sticky Box (130x10 at 20,0)
// | +-----------------+ |
// | |
// +-------------------------+ <-- Overflow Clip Rectangle (170x60 at 0,50)
// | | | |
// | | | |
// | +---------------------+ |
// | |
// | |
// | |
// +-------------------------+
//
// Here the cached sticky box would be moved downwards to try and be at position
// (20,100) - 50 pixels down from the top of the clip rectangle. However doing
// so would take it outside the cached containing block rectangle, so the final
// sticky position would be capped to (20,20).
//
// Unfortunately this approach breaks down in the presence of nested sticky
// elements, as the cached locations would be moved by ancestor sticky elements.
// Consider:
//
// +------------------------+ <-- Outer sticky (top: 10px, 150x50 at 0,0)
// | +------------------+ |
// | | | <-- Inner sticky (top: 25px, 100x20 at 20,0)
// | +------------------+ |
// | |
// +------------------------+
//
// Assume the overflow clip rectangle is centered perfectly over the outer
// sticky. We would then want to move the outer sticky element down by 10
// pixels, and the inner sticky element down by only 15 pixels - because it is
// already being shifted by its ancestor. To correctly handle such situations we
// apply more complicated logic which is explained in the implementation of
// |ComputeStickyOffset|.
struct StickyPositionScrollingConstraints final {
public:
StickyPositionScrollingConstraints()
: is_anchored_left(false),
is_anchored_right(false),
is_anchored_top(false),
is_anchored_bottom(false) {}
StickyPositionScrollingConstraints(
const StickyPositionScrollingConstraints& other) = default;
// Computes the sticky offset for a given overflow clip rect.
//
// This method is non-const as we cache internal state for performance; see
// documentation in the implementation for details.
FloatSize ComputeStickyOffset(const FloatRect& content_box_rect,
const StickyConstraintsMap&);
// Returns the last-computed offset of the sticky box from its original
// position before scroll.
//
// This method exists for performance (to avoid recomputing the sticky offset)
// and must only be called when compositing inputs are clean for the sticky
// element. (Or after prepaint for CompositeAfterPaint).
FloatSize GetOffsetForStickyPosition(const StickyConstraintsMap&) const;
bool is_anchored_left : 1;
bool is_anchored_right : 1;
bool is_anchored_top : 1;
bool is_anchored_bottom : 1;
float left_offset = 0.f;
float right_offset = 0.f;
float top_offset = 0.f;
float bottom_offset = 0.f;
// The containing block rect and sticky box rect are the basic components
// for calculating the sticky offset to apply after a scroll. Consider the
// following setup:
//
// <scroll-container>
// <containing-block> (*)
// <sticky-element>
//
// (*) <containing-block> may be the same as <scroll-container>.
// The layout position of the containing block relative to the scroll
// container. When calculating the sticky offset it is used to ensure the
// sticky element stays bounded by its containing block.
FloatRect scroll_container_relative_containing_block_rect;
// The layout position of the sticky element relative to the scroll container.
// When calculating the sticky offset it is used to determine how large the
// offset needs to be to satisfy the sticky constraints.
FloatRect scroll_container_relative_sticky_box_rect;
// In the case of nested sticky elements the layout position of the sticky
// element and its containing block are not accurate (as they are affected by
// ancestor sticky offsets). To ensure a correct sticky offset calculation in
// that case we must track any sticky ancestors between the sticky element and
// its containing block, and between its containing block and the overflow
// clip ancestor.
//
// See the implementation of |ComputeStickyOffset| for documentation on how
// these ancestors are used to correct the offset calculation.
PaintLayer* nearest_sticky_layer_shifting_sticky_box = nullptr;
PaintLayer* nearest_sticky_layer_shifting_containing_block = nullptr;
private:
// For performance we cache our accumulated sticky offset to allow descendant
// sticky elements to offset their constraint rects. Because we can either
// affect a descendant element's sticky box constraint rect or containing
// block constraint rect, we need to accumulate two offsets.
// The sticky box offset accumulates the chain of sticky elements that are
// between this sticky element and its containing block. Any descendant using
// |total_sticky_box_sticky_offset| has the same containing block as this
// element, so |total_sticky_box_sticky_offset| does not accumulate
// containing block sticky offsets. For example, consider the following chain:
//
// <div style="position: sticky;">
// <div id="outerInline" style="position: sticky; display: inline;">
// <div id="innerInline" style="position: sticky; display: inline;"><div>
// </div>
// </div>
//
// In the above example, both outerInline and innerInline have the same
// containing block - the outermost <div>.
FloatSize total_sticky_box_sticky_offset;
// The containing block offset accumulates all sticky-related offsets between
// this element and the ancestor scroller. If this element is a containing
// block shifting ancestor for some descendant, it shifts the descendant's
// constraint rects by its entire offset.
FloatSize total_containing_block_sticky_offset;
FloatSize AncestorStickyBoxOffset(const StickyConstraintsMap&) const;
FloatSize AncestorContainingBlockOffset(const StickyConstraintsMap&) const;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_SCROLLING_STICKY_POSITION_SCROLLING_CONSTRAINTS_H_