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 @@
#include "core/paint/PaintLayer.h"
#include "core/style/ShadowList.h"
#include "platform/LengthFunctions.h"
#include "platform/geometry/TransformState.h"
#include "wtf/PtrUtil.h"
namespace blink {
......@@ -701,33 +702,40 @@ void LayoutBoxModelObject::updateStickyPositionConstraints() const
}
LayoutBox* scrollAncestor = layer()->ancestorOverflowLayer()->isRootLayer() ? nullptr : toLayoutBox(layer()->ancestorOverflowLayer()->layoutObject());
LayoutRect containerContentRect = containingBlock->layoutOverflowRect();
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
// containingBlockLogicalWidthForContent). It's unclear whether this is totally fine.
// Compute the container-relative area within which the sticky element is allowed to move.
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
// 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
// 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()->paddingRight(), maxContainerWidth) + minimumValueForLength(style()->marginRight(), maxWidth),
minimumValueForLength(containingBlock->style()->paddingBottom(), maxContainerWidth) + minimumValueForLength(style()->marginBottom(), maxWidth),
minimumValueForLength(containingBlock->style()->paddingLeft(), maxContainerWidth) + minimumValueForLength(style()->marginLeft(), maxWidth));
// Map to the scroll ancestor.
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);
constraints.setScrollContainerRelativeContainingBlockRect(FloatRect(scrollContainerRelativeContainingBlockRect));
FloatRect stickyBoxRect = isLayoutInline()
? FloatRect(toLayoutInline(this)->linesBoundingBox())
......@@ -736,16 +744,13 @@ void LayoutBoxModelObject::updateStickyPositionConstraints() const
containingBlock->flipForWritingMode(flippedStickyBoxRect);
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.
// Map to the scroll ancestor.
FloatRect scrollContainerRelativeContainerFrame = containingBlock->localToAncestorQuad(FloatRect(FloatPoint(), FloatSize(containingBlock->size())), scrollAncestor).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.
if (containingBlock != scrollAncestor)
scrollContainerRelativeContainerFrame.move(scrollOffset);
constraints.setScrollContainerRelativeStickyBoxRect(FloatRect(scrollContainerRelativeContainerFrame.location() + toFloatSize(stickyLocation), flippedStickyBoxRect.size()));
// The scrollContainerRelativePaddingBoxRect's position is the padding box so we need to remove the border when finding
// the position of the sticky box within the scroll ancestor if the container is not our scroll ancestor.
if (containingBlock != scrollAncestor) {
FloatSize containerBorderOffset(containingBlock->borderLeft(), containingBlock->borderTop());
stickyLocation -= containerBorderOffset;
}
constraints.setScrollContainerRelativeStickyBoxRect(FloatRect(scrollContainerRelativePaddingBoxRect.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.
LayoutUnit horizontalOffsets = minimumValueForLength(style()->right(), LayoutUnit(constrainingSize.width())) +
......@@ -753,8 +758,8 @@ void LayoutBoxModelObject::updateStickyPositionConstraints() const
bool skipRight = false;
bool skipLeft = false;
if (!style()->left().isAuto() && !style()->right().isAuto()) {
if (horizontalOffsets > containerContentRect.width()
|| horizontalOffsets + containerContentRect.width() > constrainingSize.width()) {
if (horizontalOffsets > scrollContainerRelativeContainingBlockRect.width()
|| horizontalOffsets + scrollContainerRelativeContainingBlockRect.width() > constrainingSize.width()) {
skipRight = style()->isLeftToRightDirection();
skipLeft = !skipRight;
}
......@@ -776,8 +781,8 @@ void LayoutBoxModelObject::updateStickyPositionConstraints() const
LayoutUnit verticalOffsets = minimumValueForLength(style()->top(), LayoutUnit(constrainingSize.height())) +
minimumValueForLength(style()->bottom(), LayoutUnit(constrainingSize.height()));
if (!style()->top().isAuto() && !style()->bottom().isAuto()) {
if (verticalOffsets > containerContentRect.height()
|| verticalOffsets + containerContentRect.height() > constrainingSize.height()) {
if (verticalOffsets > scrollContainerRelativeContainingBlockRect.height()
|| verticalOffsets + scrollContainerRelativeContainingBlockRect.height() > constrainingSize.height()) {
skipBottom = true;
}
}
......
......@@ -50,6 +50,29 @@ TEST_F(LayoutBoxModelObjectTest, StickyPositionConstraints)
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.
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