blob: 36b593b6f1526028de6dee67eca448d399593cb4 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2007 David Smith (catfish.man@gmail.com)
* Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 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(LayoutBlock*, 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