Commit 3e7e8fe5 authored by flackr's avatar flackr Committed by Commit bot

Compute sticky position constraints without considering transforms.

Sticky position is a layout offset and should not be affected by transforms.

BUG=648683
TEST=LayoutBoxModelObjectTest.StickyPositionTransforms

Review-Url: https://codereview.chromium.org/2346383005
Cr-Commit-Position: refs/heads/master@{#419954}
parent 6f1eb2d8
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
#include "core/paint/PaintLayer.h" #include "core/paint/PaintLayer.h"
#include "core/style/ShadowList.h" #include "core/style/ShadowList.h"
#include "platform/LengthFunctions.h" #include "platform/LengthFunctions.h"
#include "platform/geometry/TransformState.h"
#include "wtf/PtrUtil.h" #include "wtf/PtrUtil.h"
namespace blink { namespace blink {
...@@ -701,33 +702,40 @@ void LayoutBoxModelObject::updateStickyPositionConstraints() const ...@@ -701,33 +702,40 @@ void LayoutBoxModelObject::updateStickyPositionConstraints() const
} }
LayoutBox* scrollAncestor = layer()->ancestorOverflowLayer()->isRootLayer() ? nullptr : toLayoutBox(layer()->ancestorOverflowLayer()->layoutObject()); LayoutBox* scrollAncestor = layer()->ancestorOverflowLayer()->isRootLayer() ? nullptr : toLayoutBox(layer()->ancestorOverflowLayer()->layoutObject());
LayoutRect containerContentRect = containingBlock->layoutOverflowRect();
LayoutUnit maxContainerWidth = containingBlock->isLayoutView() ? containingBlock->logicalWidth() : containingBlock->containingBlockLogicalWidthForContent(); LayoutUnit maxContainerWidth = containingBlock->isLayoutView() ? containingBlock->logicalWidth() : containingBlock->containingBlockLogicalWidthForContent();
// Sticky positioned element ignore any override logical width on the containing block (as they don't call // Sticky positioned element ignore any override logical width on the containing block (as they don't call
// containingBlockLogicalWidthForContent). It's unclear whether this is totally fine. // containingBlockLogicalWidthForContent). It's unclear whether this is totally fine.
// Compute the container-relative area within which the sticky element is allowed to move. // Compute the container-relative area within which the sticky element is allowed to move.
LayoutUnit maxWidth = containingBlock->availableLogicalWidth(); LayoutUnit maxWidth = containingBlock->availableLogicalWidth();
// Map the containing block to the scroll ancestor without transforms.
FloatRect scrollContainerRelativePaddingBoxRect(containingBlock->layoutOverflowRect());
if (containingBlock != scrollAncestor) {
FloatQuad localQuad(FloatRect(containingBlock->paddingBoxRect()));
TransformState transformState(TransformState::ApplyTransformDirection, localQuad.boundingBox().center(), localQuad);
containingBlock->mapLocalToAncestor(scrollAncestor, transformState, ApplyContainerFlip);
transformState.flatten();
scrollContainerRelativePaddingBoxRect = transformState.lastPlanarQuad().boundingBox();
// The sticky position constraint rects should be independent of the current scroll position, so after
// mapping we add in the scroll position to get the container's position within the ancestor scroller's
// unscrolled layout overflow.
FloatSize scrollOffset(scrollAncestor ? toFloatSize(scrollAncestor->getScrollableArea()->adjustedScrollOffset()) : FloatSize());
scrollContainerRelativePaddingBoxRect.move(scrollOffset);
}
LayoutRect scrollContainerRelativeContainingBlockRect(scrollContainerRelativePaddingBoxRect);
// This is removing the padding of the containing block's overflow rect to get the flow // This is removing the padding of the containing block's overflow rect to get the flow
// box rectangle and removing the margin of the sticky element to ensure that space between // box rectangle and removing the margin of the sticky element to ensure that space between
// the sticky element and its containing flow box. It is an open issue whether the margin // the sticky element and its containing flow box. It is an open issue whether the margin
// should collapse (See https://www.w3.org/TR/css-position-3/#sticky-pos). // should collapse (See https://www.w3.org/TR/css-position-3/#sticky-pos).
containerContentRect.contractEdges( scrollContainerRelativeContainingBlockRect.contractEdges(
minimumValueForLength(containingBlock->style()->paddingTop(), maxContainerWidth) + minimumValueForLength(style()->marginTop(), maxWidth), minimumValueForLength(containingBlock->style()->paddingTop(), maxContainerWidth) + minimumValueForLength(style()->marginTop(), maxWidth),
minimumValueForLength(containingBlock->style()->paddingRight(), maxContainerWidth) + minimumValueForLength(style()->marginRight(), maxWidth), minimumValueForLength(containingBlock->style()->paddingRight(), maxContainerWidth) + minimumValueForLength(style()->marginRight(), maxWidth),
minimumValueForLength(containingBlock->style()->paddingBottom(), maxContainerWidth) + minimumValueForLength(style()->marginBottom(), maxWidth), minimumValueForLength(containingBlock->style()->paddingBottom(), maxContainerWidth) + minimumValueForLength(style()->marginBottom(), maxWidth),
minimumValueForLength(containingBlock->style()->paddingLeft(), maxContainerWidth) + minimumValueForLength(style()->marginLeft(), maxWidth)); minimumValueForLength(containingBlock->style()->paddingLeft(), maxContainerWidth) + minimumValueForLength(style()->marginLeft(), maxWidth));
// Map to the scroll ancestor. constraints.setScrollContainerRelativeContainingBlockRect(FloatRect(scrollContainerRelativeContainingBlockRect));
FloatRect scrollContainerRelativeContainingBlockRect(containingBlock->localToAncestorQuad(FloatRect(containerContentRect), scrollAncestor).boundingBox());
FloatSize scrollOffset(scrollAncestor ? toFloatSize(scrollAncestor->getScrollableArea()->adjustedScrollOffset()) : FloatSize());
// The sticky position constraint rects should be independent of the current scroll position, so after
// mapping we add in the scroll position to get the container's position within the ancestor scroller's
// unscrolled layout overflow.
if (containingBlock != scrollAncestor)
scrollContainerRelativeContainingBlockRect.move(scrollOffset);
constraints.setScrollContainerRelativeContainingBlockRect(scrollContainerRelativeContainingBlockRect);
FloatRect stickyBoxRect = isLayoutInline() FloatRect stickyBoxRect = isLayoutInline()
? FloatRect(toLayoutInline(this)->linesBoundingBox()) ? FloatRect(toLayoutInline(this)->linesBoundingBox())
...@@ -736,16 +744,13 @@ void LayoutBoxModelObject::updateStickyPositionConstraints() const ...@@ -736,16 +744,13 @@ void LayoutBoxModelObject::updateStickyPositionConstraints() const
containingBlock->flipForWritingMode(flippedStickyBoxRect); containingBlock->flipForWritingMode(flippedStickyBoxRect);
FloatPoint stickyLocation = flippedStickyBoxRect.location() + skippedContainersOffset; FloatPoint stickyLocation = flippedStickyBoxRect.location() + skippedContainersOffset;
// TODO(flackr): Unfortunate to call localToAncestorQuad again, but we can't just offset from the previously computed rect if there are transforms. // The scrollContainerRelativePaddingBoxRect's position is the padding box so we need to remove the border when finding
// Map to the scroll ancestor. // the position of the sticky box within the scroll ancestor if the container is not our scroll ancestor.
FloatRect scrollContainerRelativeContainerFrame = containingBlock->localToAncestorQuad(FloatRect(FloatPoint(), FloatSize(containingBlock->size())), scrollAncestor).boundingBox(); if (containingBlock != scrollAncestor) {
// The sticky position constraint rects should be independent of the current scroll position, so after FloatSize containerBorderOffset(containingBlock->borderLeft(), containingBlock->borderTop());
// mapping we add in the scroll position to get the container's position within the ancestor scroller's stickyLocation -= containerBorderOffset;
// unscrolled layout overflow. }
if (containingBlock != scrollAncestor) constraints.setScrollContainerRelativeStickyBoxRect(FloatRect(scrollContainerRelativePaddingBoxRect.location() + toFloatSize(stickyLocation), flippedStickyBoxRect.size()));
scrollContainerRelativeContainerFrame.move(scrollOffset);
constraints.setScrollContainerRelativeStickyBoxRect(FloatRect(scrollContainerRelativeContainerFrame.location() + toFloatSize(stickyLocation), flippedStickyBoxRect.size()));
// We skip the right or top sticky offset if there is not enough space to honor both the left/right or top/bottom offsets. // We skip the right or top sticky offset if there is not enough space to honor both the left/right or top/bottom offsets.
LayoutUnit horizontalOffsets = minimumValueForLength(style()->right(), LayoutUnit(constrainingSize.width())) + LayoutUnit horizontalOffsets = minimumValueForLength(style()->right(), LayoutUnit(constrainingSize.width())) +
...@@ -753,8 +758,8 @@ void LayoutBoxModelObject::updateStickyPositionConstraints() const ...@@ -753,8 +758,8 @@ void LayoutBoxModelObject::updateStickyPositionConstraints() const
bool skipRight = false; bool skipRight = false;
bool skipLeft = false; bool skipLeft = false;
if (!style()->left().isAuto() && !style()->right().isAuto()) { if (!style()->left().isAuto() && !style()->right().isAuto()) {
if (horizontalOffsets > containerContentRect.width() if (horizontalOffsets > scrollContainerRelativeContainingBlockRect.width()
|| horizontalOffsets + containerContentRect.width() > constrainingSize.width()) { || horizontalOffsets + scrollContainerRelativeContainingBlockRect.width() > constrainingSize.width()) {
skipRight = style()->isLeftToRightDirection(); skipRight = style()->isLeftToRightDirection();
skipLeft = !skipRight; skipLeft = !skipRight;
} }
...@@ -776,8 +781,8 @@ void LayoutBoxModelObject::updateStickyPositionConstraints() const ...@@ -776,8 +781,8 @@ void LayoutBoxModelObject::updateStickyPositionConstraints() const
LayoutUnit verticalOffsets = minimumValueForLength(style()->top(), LayoutUnit(constrainingSize.height())) + LayoutUnit verticalOffsets = minimumValueForLength(style()->top(), LayoutUnit(constrainingSize.height())) +
minimumValueForLength(style()->bottom(), LayoutUnit(constrainingSize.height())); minimumValueForLength(style()->bottom(), LayoutUnit(constrainingSize.height()));
if (!style()->top().isAuto() && !style()->bottom().isAuto()) { if (!style()->top().isAuto() && !style()->bottom().isAuto()) {
if (verticalOffsets > containerContentRect.height() if (verticalOffsets > scrollContainerRelativeContainingBlockRect.height()
|| verticalOffsets + containerContentRect.height() > constrainingSize.height()) { || verticalOffsets + scrollContainerRelativeContainingBlockRect.height() > constrainingSize.height()) {
skipBottom = true; skipBottom = true;
} }
} }
......
...@@ -50,6 +50,29 @@ TEST_F(LayoutBoxModelObjectTest, StickyPositionConstraints) ...@@ -50,6 +50,29 @@ TEST_F(LayoutBoxModelObjectTest, StickyPositionConstraints)
ASSERT_EQ(IntRect(15, 115, 100, 100), enclosingIntRect(getScrollContainerRelativeStickyBoxRect(constraints))); ASSERT_EQ(IntRect(15, 115, 100, 100), enclosingIntRect(getScrollContainerRelativeStickyBoxRect(constraints)));
} }
// Verifies that the sticky constraints are not affected by transforms
TEST_F(LayoutBoxModelObjectTest, StickyPositionTransforms)
{
setBodyInnerHTML("<style>#sticky { position: sticky; top: 0; width: 100px; height: 100px; transform: scale(2); transform-origin: top left; }"
"#container { box-sizing: border-box; position: relative; top: 100px; height: 400px; width: 200px; padding: 10px; border: 5px solid black; transform: scale(2); transform-origin: top left; }"
"#scroller { height: 100px; overflow: auto; position: relative; top: 200px; }"
".spacer { height: 1000px; }</style>"
"<div id='scroller'><div id='container'><div id='sticky'></div></div><div class='spacer'></div></div>");
LayoutBoxModelObject* scroller = toLayoutBoxModelObject(getLayoutObjectByElementId("scroller"));
scroller->getScrollableArea()->scrollToYOffset(50);
ASSERT_EQ(50.0, scroller->getScrollableArea()->scrollYOffset());
LayoutBoxModelObject* sticky = toLayoutBoxModelObject(getLayoutObjectByElementId("sticky"));
sticky->updateStickyPositionConstraints();
ASSERT_EQ(scroller->layer(), sticky->layer()->ancestorOverflowLayer());
const StickyPositionScrollingConstraints& constraints = scroller->getScrollableArea()->stickyConstraintsMap().get(sticky->layer());
ASSERT_EQ(0.f, constraints.topOffset());
// The coordinates of the constraint rects should all be with respect to the unscrolled scroller.
ASSERT_EQ(IntRect(15, 115, 170, 370), enclosingIntRect(getScrollContainerRelativeContainingBlockRect(constraints)));
ASSERT_EQ(IntRect(15, 115, 100, 100), enclosingIntRect(getScrollContainerRelativeStickyBoxRect(constraints)));
}
// Verifies that the sticky constraints are correctly computed. // Verifies that the sticky constraints are correctly computed.
TEST_F(LayoutBoxModelObjectTest, StickyPositionPercentageStyles) TEST_F(LayoutBoxModelObjectTest, StickyPositionPercentageStyles)
{ {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment