blob: ed17e8906fab69a20c20aa973e8f5d52b34b4c37 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com)
* (C) 2005, 2006 Samuel Weinig (sam.weinig@gmail.com)
* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
* Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "core/layout/LayoutBox.h"
#include "core/dom/Document.h"
#include "core/editing/EditingUtilities.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLElement.h"
#include "core/html/HTMLFrameElementBase.h"
#include "core/html/HTMLFrameOwnerElement.h"
#include "core/input/EventHandler.h"
#include "core/layout/HitTestResult.h"
#include "core/layout/LayoutAnalyzer.h"
#include "core/layout/LayoutDeprecatedFlexibleBox.h"
#include "core/layout/LayoutFlexibleBox.h"
#include "core/layout/LayoutGrid.h"
#include "core/layout/LayoutInline.h"
#include "core/layout/LayoutListMarker.h"
#include "core/layout/LayoutMultiColumnFlowThread.h"
#include "core/layout/LayoutMultiColumnSpannerPlaceholder.h"
#include "core/layout/LayoutPart.h"
#include "core/layout/LayoutTableCell.h"
#include "core/layout/LayoutView.h"
#include "core/layout/api/LayoutAPIShim.h"
#include "core/layout/api/LayoutPartItem.h"
#include "core/layout/api/LineLayoutBlockFlow.h"
#include "core/layout/api/LineLayoutBox.h"
#include "core/layout/compositing/PaintLayerCompositor.h"
#include "core/layout/shapes/ShapeOutsideInfo.h"
#include "core/page/AutoscrollController.h"
#include "core/page/Page.h"
#include "core/page/scrolling/SnapCoordinator.h"
#include "core/paint/BackgroundImageGeometry.h"
#include "core/paint/BoxPaintInvalidator.h"
#include "core/paint/BoxPainter.h"
#include "core/paint/PaintLayer.h"
#include "core/style/ShadowList.h"
#include "platform/LengthFunctions.h"
#include "platform/geometry/DoubleRect.h"
#include "platform/geometry/FloatQuad.h"
#include "platform/geometry/FloatRoundedRect.h"
#include "wtf/PtrUtil.h"
#include <algorithm>
#include <math.h>
namespace blink {
// Used by flexible boxes when flexing this element and by table cells.
typedef WTF::HashMap<const LayoutBox*, LayoutUnit> OverrideSizeMap;
static OverrideSizeMap* gExtraInlineOffsetMap = nullptr;
static OverrideSizeMap* gExtraBlockOffsetMap = nullptr;
// Size of border belt for autoscroll. When mouse pointer in border belt,
// autoscroll is started.
static const int autoscrollBeltSize = 20;
static const unsigned backgroundObscurationTestMaxDepth = 4;
struct SameSizeAsLayoutBox : public LayoutBoxModelObject {
LayoutRect frameRect;
LayoutUnit intrinsicContentLogicalHeight;
LayoutRectOutsets marginBoxOutsets;
LayoutUnit preferredLogicalWidth[2];
void* pointers[3];
};
static_assert(sizeof(LayoutBox) == sizeof(SameSizeAsLayoutBox), "LayoutBox should stay small");
LayoutBox::LayoutBox(ContainerNode* node)
: LayoutBoxModelObject(node)
, m_intrinsicContentLogicalHeight(-1)
, m_minPreferredLogicalWidth(-1)
, m_maxPreferredLogicalWidth(-1)
, m_inlineBoxWrapper(nullptr)
{
setIsBox();
}
PaintLayerType LayoutBox::layerTypeRequired() const
{
// hasAutoZIndex only returns true if the element is positioned or a flex-item since
// position:static elements that are not flex-items get their z-index coerced to auto.
if (isPositioned() || createsGroup() || hasClipPath() || hasTransformRelatedProperty()
|| style()->hasCompositorProxy() || hasHiddenBackface() || hasReflection() || style()->specifiesColumns()
|| style()->isStackingContext() || style()->shouldCompositeForCurrentAnimations())
return NormalPaintLayer;
if (hasOverflowClip())
return OverflowClipPaintLayer;
return NoPaintLayer;
}
void LayoutBox::willBeDestroyed()
{
clearOverrideSize();
clearContainingBlockOverrideSize();
clearExtraInlineAndBlockOffests();
if (isOutOfFlowPositioned())
LayoutBlock::removePositionedObject(this);
removeFromPercentHeightContainer();
if (isOrthogonalWritingModeRoot() && !documentBeingDestroyed())
unmarkOrthogonalWritingModeRoot();
ShapeOutsideInfo::removeInfo(*this);
BoxPaintInvalidator::boxWillBeDestroyed(*this);
LayoutBoxModelObject::willBeDestroyed();
}
void LayoutBox::insertedIntoTree()
{
LayoutBoxModelObject::insertedIntoTree();
addScrollSnapMapping();
if (isOrthogonalWritingModeRoot())
markOrthogonalWritingModeRoot();
}
void LayoutBox::willBeRemovedFromTree()
{
if (!documentBeingDestroyed() && isOrthogonalWritingModeRoot())
unmarkOrthogonalWritingModeRoot();
clearScrollSnapMapping();
LayoutBoxModelObject::willBeRemovedFromTree();
}
void LayoutBox::removeFloatingOrPositionedChildFromBlockLists()
{
ASSERT(isFloatingOrOutOfFlowPositioned());
if (documentBeingDestroyed())
return;
if (isFloating()) {
LayoutBlockFlow* parentBlockFlow = nullptr;
for (LayoutObject* curr = parent(); curr; curr = curr->parent()) {
if (curr->isLayoutBlockFlow()) {
LayoutBlockFlow* currBlockFlow = toLayoutBlockFlow(curr);
if (!parentBlockFlow || currBlockFlow->containsFloat(this))
parentBlockFlow = currBlockFlow;
}
}
if (parentBlockFlow) {
parentBlockFlow->markSiblingsWithFloatsForLayout(this);
parentBlockFlow->markAllDescendantsWithFloatsForLayout(this, false);
}
}
if (isOutOfFlowPositioned())
LayoutBlock::removePositionedObject(this);
}
void LayoutBox::styleWillChange(StyleDifference diff, const ComputedStyle& newStyle)
{
const ComputedStyle* oldStyle = style();
if (oldStyle) {
LayoutFlowThread* flowThread = flowThreadContainingBlock();
if (flowThread && flowThread != this)
flowThread->flowThreadDescendantStyleWillChange(this, diff, newStyle);
// The background of the root element or the body element could propagate up to
// the canvas. Just dirty the entire canvas when our style changes substantially.
if ((diff.needsPaintInvalidation() || diff.needsLayout()) && node()
&& (isHTMLHtmlElement(*node()) || isHTMLBodyElement(*node()))) {
view()->setShouldDoFullPaintInvalidation();
if (oldStyle->hasEntirelyFixedBackground() != newStyle.hasEntirelyFixedBackground())
view()->compositor()->setNeedsUpdateFixedBackground();
}
// When a layout hint happens and an object's position style changes, we have to do a layout
// to dirty the layout tree using the old position value now.
if (diff.needsFullLayout() && parent() && oldStyle->position() != newStyle.position()) {
if (!oldStyle->hasOutOfFlowPosition() && newStyle.hasOutOfFlowPosition()) {
// We're about to go out of flow. Before that takes place, we need to mark the
// current containing block chain for preferred widths recalculation.
setNeedsLayoutAndPrefWidthsRecalc(LayoutInvalidationReason::StyleChange);
} else {
markContainerChainForLayout();
}
if (oldStyle->position() == StaticPosition)
setShouldDoFullPaintInvalidation();
else if (newStyle.hasOutOfFlowPosition())
parent()->setChildNeedsLayout();
if (isFloating() && !isOutOfFlowPositioned() && newStyle.hasOutOfFlowPosition())
removeFloatingOrPositionedChildFromBlockLists();
}
// FIXME: This branch runs when !oldStyle, which means that layout was never called
// so what's the point in invalidating the whole view that we never painted?
} else if (isBody()) {
view()->setShouldDoFullPaintInvalidation();
}
LayoutBoxModelObject::styleWillChange(diff, newStyle);
}
void LayoutBox::styleDidChange(StyleDifference diff, const ComputedStyle* oldStyle)
{
// Horizontal writing mode definition is updated in LayoutBoxModelObject::updateFromStyle,
// (as part of the LayoutBoxModelObject::styleDidChange call below). So, we can safely cache the horizontal
// writing mode value before style change here.
bool oldHorizontalWritingMode = isHorizontalWritingMode();
LayoutBoxModelObject::styleDidChange(diff, oldStyle);
if (isFloatingOrOutOfFlowPositioned() && oldStyle && !oldStyle->isFloating() && !oldStyle->hasOutOfFlowPosition() && parent() && parent()->isLayoutBlockFlow())
toLayoutBlockFlow(parent())->childBecameFloatingOrOutOfFlow(this);
const ComputedStyle& newStyle = styleRef();
if (needsLayout() && oldStyle)
removeFromPercentHeightContainer();
if (oldHorizontalWritingMode != isHorizontalWritingMode()) {
if (oldStyle) {
if (isOrthogonalWritingModeRoot())
markOrthogonalWritingModeRoot();
else
unmarkOrthogonalWritingModeRoot();
}
clearPercentHeightDescendants();
}
// If our zoom factor changes and we have a defined scrollLeft/Top, we need to adjust that value into the
// new zoomed coordinate space.
if (hasOverflowClip() && oldStyle && oldStyle->effectiveZoom() != newStyle.effectiveZoom()) {
PaintLayerScrollableArea* scrollableArea = this->getScrollableArea();
ASSERT(scrollableArea);
if (int left = scrollableArea->scrollXOffset()) {
left = (left / oldStyle->effectiveZoom()) * newStyle.effectiveZoom();
scrollableArea->scrollToXOffset(left);
}
if (int top = scrollableArea->scrollYOffset()) {
top = (top / oldStyle->effectiveZoom()) * newStyle.effectiveZoom();
scrollableArea->scrollToYOffset(top);
}
}
// Our opaqueness might have changed without triggering layout.
if (diff.needsPaintInvalidation()) {
LayoutObject* parentToInvalidate = parent();
for (unsigned i = 0; i < backgroundObscurationTestMaxDepth && parentToInvalidate; ++i) {
parentToInvalidate->invalidateBackgroundObscurationStatus();
parentToInvalidate = parentToInvalidate->parent();
}
}
if (isDocumentElement() || isBody()) {
document().view()->recalculateScrollbarOverlayStyle(document().view()->documentBackgroundColor());
document().view()->recalculateCustomScrollbarStyle();
if (LayoutView* layoutView = view()) {
if (PaintLayerScrollableArea* scrollableArea = layoutView->getScrollableArea()) {
if (scrollableArea->horizontalScrollbar() && scrollableArea->horizontalScrollbar()->isCustomScrollbar())
scrollableArea->horizontalScrollbar()->styleChanged();
if (scrollableArea->verticalScrollbar() && scrollableArea->verticalScrollbar()->isCustomScrollbar())
scrollableArea->verticalScrollbar()->styleChanged();
}
}
}
updateShapeOutsideInfoAfterStyleChange(*style(), oldStyle);
updateGridPositionAfterStyleChange(oldStyle);
if (LayoutMultiColumnSpannerPlaceholder* placeholder = this->spannerPlaceholder())
placeholder->layoutObjectInFlowThreadStyleDidChange(oldStyle);
updateBackgroundAttachmentFixedStatusAfterStyleChange();
if (oldStyle) {
LayoutFlowThread* flowThread = flowThreadContainingBlock();
if (flowThread && flowThread != this)
flowThread->flowThreadDescendantStyleDidChange(this, diff, *oldStyle);
updateScrollSnapMappingAfterStyleChange(&newStyle, oldStyle);
}
ASSERT(!isInline() || isAtomicInlineLevel()); // Non-atomic inlines should be LayoutInline or LayoutText, not LayoutBox.
}
void LayoutBox::updateBackgroundAttachmentFixedStatusAfterStyleChange()
{
if (!frameView())
return;
// On low-powered/mobile devices, preventing blitting on a scroll can cause noticeable delays
// when scrolling a page with a fixed background image. As an optimization, assuming there are
// no fixed positoned elements on the page, we can acclerate scrolling (via blitting) if we
// ignore the CSS property "background-attachment: fixed".
bool ignoreFixedBackgroundAttachment = RuntimeEnabledFeatures::fastMobileScrollingEnabled();
if (ignoreFixedBackgroundAttachment)
return;
// An object needs to be repainted on frame scroll when it has background-attachment:fixed.
// LayoutView is responsible for painting root background, thus the root element (and the
// body element if html element has no background) skips painting backgrounds.
bool isBackgroundAttachmentFixedObject = !isDocumentElement() && !backgroundStolenForBeingBody() && styleRef().hasFixedBackgroundImage();
if (isLayoutView() && view()->compositor()->supportsFixedRootBackgroundCompositing()) {
if (styleRef().hasEntirelyFixedBackground())
isBackgroundAttachmentFixedObject = false;
}
setIsBackgroundAttachmentFixedObject(isBackgroundAttachmentFixedObject);
}
void LayoutBox::updateShapeOutsideInfoAfterStyleChange(const ComputedStyle& style, const ComputedStyle* oldStyle)
{
const ShapeValue* shapeOutside = style.shapeOutside();
const ShapeValue* oldShapeOutside = oldStyle ? oldStyle->shapeOutside() : ComputedStyle::initialShapeOutside();
Length shapeMargin = style.shapeMargin();
Length oldShapeMargin = oldStyle ? oldStyle->shapeMargin() : ComputedStyle::initialShapeMargin();
float shapeImageThreshold = style.shapeImageThreshold();
float oldShapeImageThreshold = oldStyle ? oldStyle->shapeImageThreshold() : ComputedStyle::initialShapeImageThreshold();
// FIXME: A future optimization would do a deep comparison for equality. (bug 100811)
if (shapeOutside == oldShapeOutside && shapeMargin == oldShapeMargin && shapeImageThreshold == oldShapeImageThreshold)
return;
if (!shapeOutside)
ShapeOutsideInfo::removeInfo(*this);
else
ShapeOutsideInfo::ensureInfo(*this).markShapeAsDirty();
if (shapeOutside || shapeOutside != oldShapeOutside)
markShapeOutsideDependentsForLayout();
}
void LayoutBox::updateGridPositionAfterStyleChange(const ComputedStyle* oldStyle)
{
if (!oldStyle || !parent() || !parent()->isLayoutGrid())
return;
if (oldStyle->gridColumnStart() == style()->gridColumnStart()
&& oldStyle->gridColumnEnd() == style()->gridColumnEnd()
&& oldStyle->gridRowStart() == style()->gridRowStart()
&& oldStyle->gridRowEnd() == style()->gridRowEnd()
&& oldStyle->order() == style()->order()
&& oldStyle->hasOutOfFlowPosition() == style()->hasOutOfFlowPosition())
return;
// It should be possible to not dirty the grid in some cases (like moving an explicitly placed grid item).
// For now, it's more simple to just always recompute the grid.
toLayoutGrid(parent())->dirtyGrid();
}
void LayoutBox::updateScrollSnapMappingAfterStyleChange(const ComputedStyle* newStyle, const ComputedStyle* oldStyle)
{
SnapCoordinator* snapCoordinator = document().snapCoordinator();
if (!snapCoordinator)
return;
// Scroll snap type has no effect on the viewport defining element instead
// they are handled by the LayoutView.
bool allowsSnapContainer = node() != document().viewportDefiningElement();
ScrollSnapType oldSnapType = oldStyle ? oldStyle->getScrollSnapType() : ScrollSnapTypeNone;
ScrollSnapType newSnapType = newStyle && allowsSnapContainer ? newStyle->getScrollSnapType() : ScrollSnapTypeNone;
if (oldSnapType != newSnapType)
snapCoordinator->snapContainerDidChange(*this, newSnapType);
Vector<LengthPoint> emptyVector;
const Vector<LengthPoint>& oldSnapCoordinate = oldStyle ? oldStyle->scrollSnapCoordinate() : emptyVector;
const Vector<LengthPoint>& newSnapCoordinate = newStyle ? newStyle->scrollSnapCoordinate() : emptyVector;
if (oldSnapCoordinate != newSnapCoordinate)
snapCoordinator->snapAreaDidChange(*this, newSnapCoordinate);
}
void LayoutBox::addScrollSnapMapping()
{
updateScrollSnapMappingAfterStyleChange(style(), nullptr);
}
void LayoutBox::clearScrollSnapMapping()
{
updateScrollSnapMappingAfterStyleChange(nullptr, style());
}
void LayoutBox::updateFromStyle()
{
LayoutBoxModelObject::updateFromStyle();
const ComputedStyle& styleToUse = styleRef();
setFloating(!isOutOfFlowPositioned() && styleToUse.isFloating());
setHasTransformRelatedProperty(styleToUse.hasTransformRelatedProperty());
setHasReflection(styleToUse.boxReflect());
}
void LayoutBox::layout()
{
ASSERT(needsLayout());
LayoutAnalyzer::Scope analyzer(*this);
LayoutObject* child = slowFirstChild();
if (!child) {
clearNeedsLayout();
return;
}
LayoutState state(*this, locationOffset());
while (child) {
child->layoutIfNeeded();
ASSERT(!child->needsLayout());
child = child->nextSibling();
}
invalidateBackgroundObscurationStatus();
clearNeedsLayout();
}
// More IE extensions. clientWidth and clientHeight represent the interior of an object
// excluding border and scrollbar.
DISABLE_CFI_PERF
LayoutUnit LayoutBox::clientWidth() const
{
return m_frameRect.width() - borderLeft() - borderRight() - verticalScrollbarWidth();
}
DISABLE_CFI_PERF
LayoutUnit LayoutBox::clientHeight() const
{
return m_frameRect.height() - borderTop() - borderBottom() - horizontalScrollbarHeight();
}
int LayoutBox::pixelSnappedClientWidth() const
{
return snapSizeToPixel(clientWidth(), location().x() + clientLeft());
}
DISABLE_CFI_PERF
int LayoutBox::pixelSnappedClientHeight() const
{
return snapSizeToPixel(clientHeight(), location().y() + clientTop());
}
int LayoutBox::pixelSnappedOffsetWidth(const Element*) const
{
return snapSizeToPixel(offsetWidth(), location().x() + clientLeft());
}
int LayoutBox::pixelSnappedOffsetHeight(const Element*) const
{
return snapSizeToPixel(offsetHeight(), location().y() + clientTop());
}
LayoutUnit LayoutBox::scrollWidth() const
{
if (hasOverflowClip())
return getScrollableArea()->scrollWidth();
// For objects with visible overflow, this matches IE.
// FIXME: Need to work right with writing modes.
if (style()->isLeftToRightDirection())
return std::max(clientWidth(), layoutOverflowRect().maxX() - borderLeft());
return clientWidth() - std::min(LayoutUnit(), layoutOverflowRect().x() - borderLeft());
}
LayoutUnit LayoutBox::scrollHeight() const
{
if (hasOverflowClip())
return getScrollableArea()->scrollHeight();
// For objects with visible overflow, this matches IE.
// FIXME: Need to work right with writing modes.
return std::max(clientHeight(), layoutOverflowRect().maxY() - borderTop());
}
LayoutUnit LayoutBox::scrollLeft() const
{
return hasOverflowClip() ? LayoutUnit(getScrollableArea()->scrollXOffset()) : LayoutUnit();
}
LayoutUnit LayoutBox::scrollTop() const
{
return hasOverflowClip() ? LayoutUnit(getScrollableArea()->scrollYOffset()) : LayoutUnit();
}
int LayoutBox::pixelSnappedScrollWidth() const
{
return snapSizeToPixel(scrollWidth(), location().x() + clientLeft());
}
int LayoutBox::pixelSnappedScrollHeight() const
{
if (hasOverflowClip())
return snapSizeToPixel(getScrollableArea()->scrollHeight(), location().y() + clientTop());
// For objects with visible overflow, this matches IE.
// FIXME: Need to work right with writing modes.
return snapSizeToPixel(scrollHeight(), location().y() + clientTop());
}
void LayoutBox::setScrollLeft(LayoutUnit newLeft)
{
// This doesn't hit in any tests, but since the equivalent code in setScrollTop
// does, presumably this code does as well.
DisableCompositingQueryAsserts disabler;
if (hasOverflowClip())
getScrollableArea()->scrollToXOffset(newLeft, ScrollOffsetClamped, ScrollBehaviorAuto);
}
void LayoutBox::setScrollTop(LayoutUnit newTop)
{
// Hits in compositing/overflow/do-not-assert-on-invisible-composited-layers.html
DisableCompositingQueryAsserts disabler;
if (hasOverflowClip())
getScrollableArea()->scrollToYOffset(newTop, ScrollOffsetClamped, ScrollBehaviorAuto);
}
void LayoutBox::scrollToOffset(const DoubleSize& offset, ScrollBehavior scrollBehavior)
{
// This doesn't hit in any tests, but since the equivalent code in setScrollTop
// does, presumably this code does as well.
DisableCompositingQueryAsserts disabler;
if (hasOverflowClip())
getScrollableArea()->scrollToOffset(offset, ScrollOffsetClamped, scrollBehavior);
}
// Returns true iff we are attempting an autoscroll inside an iframe with scrolling="no".
static bool isDisallowedAutoscroll(HTMLFrameOwnerElement* ownerElement, FrameView* frameView)
{
if (ownerElement && isHTMLFrameElementBase(*ownerElement)) {
HTMLFrameElementBase* frameElementBase = toHTMLFrameElementBase(ownerElement);
if (Page* page = frameView->frame().page()) {
return page->autoscrollController().autoscrollInProgress()
&& frameElementBase->scrollingMode() == ScrollbarAlwaysOff;
}
}
return false;
}
void LayoutBox::scrollRectToVisible(const LayoutRect& rect, const ScrollAlignment& alignX, const ScrollAlignment& alignY, ScrollType scrollType, bool makeVisibleInVisualViewport)
{
ASSERT(scrollType == ProgrammaticScroll || scrollType == UserScroll);
// Presumably the same issue as in setScrollTop. See crbug.com/343132.
DisableCompositingQueryAsserts disabler;
LayoutRect rectToScroll = rect;
if (rectToScroll.width() <= 0)
rectToScroll.setWidth(LayoutUnit(1));
if (rectToScroll.height() <= 0)
rectToScroll.setHeight(LayoutUnit(1));
LayoutBox* parentBox = nullptr;
LayoutRect newRect = rectToScroll;
bool restrictedByLineClamp = false;
if (containingBlock()) {
parentBox = containingBlock();
restrictedByLineClamp = !containingBlock()->style()->lineClamp().isNone();
}
if (hasOverflowClip() && !restrictedByLineClamp) {
// Don't scroll to reveal an overflow layer that is restricted by the -webkit-line-clamp property.
// This will prevent us from revealing text hidden by the slider in Safari RSS.
newRect = getScrollableArea()->scrollIntoView(rectToScroll, alignX, alignY, scrollType);
if (newRect.isEmpty())
return;
} else if (!parentBox && canBeProgramaticallyScrolled()) {
if (FrameView* frameView = this->frameView()) {
HTMLFrameOwnerElement* ownerElement = document().localOwner();
if (!isDisallowedAutoscroll(ownerElement, frameView)) {
if (makeVisibleInVisualViewport) {
frameView->getScrollableArea()->scrollIntoView(rectToScroll, alignX, alignY, scrollType);
} else {
frameView->layoutViewportScrollableArea()->scrollIntoView(rectToScroll, alignX, alignY, scrollType);
}
if (ownerElement && ownerElement->layoutObject()) {
if (frameView->safeToPropagateScrollToParent()) {
parentBox = ownerElement->layoutObject()->enclosingBox();
LayoutView* parentView = ownerElement->layoutObject()->view();
newRect = enclosingLayoutRect(view()->localToAncestorQuad(FloatRect(rectToScroll), parentView, UseTransforms | TraverseDocumentBoundaries).boundingBox());
} else {
parentBox = nullptr;
}
}
}
}
}
// If we are fixed-position, it is useless to scroll the parent.
if (hasLayer() && layer()->scrollsWithViewport())
return;
if (frame()->page()->autoscrollController().autoscrollInProgress())
parentBox = enclosingScrollableBox();
if (parentBox)
parentBox->scrollRectToVisible(newRect, alignX, alignY, scrollType, makeVisibleInVisualViewport);
}
void LayoutBox::absoluteRects(Vector<IntRect>& rects, const LayoutPoint& accumulatedOffset) const
{
rects.append(pixelSnappedIntRect(accumulatedOffset, size()));
}
void LayoutBox::absoluteQuads(Vector<FloatQuad>& quads) const
{
if (LayoutFlowThread* flowThread = flowThreadContainingBlock()) {
flowThread->absoluteQuadsForDescendant(*this, quads);
return;
}
quads.append(localToAbsoluteQuad(FloatRect(0, 0, m_frameRect.width().toFloat(), m_frameRect.height().toFloat())));
}
FloatRect LayoutBox::localBoundingBoxRectForAccessibility() const
{
return FloatRect(0, 0, m_frameRect.width().toFloat(), m_frameRect.height().toFloat());
}
void LayoutBox::updateLayerTransformAfterLayout()
{
// Transform-origin depends on box size, so we need to update the layer transform after layout.
if (hasLayer())
layer()->updateTransformationMatrix();
}
LayoutUnit LayoutBox::constrainLogicalWidthByMinMax(LayoutUnit logicalWidth, LayoutUnit availableWidth, LayoutBlock* cb) const
{
const ComputedStyle& styleToUse = styleRef();
if (!styleToUse.logicalMaxWidth().isMaxSizeNone())
logicalWidth = std::min(logicalWidth, computeLogicalWidthUsing(MaxSize, styleToUse.logicalMaxWidth(), availableWidth, cb));
return std::max(logicalWidth, computeLogicalWidthUsing(MinSize, styleToUse.logicalMinWidth(), availableWidth, cb));
}
LayoutUnit LayoutBox::constrainLogicalHeightByMinMax(LayoutUnit logicalHeight, LayoutUnit intrinsicContentHeight) const
{
const ComputedStyle& styleToUse = styleRef();
if (!styleToUse.logicalMaxHeight().isMaxSizeNone()) {
LayoutUnit maxH = computeLogicalHeightUsing(MaxSize, styleToUse.logicalMaxHeight(), intrinsicContentHeight);
if (maxH != -1)
logicalHeight = std::min(logicalHeight, maxH);
}
return std::max(logicalHeight, computeLogicalHeightUsing(MinSize, styleToUse.logicalMinHeight(), intrinsicContentHeight));
}
LayoutUnit LayoutBox::constrainContentBoxLogicalHeightByMinMax(LayoutUnit logicalHeight, LayoutUnit intrinsicContentHeight) const
{
// If the min/max height and logical height are both percentages we take advantage of already knowing the current resolved percentage height
// to avoid recursing up through our containing blocks again to determine it.
const ComputedStyle& styleToUse = styleRef();
if (!styleToUse.logicalMaxHeight().isMaxSizeNone()) {
if (styleToUse.logicalMaxHeight().type() == Percent && styleToUse.logicalHeight().type() == Percent) {
LayoutUnit availableLogicalHeight(logicalHeight / styleToUse.logicalHeight().value() * 100);
logicalHeight = std::min(logicalHeight, valueForLength(styleToUse.logicalMaxHeight(), availableLogicalHeight));
} else {
LayoutUnit maxHeight(computeContentLogicalHeight(MaxSize, styleToUse.logicalMaxHeight(), intrinsicContentHeight));
if (maxHeight != -1)
logicalHeight = std::min(logicalHeight, maxHeight);
}
}
if (styleToUse.logicalMinHeight().type() == Percent && styleToUse.logicalHeight().type() == Percent) {
LayoutUnit availableLogicalHeight(logicalHeight / styleToUse.logicalHeight().value() * 100);
logicalHeight = std::max(logicalHeight, valueForLength(styleToUse.logicalMinHeight(), availableLogicalHeight));
} else {
logicalHeight = std::max(logicalHeight, computeContentLogicalHeight(MinSize, styleToUse.logicalMinHeight(), intrinsicContentHeight));
}
return logicalHeight;
}
void LayoutBox::setLocationAndUpdateOverflowControlsIfNeeded(const LayoutPoint& location)
{
if (hasOverflowClip()) {
IntSize oldPixelSnappedBorderRectSize = pixelSnappedBorderBoxRect().size();
setLocation(location);
if (pixelSnappedBorderBoxRect().size() != oldPixelSnappedBorderRectSize)
getScrollableArea()->updateAfterLayout();
return;
}
setLocation(location);
}
IntRect LayoutBox::absoluteContentBox() const
{
// This is wrong with transforms and flipped writing modes.
IntRect rect = pixelSnappedIntRect(contentBoxRect());
FloatPoint absPos = localToAbsolute();
rect.move(absPos.x(), absPos.y());
return rect;
}
IntSize LayoutBox::absoluteContentBoxOffset() const
{
IntPoint offset = roundedIntPoint(contentBoxOffset());
FloatPoint absPos = localToAbsolute();
offset.move(absPos.x(), absPos.y());
return toIntSize(offset);
}
FloatQuad LayoutBox::absoluteContentQuad() const
{
LayoutRect rect = contentBoxRect();
return localToAbsoluteQuad(FloatRect(rect));
}
LayoutRect LayoutBox::backgroundRect(BackgroundRectType rectType) const
{
EFillBox backgroundBox = TextFillBox;
// Find the largest background rect of the given opaqueness.
if (const FillLayer* current = &(style()->backgroundLayers())) {
do {
const FillLayer* cur = current;
current = current->next();
if (rectType == BackgroundKnownOpaqueRect) {
if (cur->blendMode() != WebBlendModeNormal || cur->composite() != CompositeSourceOver)
continue;
bool layerKnownOpaque = false;
// Check if the image is opaque and fills the clip.
if (const StyleImage* image = cur->image()) {
if ((cur->repeatX() == RepeatFill || cur->repeatX() == RoundFill)
&& (cur->repeatY() == RepeatFill || cur->repeatY() == RoundFill)
&& image->knownToBeOpaque(*this)) {
layerKnownOpaque = true;
}
}
// The background color is painted into the last layer.
if (!cur->next()) {
Color backgroundColor = resolveColor(CSSPropertyBackgroundColor);
if (!backgroundColor.hasAlpha())
layerKnownOpaque = true;
}
// If neither the image nor the color are opaque then skip this layer.
if (!layerKnownOpaque)
continue;
}
EFillBox currentClip = cur->clip();
// Restrict clip if attachment is local.
if (currentClip == BorderFillBox && cur->attachment() == LocalBackgroundAttachment)
currentClip = PaddingFillBox;
// If we're asking for the clip rect, a content-box clipped fill layer can be scrolled
// into the padding box of the overflow container.
if (rectType == BackgroundClipRect
&& currentClip == ContentFillBox && cur->attachment() == LocalBackgroundAttachment) {
currentClip = PaddingFillBox;
}
backgroundBox = enclosingFillBox(backgroundBox, currentClip);
} while (current);
}
switch (backgroundBox) {
case BorderFillBox:
return borderBoxRect();
break;
case PaddingFillBox:
return paddingBoxRect();
break;
case ContentFillBox:
return contentBoxRect();
break;
default:
break;
}
return LayoutRect();
}
void LayoutBox::addOutlineRects(Vector<LayoutRect>& rects, const LayoutPoint& additionalOffset, IncludeBlockVisualOverflowOrNot) const
{
rects.append(LayoutRect(additionalOffset, size()));
}
bool LayoutBox::canResize() const
{
// We need a special case for <iframe> because they never have
// hasOverflowClip(). However, they do "implicitly" clip their contents, so
// we want to allow resizing them also.
return (hasOverflowClip() || isLayoutIFrame()) && style()->resize() != RESIZE_NONE;
}
void LayoutBox::addLayerHitTestRects(LayerHitTestRects& layerRects, const PaintLayer* currentLayer, const LayoutPoint& layerOffset, const LayoutRect& containerRect) const
{
LayoutPoint adjustedLayerOffset = layerOffset + locationOffset();
LayoutBoxModelObject::addLayerHitTestRects(layerRects, currentLayer, adjustedLayerOffset, containerRect);
}
void LayoutBox::computeSelfHitTestRects(Vector<LayoutRect>& rects, const LayoutPoint& layerOffset) const
{
if (!size().isEmpty())
rects.append(LayoutRect(layerOffset, size()));
}
int LayoutBox::reflectionOffset() const
{
if (!style()->boxReflect())
return 0;
if (style()->boxReflect()->direction() == ReflectionLeft || style()->boxReflect()->direction() == ReflectionRight)
return valueForLength(style()->boxReflect()->offset(), borderBoxRect().width()).toInt();
return valueForLength(style()->boxReflect()->offset(), borderBoxRect().height()).toInt();
}
LayoutRect LayoutBox::reflectedRect(const LayoutRect& r) const
{
if (!style()->boxReflect())
return LayoutRect();
LayoutRect box = borderBoxRect();
LayoutRect result = r;
switch (style()->boxReflect()->direction()) {
case ReflectionBelow:
result.setY(box.maxY() + reflectionOffset() + (box.maxY() - r.maxY()));
break;
case ReflectionAbove:
result.setY(box.y() - reflectionOffset() - box.height() + (box.maxY() - r.maxY()));
break;
case ReflectionLeft:
result.setX(box.x() - reflectionOffset() - box.width() + (box.maxX() - r.maxX()));
break;
case ReflectionRight:
result.setX(box.maxX() + reflectionOffset() + (box.maxX() - r.maxX()));
break;
}
return result;
}
int LayoutBox::verticalScrollbarWidth() const
{
if (!hasOverflowClip() || style()->overflowY() == OverflowOverlay)
return 0;
return getScrollableArea()->verticalScrollbarWidth();
}
int LayoutBox::horizontalScrollbarHeight() const
{
if (!hasOverflowClip() || style()->overflowX() == OverflowOverlay)
return 0;
return getScrollableArea()->horizontalScrollbarHeight();
}
ScrollResult LayoutBox::scroll(ScrollGranularity granularity, const FloatSize& delta)
{
// Presumably the same issue as in setScrollTop. See crbug.com/343132.
DisableCompositingQueryAsserts disabler;
if (!getScrollableArea())
return ScrollResult();
return getScrollableArea()->userScroll(granularity, delta);
}
bool LayoutBox::canBeScrolledAndHasScrollableArea() const
{
return canBeProgramaticallyScrolled() && (pixelSnappedScrollHeight() != pixelSnappedClientHeight() || pixelSnappedScrollWidth() != pixelSnappedClientWidth());
}
bool LayoutBox::canBeProgramaticallyScrolled() const
{
Node* node = this->node();
if (node && node->isDocumentNode())
return true;
if (!hasOverflowClip())
return false;
bool hasScrollableOverflow = hasScrollableOverflowX() || hasScrollableOverflowY();
if (scrollsOverflow() && hasScrollableOverflow)
return true;
return node && hasEditableStyle(*node);
}
void LayoutBox::autoscroll(const IntPoint& positionInRootFrame)
{
LocalFrame* frame = this->frame();
if (!frame)
return;
FrameView* frameView = frame->view();
if (!frameView)
return;
IntPoint positionInContent = frameView->rootFrameToContents(positionInRootFrame);
scrollRectToVisible(LayoutRect(positionInContent, LayoutSize(1, 1)), ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignToEdgeIfNeeded, UserScroll);
}
// There are two kinds of layoutObject that can autoscroll.
bool LayoutBox::canAutoscroll() const
{
if (node() && node()->isDocumentNode())
return view()->frameView()->isScrollable();
// Check for a box that can be scrolled in its own right.
return canBeScrolledAndHasScrollableArea();
}
// If specified point is in border belt, returned offset denotes direction of
// scrolling.
IntSize LayoutBox::calculateAutoscrollDirection(const IntPoint& pointInRootFrame) const
{
if (!frame())
return IntSize();
FrameView* frameView = frame()->view();
if (!frameView)
return IntSize();
IntRect box(absoluteBoundingBoxRect());
box.move(view()->frameView()->scrollOffset());
IntRect windowBox = view()->frameView()->contentsToRootFrame(box);
IntPoint windowAutoscrollPoint = pointInRootFrame;
if (windowAutoscrollPoint.x() < windowBox.x() + autoscrollBeltSize)
windowAutoscrollPoint.move(-autoscrollBeltSize, 0);
else if (windowAutoscrollPoint.x() > windowBox.maxX() - autoscrollBeltSize)
windowAutoscrollPoint.move(autoscrollBeltSize, 0);
if (windowAutoscrollPoint.y() < windowBox.y() + autoscrollBeltSize)
windowAutoscrollPoint.move(0, -autoscrollBeltSize);
else if (windowAutoscrollPoint.y() > windowBox.maxY() - autoscrollBeltSize)
windowAutoscrollPoint.move(0, autoscrollBeltSize);
return windowAutoscrollPoint - pointInRootFrame;
}
LayoutBox* LayoutBox::findAutoscrollable(LayoutObject* layoutObject)
{
while (layoutObject && !(layoutObject->isBox() && toLayoutBox(layoutObject)->canAutoscroll())) {
if (!layoutObject->parent() && layoutObject->node() == layoutObject->document() && layoutObject->document().localOwner())
layoutObject = layoutObject->document().localOwner()->layoutObject();
else
layoutObject = layoutObject->parent();
}
return layoutObject && layoutObject->isBox() ? toLayoutBox(layoutObject) : 0;
}
static inline int adjustedScrollDelta(int beginningDelta)
{
// This implemention matches Firefox's.
// http://mxr.mozilla.org/firefox/source/toolkit/content/widgets/browser.xml#856.
const int speedReducer = 12;
int adjustedDelta = beginningDelta / speedReducer;
if (adjustedDelta > 1)
adjustedDelta = static_cast<int>(adjustedDelta * sqrt(static_cast<double>(adjustedDelta))) - 1;
else if (adjustedDelta < -1)
adjustedDelta = static_cast<int>(adjustedDelta * sqrt(static_cast<double>(-adjustedDelta))) + 1;
return adjustedDelta;
}
static inline IntSize adjustedScrollDelta(const IntSize& delta)
{
return IntSize(adjustedScrollDelta(delta.width()), adjustedScrollDelta(delta.height()));
}
void LayoutBox::middleClickAutoscroll(const IntPoint& sourcePoint)
{
LocalFrame* frame = this->frame();
if (!frame)
return;
IntPoint lastKnownMousePosition = frame->eventHandler().lastKnownMousePosition();
// We need to check if the last known mouse position is out of the window. When the mouse is out of the window, the position is incoherent
static IntPoint previousMousePosition;
if (lastKnownMousePosition.x() < 0 || lastKnownMousePosition.y() < 0)
lastKnownMousePosition = previousMousePosition;
else
previousMousePosition = lastKnownMousePosition;
IntSize delta = lastKnownMousePosition - sourcePoint;
if (abs(delta.width()) <= AutoscrollController::noMiddleClickAutoscrollRadius) // at the center we let the space for the icon
delta.setWidth(0);
if (abs(delta.height()) <= AutoscrollController::noMiddleClickAutoscrollRadius)
delta.setHeight(0);
scroll(ScrollByPixel, FloatSize(adjustedScrollDelta(delta)));
}
void LayoutBox::scrollByRecursively(const DoubleSize& delta, ScrollOffsetClamping clamp)
{
if (delta.isZero())
return;
bool restrictedByLineClamp = false;
if (parent())
restrictedByLineClamp = !parent()->style()->lineClamp().isNone();
if (hasOverflowClip() && !restrictedByLineClamp) {
PaintLayerScrollableArea* scrollableArea = this->getScrollableArea();
ASSERT(scrollableArea);
DoubleSize newScrollOffset = scrollableArea->adjustedScrollOffset() + delta;
scrollableArea->scrollToOffset(newScrollOffset, clamp);
// If this layer can't do the scroll we ask the next layer up that can scroll to try
DoubleSize remainingScrollOffset = newScrollOffset - scrollableArea->adjustedScrollOffset();
if (!remainingScrollOffset.isZero() && parent()) {
if (LayoutBox* scrollableBox = enclosingScrollableBox())
scrollableBox->scrollByRecursively(remainingScrollOffset, clamp);
LocalFrame* frame = this->frame();
if (frame && frame->page())
frame->page()->autoscrollController().updateAutoscrollLayoutObject();
}
} else if (view()->frameView()) {
// If we are here, we were called on a layoutObject that can be programmatically scrolled, but doesn't
// have an overflow clip. Which means that it is a document node that can be scrolled.
// FIXME: Pass in DoubleSize. crbug.com/414283.
view()->frameView()->scrollBy(flooredIntSize(delta), UserScroll);
// FIXME: If we didn't scroll the whole way, do we want to try looking at the frames ownerElement?
// https://bugs.webkit.org/show_bug.cgi?id=28237
}
}
bool LayoutBox::needsPreferredWidthsRecalculation() const
{
return style()->paddingStart().isPercentOrCalc() || style()->paddingEnd().isPercentOrCalc();
}
IntSize LayoutBox::originAdjustmentForScrollbars() const
{
IntSize size;
int adjustmentWidth = verticalScrollbarWidth();
if (hasFlippedBlocksWritingMode()
|| (isHorizontalWritingMode() && shouldPlaceBlockDirectionScrollbarOnLogicalLeft())) {
size.expand(adjustmentWidth, 0);
}
return size;
}
IntSize LayoutBox::scrolledContentOffset() const
{
ASSERT(hasOverflowClip());
ASSERT(hasLayer());
// FIXME: Return DoubleSize here. crbug.com/414283.
PaintLayerScrollableArea* scrollableArea = getScrollableArea();
IntSize result = flooredIntSize(scrollableArea->scrollOffset()) + originAdjustmentForScrollbars();
if (isHorizontalWritingMode() && shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
result.expand(-verticalScrollbarWidth(), 0);
return result;
}
LayoutRect LayoutBox::clippingRect() const
{
LayoutRect result = LayoutRect(LayoutRect::infiniteIntRect());
if (hasOverflowClip() || style()->containsPaint())
result = overflowClipRect(LayoutPoint());
if (hasClip())
result.intersect(clipRect(LayoutPoint()));
return result;
}
bool LayoutBox::mapScrollingContentsRectToBoxSpace(LayoutRect& rect, ApplyOverflowClipFlag applyOverflowClip, VisualRectFlags visualRectFlags) const
{
if (!hasClipRelatedProperty())
return true;
if (applyOverflowClip == ApplyNonScrollOverflowClip)
return true;
if (hasOverflowClip()) {
LayoutSize offset = LayoutSize(-scrolledContentOffset());
rect.move(offset);
}
// This won't work fully correctly for fixed-position elements, who should receive CSS clip but for whom the current object
// is not in the containing block chain.
LayoutRect clipRect = clippingRect();
bool doesIntersect;
if (visualRectFlags & EdgeInclusive) {
doesIntersect = rect.inclusiveIntersect(clipRect);
} else {
rect.intersect(clipRect);
doesIntersect = !rect.isEmpty();
}
return doesIntersect;
}
void LayoutBox::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const
{
minLogicalWidth = minPreferredLogicalWidth() - borderAndPaddingLogicalWidth();
maxLogicalWidth = maxPreferredLogicalWidth() - borderAndPaddingLogicalWidth();
}
LayoutUnit LayoutBox::minPreferredLogicalWidth() const
{
if (preferredLogicalWidthsDirty()) {
#if ENABLE(ASSERT)
SetLayoutNeededForbiddenScope layoutForbiddenScope(const_cast<LayoutBox&>(*this));
#endif
const_cast<LayoutBox*>(this)->computePreferredLogicalWidths();
ASSERT(!preferredLogicalWidthsDirty());
}
return m_minPreferredLogicalWidth;
}
DISABLE_CFI_PERF
LayoutUnit LayoutBox::maxPreferredLogicalWidth() const
{
if (preferredLogicalWidthsDirty()) {
#if ENABLE(ASSERT)
SetLayoutNeededForbiddenScope layoutForbiddenScope(const_cast<LayoutBox&>(*this));
#endif
const_cast<LayoutBox*>(this)->computePreferredLogicalWidths();
ASSERT(!preferredLogicalWidthsDirty());
}
return m_maxPreferredLogicalWidth;
}
bool LayoutBox::hasOverrideLogicalContentHeight() const
{
return m_rareData && m_rareData->m_overrideLogicalContentHeight != -1;
}
bool LayoutBox::hasOverrideLogicalContentWidth() const
{
return m_rareData && m_rareData->m_overrideLogicalContentWidth != -1;
}
void LayoutBox::setOverrideLogicalContentHeight(LayoutUnit height)
{
ASSERT(height >= 0);
ensureRareData().m_overrideLogicalContentHeight = height;
}
void LayoutBox::setOverrideLogicalContentWidth(LayoutUnit width)
{
ASSERT(width >= 0);
ensureRareData().m_overrideLogicalContentWidth = width;
}
void LayoutBox::clearOverrideLogicalContentHeight()
{
if (m_rareData)
m_rareData->m_overrideLogicalContentHeight = LayoutUnit(-1);
}
void LayoutBox::clearOverrideLogicalContentWidth()
{
if (m_rareData)
m_rareData->m_overrideLogicalContentWidth = LayoutUnit(-1);
}
void LayoutBox::clearOverrideSize()
{
clearOverrideLogicalContentHeight();
clearOverrideLogicalContentWidth();
}
LayoutUnit LayoutBox::overrideLogicalContentWidth() const
{
ASSERT(hasOverrideLogicalContentWidth());
return m_rareData->m_overrideLogicalContentWidth;
}
LayoutUnit LayoutBox::overrideLogicalContentHeight() const
{
ASSERT(hasOverrideLogicalContentHeight());
return m_rareData->m_overrideLogicalContentHeight;
}
// TODO (lajava) Now that we have implemented these functions based on physical direction, we'd rather remove the logical ones.
LayoutUnit LayoutBox::overrideContainingBlockContentLogicalWidth() const
{
DCHECK(hasOverrideContainingBlockLogicalWidth());
return m_rareData->m_overrideContainingBlockContentLogicalWidth;
}
// TODO (lajava) Shouldn't we implement these functions based on physical direction ?.
LayoutUnit LayoutBox::overrideContainingBlockContentLogicalHeight() const
{
DCHECK(hasOverrideContainingBlockLogicalHeight());
return m_rareData->m_overrideContainingBlockContentLogicalHeight;
}
// TODO (lajava) Shouldn't we implement these functions based on physical direction ?.
bool LayoutBox::hasOverrideContainingBlockLogicalWidth() const
{
return m_rareData && m_rareData->m_hasOverrideContainingBlockContentLogicalWidth;
}
// TODO (lajava) Shouldn't we implement these functions based on physical direction ?.
bool LayoutBox::hasOverrideContainingBlockLogicalHeight() const
{
return m_rareData && m_rareData->m_hasOverrideContainingBlockContentLogicalHeight;
}
// TODO (lajava) Shouldn't we implement these functions based on physical direction ?.
void LayoutBox::setOverrideContainingBlockContentLogicalWidth(LayoutUnit logicalWidth)
{
DCHECK_GE(logicalWidth, LayoutUnit(-1));
ensureRareData().m_overrideContainingBlockContentLogicalWidth = logicalWidth;
ensureRareData().m_hasOverrideContainingBlockContentLogicalWidth = true;
}
// TODO (lajava) Shouldn't we implement these functions based on physical direction ?.
void LayoutBox::setOverrideContainingBlockContentLogicalHeight(LayoutUnit logicalHeight)
{
DCHECK_GE(logicalHeight, LayoutUnit(-1));
ensureRareData().m_overrideContainingBlockContentLogicalHeight = logicalHeight;
ensureRareData().m_hasOverrideContainingBlockContentLogicalHeight = true;
}
// TODO (lajava) Shouldn't we implement these functions based on physical direction ?.
void LayoutBox::clearContainingBlockOverrideSize()
{
if (!m_rareData)
return;
ensureRareData().m_hasOverrideContainingBlockContentLogicalWidth = false;
ensureRareData().m_hasOverrideContainingBlockContentLogicalHeight = false;
}
// TODO (lajava) Shouldn't we implement these functions based on physical direction ?.
void LayoutBox::clearOverrideContainingBlockContentLogicalHeight()
{
if (!m_rareData)
return;
ensureRareData().m_hasOverrideContainingBlockContentLogicalHeight = false;
}
LayoutUnit LayoutBox::extraInlineOffset() const
{
return gExtraInlineOffsetMap ? gExtraInlineOffsetMap->get(this) : LayoutUnit();
}
LayoutUnit LayoutBox::extraBlockOffset() const
{
return gExtraBlockOffsetMap ? gExtraBlockOffsetMap->get(this) : LayoutUnit();
}
void LayoutBox::setExtraInlineOffset(LayoutUnit inlineOffest)
{
if (!gExtraInlineOffsetMap)
gExtraInlineOffsetMap = new OverrideSizeMap;
gExtraInlineOffsetMap->set(this, inlineOffest);
}
void LayoutBox::setExtraBlockOffset(LayoutUnit blockOffest)
{
if (!gExtraBlockOffsetMap)
gExtraBlockOffsetMap = new OverrideSizeMap;
gExtraBlockOffsetMap->set(this, blockOffest);
}
void LayoutBox::clearExtraInlineAndBlockOffests()
{
if (gExtraInlineOffsetMap)
gExtraInlineOffsetMap->remove(this);
if (gExtraBlockOffsetMap)
gExtraBlockOffsetMap->remove(this);
}
LayoutUnit LayoutBox::adjustBorderBoxLogicalWidthForBoxSizing(float width) const
{
LayoutUnit bordersPlusPadding = borderAndPaddingLogicalWidth();
LayoutUnit result(width);
if (style()->boxSizing() == BoxSizingContentBox)
return result + bordersPlusPadding;
return std::max(result, bordersPlusPadding);
}
LayoutUnit LayoutBox::adjustBorderBoxLogicalHeightForBoxSizing(float height) const
{
LayoutUnit bordersPlusPadding = borderAndPaddingLogicalHeight();
LayoutUnit result(height);
if (style()->boxSizing() == BoxSizingContentBox)
return result + bordersPlusPadding;
return std::max(result, bordersPlusPadding);
}
LayoutUnit LayoutBox::adjustContentBoxLogicalWidthForBoxSizing(float width) const
{
LayoutUnit result(width);
if (style()->boxSizing() == BoxSizingBorderBox)
result -= borderAndPaddingLogicalWidth();
return std::max(LayoutUnit(), result);
}
LayoutUnit LayoutBox::adjustContentBoxLogicalHeightForBoxSizing(float height) const
{
LayoutUnit result(height);
if (style()->boxSizing() == BoxSizingBorderBox)
result -= borderAndPaddingLogicalHeight();
return std::max(LayoutUnit(), result);
}
// Hit Testing
bool LayoutBox::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction action)
{
LayoutPoint adjustedLocation = accumulatedOffset + location();
if (!isLayoutView()) {
// Check if we need to do anything at all.
// If we have clipping, then we can't have any spillout.
LayoutRect overflowBox = hasOverflowClip() ? borderBoxRect() : visualOverflowRect();
flipForWritingMode(overflowBox);
overflowBox.moveBy(adjustedLocation);
if (!locationInContainer.intersects(overflowBox))
return false;
}
bool shouldHitTestSelf = isInSelfHitTestingPhase(action);
if (shouldHitTestSelf && hasOverflowClip()
&& hitTestOverflowControl(result, locationInContainer, adjustedLocation))
return true;
// TODO(pdr): We should also check for css clip in the !isSelfPaintingLayer
// case, similar to overflow clip below.
bool skipChildren = false;
if (hasOverflowClip() && !hasSelfPaintingLayer()) {
if (!locationInContainer.intersects(overflowClipRect(adjustedLocation, ExcludeOverlayScrollbarSizeForHitTesting))) {
skipChildren = true;
} else if (style()->hasBorderRadius()) {
LayoutRect boundsRect(adjustedLocation, size());
skipChildren = !locationInContainer.intersects(style()->getRoundedInnerBorderFor(boundsRect));
}
}
// A control clip can also clip out child hit testing.
if (!skipChildren && hasControlClip() && !locationInContainer.intersects(controlClipRect(adjustedLocation)))
skipChildren = true;
// TODO(pdr): We should also include checks for hit testing border radius at
// the layer level (see: crbug.com/568904).
if (!skipChildren && hitTestChildren(result, locationInContainer, adjustedLocation, action))
return true;
if (hitTestClippedOutByRoundedBorder(locationInContainer, adjustedLocation))
return false;
// Now hit test ourselves.
if (shouldHitTestSelf && visibleToHitTestRequest(result.hitTestRequest())) {
LayoutRect boundsRect(adjustedLocation, size());
if (locationInContainer.intersects(boundsRect)) {
updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(adjustedLocation)));
if (result.addNodeToListBasedTestResult(nodeForHitTest(), locationInContainer, boundsRect) == StopHitTesting)
return true;
}
}
return false;
}
bool LayoutBox::hitTestChildren(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction action)
{
for (LayoutObject* child = slowLastChild(); child; child = child->previousSibling()) {
if ((!child->hasLayer() || !toLayoutBoxModelObject(child)->layer()->isSelfPaintingLayer()) && child->nodeAtPoint(result, locationInContainer, accumulatedOffset, action))
return true;
}
return false;
}
bool LayoutBox::hitTestClippedOutByRoundedBorder(const HitTestLocation& locationInContainer, const LayoutPoint& borderBoxLocation) const
{
if (!style()->hasBorderRadius())
return false;
LayoutRect borderRect = borderBoxRect();
borderRect.moveBy(borderBoxLocation);
return !locationInContainer.intersects(style()->getRoundedBorderFor(borderRect));
}
void LayoutBox::paint(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) const
{
BoxPainter(*this).paint(paintInfo, paintOffset);
}
void LayoutBox::paintBoxDecorationBackground(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) const
{
BoxPainter(*this).paintBoxDecorationBackground(paintInfo, paintOffset);
}
bool LayoutBox::getBackgroundPaintedExtent(LayoutRect& paintedExtent) const
{
DCHECK(styleRef().hasBackground());
// LayoutView is special in the sense that it expands to the whole canvas,
// thus can't be handled by this function.
DCHECK(!isLayoutView());
LayoutRect backgroundRect(borderBoxRect());
Color backgroundColor = resolveColor(CSSPropertyBackgroundColor);
if (backgroundColor.alpha()) {
paintedExtent = backgroundRect;
return true;
}
if (!style()->backgroundLayers().image() || style()->backgroundLayers().next()) {
paintedExtent = backgroundRect;
return true;
}
BackgroundImageGeometry geometry;
// TODO(jchaffraix): This function should be rethought as it's called during and outside
// of the paint phase. Potentially returning different results at different phases.
geometry.calculate(*this, nullptr, GlobalPaintNormalPhase, style()->backgroundLayers(), backgroundRect);
if (geometry.hasNonLocalGeometry())
return false;
paintedExtent = LayoutRect(geometry.destRect());
return true;
}
bool LayoutBox::backgroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect) const
{
if (isDocumentElement() || backgroundStolenForBeingBody())
return false;
// If the element has appearance, it might be painted by theme.
// We cannot be sure if theme paints the background opaque.
// In this case it is safe to not assume opaqueness.
// FIXME: May be ask theme if it paints opaque.
if (style()->hasAppearance())
return false;
// FIXME: Check the opaqueness of background images.
// FIXME: Use rounded rect if border radius is present.
if (style()->hasBorderRadius())
return false;
if (hasClipPath())
return false;
if (style()->hasBlendMode())
return false;
return backgroundRect(BackgroundKnownOpaqueRect).contains(localRect);
}
static bool isCandidateForOpaquenessTest(const LayoutBox& childBox)
{
const ComputedStyle& childStyle = childBox.styleRef();
if (childStyle.position() != StaticPosition && childBox.containingBlock() != childBox.parent())
return false;
if (childStyle.visibility() != EVisibility::Visible || childStyle.shapeOutside())
return false;
if (childBox.size().isZero())
return false;
if (PaintLayer* childLayer = childBox.layer()) {
// FIXME: perhaps this could be less conservative?
if (childLayer->compositingState() != NotComposited)
return false;
// FIXME: Deal with z-index.
if (childStyle.isStackingContext())
return false;
if (childLayer->hasTransformRelatedProperty() || childLayer->isTransparent() || childLayer->hasFilterInducingProperty())
return false;
if (childBox.hasOverflowClip() && childStyle.hasBorderRadius())
return false;
}
return true;
}
bool LayoutBox::foregroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect, unsigned maxDepthToTest) const
{
if (!maxDepthToTest)
return false;
for (LayoutObject* child = slowFirstChild(); child; child = child->nextSibling()) {
if (!child->isBox())
continue;
LayoutBox* childBox = toLayoutBox(child);
if (!isCandidateForOpaquenessTest(*childBox))
continue;
LayoutPoint childLocation = childBox->location();
if (childBox->isInFlowPositioned())
childLocation.move(childBox->offsetForInFlowPosition());
LayoutRect childLocalRect = localRect;
childLocalRect.moveBy(-childLocation);
if (childLocalRect.y() < 0 || childLocalRect.x() < 0) {
// If there is unobscured area above/left of a static positioned box then the rect is probably not covered.
if (!childBox->isPositioned())
return false;
continue;
}
if (childLocalRect.maxY() > childBox->size().height() || childLocalRect.maxX() > childBox->size().width())
continue;
if (childBox->backgroundIsKnownToBeOpaqueInRect(childLocalRect))
return true;
if (childBox->foregroundIsKnownToBeOpaqueInRect(childLocalRect, maxDepthToTest - 1))
return true;
}
return false;
}
DISABLE_CFI_PERF
bool LayoutBox::computeBackgroundIsKnownToBeObscured() const
{
if (scrollsOverflow())
return false;
// Test to see if the children trivially obscure the background.
if (!styleRef().hasBackground())
return false;
// Root background painting is special.
if (isLayoutView())
return false;
// FIXME: box-shadow is painted while background painting.
if (style()->boxShadow())
return false;
LayoutRect backgroundRect;
if (!getBackgroundPaintedExtent(backgroundRect))
return false;
return foregroundIsKnownToBeOpaqueInRect(backgroundRect, backgroundObscurationTestMaxDepth);
}
void LayoutBox::paintMask(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) const
{
BoxPainter(*this).paintMask(paintInfo, paintOffset);
}
void LayoutBox::imageChanged(WrappedImagePtr image, const IntRect*)
{
// TODO(chrishtr): support PaintInvalidationDelayedFull for animated border images.
if ((styleRef().borderImage().image() && styleRef().borderImage().image()->data() == image)
|| (styleRef().maskBoxImage().image() && styleRef().maskBoxImage().image()->data() == image)
|| (styleRef().boxReflect() && styleRef().boxReflect()->mask().image() && styleRef().boxReflect()->mask().image()->data() == image)) {
setShouldDoFullPaintInvalidation();
} else {
for (const FillLayer* layer = &styleRef().maskLayers(); layer; layer = layer->next()) {
if (layer->image() && image == layer->image()->data()) {
setShouldDoFullPaintInvalidation();
break;
}
}
}
if (!isDocumentElement() && !backgroundStolenForBeingBody()) {
for (const FillLayer* layer = &styleRef().backgroundLayers(); layer; layer = layer->next()) {
if (layer->image() && image == layer->image()->data()) {
invalidateBackgroundObscurationStatus();
bool maybeAnimated = layer->image()->cachedImage() && layer->image()->cachedImage()->getImage() && layer->image()->cachedImage()->getImage()->maybeAnimated();
if (maybeAnimated)
setMayNeedPaintInvalidationAnimatedBackgroundImage();
else
setShouldDoFullPaintInvalidation();
break;
}
}
}
ShapeValue* shapeOutsideValue = style()->shapeOutside();
if (!frameView()->isInPerformLayout() && isFloating() && shapeOutsideValue && shapeOutsideValue->image() && shapeOutsideValue->image()->data() == image) {
ShapeOutsideInfo& info = ShapeOutsideInfo::ensureInfo(*this);
if (!info.isComputingShape()) {
info.markShapeAsDirty();
markShapeOutsideDependentsForLayout();
}
}
}
ResourcePriority LayoutBox::computeResourcePriority() const
{
LayoutRect viewBounds = viewRect();
LayoutRect objectBounds = LayoutRect(absoluteContentBox());
// The object bounds might be empty right now, so intersects will fail since it doesn't deal
// with empty rects. Use LayoutRect::contains in that case.
bool isVisible;
if (!objectBounds.isEmpty())
isVisible = viewBounds.intersects(objectBounds);
else
isVisible = viewBounds.contains(objectBounds);
LayoutRect screenRect;
if (!objectBounds.isEmpty()) {
screenRect = viewBounds;
screenRect.intersect(objectBounds);
}
int screenArea = 0;
if (!screenRect.isEmpty() && isVisible)
screenArea = (screenRect.width() * screenRect.height()).toInt();
return ResourcePriority(isVisible ? ResourcePriority::Visible : ResourcePriority::NotVisible, screenArea);
}
void LayoutBox::frameRectChanged()
{
if (node() && node()->isElementNode()) {
Element& element = toElement(*node());
element.setNeedsResizeObserverUpdate();
}
// The frame rect may change because of layout of other objects.
// Should check this object for paint invalidation.
if (!needsLayout())
setMayNeedPaintInvalidation();
}
bool LayoutBox::intersectsVisibleViewport() const
{
LayoutRect rect = visualOverflowRect();
LayoutView* layoutView = view();
while (!layoutView->frame()->ownerLayoutItem().isNull())
layoutView = LayoutAPIShim::layoutObjectFrom(layoutView->frame()->ownerLayoutItem())->view();
mapToVisualRectInAncestorSpace(layoutView, rect);
return rect.intersects(LayoutRect(layoutView->frameView()->getScrollableArea()->visibleContentRectDouble()));
}
PaintInvalidationReason LayoutBox::invalidatePaintIfNeeded(const PaintInvalidationState& paintInvalidationState)
{
if (hasBoxDecorationBackground()
// We also paint overflow controls in background phase.
|| (hasOverflowClip() && getScrollableArea()->hasOverflowControls())) {
PaintLayer& layer = paintInvalidationState.paintingLayer();
if (layer.layoutObject() != this)
layer.setNeedsPaintPhaseDescendantBlockBackgrounds();
}
return LayoutBoxModelObject::invalidatePaintIfNeeded(paintInvalidationState);
}
PaintInvalidationReason LayoutBox::invalidatePaintIfNeeded(const PaintInvalidatorContext& context) const
{
return BoxPaintInvalidator(*this, context).invalidatePaintIfNeeded();
}
LayoutRect LayoutBox::overflowClipRect(const LayoutPoint& location, OverlayScrollbarClipBehavior overlayScrollbarClipBehavior) const
{
// FIXME: When overflow-clip (CSS3) is implemented, we'll obtain the property
// here.
LayoutRect clipRect = borderBoxRect();
clipRect.setLocation(location + clipRect.location() + LayoutSize(borderLeft(), borderTop()));
clipRect.setSize(clipRect.size() - LayoutSize(borderLeft() + borderRight(), borderTop() + borderBottom()));
if (hasOverflowClip())
excludeScrollbars(clipRect, overlayScrollbarClipBehavior);
return clipRect;
}
void LayoutBox::excludeScrollbars(LayoutRect& rect, OverlayScrollbarClipBehavior overlayScrollbarClipBehavior) const
{
if (PaintLayerScrollableArea* scrollableArea = this->getScrollableArea()) {
if (shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
rect.move(scrollableArea->verticalScrollbarWidth(overlayScrollbarClipBehavior), 0);
rect.contract(scrollableArea->verticalScrollbarWidth(overlayScrollbarClipBehavior), scrollableArea->horizontalScrollbarHeight(overlayScrollbarClipBehavior));
}
}
LayoutRect LayoutBox::clipRect(const LayoutPoint& location) const
{
LayoutRect borderBoxRect = this->borderBoxRect();
LayoutRect clipRect = LayoutRect(borderBoxRect.location() + location, borderBoxRect.size());
if (!style()->clipLeft().isAuto()) {
LayoutUnit c = valueForLength(style()->clipLeft(), borderBoxRect.width());
clipRect.move(c, LayoutUnit());
clipRect.contract(c, LayoutUnit());
}
if (!style()->clipRight().isAuto())
clipRect.contract(size().width() - valueForLength(style()->clipRight(), size().width()), LayoutUnit());
if (!style()->clipTop().isAuto()) {
LayoutUnit c = valueForLength(style()->clipTop(), borderBoxRect.height());
clipRect.move(LayoutUnit(), c);
clipRect.contract(LayoutUnit(), c);
}
if (!style()->clipBottom().isAuto())
clipRect.contract(LayoutUnit(), size().height() - valueForLength(style()->clipBottom(), size().height()));
return clipRect;
}
static LayoutUnit portionOfMarginNotConsumedByFloat(LayoutUnit childMargin, LayoutUnit contentSide, LayoutUnit offset)
{
if (childMargin <= 0)
return LayoutUnit();
LayoutUnit contentSideWithMargin = contentSide + childMargin;
if (offset > contentSideWithMargin)
return childMargin;
return offset - contentSide;
}
LayoutUnit LayoutBox::shrinkLogicalWidthToAvoidFloats(LayoutUnit childMarginStart, LayoutUnit childMarginEnd, const LayoutBlockFlow* cb) const
{
LayoutUnit logicalTopPosition = logicalTop();
LayoutUnit startOffsetForContent = cb->startOffsetForContent();
LayoutUnit endOffsetForContent = cb->endOffsetForContent();
LayoutUnit startOffsetForLine = cb->startOffsetForLine(logicalTopPosition, DoNotIndentText);
LayoutUnit endOffsetForLine = cb->endOffsetForLine(logicalTopPosition, DoNotIndentText);
// If there aren't any floats constraining us then allow the margins to shrink/expand the width as much as they want.
if (startOffsetForContent == startOffsetForLine && endOffsetForContent == endOffsetForLine)
return cb->availableLogicalWidthForLine(logicalTopPosition, DoNotIndentText) - childMarginStart - childMarginEnd;
LayoutUnit width = cb->availableLogicalWidthForLine(logicalTopPosition, DoNotIndentText) - std::max(LayoutUnit(), childMarginStart) - std::max(LayoutUnit(), childMarginEnd);
// We need to see if margins on either the start side or the end side can contain the floats in question. If they can,
// then just using the line width is inaccurate. In the case where a float completely fits, we don't need to use the line
// offset at all, but can instead push all the way to the content edge of the containing block. In the case where the float
// doesn't fit, we can use the line offset, but we need to grow it by the margin to reflect the fact that the margin was
// "consumed" by the float. Negative margins aren't consumed by the float, and so we ignore them.
width += portionOfMarginNotConsumedByFloat(childMarginStart, startOffsetForContent, startOffsetForLine);
width += portionOfMarginNotConsumedByFloat(childMarginEnd, endOffsetForContent, endOffsetForLine);
return width;
}
LayoutUnit LayoutBox::containingBlockLogicalHeightForGetComputedStyle() const
{
if (hasOverrideContainingBlockLogicalHeight())
return overrideContainingBlockContentLogicalHeight();
if (!isPositioned())
return containingBlockLogicalHeightForContent(ExcludeMarginBorderPadding);
LayoutBoxModelObject* cb = toLayoutBoxModelObject(container());
LayoutUnit height = containingBlockLogicalHeightForPositioned(cb);
if (styleRef().position() != AbsolutePosition)
height -= cb->paddingLogicalHeight();
return height;
}
LayoutUnit LayoutBox::containingBlockLogicalWidthForContent() const
{
if (hasOverrideContainingBlockLogicalWidth())
return overrideContainingBlockContentLogicalWidth();
LayoutBlock* cb = containingBlock();
if (isOutOfFlowPositioned())
return cb->clientLogicalWidth();
return cb->availableLogicalWidth();
}
LayoutUnit LayoutBox::containingBlockLogicalHeightForContent(AvailableLogicalHeightType heightType) const
{
if (hasOverrideContainingBlockLogicalHeight())
return overrideContainingBlockContentLogicalHeight();
LayoutBlock* cb = containingBlock();
return cb->availableLogicalHeight(heightType);
}
LayoutUnit LayoutBox::containingBlockAvailableLineWidth() const
{
LayoutBlock* cb = containingBlock();
if (cb->isLayoutBlockFlow())
return toLayoutBlockFlow(cb)->availableLogicalWidthForLine(logicalTop(), DoNotIndentText, availableLogicalHeight(IncludeMarginBorderPadding));
return LayoutUnit();
}
LayoutUnit LayoutBox::perpendicularContainingBlockLogicalHeight() const
{
if (hasOverrideContainingBlockLogicalHeight())
return overrideContainingBlockContentLogicalHeight();
LayoutBlock* cb = containingBlock();
if (cb->hasOverrideLogicalContentHeight())
return cb->overrideLogicalContentHeight();
const ComputedStyle& containingBlockStyle = cb->styleRef();
Length logicalHeightLength = containingBlockStyle.logicalHeight();
// FIXME: For now just support fixed heights. Eventually should support percentage heights as well.
if (!logicalHeightLength.isFixed()) {
LayoutUnit fillFallbackExtent = LayoutUnit(containingBlockStyle.isHorizontalWritingMode()
? view()->frameView()->visibleContentSize().height()
: view()->frameView()->visibleContentSize().width());
LayoutUnit fillAvailableExtent = containingBlock()->availableLogicalHeight(ExcludeMarginBorderPadding);
if (fillAvailableExtent == -1)
return fillFallbackExtent;
return std::min(fillAvailableExtent, fillFallbackExtent);
}
// Use the content box logical height as specified by the style.
return cb->adjustContentBoxLogicalHeightForBoxSizing(LayoutUnit(logicalHeightLength.value()));
}
void LayoutBox::mapLocalToAncestor(const LayoutBoxModelObject* ancestor, TransformState& transformState, MapCoordinatesFlags mode) const
{
bool isFixedPos = style()->position() == FixedPosition;
// If this box has a transform or contains paint, it acts as a fixed position container for fixed descendants,
// and may itself also be fixed position. So propagate 'fixed' up only if this box is fixed position.
if (style()->canContainFixedPositionObjects() && !isFixedPos)
mode &= ~IsFixed;
else if (isFixedPos)
mode |= IsFixed;
LayoutBoxModelObject::mapLocalToAncestor(ancestor, transformState, mode);
}
void LayoutBox::mapAncestorToLocal(const LayoutBoxModelObject* ancestor, TransformState& transformState, MapCoordinatesFlags mode) const
{
if (this == ancestor)
return;
bool isFixedPos = style()->position() == FixedPosition;
// If this box has a transform or contains paint, it acts as a fixed position container for fixed descendants,
// and may itself also be fixed position. So propagate 'fixed' up only if this box is fixed position.
if (style()->canContainFixedPositionObjects() && !isFixedPos)
mode &= ~IsFixed;
else if (isFixedPos)
mode |= IsFixed;
LayoutBoxModelObject::mapAncestorToLocal(ancestor, transformState, mode);
}
LayoutSize LayoutBox::offsetFromContainer(const LayoutObject* o) const
{
ASSERT(o == container());
LayoutSize offset;
if (isInFlowPositioned())
offset += offsetForInFlowPosition();
offset += topLeftLocationOffset();
if (o->hasOverflowClip())
offset -= toLayoutBox(o)->scrolledContentOffset();
if (style()->position() == AbsolutePosition && o->isInFlowPositioned() && o->isLayoutInline())
offset += toLayoutInline(o)->offsetForInFlowPositionedInline(*this);
return offset;
}
InlineBox* LayoutBox::createInlineBox()
{
return new InlineBox(LineLayoutItem(this));
}
void LayoutBox::dirtyLineBoxes(bool fullLayout)
{
if (m_inlineBoxWrapper) {
if (fullLayout) {
m_inlineBoxWrapper->destroy();
m_inlineBoxWrapper = nullptr;
} else {
m_inlineBoxWrapper->dirtyLineBoxes();
}
}
}
void LayoutBox::positionLineBox(InlineBox* box)
{
if (isOutOfFlowPositioned()) {
// Cache the x position only if we were an INLINE type originally.
bool originallyInline = style()->isOriginalDisplayInlineType();
if (originallyInline) {
// The value is cached in the xPos of the box. We only need this value if
// our object was inline originally, since otherwise it would have ended up underneath
// the inlines.
RootInlineBox& root = box->root();
root.block().setStaticInlinePositionForChild(LineLayoutBox(this), box->logicalLeft());
} else {
// Our object was a block originally, so we make our normal flow position be
// just below the line box (as though all the inlines that came before us got
// wrapped in an anonymous block, which is what would have happened had we been
// in flow). This value was cached in the y() of the box.
layer()->setStaticBlockPosition(box->logicalTop());
}
if (container()->isLayoutInline())
moveWithEdgeOfInlineContainerIfNecessary(box->isHorizontal());
// Nuke the box.
box->remove(DontMarkLineBoxes);
box->destroy();
} else if (isAtomicInlineLevel()) {
// FIXME: the call to roundedLayoutPoint() below is temporary and should be removed once
// the transition to LayoutUnit-based types is complete (crbug.com/321237)
setLocationAndUpdateOverflowControlsIfNeeded(box->topLeft());
setInlineBoxWrapper(box);
}
}
void LayoutBox::moveWithEdgeOfInlineContainerIfNecessary(bool isHorizontal)
{
ASSERT(isOutOfFlowPositioned() && container()->isLayoutInline() && container()->isInFlowPositioned());
// If this object is inside a relative positioned inline and its inline position is an explicit offset from the edge of its container
// then it will need to move if its inline container has changed width. We do not track if the width has changed
// but if we are here then we are laying out lines inside it, so it probably has - mark our object for layout so that it can
// move to the new offset created by the new width.
if (!normalChildNeedsLayout() && !style()->hasStaticInlinePosition(isHorizontal))
setChildNeedsLayout(MarkOnlyThis);
}
void LayoutBox::deleteLineBoxWrapper()
{
if (m_inlineBoxWrapper) {
if (!documentBeingDestroyed())
m_inlineBoxWrapper->remove();
m_inlineBoxWrapper->destroy();
m_inlineBoxWrapper = nullptr;
}
}
void LayoutBox::setSpannerPlaceholder(LayoutMultiColumnSpannerPlaceholder& placeholder)
{
RELEASE_ASSERT(!m_rareData || !m_rareData->m_spannerPlaceholder); // not expected to change directly from one spanner to another.
ensureRareData().m_spannerPlaceholder = &placeholder;
}
void LayoutBox::clearSpannerPlaceholder()
{
if (!m_rareData)
return;
m_rareData->m_spannerPlaceholder = nullptr;
}
void LayoutBox::setPaginationStrut(LayoutUnit strut)
{
if (!strut && !m_rareData)
return;
ensureRareData().m_paginationStrut = strut;
}
bool LayoutBox::isBreakBetweenControllable(EBreak breakValue) const
{
if (breakValue == BreakAuto)
return true;
// We currently only support non-auto break-before and break-after values on in-flow block
// level elements, which is the minimum requirement according to the spec.
if (isInline() || isFloatingOrOutOfFlowPositioned())
return false;
const LayoutBlock* curr = containingBlock();
if (!curr || !curr->isLayoutBlockFlow())
return false;
const LayoutView* layoutView = view();
bool viewIsPaginated = layoutView->fragmentationContext();
if (!viewIsPaginated && !flowThreadContainingBlock())
return false;
while (curr) {
if (curr == layoutView)
return viewIsPaginated && breakValue != BreakColumn && breakValue != BreakAvoidColumn;
if (curr->isLayoutFlowThread()) {
if (breakValue == BreakAvoid) // Valid in any kind of fragmentation context.
return true;
bool isMulticolValue = breakValue == BreakColumn || breakValue == BreakAvoidColumn;
if (toLayoutFlowThread(curr)->isLayoutPagedFlowThread())
return !isMulticolValue;
if (isMulticolValue)
return true;
// If this is a flow thread for a multicol container, and we have a break value for
// paged, we need to keep looking.
}
if (curr->isFloatingOrOutOfFlowPositioned())
return false;
curr = curr->containingBlock();
}
ASSERT_NOT_REACHED();
return false;
}
bool LayoutBox::isBreakInsideControllable(EBreak breakValue) const
{
ASSERT(!isForcedFragmentainerBreakValue(breakValue));
if (breakValue == BreakAuto)
return true;
// First check multicol.
const LayoutFlowThread* flowThread = flowThreadContainingBlock();
// 'avoid-column' is only valid in a multicol context.
if (breakValue == BreakAvoidColumn)
return flowThread && !flowThread->isLayoutPagedFlowThread();
// 'avoid' is valid in any kind of fragmentation context.
if (breakValue == BreakAvoid && flowThread)
return true;
ASSERT(breakValue == BreakAvoidPage || breakValue == BreakAvoid);
if (view()->fragmentationContext())
return true; // The view is paginated, probably because we're printing.
if (!flowThread)
return false; // We're not inside any pagination context
// We're inside a flow thread. We need to be contained by a flow thread for paged overflow in
// order for pagination values to be valid, though.
for (const LayoutBlock* ancestor = flowThread; ancestor; ancestor = ancestor->containingBlock()) {
if (ancestor->isLayoutFlowThread() && toLayoutFlowThread(ancestor)->isLayoutPagedFlowThread())
return true;
}
return false;
}
EBreak LayoutBox::breakAfter() const
{
EBreak breakValue = style()->breakAfter();
if (breakValue == BreakAuto || isBreakBetweenControllable(breakValue))
return breakValue;
return BreakAuto;
}
EBreak LayoutBox::breakBefore() const
{
EBreak breakValue = style()->breakBefore();
if (breakValue == BreakAuto || isBreakBetweenControllable(breakValue))
return breakValue;
return BreakAuto;
}
EBreak LayoutBox::breakInside() const
{
EBreak breakValue = style()->breakInside();
if (breakValue == BreakAuto || isBreakInsideControllable(breakValue))
return breakValue;
return BreakAuto;
}
// At a class A break point [1], the break value with the highest precedence wins. If the two values
// have the same precedence (e.g. "left" and "right"), the value specified on a latter object wins.
//
// [1] https://drafts.csswg.org/css-break/#possible-breaks
static inline int fragmentainerBreakPrecedence(EBreak breakValue)
{
// "auto" has the lowest priority.
// "avoid*" values win over "auto".
// "avoid-page" wins over "avoid-column".
// "avoid" wins over "avoid-page".
// Forced break values win over "avoid".
// Any forced page break value wins over "column" forced break.
// More specific break values (left, right, recto, verso) wins over generic "page" values.
switch (breakValue) {
default:
ASSERT_NOT_REACHED();
// fall-through
case BreakAuto:
return 0;
case BreakAvoidColumn:
return 1;
case BreakAvoidPage:
return 2;
case BreakAvoid:
return 3;
case BreakColumn:
return 4;
case BreakPage:
return 5;
case BreakLeft:
case BreakRight:
case BreakRecto:
case BreakVerso:
return 6;
}
}
EBreak LayoutBox::joinFragmentainerBreakValues(EBreak firstValue, EBreak secondValue)
{
if (fragmentainerBreakPrecedence(secondValue) >= fragmentainerBreakPrecedence(firstValue))
return secondValue;
return firstValue;
}
EBreak LayoutBox::classABreakPointValue(EBreak previousBreakAfterValue) const
{
// First assert that we're at a class A break point.
ASSERT(isBreakBetweenControllable(previousBreakAfterValue));
return joinFragmentainerBreakValues(previousBreakAfterValue, breakBefore());
}
bool LayoutBox::needsForcedBreakBefore(EBreak previousBreakAfterValue) const
{
// Forced break values are only honored when specified on in-flow objects, but floats and
// out-of-flow positioned objects may be affected by a break-after value of the previous
// in-flow object, even though we're not at a class A break point.
EBreak breakValue = isFloatingOrOutOfFlowPositioned()
? previousBreakAfterValue : classABreakPointValue(previousBreakAfterValue);
return isForcedFragmentainerBreakValue(breakValue);
}
bool LayoutBox::paintedOutputOfObjectHasNoEffectRegardlessOfSize() const
{
// In case scrollbars got repositioned (which will typically happen if the box got
// resized), we cannot skip invalidation.
if (hasNonCompositedScrollbars())
return false;
// Cannot skip paint invalidation if the box has real things to paint.
if (getSelectionState() != SelectionNone || hasBoxDecorationBackground() || styleRef().hasBoxDecorations() || styleRef().hasVisualOverflowingEffect())
return false;
// If the box has clip, we need issue a paint invalidation to cover the changed part of
// children because of change of clip when the box got resized. In theory the children
// should invalidate themselves when ancestor clip changes, but for now this is missing
// and ensuring it may hurt performance.
// TODO(wangxianzhu): Paint invalidation for clip change will be different in spv2.
if (hasClipRelatedProperty() || hasControlClip())
return false;
return true;
}
LayoutRect LayoutBox::localOverflowRectForPaintInvalidation() const
{
if (style()->visibility() != EVisibility::Visible)
return LayoutRect();
return selfVisualOverflowRect();
}
void LayoutBox::inflateVisualRectForFilterUnderContainer(LayoutRect& rect, const LayoutObject& container, const LayoutBoxModelObject* ancestorToStopAt) const
{
// Apply visual overflow caused by reflections and filters defined on objects between this object
// and container (not included) or ancestorToStopAt (included).
LayoutSize offsetFromContainer = this->offsetFromContainer(&container);
rect.move(offsetFromContainer);
for (LayoutObject* parent = this->parent(); parent && parent != container; parent = parent->parent()) {
if (parent->isBox()) {
// Convert rect into coordinate space of parent to apply parent's reflection and filter.
LayoutSize parentOffset = parent->offsetFromAncestorContainer(&container);
rect.move(-parentOffset);
toLayoutBox(parent)->inflateVisualRectForFilter(rect);
rect.move(parentOffset);
}
if (parent == ancestorToStopAt)
break;
}
rect.move(-offsetFromContainer);
}
bool LayoutBox::mapToVisualRectInAncestorSpace(const LayoutBoxModelObject* ancestor, LayoutRect& rect, VisualRectFlags visualRectFlags) const
{
inflateVisualRectForFilter(rect);
if (ancestor == this)
return true;
bool ancestorSkipped;
bool filterSkipped;
LayoutObject* container = this->container(ancestor, &ancestorSkipped, &filterSkipped);
LayoutBox* tableRowContainer = nullptr;
// Skip table row because cells and rows are in the same coordinate space
// (see below, however for more comments about when |ancestor| is the table row).
// The second and third conditionals below are to skip cases where content has display: table-row or display: table-cell but is not
// parented like a cell/row combo.
if (container->isTableRow() && isTableCell() && parentBox() == container) {
if (container != ancestor)
container = container->parent();
else
tableRowContainer = toLayoutBox(container);
}
if (!container)
return true;
if (filterSkipped)
inflateVisualRectForFilterUnderContainer(rect, *container, ancestor);
// We are now in our parent container's coordinate space. Apply our transform to obtain a bounding box
// in the parent's coordinate space that encloses us.
if (hasLayer() && layer()->transform()) {
// Use enclosingIntRect because we cannot properly compute pixel snapping for painted elements within
// the transform since we don't know the desired subpixel accumulation at this point, and the transform may
// include a scale.
rect = LayoutRect(layer()->transform()->mapRect(enclosingIntRect(rect)));
}
LayoutPoint topLeft = rect.location();
if (container->isBox()) {
topLeft.moveBy(topLeftLocation(toLayoutBox(container)));
// If the row is the ancestor, however, add its offset back in. In effect, this passes from the joint <td> / <tr>
// coordinate space to the parent space, then back to <tr> / <td>.
if (tableRowContainer)
topLeft.moveBy(-tableRowContainer->topLeftLocation(toLayoutBox(container)));
} else if (container->isRuby()) {
// TODO(wkorman): Generalize Ruby specialization and/or document more clearly.
// See the accompanying specialization in LayoutInline::mapToVisualRectInAncestorSpace.
topLeft.moveBy(topLeftLocation());
} else {
topLeft.moveBy(location());
}
const ComputedStyle& styleToUse = styleRef();
EPosition position = styleToUse.position();
if (position == AbsolutePosition && container->isInFlowPositioned() && container->isLayoutInline()) {
topLeft += toLayoutInline(container)->offsetForInFlowPositionedInline(*this);
} else if (styleToUse.hasInFlowPosition() && layer()) {
// Apply the relative position offset when invalidating a rectangle. The layer
// is translated, but the layout box isn't, so we need to do this to get the
// right dirty rect. Since this is called from LayoutObject::setStyle, the relative position
// flag on the LayoutObject has been cleared, so use the one on the style().
topLeft += layer()->offsetForInFlowPosition();
}
// FIXME: We ignore the lightweight clipping rect that controls use, since if |o| is in mid-layout,
// its controlClipRect will be wrong. For overflow clip we use the values cached by the layer.
rect.setLocation(topLeft);
if (container->isBox() && !toLayoutBox(container)->mapScrollingContentsRectToBoxSpace(rect, container == ancestor ? ApplyNonScrollOverflowClip : ApplyOverflowClip, visualRectFlags))
return false;
if (ancestorSkipped) {
// If the ancestor is below the container, then we need to map the rect into ancestor's coordinates.
LayoutSize containerOffset = ancestor->offsetFromAncestorContainer(container);
rect.move(-containerOffset);
// If the ancestor is fixed, then the rect is already in its coordinates so doesn't need viewport-adjusting.
if (ancestor->style()->position() != FixedPosition && container->isLayoutView() && position == FixedPosition)
toLayoutView(container)->adjustOffsetForFixedPosition(rect);
return true;
}
if (container->isLayoutView())
return toLayoutView(container)->mapToVisualRectInAncestorSpace(ancestor, rect, position == FixedPosition ? IsFixed : 0, visualRectFlags);
else
return container->mapToVisualRectInAncestorSpace(ancestor, rect, visualRectFlags);
}
void LayoutBox::inflateVisualRectForFilter(LayoutRect& paintInvalidationRect) const
{
if (layer() && layer()->hasFilterInducingProperty())
paintInvalidationRect = layer()->mapLayoutRectForFilter(paintInvalidationRect);
}
void LayoutBox::updateLogicalWidth()
{
LogicalExtentComputedValues computedValues;
computeLogicalWidth(computedValues);
setLogicalWidth(computedValues.m_extent);
setLogicalLeft(computedValues.m_position);
setMarginStart(computedValues.m_margins.m_start);
setMarginEnd(computedValues.m_margins.m_end);
}
static float getMaxWidthListMarker(const LayoutBox* layoutObject)
{
#if ENABLE(ASSERT)
ASSERT(layoutObject);
Node* parentNode = layoutObject->generatingNode();
ASSERT(parentNode);
ASSERT(isHTMLOListElement(parentNode) || isHTMLUListElement(parentNode));
ASSERT(layoutObject->style()->textAutosizingMultiplier() != 1);
#endif
float maxWidth = 0;
for (LayoutObject* child = layoutObject->slowFirstChild(); child; child = child->nextSibling()) {
if (!child->isListItem())
continue;
LayoutBox* listItem = toLayoutBox(child);
for (LayoutObject* itemChild = listItem->slowFirstChild(); itemChild; itemChild = itemChild->nextSibling()) {
if (!itemChild->isListMarker())
continue;
LayoutBox* itemMarker = toLayoutBox(itemChild);
// Make sure to compute the autosized width.
if (itemMarker->needsLayout())
itemMarker->layout();
maxWidth = std::max<float>(maxWidth, toLayoutListMarker(itemMarker)->logicalWidth().toFloat());
break;
}
}
return maxWidth;
}
DISABLE_CFI_PERF
void LayoutBox::computeLogicalWidth(LogicalExtentComputedValues& computedValues) const
{
computedValues.m_extent = style()->containsSize() ? borderAndPaddingLogicalWidth() : logicalWidth();
computedValues.m_position = logicalLeft();
computedValues.m_margins.m_start = marginStart();
computedValues.m_margins.m_end = marginEnd();
if (isOutOfFlowPositioned()) {
computePositionedLogicalWidth(computedValues);
return;
}
// The parent box is flexing us, so it has increased or decreased our
// width. Use the width from the style context.
// FIXME: Account for writing-mode in flexible boxes.
// https://bugs.webkit.org/show_bug.cgi?id=46418
if (hasOverrideLogicalContentWidth() && parent()->isFlexibleBoxIncludingDeprecated()) {
computedValues.m_extent = overrideLogicalContentWidth() + borderAndPaddingLogicalWidth();
return;
}
// FIXME: Account for writing-mode in flexible boxes.
// https://bugs.webkit.org/show_bug.cgi?id=46418
bool inVerticalBox = parent()->isDeprecatedFlexibleBox() && (parent()->style()->boxOrient() == VERTICAL);
bool stretching = (parent()->style()->boxAlign() == BSTRETCH);
// TODO (lajava): Stretching is the only reason why we don't want the box to be treated as a replaced element, so we could perhaps
// refactor all this logic, not only for flex and grid since alignment is intended to be applied to any block.
bool treatAsReplaced = shouldComputeSizeAsReplaced() && (!inVerticalBox || !stretching) && (!isGridItem() || !hasStretchedLogicalWidth());
const ComputedStyle& styleToUse = styleRef();
Length logicalWidthLength = treatAsReplaced ? Length(computeReplacedLogicalWidth(), Fixed) : styleToUse.logicalWidth();
LayoutBlock* cb = containingBlock();
LayoutUnit containerLogicalWidth = std::max(LayoutUnit(), containingBlockLogicalWidthForContent());
bool hasPerpendicularContainingBlock = cb->isHorizontalWritingMode() != isHorizontalWritingMode();
if (isInline() && !isInlineBlockOrInlineTable()) {
// just calculate margins
computedValues.m_margins.m_start = minimumValueForLength(styleToUse.marginStart(), containerLogicalWidth);
computedValues.m_margins.m_end = minimumValueForLength(styleToUse.marginEnd(), containerLogicalWidth);
if (treatAsReplaced)
computedValues.m_extent = std::max(LayoutUnit(floatValueForLength(logicalWidthLength, 0)) + borderAndPaddingLogicalWidth(), minPreferredLogicalWidth());
return;
}
LayoutUnit containerWidthInInlineDirection = containerLogicalWidth;
if (hasPerpendicularContainingBlock)
containerWidthInInlineDirection = perpendicularContainingBlockLogicalHeight();
// Width calculations
if (treatAsReplaced) {
computedValues.m_extent = LayoutUnit(logicalWidthLength.value()) + borderAndPaddingLogicalWidth();
} else if (parent()->isLayoutGrid() && style()->logicalWidth().isAuto() && style()->logicalMinWidth().isAuto() && style()->overflowInlineDirection() == OverflowVisible && containerWidthInInlineDirection < minPreferredLogicalWidth()) {
// TODO (lajava) Move this logic to the LayoutGrid class.
// Implied minimum size of Grid items.
computedValues.m_extent = constrainLogicalWidthByMinMax(minPreferredLogicalWidth(), containerWidthInInlineDirection, cb);
} else {
LayoutUnit preferredWidth = computeLogicalWidthUsing(MainOrPreferredSize, styleToUse.logicalWidth(), containerWidthInInlineDirection, cb);
computedValues.m_extent = constrainLogicalWidthByMinMax(preferredWidth, containerWidthInInlineDirection, cb);
}
// Margin calculations.
computeMarginsForDirection(InlineDirection, cb, containerLogicalWidth, computedValues.m_extent, computedValues.m_margins.m_start,
computedValues.m_margins.m_end, style()->marginStart(), style()->marginEnd());
if (!hasPerpendicularContainingBlock && containerLogicalWidth && containerLogicalWidth != (computedValues.m_extent + computedValues.m_margins.m_start + computedValues.m_margins.m_end)
&& !isFloating() && !isInline() && !cb->isFlexibleBoxIncludingDeprecated() && !cb->isLayoutGrid()) {
LayoutUnit newMargin = containerLogicalWidth - computedValues.m_extent - cb->marginStartForChild(*this);
bool hasInvertedDirection = cb->style()->isLeftToRightDirection() != style()->isLeftToRightDirection();
if (hasInvertedDirection)
computedValues.m_margins.m_start = newMargin;
else
computedValues.m_margins.m_end = newMargin;
}
if (styleToUse.textAutosizingMultiplier() != 1 && styleToUse.marginStart().type() == Fixed) {
Node* parentNode = generatingNode();
if (parentNode && (isHTMLOListElement(*parentNode) || isHTMLUListElement(*parentNode))) {
// Make sure the markers in a list are properly positioned (i.e. not chopped off) when autosized.
const float adjustedMargin = (1 - 1.0 / styleToUse.textAutosizingMultiplier()) * getMaxWidthListMarker(this);
bool hasInvertedDirection = cb->style()->isLeftToRightDirection() != style()->isLeftToRightDirection();
if (hasInvertedDirection)
computedValues.m_margins.m_end += adjustedMargin;
else
computedValues.m_margins.m_start += adjustedMargin;
}
}
}
LayoutUnit LayoutBox::fillAvailableMeasure(LayoutUnit availableLogicalWidth) const
{
LayoutUnit marginStart;
LayoutUnit marginEnd;
return fillAvailableMeasure(availableLogicalWidth, marginStart, marginEnd);
}
LayoutUnit LayoutBox::fillAvailableMeasure(LayoutUnit availableLogicalWidth, LayoutUnit& marginStart, LayoutUnit& marginEnd) const
{
ASSERT(availableLogicalWidth >= 0);
marginStart = minimumValueForLength(style()->marginStart(), availableLogicalWidth);
marginEnd = minimumValueForLength(style()->marginEnd(), availableLogicalWidth);
LayoutUnit available = availableLogicalWidth - marginStart - marginEnd;
available = std::max(available, LayoutUnit());
return available;
}
DISABLE_CFI_PERF
LayoutUnit LayoutBox::computeIntrinsicLogicalWidthUsing(const Length& logicalWidthLength, LayoutUnit availableLogicalWidth, LayoutUnit borderAndPadding) const
{
if (logicalWidthLength.type() == FillAvailable)
return std::max(borderAndPadding, fillAvailableMeasure(availableLogicalWidth));
LayoutUnit minLogicalWidth;
LayoutUnit maxLogicalWidth;
computeIntrinsicLogicalWidths(minLogicalWidth, maxLogicalWidth);
if (logicalWidthLength.type() == MinContent)
return minLogicalWidth + borderAndPadding;
if (logicalWidthLength.type() == MaxContent)
return maxLogicalWidth + borderAndPadding;
if (logicalWidthLength.type() == FitContent) {
minLogicalWidth += borderAndPadding;
maxLogicalWidth += borderAndPadding;
return std::max(minLogicalWidth, std::min(maxLogicalWidth, fillAvailableMeasure(availableLogicalWidth)));
}
ASSERT_NOT_REACHED();
return LayoutUnit();
}
DISABLE_CFI_PERF
LayoutUnit LayoutBox::computeLogicalWidthUsing(SizeType widthType, const Length& logicalWidth, LayoutUnit availableLogicalWidth, const LayoutBlock* cb) const
{
ASSERT(widthType == MinSize || widthType == MainOrPreferredSize || !logicalWidth.isAuto());
if (widthType == MinSize && logicalWidth.isAuto())
return adjustBorderBoxLogicalWidthForBoxSizing(0);
if (!logicalWidth.isIntrinsicOrAuto()) {
// FIXME: If the containing block flow is perpendicular to our direction we need to use the available logical height instead.
return adjustBorderBoxLogicalWidthForBoxSizing(valueForLength(logicalWidth, availableLogicalWidth));
}
if (logicalWidth.isIntrinsic())
return computeIntrinsicLogicalWidthUsing(logicalWidth, availableLogicalWidth, borderAndPaddingLogicalWidth());
LayoutUnit marginStart;
LayoutUnit marginEnd;
LayoutUnit logicalWidthResult = fillAvailableMeasure(availableLogicalWidth, marginStart, marginEnd);
if (shrinkToAvoidFloats() && cb->isLayoutBlockFlow() && toLayoutBlockFlow(cb)->containsFloats())
logicalWidthResult = std::min(logicalWidthResult, shrinkLogicalWidthToAvoidFloats(marginStart, marginEnd, toLayoutBlockFlow(cb)));
if (widthType == MainOrPreferredSize && sizesLogicalWidthToFitContent(logicalWidth))
return std::max(minPreferredLogicalWidth(), std::min(maxPreferredLogicalWidth(), logicalWidthResult));
return logicalWidthResult;
}
bool LayoutBox::columnFlexItemHasStretchAlignment() const
{
// auto margins mean we don't stretch. Note that this function will only be used for
// widths, so we don't have to check marginBefore/marginAfter.
const auto& parentStyle = parent()->styleRef();
DCHECK(parentStyle.isColumnFlexDirection());
if (styleRef().marginStart().isAuto() || styleRef().marginEnd().isAuto())
return false;
return styleRef().resolvedAlignSelf(containingBlock()->selfAlignmentNormalBehavior(), isAnonymous() ? &parentStyle : nullptr).position() == ItemPositionStretch;
}
bool LayoutBox::isStretchingColumnFlexItem() const
{
LayoutObject* parent = this->parent();
if (parent->isDeprecatedFlexibleBox() && parent->style()->boxOrient() == VERTICAL && parent->style()->boxAlign() == BSTRETCH)
return true;
// We don't stretch multiline flexboxes because they need to apply line spacing (align-content) first.
if (parent->isFlexibleBox() && parent->style()->flexWrap() == FlexNoWrap && parent->style()->isColumnFlexDirection() && columnFlexItemHasStretchAlignment())
return true;
return false;
}
// TODO (lajava) Can/Should we move this inside specific layout classes (flex. grid)? Can we refactor columnFlexItemHasStretchAlignment logic?
bool LayoutBox::hasStretchedLogicalWidth() const
{
const ComputedStyle& style = styleRef();
if (!style.logicalWidth().isAuto() || style.marginStart().isAuto() || style.marginEnd().isAuto())
return false;
LayoutBlock* cb = containingBlock();
if (!cb) {
// We are evaluating align-self/justify-self, which default to 'normal' for the root element.
// The 'normal' value behaves like 'start' except for Flexbox Items, which obviously should have a container.
return false;
}
const ComputedStyle* parentStyle = isAnonymous() ? cb->style() : nullptr;
if (cb->isHorizontalWritingMode() != isHorizontalWritingMode())
return style.resolvedAlignSelf(cb->selfAlignmentNormalBehavior(), parentStyle).position() == ItemPositionStretch;
return style.resolvedJustifySelf(cb->selfAlignmentNormalBehavior(), parentStyle).position() == ItemPositionStretch;
}
bool LayoutBox::sizesLogicalWidthToFitContent(const Length& logicalWidth) const
{
if (isFloating() || isInlineBlockOrInlineTable())
return true;
if (isGridItem())
return !hasStretchedLogicalWidth();
// Flexible box items should shrink wrap, so we lay them out at their intrinsic widths.
// In the case of columns that have a stretch alignment, we go ahead and layout at the
// stretched size to avoid an extra layout when applying alignment.
if (parent()->isFlexibleBox()) {
// For multiline columns, we need to apply align-content first, so we can't stretch now.
if (!parent()->style()->isColumnFlexDirection() || parent()->style()->flexWrap() != FlexNoWrap)
return true;
if (!columnFlexItemHasStretchAlignment())
return true;
}
// Flexible horizontal boxes lay out children at their intrinsic widths. Also vertical boxes
// that don't stretch their kids lay out their children at their intrinsic widths.
// FIXME: Think about writing-mode here.
// https://bugs.webkit.org/show_bug.cgi?id=46473
if (parent()->isDeprecatedFlexibleBox() && (parent()->style()->boxOrient() == HORIZONTAL || parent()->style()->boxAlign() != BSTRETCH))
return true;
// Button, input, select, textarea, and legend treat width value of 'auto' as 'intrinsic' unless it's in a
// stretching column flexbox.
// FIXME: Think about writing-mode here.
// https://bugs.webkit.org/show_bug.cgi?id=46473
if (logicalWidth.isAuto() && !isStretchingColumnFlexItem() && autoWidthShouldFitContent())
return true;
if (isHorizontalWritingMode() != containingBlock()->isHorizontalWritingMode())
return true;
return false;
}
bool LayoutBox::autoWidthShouldFitContent() const
{
return node() && (isHTMLInputElement(*node()) || isHTMLSelectElement(*node()) || isHTMLButtonElement(*node())
|| isHTMLTextAreaElement(*node()) || (isHTMLLegendElement(*node()) && !style()->hasOutOfFlowPosition()));
}
void LayoutBox::computeMarginsForDirection(MarginDirection flowDirection, const LayoutBlock* containingBlock, LayoutUnit containerWidth, LayoutUnit childWidth, LayoutUnit& marginStart, LayoutUnit& marginEnd, Length marginStartLength, Length marginEndLength) const
{
// First assert that we're not calling this method on box types that don't support margins.
ASSERT(!isTableCell());
ASSERT(!isTableRow());
ASSERT(!isTableSection());
ASSERT(!isLayoutTableCol());
if (flowDirection == BlockDirection || isFloating() || isInline()) {
// Margins are calculated with respect to the logical width of
// the containing block (8.3)
// Inline blocks/tables and floats don't have their margins increased.
marginStart = minimumValueForLength(marginStartLength, containerWidth);
marginEnd = minimumValueForLength(marginEndLength, containerWidth);
return;
}
if (containingBlock->isFlexibleBox()) {
// We need to let flexbox handle the margin adjustment - otherwise, flexbox
// will think we're wider than we actually are and calculate line sizes wrong.