blob: 2f7d9e9e1065d14c5d1857d4b7761377607469c0 [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/LayoutReplica.h"
#include "core/layout/LayoutTableCell.h"
#include "core/layout/LayoutView.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;
// Used by grid elements to properly size their grid items.
// FIXME: Move these into LayoutBoxRareData.
static OverrideSizeMap* gOverrideContainingBlockLogicalHeightMap = nullptr;
static OverrideSizeMap* gOverrideContainingBlockLogicalWidthMap = nullptr;
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.
LayoutUnit LayoutBox::clientWidth() const
{
return m_frameRect.width() - borderLeft() - borderRight() - verticalScrollbarWidth();
}
LayoutUnit LayoutBox::clientHeight() const
{
return m_frameRect.height() - borderTop() - borderBottom() - horizontalScrollbarHeight();
}
int LayoutBox::pixelSnappedClientWidth() const
{
return snapSizeToPixel(clientWidth(), location().x() + clientLeft());
}
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;
LayoutBox* parentBox = nullptr;
LayoutRect newRect = rect;
bool restrictedByLineClamp = false;
if (parent()) {
parentBox = parent()->enclosingBox();
restrictedByLineClamp = !parent()->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(rect, alignX, alignY, scrollType);
} else if (!parentBox && canBeProgramaticallyScrolled()) {
if (FrameView* frameView = this->frameView()) {
HTMLFrameOwnerElement* ownerElement = document().localOwner();
if (!isDisallowedAutoscroll(ownerElement, frameView)) {
if (makeVisibleInVisualViewport) {
frameView->getScrollableArea()->scrollIntoView(rect, alignX, alignY, scrollType);
} else {
frameView->layoutViewportScrollableArea()->scrollIntoView(rect, alignX, alignY, scrollType);
}
if (ownerElement && ownerElement->layoutObject()) {
if (frameView->safeToPropagateScrollToParent()) {
parentBox = ownerElement->layoutObject()->enclosingBox();
LayoutView* parentView = ownerElement->layoutObject()->view();
newRect = enclosingLayoutRect(view()->localToAncestorQuad(FloatRect(rect), 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
{
quads.append(localToAbsoluteQuad(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::backgroundClipRect() const
{
// TODO(flackr): Check for the maximum background clip rect.
switch (style()->backgroundClip()) {
case BorderFillBox:
// A 'border-box' clip on scrollable elements local attachment is treated as 'padding-box'.
// https://www.w3.org/TR/css3-background/#the-background-attachment
if (!style()->isOverflowVisible() && style()->backgroundLayers().attachment() == LocalBackgroundAttachment)
return paddingBoxRect();
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());
return valueForLength(style()->boxReflect()->offset(), borderBoxRect().height());
}
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::panScroll(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::noPanScrollRadius) // at the center we let the space for the icon
delta.setWidth(0);
if (abs(delta.height()) <= AutoscrollController::noPanScrollRadius)
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().hasPercent() || style()->paddingEnd().hasPercent();
}
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;
}
bool LayoutBox::mapScrollingContentsRectToBoxSpace(LayoutRect& rect, ApplyOverflowClipFlag applyOverflowClip, VisualRectFlags visualRectFlags) const
{
if (!hasOverflowClip())
return true;
if (applyOverflowClip == ApplyNonScrollOverflowClip && scrollsOverflow()) {
return true;
}
LayoutSize offset = LayoutSize(-scrolledContentOffset());
rect.move(offset);
LayoutRect clipRect = overflowClipRect(LayoutPoint());
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;
}
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
{
ASSERT(hasOverrideContainingBlockLogicalWidth());
return gOverrideContainingBlockLogicalWidthMap->get(this);
}
// TODO (lajava) Shouldn't we implement these functions based on physical direction ?.
LayoutUnit LayoutBox::overrideContainingBlockContentLogicalHeight() const
{
ASSERT(hasOverrideContainingBlockLogicalHeight());
return gOverrideContainingBlockLogicalHeightMap->get(this);
}
// TODO (lajava) Shouldn't we implement these functions based on physical direction ?.
bool LayoutBox::hasOverrideContainingBlockLogicalWidth() const
{
return gOverrideContainingBlockLogicalWidthMap && gOverrideContainingBlockLogicalWidthMap->contains(this);
}
// TODO (lajava) Shouldn't we implement these functions based on physical direction ?.
bool LayoutBox::hasOverrideContainingBlockLogicalHeight() const
{
return gOverrideContainingBlockLogicalHeightMap && gOverrideContainingBlockLogicalHeightMap->contains(this);
}
// TODO (lajava) Shouldn't we implement these functions based on physical direction ?.
void LayoutBox::setOverrideContainingBlockContentLogicalWidth(LayoutUnit logicalWidth)
{
if (!gOverrideContainingBlockLogicalWidthMap)
gOverrideContainingBlockLogicalWidthMap = new OverrideSizeMap;
gOverrideContainingBlockLogicalWidthMap->set(this, logicalWidth);
}
// TODO (lajava) Shouldn't we implement these functions based on physical direction ?.
void LayoutBox::setOverrideContainingBlockContentLogicalHeight(LayoutUnit logicalHeight)
{
if (!gOverrideContainingBlockLogicalHeightMap)
gOverrideContainingBlockLogicalHeightMap = new OverrideSizeMap;
gOverrideContainingBlockLogicalHeightMap->set(this, logicalHeight);
}
// TODO (lajava) Shouldn't we implement these functions based on physical direction ?.
void LayoutBox::clearContainingBlockOverrideSize()
{
if (gOverrideContainingBlockLogicalWidthMap)
gOverrideContainingBlockLogicalWidthMap->remove(this);
clearOverrideContainingBlockContentLogicalHeight();
}
// TODO (lajava) Shouldn't we implement these functions based on physical direction ?.
void LayoutBox::clearOverrideContainingBlockContentLogicalHeight()
{
if (gOverrideContainingBlockLogicalHeightMap)
gOverrideContainingBlockLogicalHeightMap->remove(this);
}
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);
}
static LayoutUnit borderPaddingWidthForBoxSizing(const LayoutBox* box)
{
// This excludes intrinsic padding on cells. It includes width from collapsed borders.
return box->computedCSSPaddingStart() + box->computedCSSPaddingEnd() + box->borderStart() + box->borderEnd();
}
static LayoutUnit borderPaddingHeightForBoxSizing(const LayoutBox* box)
{
// This excludes intrinsic padding on cells. It includes height from collapsed borders.
return box->computedCSSPaddingBefore() + box->computedCSSPaddingAfter() + box->borderBefore() + box->borderAfter();
}
LayoutUnit LayoutBox::adjustBorderBoxLogicalWidthForBoxSizing(float width) const
{
LayoutUnit bordersPlusPadding = borderPaddingWidthForBoxSizing(this);
LayoutUnit result(width);
if (style()->boxSizing() == BoxSizingContentBox)
return result + bordersPlusPadding;
return std::max(result, bordersPlusPadding);
}
LayoutUnit LayoutBox::adjustBorderBoxLogicalHeightForBoxSizing(float height) const
{
LayoutUnit bordersPlusPadding = borderPaddingHeightForBoxSizing(this);
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 -= borderPaddingWidthForBoxSizing(this);
return std::max(LayoutUnit(), result);
}
LayoutUnit LayoutBox::adjustContentBoxLogicalHeightForBoxSizing(float height) const
{
LayoutUnit result(height);
if (style()->boxSizing() == BoxSizingBorderBox)
result -= borderPaddingHeightForBoxSizing(this);
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;
Color backgroundColor = resolveColor(CSSPropertyBackgroundColor);
if (backgroundColor.hasAlpha())
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;
// FIXME: The background color clip is defined by the last layer.
if (style()->backgroundLayers().next())
return false;
return backgroundClipRect().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;
}
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)) {
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)
setMayNeedPaintInvalidationAnimatgedBackgroundImage();
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 = static_cast<uint32_t>(screenRect.width() * screenRect.height());
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()->ownerLayoutObject())
layoutView = layoutView->frame()->ownerLayoutObject()->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();
}
void LayoutBox::invalidatePaintOfSubtreesIfNeeded(const PaintInvalidationState& childPaintInvalidationState)
{
LayoutBoxModelObject::invalidatePaintOfSubtreesIfNeeded(childPaintInvalidationState);
if (PaintLayer* layer = this->layer()) {
if (PaintLayerReflectionInfo* reflectionInfo = layer->reflectionInfo())
reflectionInfo->reflection()->invalidateTreeIfNeeded(childPaintInvalidationState);
}
}
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 (style()->canContainFixedPositionObjects() && !isFixedPos) {
// 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.
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::paintedOutputOfObjectHasNoEffect() 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::inflateVisualRectForReflectionAndFilterUnderContainer(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)->inflateVisualRectForReflectionAndFilter(rect);
rect.move(parentOffset);
}
if (parent == ancestorToStopAt)
break;
}
rect.move(-offsetFromContainer);
}
bool LayoutBox::mapToVisualRectInAncestorSpace(const LayoutBoxModelObject* ancestor, LayoutRect& rect, VisualRectFlags visualRectFlags) const
{
inflateVisualRectForReflectionAndFilter(rect);
if (ancestor == this)
return true;
bool ancestorSkipped;
bool filterOrReflectionSkipped;
LayoutObject* container = this->container(ancestor, &ancestorSkipped, &filterOrReflectionSkipped);
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).
if (container->isTableRow()) {
DCHECK(isTableCell() && parentBox() == container);
if (container != ancestor)
container = container->parent();
else
tableRowContainer = toLayoutBox(container);
}
if (!container)
return true;
if (filterOrReflectionSkipped)
inflateVisualRectForReflectionAndFilterUnderContainer(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::inflateVisualRectForReflectionAndFilter(LayoutRect& paintInvalidationRect) const
{
if (!RuntimeEnabledFeatures::cssBoxReflectFilterEnabled() && hasReflection())
paintInvalidationRect.unite(reflectedRect(paintInvalidationRect));
if (layer() && layer()->hasFilterInducingProperty())
paintInvalidationRect = layer()->mapLayoutRectForFilter(paintInvalidationRect);
}
void LayoutBox::invalidatePaintForOverhangingFloats(bool)
{
}
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;
}
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;
}
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();
}
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;
}
static bool columnFlexItemHasStretchAlignment(const LayoutObject* flexitem)
{
LayoutObject* parent = flexitem->parent();
// 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.
ASSERT(parent->style()->isColumnFlexDirection());
if (flexitem->style()->marginStart().isAuto() || flexitem->style()->marginEnd().isAuto())
return false;
return flexitem->style()->alignSelfPosition() == ItemPositionStretch || (flexitem->style()->alignSelfPosition() == ItemPositionAuto && parent->style()->alignItemsPosition() == ItemPositionStretch);
}
static bool isStretchingColumnFlexItem(const LayoutObject* flexitem)
{
LayoutObject* parent = flexitem->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(flexitem))
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;
}
if (cb->isHorizontalWritingMode() != isHorizontalWritingMode())
return ComputedStyle::resolveAlignment(cb->styleRef(), style, ItemPositionStretch) == ItemPositionStretch;
return ComputedStyle::resolveJustification(cb->styleRef(), style, ItemPositionStretch) == 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(this))
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(this) && 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.
// See also http://dev.w3.org/csswg/css-flexbox/#auto-margins
if (marginStartLength.isAuto())
marginStartLength.setValue(0);
if (marginEndLength.isAuto())
marginEndLength.setValue(0);
}
LayoutUnit marginStartWidth = minimumValueForLength(marginStartLength, containerWidth);
LayoutUnit marginEndWidth = minimumValueForLength(marginEndLength, containerWidth);
LayoutUnit availableWidth = containerWidth;
if (avoidsFloats() && containingBlock->isLayoutBlockFlow() && toLayoutBlockFlow(containingBlock)->containsFloats()) {
availableWidth = containingBlockAvailableLineWidth();
if (shrinkToAvoidFloats() && availableWidth < containerWidth) {
marginStart = std::max(LayoutUnit(), marginStartWidth);
marginEnd = std::max(LayoutUnit(), marginEndWidth);
}
}
// CSS 2.1 (10.3.3): "If 'width' is not 'auto' and 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width'
// (plus any of 'margin-left' or 'margin-right' that are not 'auto') is larger than the width of the containing block, then any 'auto'
// values for 'margin-left' or 'margin-right' are, for the following rules, treated as zero.
LayoutUnit marginBoxWidth = childWidth + (!style()->width().isAuto() ? marginStartWidth + marginEndWidth : LayoutUnit());
if (marginBoxWidth < availableWidth) {
// CSS 2.1: "If both 'margin-left' and 'margin-right' are 'auto', their used values are equal. This horizontally centers the element
// with respect to the edges of the containing block."
const ComputedStyle& containingBlockStyle = containingBlock->styleRef();
if ((marginStartLength.isAuto() && marginEndLength.isAuto())
|| (!marginStartLength.isAuto() && !marginEndLength.isAuto() && containingBlockStyle.textAlign() == WEBKIT_CENTER)) {
// Other browsers center the margin box for align=center elements so we match them here.
LayoutUnit centeredMarginBoxStart = std::max(LayoutUnit(), (availableWidth - childWidth - marginStartWidth - marginEndWidth) / 2);
marginStart = centeredMarginBoxStart + marginStartWidth;
marginEnd = availableWidth - childWidth - marginStart + marginEndWidth;
return;
}
// Adjust margins for the align attribute
if ((!containingBlockStyle.isLeftToRightDirection() && containingBlockStyle.textAlign() == WEBKIT_LEFT)
|| (containingBlockStyle.isLeftToRightDirection() && containingBlockStyle.textAlign() == WEBKIT_RIGHT)) {
if (containingBlockStyle.isLeftToRightDirection() != styleRef().isLeftToRightDirection()) {
if (!marginStartLength.isAuto())
marginEndLength = Length(Auto);
} else {
if (!marginEndLength.isAuto())
marginStartLength = Length(Auto);
}
}
// CSS 2.1: "If there is exactly one value specified as 'auto', its used value follows from the equality."
if (marginEndLength.isAuto()) {
marginStart = marginStartWidth;
marginEnd = availableWidth - childWidth - marginStart;
return;
}
if (marginStartLength.isAuto()) {
marginEnd = marginEndWidth;
marginStart = availableWidth - childWidth - marginEnd;
return;
}
}
// Either no auto margins, or our margin box width is >= the container width, auto margins will just turn into 0.
marginStart = marginStartWidth;
marginEnd = marginEndWidth;
}
void LayoutBox::updateLogicalHeight()
{
m_intrinsicContentLogicalHeight = contentLogicalHeight();
LogicalExtentComputedValues computedValues;
LayoutUnit height = style()->containsSize() ? borderAndPaddingLogicalHeight() : logicalHeight();
computeLogicalHeight(height, logicalTop(), computedValues);
setLogicalHeight(computedValues.m_extent);
setLogicalTop(computedValues.m_position);
setMarginBefore(computedValues.m_margins.m_before);
setMarginAfter(computedValues.m_margins.m_after);
}
void LayoutBox::computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
{
computedValues.m_extent = logicalHeight;
computedValues.m_position = logicalTop;
// Cell height is managed by the table.
if (isTableCell())
return;
Length h;
if (isOutOfFlowPositioned()) {
computePositionedLogicalHeight(computedValues);
} else {
LayoutBlock* cb = containingBlock();
// If we are perpendicular to our containing block then we need to resolve our block-start and block-end margins so that if they
// are 'auto' we are centred or aligned within the inline flow containing block: this is done by computing the margins as though they are inline.
// Note that as this is the 'sizing phase' we are using our own writing mode rather than the containing block's. We use the containing block's
// writing mode when figuring out the block-direction margins for positioning in |computeAndSetBlockDirectionMargins| (i.e. margin collapsing etc.).
// See http://www.w3.org/TR/2014/CR-css-writing-modes-3-20140320/#orthogonal-flows
MarginDirection flowDirection = isHorizontalWritingMode() != cb->isHorizontalWritingMode() ? InlineDirection : BlockDirection;
// For tables, calculate margins only.
if (isTable()) {
computeMarginsForDirection(flowDirection, cb, containingBlockLogicalWidthForContent(), computedValues.m_extent, computedValues.m_margins.m_before,
computedValues.m_margins.m_after, style()->marginBefore(), style()->marginAfter());
return;
}
// FIXME: Account for writing-mode in flexible boxes.
// https://bugs.webkit.org/show_bug.cgi?id=46418
bool inHorizontalBox = parent()->isDeprecatedFlexibleBox() && parent()->style()->boxOrient() == HORIZONTAL;
bool stretching = parent()->style()->boxAlign() == BSTRETCH;
bool treatAsReplaced = shouldComputeSizeAsReplaced() && (!inHorizontalBox || !stretching);
bool checkMinMaxHeight = false;
// The parent box is flexing us, so it has increased or decreased our height. We have to
// grab our cached flexible height.
// FIXME: Account for writing-mode in flexible boxes.
// https://bugs.webkit.org/show_bug.cgi?id=46418
if (hasOverrideLogicalContentHeight()) {
LayoutUnit contentHeight = overrideLogicalContentHeight();
if (parent()->isLayoutGrid() && style()->logicalMinHeight().isAuto() && style()->overflowY() == OverflowVisible) {
ASSERT(style()->logicalHeight().isAuto());
LayoutUnit minContentHeight = computeContentLogicalHeight(MinSize, Length(MinContent), computedValues.m_extent - borderAndPaddingLogicalHeight());
contentHeight = std::max(contentHeight, constrainContentBoxLogicalHeightByMinMax(minContentHeight, computedValues.m_extent - borderAndPaddingLogicalHeight()));
}
h = Length(contentHeight, Fixed);
} else if (treatAsReplaced) {
h = Length(computeReplacedLogicalHeight(), Fixed);
} else {
h = style()->logicalHeight();
checkMinMaxHeight = true;
}
// Block children of horizontal flexible boxes fill the height of the box.
// FIXME: Account for writing-mode in flexible boxes.
// https://bugs.webkit.org/show_bug.cgi?id=46418
if (h.isAuto() && inHorizontalBox && toLayoutDeprecatedFlexibleBox(parent())->isStretchingChildren()) {
h = Length(parentBox()->contentLogicalHeight() - marginBefore() - marginAfter() - borderAndPaddingLogicalHeight(), Fixed);
checkMinMaxHeight = false;
}
LayoutUnit heightResult;
if (checkMinMaxHeight) {
heightResult = computeLogicalHeightUsing(MainOrPreferredSize, style()->logicalHeight(), computedValues.m_extent - borderAndPaddingLogicalHeight());
if (heightResult == -1)
heightResult = computedValues.m_extent;
heightResult = constrainLogicalHeightByMinMax(heightResult, computedValues.m_extent - borderAndPaddingLogicalHeight());
} else {
// The only times we don't check min/max height are when a fixed length has
// been given as an override. Just use that. The value has already been adjusted
// for box-sizing.
ASSERT(h.isFixed());
heightResult = LayoutUnit(h.value()) + borderAndPaddingLogicalHeight();
}
computedValues.m_extent = heightResult;
computeMarginsForDirection(flowDirection, cb, containingBlockLogicalWidthForContent(), computedValues.m_extent, computedValues.m_margins.m_before,
computedValues.m_margins.m_after, style()->marginBefore(), style()->marginAfter());
}
// WinIE quirk: The <html> block always fills the entire canvas in quirks mode. The <body> always fills the
// <html> block in quirks mode. Only apply this quirk if the block is normal flow and no height
// is specified. When we're printing, we also need this quirk if the body or root has a percentage
// height since we don't set a height in LayoutView when we're printing. So without this quirk, the
// height has nothing to be a percentage of, and it ends up being 0. That is bad.
bool paginatedContentNeedsBaseHeight = document().printing() && h.hasPercent()
&& (isDocumentElement() || (isBody() && document().documentElement()->layoutObject()->style()->logicalHeight().hasPercent())) && !isInline();
if (stretchesToViewport() || paginatedContentNeedsBaseHeight) {
LayoutUnit margins = collapsedMarginBefore() + collapsedMarginAfter();
LayoutUnit visibleHeight = view()->viewLogicalHeightForPercentages();
if (isDocumentElement()) {
computedValues.m_extent = std::max(computedValues.m_extent, visibleHeight - margins);
} else {
LayoutUnit marginsBordersPadding = margins + parentBox()->marginBefore() + parentBox()->marginAfter() + parentBox()->borderAndPaddingLogicalHeight();
computedValues.m_extent = std::max(computedValues.m_extent, visibleHeight - marginsBordersPadding);
}
}
}
LayoutUnit LayoutBox::computeLogicalHeightWithoutLayout() const
{
// TODO(cbiesinger): We should probably return something other than just border + padding, but for now
// we have no good way to do anything else without layout, so we just use that.
LogicalExtentComputedValues computedValues;
computeLogicalHeight(borderAndPaddingLogicalHeight(), LayoutUnit(), computedValues);
return computedValues.m_extent;
}
LayoutUnit LayoutBox::computeLogicalHeightUsing(SizeType heightType, const Length& height, LayoutUnit intrinsicContentHeight) const
{
LayoutUnit logicalHeight = computeContentAndScrollbarLogicalHeightUsing(heightType, height, intrinsicContentHeight);
if (logicalHeight != -1) {
if (height.isSpecified())
logicalHeight = adjustBorderBoxLogicalHeightForBoxSizing(logicalHeight);
else
logicalHeight += borderAndPaddingLogicalHeight();
}
return logicalHeight;
}
LayoutUnit LayoutBox::computeContentLogicalHeight(SizeType heightType, const Length& height, LayoutUnit intrinsicContentHeight) const
{
LayoutUnit heightIncludingScrollbar = computeContentAndScrollbarLogicalHeightUsing(heightType, height, intrinsicContentHeight);
if (heightIncludingScrollbar == -1)
return LayoutUnit(-1);
LayoutUnit adjusted = heightIncludingScrollbar;
if (height.isSpecified()) {
// Keywords don't get adjusted for box-sizing
adjusted = adjustContentBoxLogicalHeightForBoxSizing(heightIncludingScrollbar);
}
return std::max(LayoutUnit(), adjusted - scrollbarLogicalHeight());
}
LayoutUnit LayoutBox::computeIntrinsicLogicalContentHeightUsing(const Length& logicalHeightLength, LayoutUnit intrinsicContentHeight, LayoutUnit borderAndPadding) const
{
// FIXME(cbiesinger): The css-sizing spec is considering changing what min-content/max-content should resolve to.
// If that happens, this code will have to change.
if (logicalHeightLength.isMinContent() || logicalHeightLength.isMaxContent() || logicalHeightLength.isFitContent()) {
if (isAtomicInlineLevel())
return intrinsicSize().height();
return intrinsicContentHeight;
}
if (logicalHeightLength.isFillAvailable())
return containingBlock()->availableLogicalHeight(ExcludeMarginBorderPadding) - borderAndPadding;
ASSERT_NOT_REACHED();
return LayoutUnit();
}
LayoutUnit LayoutBox::computeContentAndScrollbarLogicalHeightUsing(SizeType heightType, const Length& height, LayoutUnit intrinsicContentHeight) const
{
if (height.isAuto())
return heightType == MinSize ? LayoutUnit() : LayoutUnit(-1);
// FIXME(cbiesinger): The css-sizing spec is considering changing what min-content/max-content should resolve to.
// If that happens, this code will have to change.
if (height.isIntrinsic()) {
if (intrinsicContentHeight == -1)
return LayoutUnit(-1); // Intrinsic height isn't available.
return computeIntrinsicLogicalContentHeightUsing(height, intrinsicContentHeight, borderAndPaddingLogicalHeight()) + scrollbarLogicalHeight();
}
if (height.isFixed())
return LayoutUnit(height.value());
if (height.hasPercent())
return computePercentageLogicalHeight(height);
return LayoutUnit(-1);
}
bool LayoutBox::stretchesToViewportInQuirksMode() const
{
if (!isDocumentElement() && !isBody())
return false;
return style()->logicalHeight().isAuto() && !isFloatingOrOutOfFlowPositioned() && !isInline() && !flowThreadContainingBlock();
}
bool LayoutBox::skipContainingBlockForPercentHeightCalculation(const LayoutBox* containingBlock) const
{
// If the writing mode of the containing block is orthogonal to ours, it means that we shouldn't
// skip anything, since we're going to resolve the percentage height against a containing block *width*.
if (isHorizontalWritingMode() != containingBlock->isHorizontalWritingMode())
return false;
// Anonymous blocks should not impede percentage resolution on a child. Examples of such
// anonymous blocks are blocks wrapped around inlines that have block siblings (from the CSS
// spec) and multicol flow threads (an implementation detail). Another implementation detail,
// ruby runs, create anonymous inline-blocks, so skip those too. All other types of anonymous
// objects, such as table-cells, will be treated just as if they were non-anonymous.
if (containingBlock->isAnonymous()) {
EDisplay display = containingBlock->styleRef().display();
return display == BLOCK || display == INLINE_BLOCK;
}
// For quirks mode, we skip most auto-height containing blocks when computing percentages.
return document().inQuirksMode() && !containingBlock->isTableCell() && !containingBlock->isOutOfFlowPositioned() && !containingBlock->isLayoutGrid() && containingBlock->style()->logicalHeight().isAuto();
}
LayoutUnit LayoutBox::computePercentageLogicalHeight(const Length& height) const
{
LayoutUnit availableHeight(-1);
bool skippedAutoHeightContainingBlock = false;
LayoutBlock* cb = containingBlock();
const LayoutBox* containingBlockChild = this;
LayoutUnit rootMarginBorderPaddingHeight;
while (!cb->isLayoutView() && skipContainingBlockForPercentHeightCalculation(cb)) {
if (cb->isBody() || cb->isDocumentElement())
rootMarginBorderPaddingHeight += cb->marginBefore() + cb->marginAfter() + cb->borderAndPaddingLogicalHeight();
skippedAutoHeightContainingBlock = true;
containingBlockChild = cb;
cb = cb->containingBlock();
}
cb->addPercentHeightDescendant(const_cast<LayoutBox*>(this));
const ComputedStyle& cbstyle = cb->styleRef();
// A positioned element that specified both top/bottom or that specifies height should be treated as though it has a height
// explicitly specified that can be used for any percentage computations.
bool isOutOfFlowPositionedWithSpecifiedHeight = cb->isOutOfFlowPositioned() && (!cbstyle.logicalHeight().isAuto() || (!cbstyle.logicalTop().isAuto() && !cbstyle.logicalBottom().isAuto()));
bool includeBorderPadding = isTable();
LayoutUnit stretchedFlexHeight(-1);
if (cb->isFlexItem())
stretchedFlexHeight = toLayoutFlexibleBox(cb->parent())->childLogicalHeightForPercentageResolution(*cb);
if (isHorizontalWritingMode() != cb->isHorizontalWritingMode()) {
availableHeight = containingBlockChild->containingBlockLogicalWidthForContent();
} else if (stretchedFlexHeight != LayoutUnit(-1)) {
availableHeight = stretchedFlexHeight;
} else if (hasOverrideContainingBlockLogicalHeight() && !isOutOfFlowPositionedWithSpecifiedHeight) {
availableHeight = overrideContainingBlockContentLogicalHeight();
} else if (cb->isGridItem() && cb->hasOverrideLogicalContentHeight()) {
availableHeight = cb->overrideLogicalContentHeight();
} else if (cbstyle.logicalHeight().isFixed()) {
LayoutUnit contentBoxHeight = cb->adjustContentBoxLogicalHeightForBoxSizing(cbstyle.logicalHeight().value());
availableHeight = cb->constrainContentBoxLogicalHeightByMinMax(
contentBoxHeight - cb->scrollbarLogicalHeight(), LayoutUnit(-1)).clampNegativeToZero();
} else if (cb->isTableCell()) {
if (!skippedAutoHeightContainingBlock) {
// Table cells violate what the CSS spec says to do with heights. Basically we
// don't care if the cell specified a height or not. We just always make ourselves
// be a percentage of the cell's current content height.
if (!cb->hasOverrideLogicalContentHeight()) {
// Normally we would let the cell size intrinsically, but scrolling overflow has to be
// treated differently, since WinIE lets scrolled overflow regions shrink as needed.
// While we can't get all cases right, we can at least detect when the cell has a specified
// height or when the table has a specified height. In these cases we want to initially have
// no size and allow the flexing of the table or the cell to its specified height to cause us
// to grow to fill the space. This could end up being wrong in some cases, but it is
// preferable to the alternative (sizing intrinsically and making the row end up too big).
LayoutTableCell* cell = toLayoutTableCell(cb);
if (scrollsOverflowY() && (!cell->style()->logicalHeight().isAuto() || !cell->table()->style()->logicalHeight().isAuto()))
return LayoutUnit();
return LayoutUnit(-1);
}
availableHeight = cb->overrideLogicalContentHeight();
includeBorderPadding = true;
}
} else if (cbstyle.logicalHeight().hasPercent() && !isOutOfFlowPositionedWithSpecifiedHeight) {
// We need to recur and compute the percentage height for our containing block.
LayoutUnit heightWithScrollbar = cb->computePercentageLogicalHeight(cbstyle.logicalHeight());
if (heightWithScrollbar != -1) {
LayoutUnit contentBoxHeightWithScrollbar = cb->adjustContentBoxLogicalHeightForBoxSizing(heightWithScrollbar);
// We need to adjust for min/max height because this method does not
// handle the min/max of the current block, its caller does. So the
// return value from the recursive call will not have been adjusted
// yet.
LayoutUnit contentBoxHeight = cb->constrainContentBoxLogicalHeightByMinMax(contentBoxHeightWithScrollbar - cb->scrollbarLogicalHeight(), LayoutUnit(-1));
availableHeight = std::max(LayoutUnit(), contentBoxHeight);
}
} else if (isOutOfFlowPositionedWithSpecifiedHeight) {
// Don't allow this to affect the block' size() member variable, since this
// can get called while the block is still laying out its kids.
LogicalExtentComputedValues computedValues;
cb->computeLogicalHeight(cb->logicalHeight(), LayoutUnit(), computedValues);
availableHeight = computedValues.m_extent - cb->borderAndPaddingLogicalHeight() - cb->scrollbarLogicalHeight();
} else if (cb->isLayoutView()) {
availableHeight = view()->viewLogicalHeightForPercentages();
}
if (availableHeight == -1)
return availableHeight;
availableHeight -= rootMarginBorderPaddingHeight;
if (isTable() && isOutOfFlowPositioned())
availableHeight += cb->paddingLogicalHeight();
LayoutUnit result = valueForLength(height, availableHeight);
if (includeBorderPadding) {
// TODO(rhogan) crbug.com/467378: Doing this for content inside tables cells is wrong, it should fill
// whatever height the cell makes available.
result -= borderAndPaddingLogicalHeight();
return std::max(LayoutUnit(), result);
}
return result;
}
LayoutUnit LayoutBox::computeReplacedLogicalWidth(ShouldComputePreferred shouldComputePreferred) const
{
return computeReplacedLogicalWidthRespectingMinMaxWidth(computeReplacedLogicalWidthUsing(MainOrPreferredSize, style()->logicalWidth()), shouldComputePreferred);
}
LayoutUnit LayoutBox::computeReplacedLogicalWidthRespectingMinMaxWidth(LayoutUnit logicalWidth, ShouldComputePreferred shouldComputePreferred) const
{
LayoutUnit minLogicalWidth = (shouldComputePreferred == ComputePreferred && style()->logicalMinWidth().hasPercent()) ? logicalWidth : computeReplacedLogicalWidthUsing(MinSize, style()->logicalMinWidth());
LayoutUnit maxLogicalWidth = (shouldComputePreferred == ComputePreferred && style()->logicalMaxWidth().hasPercent()) || style()->logicalMaxWidth().isMaxSizeNone() ? logicalWidth : computeReplacedLogicalWidthUsing(MaxSize, style()->logicalMaxWidth());
return std::max(minLogicalWidth, std::min(logicalWidth, maxLogicalWidth));
}
LayoutUnit LayoutBox::computeReplacedLogicalWidthUsing(SizeType sizeType, const Length& logicalWidth) const
{
ASSERT(sizeType == MinSize || sizeType == MainOrPreferredSize || !logicalWidth.isAuto());
if (sizeType == MinSize && logicalWidth.isAuto())
return adjustContentBoxLogicalWidthForBoxSizing(LayoutUnit());
switch (logicalWidth.type()) {
case Fixed:
return adjustContentBoxLogicalWidthForBoxSizing(logicalWidth.value());
case MinContent:
case MaxContent: {
// MinContent/MaxContent don't need the availableLogicalWidth argument.
LayoutUnit availableLogicalWidth;
return computeIntrinsicLogicalWidthUsing(logicalWidth, availableLogicalWidth, borderAndPaddingLogicalWidth()) - borderAndPaddingLogicalWidth();
}
case FitContent:
case FillAvailable:
case Percent:
case Calculated: {
// FIXME: containingBlockLogicalWidthForContent() is wrong if the replaced element's writing-mode is perpendicular to the
// containing block's writing-mode.
// https://bugs.webkit.org/show_bug.cgi?id=46496
const LayoutUnit cw = isOutOfFlowPositioned() ? containingBlockLogicalWidthForPositioned(toLayoutBoxModelObject(container())) : containingBlockLogicalWidthForContent();
Length containerLogicalWidth = containingBlock()->style()->logicalWidth();
// FIXME: Handle cases when containing block width is calculated or viewport percent.
// https://bugs.webkit.org/show_bug.cgi?id=91071
if (logicalWidth.isIntrinsic())
return computeIntrinsicLogicalWidthUsing(logicalWidth, cw, borderAndPaddingLogicalWidth()) - borderAndPaddingLogicalWidth();
if (cw > 0 || (!cw && (containerLogicalWidth.isFixed() || containerLogicalWidth.hasPercent())))
return adjustContentBoxLogicalWidthForBoxSizing(minimumValueForLength(logicalWidth, cw));
return LayoutUnit();
}
case Auto:
case MaxSizeNone:
return intrinsicLogicalWidth();
case ExtendToZoom:
case DeviceWidth:
case DeviceHeight:
break;
}
ASSERT_NOT_REACHED();
return LayoutUnit();
}
LayoutUnit LayoutBox::computeReplacedLogicalHeight(LayoutUnit) const
{
return computeReplacedLogicalHeightRespectingMinMaxHeight(computeReplacedLogicalHeightUsing(MainOrPreferredSize, style()->logicalHeight()));
}
bool LayoutBox::logicalHeightComputesAsNone(SizeType sizeType) const
{
ASSERT(sizeType == MinSize || sizeType == MaxSize);
Length logicalHeight = sizeType == MinSize ? style()->logicalMinHeight() : style()->logicalMaxHeight();
Length initialLogicalHeight = sizeType == MinSize ? ComputedStyle::initialMinSize() : ComputedStyle::initialMaxSize();
if (logicalHeight == initialLogicalHeight)
return true;
if (LayoutBlock* cb = containingBlockForAutoHeightDetection(logicalHeight))
return cb->hasAutoHeightOrContainingBlockWithAutoHeight();
return false;
}
LayoutUnit LayoutBox::computeReplacedLogicalHeightRespectingMinMaxHeight(LayoutUnit logicalHeight) const
{
// If the height of the containing block is not specified explicitly (i.e., it depends on content height), and this element is not absolutely positioned,
// the percentage value is treated as '0' (for 'min-height') or 'none' (for 'max-height').
LayoutUnit minLogicalHeight;
if (!logicalHeightComputesAsNone(MinSize))
minLogicalHeight = computeReplacedLogicalHeightUsing(MinSize, style()->logicalMinHeight());
LayoutUnit maxLogicalHeight = logicalHeight;
if (!logicalHeightComputesAsNone(MaxSize))
maxLogicalHeight = computeReplacedLogicalHeightUsing(MaxSize, style()->logicalMaxHeight());
return std::max(minLogicalHeight, std::min(logicalHeight, maxLogicalHeight));
}
LayoutUnit LayoutBox::computeReplacedLogicalHeightUsing(SizeType sizeType, const Length& logicalHeight) const
{
ASSERT(sizeType == MinSize || sizeType == MainOrPreferredSize || !logicalHeight.isAuto());
if (sizeType == MinSize && logicalHeight.isAuto())
return adjustContentBoxLogicalHeightForBoxSizing(LayoutUnit());
switch (logicalHeight.type()) {
case Fixed:
return adjustContentBoxLogicalHeightForBoxSizing(logicalHeight.value());
case Percent:
case Calculated:
{
LayoutObject* cb = isOutOfFlowPositioned() ? container() : containingBlock();
while (cb->isAnonymous())
cb = cb->containingBlock();
LayoutUnit stretchedHeight(-1);
if (cb->isLayoutBlock()) {
LayoutBlock* block = toLayoutBlock(cb);
block->addPercentHeightDescendant(const_cast<LayoutBox*>(this));
if (block->isFlexItem())
stretchedHeight = toLayoutFlexibleBox(block->parent())->childLogicalHeightForPercentageResolution(*block);
else if (block->isGridItem() && block->hasOverrideLogicalContentHeight())
stretchedHeight = block->overrideLogicalContentHeight();
}
if (cb->isOutOfFlowPositioned() && cb->style()->height().isAuto() && !(cb->style()->top().isAuto() || cb->style()->bottom().isAuto())) {
ASSERT_WITH_SECURITY_IMPLICATION(cb->isLayoutBlock());
LayoutBlock* block = toLayoutBlock(cb);
LogicalExtentComputedValues computedValues;
block->computeLogicalHeight(block->logicalHeight(), LayoutUnit(), computedValues);
LayoutUnit newContentHeight = computedValues.m_extent - block->borderAndPaddingLogicalHeight() - block->scrollbarLogicalHeight();
LayoutUnit newHeight = block->adjustContentBoxLogicalHeightForBoxSizing(newContentHeight);
return adjustContentBoxLogicalHeightForBoxSizing(valueForLength(logicalHeight, newHeight));
}
// FIXME: availableLogicalHeight() is wrong if the replaced element's writing-mode is perpendicular to the
// containing block's writing-mode.
// https://bugs.webkit.org/show_bug.cgi?id=46496
LayoutUnit availableHeight;
if (isOutOfFlowPositioned()) {
availableHeight = containingBlockLogicalHeightForPositioned(toLayoutBoxModelObject(cb));
} else if (stretchedHeight != -1) {
availableHeight = stretchedHeight;
} else if (isGridItem() && hasOverrideContainingBlockLogicalHeight()) {
availableHeight = overrideContainingBlockContentLogicalHeight();
} else {
availableHeight = containingBlockLogicalHeightForContent(IncludeMarginBorderPadding);
// It is necessary to use the border-box to match WinIE's broken
// box model. This is essential for sizing inside
// table cells using percentage heights.
// FIXME: This needs to be made writing-mode-aware. If the cell and image are perpendicular writing-modes, this isn't right.
// https://bugs.webkit.org/show_bug.cgi?id=46997
while (cb && !cb->isLayoutView() && (cb->style()->logicalHeight().isAuto() || cb->style()->logicalHeight().hasPercent())) {
if (cb->isTableCell()) {
// Don't let table cells squeeze percent-height replaced elements
// <http://bugs.webkit.org/show_bug.cgi?id=15359>
availableHeight = std::max(availableHeight, intrinsicLogicalHeight());
return valueForLength(logicalHeight, availableHeight - borderAndPaddingLogicalHeight());
}
toLayoutBlock(cb)->addPercentHeightDescendant(const_cast<LayoutBox*>(this));
cb = cb->containingBlock();
}
}
return adjustContentBoxLogicalHeightForBoxSizing(valueForLength(logicalHeight, availableHeight));
}
case MinContent:
case MaxContent:
case FitContent:
case FillAvailable:
return adjustContentBoxLogicalHeightForBoxSizing(computeIntrinsicLogicalContentHeightUsing(logicalHeight, intrinsicLogicalHeight(), borderAndPaddingHeight()));
default:
return intrinsicLogicalHeight();
}
}
LayoutUnit LayoutBox::availableLogicalHeight(AvailableLogicalHeightType heightType) const
{
// http://www.w3.org/TR/CSS2/visudet.html#propdef-height - We are interested in the content height.
// FIXME: Should we pass intrinsicContentLogicalHeight() instead of -1 here?
return constrainContentBoxLogicalHeightByMinMax(availableLogicalHeightUsing(style()->logicalHeight(), heightType), LayoutUnit(-1));
}
LayoutUnit LayoutBox::availableLogicalHeightUsing(const Length& h, AvailableLogicalHeightType heightType) const
{
if (isLayoutView()) {
return LayoutUnit(isHorizontalWritingMode()
? toLayoutView(this)->frameView()->visibleContentSize().height()
: toLayoutView(this)->frameView()->visibleContentSize().width());
}
// We need to stop here, since we don't want to increase the height of the table
// artificially. We're going to rely on this cell getting expanded to some new
// height, and then when we lay out again we'll use the calculation below.
if (isTableCell() && (h.isAuto() || h.hasPercent())) {
if (hasOverrideLogicalContentHeight())
return overrideLogicalContentHeight();
return logicalHeight() - borderAndPaddingLogicalHeight();
}
if (h.hasPercent() && isOutOfFlowPositioned()) {
// FIXME: This is wrong if the containingBlock has a perpendicular writing mode.
LayoutUnit availableHeight = containingBlockLogicalHeightForPositioned(containingBlock());
return adjustContentBoxLogicalHeightForBoxSizing(valueForLength(h, availableHeight));
}
// FIXME: Should we pass intrinsicContentLogicalHeight() instead of -1 here?
LayoutUnit heightIncludingScrollbar = computeContentAndScrollbarLogicalHeightUsing(MainOrPreferredSize, h, LayoutUnit(-1));
if (heightIncludingScrollbar != -1)
return std::max(LayoutUnit(), adjustContentBoxLogicalHeightForBoxSizing(heightIncludingScrollbar) - scrollbarLogicalHeight());
// FIXME: Check logicalTop/logicalBottom here to correctly handle vertical writing-mode.
// https://bugs.webkit.org/show_bug.cgi?id=46500
if (isLayoutBlock() && isOutOfFlowPositioned() && style()->height().isAuto() && !(style()->top().isAuto() || style()->bottom().isAuto())) {
LayoutBlock* block = const_cast<LayoutBlock*>(toLayoutBlock(this));
LogicalExtentComputedValues computedValues;
block->computeLogicalHeight(block->logicalHeight(), LayoutUnit(), computedValues);
LayoutUnit newContentHeight = computedValues.m_extent - block->borderAndPaddingLogicalHeight() - block->scrollbarLogicalHeight();
return adjustContentBoxLogicalHeightForBoxSizing(newContentHeight);
}
// FIXME: This is wrong if the containingBlock has a perpendicular writing mode.
LayoutUnit availableHeight = containingBlockLogicalHeightForContent(heightType);
if (heightType == ExcludeMarginBorderPadding) {
// FIXME: Margin collapsing hasn't happened yet, so this incorrectly removes collapsed margins.
availableHeight -= marginBefore() + marginAfter() + borderAndPaddingLogicalHeight();
}
return availableHeight;
}
void LayoutBox::computeAndSetBlockDirectionMargins(const LayoutBlock* containingBlock)
{
LayoutUnit marginBefore;
LayoutUnit marginAfter;
computeMarginsForDirection(BlockDirection, containingBlock, containingBlockLogicalWidthForContent(), logicalHeight(), marginBefore, marginAfter,
style()->marginBeforeUsing(containingBlock->style()),
style()->marginAfterUsing(containingBlock->style()));
// Note that in this 'positioning phase' of the layout we are using the containing block's writing mode rather than our own when calculating margins.
// See http://www.w3.org/TR/2014/CR-css-writing-modes-3-20140320/#orthogonal-flows
containingBlock->setMarginBeforeForChild(*this, marginBefore);
containingBlock->setMarginAfterForChild(*this, marginAfter);
}
LayoutUnit LayoutBox::containingBlockLogicalWidthForPositioned(const LayoutBoxModelObject* containingBlock, bool checkForPerpendicularWritingMode) const
{
if (checkForPerpendicularWritingMode && containingBlock->isHorizontalWritingMode() != isHorizontalWritingMode())
return containingBlockLogicalHeightForPositioned(containingBlock, false);
// Use viewport as container for top-level fixed-position elements.
if (style()->position() == FixedPosition && containingBlock->isLayoutView() && !document().printing()) {
const LayoutView* view = toLayoutView(containingBlock);
if (FrameView* frameView = view->frameView()) {
// Don't use visibleContentRect since the PaintLayer's size has not been set yet.
LayoutSize viewportSize(frameView->layoutViewportScrollableArea()->excludeScrollbars(frameView->frameRect().size()));
return LayoutUnit(containingBlock->isHorizontalWritingMode() ? viewportSize.width() : viewportSize.height());
}
}
if (hasOverrideContainingBlockLogicalWidth())
return overrideContainingBlockContentLogicalWidth();
// Ensure we compute our width based on the width of our rel-pos inline container rather than any anonymous block
// created to manage a block-flow ancestor of ours in the rel-pos inline's inline flow.
if (containingBlock->isAnonymousBlock() && containingBlock->isRelPositioned())
containingBlock = toLayoutBox(containingBlock)->continuation();
else if (containingBlock->isBox())
return std::max(LayoutUnit(), toLayoutBox(containingBlock)->clientLogicalWidth());
ASSERT(containingBlock->isLayoutInline() && containingBlock->isInFlowPositioned());
const LayoutInline* flow = toLayoutInline(containingBlock);
InlineFlowBox* first = flow->firstLineBox();
InlineFlowBox* last = flow->lastLineBox();
// If the containing block is empty, return a width of 0.
if (!first || !last)
return LayoutUnit();
LayoutUnit fromLeft;
LayoutUnit fromRight;
if (containingBlock->style()->isLeftToRightDirection()) {
fromLeft = first->logicalLeft() + first->borderLogicalLeft();
fromRight = last->logicalLeft() + last->logicalWidth() - last->borderLogicalRight();
} else {
fromRight = first->logicalLeft() + first->logicalWidth() - first->borderLogicalRight();
fromLeft = last->logicalLeft() + last->borderLogicalLeft();
}
return std::max(LayoutUnit(), fromRight - fromLeft);
}
LayoutUnit LayoutBox::containingBlockLogicalHeightForPositioned(const LayoutBoxModelObject* containingBlock, bool checkForPerpendicularWritingMode) const
{
if (checkForPerpendicularWritingMode && containingBlock->isHorizontalWritingMode() != isHorizontalWritingMode())
return containingBlockLogicalWidthForPositioned(containingBlock, false);
// Use viewport as container for top-level fixed-position elements.
if (style()->position() == FixedPosition && containingBlock->isLayoutView() && !document().printing()) {
const LayoutView* view = toLayoutView(containingBlock);
if (FrameView* frameView = view->frameView()) {
// Don't use visibleContentRect since the PaintLayer's size has not been set yet.
LayoutSize viewportSize(frameView->layoutViewportScrollableArea()->excludeScrollbars(frameView->frameRect().size()));
return containingBlock->isHorizontalWritingMode() ? viewportSize.height() : viewportSize.width();
}
}
if (hasOverrideContainingBlockLogicalHeight())
return overrideContainingBlockContentLogicalHeight();
if (containingBlock->isBox()) {
const LayoutBlock* cb = containingBlock->isLayoutBlock() ?
toLayoutBlock(containingBlock) : containingBlock->containingBlock();
return cb->clientLogicalHeight();
}
ASSERT(containingBlock->isLayoutInline() && containingBlock->isInFlowPositioned());
const LayoutInline* flow = toLayoutInline(containingBlock);
InlineFlowBox* first = flow->firstLineBox();
InlineFlowBox* last = flow->lastLineBox();
// If the containing block is empty, return a height of 0.
if (!first || !last)
return LayoutUnit();
LayoutUnit heightResult;
LayoutRect boundingBox(flow->linesBoundingBox());
if (containingBlock->isHorizontalWritingMode())
heightResult = boundingBox.height();
else
heightResult = boundingBox.width();
heightResult -= (containingBlock->borderBefore() + containingBlock->borderAfter());
return heightResult;
}
void LayoutBox::computeInlineStaticDistance(Length& logicalLeft, Length& logicalRight, const LayoutBox* child, const LayoutBoxModelObject* containerBlock, LayoutUnit containerLogicalWidth)
{
if (!logicalLeft.isAuto() || !logicalRight.isAuto())
return;
// FIXME: The static distance computation has not been patched for mixed writing modes yet.
if (child->parent()->style()->direction() == LTR) {
LayoutUnit staticPosition = child->layer()->staticInlinePosition() - containerBlock->borderLogicalLeft();
for (LayoutObject* curr = child->parent(); curr && curr != containerBlock; curr = curr->container()) {
if (curr->isBox()) {
staticPosition += toLayoutBox(curr)->logicalLeft();
if (toLayoutBox(curr)->isInFlowPositioned())
staticPosition += toLayoutBox(curr)->offsetForInFlowPosition().width();
} else if (curr->isInline()) {
if (curr->isInFlowPositioned()) {
if (!curr->style()->logicalLeft().isAuto())
staticPosition += valueForLength(curr->style()->logicalLeft(), curr->containingBlock()->availableWidth());
else
staticPosition -= valueForLength(curr->style()->logicalRight(), curr->containingBlock()->availableWidth());
}
}
}
logicalLeft.setValue(Fixed, staticPosition);
} else {
LayoutBox* enclosingBox = child->parent()->enclosingBox();
LayoutUnit staticPosition = child->layer()->staticInlinePosition() + containerLogicalWidth + containerBlock->borderLogicalLeft();
for (LayoutObject* curr = child->parent(); curr; curr = curr->container()) {
if (curr->isBox()) {
if (curr != containerBlock) {
staticPosition -= toLayoutBox(curr)->logicalLeft();
if (toLayoutBox(curr)->isInFlowPositioned())
staticPosition -= toLayoutBox(curr)->offsetForInFlowPosition().width();
}
if (curr == enclosingBox)
staticPosition -= enclosingBox->logicalWidth();
} else if (curr->isInline()) {
if (curr->isInFlowPositioned()) {
if (!curr->style()->logicalLeft().isAuto())
staticPosition -= valueForLength(curr->style()->logicalLeft(), curr->containingBlock()->availableWidth());
else
staticPosition += valueForLength(curr->style()->logicalRight(), curr->containingBlock()->availableWidth());
}
}
if (curr == containerBlock)
break;
}
logicalRight.setValue(Fixed, staticPosition);
}
}
void LayoutBox::computePositionedLogicalWidth(LogicalExtentComputedValues& computedValues) const
{
// QUESTIONS
// FIXME 1: Should we still deal with these the cases of 'left' or 'right' having
// the type 'static' in determining whether to calculate the static distance?
// NOTE: 'static' is not a legal value for 'left' or 'right' as of CSS 2.1.
// FIXME 2: Can perhaps optimize out cases when max-width/min-width are greater
// than or less than the computed width(). Be careful of box-sizing and
// percentage issues.
// The following is based off of the W3C Working Draft from April 11, 2006 of
// CSS 2.1: Section 10.3.7 "Absolutely positioned, non-replaced elements"
// <http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width>
// (block-style-comments in this function and in computePositionedLogicalWidthUsing()
// correspond to text from the spec)
// We don't use containingBlock(), since we may be positioned by an enclosing
// relative positioned inline.
const LayoutBoxModelObject* containerBlock = toLayoutBoxModelObject(container());
const LayoutUnit containerLogicalWidth = containingBlockLogicalWidthForPositioned(containerBlock);
// Use the container block's direction except when calculating the static distance
// This conforms with the reference results for abspos-replaced-width-margin-000.htm
// of the CSS 2.1 test suite
TextDirection containerDirection = containerBlock->style()->direction();
bool isHorizontal = isHorizontalWritingMode();
const LayoutUnit bordersPlusPadding = borderAndPaddingLogicalWidth();
const Length marginLogicalLeft = isHorizontal ? style()->marginLeft() : style()->marginTop();
const Length marginLogicalRight = isHorizontal ? style()->marginRight() : style()->marginBottom();
Length logicalLeftLength = style()->logicalLeft();
Length logicalRightLength = style()->logicalRight();
/*---------------------------------------------------------------------------*\
* For the purposes of this section and the next, the term "static position"
* (of an element) refers, roughly, to the position an element would have had
* in the normal flow. More precisely:
*
* * The static position for 'left' is the distance from the left edge of the
* containing block to the left margin edge of a hypothetical box that would
* have been the first box of the element if its 'position' property had
* been 'static' and 'float' had been 'none'. The value is negative if the
* hypothetical box is to the left of the containing block.
* * The static position for 'right' is the distance from the right edge of the
* containing block to the right margin edge of the same hypothetical box as
* above. The value is positive if the hypothetical box is to the left of the
* containing block's edge.
*
* But rather than actually calculating the dimensions of that hypothetical box,
* user agents are free to make a guess at its probable position.
*
* For the purposes of calculating the static position, the containing block of
* fixed positioned elements is the initial containing block instead of the
* viewport, and all scrollable boxes should be assumed to be scrolled to their
* origin.
\*---------------------------------------------------------------------------*/
// see FIXME 1
// Calculate the static distance if needed.
computeInlineStaticDistance(logicalLeftLength, logicalRightLength, this, containerBlock, containerLogicalWidth);
// Calculate constraint equation values for 'width' case.
computePositionedLogicalWidthUsing(MainOrPreferredSize, style()->logicalWidth(), containerBlock, containerDirection,
containerLogicalWidth, bordersPlusPadding,
logicalLeftLength, logicalRightLength, marginLogicalLeft, marginLogicalRight,
computedValues);
// Calculate constraint equation values for 'max-width' case.
if (!style()->logicalMaxWidth().isMaxSizeNone()) {
LogicalExtentComputedValues maxValues;
computePositionedLogicalWidthUsing(MaxSize, style()->logicalMaxWidth(), containerBlock, containerDirection,
containerLogicalWidth, bordersPlusPadding,
logicalLeftLength, logicalRightLength, marginLogicalLeft, marginLogicalRight,
maxValues);
if (computedValues.m_extent > maxValues.m_extent) {
computedValues.m_extent = maxValues.m_extent;
computedValues.m_position = maxValues.m_position;
computedValues.m_margins.m_start = maxValues.m_margins.m_start;
computedValues.m_margins.m_end = maxValues.m_margins.m_end;
}
}
// Calculate constraint equation values for 'min-width' case.
if (!style()->logicalMinWidth().isZero() || style()->logicalMinWidth().isIntrinsic()) {
LogicalExtentComputedValues minValues;
computePositionedLogicalWidthUsing(MinSize, style()->logicalMinWidth(), containerBlock, containerDirection,
containerLogicalWidth, bordersPlusPadding,
logicalLeftLength, logicalRightLength, marginLogicalLeft, marginLogicalRight,
minValues);
if (computedValues.m_extent < minValues.m_extent) {
computedValues.m_extent = minValues.m_extent;
computedValues.m_position = minValues.m_position;
computedValues.m_margins.m_start = minValues.m_margins.m_start;
computedValues.m_margins.m_end = minValues.m_margins.m_end;
}
}
if (!style()->hasStaticInlinePosition(isHorizontal))
computedValues.m_position += extraInlineOffset();
computedValues.m_extent += bordersPlusPadding;
}
void LayoutBox::computeLogicalLeftPositionedOffset(LayoutUnit& logicalLeftPos, const LayoutBox* child, LayoutUnit logicalWidthValue, const LayoutBoxModelObject* containerBlock, LayoutUnit containerLogicalWidth)
{
// Deal with differing writing modes here. Our offset needs to be in the containing block's coordinate space. If the containing block is flipped
// along this axis, then we need to flip the coordinate. This can only happen if the containing block is both a flipped mode and perpendicular to us.
if (containerBlock->isHorizontalWritingMode() != child->isHorizontalWritingMode() && containerBlock->style()->isFlippedBlocksWritingMode()) {
logicalLeftPos = containerLogicalWidth - logicalWidthValue - logicalLeftPos;
logicalLeftPos += (child->isHorizontalWritingMode() ? containerBlock->borderRight() : containerBlock->borderBottom());
} else {
logicalLeftPos += (child->isHorizontalWritingMode() ? containerBlock->borderLeft() : containerBlock->borderTop());
}
}
LayoutUnit LayoutBox::shrinkToFitLogicalWidth(LayoutUnit availableLogicalWidth, LayoutUnit bordersPlusPadding) const
{
LayoutUnit preferredLogicalWidth = maxPreferredLogicalWidth() - bordersPlusPadding;
LayoutUnit preferredMinLogicalWidth = minPreferredLogicalWidth() - bordersPlusPadding;
return std::min(std::max(preferredMinLogicalWidth, availableLogicalWidth), preferredLogicalWidth);
}
void LayoutBox::computePositionedLogicalWidthUsing(SizeType widthSizeType, Length logicalWidth, const LayoutBoxModelObject* containerBlock, TextDirection containerDirection,
LayoutUnit containerLogicalWidth, LayoutUnit bordersPlusPadding,
const Length& logicalLeft, const Length& logicalRight, const Length& marginLogicalLeft,
const Length& marginLogicalRight, LogicalExtentComputedValues& computedValues) const
{
LayoutUnit logicalWidthValue;
ASSERT(widthSizeType == MinSize || widthSizeType == MainOrPreferredSize || !logicalWidth.isAuto());
if (widthSizeType == MinSize && logicalWidth.isAuto())
logicalWidthValue = LayoutUnit();
else if (logicalWidth.isIntrinsic())
logicalWidthValue = computeIntrinsicLogicalWidthUsing(logicalWidth, containerLogicalWidth, bordersPlusPadding) - bordersPlusPadding;
else
logicalWidthValue = adjustContentBoxLogicalWidthForBoxSizing(valueForLength(logicalWidth, containerLogicalWidth));
// 'left' and 'right' cannot both be 'auto' because one would of been
// converted to the static position already
ASSERT(!(logicalLeft.isAuto() && logicalRight.isAuto()));
// minimumValueForLength will convert 'auto' to 0 so that it doesn't impact the available space computation below.
LayoutUnit logicalLeftValue = minimumValueForLength(logicalLeft, containerLogicalWidth);
LayoutUnit logicalRightValue = minimumValueForLength(logicalRight, containerLogicalWidth);
const LayoutUnit containerRelativeLogicalWidth = containingBlockLogicalWidthForPositioned(containerBlock, false);
bool logicalWidthIsAuto = logicalWidth.isAuto();
bool logicalLeftIsAuto = logicalLeft.isAuto();
bool logicalRightIsAuto = logicalRight.isAuto();
LayoutUnit& marginLogicalLeftValue = style()->isLeftToRightDirection() ? computedValues.m_margins.m_start : computedValues.m_margins.m_end;
LayoutUnit& marginLogicalRightValue = style()->isLeftToRightDirection() ? computedValues.m_margins.m_end : computedValues.m_margins.m_start;
if (!logicalLeftIsAuto && !logicalWidthIsAuto && !logicalRightIsAuto) {
/*-----------------------------------------------------------------------*\
* If none of the three is 'auto': If both 'margin-left' and 'margin-
* right' are 'auto', solve the equation under the extra constraint that
* the two margins get equal values, unless this would make them negative,
* in which case when direction of the containing block is 'ltr' ('rtl'),
* set 'margin-left' ('margin-right') to zero and solve for 'margin-right'
* ('margin-left'). If one of 'margin-left' or 'margin-right' is 'auto',
* solve the equation for that value. If the values are over-constrained,
* ignore the value for 'left' (in case the 'direction' property of the
* containing block is 'rtl') or 'right' (in case 'direction' is 'ltr')
* and solve for that value.
\*-----------------------------------------------------------------------*/
// NOTE: It is not necessary to solve for 'right' in the over constrained
// case because the value is not used for any further calculations.
computedValues.m_extent = logicalWidthValue;
const LayoutUnit availableSpace = containerLogicalWidth - (logicalLeftValue + computedValues.m_extent + logicalRightValue + bordersPlusPadding);
// Margins are now the only unknown
if (marginLogicalLeft.isAuto() && marginLogicalRight.isAuto()) {
// Both margins auto, solve for equality
if (availableSpace >= 0) {
marginLogicalLeftValue = availableSpace / 2; // split the difference
marginLogicalRightValue = availableSpace - marginLogicalLeftValue; // account for odd valued differences
} else {
// Use the containing block's direction rather than the parent block's
// per CSS 2.1 reference test abspos-non-replaced-width-margin-000.
if (containerDirection == LTR) {
marginLogicalLeftValue = LayoutUnit();
marginLogicalRightValue = availableSpace; // will be negative
} else {
marginLogicalLeftValue = availableSpace; // will be negative
marginLogicalRightValue = LayoutUnit();
}
}
} else if (marginLogicalLeft.isAuto()) {
// Solve for left margin
marginLogicalRightValue = valueForLength(marginLogicalRight, containerRelativeLogicalWidth);
marginLogicalLeftValue = availableSpace - marginLogicalRightValue;
} else if (marginLogicalRight.isAuto()) {
// Solve for right margin
marginLogicalLeftValue = valueForLength(marginLogicalLeft, containerRelativeLogicalWidth);
marginLogicalRightValue = availableSpace - marginLogicalLeftValue;
} else {
// Over-constrained, solve for left if direction is RTL
marginLogicalLeftValue = valueForLength(marginLogicalLeft, containerRelativeLogicalWidth);
marginLogicalRightValue = valueForLength(marginLogicalRight, containerRelativeLogicalWidth);
// Use the containing block's direction rather than the parent block's
// per CSS 2.1 reference test abspos-non-replaced-width-margin-000.
if (containerDirection == RTL)
logicalLeftValue = (availableSpace + logicalLeftValue) - marginLogicalLeftValue - marginLogicalRightValue;
}
} else {
/*--------------------------------------------------------------------*\
* Otherwise, set 'auto' values for 'margin-left' and 'margin-right'
* to 0, and pick the one of the following six rules that applies.
*
* 1. 'left' and 'width' are 'auto' and 'right' is not 'auto', then the
* width is shrink-to-fit. Then solve for 'left'
*
* OMIT RULE 2 AS IT SHOULD NEVER BE HIT
* ------------------------------------------------------------------
* 2. 'left' and 'right' are 'auto' and 'width' is not 'auto', then if
* the 'direction' property of the containing block is 'ltr' set
* 'left' to the static position, otherwise set 'right' to the
* static position. Then solve for 'left' (if 'direction is 'rtl')
* or 'right' (if 'direction' is 'ltr').
* ------------------------------------------------------------------
*
* 3. 'width' and 'right' are 'auto' and 'left' is not 'auto', then the
* width is shrink-to-fit . Then solve for 'right'
* 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve
* for 'left'
* 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve
* for 'width'
* 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve
* for 'right'
*
* Calculation of the shrink-to-fit width is similar to calculating the
* width of a table cell using the automatic table layout algorithm.
* Roughly: calculate the preferred width by formatting the content
* without breaking lines other than where explicit line breaks occur,
* and also calculate the preferred minimum width, e.g., by trying all
* possible line breaks. CSS 2.1 does not define the exact algorithm.
* Thirdly, calculate the available width: this is found by solving
* for 'width' after setting 'left' (in case 1) or 'right' (in case 3)
* to 0.
*
* Then the shrink-to-fit width is:
* min(max(preferred minimum width, available width), preferred width).
\*--------------------------------------------------------------------*/
// NOTE: For rules 3 and 6 it is not necessary to solve for 'right'
// because the value is not used for any further calculations.
// Calculate margins, 'auto' margins are ignored.
marginLogicalLeftValue = minimumValueForLength(marginLogicalLeft, containerRelativeLogicalWidth);
marginLogicalRightValue = minimumValueForLength(marginLogicalRight, containerRelativeLogicalWidth);
const LayoutUnit availableSpace = containerLogicalWidth - (marginLogicalLeftValue + marginLogicalRightValue + logicalLeftValue + logicalRightValue + bordersPlusPadding);
// FIXME: Is there a faster way to find the correct case?
// Use rule/case that applies.
if (logicalLeftIsAuto && logicalWidthIsAuto && !logicalRightIsAuto) {
// RULE 1: (use shrink-to-fit for width, and solve of left)
computedValues.m_extent = shrinkToFitLogicalWidth(availableSpace, bordersPlusPadding);
logicalLeftValue = availableSpace - computedValues.m_extent;
} else if (!logicalLeftIsAuto && logicalWidthIsAuto && logicalRightIsAuto) {
// RULE 3: (use shrink-to-fit for width, and no need solve of right)
computedValues.m_extent = shrinkToFitLogicalWidth(availableSpace, bordersPlusPadding);
} else if (logicalLeftIsAuto && !logicalWidthIsAuto && !logicalRightIsAuto) {
// RULE 4: (solve for left)
computedValues.m_extent = logicalWidthValue;
logicalLeftValue = availableSpace - computedValues.m_extent;
} else if (!logicalLeftIsAuto && logicalWidthIsAuto && !logicalRightIsAuto) {
// RULE 5: (solve for width)
if (autoWidthShouldFitContent())
computedValues.m_extent = shrinkToFitLogicalWidth(availableSpace, bordersPlusPadding);
else
computedValues.m_extent = std::max(LayoutUnit(), availableSpace);
} else if (!logicalLeftIsAuto && !logicalWidthIsAuto && logicalRightIsAuto) {
// RULE 6: (no need solve for right)
computedValues.m_extent = logicalWidthValue;
}
}
// Use computed values to calculate the horizontal position.
// FIXME: This hack is needed to calculate the logical left position for a 'rtl' relatively
// positioned, inline because right now, it is using the logical left position
// of the first line box when really it should use the last line box. When
// this is fixed elsewhere, this block should be removed.
if (containerBlock->isLayoutInline() && !containerBlock->style()->isLeftToRightDirection()) {
const LayoutInline* flow = toLayoutInline(containerBlock);
InlineFlowBox* firstLine = flow->firstLineBox();
InlineFlowBox* lastLine = flow->lastLineBox();
if (firstLine && lastLine && firstLine != lastLine) {
computedValues.m_position = logicalLeftValue + marginLogicalLeftValue + lastLine->borderLogicalLeft() + (lastLine->logicalLeft() - firstLine->logicalLeft());
return;
}
}
if (containerBlock->isBox() && toLayoutBox(containerBlock)->scrollsOverflowY() && toLayoutBox(containerBlock)->shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) {
logicalLeftValue = logicalLeftValue + toLayoutBox(containerBlock)->verticalScrollbarWidth();
}
computedValues.m_position = logicalLeftValue + marginLogicalLeftValue;
computeLogicalLeftPositionedOffset(computedValues.m_position, this, computedValues.m_extent, containerBlock, containerLogicalWidth);
}
void LayoutBox::computeBlockStaticDistance(Length& logicalTop, Length& logicalBottom, const LayoutBox* child, const LayoutBoxModelObject* containerBlock)
{
if (!logicalTop.isAuto() || !logicalBottom.isAuto())
return;
// FIXME: The static distance computation has not been patched for mixed writing modes.
LayoutUnit staticLogicalTop = child->layer()->staticBlockPosition() - containerBlock->borderBefore();
for (LayoutObject* curr = child->parent(); curr && curr != containerBlock; curr = curr->container()) {
if (curr->isBox() && !curr->isTableRow())
staticLogicalTop += toLayoutBox(curr)->logicalTop();
}
logicalTop.setValue(Fixed, staticLogicalTop);
}
void LayoutBox::computePositionedLogicalHeight(LogicalExtentComputedValues& computedValues) const
{
// The following is based off of the W3C Working Draft from April 11, 2006 of
// CSS 2.1: Section 10.6.4 "Absolutely positioned, non-replaced elements"
// <http://www.w3.org/TR/2005/WD-CSS21-20050613/visudet.html#abs-non-replaced-height>
// (block-style-comments in this function and in computePositionedLogicalHeightUsing()
// correspond to text from the spec)
// We don't use containingBlock(), since we may be positioned by an enclosing relpositioned inline.
const LayoutBoxModelObject* containerBlock = toLayoutBoxModelObject(container());
const LayoutUnit containerLogicalHeight = containingBlockLogicalHeightForPositioned(containerBlock);
const ComputedStyle& styleToUse = styleRef();
const LayoutUnit bordersPlusPadding = borderAndPaddingLogicalHeight();
const Length marginBefore = styleToUse.marginBefore();
const Length marginAfter = styleToUse.marginAfter();
Length logicalTopLength = styleToUse.logicalTop();
Length logicalBottomLength = styleToUse.logicalBottom();
/*---------------------------------------------------------------------------*\
* For the purposes of this section and the next, the term "static position"
* (of an element) refers, roughly, to the position an element would have had
* in the normal flow. More precisely, the static position for 'top' is the
* distance from the top edge of the containing block to the top margin edge
* of a hypothetical box that would have been the first box of the element if
* its 'position' property had been 'static' and 'float' had been 'none'. The
* value is negative if the hypothetical box is above the containing block.
*
* But rather than actually calculating the dimensions of that hypothetical
* box, user agents are free to make a guess at its probable position.
*
* For the purposes of calculating the static position, the containing block
* of fixed positioned elements is the initial containing block instead of
* the viewport.
\*---------------------------------------------------------------------------*/
// see FIXME 1
// Calculate the static distance if needed.
computeBlockStaticDistance(logicalTopLength, logicalBottomLength, this, containerBlock);
// Calculate constraint equation values for 'height' case.
LayoutUnit logicalHeight = computedValues.m_extent;
computePositionedLogicalHeightUsing(MainOrPreferredSize, styleToUse.logicalHeight(), containerBlock, containerLogicalHeight, bordersPlusPadding, logicalHeight,
logicalTopLength, logicalBottomLength, marginBefore, marginAfter,
computedValues);
// Avoid doing any work in the common case (where the values of min-height and max-height are their defaults).
// see FIXME 2
// Calculate constraint equation values for 'max-height' case.
if (!styleToUse.logicalMaxHeight().isMaxSizeNone()) {
LogicalExtentComputedValues maxValues;
computePositionedLogicalHeightUsing(MaxSize, styleToUse.logicalMaxHeight(), containerBlock, containerLogicalHeight, bordersPlusPadding, logicalHeight,
logicalTopLength, logicalBottomLength, marginBefore, marginAfter,
maxValues);
if (computedValues.m_extent > maxValues.m_extent) {
computedValues.m_extent = maxValues.m_extent;
computedValues.m_position = maxValues.m_position;
computedValues.m_margins.m_before = maxValues.m_margins.m_before;
computedValues.m_margins.m_after = maxValues.m_margins.m_after;
}
}
// Calculate constraint equation values for 'min-height' case.
if (!styleToUse.logicalMinHeight().isZero() || styleToUse.logicalMinHeight().isIntrinsic()) {
LogicalExtentComputedValues minValues;
computePositionedLogicalHeightUsing(MinSize, styleToUse.logicalMinHeight(), containerBlock, containerLogicalHeight, bordersPlusPadding, logicalHeight,
logicalTopLength, logicalBottomLength, marginBefore, marginAfter,
minValues);
if (computedValues.m_extent < minValues.m_extent) {
computedValues.m_extent = minValues.m_extent;
computedValues.m_position = minValues.m_position;
computedValues.m_margins.m_before = minValues.m_margins.m_before;
computedValues.m_margins.m_after = minValues.m_margins.m_after;
}
}
if (!style()->hasStaticBlockPosition(isHorizontalWritingMode()))
computedValues.m_position += extraBlockOffset();
// Set final height value.
computedValues.m_extent += bordersPlusPadding;
}
void LayoutBox::computeLogicalTopPositionedOffset(LayoutUnit& logicalTopPos, const LayoutBox* child, LayoutUnit logicalHeightValue, const LayoutBoxModelObject* containerBlock, LayoutUnit containerLogicalHeight)
{
// Deal with differing writing modes here. Our offset needs to be in the containing block's coordinate space. If the containing block is flipped
// along this axis, then we need to flip the coordinate. This can only happen if the containing block is both a flipped mode and perpendicular to us.
if ((child->style()->isFlippedBlocksWritingMode() && child->isHorizontalWritingMode() != containerBlock->isHorizontalWritingMode())
|| (child->style()->isFlippedBlocksWritingMode() != containerBlock->style()->isFlippedBlocksWritingMode() && child->isHorizontalWritingMode() == containerBlock->isHorizontalWritingMode()))
logicalTopPos = containerLogicalHeight - logicalHeightValue - logicalTopPos;
// Our offset is from the logical bottom edge in a flipped environment, e.g., right for vertical-rl.
if (containerBlock->style()->isFlippedBlocksWritingMode() && child->isHorizontalWritingMode() == containerBlock->isHorizontalWritingMode()) {
if (child->isHorizontalWritingMode())
logicalTopPos += containerBlock->borderBottom();
else
logicalTopPos += containerBlock->borderRight();
} else {
if (child->isHorizontalWritingMode())
logicalTopPos += containerBlock->borderTop();
else
logicalTopPos += containerBlock->borderLeft();
}
}
void LayoutBox::computePositionedLogicalHeightUsing(SizeType heightSizeType, Length logicalHeightLength, const LayoutBoxModelObject* containerBlock,
LayoutUnit containerLogicalHeight, LayoutUnit bordersPlusPadding, LayoutUnit logicalHeight,
const Length& logicalTop, const Length& logicalBottom, const Length& marginBefore,
const Length& marginAfter, LogicalExtentComputedValues& computedValues) const
{
ASSERT(heightSizeType == MinSize || heightSizeType == MainOrPreferredSize || !logicalHeightLength.isAuto());
if (heightSizeType == MinSize && logicalHeightLength.isAuto())
logicalHeightLength = Length(0, Fixed);
// 'top' and 'bottom' cannot both be 'auto' because 'top would of been
// converted to the static position in computePositionedLogicalHeight()
ASSERT(!(logicalTop.isAuto() && logicalBottom.isAuto()));
LayoutUnit logicalHeightValue;
LayoutUnit contentLogicalHeight = logicalHeight - bordersPlusPadding;
const LayoutUnit containerRelativeLogicalWidth = containingBlockLogicalWidthForPositioned(containerBlock, false);
LayoutUnit logicalTopValue;
bool logicalHeightIsAuto = logicalHeightLength.isAuto();
bool logicalTopIsAuto = logicalTop.isAuto();
bool logicalBottomIsAuto = logicalBottom.isAuto();
LayoutUnit resolvedLogicalHeight;
// Height is never unsolved for tables.
if (isTable()) {
resolvedLogicalHeight = contentLogicalHeight;
logicalHeightIsAuto = false;
} else {
if (logicalHeightLength.isIntrinsic())
resolvedLogicalHeight = computeIntrinsicLogicalContentHeightUsing(logicalHeightLength, contentLogicalHeight, bordersPlusPadding);
else
resolvedLogicalHeight = adjustContentBoxLogicalHeightForBoxSizing(valueForLength(logicalHeightLength, containerLogicalHeight));
}
if (!logicalTopIsAuto && !logicalHeightIsAuto && !logicalBottomIsAuto) {
/*-----------------------------------------------------------------------*\
* If none of the three are 'auto': If both 'margin-top' and 'margin-
* bottom' are 'auto', solve the equation under the extra constraint that
* the two margins get equal values. If one of 'margin-top' or 'margin-
* bottom' is 'auto', solve the equation for that value. If the values
* are over-constrained, ignore the value for 'bottom' and solve for that
* value.
\*-----------------------------------------------------------------------*/
// NOTE: It is not necessary to solve for 'bottom' in the over constrained
// case because the value is not used for any further calculations.
logicalHeightValue = resolvedLogicalHeight;
logicalTopValue = valueForLength(logicalTop, containerLogicalHeight);
const LayoutUnit availableSpace = containerLogicalHeight - (logicalTopValue + logicalHeightValue + valueForLength(logicalBottom, containerLogicalHeight) + bordersPlusPadding);
// Margins are now the only unknown
if (marginBefore.isAuto() && marginAfter.isAuto()) {
// Both margins auto, solve for equality
// NOTE: This may result in negative values.
computedValues.m_margins.m_before = availableSpace / 2; // split the difference
computedValues.m_margins.m_after = availableSpace - computedValues.m_margins.m_before; // account for odd valued differences
} else if (marginBefore.isAuto()) {
// Solve for top margin
computedValues.m_margins.m_after = valueForLength(marginAfter, containerRelativeLogicalWidth);
computedValues.m_margins.m_before = availableSpace - computedValues.m_margins.m_after;
} else if (marginAfter.isAuto()) {
// Solve for bottom margin
computedValues.m_margins.m_before = valueForLength(marginBefore, containerRelativeLogicalWidth);
computedValues.m_margins.m_after = availableSpace - computedValues.m_margins.m_before;
} else {
// Over-constrained, (no need solve for bottom)
computedValues.m_margins.m_before = valueForLength(marginBefore, containerRelativeLogicalWidth);
computedValues.m_margins.m_after = valueForLength(marginAfter, containerRelativeLogicalWidth);
}
} else {
/*--------------------------------------------------------------------*\
* Otherwise, set 'auto' values for 'margin-top' and 'margin-bottom'
* to 0, and pick the one of the following six rules that applies.
*
* 1. 'top' and 'height' are 'auto' and 'bottom' is not 'auto', then
* the height is based on the content, and solve for 'top'.
*
* OMIT RULE 2 AS IT SHOULD NEVER BE HIT
* ------------------------------------------------------------------
* 2. 'top' and 'bottom' are 'auto' and 'height' is not 'auto', then
* set 'top' to the static position, and solve for 'bottom'.
* ------------------------------------------------------------------
*
* 3. 'height' and 'bottom' are 'auto' and 'top' is not 'auto', then
* the height is based on the content, and solve for 'bottom'.
* 4. 'top' is 'auto', 'height' and 'bottom' are not 'auto', and
* solve for 'top'.
* 5. 'height' is 'auto', 'top' and 'bottom' are not 'auto', and
* solve for 'height'.
* 6. 'bottom' is 'auto', 'top' and 'height' are not 'auto', and
* solve for 'bottom'.
\*--------------------------------------------------------------------*/
// NOTE: For rules 3 and 6 it is not necessary to solve for 'bottom'
// because the value is not used for any further calculations.
// Calculate margins, 'auto' margins are ignored.
computedValues.m_margins.m_before = minimumValueForLength(marginBefore, containerRelativeLogicalWidth);
computedValues.m_margins.m_after = minimumValueForLength(marginAfter, containerRelativeLogicalWidth);
const LayoutUnit availableSpace = containerLogicalHeight - (computedValues.m_margins.m_before + computedValues.m_margins.m_after + bordersPlusPadding);
// Use rule/case that applies.
if (logicalTopIsAuto && logicalHeightIsAuto && !logicalBottomIsAuto) {
// RULE 1: (height is content based, solve of top)
logicalHeightValue = contentLogicalHeight;
logicalTopValue = availableSpace - (logicalHeightValue + valueForLength(logicalBottom, containerLogicalHeight));
} else if (!logicalTopIsAuto && logicalHeightIsAuto && logicalBottomIsAuto) {
// RULE 3: (height is content based, no need solve of bottom)
logicalTopValue = valueForLength(logicalTop, containerLogicalHeight);
logicalHeightValue = contentLogicalHeight;
} else if (logicalTopIsAuto && !logicalHeightIsAuto && !logicalBottomIsAuto) {
// RULE 4: (solve of top)
logicalHeightValue = resolvedLogicalHeight;
logicalTopValue = availableSpace - (logicalHeightValue + valueForLength(logicalBottom, containerLogicalHeight));
} else if (!logicalTopIsAuto && logicalHeightIsAuto && !logicalBottomIsAuto) {
// RULE 5: (solve of height)
logicalTopValue = valueForLength(logicalTop, containerLogicalHeight);
logicalHeightValue = std::max(LayoutUnit(), availableSpace - (logicalTopValue + valueForLength(logicalBottom, containerLogicalHeight)));
} else if (!logicalTopIsAuto && !logicalHeightIsAuto && logicalBottomIsAuto) {
// RULE 6: (no need solve of bottom)
logicalHeightValue = resolvedLogicalHeight;
logicalTopValue = valueForLength(logicalTop, containerLogicalHeight);
}
}
computedValues.m_extent = logicalHeightValue;
// Use computed values to calculate the vertical position.
computedValues.m_position = logicalTopValue + computedValues.m_margins.m_before;
computeLogicalTopPositionedOffset(computedValues.m_position, this, logicalHeightValue, containerBlock, containerLogicalHeight);
}
LayoutRect LayoutBox::localCaretRect(InlineBox* box, int caretOffset, LayoutUnit* extraWidthToEndOfLine)
{
// VisiblePositions at offsets inside containers either a) refer to the positions before/after
// those containers (tables and select elements) or b) refer to the position inside an empty block.
// They never refer to children.
// FIXME: Paint the carets inside empty blocks differently than the carets before/after elements.
LayoutRect rect(location(), LayoutSize(caretWidth(), size().height()));
bool ltr = box ? box->isLeftToRightDirection() : style()->isLeftToRightDirection();
if ((!caretOffset) ^ ltr)
rect.move(LayoutSize(size().width() - caretWidth(), LayoutUnit()));
if (box) {
RootInlineBox& rootBox = box->root();
LayoutUnit top = rootBox.lineTop();
rect.setY(top);
rect.setHeight(rootBox.lineBottom() - top);
}
// If height of box is smaller than font height, use the latter one,
// otherwise the caret might become invisible.
//
// Also, if the box is not an atomic inline-level element, always use the font height.
// This prevents the "big caret" bug described in:
// <rdar://problem/3777804> Deleting all content in a document can result in giant tall-as-window insertion point
//
// FIXME: ignoring :first-line, missing good reason to take care of
LayoutUnit fontHeight = LayoutUnit(style()->getFontMetrics().height());
if (fontHeight > rect.height() || (!isAtomicInlineLevel() && !isTable()))
rect.setHeight(fontHeight);
if (extraWidthToEndOfLine)
*extraWidthToEndOfLine = location().x() + size().width() - rect.maxX();
// Move to local coords
rect.moveBy(-location());
// FIXME: Border/padding should be added for all elements but this workaround
// is needed because we use offsets inside an "atomic" element to represent
// positions before and after the element in deprecated editing offsets.
if (node() && !(editingIgnoresContent(node()) || isDisplayInsideTable(node()))) {
rect.setX(rect.x() + borderLeft() + paddingLeft());
rect.setY(rect.y() + paddingTop() + borderTop());
}
if (!isHorizontalWritingMode())
return rect.transposedRect();
return rect;
}
PositionWithAffinity LayoutBox::positionForPoint(const LayoutPoint& point)
{
// no children...return this layout object's element, if there is one, and offset 0
LayoutObject* firstChild = slowFirstChild();
if (!firstChild)
return createPositionWithAffinity(nonPseudoNode() ? firstPositionInOrBeforeNode(nonPseudoNode()) : Position());
if (isTable() && nonPseudoNode()) {
LayoutUnit right = size().width() - verticalScrollbarWidth();
LayoutUnit bottom = size().height() - horizontalScrollbarHeight();
if (point.x() < 0 || point.x() > right || point.y() < 0 || point.y() > bottom) {
if (point.x() <= right / 2)
return createPositionWithAffinity(firstPositionInOrBeforeNode(nonPseudoNode()));
return createPositionWithAffinity(lastPositionInOrAfterNode(nonPseudoNode()));
}
}
// Pass off to the closest child.
LayoutUnit minDist = LayoutUnit::max();
LayoutBox* closestLayoutObject = nullptr;
LayoutPoint adjustedPoint = point;
if (isTableRow())
adjustedPoint.moveBy(location());
for (LayoutObject* layoutObject = firstChild; layoutObject; layoutObject = layoutObject->nextSibling()) {
if ((!layoutObject->slowFirstChild() && !layoutObject->isInline() && !layoutObject->isLayoutBlockFlow() )
|| layoutObject->style()->visibility() != EVisibility::Visible)
continue;
if (!layoutObject->isBox())
continue;
LayoutBox* layoutBox = toLayoutBox(layoutObject);
LayoutUnit top = layoutBox->borderTop() + layoutBox->paddingTop() + (isTableRow() ? LayoutUnit() : layoutBox->location().y());
LayoutUnit bottom = top + layoutBox->contentHeight();
LayoutUnit left = layoutBox->borderLeft() + layoutBox->paddingLeft() + (isTableRow() ? LayoutUnit() : layoutBox->location().x());
LayoutUnit right = left + layoutBox->contentWidth();
if (point.x() <= right && point.x() >= left && point.y() <= top && point.y() >= bottom) {
if (layoutBox->isTableRow())
return layoutBox->positionForPoint(point + adjustedPoint - layoutBox->locationOffset());
return layoutBox->positionForPoint(point - layoutBox->locationOffset());
}
// Find the distance from (x, y) to the box. Split the space around the box into 8 pieces
// and use a different compare depending on which piece (x, y) is in.
LayoutPoint cmp;
if (point.x() > right) {
if (point.y() < top)
cmp = LayoutPoint(right, top);
else if (point.y() > bottom)
cmp = LayoutPoint(right, bottom);
else
cmp = LayoutPoint(right, point.y());
} else if (point.x() < left) {
if (point.y() < top)
cmp = LayoutPoint(left, top);
else if (point.y() > bottom)
cmp = LayoutPoint(left, bottom);
else
cmp = LayoutPoint(left, point.y());
} else {
if (point.y() < top)
cmp = LayoutPoint(point.x(), top);
else
cmp = LayoutPoint(point.x(), bottom);
}
LayoutSize difference = cmp - point;
LayoutUnit dist = difference.width() * difference.width() + difference.height() * difference.height();
if (dist < minDist) {
closestLayoutObject = layoutBox;
minDist = dist;
}
}
if (closestLayoutObject)
return closestLayoutObject->positionForPoint(adjustedPoint - closestLayoutObject->locationOffset());
return createPositionWithAffinity(firstPositionInOrBeforeNode(nonPseudoNode()));
}
bool LayoutBox::shrinkToAvoidFloats() const
{
// Floating objects don't shrink. Objects that don't avoid floats don't shrink.
if (isInline() || !avoidsFloats() || isFloating())
return false;
// Only auto width objects can possibly shrink to avoid floats.
return style()->width().isAuto();
}
static bool shouldBeConsideredAsReplaced(Node* node)
{
// Checkboxes and radioboxes are not isAtomicInlineLevel() nor do they have their own layoutObject in which to override avoidFloats().
return node && node->isElementNode() && (toElement(node)->isFormControlElement() || isHTMLImageElement(toElement(node)));
}
bool LayoutBox::avoidsFloats() const
{
return isAtomicInlineLevel() || shouldBeConsideredAsReplaced(node()) || hasOverflowClip() || isHR() || isLegend() || isWritingModeRoot() || isFlexItemIncludingDeprecated() || style()->containsPaint() || style()->containsLayout();
}
bool LayoutBox::hasNonCompositedScrollbars() const
{
if (PaintLayerScrollableArea* scrollableArea = this->getScrollableArea()) {
if (scrollableArea->hasHorizontalScrollbar() && !scrollableArea->layerForHorizontalScrollbar())
return true;
if (scrollableArea->hasVerticalScrollbar() && !scrollableArea->layerForVerticalScrollbar())
return true;
}
return false;
}
void LayoutBox::markForPaginationRelayoutIfNeeded(SubtreeLayoutScope& layoutScope)
{
ASSERT(!needsLayout());
// If fragmentation height has changed, we need to lay out. No need to enter the layoutObject if it
// is childless, though.
if (view()->layoutState()->pageLogicalHeightChanged() && slowFirstChild())
layoutScope.setChildNeedsLayout(this);
}
void LayoutBox::markOrthogonalWritingModeRoot()
{
ASSERT(frameView());
frameView()->addOrthogonalWritingModeRoot(*this);
}
void LayoutBox::unmarkOrthogonalWritingModeRoot()
{
ASSERT(frameView());
frameView()->removeOrthogonalWritingModeRoot(*this);
}
void LayoutBox::addVisualEffectOverflow()
{
if (!style()->hasVisualOverflowingEffect())
return;
// Add in the final overflow with shadows, outsets and outline combined.
LayoutRect visualEffectOverflow = borderBoxRect();
visualEffectOverflow.expand(computeVisualEffectOverflowOutsets());
addSelfVisualOverflow(visualEffectOverflow);
}
LayoutRectOutsets LayoutBox::computeVisualEffectOverflowOutsets() const
{
ASSERT(style()->hasVisualOverflowingEffect());
LayoutUnit top;
LayoutUnit right;
LayoutUnit bottom;
LayoutUnit left;
if (const ShadowList* boxShadow = style()->boxShadow()) {
// FIXME: Use LayoutUnit edge outsets, and then simplify this.
FloatRectOutsets outsets = boxShadow->rectOutsetsIncludingOriginal();
top = LayoutUnit(outsets.top());
right = LayoutUnit(outsets.right());
bottom = LayoutUnit(outsets.bottom());
left = LayoutUnit(outsets.left());
}
if (style()->hasBorderImageOutsets()) {
LayoutRectOutsets borderOutsets = style()->borderImageOutsets();
top = std::max(top, borderOutsets.top());
right = std::max(right, borderOutsets.right());
bottom = std::max(bottom, borderOutsets.bottom());
left = std::max(left, borderOutsets.left());
}
// Box-shadow and border-image-outsets are in physical direction. Flip into block direction.
if (UNLIKELY(hasFlippedBlocksWritingMode()))
std::swap(left, right);
if (style()->hasOutline()) {
Vector<LayoutRect> outlineRects;
// The result rects are in coordinates of this object's border box.
addOutlineRects(outlineRects, LayoutPoint(), outlineRectsShouldIncludeBlockVisualOverflow());
LayoutRect rect = unionRectEvenIfEmpty(outlineRects);
int outlineOutset = style()->outlineOutsetExtent();
top = std::max(top, -rect.y() + outlineOutset);
right = std::max(right, rect.maxX() - size().width() + outlineOutset);
bottom = std::max(bottom, rect.maxY() - size().height() + outlineOutset);
left = std::max(left, -rect.x() + outlineOutset);
}
return LayoutRectOutsets(top, right, bottom, left);
}
void LayoutBox::addOverflowFromChild(LayoutBox* child, const LayoutSize& delta)
{
// Never allow flow threads to propagate overflow up to a parent.
if (child->isLayoutFlowThread())
return;
// Only propagate layout overflow from the child if the child isn't clipping its overflow. If it is, then
// its overflow is internal to it, and we don't care about it. layoutOverflowRectForPropagation takes care of this
// and just propagates the border box rect instead.
LayoutRect childLayoutOverflowRect = child->layoutOverflowRectForPropagation(styleRef());
childLayoutOverflowRect.move(delta);
addLayoutOverflow(childLayoutOverflowRect);
// Add in visual overflow from the child. Even if the child clips its overflow, it may still
// have visual overflow of its own set from box shadows or reflections. It is unnecessary to propagate this
// overflow if we are clipping our own overflow.
if (child->hasSelfPaintingLayer())
return;
LayoutRect childVisualOverflowRect = child->visualOverflowRectForPropagation(styleRef());
childVisualOverflowRect.move(delta);
addContentsVisualOverflow(childVisualOverflowRect);
}
bool LayoutBox::hasTopOverflow() const
{
return !style()->isLeftToRightDirection() && !isHorizontalWritingMode();
}
bool LayoutBox::hasLeftOverflow() const
{
return !style()->isLeftToRightDirection() && isHorizontalWritingMode();
}
void LayoutBox::addLayoutOverflow(const LayoutRect& rect)
{
if (rect.isEmpty())
return;
LayoutRect clientBox = noOverflowRect();
if (clientBox.contains(rect))
return;
// For overflow clip objects, we don't want to propagate overflow into unreachable areas.
LayoutRect overflowRect(rect);
if (hasOverflowClip() || isLayoutView()) {
// Overflow is in the block's coordinate space and thus is flipped for vertical-rl writing
// mode. At this stage that is actually a simplification, since we can treat vertical-lr/rl
// as the same.
if (hasTopOverflow())
overflowRect.shiftMaxYEdgeTo(std::min(overflowRect.maxY(), clientBox.maxY()));
else
overflowRect.shiftYEdgeTo(std::max(overflowRect.y(), clientBox.y()));
if (hasLeftOverflow())
overflowRect.shiftMaxXEdgeTo(std::min(overflowRect.maxX(), clientBox.maxX()));
else
overflowRect.shiftXEdgeTo(std::max(overflowRect.x(), clientBox.x()));
// Now re-test with the adjusted rectangle and see if it has become unreachable or fully
// contained.
if (clientBox.contains(overflowRect) || overflowRect.isEmpty())
return;
}
if (!m_overflow)
m_overflow = wrapUnique(new BoxOverflowModel(clientBox, borderBoxRect()));
m_overflow->addLayoutOverflow(overflowRect);
}
void LayoutBox::addSelfVisualOverflow(const LayoutRect& rect)
{
if (rect.isEmpty())
return;
LayoutRect borderBox = borderBoxRect();
if (borderBox.contains(rect))
return;
if (!m_overflow)
m_overflow = wrapUnique(new BoxOverflowModel(noOverflowRect(), borderBox));
m_overflow->addSelfVisualOverflow(rect);
}
void LayoutBox::addContentsVisualOverflow(const LayoutRect& rect)
{
if (rect.isEmpty())
return;
// If hasOverflowClip() we always save contents visual overflow because we need it
// e.g. to determine whether to apply rounded corner clip on contents.
// Otherwise we save contents visual overflow only if it overflows the border box.
LayoutRect borderBox = borderBoxRect();
if (!hasOverflowClip() && borderBox.contains(rect))
return;
if (!m_overflow)
m_overflow = wrapUnique(new BoxOverflowModel(noOverflowRect(), borderBox));
m_overflow->addContentsVisualOverflow(rect);
}
void LayoutBox::clearLayoutOverflow()
{
if (!m_overflow)
return;
if (!hasSelfVisualOverflow() && contentsVisualOverflowRect().isEmpty()) {
clearAllOverflows();
return;
}
m_overflow->setLayoutOverflow(noOverflowRect());
}
bool LayoutBox::percentageLogicalHeightIsResolvable() const
{
Length fakeLength(100, Percent);
return computePercentageLogicalHeight(fakeLength) != -1;
}
bool LayoutBox::hasUnsplittableScrollingOverflow() const
{
// We will paginate as long as we don't scroll overflow in the pagination direction.
bool isHorizontal = isHorizontalWritingMode();
if ((isHorizontal && !scrollsOverflowY()) || (!isHorizontal && !scrollsOverflowX()))
return false;
// We do have overflow. We'll still be willing to paginate as long as the block
// has auto logical height, auto or undefined max-logical-height and a zero or auto min-logical-height.
// Note this is just a heuristic, and it's still possible to have overflow under these
// conditions, but it should work out to be good enough for common cases. Paginating overflow
// with scrollbars present is not the end of the world and is what we used to do in the old model anyway.
return !style()->logicalHeight().isIntrinsicOrAuto()
|| (!style()->logicalMaxHeight().isIntrinsicOrAuto() && !style()->logicalMaxHeight().isMaxSizeNone() && (!style()->logicalMaxHeight().hasPercent() || percentageLogicalHeightIsResolvable()))
|| (!style()->logicalMinHeight().isIntrinsicOrAuto() && style()->logicalMinHeight().isPositive() && (!style()->logicalMinHeight().hasPercent() || percentageLogicalHeightIsResolvable()));
}
LayoutBox::PaginationBreakability LayoutBox::getPaginationBreakability() const
{
// TODO(mstensho): It is wrong to check isAtomicInlineLevel() as we
// actually look for replaced elements.
if (isAtomicInlineLevel()
|| hasUnsplittableScrollingOverflow()
|| (parent() && isWritingModeRoot())
|| (isOutOfFlowPositioned() && style()->position() == FixedPosition))
return ForbidBreaks;
EBreak breakValue = breakInside();
if (breakValue == BreakAvoid || breakValue == BreakAvoidPage || breakValue == BreakAvoidColumn)
return AvoidBreaks;
return AllowAnyBreaks;
}
LayoutUnit LayoutBox::lineHeight(bool /*firstLine*/, LineDirectionMode direction, LinePositionMode /*linePositionMode*/) const
{
if (isAtomicInlineLevel())
return direction == HorizontalLine ? marginHeight() + size().height() : marginWidth() + size().width();
return LayoutUnit();
}
int LayoutBox::baselinePosition(FontBaseline baselineType, bool /*firstLine*/, LineDirectionMode direction, LinePositionMode linePositionMode) const
{
ASSERT(linePositionMode == PositionOnContainingLine);
if (isAtomicInlineLevel()) {
int result = direction == HorizontalLine ? roundToInt(marginHeight() + size().height()) : roundToInt(marginWidth() + size().width());
if (baselineType == AlphabeticBaseline)
return result;
return result - result / 2;
}
return 0;
}
PaintLayer* LayoutBox::enclosingFloatPaintingLayer() const
{
const LayoutObject* curr = this;
while (curr) {
PaintLayer* layer = curr->hasLayer() && curr->isBox() ? toLayoutBox(curr)->layer() : 0;
if (layer && layer->isSelfPaintingLayer())
return layer;
curr = curr->parent();
}
return nullptr;
}
LayoutRect LayoutBox::logicalVisualOverflowRectForPropagation(const ComputedStyle& parentStyle) const
{
LayoutRect rect = visualOverflowRectForPropagation(parentStyle);
if (!parentStyle.isHorizontalWritingMode())
return rect.transposedRect();
return rect;
}
LayoutRect LayoutBox::visualOverflowRectForPropagation(const ComputedStyle& parentStyle) const
{
// If the writing modes of the child and parent match, then we don't have to
// do anything fancy. Just return the result.
LayoutRect rect = visualOverflowRect();
if (parentStyle.getWritingMode() == style()->getWritingMode())
return rect;
// We are putting ourselves into our parent's coordinate space. If there is a flipped block mismatch
// in a particular axis, then we have to flip the rect along that axis.
if (style()->getWritingMode() == RightToLeftWritingMode || parentStyle.getWritingMode() == RightToLeftWritingMode)
rect.setX(size().width() - rect.maxX());
return rect;
}
LayoutRect LayoutBox::logicalLayoutOverflowRectForPropagation(const ComputedStyle& parentStyle) const
{
LayoutRect rect = layoutOverflowRectForPropagation(parentStyle);
if (!parentStyle.isHorizontalWritingMode())
return rect.transposedRect();
return rect;
}
LayoutRect LayoutBox::layoutOverflowRectForPropagation(const ComputedStyle& parentStyle) const
{
// Only propagate interior layout overflow if we don't clip it.
LayoutRect rect = borderBoxRect();
// We want to include the margin, but only when it adds height. Quirky margins don't contribute height
// nor do the margins of self-collapsing blocks.
if (!styleRef().hasMarginAfterQuirk() && !isSelfCollapsingBlock())
rect.expand(isHorizontalWritingMode() ? LayoutSize(LayoutUnit(), marginAfter()) : LayoutSize(marginAfter(), LayoutUnit()));
if (!hasOverflowClip())
rect.unite(layoutOverflowRect());
bool hasTransform = hasLayer() && layer()->transform();
if (isInFlowPositioned() || hasTransform) {
// If we are relatively positioned or if we have a transform, then we have to convert
// this rectangle into physical coordinates, apply relative positioning and transforms
// to it, and then convert it back.
flipForWritingMode(rect);
if (hasTransform)
rect = layer()->currentTransform().mapRect(rect);
if (isInFlowPositioned())
rect.move(offsetForInFlowPosition());
// Now we need to flip back.
flipForWritingMode(rect);
}
// If the writing modes of the child and parent match, then we don't have to
// do anything fancy. Just return the result.
if (parentStyle.getWritingMode() == style()->getWritingMode())
return rect;
// We are putting ourselves into our parent's coordinate space. If there is a flipped block mismatch
// in a particular axis, then we have to flip the rect along that axis.
if (style()->getWritingMode() == RightToLeftWritingMode || parentStyle.getWritingMode() == RightToLeftWritingMode)
rect.setX(size().width() - rect.maxX());
return rect;
}
LayoutRect LayoutBox::noOverflowRect() const
{
// Because of the special coordinate system used for overflow rectangles and many other
// rectangles (not quite logical, not quite physical), we need to flip the block progression
// coordinate in vertical-rl writing mode. In other words, the rectangle returned is physical,
// except for the block direction progression coordinate (x in vertical writing mode), which is
// always "logical top". Apart from the flipping, this method does the same thing as
// clientBoxRect().
const int scrollBarWidth = verticalScrollbarWidth();
const int scrollBarHeight = horizontalScrollbarHeight();
LayoutUnit left(borderLeft() + (shouldPlaceBlockDirectionScrollbarOnLogicalLeft() ? scrollBarWidth : 0));
LayoutUnit top(borderTop());
LayoutUnit right(borderRight());
LayoutUnit bottom(borderBottom());
LayoutRect rect(left, top, size().width() - left - right, size().height() - top - bottom);
flipForWritingMode(rect);
// Subtract space occupied by scrollbars. Order is important here: first flip, then subtract
// scrollbars. This may seem backwards and weird, since one would think that a vertical
// scrollbar at the physical right in vertical-rl ought to be at the logical left (physical
// right), between the logical left (physical right) border and the logical left (physical
// right) padding. But this is how the rest of the code expects us to behave. This is highly
// related to https://bugs.webkit.org/show_bug.cgi?id=76129
// FIXME: when the above mentioned bug is fixed, it should hopefully be possible to call
// clientBoxRect() or paddingBoxRect() in this method, rather than fiddling with the edges on
// our own.
if (shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
rect.contract(0, scrollBarHeight);
else
rect.contract(scrollBarWidth, scrollBarHeight);
return rect;
}
LayoutRect LayoutBox::visualOverflowRect() const
{
if (!m_overflow)
return borderBoxRect();
if (hasOverflowClip())
return m_overflow->selfVisualOverflowRect();
return unionRect(m_overflow->selfVisualOverflowRect(), m_overflow->contentsVisualOverflowRect());
}
LayoutUnit LayoutBox::offsetLeft(const Element* parent) const
{
return adjustedPositionRelativeTo(topLeftLocation(), parent).x();
}
LayoutUnit LayoutBox::offsetTop(const Element* parent) const
{
return adjustedPositionRelativeTo(topLeftLocation(), parent).y();
}
LayoutPoint LayoutBox::flipForWritingModeForChild(const LayoutBox* child, const LayoutPoint& point) const
{
if (!style()->isFlippedBlocksWritingMode())
return point;
// The child is going to add in its x(), so we have to make sure it ends up in
// the right place.
return LayoutPoint(point.x() + size().width() - child->size().width() - (2 * child->location().x()), point.y());
}
LayoutPoint LayoutBox::topLeftLocation(const LayoutBox* flippedBlocksContainer) const
{
const LayoutBox* containerBox = flippedBlocksContainer ? flippedBlocksContainer : containingBlock();
if (!containerBox || containerBox == this)
return location();
return containerBox->flipForWritingModeForChild(this, location());
}
bool LayoutBox::hasRelativeLogicalWidth() const
{
return style()->logicalWidth().hasPercent()
|| style()->logicalMinWidth().hasPercent()
|| style()->logicalMaxWidth().hasPercent();
}
bool LayoutBox::hasRelativeLogicalHeight() const
{
return style()->logicalHeight().hasPercent()
|| style()->logicalMinHeight().hasPercent()
|| style()->logicalMaxHeight().hasPercent();
}
static void markBoxForRelayoutAfterSplit(LayoutBox* box)
{
// FIXME: The table code should handle that automatically. If not,
// we should fix it and remove the table part checks.
if (box->isTable()) {
// Because we may have added some sections with already computed column structures, we need to
// sync the table structure with them now. This avoids crashes when adding new cells to the table.
toLayoutTable(box)->forceSectionsRecalc();
} else if (box->isTableSection()) {
toLayoutTableSection(box)->setNeedsCellRecalc();
}
box->setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(LayoutInvalidationReason::AnonymousBlockChange);
}
static void collapseLoneAnonymousBlockChild(LayoutBox* parent, LayoutObject* child)
{
if (!child->isAnonymousBlock() || !child->isLayoutBlockFlow())
return;
if (!parent->isLayoutBlockFlow())
return;
toLayoutBlockFlow(parent)->collapseAnonymousBlockChild(toLayoutBlockFlow(child));
}
LayoutObject* LayoutBox::splitAnonymousBoxesAroundChild(LayoutObject* beforeChild)
{
LayoutBox* boxAtTopOfNewBranch = nullptr;
while (beforeChild->parent() != this) {
LayoutBox* boxToSplit = toLayoutBox(beforeChild->parent());
if (boxToSplit->slowFirstChild() != beforeChild && boxToSplit->isAnonymous()) {
// We have to split the parent box into two boxes and move children
// from |beforeChild| to end into the new post box.
LayoutBox* postBox = boxToSplit->createAnonymousBoxWithSameTypeAs(this);
postBox->setChildrenInline(boxToSplit->childrenInline());
LayoutBox* parentBox = toLayoutBox(boxToSplit->parent());
// We need to invalidate the |parentBox| before inserting the new node
// so that the table paint invalidation logic knows the structure is dirty.
// See for example LayoutTableCell:localOverflowRectForPaintInvalidation.
markBoxForRelayoutAfterSplit(parentBox);
parentBox->virtualChildren()->insertChildNode(parentBox, postBox, boxToSplit->nextSibling());
boxToSplit->moveChildrenTo(postBox, beforeChild, 0, true);
LayoutObject* child = postBox->slowFirstChild();
ASSERT(child);
if (child && !child->nextSibling())
collapseLoneAnonymousBlockChild(postBox, child);
child = boxToSplit->slowFirstChild();
ASSERT(child);
if (child && !child->nextSibling())
collapseLoneAnonymousBlockChild(boxToSplit, child);
markBoxForRelayoutAfterSplit(boxToSplit);
markBoxForRelayoutAfterSplit(postBox);
boxAtTopOfNewBranch = postBox;
beforeChild = postBox;
} else {
beforeChild = boxToSplit;
}
}
// Splitting the box means the left side of the container chain will lose any percent height descendants
// below |boxAtTopOfNewBranch| on the right hand side.
if (boxAtTopOfNewBranch) {
boxAtTopOfNewBranch->clearPercentHeightDescendants();
markBoxForRelayoutAfterSplit(this);
}
ASSERT(beforeChild->parent() == this);
return beforeChild;
}
LayoutUnit LayoutBox::offsetFromLogicalTopOfFirstPage() const
{
LayoutState* layoutState = view()->layoutState();
if (!layoutState || !layoutState->isPaginated())
return LayoutUnit();
if (layoutState->layoutObject() == this) {
LayoutSize offsetDelta = layoutState->layoutOffset() - layoutState->pageOffset();
return isHorizontalWritingMode() ? offsetDelta.height() : offsetDelta.width();
}
// A LayoutBlock always establishes a layout state, and this method is only meant to be called
// on the object currently being laid out.
ASSERT(!isLayoutBlock());
// In case this box doesn't establish a layout state, try the containing block.
LayoutBlock* containerBlock = containingBlock();
ASSERT(layoutState->layoutObject() == containerBlock);
return containerBlock->offsetFromLogicalTopOfFirstPage() + logicalTop();
}
void LayoutBox::setPageLogicalOffset(LayoutUnit offset)
{
if (!m_rareData && !offset)
return;
ensureRareData().m_pageLogicalOffset = offset;
}
void LayoutBox::logicalExtentAfterUpdatingLogicalWidth(const LayoutUnit& newLogicalTop, LayoutBox::LogicalExtentComputedValues& computedValues)
{
// FIXME: None of this is right for perpendicular writing-mode children.
LayoutUnit oldLogicalWidth = logicalWidth();
LayoutUnit oldLogicalLeft = logicalLeft();
LayoutUnit oldMarginLeft = marginLeft();
LayoutUnit oldMarginRight = marginRight();
LayoutUnit oldLogicalTop = logicalTop();
setLogicalTop(newLogicalTop);
updateLogicalWidth();
computedValues.m_extent = logicalWidth();
computedValues.m_position = logicalLeft();
computedValues.m_margins.m_start = marginStart();
computedValues.m_margins.m_end = marginEnd();
setLogicalTop(oldLogicalTop);
setLogicalWidth(oldLogicalWidth);
setLogicalLeft(oldLogicalLeft);
setMarginLeft(oldMarginLeft);
setMarginRight(oldMarginRight);
}
bool LayoutBox::mustInvalidateFillLayersPaintOnHeightChange(const FillLayer& layer)
{
// Nobody will use multiple layers without wanting fancy positioning.
if (layer.next())
return true;
// Make sure we have a valid image.
StyleImage* img = layer.image();
if (!img || !img->canRender())
return false;
if (layer.repeatY() != RepeatFill && layer.repeatY() != NoRepeatFill)
return true;
// TODO(alancutter): Make this work correctly for calc lengths.
if (layer.yPosition().hasPercent() && !layer.yPosition().isZero())
return true;
if (layer.backgroundYOrigin() != TopEdge)
return true;
EFillSizeType sizeType = layer.sizeType();
if (sizeType == Contain || sizeType == Cover)
return true;
if (sizeType == SizeLength) {
// TODO(alancutter): Make this work correctly for calc lengths.
if (layer.sizeLength().height().hasPercent() && !layer.sizeLength().height().isZero())
return true;
if (img->isGeneratedImage() && layer.sizeLength().height().isAuto())
return true;
} else if (img->usesImageContainerSize()) {
return true;
}
return false;
}
bool LayoutBox::mustInvalidateFillLayersPaintOnWidthChange(const FillLayer& layer)
{
// Nobody will use multiple layers without wanting fancy positioning.
if (layer.next())
return true;
// Make sure we have a valid image.
StyleImage* img = layer.image();
if (!img || !img->canRender())
return false;
if (layer.repeatX() != RepeatFill && layer.repeatX() != NoRepeatFill)
return true;
// TODO(alancutter): Make this work correctly for calc lengths.
if (layer.xPosition().hasPercent() && !layer.xPosition().isZero())
return true;
if (layer.backgroundXOrigin() != LeftEdge)
return true;
EFillSizeType sizeType = layer.sizeType();
if (sizeType == Contain || sizeType == Cover)
return true;
if (sizeType == SizeLength) {
// TODO(alancutter): Make this work correctly for calc lengths.
if (layer.sizeLength().width().hasPercent() && !layer.sizeLength().width().isZero())
return true;
if (img->isGeneratedImage() && layer.sizeLength().width().isAuto())
return true;
} else if (img->usesImageContainerSize()) {
return true;
}
return false;
}
bool LayoutBox::mustInvalidateBackgroundOrBorderPaintOnWidthChange() const
{
if (hasMask() && mustInvalidateFillLayersPaintOnWidthChange(style()->maskLayers()))
return true;
// If we don't have a background/border/mask, then nothing to do.
if (!hasBoxDecorationBackground())
return false;
if (mustInvalidateFillLayersPaintOnWidthChange(style()->backgroundLayers()))
return true;
// Our fill layers are ok. Let's check border.
if (style()->hasBorderDecoration() && canRenderBorderImage())
return true;
return false;
}
bool LayoutBox::mustInvalidateBackgroundOrBorderPaintOnHeightChange() const
{
if (hasMask() && mustInvalidateFillLayersPaintOnHeightChange(style()->maskLayers()))
return true;
// If we don't have a background/border/mask, then nothing to do.
if (!hasBoxDecorationBackground())
return false;
if (mustInvalidateFillLayersPaintOnHeightChange(style()->backgroundLayers()))
return true;
// Our fill layers are ok. Let's check border.
if (style()->hasBorderDecoration() && canRenderBorderImage())
return true;
return false;
}
bool LayoutBox::canRenderBorderImage() const
{
if (!style()->hasBorderDecoration())
return false;
StyleImage* borderImage = style()->borderImage().image();
return borderImage && borderImage->canRender() && borderImage->isLoaded();
}
ShapeOutsideInfo* LayoutBox::shapeOutsideInfo() const
{
return ShapeOutsideInfo::isEnabledFor(*this) ? ShapeOutsideInfo::info(*this) : nullptr;
}
void LayoutBox::clearPreviousPaintInvalidationRects()
{
LayoutBoxModelObject::clearPreviousPaintInvalidationRects();
if (PaintLayerScrollableArea* scrollableArea = this->getScrollableArea())
scrollableArea->clearPreviousPaintInvalidationRects();
}
void LayoutBox::setPercentHeightContainer(LayoutBlock* container)
{
ASSERT(!container || !percentHeightContainer());
if (!container && !m_rareData)
return;
ensureRareData().m_percentHeightContainer = container;
}
void LayoutBox::removeFromPercentHeightContainer()
{
if (!percentHeightContainer())
return;
ASSERT(percentHeightContainer()->hasPercentHeightDescendant(this));
percentHeightContainer()->removePercentHeightDescendant(this);
// The above call should call this object's setPercentHeightContainer(nullptr).
ASSERT(!percentHeightContainer());
}
void LayoutBox::clearPercentHeightDescendants()
{
for (LayoutObject* curr = slowFirstChild(); curr; curr = curr->nextInPreOrder(this)) {
if (curr->isBox())
toLayoutBox(curr)->removeFromPercentHeightContainer();
}
}
LayoutUnit LayoutBox::pageLogicalHeightForOffset(LayoutUnit offset) const
{
LayoutView* layoutView = view();
LayoutFlowThread* flowThread = flowThreadContainingBlock();
if (!flowThread)
return layoutView->pageLogicalHeight();
return flowThread->pageLogicalHeightForOffset(offset + offsetFromLogicalTopOfFirstPage());
}
LayoutUnit LayoutBox::pageRemainingLogicalHeightForOffset(LayoutUnit offset, PageBoundaryRule pageBoundaryRule) const
{
LayoutView* layoutView = view();
offset += offsetFromLogicalTopOfFirstPage();
LayoutFlowThread* flowThread = flowThreadContainingBlock();
if (!flowThread) {
LayoutUnit pageLogicalHeight = layoutView->pageLogicalHeight();
LayoutUnit remainingHeight = pageLogicalHeight - intMod(offset, pageLogicalHeight);
if (pageBoundaryRule == AssociateWithFormerPage) {
// An offset exactly at a page boundary will act as being part of the former page in
// question (i.e. no remaining space), rather than being part of the latter (i.e. one
// whole page length of remaining space).
remainingHeight = intMod(remainingHeight, pageLogicalHeight);
}
return remainingHeight;
}
return flowThread->pageRemainingLogicalHeightForOffset(offset, pageBoundaryRule);
}
LayoutUnit LayoutBox::calculatePaginationStrutToFitContent(LayoutUnit offset, LayoutUnit strutToNextPage, LayoutUnit contentLogicalHeight) const
{
ASSERT(strutToNextPage == pageRemainingLogicalHeightForOffset(offset, AssociateWithLatterPage));
LayoutUnit nextPageLogicalTop = offset + strutToNextPage;
if (pageLogicalHeightForOffset(nextPageLogicalTop) >= contentLogicalHeight)
return strutToNextPage; // Content fits just fine in the next page or column.
// Moving to the top of the next page or column doesn't result in enough space for the content
// that we're trying to fit. If we're in a nested fragmentation context, we may find enough
// space if we move to a column further ahead, by effectively breaking to the next outer
// fragmentainer.
LayoutFlowThread* flowThread = flowThreadContainingBlock();
if (!flowThread) {
// If there's no flow thread, we're not nested. All pages have the same height. Give up.
return strutToNextPage;
}
// Start searching for a suitable offset at the top of the next page or column.
LayoutUnit flowThreadOffset = offsetFromLogicalTopOfFirstPage() + nextPageLogicalTop;
return strutToNextPage + flowThread->nextLogicalTopForUnbreakableContent(flowThreadOffset, contentLogicalHeight) - flowThreadOffset;
}
LayoutBox* LayoutBox::snapContainer() const
{
return m_rareData ? m_rareData->m_snapContainer : nullptr;
}
void LayoutBox::setSnapContainer(LayoutBox* newContainer)
{
LayoutBox* oldContainer = snapContainer();
if (oldContainer == newContainer)
return;
if (oldContainer)
oldContainer->removeSnapArea(*this);
ensureRareData().m_snapContainer = newContainer;
if (newContainer)
newContainer->addSnapArea(*this);
}
void LayoutBox::clearSnapAreas()
{
if (SnapAreaSet* areas = snapAreas()) {
for (auto& snapArea : *areas)
snapArea->m_rareData->m_snapContainer = nullptr;
areas->clear();
}
}
void LayoutBox::addSnapArea(const LayoutBox& snapArea)
{
ensureRareData().ensureSnapAreas().add(&snapArea);
}
void LayoutBox::removeSnapArea(const LayoutBox& snapArea)
{
if (m_rareData && m_rareData->m_snapAreas) {
m_rareData->m_snapAreas->remove(&snapArea);
}
}
SnapAreaSet* LayoutBox::snapAreas() const
{
return m_rareData ? m_rareData->m_snapAreas.get() : nullptr;
}
} // namespace blink