blob: 804954fe785c3c19de25958bdde63af399a34810 [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(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