| 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 e5294b73ddbcbf4e736ab70286b6ded5453db3f1..38daffa8d980f8920dc7baee2e55bb905f03e760 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)
|
| {
|
| // If the current paint invalidation container is not a stacking context and this object is
|
| @@ -612,6 +651,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
|
| @@ -634,7 +801,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()) {
|
| @@ -656,7 +823,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
|
| @@ -889,10 +1062,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;
|
|
|