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 9d8908a99e3df24820b07ec9299ba7befdb89d02..8b58376fea7a9963e5de4954ef1d3a79fea323a1 100644 |
--- a/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp |
+++ b/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp |
@@ -282,8 +282,34 @@ 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 { |
+ // This may get re-added to viewport constrained objects if the object went |
+ // from sticky to fixed. |
+ frameView->removeViewportConstrainedObject(this); |
+ frameView->removeStickyPositionObject(); |
+ |
+ // Remove sticky constraints for this layer. |
+ if (layer()) { |
+ DisableCompositingQueryAsserts disabler; |
+ if (const PaintLayer* ancestorOverflowLayer = layer()->ancestorOverflowLayer()) |
+ ancestorOverflowLayer->getScrollableArea()->invalidateStickyConstraintsFor(layer()); |
+ } |
+ } |
+ } |
+ |
if (newStyleIsViewportConstained != oldStyleIsViewportConstrained) { |
if (newStyleIsViewportConstained && layer()) |
frameView->addViewportConstrainedObject(this); |
@@ -293,6 +319,19 @@ void LayoutBoxModelObject::styleDidChange(StyleDifference diff, const ComputedSt |
} |
} |
+void LayoutBoxModelObject::invalidateStickyConstraints() |
+{ |
+ 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* ancestorOverflowLayer = layer()->ancestorOverflowLayer()) |
+ ancestorOverflowLayer->getScrollableArea()->invalidateAllStickyConstraints(); |
+} |
+ |
void LayoutBoxModelObject::createLayer(PaintLayerType type) |
{ |
ASSERT(!m_layer); |
@@ -598,6 +637,134 @@ LayoutSize LayoutBoxModelObject::relativePositionOffset() const |
return offset; |
} |
+void LayoutBoxModelObject::updateStickyPositionConstraints() const |
+{ |
+ // TODO(flackr): This method is reasonably complicated and should have some direct unit testing. |
+ const FloatSize constrainingSize = computeStickyConstrainingRect().size(); |
+ |
+ PaintLayerScrollableArea* scrollableArea = layer()->ancestorOverflowLayer()->getScrollableArea(); |
+ StickyPositionScrollingConstraints 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 = layer()->ancestorOverflowLayer()->isRootLayer() ? nullptr : toLayoutBox(layer()->ancestorOverflowLayer()->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(); |
+ |
+ // 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()->getScrollableArea()->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(), LayoutUnit(constrainingSize.width())) + |
+ minimumValueForLength(style()->left(), LayoutUnit(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(), LayoutUnit(constrainingSize.width()))); |
+ constraints.addAnchorEdge(StickyPositionScrollingConstraints::AnchorEdgeLeft); |
+ } |
+ |
+ if (!style()->right().isAuto() && !skipRight) { |
+ constraints.setRightOffset(minimumValueForLength(style()->right(), LayoutUnit(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(), 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()) { |
+ skipBottom = true; |
+ } |
+ } |
+ |
+ if (!style()->top().isAuto()) { |
+ constraints.setTopOffset(minimumValueForLength(style()->top(), LayoutUnit(constrainingSize.height()))); |
+ constraints.addAnchorEdge(StickyPositionScrollingConstraints::AnchorEdgeTop); |
+ } |
+ |
+ if (!style()->bottom().isAuto() && !skipBottom) { |
+ constraints.setBottomOffset(minimumValueForLength(style()->bottom(), LayoutUnit(constrainingSize.height()))); |
+ constraints.addAnchorEdge(StickyPositionScrollingConstraints::AnchorEdgeBottom); |
+ } |
+ scrollableArea->stickyConstraintsMap().set(layer(), constraints); |
+} |
+ |
+FloatRect LayoutBoxModelObject::computeStickyConstrainingRect() const |
+{ |
+ if (layer()->ancestorOverflowLayer()->isRootLayer()) |
+ return view()->frameView()->visibleContentRect(); |
+ |
+ LayoutBox* enclosingClippingBox = toLayoutBox(layer()->ancestorOverflowLayer()->layoutObject()); |
+ FloatRect constrainingRect; |
+ constrainingRect = FloatRect(enclosingClippingBox->overflowClipRect(LayoutPoint())); |
+ constrainingRect.move(enclosingClippingBox->paddingLeft(), enclosingClippingBox->paddingTop()); |
+ constrainingRect.contract(FloatSize(enclosingClippingBox->paddingLeft() + enclosingClippingBox->paddingRight(), |
+ enclosingClippingBox->paddingTop() + enclosingClippingBox->paddingBottom())); |
+ return constrainingRect; |
+} |
+ |
+LayoutSize LayoutBoxModelObject::stickyPositionOffset() const |
+{ |
+ const PaintLayer* ancestorOverflowLayer = layer()->ancestorOverflowLayer(); |
+ // TODO: Force compositing input update if we ask for offset before compositing inputs have been computed? |
+ if (!ancestorOverflowLayer) |
+ return LayoutSize(); |
+ FloatRect constrainingRect = computeStickyConstrainingRect(); |
+ PaintLayerScrollableArea* scrollableArea = ancestorOverflowLayer->getScrollableArea(); |
+ |
+ // The sticky offset is physical, so we can just return the delta computed in absolute coords (though it may be wrong with transforms). |
+ // TODO: Force compositing input update if we ask for offset with stale compositing inputs. |
+ if (!scrollableArea->stickyConstraintsMap().contains(layer())) |
+ return LayoutSize(); |
+ return LayoutSize(scrollableArea->stickyConstraintsMap().get(layer()).computeStickyOffset(constrainingRect)); |
+} |
+ |
LayoutPoint LayoutBoxModelObject::adjustedPositionRelativeToOffsetParent(const LayoutPoint& startPoint) const |
{ |
// If the element is the HTML body element or doesn't have a parent |
@@ -620,7 +787,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()) { |
@@ -642,7 +809,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 |
@@ -881,10 +1054,10 @@ const LayoutObject* LayoutBoxModelObject::pushMappingToContainer(const LayoutBox |
TransformationMatrix t; |
getTransformFromContainer(container, containerOffset, t); |
t.translateRight(adjustmentForSkippedAncestor.width().toFloat(), adjustmentForSkippedAncestor.height().toFloat()); |
- geometryMap.push(this, t, flags); |
+ geometryMap.push(this, t, flags, LayoutSize()); |
} else { |
containerOffset += adjustmentForSkippedAncestor; |
- geometryMap.push(this, containerOffset, flags); |
+ geometryMap.push(this, containerOffset, flags, LayoutSize()); |
} |
return ancestorSkipped ? ancestorToStopAt : container; |