Chromium Code Reviews| 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 af2bed6fa3b128e709ace02be291992f5c2867c9..aef888475e79c591ddae5ba2418752768fab197a 100644 |
| --- a/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp |
| +++ b/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp |
| @@ -282,8 +282,27 @@ 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) { |
|
chrishtr
2016/03/24 17:36:02
Do you need to invalidate sticky constraints here?
flackr
2016/03/29 16:02:32
Yes, but we're currently invalidating sticky const
chrishtr
2016/03/29 16:28:19
Does the code do that? Which line of code implemen
|
| + 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(); |
| + } |
| + } |
| + |
| if (newStyleIsViewportConstained != oldStyleIsViewportConstrained) { |
| if (newStyleIsViewportConstained && layer()) |
| frameView->addViewportConstrainedObject(this); |
| @@ -293,6 +312,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 |
| @@ -610,6 +642,133 @@ LayoutSize LayoutBoxModelObject::relativePositionOffset() const |
| return offset; |
| } |
| +void LayoutBoxModelObject::updateStickyPositionConstraints() const |
|
chrishtr
2016/03/24 17:36:02
It would be good to directly unittest this a bit.
flackr
2016/03/29 16:02:32
Done.
|
| +{ |
| + 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 |
| @@ -632,7 +791,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()) { |
| @@ -654,7 +813,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 |
| @@ -887,10 +1052,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; |