blob: da20d91b76b7e75db4c976ee0328591e22c9591f [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 Apple Inc. All rights reserved.
* Copyright (C) 2010 Google Inc. 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/LayoutBoxModelObject.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/html/HTMLBodyElement.h"
#include "core/layout/ImageQualityController.h"
#include "core/layout/LayoutBlock.h"
#include "core/layout/LayoutFlexibleBox.h"
#include "core/layout/LayoutGeometryMap.h"
#include "core/layout/LayoutInline.h"
#include "core/layout/LayoutTheme.h"
#include "core/layout/LayoutView.h"
#include "core/layout/compositing/CompositedLayerMapping.h"
#include "core/layout/compositing/PaintLayerCompositor.h"
#include "core/paint/ObjectPaintInvalidator.h"
#include "core/paint/PaintLayer.h"
#include "core/style/ShadowList.h"
#include "platform/LengthFunctions.h"
#include "platform/geometry/TransformState.h"
#include "wtf/PtrUtil.h"
namespace blink {
class FloatStateForStyleChange {
public:
static void setWasFloating(LayoutBoxModelObject* boxModelObject,
bool wasFloating) {
s_wasFloating = wasFloating;
s_boxModelObject = boxModelObject;
}
static bool wasFloating(LayoutBoxModelObject* boxModelObject) {
ASSERT(boxModelObject == s_boxModelObject);
return s_wasFloating;
}
private:
// Used to store state between styleWillChange and styleDidChange
static bool s_wasFloating;
static LayoutBoxModelObject* s_boxModelObject;
};
bool FloatStateForStyleChange::s_wasFloating = false;
LayoutBoxModelObject* FloatStateForStyleChange::s_boxModelObject = nullptr;
// The HashMap for storing continuation pointers.
// The continuation chain is a singly linked list. As such, the HashMap's value
// is the next pointer associated with the key.
typedef HashMap<const LayoutBoxModelObject*, LayoutBoxModelObject*>
ContinuationMap;
static ContinuationMap* continuationMap = nullptr;
void LayoutBoxModelObject::setSelectionState(SelectionState state) {
if (state == SelectionInside && getSelectionState() != SelectionNone)
return;
if ((state == SelectionStart && getSelectionState() == SelectionEnd) ||
(state == SelectionEnd && getSelectionState() == SelectionStart))
LayoutObject::setSelectionState(SelectionBoth);
else
LayoutObject::setSelectionState(state);
// FIXME: We should consider whether it is OK propagating to ancestor
// LayoutInlines. This is a workaround for http://webkit.org/b/32123
// The containing block can be null in case of an orphaned tree.
LayoutBlock* containingBlock = this->containingBlock();
if (containingBlock && !containingBlock->isLayoutView())
containingBlock->setSelectionState(state);
}
void LayoutBoxModelObject::contentChanged(ContentChangeType changeType) {
if (!hasLayer())
return;
layer()->contentChanged(changeType);
}
bool LayoutBoxModelObject::hasAcceleratedCompositing() const {
return view()->compositor()->hasAcceleratedCompositing();
}
LayoutBoxModelObject::LayoutBoxModelObject(ContainerNode* node)
: LayoutObject(node) {}
bool LayoutBoxModelObject::usesCompositedScrolling() const {
return hasOverflowClip() && hasLayer() &&
layer()->getScrollableArea()->usesCompositedScrolling();
}
bool LayoutBoxModelObject::hasLocalEquivalentBackground() const {
int minBorderWidth = std::min(
style()->borderTopWidth(),
std::min(
style()->borderLeftWidth(),
std::min(style()->borderRightWidth(), style()->borderBottomWidth())));
bool outlineOverlapsPaddingBox = style()->outlineOffset() < -minBorderWidth;
bool hasCustomScrollbars = false;
// TODO(flackr): Detect opaque custom scrollbars which would cover up a
// border-box background.
if (PaintLayerScrollableArea* scrollableArea = getScrollableArea()) {
if ((scrollableArea->horizontalScrollbar() &&
scrollableArea->horizontalScrollbar()->isCustomScrollbar()) ||
(scrollableArea->verticalScrollbar() &&
scrollableArea->verticalScrollbar()->isCustomScrollbar())) {
hasCustomScrollbars = true;
}
}
// TODO(flackr): When we correctly clip the scrolling contents layer we can
// paint locally equivalent backgrounds into it. https://crbug.com/645957
if (!style()->hasAutoClip())
return false;
// TODO(flackr): Remove this when box shadows are still painted correctly when
// painting into the composited scrolling contents layer.
// https://crbug.com/646464
if (style()->boxShadow())
return false;
const FillLayer* layer = &(style()->backgroundLayers());
for (; layer; layer = layer->next()) {
if (layer->attachment() == LocalBackgroundAttachment)
continue;
// If the outline draws inside the border, it intends to draw on top of the
// scroller's background, however because it is painted into a layer behind
// the scrolling contents layer we avoid auto promoting in this case to
// avoid obscuring the outline.
// TODO(flackr): Outlines should be drawn on top of the scrolling contents
// layer so that they cannot be covered up by composited scrolling contents.
if (outlineOverlapsPaddingBox)
return false;
// Solid color layers with an effective background clip of the padding box
// can be treated as local.
if (!layer->image() && !layer->next() &&
resolveColor(CSSPropertyBackgroundColor).alpha() > 0) {
EFillBox clip = layer->clip();
if (clip == PaddingFillBox)
continue;
// A border box can be treated as a padding box if the border is opaque or
// there is no border and we don't have custom scrollbars.
if (clip == BorderFillBox && !hasCustomScrollbars &&
(style()->borderTopWidth() == 0 ||
!resolveColor(CSSPropertyBorderTopColor).hasAlpha()) &&
(style()->borderLeftWidth() == 0 ||
!resolveColor(CSSPropertyBorderLeftColor).hasAlpha()) &&
(style()->borderRightWidth() == 0 ||
!resolveColor(CSSPropertyBorderRightColor).hasAlpha()) &&
(style()->borderBottomWidth() == 0 ||
!resolveColor(CSSPropertyBorderBottomColor).hasAlpha())) {
continue;
}
// A content fill box can be treated as a padding fill box if there is no
// padding.
if (clip == ContentFillBox && style()->paddingTop().isZero() &&
style()->paddingLeft().isZero() && style()->paddingRight().isZero() &&
style()->paddingBottom().isZero()) {
continue;
}
}
return false;
}
return true;
}
LayoutBoxModelObject::~LayoutBoxModelObject() {
// Our layer should have been destroyed and cleared by now
ASSERT(!hasLayer());
ASSERT(!m_layer);
}
void LayoutBoxModelObject::willBeDestroyed() {
ImageQualityController::remove(*this);
// A continuation of this LayoutObject should be destroyed at subclasses.
ASSERT(!continuation());
if (isPositioned()) {
// Don't use this->view() because the document's layoutView has been set to
// 0 during destruction.
if (LocalFrame* frame = this->frame()) {
if (FrameView* frameView = frame->view()) {
if (style()->hasViewportConstrainedPosition())
frameView->removeViewportConstrainedObject(this);
}
}
}
LayoutObject::willBeDestroyed();
destroyLayer();
}
void LayoutBoxModelObject::styleWillChange(StyleDifference diff,
const ComputedStyle& newStyle) {
// This object's layer may cease to be a stacking context, in which case the
// paint invalidation container of the children may change. Thus we need to
// invalidate paint eagerly for all such children.
if (hasLayer() && enclosingLayer()->stackingNode() &&
enclosingLayer()->stackingNode()->isStackingContext() &&
!newStyle.isStackingContext()) {
// The following disablers are valid because we need to invalidate based on
// the current status.
DisableCompositingQueryAsserts compositingDisabler;
DisablePaintInvalidationStateAsserts paintDisabler;
ObjectPaintInvalidator(*this)
.invalidatePaintIncludingNonCompositingDescendants();
}
FloatStateForStyleChange::setWasFloating(this, isFloating());
if (const ComputedStyle* oldStyle = style()) {
if (hasLayer() && diff.needsPaintInvalidationSubtree()) {
if (oldStyle->hasAutoClip() != newStyle.hasAutoClip() ||
oldStyle->clip() != newStyle.clip())
layer()->clipper().clearClipRectsIncludingDescendants();
}
}
LayoutObject::styleWillChange(diff, newStyle);
}
DISABLE_CFI_PERF
void LayoutBoxModelObject::styleDidChange(StyleDifference diff,
const ComputedStyle* oldStyle) {
bool hadTransform = hasTransformRelatedProperty();
bool hadLayer = hasLayer();
bool layerWasSelfPainting = hadLayer && layer()->isSelfPaintingLayer();
bool wasFloatingBeforeStyleChanged =
FloatStateForStyleChange::wasFloating(this);
bool wasHorizontalWritingMode = isHorizontalWritingMode();
LayoutObject::styleDidChange(diff, oldStyle);
updateFromStyle();
// When an out-of-flow-positioned element changes its display between block
// and inline-block, then an incremental layout on the element's containing
// block lays out the element through LayoutPositionedObjects, which skips
// laying out the element's parent.
// The element's parent needs to relayout so that it calls LayoutBlockFlow::
// setStaticInlinePositionForChild with the out-of-flow-positioned child, so
// that when it's laid out, its LayoutBox::computePositionedLogicalWidth/
// Height takes into account its new inline/block position rather than its old
// block/inline position.
// Position changes and other types of display changes are handled elsewhere.
if (oldStyle && isOutOfFlowPositioned() && parent() &&
(parent() != containingBlock()) &&
(styleRef().position() == oldStyle->position()) &&
(styleRef().originalDisplay() != oldStyle->originalDisplay()) &&
((styleRef().originalDisplay() == EDisplay::Block) ||
(styleRef().originalDisplay() == EDisplay::InlineBlock)) &&
((oldStyle->originalDisplay() == EDisplay::Block) ||
(oldStyle->originalDisplay() == EDisplay::InlineBlock)))
parent()->setNeedsLayout(LayoutInvalidationReason::ChildChanged,
MarkContainerChain);
PaintLayerType type = layerTypeRequired();
if (type != NoPaintLayer) {
if (!layer() && layerCreationAllowedForSubtree()) {
if (wasFloatingBeforeStyleChanged && isFloating())
setChildNeedsLayout();
createLayer();
if (parent() && !needsLayout()) {
// FIXME: We should call a specialized version of this function.
layer()->updateLayerPositionsAfterLayout();
}
}
} else if (layer() && layer()->parent()) {
PaintLayer* parentLayer = layer()->parent();
// Either a transform wasn't specified or the object doesn't support
// transforms, so just null out the bit.
setHasTransformRelatedProperty(false);
setHasReflection(false);
// Calls destroyLayer() which clears m_layer.
layer()->removeOnlyThisLayerAfterStyleChange();
if (wasFloatingBeforeStyleChanged && isFloating())
setChildNeedsLayout();
if (hadTransform)
setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(
LayoutInvalidationReason::StyleChange);
if (!needsLayout()) {
// FIXME: We should call a specialized version of this function.
parentLayer->updateLayerPositionsAfterLayout();
}
}
if (layer()) {
layer()->styleDidChange(diff, oldStyle);
if (hadLayer && layer()->isSelfPaintingLayer() != layerWasSelfPainting)
setChildNeedsLayout();
}
if (oldStyle && wasHorizontalWritingMode != isHorizontalWritingMode()) {
// Changing the getWritingMode() may change isOrthogonalWritingModeRoot()
// of children. Make sure all children are marked/unmarked as orthogonal
// writing-mode roots.
bool newHorizontalWritingMode = isHorizontalWritingMode();
for (LayoutObject* child = slowFirstChild(); child;
child = child->nextSibling()) {
if (!child->isBox())
continue;
if (newHorizontalWritingMode != child->isHorizontalWritingMode())
toLayoutBox(child)->markOrthogonalWritingModeRoot();
else
toLayoutBox(child)->unmarkOrthogonalWritingModeRoot();
}
}
// Fixed-position is painted using transform. In the case that the object
// gets the same layout after changing position property, although no
// re-raster (rect-based invalidation) is needed, display items should
// still update their paint offset.
if (oldStyle) {
bool newStyleIsFixedPosition = style()->position() == FixedPosition;
bool oldStyleIsFixedPosition = oldStyle->position() == FixedPosition;
if (newStyleIsFixedPosition != oldStyleIsFixedPosition)
ObjectPaintInvalidator(*this)
.invalidateDisplayItemClientsIncludingNonCompositingDescendants(
PaintInvalidationStyleChange);
}
// The used style for body background may change due to computed style change
// on the document element because of background stealing.
// Refer to backgroundStolenForBeingBody() and
// http://www.w3.org/TR/css3-background/#body-background for more info.
if (isDocumentElement()) {
HTMLBodyElement* body = document().firstBodyElement();
LayoutObject* bodyLayout = body ? body->layoutObject() : nullptr;
if (bodyLayout && bodyLayout->isBoxModelObject()) {
bool newStoleBodyBackground = toLayoutBoxModelObject(bodyLayout)
->backgroundStolenForBeingBody(style());
bool oldStoleBodyBackground =
oldStyle &&
toLayoutBoxModelObject(bodyLayout)
->backgroundStolenForBeingBody(oldStyle);
if (newStoleBodyBackground != oldStoleBodyBackground &&
bodyLayout->style() && bodyLayout->style()->hasBackground()) {
bodyLayout->setShouldDoFullPaintInvalidation();
}
}
}
if (FrameView* frameView = view()->frameView()) {
bool newStyleIsViewportConstained = style()->position() == FixedPosition;
bool oldStyleIsViewportConstrained =
oldStyle && oldStyle->position() == FixedPosition;
bool newStyleIsSticky = style()->position() == StickyPosition;
bool oldStyleIsSticky = oldStyle && oldStyle->position() == StickyPosition;
if (newStyleIsSticky != oldStyleIsSticky) {
if (newStyleIsSticky) {
// During compositing inputs update we'll have the scroll ancestor
// without having to walk up the tree and can compute the sticky
// position constraints then.
if (layer())
layer()->setNeedsCompositingInputsUpdate();
// TODO(pdr): When slimming paint v2 is enabled, we will need to
// invalidate the scroll paint property subtree for this so main thread
// scroll reasons are recomputed.
} else {
// This may get re-added to viewport constrained objects if the object
// went from sticky to fixed.
frameView->removeViewportConstrainedObject(this);
// Remove sticky constraints for this layer.
if (layer()) {
DisableCompositingQueryAsserts disabler;
if (const PaintLayer* ancestorOverflowLayer =
layer()->ancestorOverflowLayer())
ancestorOverflowLayer->getScrollableArea()
->invalidateStickyConstraintsFor(layer());
}
// TODO(pdr): When slimming paint v2 is enabled, we will need to
// invalidate the scroll paint property subtree for this so main thread
// scroll reasons are recomputed.
}
}
if (newStyleIsViewportConstained != oldStyleIsViewportConstrained) {
if (newStyleIsViewportConstained && layer())
frameView->addViewportConstrainedObject(this);
else
frameView->removeViewportConstrainedObject(this);
}
}
}
void LayoutBoxModelObject::invalidateStickyConstraints() {
if (!layer())
return;
// This intentionally uses the stale ancestor overflow layer compositing input
// as if we have saved constraints for this layer they were saved in the
// previous frame.
DisableCompositingQueryAsserts disabler;
if (const PaintLayer* ancestorOverflowLayer =
layer()->ancestorOverflowLayer())
ancestorOverflowLayer->getScrollableArea()
->invalidateAllStickyConstraints();
}
void LayoutBoxModelObject::createLayer() {
ASSERT(!m_layer);
m_layer = wrapUnique(new PaintLayer(this));
setHasLayer(true);
m_layer->insertOnlyThisLayerAfterStyleChange();
}
void LayoutBoxModelObject::destroyLayer() {
setHasLayer(false);
m_layer = nullptr;
}
bool LayoutBoxModelObject::hasSelfPaintingLayer() const {
return m_layer && m_layer->isSelfPaintingLayer();
}
PaintLayerScrollableArea* LayoutBoxModelObject::getScrollableArea() const {
return m_layer ? m_layer->getScrollableArea() : 0;
}
void LayoutBoxModelObject::addLayerHitTestRects(
LayerHitTestRects& rects,
const PaintLayer* currentLayer,
const LayoutPoint& layerOffset,
const LayoutRect& containerRect) const {
if (hasLayer()) {
if (isLayoutView()) {
// LayoutView is handled with a special fast-path, but it needs to know
// the current layer.
LayoutObject::addLayerHitTestRects(rects, layer(), LayoutPoint(),
LayoutRect());
} else {
// Since a LayoutObject never lives outside it's container Layer, we can
// switch to marking entire layers instead. This may sometimes mark more
// than necessary (when a layer is made of disjoint objects) but in
// practice is a significant performance savings.
layer()->addLayerHitTestRects(rects);
}
} else {
LayoutObject::addLayerHitTestRects(rects, currentLayer, layerOffset,
containerRect);
}
}
static bool hasPercentageTransform(const ComputedStyle& style) {
if (TransformOperation* translate = style.translate()) {
if (translate->dependsOnBoxSize())
return true;
}
return style.transform().dependsOnBoxSize() ||
(style.transformOriginX() != Length(50, Percent) &&
style.transformOriginX().isPercentOrCalc()) ||
(style.transformOriginY() != Length(50, Percent) &&
style.transformOriginY().isPercentOrCalc());
}
DISABLE_CFI_PERF
void LayoutBoxModelObject::invalidateTreeIfNeeded(
const PaintInvalidationState& paintInvalidationState) {
DCHECK(!RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled());
ensureIsReadyForPaintInvalidation();
PaintInvalidationState newPaintInvalidationState(paintInvalidationState,
*this);
if (!shouldCheckForPaintInvalidation(newPaintInvalidationState))
return;
if (mayNeedPaintInvalidationSubtree())
newPaintInvalidationState
.setForceSubtreeInvalidationCheckingWithinContainer();
LayoutRect previousPaintInvalidationRect =
this->previousPaintInvalidationRect();
LayoutPoint previousPosition = previousPositionFromPaintInvalidationBacking();
PaintInvalidationReason reason =
invalidatePaintIfNeeded(newPaintInvalidationState);
clearPaintInvalidationFlags();
if (previousPosition != previousPositionFromPaintInvalidationBacking())
newPaintInvalidationState
.setForceSubtreeInvalidationCheckingWithinContainer();
// TODO(wangxianzhu): Combine this function into LayoutObject::
// invalidateTreeIfNeeded() when removing the following workarounds.
// TODO(wangxianzhu): This is a workaround for crbug.com/533277. Will remove
// when we enable paint offset caching.
if (reason != PaintInvalidationNone && hasPercentageTransform(styleRef()))
newPaintInvalidationState
.setForceSubtreeInvalidationCheckingWithinContainer();
// TODO(wangxianzhu): This is a workaround for crbug.com/490725. We don't have
// enough saved information to do accurate check of clipping change. Will
// remove when we remove rect-based paint invalidation.
if (!RuntimeEnabledFeatures::slimmingPaintV2Enabled() &&
previousPaintInvalidationRect != this->previousPaintInvalidationRect() &&
!usesCompositedScrolling()
// Note that isLayoutView() below becomes unnecessary after the launch of
// root layer scrolling.
&& (hasOverflowClip() || isLayoutView()))
newPaintInvalidationState
.setForceSubtreeInvalidationRectUpdateWithinContainer();
newPaintInvalidationState.updateForChildren(reason);
invalidatePaintOfSubtreesIfNeeded(newPaintInvalidationState);
}
void LayoutBoxModelObject::addOutlineRectsForNormalChildren(
Vector<LayoutRect>& rects,
const LayoutPoint& additionalOffset,
IncludeBlockVisualOverflowOrNot includeBlockOverflows) const {
for (LayoutObject* child = slowFirstChild(); child;
child = child->nextSibling()) {
// Outlines of out-of-flow positioned descendants are handled in
// LayoutBlock::addOutlineRects().
if (child->isOutOfFlowPositioned())
continue;
// Outline of an element continuation or anonymous block continuation is
// added when we iterate the continuation chain.
// See LayoutBlock::addOutlineRects() and LayoutInline::addOutlineRects().
if (child->isElementContinuation() ||
(child->isLayoutBlockFlow() &&
toLayoutBlockFlow(child)->isAnonymousBlockContinuation()))
continue;
addOutlineRectsForDescendant(*child, rects, additionalOffset,
includeBlockOverflows);
}
}
void LayoutBoxModelObject::addOutlineRectsForDescendant(
const LayoutObject& descendant,
Vector<LayoutRect>& rects,
const LayoutPoint& additionalOffset,
IncludeBlockVisualOverflowOrNot includeBlockOverflows) const {
if (descendant.isText() || descendant.isListMarker())
return;
if (descendant.hasLayer()) {
Vector<LayoutRect> layerOutlineRects;
descendant.addOutlineRects(layerOutlineRects, LayoutPoint(),
includeBlockOverflows);
descendant.localToAncestorRects(layerOutlineRects, this, LayoutPoint(),
additionalOffset);
rects.appendVector(layerOutlineRects);
return;
}
if (descendant.isBox()) {
descendant.addOutlineRects(
rects, additionalOffset + toLayoutBox(descendant).locationOffset(),
includeBlockOverflows);
return;
}
if (descendant.isLayoutInline()) {
// As an optimization, an ancestor has added rects for its line boxes
// covering descendants' line boxes, so descendants don't need to add line
// boxes again. For example, if the parent is a LayoutBlock, it adds rects
// for its RootOutlineBoxes which cover the line boxes of this LayoutInline.
// So the LayoutInline needs to add rects for children and continuations
// only.
toLayoutInline(descendant)
.addOutlineRectsForChildrenAndContinuations(rects, additionalOffset,
includeBlockOverflows);
return;
}
descendant.addOutlineRects(rects, additionalOffset, includeBlockOverflows);
}
bool LayoutBoxModelObject::hasNonEmptyLayoutSize() const {
for (const LayoutBoxModelObject* root = this; root;
root = root->continuation()) {
for (const LayoutObject* object = root; object;
object = object->nextInPreOrder(object)) {
if (object->isBox()) {
const LayoutBox& box = toLayoutBox(*object);
if (box.logicalHeight() && box.logicalWidth())
return true;
} else if (object->isLayoutInline()) {
const LayoutInline& layoutInline = toLayoutInline(*object);
if (!layoutInline.linesBoundingBox().isEmpty())
return true;
} else {
ASSERT(object->isText());
}
}
}
return false;
}
void LayoutBoxModelObject::absoluteQuadsForSelf(
Vector<FloatQuad>& quads) const {
NOTREACHED();
}
void LayoutBoxModelObject::absoluteQuads(Vector<FloatQuad>& quads) const {
absoluteQuadsForSelf(quads);
// Iterate over continuations, avoiding recursion in case there are
// many of them. See crbug.com/653767.
for (const LayoutBoxModelObject* continuationObject = this->continuation();
continuationObject;
continuationObject = continuationObject->continuation()) {
DCHECK(continuationObject->isLayoutInline() ||
(continuationObject->isLayoutBlockFlow() &&
toLayoutBlockFlow(continuationObject)
->isAnonymousBlockContinuation()));
continuationObject->absoluteQuadsForSelf(quads);
}
}
void LayoutBoxModelObject::updateFromStyle() {
const ComputedStyle& styleToUse = styleRef();
setHasBoxDecorationBackground(styleToUse.hasBoxDecorationBackground());
setInline(styleToUse.isDisplayInlineType());
setPositionState(styleToUse.position());
setHorizontalWritingMode(styleToUse.isHorizontalWritingMode());
}
static inline bool isOutOfFlowPositionedWithImplicitHeight(
const LayoutBoxModelObject* child) {
return child->isOutOfFlowPositioned() &&
!child->style()->logicalTop().isAuto() &&
!child->style()->logicalBottom().isAuto();
}
LayoutBlock* LayoutBoxModelObject::containingBlockForAutoHeightDetection(
Length logicalHeight) const {
// For percentage heights: The percentage is calculated with respect to the
// height of the generated box's containing block. 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 used height is
// calculated as if 'auto' was specified.
if (!logicalHeight.isPercentOrCalc() || isOutOfFlowPositioned())
return nullptr;
// Anonymous block boxes are ignored when resolving percentage values that
// would refer to it: the closest non-anonymous ancestor box is used instead.
LayoutBlock* cb = containingBlock();
while (cb->isAnonymous())
cb = cb->containingBlock();
// Matching LayoutBox::percentageLogicalHeightIsResolvableFromBlock() by
// ignoring table cell's attribute value, where it says that 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.
if (cb->isTableCell())
return nullptr;
// Match LayoutBox::availableLogicalHeightUsing by special casing the layout
// view. The available height is taken from the frame.
if (cb->isLayoutView())
return nullptr;
if (isOutOfFlowPositionedWithImplicitHeight(cb))
return nullptr;
return cb;
}
bool LayoutBoxModelObject::hasAutoHeightOrContainingBlockWithAutoHeight()
const {
// TODO(rego): Check if we can somehow reuse LayoutBlock::
// availableLogicalHeightForPercentageComputation() (see crbug.com/635655).
const LayoutBox* thisBox = isBox() ? toLayoutBox(this) : nullptr;
Length logicalHeightLength = style()->logicalHeight();
LayoutBlock* cb = containingBlockForAutoHeightDetection(logicalHeightLength);
if (logicalHeightLength.isPercentOrCalc() && cb && isBox())
cb->addPercentHeightDescendant(const_cast<LayoutBox*>(toLayoutBox(this)));
if (thisBox && thisBox->isFlexItem()) {
LayoutFlexibleBox& flexBox = toLayoutFlexibleBox(*parent());
if (flexBox.childLogicalHeightForPercentageResolution(*thisBox) !=
LayoutUnit(-1))
return false;
}
if (thisBox && thisBox->isGridItem() &&
thisBox->hasOverrideContainingBlockLogicalHeight())
return false;
if (logicalHeightLength.isAuto() &&
!isOutOfFlowPositionedWithImplicitHeight(this))
return true;
if (document().inQuirksMode())
return false;
if (cb)
return !cb->hasDefiniteLogicalHeight();
return false;
}
LayoutSize LayoutBoxModelObject::relativePositionOffset() const {
LayoutSize offset = accumulateInFlowPositionOffsets();
LayoutBlock* containingBlock = this->containingBlock();
// Objects that shrink to avoid floats normally use available line width when
// computing containing block width. However in the case of relative
// positioning using percentages, we can't do this. The offset should always
// be resolved using the available width of the containing block. Therefore we
// don't use containingBlockLogicalWidthForContent() here, but instead
// explicitly call availableWidth on our containing block.
if (!style()->left().isAuto()) {
if (!style()->right().isAuto() &&
!containingBlock->style()->isLeftToRightDirection())
offset.setWidth(
-valueForLength(style()->right(), containingBlock->availableWidth()));
else
offset.expand(
valueForLength(style()->left(), containingBlock->availableWidth()),
LayoutUnit());
} else if (!style()->right().isAuto()) {
offset.expand(
-valueForLength(style()->right(), containingBlock->availableWidth()),
LayoutUnit());
}
// If the containing block of a relatively positioned element does not specify
// a height, a percentage top or bottom offset should be resolved as auto.
// An exception to this is if the containing block has the WinIE quirk where
// <html> and <body> assume the size of the viewport. In this case, calculate
// the percent offset based on this height.
// See <https://bugs.webkit.org/show_bug.cgi?id=26396>.
if (!style()->top().isAuto() &&
(!containingBlock->hasAutoHeightOrContainingBlockWithAutoHeight() ||
!style()->top().isPercentOrCalc() ||
containingBlock->stretchesToViewport()))
offset.expand(
LayoutUnit(),
valueForLength(style()->top(), containingBlock->availableHeight()));
else if (!style()->bottom().isAuto() &&
(!containingBlock->hasAutoHeightOrContainingBlockWithAutoHeight() ||
!style()->bottom().isPercentOrCalc() ||
containingBlock->stretchesToViewport()))
offset.expand(
LayoutUnit(),
-valueForLength(style()->bottom(), containingBlock->availableHeight()));
return offset;
}
void LayoutBoxModelObject::updateStickyPositionConstraints() const {
const FloatSize constrainingSize = computeStickyConstrainingRect().size();
PaintLayerScrollableArea* scrollableArea =
layer()->ancestorOverflowLayer()->getScrollableArea();
StickyPositionScrollingConstraints constraints;
FloatSize skippedContainersOffset;
LayoutBlock* containingBlock = this->containingBlock();
// Skip anonymous containing blocks.
while (containingBlock->isAnonymous()) {
skippedContainersOffset +=
toFloatSize(FloatPoint(containingBlock->frameRect().location()));
containingBlock = containingBlock->containingBlock();
}
LayoutBox* scrollAncestor =
layer()->ancestorOverflowLayer()->isRootLayer()
? nullptr
: toLayoutBox(layer()->ancestorOverflowLayer()->layoutObject());
LayoutUnit maxContainerWidth =
containingBlock->isLayoutView()
? containingBlock->logicalWidth()
: containingBlock->containingBlockLogicalWidthForContent();
// Sticky positioned element ignore any override logical width on the
// containing block, as they don't call containingBlockLogicalWidthForContent.
// It's unclear whether this is totally fine.
// Compute the container-relative area within which the sticky element is
// allowed to move.
LayoutUnit maxWidth = containingBlock->availableLogicalWidth();
// Map the containing block to the scroll ancestor without transforms.
FloatRect scrollContainerRelativePaddingBoxRect(
containingBlock->layoutOverflowRect());
if (containingBlock != scrollAncestor) {
FloatQuad localQuad(FloatRect(containingBlock->paddingBoxRect()));
TransformState transformState(TransformState::ApplyTransformDirection,
localQuad.boundingBox().center(), localQuad);
containingBlock->mapLocalToAncestor(scrollAncestor, transformState,
ApplyContainerFlip);
transformState.flatten();
scrollContainerRelativePaddingBoxRect =
transformState.lastPlanarQuad().boundingBox();
// The sticky position constraint rects should be independent of the current
// scroll position, so after mapping we add in the scroll position to get
// the container's position within the ancestor scroller's unscrolled layout
// overflow.
ScrollOffset scrollOffset(
scrollAncestor
? toFloatSize(scrollAncestor->getScrollableArea()->scrollPosition())
: FloatSize());
scrollContainerRelativePaddingBoxRect.move(scrollOffset);
}
LayoutRect scrollContainerRelativeContainingBlockRect(
scrollContainerRelativePaddingBoxRect);
// This is removing the padding of the containing block's overflow rect to get
// the flow box rectangle and removing the margin of the sticky element to
// ensure that space between the sticky element and its containing flow box.
// It is an open issue whether the margin should collapse.
// See https://www.w3.org/TR/css-position-3/#sticky-pos
scrollContainerRelativeContainingBlockRect.contractEdges(
minimumValueForLength(containingBlock->style()->paddingTop(),
maxContainerWidth) +
minimumValueForLength(style()->marginTop(), maxWidth),
minimumValueForLength(containingBlock->style()->paddingRight(),
maxContainerWidth) +
minimumValueForLength(style()->marginRight(), maxWidth),
minimumValueForLength(containingBlock->style()->paddingBottom(),
maxContainerWidth) +
minimumValueForLength(style()->marginBottom(), maxWidth),
minimumValueForLength(containingBlock->style()->paddingLeft(),
maxContainerWidth) +
minimumValueForLength(style()->marginLeft(), maxWidth));
constraints.setScrollContainerRelativeContainingBlockRect(
FloatRect(scrollContainerRelativeContainingBlockRect));
FloatRect stickyBoxRect =
isLayoutInline() ? FloatRect(toLayoutInline(this)->linesBoundingBox())
: FloatRect(toLayoutBox(this)->frameRect());
FloatRect flippedStickyBoxRect = stickyBoxRect;
containingBlock->flipForWritingMode(flippedStickyBoxRect);
FloatPoint stickyLocation =
flippedStickyBoxRect.location() + skippedContainersOffset;
// The scrollContainerRelativePaddingBoxRect's position is the padding box so
// we need to remove the border when finding the position of the sticky box
// within the scroll ancestor if the container is not our scroll ancestor.
if (containingBlock != scrollAncestor) {
FloatSize containerBorderOffset(containingBlock->borderLeft(),
containingBlock->borderTop());
stickyLocation -= containerBorderOffset;
}
constraints.setScrollContainerRelativeStickyBoxRect(
FloatRect(scrollContainerRelativePaddingBoxRect.location() +
toFloatSize(stickyLocation),
flippedStickyBoxRect.size()));
// We skip the right or top sticky offset if there is not enough space to
// honor both the left/right or top/bottom offsets.
LayoutUnit horizontalOffsets =
minimumValueForLength(style()->right(),
LayoutUnit(constrainingSize.width())) +
minimumValueForLength(style()->left(),
LayoutUnit(constrainingSize.width()));
bool skipRight = false;
bool skipLeft = false;
if (!style()->left().isAuto() && !style()->right().isAuto()) {
if (horizontalOffsets >
scrollContainerRelativeContainingBlockRect.width() ||
horizontalOffsets + scrollContainerRelativeContainingBlockRect.width() >
constrainingSize.width()) {
skipRight = style()->isLeftToRightDirection();
skipLeft = !skipRight;
}
}
if (!style()->left().isAuto() && !skipLeft) {
constraints.setLeftOffset(minimumValueForLength(
style()->left(), LayoutUnit(constrainingSize.width())));
constraints.addAnchorEdge(
StickyPositionScrollingConstraints::AnchorEdgeLeft);
}
if (!style()->right().isAuto() && !skipRight) {
constraints.setRightOffset(minimumValueForLength(
style()->right(), LayoutUnit(constrainingSize.width())));
constraints.addAnchorEdge(
StickyPositionScrollingConstraints::AnchorEdgeRight);
}
bool skipBottom = false;
// TODO(flackr): Exclude top or bottom edge offset depending on the writing
// mode when related sections are fixed in spec.
// See http://lists.w3.org/Archives/Public/www-style/2014May/0286.html
LayoutUnit verticalOffsets =
minimumValueForLength(style()->top(),
LayoutUnit(constrainingSize.height())) +
minimumValueForLength(style()->bottom(),
LayoutUnit(constrainingSize.height()));
if (!style()->top().isAuto() && !style()->bottom().isAuto()) {
if (verticalOffsets > scrollContainerRelativeContainingBlockRect.height() ||
verticalOffsets + scrollContainerRelativeContainingBlockRect.height() >
constrainingSize.height()) {
skipBottom = true;
}
}
if (!style()->top().isAuto()) {
constraints.setTopOffset(minimumValueForLength(
style()->top(), LayoutUnit(constrainingSize.height())));
constraints.addAnchorEdge(
StickyPositionScrollingConstraints::AnchorEdgeTop);
}
if (!style()->bottom().isAuto() && !skipBottom) {
constraints.setBottomOffset(minimumValueForLength(
style()->bottom(), LayoutUnit(constrainingSize.height())));
constraints.addAnchorEdge(
StickyPositionScrollingConstraints::AnchorEdgeBottom);
}
scrollableArea->stickyConstraintsMap().set(layer(), constraints);
}
FloatRect LayoutBoxModelObject::computeStickyConstrainingRect() const {
if (layer()->ancestorOverflowLayer()->isRootLayer())
return view()->frameView()->visibleContentRect();
LayoutBox* enclosingClippingBox =
toLayoutBox(layer()->ancestorOverflowLayer()->layoutObject());
FloatRect constrainingRect;
constrainingRect =
FloatRect(enclosingClippingBox->overflowClipRect(LayoutPoint(DoublePoint(
enclosingClippingBox->getScrollableArea()->scrollPosition()))));
constrainingRect.move(enclosingClippingBox->paddingLeft(),
enclosingClippingBox->paddingTop());
constrainingRect.contract(
FloatSize(enclosingClippingBox->paddingLeft() +
enclosingClippingBox->paddingRight(),
enclosingClippingBox->paddingTop() +
enclosingClippingBox->paddingBottom()));
return constrainingRect;
}
LayoutSize LayoutBoxModelObject::stickyPositionOffset() const {
const PaintLayer* ancestorOverflowLayer = layer()->ancestorOverflowLayer();
// TODO: Force compositing input update if we ask for offset before
// compositing inputs have been computed?
if (!ancestorOverflowLayer)
return LayoutSize();
FloatRect constrainingRect = computeStickyConstrainingRect();
PaintLayerScrollableArea* scrollableArea =
ancestorOverflowLayer->getScrollableArea();
// The sticky offset is physical, so we can just return the delta computed in
// absolute coords (though it may be wrong with transforms).
// TODO: Force compositing input update if we ask for offset with stale
// compositing inputs.
if (!scrollableArea->stickyConstraintsMap().contains(layer()))
return LayoutSize();
return LayoutSize(
scrollableArea->stickyConstraintsMap().get(layer()).computeStickyOffset(
constrainingRect));
}
LayoutPoint LayoutBoxModelObject::adjustedPositionRelativeTo(
const LayoutPoint& startPoint,
const Element* element) const {
// If the element is the HTML body element or doesn't have a parent
// return 0 and stop this algorithm.
if (isBody() || !parent())
return LayoutPoint();
LayoutPoint referencePoint = startPoint;
// If the base element is null, return the distance between the canvas origin
// and the left border edge of the element and stop this algorithm.
if (!element)
return referencePoint;
if (const LayoutBoxModelObject* offsetParent =
element->layoutBoxModelObject()) {
if (!isOutOfFlowPositioned()) {
if (isInFlowPositioned())
referencePoint.move(offsetForInFlowPosition());
// Note that we may fail to find |offsetParent| while walking the
// container chain, if |offsetParent| is an inline split into
// continuations: <body style="display:inline;" id="offsetParent"><div>
// </div><span id="this">
// This is why we have to do a nullptr check here.
// offset(Left|Top) is generally broken when offsetParent is inline.
for (const LayoutObject* current = container();
current && current != offsetParent; current = current->container()) {
// FIXME: What are we supposed to do inside SVG content?
referencePoint.move(current->columnOffset(referencePoint));
if (current->isBox() && !current->isTableRow())
referencePoint.moveBy(toLayoutBox(current)->topLeftLocation());
}
if (offsetParent->isBox() && offsetParent->isBody() &&
!offsetParent->isPositioned())
referencePoint.moveBy(toLayoutBox(offsetParent)->topLeftLocation());
}
if (offsetParent->isBox() && !offsetParent->isBody())
referencePoint.move(-toLayoutBox(offsetParent)->borderLeft(),
-toLayoutBox(offsetParent)->borderTop());
}
return referencePoint;
}
LayoutSize LayoutBoxModelObject::offsetForInFlowPosition() const {
if (isRelPositioned())
return relativePositionOffset();
if (isStickyPositioned())
return stickyPositionOffset();
return LayoutSize();
}
LayoutUnit LayoutBoxModelObject::offsetLeft(const Element* parent) const {
// Note that LayoutInline and LayoutBox override this to pass a different
// startPoint to adjustedPositionRelativeTo.
return adjustedPositionRelativeTo(LayoutPoint(), parent).x();
}
LayoutUnit LayoutBoxModelObject::offsetTop(const Element* parent) const {
// Note that LayoutInline and LayoutBox override this to pass a different
// startPoint to adjustedPositionRelativeTo.
return adjustedPositionRelativeTo(LayoutPoint(), parent).y();
}
int LayoutBoxModelObject::pixelSnappedOffsetWidth(const Element* parent) const {
return snapSizeToPixel(offsetWidth(), offsetLeft(parent));
}
int LayoutBoxModelObject::pixelSnappedOffsetHeight(
const Element* parent) const {
return snapSizeToPixel(offsetHeight(), offsetTop(parent));
}
LayoutUnit LayoutBoxModelObject::computedCSSPadding(
const Length& padding) const {
LayoutUnit w;
if (padding.isPercentOrCalc())
w = containingBlockLogicalWidthForContent();
return minimumValueForLength(padding, w);
}
bool LayoutBoxModelObject::boxShadowShouldBeAppliedToBackground(
BackgroundBleedAvoidance bleedAvoidance,
const InlineFlowBox* inlineFlowBox) const {
if (bleedAvoidance != BackgroundBleedNone)
return false;
if (style()->hasAppearance())
return false;
const ShadowList* shadowList = style()->boxShadow();
if (!shadowList)
return false;
bool hasOneNormalBoxShadow = false;
size_t shadowCount = shadowList->shadows().size();
for (size_t i = 0; i < shadowCount; ++i) {
const ShadowData& currentShadow = shadowList->shadows()[i];
if (currentShadow.style() != Normal)
continue;
if (hasOneNormalBoxShadow)
return false;
hasOneNormalBoxShadow = true;
if (currentShadow.spread())
return false;
}
if (!hasOneNormalBoxShadow)
return false;
Color backgroundColor = resolveColor(CSSPropertyBackgroundColor);
if (backgroundColor.hasAlpha())
return false;
const FillLayer* lastBackgroundLayer = &style()->backgroundLayers();
for (const FillLayer* next = lastBackgroundLayer->next(); next;
next = lastBackgroundLayer->next())
lastBackgroundLayer = next;
if (lastBackgroundLayer->clip() != BorderFillBox)
return false;
if (lastBackgroundLayer->image() && style()->hasBorderRadius())
return false;
if (inlineFlowBox &&
!inlineFlowBox->boxShadowCanBeAppliedToBackground(*lastBackgroundLayer))
return false;
if (hasOverflowClip() &&
lastBackgroundLayer->attachment() == LocalBackgroundAttachment)
return false;
return true;
}
LayoutUnit LayoutBoxModelObject::containingBlockLogicalWidthForContent() const {
return containingBlock()->availableLogicalWidth();
}
LayoutBoxModelObject* LayoutBoxModelObject::continuation() const {
return (!continuationMap) ? nullptr : continuationMap->get(this);
}
void LayoutBoxModelObject::setContinuation(LayoutBoxModelObject* continuation) {
if (continuation) {
ASSERT(continuation->isLayoutInline() || continuation->isLayoutBlockFlow());
if (!continuationMap)
continuationMap = new ContinuationMap;
continuationMap->set(this, continuation);
} else {
if (continuationMap)
continuationMap->remove(this);
}
}
void LayoutBoxModelObject::computeLayerHitTestRects(
LayerHitTestRects& rects) const {
LayoutObject::computeLayerHitTestRects(rects);
// If there is a continuation then we need to consult it here, since this is
// the root of the tree walk and it wouldn't otherwise get picked up.
// Continuations should always be siblings in the tree, so any others should
// get picked up already by the tree walk.
if (continuation())
continuation()->computeLayerHitTestRects(rects);
}
LayoutRect LayoutBoxModelObject::localCaretRectForEmptyElement(
LayoutUnit width,
LayoutUnit textIndentOffset) {
DCHECK(!slowFirstChild() || slowFirstChild()->isPseudoElement());
// FIXME: This does not take into account either :first-line or :first-letter
// However, as soon as some content is entered, the line boxes will be
// constructed and this kludge is not called any more. So only the caret size
// of an empty :first-line'd block is wrong. I think we can live with that.
const ComputedStyle& currentStyle = firstLineStyleRef();
enum CaretAlignment { AlignLeft, AlignRight, AlignCenter };
CaretAlignment alignment = AlignLeft;
switch (currentStyle.textAlign()) {
case LEFT:
case WEBKIT_LEFT:
break;
case CENTER:
case WEBKIT_CENTER:
alignment = AlignCenter;
break;
case RIGHT:
case WEBKIT_RIGHT:
alignment = AlignRight;
break;
case JUSTIFY:
case TASTART:
if (!currentStyle.isLeftToRightDirection())
alignment = AlignRight;
break;
case TAEND:
if (currentStyle.isLeftToRightDirection())
alignment = AlignRight;
break;
}
LayoutUnit x = borderLeft() + paddingLeft();
LayoutUnit maxX = width - borderRight() - paddingRight();
switch (alignment) {
case AlignLeft:
if (currentStyle.isLeftToRightDirection())
x += textIndentOffset;
break;
case AlignCenter:
x = (x + maxX) / 2;
if (currentStyle.isLeftToRightDirection())
x += textIndentOffset / 2;
else
x -= textIndentOffset / 2;
break;
case AlignRight:
x = maxX - caretWidth();
if (!currentStyle.isLeftToRightDirection())
x -= textIndentOffset;
break;
}
x = std::min(x, (maxX - caretWidth()).clampNegativeToZero());
const Font& font = style()->font();
const SimpleFontData* fontData = font.primaryFont();
LayoutUnit height;
// crbug.com/595692 This check should not be needed but sometimes
// primaryFont is null.
if (fontData)
height = LayoutUnit(fontData->getFontMetrics().height());
LayoutUnit verticalSpace =
lineHeight(true, currentStyle.isHorizontalWritingMode() ? HorizontalLine
: VerticalLine,
PositionOfInteriorLineBoxes) -
height;
LayoutUnit y = paddingTop() + borderTop() + (verticalSpace / 2);
return currentStyle.isHorizontalWritingMode()
? LayoutRect(x, y, caretWidth(), height)
: LayoutRect(y, x, height, caretWidth());
}
const LayoutObject* LayoutBoxModelObject::pushMappingToContainer(
const LayoutBoxModelObject* ancestorToStopAt,
LayoutGeometryMap& geometryMap) const {
ASSERT(ancestorToStopAt != this);
bool ancestorSkipped;
LayoutObject* container = this->container(ancestorToStopAt, &ancestorSkipped);
if (!container)
return nullptr;
bool isInline = isLayoutInline();
bool isFixedPos = !isInline && style()->position() == FixedPosition;
bool containsFixedPosition = canContainFixedPositionObjects();
LayoutSize adjustmentForSkippedAncestor;
if (ancestorSkipped) {
// There can't be a transform between paintInvalidationContainer and
// ancestorToStopAt, because transforms create containers, so it should be
// safe to just subtract the delta between the ancestor and
// ancestorToStopAt.
adjustmentForSkippedAncestor =
-ancestorToStopAt->offsetFromAncestorContainer(container);
}
LayoutSize containerOffset = offsetFromContainer(container);
bool offsetDependsOnPoint;
if (isLayoutFlowThread()) {
containerOffset += columnOffset(LayoutPoint());
offsetDependsOnPoint = true;
} else {
offsetDependsOnPoint =
container->style()->isFlippedBlocksWritingMode() && container->isBox();
}
bool preserve3D = container->style()->preserves3D() || style()->preserves3D();
GeometryInfoFlags flags = 0;
if (preserve3D)
flags |= AccumulatingTransform;
if (offsetDependsOnPoint)
flags |= IsNonUniform;
if (isFixedPos)
flags |= IsFixedPosition;
if (containsFixedPosition)
flags |= ContainsFixedPosition;
if (shouldUseTransformFromContainer(container)) {
TransformationMatrix t;
getTransformFromContainer(container, containerOffset, t);
t.translateRight(adjustmentForSkippedAncestor.width().toFloat(),
adjustmentForSkippedAncestor.height().toFloat());
geometryMap.push(this, t, flags, LayoutSize());
} else {
containerOffset += adjustmentForSkippedAncestor;
geometryMap.push(this, containerOffset, flags, LayoutSize());
}
return ancestorSkipped ? ancestorToStopAt : container;
}
void LayoutBoxModelObject::moveChildTo(LayoutBoxModelObject* toBoxModelObject,
LayoutObject* child,
LayoutObject* beforeChild,
bool fullRemoveInsert) {
// We assume that callers have cleared their positioned objects list for child
// moves (!fullRemoveInsert) so the positioned layoutObject maps don't become
// stale. It would be too slow to do the map lookup on each call.
ASSERT(!fullRemoveInsert || !isLayoutBlock() ||
!toLayoutBlock(this)->hasPositionedObjects());
ASSERT(this == child->parent());
ASSERT(!beforeChild || toBoxModelObject == beforeChild->parent());
// If a child is moving from a block-flow to an inline-flow parent then any
// floats currently intruding into the child can no longer do so. This can
// happen if a block becomes floating or out-of-flow and is moved to an
// anonymous block. Remove all floats from their float-lists immediately as
// markAllDescendantsWithFloatsForLayout won't attempt to remove floats from
// parents that have inline-flow if we try later.
if (child->isLayoutBlockFlow() && toBoxModelObject->childrenInline() &&
!childrenInline()) {
toLayoutBlockFlow(child)->removeFloatingObjectsFromDescendants();
ASSERT(!toLayoutBlockFlow(child)->containsFloats());
}
if (fullRemoveInsert && isLayoutBlock() && child->isBox())
toLayoutBox(child)->removeFromPercentHeightContainer();
if (fullRemoveInsert && (toBoxModelObject->isLayoutBlock() ||
toBoxModelObject->isLayoutInline())) {
// Takes care of adding the new child correctly if toBlock and fromBlock
// have different kind of children (block vs inline).
toBoxModelObject->addChild(virtualChildren()->removeChildNode(this, child),
beforeChild);
} else {
toBoxModelObject->virtualChildren()->insertChildNode(
toBoxModelObject,
virtualChildren()->removeChildNode(this, child, fullRemoveInsert),
beforeChild, fullRemoveInsert);
}
}
void LayoutBoxModelObject::moveChildrenTo(
LayoutBoxModelObject* toBoxModelObject,
LayoutObject* startChild,
LayoutObject* endChild,
LayoutObject* beforeChild,
bool fullRemoveInsert) {
// This condition is rarely hit since this function is usually called on
// anonymous blocks which can no longer carry positioned objects (see r120761)
// or when fullRemoveInsert is false.
if (fullRemoveInsert && isLayoutBlock()) {
LayoutBlock* block = toLayoutBlock(this);
block->removePositionedObjects(nullptr);
block->removeFromPercentHeightContainer();
if (block->isLayoutBlockFlow())
toLayoutBlockFlow(block)->removeFloatingObjects();
}
ASSERT(!beforeChild || toBoxModelObject == beforeChild->parent());
for (LayoutObject* child = startChild; child && child != endChild;) {
// Save our next sibling as moveChildTo will clear it.
LayoutObject* nextSibling = child->nextSibling();
moveChildTo(toBoxModelObject, child, beforeChild, fullRemoveInsert);
child = nextSibling;
}
}
bool LayoutBoxModelObject::backgroundStolenForBeingBody(
const ComputedStyle* rootElementStyle) const {
// http://www.w3.org/TR/css3-background/#body-background
// If the root element is <html> with no background, and a <body> child
// element exists, the root element steals the first <body> child element's
// background.
if (!isBody())
return false;
Element* rootElement = document().documentElement();
if (!isHTMLHtmlElement(rootElement))
return false;
if (!rootElementStyle)
rootElementStyle = rootElement->ensureComputedStyle();
if (rootElementStyle->hasBackground())
return false;
if (node() != document().firstBodyElement())
return false;
return true;
}
} // namespace blink