blob: 0b1aaf729641d2b4848657af365fb33d9982265b [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2007 David Smith (catfish.man@gmail.com)
* Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc.
* All rights reserved.
* Copyright (C) Research In Motion Limited 2010. 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/LayoutBlock.h"
#include "core/HTMLNames.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/StyleEngine.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/editing/DragCaret.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/FrameSelection.h"
#include "core/frame/FrameView.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLMarqueeElement.h"
#include "core/layout/HitTestLocation.h"
#include "core/layout/HitTestResult.h"
#include "core/layout/LayoutAnalyzer.h"
#include "core/layout/LayoutFlexibleBox.h"
#include "core/layout/LayoutFlowThread.h"
#include "core/layout/LayoutGrid.h"
#include "core/layout/LayoutMultiColumnSpannerPlaceholder.h"
#include "core/layout/LayoutTableCell.h"
#include "core/layout/LayoutTheme.h"
#include "core/layout/LayoutView.h"
#include "core/layout/TextAutosizer.h"
#include "core/layout/api/LineLayoutBox.h"
#include "core/layout/api/LineLayoutItem.h"
#include "core/layout/line/InlineTextBox.h"
#include "core/page/Page.h"
#include "core/paint/BlockPaintInvalidator.h"
#include "core/paint/BlockPainter.h"
#include "core/paint/ObjectPaintInvalidator.h"
#include "core/paint/PaintLayer.h"
#include "core/style/ComputedStyle.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "wtf/PtrUtil.h"
#include "wtf/StdLibExtras.h"
#include <memory>
namespace blink {
struct SameSizeAsLayoutBlock : public LayoutBox {
LayoutObjectChildList children;
uint32_t bitfields;
};
static_assert(sizeof(LayoutBlock) == sizeof(SameSizeAsLayoutBlock),
"LayoutBlock should stay small");
// This map keeps track of the positioned objects associated with a containing
// block.
//
// This map is populated during layout. It is kept across layouts to handle
// that we skip unchanged sub-trees during layout, in such a way that we are
// able to lay out deeply nested out-of-flow descendants if their containing
// block got laid out. The map could be invalidated during style change but
// keeping track of containing blocks at that time is complicated (we are in
// the middle of recomputing the style so we can't rely on any of its
// information), which is why it's easier to just update it for every layout.
static TrackedDescendantsMap* gPositionedDescendantsMap = nullptr;
static TrackedContainerMap* gPositionedContainerMap = nullptr;
// This map keeps track of the descendants whose 'height' is percentage
// associated with a containing block. Like |gPositionedDescendantsMap|, it is
// also recomputed for every layout (see the comment above about why).
static TrackedDescendantsMap* gPercentHeightDescendantsMap = nullptr;
LayoutBlock::LayoutBlock(ContainerNode* node)
: LayoutBox(node),
m_hasMarginBeforeQuirk(false),
m_hasMarginAfterQuirk(false),
m_beingDestroyed(false),
m_hasMarkupTruncation(false),
m_widthAvailableToChildrenChanged(false),
m_heightAvailableToChildrenChanged(false),
m_isSelfCollapsing(false),
m_descendantsWithFloatsMarkedForLayout(false),
m_hasPositionedObjects(false),
m_hasPercentHeightDescendants(false),
m_paginationStateChanged(false) {
// LayoutBlockFlow calls setChildrenInline(true).
// By default, subclasses do not have inline children.
}
void LayoutBlock::removeFromGlobalMaps() {
if (hasPositionedObjects()) {
std::unique_ptr<TrackedLayoutBoxListHashSet> descendants =
gPositionedDescendantsMap->take(this);
ASSERT(!descendants->isEmpty());
for (LayoutBox* descendant : *descendants) {
ASSERT(gPositionedContainerMap->at(descendant) == this);
gPositionedContainerMap->erase(descendant);
}
}
if (hasPercentHeightDescendants()) {
std::unique_ptr<TrackedLayoutBoxListHashSet> descendants =
gPercentHeightDescendantsMap->take(this);
ASSERT(!descendants->isEmpty());
for (LayoutBox* descendant : *descendants) {
ASSERT(descendant->percentHeightContainer() == this);
descendant->setPercentHeightContainer(nullptr);
}
}
}
LayoutBlock::~LayoutBlock() {
removeFromGlobalMaps();
}
void LayoutBlock::willBeDestroyed() {
if (!documentBeingDestroyed() && parent())
parent()->dirtyLinesFromChangedChild(this);
if (LocalFrame* frame = this->frame()) {
frame->selection().layoutBlockWillBeDestroyed(*this);
frame->page()->dragCaret().layoutBlockWillBeDestroyed(*this);
}
if (TextAutosizer* textAutosizer = document().textAutosizer())
textAutosizer->destroy(this);
LayoutBox::willBeDestroyed();
}
void LayoutBlock::styleWillChange(StyleDifference diff,
const ComputedStyle& newStyle) {
const ComputedStyle* oldStyle = style();
setIsAtomicInlineLevel(newStyle.isDisplayInlineType());
if (oldStyle && parent()) {
bool oldStyleContainsFixedPosition =
oldStyle->canContainFixedPositionObjects();
bool oldStyleContainsAbsolutePosition =
oldStyleContainsFixedPosition ||
oldStyle->canContainAbsolutePositionObjects();
bool newStyleContainsFixedPosition =
newStyle.canContainFixedPositionObjects();
bool newStyleContainsAbsolutePosition =
newStyleContainsFixedPosition ||
newStyle.canContainAbsolutePositionObjects();
if ((oldStyleContainsFixedPosition && !newStyleContainsFixedPosition) ||
(oldStyleContainsAbsolutePosition &&
!newStyleContainsAbsolutePosition)) {
// Clear our positioned objects list. Our absolute and fixed positioned
// descendants will be inserted into our containing block's positioned
// objects list during layout.
removePositionedObjects(nullptr, NewContainingBlock);
}
if (!oldStyleContainsAbsolutePosition && newStyleContainsAbsolutePosition) {
// Remove our absolutely positioned descendants from their current
// containing block.
// They will be inserted into our positioned objects list during layout.
if (LayoutBlock* cb = containingBlockForAbsolutePosition())
cb->removePositionedObjects(this, NewContainingBlock);
}
if (!oldStyleContainsFixedPosition && newStyleContainsFixedPosition) {
// Remove our fixed positioned descendants from their current containing
// block.
// They will be inserted into our positioned objects list during layout.
if (LayoutBlock* cb = containerForFixedPosition())
cb->removePositionedObjects(this, NewContainingBlock);
}
}
LayoutBox::styleWillChange(diff, newStyle);
}
enum LogicalExtent { LogicalWidth, LogicalHeight };
static bool borderOrPaddingLogicalDimensionChanged(
const ComputedStyle& oldStyle,
const ComputedStyle& newStyle,
LogicalExtent logicalExtent) {
if (newStyle.isHorizontalWritingMode() == (logicalExtent == LogicalWidth)) {
return oldStyle.borderLeftWidth() != newStyle.borderLeftWidth() ||
oldStyle.borderRightWidth() != newStyle.borderRightWidth() ||
oldStyle.paddingLeft() != newStyle.paddingLeft() ||
oldStyle.paddingRight() != newStyle.paddingRight();
}
return oldStyle.borderTopWidth() != newStyle.borderTopWidth() ||
oldStyle.borderBottomWidth() != newStyle.borderBottomWidth() ||
oldStyle.paddingTop() != newStyle.paddingTop() ||
oldStyle.paddingBottom() != newStyle.paddingBottom();
}
void LayoutBlock::styleDidChange(StyleDifference diff,
const ComputedStyle* oldStyle) {
LayoutBox::styleDidChange(diff, oldStyle);
const ComputedStyle& newStyle = styleRef();
if (oldStyle && parent()) {
if (oldStyle->position() != newStyle.position() &&
newStyle.position() != EPosition::kStatic) {
// In LayoutObject::styleWillChange() we already removed ourself from our
// old containing block's positioned descendant list, and we will be
// inserted to the new containing block's list during layout. However the
// positioned descendant layout logic assumes layout objects to obey
// parent-child order in the list. Remove our descendants here so they
// will be re-inserted after us.
if (LayoutBlock* cb = containingBlock()) {
cb->removePositionedObjects(this, NewContainingBlock);
if (isOutOfFlowPositioned()) {
// Insert this object into containing block's positioned descendants
// list in case the parent won't layout. This is needed especially
// there are descendants scheduled for overflow recalc.
cb->insertPositionedObject(this);
}
}
}
}
if (TextAutosizer* textAutosizer = document().textAutosizer())
textAutosizer->record(this);
propagateStyleToAnonymousChildren();
// The LayoutView is always a container of fixed positioned descendants. In
// addition, SVG foreignObjects become such containers, so that descendants
// of a foreignObject cannot escape it. Similarly, text controls let authors
// select elements inside that are created by user agent shadow DOM, and we
// have (C++) code that assumes that the elements are indeed contained by the
// text control. So just make sure this is the case. Finally, computed style
// may turn us into a container of all things, e.g. if the element is
// transformed, or contain:paint is specified.
setCanContainFixedPositionObjects(isLayoutView() || isSVGForeignObject() ||
isTextControl() ||
newStyle.canContainFixedPositionObjects());
// It's possible for our border/padding to change, but for the overall logical
// width or height of the block to end up being the same. We keep track of
// this change so in layoutBlock, we can know to set relayoutChildren=true.
m_widthAvailableToChildrenChanged |=
oldStyle && needsLayout() &&
(diff.needsFullLayout() || borderOrPaddingLogicalDimensionChanged(
*oldStyle, newStyle, LogicalWidth));
m_heightAvailableToChildrenChanged |= oldStyle && diff.needsFullLayout() &&
needsLayout() &&
borderOrPaddingLogicalDimensionChanged(
*oldStyle, newStyle, LogicalHeight);
}
void LayoutBlock::updateFromStyle() {
LayoutBox::updateFromStyle();
bool shouldClipOverflow =
!styleRef().isOverflowVisible() && allowsOverflowClip();
if (shouldClipOverflow != hasOverflowClip()) {
if (!shouldClipOverflow)
getScrollableArea()->invalidateAllStickyConstraints();
setMayNeedPaintInvalidationSubtree();
if (RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled()) {
// The overflow clip paint property depends on whether overflow clip is
// present so we need to update paint properties if this changes.
setNeedsPaintPropertyUpdate();
}
}
setHasOverflowClip(shouldClipOverflow);
}
bool LayoutBlock::allowsOverflowClip() const {
// If overflow has been propagated to the viewport, it has no effect here.
return node() != document().viewportDefiningElement();
}
void LayoutBlock::addChildBeforeDescendant(LayoutObject* newChild,
LayoutObject* beforeDescendant) {
ASSERT(beforeDescendant->parent() != this);
LayoutObject* beforeDescendantContainer = beforeDescendant->parent();
while (beforeDescendantContainer->parent() != this)
beforeDescendantContainer = beforeDescendantContainer->parent();
ASSERT(beforeDescendantContainer);
// We really can't go on if what we have found isn't anonymous. We're not
// supposed to use some random non-anonymous object and put the child there.
// That's a recipe for security issues.
CHECK(beforeDescendantContainer->isAnonymous());
// If the requested insertion point is not one of our children, then this is
// because there is an anonymous container within this object that contains
// the beforeDescendant.
if (beforeDescendantContainer->isAnonymousBlock()
// Full screen layoutObjects and full screen placeholders act as anonymous
// blocks, not tables:
|| beforeDescendantContainer->isLayoutFullScreen() ||
beforeDescendantContainer->isLayoutFullScreenPlaceholder()) {
// Insert the child into the anonymous block box instead of here.
if (newChild->isInline() || newChild->isFloatingOrOutOfFlowPositioned() ||
beforeDescendant->parent()->slowFirstChild() != beforeDescendant)
beforeDescendant->parent()->addChild(newChild, beforeDescendant);
else
addChild(newChild, beforeDescendant->parent());
return;
}
ASSERT(beforeDescendantContainer->isTable());
if (newChild->isTablePart()) {
// Insert into the anonymous table.
beforeDescendantContainer->addChild(newChild, beforeDescendant);
return;
}
LayoutObject* beforeChild = splitAnonymousBoxesAroundChild(beforeDescendant);
ASSERT(beforeChild->parent() == this);
if (beforeChild->parent() != this) {
// We should never reach here. If we do, we need to use the
// safe fallback to use the topmost beforeChild container.
beforeChild = beforeDescendantContainer;
}
addChild(newChild, beforeChild);
}
void LayoutBlock::addChild(LayoutObject* newChild, LayoutObject* beforeChild) {
if (beforeChild && beforeChild->parent() != this) {
addChildBeforeDescendant(newChild, beforeChild);
return;
}
// Only LayoutBlockFlow should have inline children, and then we shouldn't be
// here.
ASSERT(!childrenInline());
if (newChild->isInline() || newChild->isFloatingOrOutOfFlowPositioned()) {
// If we're inserting an inline child but all of our children are blocks,
// then we have to make sure it is put into an anomyous block box. We try to
// use an existing anonymous box if possible, otherwise a new one is created
// and inserted into our list of children in the appropriate position.
LayoutObject* afterChild =
beforeChild ? beforeChild->previousSibling() : lastChild();
if (afterChild && afterChild->isAnonymousBlock()) {
afterChild->addChild(newChild);
return;
}
if (newChild->isInline()) {
// No suitable existing anonymous box - create a new one.
LayoutBlock* newBox = createAnonymousBlock();
LayoutBox::addChild(newBox, beforeChild);
newBox->addChild(newChild);
return;
}
}
LayoutBox::addChild(newChild, beforeChild);
}
void LayoutBlock::removeLeftoverAnonymousBlock(LayoutBlock* child) {
ASSERT(child->isAnonymousBlock());
ASSERT(!child->childrenInline());
ASSERT(child->parent() == this);
if (child->continuation())
return;
// Promote all the leftover anonymous block's children (to become children of
// this block instead). We still want to keep the leftover block in the tree
// for a moment, for notification purposes done further below (flow threads
// and grids).
child->moveAllChildrenTo(this, child->nextSibling());
// Remove all the information in the flow thread associated with the leftover
// anonymous block.
child->removeFromLayoutFlowThread();
// LayoutGrid keeps track of its children, we must notify it about changes in
// the tree.
if (child->parent()->isLayoutGrid())
toLayoutGrid(child->parent())->dirtyGrid();
// Now remove the leftover anonymous block from the tree, and destroy it.
// We'll rip it out manually from the tree before destroying it, because we
// don't want to trigger any tree adjustments with regards to anonymous blocks
// (or any other kind of undesired chain-reaction).
children()->removeChildNode(this, child, false);
child->destroy();
}
void LayoutBlock::updateAfterLayout() {
invalidateStickyConstraints();
// Update our scroll information if we're overflow:auto/scroll/hidden now that
// we know if we overflow or not.
if (hasOverflowClip())
layer()->getScrollableArea()->updateAfterLayout();
}
void LayoutBlock::layout() {
DCHECK(!getScrollableArea() || getScrollableArea()->scrollAnchor());
LayoutAnalyzer::Scope analyzer(*this);
bool needsScrollAnchoring =
hasOverflowClip() && getScrollableArea()->shouldPerformScrollAnchoring();
if (needsScrollAnchoring)
getScrollableArea()->scrollAnchor()->notifyBeforeLayout();
// Table cells call layoutBlock directly, so don't add any logic here. Put
// code into layoutBlock().
layoutBlock(false);
// It's safe to check for control clip here, since controls can never be table
// cells. If we have a lightweight clip, there can never be any overflow from
// children.
if (hasControlClip() && m_overflow)
clearLayoutOverflow();
invalidateBackgroundObscurationStatus();
m_heightAvailableToChildrenChanged = false;
}
bool LayoutBlock::widthAvailableToChildrenHasChanged() {
// TODO(robhogan): Does m_widthAvailableToChildrenChanged always get reset
// when it needs to?
bool widthAvailableToChildrenHasChanged = m_widthAvailableToChildrenChanged;
m_widthAvailableToChildrenChanged = false;
// If we use border-box sizing, have percentage padding, and our parent has
// changed width then the width available to our children has changed even
// though our own width has remained the same.
widthAvailableToChildrenHasChanged |=
style()->boxSizing() == EBoxSizing::kBorderBox &&
needsPreferredWidthsRecalculation() &&
view()->layoutState()->containingBlockLogicalWidthChanged();
return widthAvailableToChildrenHasChanged;
}
DISABLE_CFI_PERF
bool LayoutBlock::updateLogicalWidthAndColumnWidth() {
LayoutUnit oldWidth = logicalWidth();
updateLogicalWidth();
return oldWidth != logicalWidth() || widthAvailableToChildrenHasChanged();
}
void LayoutBlock::layoutBlock(bool) {
ASSERT_NOT_REACHED();
clearNeedsLayout();
}
void LayoutBlock::addOverflowFromChildren() {
if (childrenInline())
toLayoutBlockFlow(this)->addOverflowFromInlineChildren();
else
addOverflowFromBlockChildren();
}
DISABLE_CFI_PERF
void LayoutBlock::computeOverflow(LayoutUnit oldClientAfterEdge, bool) {
m_overflow.reset();
// Add overflow from children.
addOverflowFromChildren();
// Add in the overflow from positioned objects.
addOverflowFromPositionedObjects();
if (hasOverflowClip()) {
// When we have overflow clip, propagate the original spillout since it will
// include collapsed bottom margins and bottom padding. Set the axis we
// don't care about to be 1, since we want this overflow to always be
// considered reachable.
LayoutRect clientRect(noOverflowRect());
LayoutRect rectToApply;
if (isHorizontalWritingMode())
rectToApply = LayoutRect(
clientRect.x(), clientRect.y(), LayoutUnit(1),
(oldClientAfterEdge - clientRect.y()).clampNegativeToZero());
else
rectToApply = LayoutRect(
clientRect.x(), clientRect.y(),
(oldClientAfterEdge - clientRect.x()).clampNegativeToZero(),
LayoutUnit(1));
addLayoutOverflow(rectToApply);
if (hasOverflowModel())
m_overflow->setLayoutClientAfterEdge(oldClientAfterEdge);
}
addVisualEffectOverflow();
addVisualOverflowFromTheme();
// An enclosing composited layer will need to update its bounds if we now
// overflow it.
PaintLayer* layer = enclosingLayer();
if (!needsLayout() && layer->hasCompositedLayerMapping() &&
!layer->visualRect().contains(visualOverflowRect()))
layer->setNeedsCompositingInputsUpdate();
}
void LayoutBlock::addOverflowFromBlockChildren() {
for (LayoutBox* child = firstChildBox(); child;
child = child->nextSiblingBox()) {
if (!child->isFloatingOrOutOfFlowPositioned() && !child->isColumnSpanAll())
addOverflowFromChild(child);
}
}
void LayoutBlock::addOverflowFromPositionedObjects() {
TrackedLayoutBoxListHashSet* positionedDescendants = positionedObjects();
if (!positionedDescendants)
return;
for (auto* positionedObject : *positionedDescendants) {
// Fixed positioned elements don't contribute to layout overflow, since they
// don't scroll with the content.
if (positionedObject->style()->position() != EPosition::kFixed)
addOverflowFromChild(positionedObject,
toLayoutSize(positionedObject->location()));
}
}
void LayoutBlock::addVisualOverflowFromTheme() {
if (!style()->hasAppearance())
return;
IntRect inflatedRect = pixelSnappedBorderBoxRect();
LayoutTheme::theme().addVisualOverflow(*this, inflatedRect);
addSelfVisualOverflow(LayoutRect(inflatedRect));
}
DISABLE_CFI_PERF
bool LayoutBlock::createsNewFormattingContext() const {
return isInlineBlockOrInlineTable() || isFloatingOrOutOfFlowPositioned() ||
hasOverflowClip() || isFlexItemIncludingDeprecated() ||
style()->specifiesColumns() || isLayoutFlowThread() || isTableCell() ||
isTableCaption() || isFieldset() || isWritingModeRoot() ||
isDocumentElement() || isColumnSpanAll() || isGridItem() ||
style()->containsPaint() || style()->containsLayout() ||
isSVGForeignObject() || style()->display() == EDisplay::kFlowRoot;
}
static inline bool changeInAvailableLogicalHeightAffectsChild(
LayoutBlock* parent,
LayoutBox& child) {
if (parent->style()->boxSizing() != EBoxSizing::kBorderBox)
return false;
return parent->style()->isHorizontalWritingMode() &&
!child.style()->isHorizontalWritingMode();
}
void LayoutBlock::updateBlockChildDirtyBitsBeforeLayout(bool relayoutChildren,
LayoutBox& child) {
if (child.isOutOfFlowPositioned()) {
// It's rather useless to mark out-of-flow children at this point. We may
// not be their containing block (and if we are, it's just pure luck), so
// this would be the wrong place for it. Furthermore, it would cause trouble
// for out-of-flow descendants of column spanners, if the containing block
// is outside the spanner but inside the multicol container.
return;
}
// FIXME: Technically percentage height objects only need a relayout if their
// percentage isn't going to be turned into an auto value. Add a method to
// determine this, so that we can avoid the relayout.
bool hasRelativeLogicalHeight =
child.hasRelativeLogicalHeight() ||
(child.isAnonymous() && this->hasRelativeLogicalHeight()) ||
child.stretchesToViewport();
if (relayoutChildren || (hasRelativeLogicalHeight && !isLayoutView()) ||
(m_heightAvailableToChildrenChanged &&
changeInAvailableLogicalHeightAffectsChild(this, child))) {
child.setChildNeedsLayout(MarkOnlyThis);
// If the child has percentage padding or an embedded content box, we also
// need to invalidate the childs pref widths.
if (child.needsPreferredWidthsRecalculation())
child.setPreferredLogicalWidthsDirty(MarkOnlyThis);
}
}
void LayoutBlock::simplifiedNormalFlowLayout() {
if (childrenInline()) {
SECURITY_DCHECK(isLayoutBlockFlow());
LayoutBlockFlow* blockFlow = toLayoutBlockFlow(this);
blockFlow->simplifiedNormalFlowInlineLayout();
} else {
for (LayoutBox* box = firstChildBox(); box; box = box->nextSiblingBox()) {
if (!box->isOutOfFlowPositioned()) {
if (box->isLayoutMultiColumnSpannerPlaceholder())
toLayoutMultiColumnSpannerPlaceholder(box)
->markForLayoutIfObjectInFlowThreadNeedsLayout();
box->layoutIfNeeded();
}
}
}
}
bool LayoutBlock::simplifiedLayout() {
// Check if we need to do a full layout.
if (normalChildNeedsLayout() || selfNeedsLayout())
return false;
// Check that we actually need to do a simplified layout.
if (!posChildNeedsLayout() &&
!(needsSimplifiedNormalFlowLayout() || needsPositionedMovementLayout()))
return false;
{
// LayoutState needs this deliberate scope to pop before paint invalidation.
LayoutState state(*this);
if (needsPositionedMovementLayout() &&
!tryLayoutDoingPositionedMovementOnly())
return false;
if (LayoutFlowThread* flowThread = flowThreadContainingBlock()) {
if (!flowThread->canSkipLayout(*this))
return false;
}
TextAutosizer::LayoutScope textAutosizerLayoutScope(this);
// Lay out positioned descendants or objects that just need to recompute
// overflow.
if (needsSimplifiedNormalFlowLayout())
simplifiedNormalFlowLayout();
// Lay out our positioned objects if our positioned child bit is set.
// Also, if an absolute position element inside a relative positioned
// container moves, and the absolute element has a fixed position child
// neither the fixed element nor its container learn of the movement since
// posChildNeedsLayout() is only marked as far as the relative positioned
// container. So if we can have fixed pos objects in our positioned objects
// list check if any of them are statically positioned and thus need to move
// with their absolute ancestors.
bool canContainFixedPosObjects = canContainFixedPositionObjects();
if (posChildNeedsLayout() || needsPositionedMovementLayout() ||
canContainFixedPosObjects)
layoutPositionedObjects(
false, needsPositionedMovementLayout()
? ForcedLayoutAfterContainingBlockMoved
: (!posChildNeedsLayout() && canContainFixedPosObjects
? LayoutOnlyFixedPositionedObjects
: DefaultLayout));
// Recompute our overflow information.
// FIXME: We could do better here by computing a temporary overflow object
// from layoutPositionedObjects and only updating our overflow if we either
// used to have overflow or if the new temporary object has overflow.
// For now just always recompute overflow. This is no worse performance-wise
// than the old code that called rightmostPosition and lowestPosition on
// every relayout so it's not a regression. computeOverflow expects the
// bottom edge before we clamp our height. Since this information isn't
// available during simplifiedLayout, we cache the value in m_overflow.
LayoutUnit oldClientAfterEdge = hasOverflowModel()
? m_overflow->layoutClientAfterEdge()
: clientLogicalBottom();
computeOverflow(oldClientAfterEdge, true);
}
updateLayerTransformAfterLayout();
updateAfterLayout();
clearNeedsLayout();
if (LayoutAnalyzer* analyzer = frameView()->layoutAnalyzer())
analyzer->increment(LayoutAnalyzer::LayoutObjectsThatNeedSimplifiedLayout);
return true;
}
void LayoutBlock::markFixedPositionObjectForLayoutIfNeeded(
LayoutObject* child,
SubtreeLayoutScope& layoutScope) {
if (child->style()->position() != EPosition::kFixed)
return;
bool hasStaticBlockPosition =
child->style()->hasStaticBlockPosition(isHorizontalWritingMode());
bool hasStaticInlinePosition =
child->style()->hasStaticInlinePosition(isHorizontalWritingMode());
if (!hasStaticBlockPosition && !hasStaticInlinePosition)
return;
LayoutObject* o = child->parent();
while (o && !o->isLayoutView() &&
o->style()->position() != EPosition::kAbsolute)
o = o->parent();
// The LayoutView is absolute-positioned, but does not move.
if (o->isLayoutView())
return;
// We must compute child's width and height, but not update them now.
// The child will update its width and height when it gets laid out, and needs
// to see them change there.
LayoutBox* box = toLayoutBox(child);
if (hasStaticInlinePosition) {
LogicalExtentComputedValues computedValues;
box->computeLogicalWidth(computedValues);
LayoutUnit newLeft = computedValues.m_position;
if (newLeft != box->logicalLeft())
layoutScope.setChildNeedsLayout(child);
}
if (hasStaticBlockPosition) {
LogicalExtentComputedValues computedValues;
box->computeLogicalHeight(computedValues);
LayoutUnit newTop = computedValues.m_position;
if (newTop != box->logicalTop())
layoutScope.setChildNeedsLayout(child);
}
}
LayoutUnit LayoutBlock::marginIntrinsicLogicalWidthForChild(
const LayoutBox& child) const {
// A margin has three types: fixed, percentage, and auto (variable).
// Auto and percentage margins become 0 when computing min/max width.
// Fixed margins can be added in as is.
Length marginLeft = child.style()->marginStartUsing(style());
Length marginRight = child.style()->marginEndUsing(style());
LayoutUnit margin;
if (marginLeft.isFixed())
margin += marginLeft.value();
if (marginRight.isFixed())
margin += marginRight.value();
return margin;
}
static bool needsLayoutDueToStaticPosition(LayoutBox* child) {
// When a non-positioned block element moves, it may have positioned children
// that are implicitly positioned relative to the non-positioned block.
const ComputedStyle* style = child->style();
bool isHorizontal = style->isHorizontalWritingMode();
if (style->hasStaticBlockPosition(isHorizontal)) {
LayoutBox::LogicalExtentComputedValues computedValues;
LayoutUnit currentLogicalTop = child->logicalTop();
LayoutUnit currentLogicalHeight = child->logicalHeight();
child->computeLogicalHeight(currentLogicalHeight, currentLogicalTop,
computedValues);
if (computedValues.m_position != currentLogicalTop ||
computedValues.m_extent != currentLogicalHeight)
return true;
}
if (style->hasStaticInlinePosition(isHorizontal)) {
LayoutBox::LogicalExtentComputedValues computedValues;
LayoutUnit currentLogicalLeft = child->logicalLeft();
LayoutUnit currentLogicalWidth = child->logicalWidth();
child->computeLogicalWidth(computedValues);
if (computedValues.m_position != currentLogicalLeft ||
computedValues.m_extent != currentLogicalWidth)
return true;
}
return false;
}
void LayoutBlock::layoutPositionedObjects(bool relayoutChildren,
PositionedLayoutBehavior info) {
TrackedLayoutBoxListHashSet* positionedDescendants = positionedObjects();
if (!positionedDescendants)
return;
for (auto* positionedObject : *positionedDescendants) {
layoutPositionedObject(positionedObject, relayoutChildren, info);
}
}
void LayoutBlock::layoutPositionedObject(LayoutBox* positionedObject,
bool relayoutChildren,
PositionedLayoutBehavior info) {
positionedObject->setMayNeedPaintInvalidation();
SubtreeLayoutScope layoutScope(*positionedObject);
// If positionedObject is fixed-positioned and moves with an absolute-
// positioned ancestor (other than the LayoutView, which cannot move),
// mark it for layout now.
markFixedPositionObjectForLayoutIfNeeded(positionedObject, layoutScope);
if (info == LayoutOnlyFixedPositionedObjects) {
positionedObject->layoutIfNeeded();
return;
}
if (!positionedObject->normalChildNeedsLayout() &&
(relayoutChildren || m_heightAvailableToChildrenChanged ||
needsLayoutDueToStaticPosition(positionedObject)))
layoutScope.setChildNeedsLayout(positionedObject);
// If relayoutChildren is set and the child has percentage padding or an
// embedded content box, we also need to invalidate the childs pref widths.
if (relayoutChildren && positionedObject->needsPreferredWidthsRecalculation())
positionedObject->setPreferredLogicalWidthsDirty(MarkOnlyThis);
LayoutUnit logicalTopEstimate;
bool isPaginated = view()->layoutState()->isPaginated();
bool needsBlockDirectionLocationSetBeforeLayout =
isPaginated &&
positionedObject->getPaginationBreakability() != ForbidBreaks;
if (needsBlockDirectionLocationSetBeforeLayout) {
// Out-of-flow objects are normally positioned after layout (while in-flow
// objects are positioned before layout). If the child object is paginated
// in the same context as we are, estimate its logical top now. We need to
// know this up-front, to correctly evaluate if we need to mark for
// relayout, and, if our estimate is correct, we'll even be able to insert
// correct pagination struts on the first attempt.
LogicalExtentComputedValues computedValues;
positionedObject->computeLogicalHeight(positionedObject->logicalHeight(),
positionedObject->logicalTop(),
computedValues);
logicalTopEstimate = computedValues.m_position;
positionedObject->setLogicalTop(logicalTopEstimate);
}
if (!positionedObject->needsLayout())
markChildForPaginationRelayoutIfNeeded(*positionedObject, layoutScope);
// FIXME: We should be able to do a r->setNeedsPositionedMovementLayout()
// here instead of a full layout. Need to investigate why it does not
// trigger the correct invalidations in that case. crbug.com/350756
if (info == ForcedLayoutAfterContainingBlockMoved) {
positionedObject->setNeedsLayout(LayoutInvalidationReason::AncestorMoved,
MarkOnlyThis);
}
positionedObject->layoutIfNeeded();
LayoutObject* parent = positionedObject->parent();
bool layoutChanged = false;
if (parent->isFlexibleBox() &&
toLayoutFlexibleBox(parent)->setStaticPositionForPositionedLayout(
*positionedObject)) {
// The static position of an abspos child of a flexbox depends on its size
// (for example, they can be centered). So we may have to reposition the
// item after layout.
// TODO(cbiesinger): We could probably avoid a layout here and just
// reposition?
positionedObject->forceChildLayout();
layoutChanged = true;
}
// Lay out again if our estimate was wrong.
if (!layoutChanged && needsBlockDirectionLocationSetBeforeLayout &&
logicalTopEstimate != logicalTopForChild(*positionedObject))
positionedObject->forceChildLayout();
if (isPaginated)
updateFragmentationInfoForChild(*positionedObject);
}
void LayoutBlock::markPositionedObjectsForLayout() {
if (TrackedLayoutBoxListHashSet* positionedDescendants =
positionedObjects()) {
for (auto* descendant : *positionedDescendants)
descendant->setChildNeedsLayout();
}
}
void LayoutBlock::paint(const PaintInfo& paintInfo,
const LayoutPoint& paintOffset) const {
BlockPainter(*this).paint(paintInfo, paintOffset);
}
void LayoutBlock::paintChildren(const PaintInfo& paintInfo,
const LayoutPoint& paintOffset) const {
BlockPainter(*this).paintChildren(paintInfo, paintOffset);
}
void LayoutBlock::paintObject(const PaintInfo& paintInfo,
const LayoutPoint& paintOffset) const {
BlockPainter(*this).paintObject(paintInfo, paintOffset);
}
bool LayoutBlock::isSelectionRoot() const {
if (isPseudoElement())
return false;
ASSERT(node() || isAnonymous());
// FIXME: Eventually tables should have to learn how to fill gaps between
// cells, at least in simple non-spanning cases.
if (isTable())
return false;
if (isBody() || isDocumentElement() || hasOverflowClip() || isPositioned() ||
isFloating() || isTableCell() || isInlineBlockOrInlineTable() ||
hasTransformRelatedProperty() || hasReflection() || hasMask() ||
isWritingModeRoot() || isLayoutFlowThread() ||
isFlexItemIncludingDeprecated())
return true;
if (view() && view()->selectionStart()) {
Node* startElement = view()->selectionStart()->node();
if (startElement && rootEditableElement(*startElement) == node())
return true;
}
return false;
}
LayoutUnit LayoutBlock::blockDirectionOffset(
const LayoutSize& offsetFromBlock) const {
return isHorizontalWritingMode() ? offsetFromBlock.height()
: offsetFromBlock.width();
}
LayoutUnit LayoutBlock::inlineDirectionOffset(
const LayoutSize& offsetFromBlock) const {
return isHorizontalWritingMode() ? offsetFromBlock.width()
: offsetFromBlock.height();
}
LayoutUnit LayoutBlock::logicalLeftSelectionOffset(const LayoutBlock* rootBlock,
LayoutUnit position) const {
// The border can potentially be further extended by our containingBlock().
if (rootBlock != this)
return containingBlock()->logicalLeftSelectionOffset(
rootBlock, position + logicalTop());
return logicalLeftOffsetForContent();
}
LayoutUnit LayoutBlock::logicalRightSelectionOffset(
const LayoutBlock* rootBlock,
LayoutUnit position) const {
// The border can potentially be further extended by our containingBlock().
if (rootBlock != this)
return containingBlock()->logicalRightSelectionOffset(
rootBlock, position + logicalTop());
return logicalRightOffsetForContent();
}
LayoutBlock* LayoutBlock::blockBeforeWithinSelectionRoot(
LayoutSize& offset) const {
if (isSelectionRoot())
return nullptr;
const LayoutObject* object = this;
LayoutObject* sibling;
do {
sibling = object->previousSibling();
while (sibling && (!sibling->isLayoutBlock() ||
toLayoutBlock(sibling)->isSelectionRoot()))
sibling = sibling->previousSibling();
offset -= LayoutSize(toLayoutBlock(object)->logicalLeft(),
toLayoutBlock(object)->logicalTop());
object = object->parent();
} while (!sibling && object && object->isLayoutBlock() &&
!toLayoutBlock(object)->isSelectionRoot());
if (!sibling)
return nullptr;
LayoutBlock* beforeBlock = toLayoutBlock(sibling);
offset += LayoutSize(beforeBlock->logicalLeft(), beforeBlock->logicalTop());
LayoutObject* child = beforeBlock->lastChild();
while (child && child->isLayoutBlock()) {
beforeBlock = toLayoutBlock(child);
offset += LayoutSize(beforeBlock->logicalLeft(), beforeBlock->logicalTop());
child = beforeBlock->lastChild();
}
return beforeBlock;
}
void LayoutBlock::setSelectionState(SelectionState state) {
LayoutBox::setSelectionState(state);
if (inlineBoxWrapper() && canUpdateSelectionOnRootLineBoxes())
inlineBoxWrapper()->root().setHasSelectedChildren(state != SelectionNone);
}
TrackedLayoutBoxListHashSet* LayoutBlock::positionedObjectsInternal() const {
return gPositionedDescendantsMap ? gPositionedDescendantsMap->at(this)
: nullptr;
}
void LayoutBlock::insertPositionedObject(LayoutBox* o) {
ASSERT(!isAnonymousBlock());
ASSERT(o->containingBlock() == this);
if (gPositionedContainerMap) {
auto containerMapIt = gPositionedContainerMap->find(o);
if (containerMapIt != gPositionedContainerMap->end()) {
if (containerMapIt->value == this) {
ASSERT(hasPositionedObjects() && positionedObjects()->contains(o));
return;
}
removePositionedObject(o);
}
} else {
gPositionedContainerMap = new TrackedContainerMap;
}
gPositionedContainerMap->set(o, this);
if (!gPositionedDescendantsMap)
gPositionedDescendantsMap = new TrackedDescendantsMap;
TrackedLayoutBoxListHashSet* descendantSet =
gPositionedDescendantsMap->at(this);
if (!descendantSet) {
descendantSet = new TrackedLayoutBoxListHashSet;
gPositionedDescendantsMap->set(this, WTF::wrapUnique(descendantSet));
}
descendantSet->insert(o);
m_hasPositionedObjects = true;
}
void LayoutBlock::removePositionedObject(LayoutBox* o) {
if (!gPositionedContainerMap)
return;
LayoutBlock* container = gPositionedContainerMap->take(o);
if (!container)
return;
TrackedLayoutBoxListHashSet* positionedDescendants =
gPositionedDescendantsMap->at(container);
ASSERT(positionedDescendants && positionedDescendants->contains(o));
positionedDescendants->erase(o);
if (positionedDescendants->isEmpty()) {
gPositionedDescendantsMap->erase(container);
container->m_hasPositionedObjects = false;
}
}
PaintInvalidationReason LayoutBlock::invalidatePaintIfNeeded(
const PaintInvalidationState& paintInvalidationState) {
return LayoutBox::invalidatePaintIfNeeded(paintInvalidationState);
}
PaintInvalidationReason LayoutBlock::invalidatePaintIfNeeded(
const PaintInvalidatorContext& context) const {
return BlockPaintInvalidator(*this).invalidatePaintIfNeeded(context);
}
void LayoutBlock::clearPreviousVisualRects() {
LayoutBox::clearPreviousVisualRects();
BlockPaintInvalidator(*this).clearPreviousVisualRects();
}
void LayoutBlock::removePositionedObjects(
LayoutObject* o,
ContainingBlockState containingBlockState) {
TrackedLayoutBoxListHashSet* positionedDescendants = positionedObjects();
if (!positionedDescendants)
return;
Vector<LayoutBox*, 16> deadObjects;
for (auto* positionedObject : *positionedDescendants) {
if (!o || (positionedObject->isDescendantOf(o) && o != positionedObject)) {
if (containingBlockState == NewContainingBlock) {
positionedObject->setChildNeedsLayout(MarkOnlyThis);
if (positionedObject->needsPreferredWidthsRecalculation())
positionedObject->setPreferredLogicalWidthsDirty(MarkOnlyThis);
// The positioned object changing containing block may change paint
// invalidation container.
// Invalidate it (including non-compositing descendants) on its original
// paint invalidation container.
if (!RuntimeEnabledFeatures::slimmingPaintV2Enabled()) {
// This valid because we need to invalidate based on the current
// status.
DisableCompositingQueryAsserts compositingDisabler;
if (!positionedObject->isPaintInvalidationContainer())
ObjectPaintInvalidator(*positionedObject)
.invalidatePaintIncludingNonCompositingDescendants();
}
}
// It is parent blocks job to add positioned child to positioned objects
// list of its containing block
// Parent layout needs to be invalidated to ensure this happens.
LayoutObject* p = positionedObject->parent();
while (p && !p->isLayoutBlock())
p = p->parent();
if (p)
p->setChildNeedsLayout();
deadObjects.push_back(positionedObject);
}
}
for (auto object : deadObjects) {
ASSERT(gPositionedContainerMap->at(object) == this);
positionedDescendants->erase(object);
gPositionedContainerMap->erase(object);
}
if (positionedDescendants->isEmpty()) {
gPositionedDescendantsMap->erase(this);
m_hasPositionedObjects = false;
}
}
void LayoutBlock::addPercentHeightDescendant(LayoutBox* descendant) {
if (descendant->percentHeightContainer()) {
if (descendant->percentHeightContainer() == this) {
ASSERT(hasPercentHeightDescendant(descendant));
return;
}
descendant->removeFromPercentHeightContainer();
}
descendant->setPercentHeightContainer(this);
if (!gPercentHeightDescendantsMap)
gPercentHeightDescendantsMap = new TrackedDescendantsMap;
TrackedLayoutBoxListHashSet* descendantSet =
gPercentHeightDescendantsMap->at(this);
if (!descendantSet) {
descendantSet = new TrackedLayoutBoxListHashSet;
gPercentHeightDescendantsMap->set(this, WTF::wrapUnique(descendantSet));
}
descendantSet->insert(descendant);
m_hasPercentHeightDescendants = true;
}
void LayoutBlock::removePercentHeightDescendant(LayoutBox* descendant) {
if (TrackedLayoutBoxListHashSet* descendants = percentHeightDescendants()) {
descendants->erase(descendant);
descendant->setPercentHeightContainer(nullptr);
if (descendants->isEmpty()) {
gPercentHeightDescendantsMap->erase(this);
m_hasPercentHeightDescendants = false;
}
}
}
TrackedLayoutBoxListHashSet* LayoutBlock::percentHeightDescendantsInternal()
const {
return gPercentHeightDescendantsMap ? gPercentHeightDescendantsMap->at(this)
: nullptr;
}
void LayoutBlock::dirtyForLayoutFromPercentageHeightDescendants(
SubtreeLayoutScope& layoutScope) {
TrackedLayoutBoxListHashSet* descendants = percentHeightDescendants();
if (!descendants)
return;
for (auto* box : *descendants) {
ASSERT(box->isDescendantOf(this));
while (box != this) {
if (box->normalChildNeedsLayout())
break;
layoutScope.setChildNeedsLayout(box);
box = box->containingBlock();
ASSERT(box);
if (!box)
break;
}
}
}
LayoutUnit LayoutBlock::textIndentOffset() const {
LayoutUnit cw;
if (style()->textIndent().isPercentOrCalc())
cw = containingBlock()->availableLogicalWidth();
return minimumValueForLength(style()->textIndent(), cw);
}
bool LayoutBlock::isPointInOverflowControl(
HitTestResult& result,
const LayoutPoint& locationInContainer,
const LayoutPoint& accumulatedOffset) const {
if (!scrollsOverflow())
return false;
return layer()->getScrollableArea()->hitTestOverflowControls(
result,
roundedIntPoint(locationInContainer - toLayoutSize(accumulatedOffset)));
}
bool LayoutBlock::hitTestOverflowControl(
HitTestResult& result,
const HitTestLocation& locationInContainer,
const LayoutPoint& adjustedLocation) {
if (visibleToHitTestRequest(result.hitTestRequest()) &&
isPointInOverflowControl(result, locationInContainer.point(),
adjustedLocation)) {
updateHitTestResult(
result, locationInContainer.point() - toLayoutSize(adjustedLocation));
// FIXME: isPointInOverflowControl() doesn't handle rect-based tests yet.
if (result.addNodeToListBasedTestResult(
nodeForHitTest(), locationInContainer) == StopHitTesting)
return true;
}
return false;
}
bool LayoutBlock::hitTestChildren(HitTestResult& result,
const HitTestLocation& locationInContainer,
const LayoutPoint& accumulatedOffset,
HitTestAction hitTestAction) {
ASSERT(!childrenInline());
LayoutPoint scrolledOffset(hasOverflowClip()
? accumulatedOffset - scrolledContentOffset()
: accumulatedOffset);
HitTestAction childHitTest = hitTestAction;
if (hitTestAction == HitTestChildBlockBackgrounds)
childHitTest = HitTestChildBlockBackground;
for (LayoutBox* child = lastChildBox(); child;
child = child->previousSiblingBox()) {
LayoutPoint childPoint = flipForWritingModeForChild(child, scrolledOffset);
if (!child->hasSelfPaintingLayer() && !child->isFloating() &&
!child->isColumnSpanAll() &&
child->nodeAtPoint(result, locationInContainer, childPoint,
childHitTest)) {
updateHitTestResult(
result, flipForWritingMode(toLayoutPoint(locationInContainer.point() -
accumulatedOffset)));
return true;
}
}
return false;
}
Position LayoutBlock::positionForBox(InlineBox* box, bool start) const {
if (!box)
return Position();
if (!box->getLineLayoutItem().nonPseudoNode())
return Position::editingPositionOf(
nonPseudoNode(), start ? caretMinOffset() : caretMaxOffset());
if (!box->isInlineTextBox())
return Position::editingPositionOf(
box->getLineLayoutItem().nonPseudoNode(),
start ? box->getLineLayoutItem().caretMinOffset()
: box->getLineLayoutItem().caretMaxOffset());
InlineTextBox* textBox = toInlineTextBox(box);
return Position::editingPositionOf(
box->getLineLayoutItem().nonPseudoNode(),
start ? textBox->start() : textBox->start() + textBox->len());
}
static inline bool isEditingBoundary(LayoutObject* ancestor,
LineLayoutBox child) {
ASSERT(!ancestor || ancestor->nonPseudoNode());
ASSERT(child && child.nonPseudoNode());
return !ancestor || !ancestor->parent() ||
(ancestor->hasLayer() && ancestor->parent()->isLayoutView()) ||
hasEditableStyle(*ancestor->nonPseudoNode()) ==
hasEditableStyle(*child.nonPseudoNode());
}
// FIXME: This function should go on LayoutObject.
// Then all cases in which positionForPoint recurs could call this instead to
// prevent crossing editable boundaries. This would require many tests.
PositionWithAffinity LayoutBlock::positionForPointRespectingEditingBoundaries(
LineLayoutBox child,
const LayoutPoint& pointInParentCoordinates) {
LayoutPoint childLocation = child.location();
if (child.isInFlowPositioned())
childLocation += child.offsetForInFlowPosition();
// FIXME: This is wrong if the child's writing-mode is different from the
// parent's.
LayoutPoint pointInChildCoordinates(
toLayoutPoint(pointInParentCoordinates - childLocation));
// If this is an anonymous layoutObject, we just recur normally
Node* childNode = child.nonPseudoNode();
if (!childNode)
return child.positionForPoint(pointInChildCoordinates);
// Otherwise, first make sure that the editability of the parent and child
// agree. If they don't agree, then we return a visible position just before
// or after the child
LayoutObject* ancestor = this;
while (ancestor && !ancestor->nonPseudoNode())
ancestor = ancestor->parent();
// If we can't find an ancestor to check editability on, or editability is
// unchanged, we recur like normal
if (isEditingBoundary(ancestor, child))
return child.positionForPoint(pointInChildCoordinates);
// Otherwise return before or after the child, depending on if the click was
// to the logical left or logical right of the child
LayoutUnit childMiddle = logicalWidthForChildSize(child.size()) / 2;
LayoutUnit logicalLeft = isHorizontalWritingMode()
? pointInChildCoordinates.x()
: pointInChildCoordinates.y();
if (logicalLeft < childMiddle)
return ancestor->createPositionWithAffinity(childNode->nodeIndex());
return ancestor->createPositionWithAffinity(childNode->nodeIndex() + 1,
TextAffinity::Upstream);
}
PositionWithAffinity LayoutBlock::positionForPointIfOutsideAtomicInlineLevel(
const LayoutPoint& point) {
ASSERT(isAtomicInlineLevel());
// FIXME: This seems wrong when the object's writing-mode doesn't match the
// line's writing-mode.
LayoutUnit pointLogicalLeft =
isHorizontalWritingMode() ? point.x() : point.y();
LayoutUnit pointLogicalTop =
isHorizontalWritingMode() ? point.y() : point.x();
if (pointLogicalLeft < 0)
return createPositionWithAffinity(caretMinOffset());
if (pointLogicalLeft >= logicalWidth())
return createPositionWithAffinity(caretMaxOffset());
if (pointLogicalTop < 0)
return createPositionWithAffinity(caretMinOffset());
if (pointLogicalTop >= logicalHeight())
return createPositionWithAffinity(caretMaxOffset());
return PositionWithAffinity();
}
static inline bool isChildHitTestCandidate(LayoutBox* box) {
return box->size().height() &&
box->style()->visibility() == EVisibility::kVisible &&
!box->isFloatingOrOutOfFlowPositioned() && !box->isLayoutFlowThread();
}
PositionWithAffinity LayoutBlock::positionForPoint(const LayoutPoint& point) {
if (isTable())
return LayoutBox::positionForPoint(point);
if (isAtomicInlineLevel()) {
PositionWithAffinity position =
positionForPointIfOutsideAtomicInlineLevel(point);
if (!position.isNull())
return position;
}
LayoutPoint pointInContents = point;
offsetForContents(pointInContents);
LayoutPoint pointInLogicalContents(pointInContents);
if (!isHorizontalWritingMode())
pointInLogicalContents = pointInLogicalContents.transposedPoint();
ASSERT(!childrenInline());
LayoutBox* lastCandidateBox = lastChildBox();
while (lastCandidateBox && !isChildHitTestCandidate(lastCandidateBox))
lastCandidateBox = lastCandidateBox->previousSiblingBox();
bool blocksAreFlipped = style()->isFlippedBlocksWritingMode();
if (lastCandidateBox) {
if (pointInLogicalContents.y() > logicalTopForChild(*lastCandidateBox) ||
(!blocksAreFlipped &&
pointInLogicalContents.y() == logicalTopForChild(*lastCandidateBox)))
return positionForPointRespectingEditingBoundaries(
LineLayoutBox(lastCandidateBox), pointInContents);
for (LayoutBox* childBox = firstChildBox(); childBox;
childBox = childBox->nextSiblingBox()) {
if (!isChildHitTestCandidate(childBox))
continue;
LayoutUnit childLogicalBottom =
logicalTopForChild(*childBox) + logicalHeightForChild(*childBox);
// We hit child if our click is above the bottom of its padding box (like
// IE6/7 and FF3).
if (pointInLogicalContents.y() < childLogicalBottom ||
(blocksAreFlipped &&
pointInLogicalContents.y() == childLogicalBottom)) {
return positionForPointRespectingEditingBoundaries(
LineLayoutBox(childBox), pointInContents);
}
}
}
// We only get here if there are no hit test candidate children below the
// click.
return LayoutBox::positionForPoint(point);
}
void LayoutBlock::offsetForContents(LayoutPoint& offset) const {
offset = flipForWritingMode(offset);
if (hasOverflowClip())
offset += LayoutSize(scrolledContentOffset());
offset = flipForWritingMode(offset);
}
int LayoutBlock::columnGap() const {
if (style()->hasNormalColumnGap()) {
// "1em" is recommended as the normal gap setting. Matches <p> margins.
return style()->getFontDescription().computedPixelSize();
}
return static_cast<int>(style()->columnGap());
}
void LayoutBlock::scrollbarsChanged(bool horizontalScrollbarChanged,
bool verticalScrollbarChanged,
ScrollbarChangeContext context) {
m_widthAvailableToChildrenChanged |= verticalScrollbarChanged;
m_heightAvailableToChildrenChanged |= horizontalScrollbarChanged;
}
void LayoutBlock::computeIntrinsicLogicalWidths(
LayoutUnit& minLogicalWidth,
LayoutUnit& maxLogicalWidth) const {
// Size-contained elements don't consider their contents for preferred sizing.
if (style()->containsSize())
return;
if (childrenInline()) {
// FIXME: Remove this const_cast.
toLayoutBlockFlow(const_cast<LayoutBlock*>(this))
->computeInlinePreferredLogicalWidths(minLogicalWidth, maxLogicalWidth);
} else {
computeBlockPreferredLogicalWidths(minLogicalWidth, maxLogicalWidth);
}
maxLogicalWidth = std::max(minLogicalWidth, maxLogicalWidth);
if (isHTMLMarqueeElement(node()) &&
toHTMLMarqueeElement(node())->isHorizontal())
minLogicalWidth = LayoutUnit();
if (isTableCell()) {
Length tableCellWidth = toLayoutTableCell(this)->styleOrColLogicalWidth();
if (tableCellWidth.isFixed() && tableCellWidth.value() > 0)
maxLogicalWidth =
std::max(minLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(
LayoutUnit(tableCellWidth.value())));
}
int scrollbarWidth = scrollbarLogicalWidth();
maxLogicalWidth += scrollbarWidth;
minLogicalWidth += scrollbarWidth;
}
DISABLE_CFI_PERF
void LayoutBlock::computePreferredLogicalWidths() {
ASSERT(preferredLogicalWidthsDirty());
m_minPreferredLogicalWidth = LayoutUnit();
m_maxPreferredLogicalWidth = LayoutUnit();
// FIXME: The isFixed() calls here should probably be checking for isSpecified
// since you should be able to use percentage, calc or viewport relative
// values for width.
const ComputedStyle& styleToUse = styleRef();
if (!isTableCell() && styleToUse.logicalWidth().isFixed() &&
styleToUse.logicalWidth().value() >= 0 &&
!(isDeprecatedFlexItem() && !styleToUse.logicalWidth().intValue()))
m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth =
adjustContentBoxLogicalWidthForBoxSizing(
LayoutUnit(styleToUse.logicalWidth().value()));
else
computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth,
m_maxPreferredLogicalWidth);
if (styleToUse.logicalMinWidth().isFixed() &&
styleToUse.logicalMinWidth().value() > 0) {
m_maxPreferredLogicalWidth =
std::max(m_maxPreferredLogicalWidth,
adjustContentBoxLogicalWidthForBoxSizing(
LayoutUnit(styleToUse.logicalMinWidth().value())));
m_minPreferredLogicalWidth =
std::max(m_minPreferredLogicalWidth,
adjustContentBoxLogicalWidthForBoxSizing(
LayoutUnit(styleToUse.logicalMinWidth().value())));
}
if (styleToUse.logicalMaxWidth().isFixed()) {
m_maxPreferredLogicalWidth =
std::min(m_maxPreferredLogicalWidth,
adjustContentBoxLogicalWidthForBoxSizing(
LayoutUnit(styleToUse.logicalMaxWidth().value())));
m_minPreferredLogicalWidth =
std::min(m_minPreferredLogicalWidth,
adjustContentBoxLogicalWidthForBoxSizing(
LayoutUnit(styleToUse.logicalMaxWidth().value())));
}
// Table layout uses integers, ceil the preferred widths to ensure that they
// can contain the contents.
if (isTableCell()) {
m_minPreferredLogicalWidth = LayoutUnit(m_minPreferredLogicalWidth.ceil());
m_maxPreferredLogicalWidth = LayoutUnit(m_maxPreferredLogicalWidth.ceil());
}
LayoutUnit borderAndPadding = borderAndPaddingLogicalWidth();
m_minPreferredLogicalWidth += borderAndPadding;
m_maxPreferredLogicalWidth += borderAndPadding;
clearPreferredLogicalWidthsDirty();
}
void LayoutBlock::computeBlockPreferredLogicalWidths(
LayoutUnit& minLogicalWidth,
LayoutUnit& maxLogicalWidth) const {
const ComputedStyle& styleToUse = styleRef();
bool nowrap = styleToUse.whiteSpace() == EWhiteSpace::kNowrap;
LayoutObject* child = firstChild();
LayoutBlock* containingBlock = this->containingBlock();
LayoutUnit floatLeftWidth, floatRightWidth;
while (child) {
// Positioned children don't affect the min/max width. Spanners only affect
// the min/max width of the multicol container, not the flow thread.
if (child->isOutOfFlowPositioned() || child->isColumnSpanAll()) {
child = child->nextSibling();
continue;
}
RefPtr<ComputedStyle> childStyle = child->mutableStyle();
if (child->isFloating() ||
(child->isBox() && toLayoutBox(child)->avoidsFloats())) {
LayoutUnit floatTotalWidth = floatLeftWidth + floatRightWidth;
if (childStyle->clear() == EClear::kBoth ||
childStyle->clear() == EClear::kLeft) {
maxLogicalWidth = std::max(floatTotalWidth, maxLogicalWidth);
floatLeftWidth = LayoutUnit();
}
if (childStyle->clear() == EClear::kBoth ||
childStyle->clear() == EClear::kRight) {
maxLogicalWidth = std::max(floatTotalWidth, maxLogicalWidth);
floatRightWidth = LayoutUnit();
}
}
// A margin basically has three types: fixed, percentage, and auto
// (variable).
// Auto and percentage margins simply become 0 when computing min/max width.
// Fixed margins can be added in as is.
Length startMarginLength = childStyle->marginStartUsing(&styleToUse);
Length endMarginLength = childStyle->marginEndUsing(&styleToUse);
LayoutUnit margin;
LayoutUnit marginStart;
LayoutUnit marginEnd;
if (startMarginLength.isFixed())
marginStart += startMarginLength.value();
if (endMarginLength.isFixed())
marginEnd += endMarginLength.value();
margin = marginStart + marginEnd;
LayoutUnit childMinPreferredLogicalWidth, childMaxPreferredLogicalWidth;
computeChildPreferredLogicalWidths(*child, childMinPreferredLogicalWidth,
childMaxPreferredLogicalWidth);
LayoutUnit w = childMinPreferredLogicalWidth + margin;
minLogicalWidth = std::max(w, minLogicalWidth);
// IE ignores tables for calculation of nowrap. Makes some sense.
if (nowrap && !child->isTable())
maxLogicalWidth = std::max(w, maxLogicalWidth);
w = childMaxPreferredLogicalWidth + margin;
if (!child->isFloating()) {
if (child->isBox() && toLayoutBox(child)->avoidsFloats()) {
// Determine a left and right max value based off whether or not the
// floats can fit in the margins of the object. For negative margins, we
// will attempt to overlap the float if the negative margin is smaller
// than the float width.
bool ltr = containingBlock
? containingBlock->style()->isLeftToRightDirection()
: styleToUse.isLeftToRightDirection();
LayoutUnit marginLogicalLeft = ltr ? marginStart : marginEnd;
LayoutUnit marginLogicalRight = ltr ? marginEnd : marginStart;
LayoutUnit maxLeft = marginLogicalLeft > 0
? std::max(floatLeftWidth, marginLogicalLeft)
: floatLeftWidth + marginLogicalLeft;
LayoutUnit maxRight =
marginLogicalRight > 0
? std::max(floatRightWidth, marginLogicalRight)
: floatRightWidth + marginLogicalRight;
w = childMaxPreferredLogicalWidth + maxLeft + maxRight;
w = std::max(w, floatLeftWidth + floatRightWidth);
} else {
maxLogicalWidth =
std::max(floatLeftWidth + floatRightWidth, maxLogicalWidth);
}
floatLeftWidth = floatRightWidth = LayoutUnit();
}
if (child->isFloating()) {
if (childStyle->floating() == EFloat::kLeft)
floatLeftWidth += w;
else
floatRightWidth += w;
} else {
maxLogicalWidth = std::max(w, maxLogicalWidth);
}
child = child->nextSibling();
}
// Always make sure these values are non-negative.
minLogicalWidth = minLogicalWidth.clampNegativeToZero();
maxLogicalWidth = maxLogicalWidth.clampNegativeToZero();
maxLogicalWidth = std::max(floatLeftWidth + floatRightWidth, maxLogicalWidth);
}
DISABLE_CFI_PERF
void LayoutBlock::computeChildPreferredLogicalWidths(
LayoutObject& child,
LayoutUnit& minPreferredLogicalWidth,
LayoutUnit& maxPreferredLogicalWidth) const {
if (child.isBox() &&
child.isHorizontalWritingMode() != isHorizontalWritingMode()) {
// If the child is an orthogonal flow, child's height determines the width,
// but the height is not available until layout.
// http://dev.w3.org/csswg/css-writing-modes-3/#orthogonal-shrink-to-fit
if (!child.needsLayout()) {
minPreferredLogicalWidth = maxPreferredLogicalWidth =
toLayoutBox(child).logicalHeight();
return;
}
minPreferredLogicalWidth = maxPreferredLogicalWidth =
toLayoutBox(child).computeLogicalHeightWithoutLayout();
return;
}
minPreferredLogicalWidth = child.minPreferredLogicalWidth();
maxPreferredLogicalWidth = child.maxPreferredLogicalWidth();
// For non-replaced blocks if the inline size is min|max-content or a definite
// size the min|max-content contribution is that size plus border, padding and
// margin https://drafts.csswg.org/css-sizing/#block-intrinsic
if (child.isLayoutBlock()) {
const Length& computedInlineSize = child.styleRef().logicalWidth();
if (computedInlineSize.isMaxContent())
minPreferredLogicalWidth = maxPreferredLogicalWidth;
else if (computedInlineSize.isMinContent())
maxPreferredLogicalWidth = minPreferredLogicalWidth;
}
}
bool LayoutBlock::hasLineIfEmpty() const {
if (!node())
return false;
if (isRootEditableElement(*node()))
return true;
if (node()->isShadowRoot() &&
isHTMLInputElement(toShadowRoot(node())->host()))
return true;
return false;
}
LayoutUnit LayoutBlock::lineHeight(bool firstLine,
LineDirectionMode direction,
LinePositionMode linePositionMode) const {
// Inline blocks are replaced elements. Otherwise, just pass off to
// the base class. If we're being queried as though we're the root line
// box, then the fact that we're an inline-block is irrelevant, and we behave
// just like a block.
if (isAtomicInlineLevel() && linePositionMode == PositionOnContainingLine)
return LayoutBox::lineHeight(firstLine, direction, linePositionMode);
const ComputedStyle& style =
styleRef(firstLine && document().styleEngine().usesFirstLineRules());
return LayoutUnit(style.computedLineHeight());
}
int LayoutBlock::beforeMarginInLineDirection(
LineDirectionMode direction) const {
// InlineFlowBox::placeBoxesInBlockDirection will flip lines in
// case of verticalLR mode, so we can assume verticalRL for now.
return (direction == HorizontalLine ? marginTop() : marginRight()).toInt();
}
int LayoutBlock::baselinePosition(FontBaseline baselineType,
bool firstLine,
LineDirectionMode direction,
LinePositionMode linePositionMode) const {
// Inline blocks are replaced elements. Otherwise, just pass off to
// the base class. If we're being queried as though we're the root line
// box, then the fact that we're an inline-block is irrelevant, and we behave
// just like a block.
if (isInline() && linePositionMode == PositionOnContainingLine) {
// For "leaf" theme objects, let the theme decide what the baseline position
// is.
// FIXME: Might be better to have a custom CSS property instead, so that if
// the theme is turned off, checkboxes/radios will still have decent
// baselines.
// FIXME: Need to patch form controls to deal with vertical lines.
if (style()->hasAppearance() &&
!LayoutTheme::theme().isControlContainer(style()->appearance()))
return LayoutTheme::theme().baselinePosition(this);
int baselinePos = (isWritingModeRoot() && !isRubyRun())
? -1
: inlineBlockBaseline(direction);
if (isDeprecatedFlexibleBox()) {
// Historically, we did this check for all baselines. But we can't
// remove this code from deprecated flexbox, because it effectively
// breaks -webkit-line-clamp, which is used in the wild -- we would
// calculate the baseline as if -webkit-line-clamp wasn't used.
// For simplicity, we use this for all uses of deprecated flexbox.
LayoutUnit bottomOfContent =
direction == HorizontalLine
? size().height() - borderBottom() - paddingBottom() -
horizontalScrollbarHeight()
: size().width() - borderLeft() - paddingLeft() -
verticalScrollbarWidth();
if (baselinePos > bottomOfContent)
baselinePos = -1;
}
if (baselinePos != -1)
return beforeMarginInLineDirection(direction) + baselinePos;
return LayoutBox::baselinePosition(baselineType, firstLine, direction,
linePositionMode);
}
// If we're not replaced, we'll only get called with
// PositionOfInteriorLineBoxes.
// Note that inline-block counts as replaced here.
ASSERT(linePositionMode == PositionOfInteriorLineBoxes);
const SimpleFontData* fontData = style(firstLine)->font().primaryFont();
DCHECK(fontData);
if (!fontData)
return -1;
const FontMetrics& fontMetrics = fontData->getFontMetrics();
return (fontMetrics.ascent(baselineType) +
(lineHeight(firstLine, direction, linePositionMode) -
fontMetrics.height()) /
2)
.toInt();
}
LayoutUnit LayoutBlock::minLineHeightForReplacedObject(
bool isFirstLine,
LayoutUnit replacedHeight) const {
if (!document().inNoQuirksMode() && replacedHeight)
return replacedHeight;
return std::max<LayoutUnit>(
replacedHeight,
lineHeight(isFirstLine,
isHorizontalWritingMode() ? HorizontalLine : VerticalLine,
PositionOfInteriorLineBoxes));
}
// TODO(mstensho): Figure out if all of this baseline code is needed here, or if
// it should be moved down to LayoutBlockFlow. LayoutDeprecatedFlexibleBox and
// LayoutGrid lack baseline calculation overrides, so the code is here just for
// them. Just walking the block children in logical order seems rather wrong for
// those two layout modes, though.
int LayoutBlock::firstLineBoxBaseline() const {
ASSERT(!childrenInline());
if (isWritingModeRoot() && !isRubyRun())
return -1;
for (LayoutBox* curr = firstChildBox(); curr; curr = curr->nextSiblingBox()) {
if (!curr->isFloatingOrOutOfFlowPositioned()) {
int result = curr->firstLineBoxBaseline();
if (result != -1)
return (curr->logicalTop() + result)
.toInt(); // Translate to our coordinate space.
}
}
return -1;
}
int LayoutBlock::inlineBlockBaseline(LineDirectionMode lineDirection) const {
ASSERT(!childrenInline());
if ((!style()->isOverflowVisible() &&
!shouldIgnoreOverflowPropertyForInlineBlockBaseline()) ||
style()->containsSize()) {
// We are not calling LayoutBox::baselinePosition here because the caller
// should add the margin-top/margin-right, not us.
return (lineDirection == HorizontalLine ? size().height() + marginBottom()
: size().width() + marginLeft())
.toInt();
}
if (isWritingModeRoot() && !isRubyRun())
return -1;
bool haveNormalFlowChild = false;
for (LayoutBox* curr = lastChildBox(); curr;
curr = curr->previousSiblingBox()) {
if (!curr->isFloatingOrOutOfFlowPositioned()) {
haveNormalFlowChild = true;
int result = curr->inlineBlockBaseline(lineDirection);
if (result != -1)
return (curr->logicalTop() + result)
.toInt(); // Translate to our coordinate space.
}
}
const SimpleFontData* fontData = firstLineStyle()->font().primaryFont();
if (fontData && !haveNormalFlowChild && hasLineIfEmpty()) {
const FontMetrics& fontMetrics = fontData->getFontMetrics();
return (fontMetrics.ascent() +
(lineHeight(true, lineDirection, PositionOfInteriorLineBoxes) -
fontMetrics.height()) /
2 +
(lineDirection == HorizontalLine ? borderTop() + paddingTop()
: borderRight() + paddingRight()))
.toInt();
}
return -1;
}
const LayoutBlock* LayoutBlock::enclosingFirstLineStyleBlock() const {
const LayoutBlock* firstLineBlock = this;
bool hasPseudo = false;
while (true) {
hasPseudo = firstLineBlock->style()->hasPseudoStyle(PseudoIdFirstLine);
if (hasPseudo)
break;
LayoutObject* parentBlock = firstLineBlock->parent();
if (firstLineBlock->isAtomicInlineLevel() ||
firstLineBlock->isFloatingOrOutOfFlowPositioned() || !parentBlock ||
!parentBlock->behavesLikeBlockContainer())
break;
SECURITY_DCHECK(parentBlock->isLayoutBlock());
if (toLayoutBlock(parentBlock)->firstChild() != firstLineBlock)
break;
firstLineBlock = toLayoutBlock(parentBlock);
}
if (!hasPseudo)
return nullptr;
return firstLineBlock;
}
LayoutBlockFlow* LayoutBlock::nearestInnerBlockWithFirstLine() {
if (childrenInline())
return toLayoutBlockFlow(this);
for (LayoutObject* child = firstChild();
child && !child->isFloatingOrOutOfFlowPositioned() &&
child->isLayoutBlockFlow();
child = toLayoutBlock(child)->firstChild()) {
if (child->childrenInline())
return toLayoutBlockFlow(child);
}
return nullptr;
}
void LayoutBlock::updateHitTestResult(HitTestResult& result,
const LayoutPoint& point) {
if (result.innerNode())
return;
if (Node* n = nodeForHitTest())
result.setNodeAndPosition(n, point);
}
// An inline-block uses its inlineBox as the inlineBoxWrapper,
// so the firstChild() is nullptr if the only child is an empty inline-block.
inline bool LayoutBlock::isInlineBoxWrapperActuallyChild() const {
return isInlineBlockOrInlineTable() && !size().isEmpty() && node() &&
editingIgnoresContent(*node());
}
bool LayoutBlock::shouldPaintCursorCaret() const {
return frame()->selection().shouldPaintCaret(*this);
}
bool LayoutBlock::shouldPaintDragCaret() const {
return frame()->page()->dragCaret().shouldPaintCaret(*this);
}
LayoutRect LayoutBlock::localCaretRect(InlineBox* inlineBox,
int caretOffset,
LayoutUnit* extraWidthToEndOfLine) {
// Do the normal calculation in most cases.
if ((firstChild() && !firstChild()->isPseudoElement()) ||
isInlineBoxWrapperActuallyChild())
return LayoutBox::localCaretRect(inlineBox, caretOffset,
extraWidthToEndOfLine);
LayoutRect caretRect =
localCaretRectForEmptyElement(size().width(), textIndentOffset());
if (extraWidthToEndOfLine)
*extraWidthToEndOfLine = size().width() - caretRect.maxX();
return caretRect;
}
void LayoutBlock::addOutlineRects(
Vector<LayoutRect>& rects,
const LayoutPoint& additionalOffset,
IncludeBlockVisualOverflowOrNot includeBlockOverflows) const {
if (!isAnonymous()) // For anonymous blocks, the children add outline rects.
rects.push_back(LayoutRect(additionalOffset, size()));
if (includeBlockOverflows == IncludeBlockVisualOverflow &&
!hasOverflowClip() && !hasControlClip()) {
addOutlineRectsForNormalChildren(rects, additionalOffset,
includeBlockOverflows);
if (TrackedLayoutBoxListHashSet* positionedObjects =
this->positionedObjects()) {
for (auto* box : *positionedObjects)
addOutlineRectsForDescendant(*box, rects, additionalOffset,
includeBlockOverflows);
}
}
}
LayoutBox* LayoutBlock::createAnonymousBoxWithSameTypeAs(
const LayoutObject* parent) const {
return createAnonymousWithParentAndDisplay(parent, style()->display());
}
void LayoutBlock::paginatedContentWasLaidOut(
LayoutUnit logicalBottomOffsetAfterPagination) {
if (LayoutFlowThread* flowThread = flowThreadContainingBlock())
flowThread->contentWasLaidOut(offsetFromLogicalTopOfFirstPage() +
logicalBottomOffsetAfterPagination);
}
LayoutUnit LayoutBlock::collapsedMarginBeforeForChild(
const LayoutBox& child) const {
// If the child has the same directionality as we do, then we can just return
// its collapsed margin.
if (!child.isWritingModeRoot())
return child.collapsedMarginBefore();
// The child has a different directionality. If the child is parallel, then
// it's just flipped relative to us. We can use the collapsed margin for the
// opposite edge.
if (child.isHorizontalWritingMode() == isHorizontalWritingMode())
return child.collapsedMarginAfter();
// The child is perpendicular to us, which means its margins don't collapse
// but are on the "logical left/right" sides of the child box. We can just
// return the raw margin in this case.
return marginBeforeForChild(child);
}
LayoutUnit LayoutBlock::collapsedMarginAfterForChild(
const LayoutBox& child) const {
// If the child has the same directionality as we do, then we can just return
// its collapsed margin.
if (!child.isWritingModeRoot())
return child.collapsedMarginAfter();
// The child has a different directionality. If the child is parallel, then
// it's just flipped relative to us. We can use the collapsed margin for the
// opposite edge.
if (child.isHorizontalWritingMode() == isHorizontalWritingMode())
return child.collapsedMarginBefore();
// The child is perpendicular to us, which means its margins don't collapse
// but are on the "logical left/right" side of the child box. We can just
// return the raw margin in this case.
return marginAfterForChild(child);
}
bool LayoutBlock::hasMarginBeforeQuirk(const LayoutBox* child) const {
// If the child has the same directionality as we do, then we can just return
// its margin quirk.
if (!child->isWritingModeRoot())
return child->isLayoutBlock() ? toLayoutBlock(child)->hasMarginBeforeQuirk()
: child->style()->hasMarginBeforeQuirk();
// The child has a different directionality. If the child is parallel, then
// it's just flipped relative to us. We can use the opposite edge.
if (child->isHorizontalWritingMode() == isHorizontalWritingMode())
return child->isLayoutBlock() ? toLayoutBlock(child)->hasMarginAfterQuirk()
: child->style()->hasMarginAfterQuirk();
// The child is perpendicular to us and box sides are never quirky in
// html.css, and we don't really care about whether or not authors specified
// quirky ems, since they're an implementation detail.
return false;
}
bool LayoutBlock::hasMarginAfterQuirk(const LayoutBox* child) const {
// If the child has the same directionality as we do, then we can just return
// its margin quirk.
if (!child->isWritingModeRoot())
return child->isLayoutBlock() ? toLayoutBlock(child)->hasMarginAfterQuirk()
: child->style()->hasMarginAfterQuirk();
// The child has a different directionality. If the child is parallel, then
// it's just flipped relative to us. We can use the opposite edge.
if (child->isHorizontalWritingMode() == isHorizontalWritingMode())
return child->isLayoutBlock() ? toLayoutBlock(child)->hasMarginBeforeQuirk()
: child->style()->hasMarginBeforeQuirk();
// The child is perpendicular to us and box sides are never quirky in
// html.css, and we don't really care about whether or not authors specified
// quirky ems, since they're an implementation detail.
return false;
}
const char* LayoutBlock::name() const {
ASSERT_NOT_REACHED();
return "LayoutBlock";
}
LayoutBlock* LayoutBlock::createAnonymousWithParentAndDisplay(
const LayoutObject* parent,
EDisplay display) {
// FIXME: Do we need to convert all our inline displays to block-type in the
// anonymous logic ?
EDisplay newDisplay;
LayoutBlock* newBox = nullptr;
if (display == EDisplay::kFlex || display == EDisplay::kInlineFlex) {
newBox = LayoutFlexibleBox::createAnonymous(&parent->document());
newDisplay = EDisplay::kFlex;
} else {
newBox = LayoutBlockFlow::createAnonymous(&parent->document());
newDisplay = EDisplay::kBlock;
}
RefPtr<ComputedStyle> newStyle =
ComputedStyle::createAnonymousStyleWithDisplay(parent->styleRef(),
newDisplay);
parent->updateAnonymousChildStyle(*newBox, *newStyle);
newBox->setStyle(std::move(newStyle));
return newBox;
}
bool LayoutBlock::recalcNormalFlowChildOverflowIfNeeded(
LayoutObject* layoutObject) {
if (layoutObject->isOutOfFlowPositioned() ||
!layoutObject->needsOverflowRecalcAfterStyleChange())
return false;
ASSERT(layoutObject->isLayoutBlock());
return toLayoutBlock(layoutObject)->recalcOverflowAfterStyleChange();
}
bool LayoutBlock::recalcChildOverflowAfterStyleChange() {
ASSERT(childNeedsOverflowRecalcAfterStyleChange());
clearChildNeedsOverflowRecalcAfterStyleChange();
bool childrenOverflowChanged = false;
if (childrenInline()) {
SECURITY_DCHECK(isLayoutBlockFlow());
childrenOverflowChanged =
toLayoutBlockFlow(this)->recalcInlineChildrenOverflowAfterStyleChange();
} else {
for (LayoutBox* box = firstChildBox(); box; box = box->nextSiblingBox()) {
if (recalcNormalFlowChildOverflowIfNeeded(box))
childrenOverflowChanged = true;
}
}
return recalcPositionedDescendantsOverflowAfterStyleChange() ||
childrenOverflowChanged;
}
bool LayoutBlock::recalcPositionedDescendantsOverflowAfterStyleChange() {
bool childrenOverflowChanged = false;
TrackedLayoutBoxListHashSet* positionedDescendants = positionedObjects();
if (!positionedDescendants)
return childrenOverflowChanged;
for (auto* box : *positionedDescendants) {
if (!box->needsOverflowRecalcAfterStyleChange())
continue;
LayoutBlock* block = toLayoutBlock(box);
if (!block->recalcOverflowAfterStyleChange() ||
box->style()->position() == EPosition::kFixed)
continue;
childrenOverflowChanged = true;
}
return childrenOverflowChanged;
}
bool LayoutBlock::recalcOverflowAfterStyleChange() {
ASSERT(needsOverflowRecalcAfterStyleChange());
bool childrenOverflowChanged = false;
if (childNeedsOverflowRecalcAfterStyleChange())
childrenOverflowChanged = recalcChildOverflowAfterStyleChange();
if (!selfNeedsOverflowRecalcAfterStyleChange() && !childrenOverflowChanged)
return false;
clearSelfNeedsOverflowRecalcAfterStyleChange();
// If the current block needs layout, overflow will be recalculated during
// layout time anyway. We can safely exit here.
if (needsLayout())
return false;
LayoutUnit oldClientAfterEdge = hasOverflowModel()
? m_overflow->layoutClientAfterEdge()
: clientLogicalBottom();
computeOverflow(oldClientAfterEdge, true);
if (hasOverflowClip())
layer()->getScrollableArea()->updateAfterOverflowRecalc();
return !hasOverflowClip();
}
// Called when a positioned object moves but doesn't necessarily change size.
// A simplified layout is attempted that just updates the object's position.
// If the size does change, the object remains dirty.
bool LayoutBlock::tryLayoutDoingPositionedMovementOnly() {
LayoutUnit oldWidth = logicalWidth();
LogicalExtentComputedValues computedValues;
logicalExtentAfterUpdatingLogicalWidth(logicalTop(), computedValues);
// If we shrink to fit our width may have changed, so we still need full
// layout.
if (oldWidth != computedValues.m_extent)
return false;
setLogicalWidth(computedValues.m_extent);
setLogicalLeft(computedValues.m_position);
setMarginStart(computedValues.m_margins.m_start);
setMarginEnd(computedValues.m_margins.m_end);
LayoutUnit oldHeight = logicalHeight();
LayoutUnit oldIntrinsicContentLogicalHeight = intrinsicContentLogicalHeight();
setIntrinsicContentLogicalHeight(contentLogicalHeight());
computeLogicalHeight(oldHeight, logicalTop(), computedValues);
if (oldHeight != computedValues.m_extent &&
(hasPercentHeightDescendants() || isFlexibleBox())) {
setIntrinsicContentLogicalHeight(oldIntrinsicContentLogicalHeight);
return false;
}
setLogicalHeight(computedValues.m_extent);
setLogicalTop(computedValues.m_position);
setMarginBefore(computedValues.m_margins.m_before);
setMarginAfter(computedValues.m_margins.m_after);
return true;
}
#if DCHECK_IS_ON()
void LayoutBlock::checkPositionedObjectsNeedLayout() {
if (!gPositionedDescendantsMap)
return;
if (TrackedLayoutBoxListHashSet* positionedDescendantSet =
positionedObjects()) {
TrackedLayoutBoxListHashSet::const_iterator end =
positionedDescendantSet->end();
for (TrackedLayoutBoxListHashSet::const_iterator it =
positionedDescendantSet->begin();
it != end; ++it) {
LayoutBox* currBox = *it;
ASSERT(!currBox->needsLayout());
}
}
}
#endif
LayoutUnit LayoutBlock::availableLogicalHeightForPercentageComputation() const {
LayoutUnit availableHeight(-1);
// For anonymous blocks that are skipped during percentage height calculation,
// we consider them to have an indefinite height.
if (skipContainingBlockForPercentHeightCalculation(this))
return availableHeight;
const ComputedStyle& style = 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 =
isOutOfFlowPositioned() &&
(!style.logicalHeight().isAuto() ||
(!style.logicalTop().isAuto() && !style.logicalBottom().isAuto()));
LayoutUnit stretchedFlexHeight(-1);
if (isFlexItem())
stretchedFlexHeight =
toLayoutFlexibleBox(parent())
->childLogicalHeightForPercentageResolution(*this);
if (stretchedFlexHeight != LayoutUnit(-1)) {
availableHeight = stretchedFlexHeight;
} else if (isGridItem() && hasOverrideLogicalContentHeight()) {
availableHeight = overrideLogicalContentHeight();
} else if (style.logicalHeight().isFixed()) {
LayoutUnit contentBoxHeight = adjustContentBoxLogicalHeightForBoxSizing(
style.logicalHeight().value());
availableHeight = std::max(
LayoutUnit(),
constrainContentBoxLogicalHeightByMinMax(
contentBoxHeight - scrollbarLogicalHeight(), LayoutUnit(-1)));
} else if (style.logicalHeight().isPercentOrCalc() &&
!isOutOfFlowPositionedWithSpecifiedHeight) {
LayoutUnit heightWithScrollbar =
computePercentageLogicalHeight(style.logicalHeight());
if (heightWithScrollbar != -1) {
LayoutUnit contentBoxHeightWithScrollbar =
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 = constrainContentBoxLogicalHeightByMinMax(
contentBoxHeightWithScrollbar - 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;
computeLogicalHeight(logicalHeight(), LayoutUnit(), computedValues);
availableHeight = computedValues.m_extent -
borderAndPaddingLogicalHeight() -
scrollbarLogicalHeight();
} else if (isLayoutView()) {
availableHeight = view()->viewLogicalHeightForPercentages();
}
return availableHeight;
}
bool LayoutBlock::hasDefiniteLogicalHeight() const {
return availableLogicalHeightForPercentageComputation() != LayoutUnit(-1);
}
} // namespace blink