Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(503)

Unified Diff: third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp

Issue 1308273010: Adapt and reland old position sticky implementation (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: WIP - Cache sticky constraints on ancestor PaintLayerScrollableArea. Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp
diff --git a/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp b/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp
index 3cf9c29073131c37b32e3e797e6b6b4b05d36186..5fbcbe314ba4a4f33ebd92bb7efbafc5c6b4f09b 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp
@@ -266,8 +266,29 @@ void LayoutBoxModelObject::styleDidChange(StyleDifference diff, const ComputedSt
}
if (FrameView *frameView = view()->frameView()) {
- bool newStyleIsViewportConstained = style()->hasViewportConstrainedPosition();
- bool oldStyleIsViewportConstrained = oldStyle && oldStyle->hasViewportConstrainedPosition();
+ bool newStyleIsViewportConstained = style()->position() == FixedPosition;
+ bool oldStyleIsViewportConstrained = oldStyle && oldStyle->position() == FixedPosition;
+ bool newStyleIsSticky = style()->position() == StickyPosition;
+ bool oldStyleIsSticky = oldStyle && oldStyle->position() == StickyPosition;
+
+ if (newStyleIsSticky != oldStyleIsSticky) {
+ if (newStyleIsSticky) {
+ frameView->addStickyPositionObject();
+ // During compositing inputs update we'll have the scroll
+ // ancestor without having to walk up the tree and can compute
+ // the sticky position constraints then.
+ if (layer())
+ layer()->setNeedsCompositingInputsUpdate();
+ } else {
+ // TODO: This should only remove the constraint for the current object.
+ invalidateScrollAncestorConstraints();
+ // This may get re-added to viewport constrained objects if the object went
+ // from sticky to fixed.
+ frameView->removeViewportConstrainedObject(this);
+ frameView->removeStickyPositionObject();
+ }
+ }
+
if (newStyleIsViewportConstained != oldStyleIsViewportConstrained) {
if (newStyleIsViewportConstained && layer())
frameView->addViewportConstrainedObject(this);
@@ -340,6 +361,18 @@ static bool hasPercentageTransform(const ComputedStyle& style)
|| (style.transformOriginY() != Length(50, Percent) && style.transformOriginY().hasPercent());
}
+void LayoutBoxModelObject::invalidateScrollAncestorConstraints()
+{
+ if (!layer())
+ return;
+ // This intentionally uses the stale ancestor overflow layer compositing
+ // input as if we have saved constraints for this layer they were saved
+ // in the previous frame.
+ DisableCompositingQueryAsserts disabler;
+ if (const PaintLayer* ancestorScrollingLayer = enclosingLayer()->ancestorOverflowLayer())
+ ancestorScrollingLayer->scrollableArea()->invalidateStickyConstraints();
+}
+
void LayoutBoxModelObject::invalidateTreeIfNeeded(PaintInvalidationState& paintInvalidationState)
{
ASSERT(!needsLayout());
@@ -595,6 +628,146 @@ LayoutSize LayoutBoxModelObject::relativePositionOffset() const
return offset;
}
+static inline LayoutBox* findScrollAncestor(const LayoutObject* startObject)
+{
+ PaintLayer* layer = startObject->enclosingLayer();
+ const PaintLayer* scrollAncestor = layer->ancestorOverflowLayer();
+ if (!scrollAncestor || scrollAncestor->isRootLayer())
+ return nullptr;
+ return toLayoutBox(scrollAncestor->layoutObject());
+}
+
+const StickyPositionScrollingConstraints& LayoutBoxModelObject::ensureStickyPositionConstraints(const FloatSize& constrainingSize) const
+{
+ const PaintLayer* scrollAncestorLayer = enclosingLayer()->ancestorOverflowLayer();
+ PaintLayerScrollableArea* scrollableArea = enclosingLayer()->ancestorOverflowLayer()->scrollableArea();
+ auto addResult = scrollableArea->stickyConstraintsMap().add(layer(), StickyPositionScrollingConstraints());
+ StickyPositionScrollingConstraints& constraints = addResult.storedValue->value;
+ if (!addResult.isNewEntry)
+ return constraints;
+
+ FloatSize skippedContainersOffset;
+ LayoutBlock* containingBlock = this->containingBlock();
+ // Skip anonymous containing blocks.
+ while (containingBlock->isAnonymous()) {
+ skippedContainersOffset += toFloatSize(FloatPoint(containingBlock->frameRect().location()));
+ containingBlock = containingBlock->containingBlock();
+ }
+ LayoutBox* scrollAncestor = scrollAncestorLayer->isRootLayer() ? nullptr : toLayoutBox(scrollAncestorLayer->layoutObject());
+
+ LayoutRect containerContentRect = containingBlock->contentBoxRect();
+ LayoutUnit maxWidth = containingBlock->availableLogicalWidth();
+
+ // 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.
+ containerContentRect.contractEdges(
+ minimumValueForLength(style()->marginTop(), maxWidth),
+ minimumValueForLength(style()->marginRight(), maxWidth),
+ minimumValueForLength(style()->marginBottom(), maxWidth),
+ minimumValueForLength(style()->marginLeft(), maxWidth));
+
+ // Map to the scroll ancestor.
+ constraints.setScrollContainerRelativeContainingBlockRect(containingBlock->localToAncestorQuad(FloatRect(containerContentRect), scrollAncestor).boundingBox());
+
+ FloatRect stickyBoxRect = isLayoutInline()
+ ? FloatRect(toLayoutInline(this)->linesBoundingBox())
+ : FloatRect(toLayoutBox(this)->frameRect());
+ FloatRect flippedStickyBoxRect = stickyBoxRect;
+ 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();
chrishtr 2016/02/23 16:31:51 For these localToAncestor* methods, you should con
flackr 2016/03/07 19:07:33 I think we actually want neither, but I will think
+
+ // If the containing block is our scroll ancestor, its location will not include the scroll offset which we need to include as
+ // part of the sticky box rect so we include it here.
+ if (containingBlock->hasOverflowClip()) {
+ FloatSize scrollOffset(toFloatSize(containingBlock->layer()->scrollableArea()->adjustedScrollOffset()));
+ stickyLocation -= 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.
+ LayoutUnit horizontalOffsets = minimumValueForLength(style()->right(), constrainingSize.width()) +
+ minimumValueForLength(style()->left(), constrainingSize.width());
+ bool skipRight = false;
+ bool skipLeft = false;
+ if (!style()->left().isAuto() && !style()->right().isAuto()) {
+ if (horizontalOffsets > containerContentRect.width()
+ || horizontalOffsets + containerContentRect.width() > constrainingSize.width()) {
+ skipRight = style()->isLeftToRightDirection();
+ skipLeft = !skipRight;
+ }
+ }
+
+ if (!style()->left().isAuto() && !skipLeft) {
+ constraints.setLeftOffset(minimumValueForLength(style()->left(), constrainingSize.width()));
+ constraints.addAnchorEdge(StickyPositionScrollingConstraints::AnchorEdgeLeft);
+ }
+
+ if (!style()->right().isAuto() && !skipRight) {
+ constraints.setRightOffset(minimumValueForLength(style()->right(), constrainingSize.width()));
+ constraints.addAnchorEdge(StickyPositionScrollingConstraints::AnchorEdgeRight);
+ }
+
+ bool skipBottom = false;
+ // TODO(flackr): Exclude top or bottom edge offset depending on the writing mode when related
+ // sections are fixed in spec: http://lists.w3.org/Archives/Public/www-style/2014May/0286.html
+ LayoutUnit verticalOffsets = minimumValueForLength(style()->top(), constrainingSize.height()) +
+ minimumValueForLength(style()->bottom(), constrainingSize.height());
+ if (!style()->top().isAuto() && !style()->bottom().isAuto()) {
+ if (verticalOffsets > containerContentRect.height()
+ || verticalOffsets + containerContentRect.height() > constrainingSize.height()) {
+ skipBottom = true;
+ }
+ }
+
+ if (!style()->top().isAuto()) {
+ constraints.setTopOffset(minimumValueForLength(style()->top(), constrainingSize.height()));
+ constraints.addAnchorEdge(StickyPositionScrollingConstraints::AnchorEdgeTop);
+ }
+
+ if (!style()->bottom().isAuto() && !skipBottom) {
+ constraints.setBottomOffset(minimumValueForLength(style()->bottom(), constrainingSize.height()));
+ constraints.addAnchorEdge(StickyPositionScrollingConstraints::AnchorEdgeBottom);
+ }
+ return constraints;
+}
+
+FloatRect LayoutBoxModelObject::computeStickyConstrainingRect() const
+{
+ FloatRect constrainingRect;
+
+ ASSERT(hasLayer());
+ LayoutBox* enclosingClippingBox = findScrollAncestor(this);
+ if (enclosingClippingBox) {
+ constrainingRect = FloatRect(enclosingClippingBox->overflowClipRect(LayoutPoint()));
+ constrainingRect.move(enclosingClippingBox->paddingLeft(), enclosingClippingBox->paddingTop());
+ constrainingRect.contract(FloatSize(enclosingClippingBox->paddingLeft() + enclosingClippingBox->paddingRight(),
+ enclosingClippingBox->paddingTop() + enclosingClippingBox->paddingBottom()));
+ } else {
+ constrainingRect = view()->frameView()->visibleContentRect();
+ }
+
+ return constrainingRect;
+}
+
+LayoutSize LayoutBoxModelObject::stickyPositionOffset() const
+{
+ // TODO: Force compositing input update if we ask for offset before this is
+ // ready rather than returning no offset or stale offset.
+ if (layer()->needsCompositingInputsUpdate())
+ return LayoutSize();
+ FloatRect constrainingRect = computeStickyConstrainingRect();
+ const StickyPositionScrollingConstraints& constraints = ensureStickyPositionConstraints(constrainingRect.size());
+
+ // The sticky offset is physical, so we can just return the delta computed in absolute coords (though it may be wrong with transforms).
+ return LayoutSize(constraints.computeStickyOffset(constrainingRect));
+}
+
LayoutPoint LayoutBoxModelObject::adjustedPositionRelativeToOffsetParent(const LayoutPoint& startPoint) const
{
// If the element is the HTML body element or doesn't have a parent
@@ -617,7 +790,7 @@ LayoutPoint LayoutBoxModelObject::adjustedPositionRelativeToOffsetParent(const L
referencePoint.move(-toLayoutBox(offsetParent)->borderLeft(), -toLayoutBox(offsetParent)->borderTop());
if (!isOutOfFlowPositioned() || flowThreadContainingBlock()) {
if (isInFlowPositioned())
- referencePoint.move(relativePositionOffset());
+ referencePoint.move(offsetForInFlowPosition());
LayoutObject* current;
for (current = parent(); current != offsetParent && current->parent(); current = current->parent()) {
@@ -639,7 +812,13 @@ LayoutPoint LayoutBoxModelObject::adjustedPositionRelativeToOffsetParent(const L
LayoutSize LayoutBoxModelObject::offsetForInFlowPosition() const
{
- return isRelPositioned() ? relativePositionOffset() : LayoutSize();
+ if (isRelPositioned())
+ return relativePositionOffset();
+
+ if (isStickyPositioned())
+ return stickyPositionOffset();
+
+ return LayoutSize();
}
LayoutUnit LayoutBoxModelObject::offsetLeft() const
@@ -962,7 +1141,7 @@ const LayoutObject* LayoutBoxModelObject::pushMappingToContainer(const LayoutBox
return nullptr;
bool isInline = isLayoutInline();
- bool isFixedPos = !isInline && style()->position() == FixedPosition;
+ bool isFixedPosition = !isInline && style()->position() == FixedPosition;
bool hasTransform = !isInline && hasLayer() && layer()->transform();
LayoutSize adjustmentForSkippedAncestor;
@@ -974,16 +1153,19 @@ const LayoutObject* LayoutBoxModelObject::pushMappingToContainer(const LayoutBox
bool offsetDependsOnPoint = false;
LayoutSize containerOffset = offsetFromContainer(container, LayoutPoint(), &offsetDependsOnPoint);
+ LayoutSize stickyOffset;
+ if (style()->position() == StickyPosition)
+ stickyOffset = offsetForInFlowPosition();
bool preserve3D = container->style()->preserves3D() || style()->preserves3D();
if (shouldUseTransformFromContainer(container)) {
TransformationMatrix t;
getTransformFromContainer(container, containerOffset, t);
t.translateRight(adjustmentForSkippedAncestor.width().toFloat(), adjustmentForSkippedAncestor.height().toFloat());
- geometryMap.push(this, t, preserve3D, offsetDependsOnPoint, isFixedPos, hasTransform);
+ geometryMap.push(this, t, preserve3D, offsetDependsOnPoint, isFixedPosition, hasTransform, LayoutSize(), stickyOffset);
} else {
containerOffset += adjustmentForSkippedAncestor;
- geometryMap.push(this, containerOffset, preserve3D, offsetDependsOnPoint, isFixedPos, hasTransform);
+ geometryMap.push(this, containerOffset, preserve3D, offsetDependsOnPoint, isFixedPosition, hasTransform, LayoutSize(), stickyOffset);
}
return ancestorSkipped ? ancestorToStopAt : container;

Powered by Google App Engine
This is Rietveld 408576698