BlockFormattingContext.cpp [plain text]
#include "config.h"
#include "BlockFormattingContext.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "BlockFormattingState.h"
#include "FloatingContext.h"
#include "FloatingState.h"
#include "InvalidationState.h"
#include "LayoutBox.h"
#include "LayoutChildIterator.h"
#include "LayoutContainerBox.h"
#include "LayoutContext.h"
#include "LayoutInitialContainingBlock.h"
#include "LayoutState.h"
#include "Logging.h"
#include "TableWrapperBlockFormattingContext.h"
#include <wtf/IsoMallocInlines.h>
#include <wtf/text/TextStream.h>
namespace WebCore {
namespace Layout {
WTF_MAKE_ISO_ALLOCATED_IMPL(BlockFormattingContext);
BlockFormattingContext::BlockFormattingContext(const ContainerBox& formattingContextRoot, BlockFormattingState& formattingState)
: FormattingContext(formattingContextRoot, formattingState)
{
}
void BlockFormattingContext::layoutInFlowContent(InvalidationState& invalidationState, const ConstraintsForInFlowContent& constraints)
{
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Start] -> block formatting context -> formatting root(" << &root() << ")");
auto& formattingRoot = root();
ASSERT(formattingRoot.hasInFlowOrFloatingChild());
auto& floatingState = formattingState().floatingState();
auto floatingContext = FloatingContext { *this, floatingState };
LayoutQueue layoutQueue;
enum class LayoutDirection { Child, Sibling };
auto appendNextToLayoutQueue = [&] (const auto& layoutBox, auto direction) {
if (direction == LayoutDirection::Child) {
if (!is<ContainerBox>(layoutBox))
return false;
for (auto* child = downcast<ContainerBox>(layoutBox).firstInFlowOrFloatingChild(); child; child = child->nextInFlowOrFloatingSibling()) {
if (!invalidationState.needsLayout(*child))
continue;
layoutQueue.append(child);
return true;
}
return false;
}
if (direction == LayoutDirection::Sibling) {
for (auto* nextSibling = layoutBox.nextInFlowOrFloatingSibling(); nextSibling; nextSibling = nextSibling->nextInFlowOrFloatingSibling()) {
if (!invalidationState.needsLayout(*nextSibling))
continue;
layoutQueue.append(nextSibling);
return true;
}
return false;
}
ASSERT_NOT_REACHED();
return false;
};
auto constraintsForLayoutBox = [&] (const auto& layoutBox) {
auto& containingBlock = layoutBox.containingBlock();
return &containingBlock == &formattingRoot ? constraints : geometry().constraintsForInFlowContent(containingBlock);
};
appendNextToLayoutQueue(formattingRoot, LayoutDirection::Child);
while (!layoutQueue.isEmpty()) {
while (true) {
auto& layoutBox = *layoutQueue.last();
auto containingBlockConstraints = constraintsForLayoutBox(layoutBox);
computeBorderAndPadding(layoutBox, containingBlockConstraints.horizontal);
computeStaticVerticalPosition(layoutBox, containingBlockConstraints.vertical);
computeWidthAndMargin(floatingContext, layoutBox, { constraints, containingBlockConstraints });
computeStaticHorizontalPosition(layoutBox, containingBlockConstraints.horizontal);
computePositionToAvoidFloats(floatingContext, layoutBox, { constraints, containingBlockConstraints });
if (layoutBox.establishesFormattingContext()) {
if (is<ContainerBox>(layoutBox) && downcast<ContainerBox>(layoutBox).hasInFlowOrFloatingChild()) {
auto& containerBox = downcast<ContainerBox>(layoutBox);
if (containerBox.establishesInlineFormattingContext()) {
precomputeVerticalPositionForBoxAndAncestors(containerBox, { constraints, containingBlockConstraints });
}
auto formattingContext = LayoutContext::createFormattingContext(containerBox, layoutState());
if (containerBox.isTableWrapperBox())
downcast<TableWrapperBlockFormattingContext>(*formattingContext).setHorizontalConstraintsIgnoringFloats(containingBlockConstraints.horizontal);
formattingContext->layoutInFlowContent(invalidationState, geometry().constraintsForInFlowContent(containerBox));
}
break;
}
if (!appendNextToLayoutQueue(layoutBox, LayoutDirection::Child))
break;
}
while (!layoutQueue.isEmpty()) {
auto& layoutBox = *layoutQueue.takeLast();
auto containingBlockConstraints = constraintsForLayoutBox(layoutBox);
computeHeightAndMargin(layoutBox, containingBlockConstraints);
if (layoutBox.isFloatingPositioned())
floatingState.append(floatingContext.toFloatItem(layoutBox));
else {
if (!layoutBox.isFloatAvoider() || floatingContext.isEmpty()) {
auto& formattingState = this->formattingState();
auto& boxGeometry = formattingState.boxGeometry(layoutBox);
boxGeometry.setLogicalTop(verticalPositionWithMargin(layoutBox, formattingState.usedVerticalMargin(layoutBox), containingBlockConstraints.vertical));
}
}
auto establishesFormattingContext = layoutBox.establishesFormattingContext();
if (establishesFormattingContext) {
if (is<ContainerBox>(layoutBox) && downcast<ContainerBox>(layoutBox).hasChild()) {
auto& containerBox = downcast<ContainerBox>(layoutBox);
LayoutContext::createFormattingContext(containerBox, layoutState())->layoutOutOfFlowContent(invalidationState, geometry().constraintsForOutOfFlowContent(containerBox));
}
}
if (!establishesFormattingContext && is<ContainerBox>(layoutBox))
placeInFlowPositionedChildren(downcast<ContainerBox>(layoutBox), containingBlockConstraints.horizontal);
if (appendNextToLayoutQueue(layoutBox, LayoutDirection::Sibling))
break;
}
}
placeInFlowPositionedChildren(formattingRoot, constraints.horizontal);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[End] -> block formatting context -> formatting root(" << &root() << ")");
}
LayoutUnit BlockFormattingContext::usedContentHeight() const
{
auto top = Optional<LayoutUnit> { };
auto bottom = Optional<LayoutUnit> { };
if (root().hasInFlowChild()) {
top = BoxGeometry::marginBoxRect(geometryForBox(*root().firstInFlowChild())).top();
bottom = BoxGeometry::marginBoxRect(geometryForBox(*root().lastInFlowChild())).bottom();
}
auto floatingContext = FloatingContext { *this, formattingState().floatingState() };
if (auto floatTop = floatingContext.top()) {
top = std::min(*floatTop, top.valueOr(*floatTop));
auto floatBottom = *floatingContext.bottom();
bottom = std::max(floatBottom, bottom.valueOr(floatBottom));
}
return *bottom - *top;
}
Optional<LayoutUnit> BlockFormattingContext::usedAvailableWidthForFloatAvoider(const FloatingContext& floatingContext, const Box& layoutBox, const ConstraintsPair& constraintsPair)
{
ASSERT(layoutBox.isFloatAvoider());
if (floatingContext.isEmpty())
return { };
if (layoutBox.hasFloatClear())
return { };
ASSERT(layoutBox.establishesFormattingContext());
precomputeVerticalPositionForBoxAndAncestors(layoutBox, constraintsPair);
auto logicalTopInFormattingContextRootCoordinate = [&] (auto& floatAvoider) {
auto top = BoxGeometry::borderBoxTop(geometryForBox(floatAvoider));
for (auto* ancestor = &floatAvoider.containingBlock(); ancestor != &root(); ancestor = &ancestor->containingBlock())
top += BoxGeometry::borderBoxTop(geometryForBox(*ancestor));
return top;
};
auto floatConstraintsInContainingBlockCoordinate = [&] (auto floatConstraints) {
if (!floatConstraints.left && !floatConstraints.right)
return FloatingContext::Constraints { };
auto offset = LayoutSize { };
for (auto* ancestor = &layoutBox.containingBlock(); ancestor != &root(); ancestor = &ancestor->containingBlock())
offset += toLayoutSize(BoxGeometry::borderBoxTopLeft(geometryForBox(*ancestor)));
if (floatConstraints.left)
floatConstraints.left = PointInContextRoot { *floatConstraints.left - offset };
if (floatConstraints.right)
floatConstraints.right = PointInContextRoot { *floatConstraints.right - offset };
return floatConstraints;
};
auto logicalTop = logicalTopInFormattingContextRootCoordinate(layoutBox);
auto constraints = floatConstraintsInContainingBlockCoordinate(floatingContext.constraints(logicalTop, logicalTop));
if (!constraints.left && !constraints.right)
return { };
auto availableWidth = constraintsPair.containingBlock.horizontal.logicalWidth;
if (constraints.left)
availableWidth -= constraints.left->x;
if (constraints.right)
availableWidth -= std::max(0_lu, constraintsPair.containingBlock.horizontal.logicalRight() - constraints.right->x);
return availableWidth;
}
void BlockFormattingContext::placeInFlowPositionedChildren(const ContainerBox& containerBox, const HorizontalConstraints& horizontalConstraints)
{
LOG_WITH_STREAM(FormattingContextLayout, stream << "Start: move in-flow positioned children -> parent: " << &containerBox);
for (auto& childBox : childrenOfType<Box>(containerBox)) {
if (!childBox.isInFlowPositioned())
continue;
auto positionOffset = geometry().inFlowPositionedPositionOffset(childBox, horizontalConstraints);
formattingState().boxGeometry(childBox).move(positionOffset);
}
LOG_WITH_STREAM(FormattingContextLayout, stream << "End: move in-flow positioned children -> parent: " << &containerBox);
}
void BlockFormattingContext::computeStaticVerticalPosition(const Box& layoutBox, const VerticalConstraints& verticalConstraints)
{
formattingState().boxGeometry(layoutBox).setLogicalTop(geometry().staticVerticalPosition(layoutBox, verticalConstraints));
}
void BlockFormattingContext::computeStaticHorizontalPosition(const Box& layoutBox, const HorizontalConstraints& horizontalConstraints)
{
formattingState().boxGeometry(layoutBox).setLogicalLeft(geometry().staticHorizontalPosition(layoutBox, horizontalConstraints));
}
void BlockFormattingContext::precomputeVerticalPositionForBoxAndAncestors(const Box& layoutBox, const ConstraintsPair& constraintsPair)
{
for (auto* ancestor = &layoutBox; ancestor && ancestor != &root(); ancestor = &ancestor->containingBlock()) {
auto constraintsForAncestor = [&] {
auto& containingBlock = ancestor->containingBlock();
return &containingBlock == &root() ? constraintsPair.formattingContextRoot : geometry().constraintsForInFlowContent(containingBlock);
}();
auto computedVerticalMargin = geometry().computedVerticalMargin(*ancestor, constraintsForAncestor.horizontal);
auto usedNonCollapsedMargin = UsedVerticalMargin::NonCollapsedValues { computedVerticalMargin.before.valueOr(0), computedVerticalMargin.after.valueOr(0) };
auto precomputedMarginBefore = marginCollapse().precomputedMarginBefore(*ancestor, usedNonCollapsedMargin);
auto& boxGeometry = formattingState().boxGeometry(*ancestor);
auto nonCollapsedValues = UsedVerticalMargin::NonCollapsedValues { precomputedMarginBefore.nonCollapsedValue, { } };
auto collapsedValues = UsedVerticalMargin::CollapsedValues { precomputedMarginBefore.collapsedValue, { }, false };
auto verticalMargin = UsedVerticalMargin { nonCollapsedValues, collapsedValues, { precomputedMarginBefore.positiveAndNegativeMarginBefore, { } } };
formattingState().setUsedVerticalMargin(*ancestor, verticalMargin);
boxGeometry.setVerticalMargin({ marginBefore(verticalMargin), marginAfter(verticalMargin) });
boxGeometry.setLogicalTop(verticalPositionWithMargin(*ancestor, verticalMargin, constraintsForAncestor.vertical));
#if ASSERT_ENABLED
setPrecomputedMarginBefore(*ancestor, precomputedMarginBefore);
boxGeometry.setHasPrecomputedMarginBefore();
#endif
}
}
void BlockFormattingContext::computePositionToAvoidFloats(const FloatingContext& floatingContext, const Box& layoutBox, const ConstraintsPair& constraintsPair)
{
if (!layoutBox.isFloatAvoider())
return;
if (layoutBox.isFloatingPositioned()) {
precomputeVerticalPositionForBoxAndAncestors(layoutBox, constraintsPair);
formattingState().boxGeometry(layoutBox).setLogicalTopLeft(floatingContext.positionForFloat(layoutBox, constraintsPair.containingBlock.horizontal));
return;
}
if (floatingContext.isEmpty())
return;
precomputeVerticalPositionForBoxAndAncestors(layoutBox, constraintsPair);
if (layoutBox.hasFloatClear())
return computeVerticalPositionForFloatClear(floatingContext, layoutBox);
ASSERT(layoutBox.establishesFormattingContext());
formattingState().boxGeometry(layoutBox).setLogicalTopLeft(floatingContext.positionForNonFloatingFloatAvoider(layoutBox, constraintsPair.containingBlock.horizontal));
}
void BlockFormattingContext::computeVerticalPositionForFloatClear(const FloatingContext& floatingContext, const Box& layoutBox)
{
ASSERT(layoutBox.hasFloatClear());
if (floatingContext.isEmpty())
return;
auto verticalPositionAndClearance = floatingContext.verticalPositionWithClearance(layoutBox);
if (!verticalPositionAndClearance)
return;
auto& boxGeometry = formattingState().boxGeometry(layoutBox);
ASSERT(verticalPositionAndClearance->position >= BoxGeometry::borderBoxTop(boxGeometry));
boxGeometry.setLogicalTop(verticalPositionAndClearance->position);
if (verticalPositionAndClearance->clearance)
formattingState().setHasClearance(layoutBox);
}
void BlockFormattingContext::computeWidthAndMargin(const FloatingContext& floatingContext, const Box& layoutBox, const ConstraintsPair& constraintsPair)
{
auto availableWidthFloatAvoider = Optional<LayoutUnit> { };
if (layoutBox.isFloatAvoider()) {
availableWidthFloatAvoider = usedAvailableWidthForFloatAvoider(floatingContext, layoutBox, constraintsPair);
}
auto contentWidthAndMargin = geometry().computedContentWidthAndMargin(layoutBox, constraintsPair.containingBlock.horizontal, availableWidthFloatAvoider);
auto& boxGeometry = formattingState().boxGeometry(layoutBox);
boxGeometry.setContentBoxWidth(contentWidthAndMargin.contentWidth);
boxGeometry.setHorizontalMargin({ contentWidthAndMargin.usedMargin.start, contentWidthAndMargin.usedMargin.end });
}
void BlockFormattingContext::computeHeightAndMargin(const Box& layoutBox, const ConstraintsForInFlowContent& constraints)
{
auto compute = [&](Optional<LayoutUnit> usedHeight) -> ContentHeightAndMargin {
if (layoutBox.isInFlow())
return geometry().inFlowContentHeightAndMargin(layoutBox, constraints.horizontal, { usedHeight });
if (layoutBox.isFloatingPositioned())
return geometry().floatingContentHeightAndMargin(layoutBox, constraints.horizontal, { usedHeight });
ASSERT_NOT_REACHED();
return { };
};
auto contentHeightAndMargin = compute({ });
if (auto maxHeight = geometry().computedMaxHeight(layoutBox)) {
if (contentHeightAndMargin.contentHeight > *maxHeight) {
auto maxHeightAndMargin = compute(maxHeight);
ASSERT((layoutState().inQuirksMode() && (layoutBox.isBodyBox() || layoutBox.isDocumentBox())) || maxHeightAndMargin.contentHeight == *maxHeight);
contentHeightAndMargin = { *maxHeight, maxHeightAndMargin.nonCollapsedMargin };
}
}
if (auto minHeight = geometry().computedMinHeight(layoutBox)) {
if (contentHeightAndMargin.contentHeight < *minHeight) {
auto minHeightAndMargin = compute(minHeight);
ASSERT((layoutState().inQuirksMode() && (layoutBox.isBodyBox() || layoutBox.isDocumentBox())) || minHeightAndMargin.contentHeight == *minHeight);
contentHeightAndMargin = { *minHeight, minHeightAndMargin.nonCollapsedMargin };
}
}
auto marginCollapse = this->marginCollapse();
auto verticalMargin = marginCollapse.collapsedVerticalValues(layoutBox, contentHeightAndMargin.nonCollapsedMargin);
formattingState().setUsedVerticalMargin(layoutBox, verticalMargin);
#if ASSERT_ENABLED
if (hasPrecomputedMarginBefore(layoutBox) && precomputedMarginBefore(layoutBox).usedValue() != marginBefore(verticalMargin)) {
ASSERT_NOT_IMPLEMENTED_YET();
}
#endif
auto& boxGeometry = formattingState().boxGeometry(layoutBox);
boxGeometry.setContentBoxHeight(contentHeightAndMargin.contentHeight);
boxGeometry.setVerticalMargin({ marginBefore(verticalMargin), marginAfter(verticalMargin) });
MarginCollapse::updateMarginAfterForPreviousSibling(*this, marginCollapse, layoutBox);
}
FormattingContext::IntrinsicWidthConstraints BlockFormattingContext::computedIntrinsicWidthConstraints()
{
auto& formattingState = this->formattingState();
ASSERT(!formattingState.intrinsicWidthConstraints());
Vector<const Box*> queue;
if (root().hasInFlowOrFloatingChild())
queue.append(root().firstInFlowOrFloatingChild());
IntrinsicWidthConstraints constraints;
auto maximumHorizontalStackingWidth = LayoutUnit { };
auto currentHorizontalStackingWidth = LayoutUnit { };
while (!queue.isEmpty()) {
while (true) {
auto& layoutBox = *queue.last();
if (layoutBox.isFloatAvoider() && !layoutBox.hasFloatClear())
break;
maximumHorizontalStackingWidth = std::max(currentHorizontalStackingWidth, maximumHorizontalStackingWidth);
currentHorizontalStackingWidth = { };
if (formattingState.intrinsicWidthConstraintsForBox(layoutBox))
break;
if (layoutBox.style().width().isFixed())
break;
if (layoutBox.establishesFormattingContext())
break;
if (!is<ContainerBox>(layoutBox) || !downcast<ContainerBox>(layoutBox).hasInFlowOrFloatingChild())
break;
queue.append(downcast<ContainerBox>(layoutBox).firstInFlowOrFloatingChild());
}
while (!queue.isEmpty()) {
auto& layoutBox = *queue.takeLast();
auto desdendantConstraints = formattingState.intrinsicWidthConstraintsForBox(layoutBox);
if (!desdendantConstraints) {
desdendantConstraints = geometry().intrinsicWidthConstraints(layoutBox);
formattingState.setIntrinsicWidthConstraintsForBox(layoutBox, *desdendantConstraints);
}
constraints.minimum = std::max(constraints.minimum, desdendantConstraints->minimum);
auto willStackHorizontally = layoutBox.isFloatAvoider() && !layoutBox.hasFloatClear();
if (willStackHorizontally)
currentHorizontalStackingWidth += desdendantConstraints->maximum;
else
constraints.maximum = std::max(constraints.maximum, desdendantConstraints->maximum);
if (auto* nextSibling = layoutBox.nextInFlowOrFloatingSibling()) {
queue.append(nextSibling);
break;
}
}
}
maximumHorizontalStackingWidth = std::max(currentHorizontalStackingWidth, maximumHorizontalStackingWidth);
constraints.maximum = std::max(constraints.maximum, maximumHorizontalStackingWidth);
formattingState.setIntrinsicWidthConstraints(constraints);
return constraints;
}
LayoutUnit BlockFormattingContext::verticalPositionWithMargin(const Box& layoutBox, const UsedVerticalMargin& verticalMargin, const VerticalConstraints& verticalConstraints) const
{
ASSERT(!layoutBox.isOutOfFlowPositioned());
auto& boxGeometry = geometryForBox(layoutBox);
if (formattingState().hasClearance(layoutBox))
return BoxGeometry::borderBoxTop(boxGeometry);
auto* currentLayoutBox = &layoutBox;
while (currentLayoutBox) {
if (!currentLayoutBox->previousInFlowSibling())
break;
auto& previousInFlowSibling = *currentLayoutBox->previousInFlowSibling();
if (!marginCollapse().marginBeforeCollapsesWithPreviousSiblingMarginAfter(*currentLayoutBox)) {
auto& previousBoxGeometry = geometryForBox(previousInFlowSibling);
return BoxGeometry::marginBoxRect(previousBoxGeometry).bottom() + marginBefore(verticalMargin);
}
if (!marginCollapse().marginsCollapseThrough(previousInFlowSibling)) {
auto& previousBoxGeometry = geometryForBox(previousInFlowSibling);
return BoxGeometry::borderBoxRect(previousBoxGeometry).bottom() + marginBefore(verticalMargin);
}
currentLayoutBox = &previousInFlowSibling;
}
auto containingBlockContentBoxTop = verticalConstraints.logicalTop;
auto directlyAdjoinsParent = !layoutBox.previousInFlowSibling();
if (directlyAdjoinsParent) {
if (verticalMargin.collapsedValues.isCollapsedThrough) {
if (marginCollapse().marginBeforeCollapsesWithParentMarginBefore(layoutBox))
return containingBlockContentBoxTop;
auto beforeMarginWithBottomBorder = marginCollapse().marginBeforeIgnoringCollapsingThrough(layoutBox, verticalMargin.nonCollapsedValues);
return containingBlockContentBoxTop + beforeMarginWithBottomBorder;
}
if (marginCollapse().marginBeforeCollapsesWithParentMarginBefore(layoutBox))
return containingBlockContentBoxTop;
return containingBlockContentBoxTop + marginBefore(verticalMargin);
}
auto& containingBlock = layoutBox.containingBlock();
ASSERT(containingBlock.firstInFlowChild());
ASSERT(containingBlock.firstInFlowChild() != &layoutBox);
if (marginCollapse().marginBeforeCollapsesWithParentMarginBefore(*containingBlock.firstInFlowChild()))
return containingBlockContentBoxTop;
return containingBlockContentBoxTop + marginBefore(verticalMargin);
}
}
}
#endif