| /* |
| * 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 Apple Inc. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #ifndef LayoutBlock_h |
| #define LayoutBlock_h |
| |
| #include "core/CoreExport.h" |
| #include "core/layout/LayoutBox.h" |
| #include "wtf/ListHashSet.h" |
| #include <memory> |
| |
| namespace blink { |
| |
| struct PaintInfo; |
| class LineLayoutBox; |
| class WordMeasurement; |
| |
| typedef WTF::ListHashSet<LayoutBox*, 16> TrackedLayoutBoxListHashSet; |
| typedef WTF::HashMap<const LayoutBlock*, |
| std::unique_ptr<TrackedLayoutBoxListHashSet>> |
| TrackedDescendantsMap; |
| typedef WTF::HashMap<const LayoutBox*, LayoutBlock*> TrackedContainerMap; |
| typedef Vector<WordMeasurement, 64> WordMeasurements; |
| |
| enum ContainingBlockState { NewContainingBlock, SameContainingBlock }; |
| |
| // LayoutBlock is the class that is used by any LayoutObject |
| // that is a containing block. |
| // http://www.w3.org/TR/CSS2/visuren.html#containing-block |
| // See also LayoutObject::containingBlock() that is the function |
| // used to get the containing block of a LayoutObject. |
| // |
| // CSS is inconsistent and allows inline elements (LayoutInline) to be |
| // containing blocks, even though they are not blocks. Our |
| // implementation is as confused with inlines. See e.g. |
| // LayoutObject::containingBlock() vs LayoutObject::container(). |
| // |
| // Containing blocks are a central concept for layout, in |
| // particular to the layout of out-of-flow positioned |
| // elements. They are used to determine the sizing as well |
| // as the positioning of the LayoutObjects. |
| // |
| // LayoutBlock is the class that handles out-of-flow positioned elements in |
| // Blink, in particular for layout (see layoutPositionedObjects()). That's why |
| // LayoutBlock keeps track of them through |gPositionedDescendantsMap| (see |
| // LayoutBlock.cpp). |
| // Note that this is a design decision made in Blink that doesn't reflect CSS: |
| // CSS allows relatively positioned inlines (LayoutInline) to be containing |
| // blocks, but they don't have the logic to handle out-of-flow positioned |
| // objects. This induces some complexity around choosing an enclosing |
| // LayoutBlock (for inserting out-of-flow objects during layout) vs the CSS |
| // containing block (for sizing, invalidation). |
| // |
| // |
| // ***** WHO LAYS OUT OUT-OF-FLOW POSITIONED OBJECTS? ***** |
| // A positioned object gets inserted into an enclosing LayoutBlock's positioned |
| // map. This is determined by LayoutObject::containingBlock(). |
| // |
| // |
| // ***** HANDLING OUT-OF-FLOW POSITIONED OBJECTS ***** |
| // Care should be taken to handle out-of-flow positioned objects during |
| // certain tree walks (e.g. layout()). The rule is that anything that |
| // cares about containing blocks should skip the out-of-flow elements |
| // in the normal tree walk and do an optional follow-up pass for them |
| // using LayoutBlock::positionedObjects(). |
| // Not doing so will result in passing the wrong containing |
| // block as tree walks will always pass the parent as the |
| // containing block. |
| // |
| // Sample code of how to handle positioned objects in LayoutBlock: |
| // |
| // for (LayoutObject* child = firstChild(); child; child = child->nextSibling()) { |
| // if (child->isOutOfFlowPositioned()) |
| // continue; |
| // |
| // // Handle normal flow children. |
| // ... |
| // } |
| // for (LayoutBox* positionedObject : positionedObjects()) { |
| // // Handle out-of-flow positioned objects. |
| // ... |
| // } |
| class CORE_EXPORT LayoutBlock : public LayoutBox { |
| protected: |
| explicit LayoutBlock(ContainerNode*); |
| ~LayoutBlock() override; |
| |
| public: |
| LayoutObject* firstChild() const { |
| ASSERT(children() == virtualChildren()); |
| return children()->firstChild(); |
| } |
| LayoutObject* lastChild() const { |
| ASSERT(children() == virtualChildren()); |
| return children()->lastChild(); |
| } |
| |
| // If you have a LayoutBlock, use firstChild or lastChild instead. |
| void slowFirstChild() const = delete; |
| void slowLastChild() const = delete; |
| |
| const LayoutObjectChildList* children() const { return &m_children; } |
| LayoutObjectChildList* children() { return &m_children; } |
| |
| // These two functions are overridden for inline-block. |
| LayoutUnit lineHeight( |
| bool firstLine, |
| LineDirectionMode, |
| LinePositionMode = PositionOnContainingLine) const final; |
| int baselinePosition( |
| FontBaseline, |
| bool firstLine, |
| LineDirectionMode, |
| LinePositionMode = PositionOnContainingLine) const override; |
| |
| LayoutUnit minLineHeightForReplacedObject(bool isFirstLine, |
| LayoutUnit replacedHeight) const; |
| |
| bool createsNewFormattingContext() const; |
| |
| const char* name() const override; |
| |
| protected: |
| // Insert a child correctly into the tree when |beforeDescendant| isn't a direct child of |
| // |this|. This happens e.g. when there's an anonymous block child of |this| and |
| // |beforeDescendant| has been reparented into that one. Such things are invisible to the DOM, |
| // and addChild() is typically called with the DOM tree (and not the layout tree) in mind. |
| void addChildBeforeDescendant(LayoutObject* newChild, |
| LayoutObject* beforeDescendant); |
| |
| public: |
| void addChild(LayoutObject* newChild, |
| LayoutObject* beforeChild = nullptr) override; |
| |
| virtual void layoutBlock(bool relayoutChildren); |
| |
| void insertPositionedObject(LayoutBox*); |
| static void removePositionedObject(LayoutBox*); |
| void removePositionedObjects(LayoutObject*, |
| ContainingBlockState = SameContainingBlock); |
| |
| TrackedLayoutBoxListHashSet* positionedObjects() const { |
| return hasPositionedObjects() ? positionedObjectsInternal() : nullptr; |
| } |
| bool hasPositionedObjects() const { |
| ASSERT(m_hasPositionedObjects ? (positionedObjectsInternal() && |
| !positionedObjectsInternal()->isEmpty()) |
| : !positionedObjectsInternal()); |
| return m_hasPositionedObjects; |
| } |
| |
| void addPercentHeightDescendant(LayoutBox*); |
| void removePercentHeightDescendant(LayoutBox*); |
| bool hasPercentHeightDescendant(LayoutBox* o) const { |
| return hasPercentHeightDescendants() && |
| percentHeightDescendantsInternal()->contains(o); |
| } |
| |
| TrackedLayoutBoxListHashSet* percentHeightDescendants() const { |
| return hasPercentHeightDescendants() ? percentHeightDescendantsInternal() |
| : nullptr; |
| } |
| bool hasPercentHeightDescendants() const { |
| ASSERT(m_hasPercentHeightDescendants |
| ? (percentHeightDescendantsInternal() && |
| !percentHeightDescendantsInternal()->isEmpty()) |
| : !percentHeightDescendantsInternal()); |
| return m_hasPercentHeightDescendants; |
| } |
| |
| void notifyScrollbarThicknessChanged() { |
| m_widthAvailableToChildrenChanged = true; |
| } |
| |
| void setHasMarkupTruncation(bool b) { m_hasMarkupTruncation = b; } |
| bool hasMarkupTruncation() const { return m_hasMarkupTruncation; } |
| |
| void setHasMarginBeforeQuirk(bool b) { m_hasMarginBeforeQuirk = b; } |
| void setHasMarginAfterQuirk(bool b) { m_hasMarginAfterQuirk = b; } |
| |
| bool hasMarginBeforeQuirk() const { return m_hasMarginBeforeQuirk; } |
| bool hasMarginAfterQuirk() const { return m_hasMarginAfterQuirk; } |
| |
| bool hasMarginBeforeQuirk(const LayoutBox* child) const; |
| bool hasMarginAfterQuirk(const LayoutBox* child) const; |
| |
| void markPositionedObjectsForLayout(); |
| |
| LayoutUnit textIndentOffset() const; |
| |
| PositionWithAffinity positionForPoint(const LayoutPoint&) override; |
| |
| LayoutUnit blockDirectionOffset(const LayoutSize& offsetFromBlock) const; |
| LayoutUnit inlineDirectionOffset(const LayoutSize& offsetFromBlock) const; |
| |
| LayoutBlock* blockBeforeWithinSelectionRoot(LayoutSize& offset) const; |
| |
| void setSelectionState(SelectionState) override; |
| |
| static LayoutBlock* createAnonymousWithParentAndDisplay( |
| const LayoutObject*, |
| EDisplay = EDisplay::Block); |
| LayoutBlock* createAnonymousBlock(EDisplay display = EDisplay::Block) const { |
| return createAnonymousWithParentAndDisplay(this, display); |
| } |
| |
| LayoutBox* createAnonymousBoxWithSameTypeAs( |
| const LayoutObject* parent) const override; |
| |
| int columnGap() const; |
| |
| // Accessors for logical width/height and margins in the containing block's block-flow direction. |
| LayoutUnit logicalWidthForChild(const LayoutBox& child) const { |
| return logicalWidthForChildSize(child.size()); |
| } |
| LayoutUnit logicalWidthForChildSize(LayoutSize childSize) const { |
| return isHorizontalWritingMode() ? childSize.width() : childSize.height(); |
| } |
| LayoutUnit logicalHeightForChild(const LayoutBox& child) const { |
| return isHorizontalWritingMode() ? child.size().height() |
| : child.size().width(); |
| } |
| LayoutSize logicalSizeForChild(const LayoutBox& child) const { |
| return isHorizontalWritingMode() ? child.size() |
| : child.size().transposedSize(); |
| } |
| LayoutUnit logicalTopForChild(const LayoutBox& child) const { |
| return isHorizontalWritingMode() ? child.location().y() |
| : child.location().x(); |
| } |
| DISABLE_CFI_PERF LayoutUnit |
| marginBeforeForChild(const LayoutBoxModelObject& child) const { |
| return child.marginBefore(style()); |
| } |
| DISABLE_CFI_PERF LayoutUnit |
| marginAfterForChild(const LayoutBoxModelObject& child) const { |
| return child.marginAfter(style()); |
| } |
| DISABLE_CFI_PERF LayoutUnit |
| marginStartForChild(const LayoutBoxModelObject& child) const { |
| return child.marginStart(style()); |
| } |
| LayoutUnit marginEndForChild(const LayoutBoxModelObject& child) const { |
| return child.marginEnd(style()); |
| } |
| void setMarginStartForChild(LayoutBox& child, LayoutUnit value) const { |
| child.setMarginStart(value, style()); |
| } |
| void setMarginEndForChild(LayoutBox& child, LayoutUnit value) const { |
| child.setMarginEnd(value, style()); |
| } |
| void setMarginBeforeForChild(LayoutBox& child, LayoutUnit value) const { |
| child.setMarginBefore(value, style()); |
| } |
| void setMarginAfterForChild(LayoutBox& child, LayoutUnit value) const { |
| child.setMarginAfter(value, style()); |
| } |
| LayoutUnit collapsedMarginBeforeForChild(const LayoutBox& child) const; |
| LayoutUnit collapsedMarginAfterForChild(const LayoutBox& child) const; |
| |
| virtual void scrollbarsChanged(bool /*horizontalScrollbarChanged*/, |
| bool /*verticalScrollbarChanged*/); |
| |
| LayoutUnit availableLogicalWidthForContent() const { |
| return (logicalRightOffsetForContent() - logicalLeftOffsetForContent()) |
| .clampNegativeToZero(); |
| } |
| DISABLE_CFI_PERF LayoutUnit logicalLeftOffsetForContent() const { |
| return isHorizontalWritingMode() ? borderLeft() + paddingLeft() |
| : borderTop() + paddingTop(); |
| } |
| LayoutUnit logicalRightOffsetForContent() const { |
| return logicalLeftOffsetForContent() + availableLogicalWidth(); |
| } |
| LayoutUnit startOffsetForContent() const { |
| return style()->isLeftToRightDirection() |
| ? logicalLeftOffsetForContent() |
| : logicalWidth() - logicalRightOffsetForContent(); |
| } |
| LayoutUnit endOffsetForContent() const { |
| return !style()->isLeftToRightDirection() |
| ? logicalLeftOffsetForContent() |
| : logicalWidth() - logicalRightOffsetForContent(); |
| } |
| |
| virtual LayoutUnit logicalLeftSelectionOffset(const LayoutBlock* rootBlock, |
| LayoutUnit position) const; |
| virtual LayoutUnit logicalRightSelectionOffset(const LayoutBlock* rootBlock, |
| LayoutUnit position) const; |
| |
| #if ENABLE(ASSERT) |
| void checkPositionedObjectsNeedLayout(); |
| #endif |
| |
| LayoutUnit availableLogicalHeightForPercentageComputation() const; |
| bool hasDefiniteLogicalHeight() const; |
| |
| protected: |
| bool recalcNormalFlowChildOverflowIfNeeded(LayoutObject*); |
| bool recalcPositionedDescendantsOverflowAfterStyleChange(); |
| |
| public: |
| virtual bool recalcChildOverflowAfterStyleChange(); |
| bool recalcOverflowAfterStyleChange(); |
| |
| // An example explaining layout tree structure about first-line style: |
| // <style> |
| // #enclosingFirstLineStyleBlock::first-line { ... } |
| // </style> |
| // <div id="enclosingFirstLineStyleBlock"> |
| // <div> |
| // <div id="nearestInnerBlockWithFirstLine"> |
| // [<span>]first line text[</span>] |
| // </div> |
| // </div> |
| // </div> |
| |
| // Returns the nearest enclosing block (including this block) that contributes a first-line style to our first line. |
| const LayoutBlock* enclosingFirstLineStyleBlock() const; |
| // Returns this block or the nearest inner block containing the actual first line. |
| LayoutBlockFlow* nearestInnerBlockWithFirstLine(); |
| |
| protected: |
| void willBeDestroyed() override; |
| |
| void dirtyForLayoutFromPercentageHeightDescendants(SubtreeLayoutScope&); |
| |
| void layout() override; |
| |
| enum PositionedLayoutBehavior { |
| DefaultLayout, |
| LayoutOnlyFixedPositionedObjects, |
| ForcedLayoutAfterContainingBlockMoved |
| }; |
| |
| void layoutPositionedObjects(bool relayoutChildren, |
| PositionedLayoutBehavior = DefaultLayout); |
| void markFixedPositionObjectForLayoutIfNeeded(LayoutObject* child, |
| SubtreeLayoutScope&); |
| |
| LayoutUnit marginIntrinsicLogicalWidthForChild(const LayoutBox& child) const; |
| |
| int beforeMarginInLineDirection(LineDirectionMode) const; |
| |
| void paint(const PaintInfo&, const LayoutPoint&) const override; |
| |
| public: |
| virtual void paintObject(const PaintInfo&, const LayoutPoint&) const; |
| virtual void paintChildren(const PaintInfo&, const LayoutPoint&) const; |
| |
| protected: |
| virtual void adjustInlineDirectionLineBounds( |
| unsigned /* expansionOpportunityCount */, |
| LayoutUnit& /* logicalLeft */, |
| LayoutUnit& /* logicalWidth */) const {} |
| |
| void computeIntrinsicLogicalWidths( |
| LayoutUnit& minLogicalWidth, |
| LayoutUnit& maxLogicalWidth) const override; |
| void computePreferredLogicalWidths() override; |
| void computeChildPreferredLogicalWidths( |
| LayoutObject& child, |
| LayoutUnit& minPreferredLogicalWidth, |
| LayoutUnit& maxPreferredLogicalWidth) const; |
| |
| int firstLineBoxBaseline() const override; |
| int inlineBlockBaseline(LineDirectionMode) const override; |
| |
| // This function disables the 'overflow' check in inlineBlockBaseline. |
| // For 'inline-block', CSS says that the baseline is the bottom margin edge |
| // if 'overflow' is not visible. But some descendant classes want to ignore |
| // this condition. |
| virtual bool shouldIgnoreOverflowPropertyForInlineBlockBaseline() const { |
| return false; |
| } |
| |
| bool hitTestOverflowControl(HitTestResult&, |
| const HitTestLocation&, |
| const LayoutPoint& adjustedLocation) override; |
| bool hitTestChildren(HitTestResult&, |
| const HitTestLocation& locationInContainer, |
| const LayoutPoint& accumulatedOffset, |
| HitTestAction) override; |
| void updateHitTestResult(HitTestResult&, const LayoutPoint&) override; |
| |
| void updateAfterLayout(); |
| |
| void styleWillChange(StyleDifference, const ComputedStyle& newStyle) override; |
| void styleDidChange(StyleDifference, const ComputedStyle* oldStyle) override; |
| void updateFromStyle() override; |
| |
| // Returns true if non-visible overflow should be respected. Otherwise hasOverflowClip() will be |
| // false and we won't create scrollable area for this object even if overflow is non-visible. |
| virtual bool allowsOverflowClip() const; |
| |
| virtual bool hasLineIfEmpty() const; |
| |
| bool simplifiedLayout(); |
| virtual void simplifiedNormalFlowLayout(); |
| |
| public: |
| virtual void computeOverflow(LayoutUnit oldClientAfterEdge, bool = false); |
| |
| protected: |
| virtual void addOverflowFromChildren(); |
| void addOverflowFromPositionedObjects(); |
| void addOverflowFromBlockChildren(); |
| void addVisualOverflowFromTheme(); |
| |
| void addOutlineRects(Vector<LayoutRect>&, |
| const LayoutPoint& additionalOffset, |
| IncludeBlockVisualOverflowOrNot) const override; |
| |
| void updateBlockChildDirtyBitsBeforeLayout(bool relayoutChildren, LayoutBox&); |
| |
| // TODO(jchaffraix): We should rename this function as inline-flex and inline-grid as also covered. |
| // Alternatively it should be removed as we clarify the meaning of isAtomicInlineLevel to imply |
| // isInline. |
| bool isInlineBlockOrInlineTable() const final { |
| return isInline() && isAtomicInlineLevel(); |
| } |
| |
| private: |
| LayoutObjectChildList* virtualChildren() final { return children(); } |
| const LayoutObjectChildList* virtualChildren() const final { |
| return children(); |
| } |
| |
| bool isLayoutBlock() const final { return true; } |
| |
| virtual void removeLeftoverAnonymousBlock(LayoutBlock* child); |
| |
| TrackedLayoutBoxListHashSet* positionedObjectsInternal() const; |
| TrackedLayoutBoxListHashSet* percentHeightDescendantsInternal() const; |
| |
| // Returns true if the positioned movement-only layout succeeded. |
| bool tryLayoutDoingPositionedMovementOnly(); |
| |
| bool avoidsFloats() const override { return true; } |
| |
| bool isInSelfHitTestingPhase(HitTestAction hitTestAction) const final { |
| return hitTestAction == HitTestBlockBackground || |
| hitTestAction == HitTestChildBlockBackground; |
| } |
| |
| bool isPointInOverflowControl(HitTestResult&, |
| const LayoutPoint& locationInContainer, |
| const LayoutPoint& accumulatedOffset) const; |
| |
| void computeBlockPreferredLogicalWidths(LayoutUnit& minLogicalWidth, |
| LayoutUnit& maxLogicalWidth) const; |
| |
| bool isSelectionRoot() const; |
| |
| public: |
| bool hasCursorCaret() const; |
| bool hasDragCaret() const; |
| bool hasCaret() const { return hasCursorCaret() || hasDragCaret(); } |
| |
| protected: |
| PaintInvalidationReason invalidatePaintIfNeeded( |
| const PaintInvalidationState&) override; |
| PaintInvalidationReason invalidatePaintIfNeeded( |
| const PaintInvalidatorContext&) const override; |
| |
| private: |
| LayoutRect localCaretRect(InlineBox*, |
| int caretOffset, |
| LayoutUnit* extraWidthToEndOfLine = nullptr) final; |
| bool isInlineBoxWrapperActuallyChild() const; |
| |
| Position positionForBox(InlineBox*, bool start = true) const; |
| |
| // End helper functions and structs used by layoutBlockChildren. |
| |
| void removeFromGlobalMaps(); |
| bool widthAvailableToChildrenHasChanged(); |
| |
| protected: |
| bool isPageLogicalHeightKnown(LayoutUnit logicalOffset) const { |
| return pageLogicalHeightForOffset(logicalOffset); |
| } |
| |
| // Returns the logical offset at the top of the next page, for a given offset. |
| LayoutUnit nextPageLogicalTop(LayoutUnit logicalOffset) const; |
| |
| // Paginated content inside this block was laid out. |
| // |logicalBottomOffsetAfterPagination| is the logical bottom offset of the child content after |
| // applying any forced or unforced breaks as needed. |
| void paginatedContentWasLaidOut( |
| LayoutUnit logicalBottomOffsetAfterPagination); |
| |
| // Adjust from painting offsets to the local coords of this layoutObject |
| void offsetForContents(LayoutPoint&) const; |
| |
| PositionWithAffinity positionForPointRespectingEditingBoundaries( |
| LineLayoutBox child, |
| const LayoutPoint& pointInParentCoordinates); |
| PositionWithAffinity positionForPointIfOutsideAtomicInlineLevel( |
| const LayoutPoint&); |
| |
| virtual bool updateLogicalWidthAndColumnWidth(); |
| |
| LayoutObjectChildList m_children; |
| |
| unsigned |
| m_hasMarginBeforeQuirk : 1; // Note these quirk values can't be put in LayoutBlockRareData since they are set too frequently. |
| unsigned m_hasMarginAfterQuirk : 1; |
| unsigned m_beingDestroyed : 1; |
| unsigned m_hasMarkupTruncation : 1; |
| unsigned m_widthAvailableToChildrenChanged : 1; |
| unsigned m_heightAvailableToChildrenChanged : 1; |
| unsigned |
| m_isSelfCollapsing : 1; // True if margin-before and margin-after are adjoining. |
| unsigned m_descendantsWithFloatsMarkedForLayout : 1; |
| |
| unsigned m_hasPositionedObjects : 1; |
| unsigned m_hasPercentHeightDescendants : 1; |
| |
| // FIXME: This is temporary as we move code that accesses block flow |
| // member variables out of LayoutBlock and into LayoutBlockFlow. |
| friend class LayoutBlockFlow; |
| |
| // This is necessary for now for interoperability between the old and new |
| // layout code. Primarily for calling layoutPositionedObjects at the moment. |
| friend class NGBox; |
| |
| public: |
| // TODO(lunalu): Temporary in order to ensure compatibility with existing layout test |
| // results. |
| virtual void adjustChildDebugRect(LayoutRect&) const {} |
| }; |
| |
| DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutBlock, isLayoutBlock()); |
| |
| } // namespace blink |
| |
| #endif // LayoutBlock_h |