| /* |
| * 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); |
| DCHECK(!descendants->isEmpty()); |
| for (LayoutBox* descendant : *descendants) { |
| DCHECK_EQ(gPositionedContainerMap->at(descendant), this); |
| gPositionedContainerMap->erase(descendant); |
| } |
| } |
| if (hasPercentHeightDescendants()) { |
| std::unique_ptr<TrackedLayoutBoxListHashSet> descendants = |
| gPercentHeightDescendantsMap->take(this); |
| DCHECK(!descendants->isEmpty()); |
| for (LayoutBox* descendant : *descendants) { |
| DCHECK_EQ(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) { |
| DCHECK_NE(beforeDescendant->parent(), this); |
| LayoutObject* beforeDescendantContainer = beforeDescendant->parent(); |
| while (beforeDescendantContainer->parent() != this) |
| beforeDescendantContainer = beforeDescendantContainer->parent(); |
| DCHECK(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; |
| } |
| |
| DCHECK(beforeDescendantContainer->isTable()); |
| if (newChild->isTablePart()) { |
| // Insert into the anonymous table. |
| beforeDescendantContainer->addChild(newChild, beforeDescendant); |
| return; |
| } |
| |
| LayoutObject* beforeChild = splitAnonymousBoxesAroundChild(beforeDescendant); |
| |
| DCHECK_EQ(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. |
| DCHECK(!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) { |
| DCHECK(child->isAnonymousBlock()); |
| DCHECK(!child->childrenInline()); |
| DCHECK_EQ(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) { |
| NOTREACHED(); |
| clearNeedsLayout(); |
| } |
| |
| void LayoutBlock::addOverflowFromChildren() { |
| if (childrenInline()) |
| toLayoutBlockFlow(this)->addOverflowFromInlineChildren(); |
| else |
| addOverflowFromBlockChildren(); |
| } |
| |
| DISABLE_CFI_PERF |
| void LayoutBlock::computeOverflow(LayoutUnit oldClientAfterEdge, bool) { |
| m_overflow.reset(); |
| |
| addOverflowFromChildren(); |
| 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()) |
| continue; |
| |
| // If the child contains inline with outline and continuation, its |
| // visual overflow computed during its layout might be inaccurate because |
| // the layout of continuations might not be up-to-date at that time. |
| // Re-add overflow from inline children to ensure its overflow covers |
| // the outline which may enclose continuations. |
| if (child->isLayoutBlockFlow() && |
| toLayoutBlockFlow(child)->containsInlineWithOutlineAndContinuation()) |
| toLayoutBlockFlow(child)->addOverflowFromInlineChildren(); |
| |
| 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() || isGridItem() || |
| style()->getColumnSpan() == ColumnSpanAll || |
| 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); |
| } |
| |
| 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(); |
| } |
| |
| 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) { |
| DCHECK(!isAnonymousBlock()); |
| DCHECK_EQ(o->containingBlock(), this); |
| |
| if (gPositionedContainerMap) { |
| auto containerMapIt = gPositionedContainerMap->find(o); |
| if (containerMapIt != gPositionedContainerMap->end()) { |
| if (containerMapIt->value == this) { |
| DCHECK(hasPositionedObjects()); |
| DCHECK(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); |
| DCHECK(positionedDescendants); |
| DCHECK(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) { |
| DCHECK_EQ(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) { |
| DCHECK(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) { |
| DCHECK(box->isDescendantOf(this)); |
| while (box != this) { |
| if (box->normalChildNeedsLayout()) |
| break; |
| layoutScope.setChildNeedsLayout(box); |
| box = box->containingBlock(); |
| DCHECK(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) { |
| DCHECK(!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) { |
| DCHECK(!ancestor || ancestor->nonPseudoNode()); |
| DCHECK(child); |
| DCHECK(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) { |
| DCHECK(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(); |
| |
| DCHECK(!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() { |
| DCHECK(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. |
| DCHECK_EQ(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 { |
| DCHECK(!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 { |
| DCHECK(!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 { |
| NOTREACHED(); |
| 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; |
| |
| DCHECK(layoutObject->isLayoutBlock()); |
| return toLayoutBlock(layoutObject)->recalcOverflowAfterStyleChange(); |
| } |
| |
| bool LayoutBlock::recalcChildOverflowAfterStyleChange() { |
| DCHECK(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() { |
| DCHECK(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; |
| DCHECK(!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 |