| /* |
| * 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 <memory> |
| #include "core/CoreExport.h" |
| #include "core/layout/LayoutBox.h" |
| #include "platform/wtf/ListHashSet.h" |
| |
| 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 { kNewContainingBlock, kSameContainingBlock }; |
| |
| // 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 { |
| DCHECK_EQ(Children(), VirtualChildren()); |
| return Children()->FirstChild(); |
| } |
| LayoutObject* LastChild() const { |
| DCHECK_EQ(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 &children_; } |
| LayoutObjectChildList* Children() { return &children_; } |
| |
| // These two functions are overridden for inline-block. |
| LayoutUnit LineHeight( |
| bool first_line, |
| LineDirectionMode, |
| LinePositionMode = kPositionOnContainingLine) const final; |
| int BaselinePosition( |
| FontBaseline, |
| bool first_line, |
| LineDirectionMode, |
| LinePositionMode = kPositionOnContainingLine) const override; |
| |
| LayoutUnit MinLineHeightForReplacedObject(bool is_first_line, |
| LayoutUnit replaced_height) const; |
| |
| virtual bool CreatesNewFormattingContext() const { return true; } |
| |
| const char* GetName() 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* new_child, |
| LayoutObject* before_descendant); |
| |
| public: |
| void AddChild(LayoutObject* new_child, |
| LayoutObject* before_child = nullptr) override; |
| |
| virtual void UpdateBlockLayout(bool relayout_children); |
| |
| void InsertPositionedObject(LayoutBox*); |
| static void RemovePositionedObject(LayoutBox*); |
| void RemovePositionedObjects(LayoutObject*, |
| ContainingBlockState = kSameContainingBlock); |
| |
| TrackedLayoutBoxListHashSet* PositionedObjects() const { |
| return HasPositionedObjects() ? PositionedObjectsInternal() : nullptr; |
| } |
| bool HasPositionedObjects() const { |
| DCHECK(has_positioned_objects_ ? (PositionedObjectsInternal() && |
| !PositionedObjectsInternal()->IsEmpty()) |
| : !PositionedObjectsInternal()); |
| return has_positioned_objects_; |
| } |
| |
| 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 { |
| DCHECK(has_percent_height_descendants_ |
| ? (PercentHeightDescendantsInternal() && |
| !PercentHeightDescendantsInternal()->IsEmpty()) |
| : !PercentHeightDescendantsInternal()); |
| return has_percent_height_descendants_; |
| } |
| |
| void NotifyScrollbarThicknessChanged() { |
| width_available_to_children_changed_ = true; |
| } |
| |
| void SetHasMarkupTruncation(bool b) { has_markup_truncation_ = b; } |
| bool HasMarkupTruncation() const { return has_markup_truncation_; } |
| |
| void SetHasMarginBeforeQuirk(bool b) { has_margin_before_quirk_ = b; } |
| void SetHasMarginAfterQuirk(bool b) { has_margin_after_quirk_ = b; } |
| |
| bool HasMarginBeforeQuirk() const { return has_margin_before_quirk_; } |
| bool HasMarginAfterQuirk() const { return has_margin_after_quirk_; } |
| |
| 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& offset_from_block) const; |
| LayoutUnit InlineDirectionOffset(const LayoutSize& offset_from_block) const; |
| |
| void SetSelectionState(SelectionState) override; |
| |
| static LayoutBlock* CreateAnonymousWithParentAndDisplay( |
| const LayoutObject*, |
| EDisplay = EDisplay::kBlock); |
| LayoutBlock* CreateAnonymousBlock(EDisplay display = EDisplay::kBlock) 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 child_size) const { |
| return IsHorizontalWritingMode() ? child_size.Width() : child_size.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; |
| |
| enum ScrollbarChangeContext { kStyleChange, kLayout }; |
| virtual void ScrollbarsChanged(bool horizontal_scrollbar_changed, |
| bool vertical_scrollbar_changed, |
| ScrollbarChangeContext = kLayout); |
| |
| 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* root_block, |
| LayoutUnit position) const; |
| virtual LayoutUnit LogicalRightSelectionOffset(const LayoutBlock* root_block, |
| LayoutUnit position) const; |
| |
| #if DCHECK_IS_ON() |
| 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 UpdateLayout() override; |
| |
| enum PositionedLayoutBehavior { |
| kDefaultLayout, |
| kLayoutOnlyFixedPositionedObjects, |
| kForcedLayoutAfterContainingBlockMoved |
| }; |
| |
| virtual void LayoutPositionedObjects( |
| bool relayout_children, |
| PositionedLayoutBehavior = kDefaultLayout); |
| void LayoutPositionedObject(LayoutBox*, |
| bool relayout_children, |
| PositionedLayoutBehavior info); |
| 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& min_logical_width, |
| LayoutUnit& max_logical_width) const override; |
| void ComputePreferredLogicalWidths() override; |
| void ComputeChildPreferredLogicalWidths( |
| LayoutObject& child, |
| LayoutUnit& min_preferred_logical_width, |
| LayoutUnit& max_preferred_logical_width) 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& adjusted_location) override; |
| bool HitTestChildren(HitTestResult&, |
| const HitTestLocation& location_in_container, |
| const LayoutPoint& accumulated_offset, |
| HitTestAction) override; |
| void UpdateHitTestResult(HitTestResult&, const LayoutPoint&) override; |
| |
| void UpdateAfterLayout() override; |
| |
| void StyleWillChange(StyleDifference, |
| const ComputedStyle& new_style) override; |
| void StyleDidChange(StyleDifference, const ComputedStyle* old_style) 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 old_client_after_edge, bool = false); |
| |
| protected: |
| virtual void AddOverflowFromChildren(); |
| void AddOverflowFromPositionedObjects(); |
| void AddOverflowFromBlockChildren(); |
| void AddVisualOverflowFromTheme(); |
| |
| void AddOutlineRects(Vector<LayoutRect>&, |
| const LayoutPoint& additional_offset, |
| IncludeBlockVisualOverflowOrNot) const override; |
| |
| void UpdateBlockChildDirtyBitsBeforeLayout(bool relayout_children, |
| 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(); |
| } |
| |
| bool NeedsPreferredWidthsRecalculation() const override; |
| |
| 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 IsInSelfHitTestingPhase(HitTestAction hit_test_action) const final { |
| return hit_test_action == kHitTestBlockBackground || |
| hit_test_action == kHitTestChildBlockBackground; |
| } |
| |
| bool IsPointInOverflowControl(HitTestResult&, |
| const LayoutPoint& location_in_container, |
| const LayoutPoint& accumulated_offset) const; |
| |
| void ComputeBlockPreferredLogicalWidths(LayoutUnit& min_logical_width, |
| LayoutUnit& max_logical_width) const; |
| |
| public: |
| bool ShouldPaintCursorCaret() const; |
| bool ShouldPaintDragCaret() const; |
| bool ShouldPaintCarets() const { |
| return ShouldPaintCursorCaret() || ShouldPaintDragCaret(); |
| } |
| |
| protected: |
| PaintInvalidationReason InvalidatePaint( |
| const PaintInvalidatorContext&) const override; |
| |
| void ClearPreviousVisualRects() override; |
| |
| private: |
| LayoutRect LocalCaretRect( |
| InlineBox*, |
| int caret_offset, |
| LayoutUnit* extra_width_to_end_of_line = 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: |
| // 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 logical_bottom_offset_after_pagination); |
| |
| // Adjust from painting offsets to the local coords of this layoutObject |
| void OffsetForContents(LayoutPoint&) const; |
| |
| PositionWithAffinity PositionForPointRespectingEditingBoundaries( |
| LineLayoutBox child, |
| const LayoutPoint& point_in_parent_coordinates); |
| PositionWithAffinity PositionForPointIfOutsideAtomicInlineLevel( |
| const LayoutPoint&); |
| |
| virtual bool UpdateLogicalWidthAndColumnWidth(); |
| |
| LayoutObjectChildList children_; |
| |
| unsigned |
| has_margin_before_quirk_ : 1; // Note these quirk values can't be put |
| // in LayoutBlockRareData since they are |
| // set too frequently. |
| unsigned has_margin_after_quirk_ : 1; |
| unsigned being_destroyed_ : 1; |
| unsigned has_markup_truncation_ : 1; |
| unsigned width_available_to_children_changed_ : 1; |
| unsigned height_available_to_children_changed_ : 1; |
| unsigned is_self_collapsing_ : 1; // True if margin-before and margin-after |
| // are adjoining. |
| unsigned descendants_with_floats_marked_for_layout_ : 1; |
| |
| unsigned has_positioned_objects_ : 1; |
| unsigned has_percent_height_descendants_ : 1; |
| |
| // When an object ceases to establish a fragmentation context (e.g. the |
| // LayoutView when we're no longer printing), we need a deep layout |
| // afterwards, to clear all pagination struts. Likewise, when an object |
| // becomes fragmented, we need to re-lay out the entire subtree. There might |
| // be forced breaks somewhere in there that we suddenly have to pay attention |
| // to, for all we know. |
| unsigned pagination_state_changed_ : 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 NGBlockNode; |
| |
| public: |
| // TODO(loonybear): 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 |