BlockFormattingContext.cpp [plain text]
#include "config.h"
#include "BlockFormattingContext.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "BlockFormattingState.h"
#include "DisplayBox.h"
#include "FloatingContext.h"
#include "FloatingState.h"
#include "LayoutBox.h"
#include "LayoutContainer.h"
#include "LayoutState.h"
#include "Logging.h"
#include <wtf/IsoMallocInlines.h>
#include <wtf/text/TextStream.h>
namespace WebCore {
namespace Layout {
WTF_MAKE_ISO_ALLOCATED_IMPL(BlockFormattingContext);
BlockFormattingContext::BlockFormattingContext(const Box& formattingContextRoot, FormattingState& formattingState)
: FormattingContext(formattingContextRoot, formattingState)
{
}
void BlockFormattingContext::layout() const
{
if (!is<Container>(root()))
return;
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Start] -> block formatting context -> formatting root(" << &root() << ")");
auto& formattingRoot = downcast<Container>(root());
LayoutQueue layoutQueue;
FloatingContext floatingContext(formattingState().floatingState());
if (auto* firstChild = formattingRoot.firstInFlowOrFloatingChild())
layoutQueue.append(firstChild);
while (!layoutQueue.isEmpty()) {
while (true) {
auto& layoutBox = *layoutQueue.last();
if (layoutBox.establishesFormattingContext()) {
layoutFormattingContextRoot(floatingContext, layoutBox);
layoutQueue.removeLast();
if (!layoutBox.nextInFlowOrFloatingSibling())
break;
layoutQueue.append(layoutBox.nextInFlowOrFloatingSibling());
continue;
}
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Compute] -> [Position][Border][Padding][Width][Margin] -> for layoutBox(" << &layoutBox << ")");
computeStaticPosition(layoutBox);
computeBorderAndPadding(layoutBox);
computeWidthAndMargin(layoutBox);
if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
break;
layoutQueue.append(downcast<Container>(layoutBox).firstInFlowOrFloatingChild());
}
while (!layoutQueue.isEmpty()) {
auto& layoutBox = *layoutQueue.takeLast();
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Compute] -> [Height][Margin] -> for layoutBox(" << &layoutBox << ")");
ASSERT(!layoutBox.establishesFormattingContext());
computeHeightAndMargin(layoutBox);
if (layoutBox.hasFloatClear())
computeVerticalPositionForFloatClear(floatingContext, layoutBox);
if (!is<Container>(layoutBox))
continue;
auto& container = downcast<Container>(layoutBox);
placeInFlowPositionedChildren(container);
if (auto* nextSibling = container.nextInFlowOrFloatingSibling()) {
layoutQueue.append(nextSibling);
break;
}
}
}
placeInFlowPositionedChildren(formattingRoot);
LOG_WITH_STREAM(FormattingContextLayout, stream << "[End] -> block formatting context -> formatting root(" << &root() << ")");
}
void BlockFormattingContext::layoutFormattingContextRoot(FloatingContext& floatingContext, const Box& layoutBox) const
{
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Compute] -> [Position][Border][Padding][Width][Margin] -> for layoutBox(" << &layoutBox << ")");
computeStaticPosition(layoutBox);
computeBorderAndPadding(layoutBox);
computeWidthAndMargin(layoutBox);
precomputeVerticalPositionForFormattingRootIfNeeded(layoutBox);
auto formattingContext = layoutState().createFormattingStateForFormattingRootIfNeeded(layoutBox).createFormattingContext(layoutBox);
formattingContext->layout();
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Compute] -> [Height][Margin] -> for layoutBox(" << &layoutBox << ")");
computeHeightAndMargin(layoutBox);
if (layoutBox.isFloatingPositioned()) {
computeFloatingPosition(floatingContext, layoutBox);
floatingContext.floatingState().append(layoutBox);
} else if (layoutBox.hasFloatClear())
computeVerticalPositionForFloatClear(floatingContext, layoutBox);
else if (layoutBox.establishesBlockFormattingContext())
computePositionToAvoidFloats(floatingContext, layoutBox);
formattingContext->layoutOutOfFlowDescendants(layoutBox);
}
void BlockFormattingContext::placeInFlowPositionedChildren(const Container& container) const
{
LOG_WITH_STREAM(FormattingContextLayout, stream << "Start: move in-flow positioned children -> parent: " << &container);
for (auto& layoutBox : childrenOfType<Box>(container)) {
if (!layoutBox.isInFlowPositioned())
continue;
auto computeInFlowPositionedPosition = [&](auto& layoutBox) {
auto& layoutState = this->layoutState();
auto positionOffset = Geometry::inFlowPositionedPositionOffset(layoutState, layoutBox);
auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
auto topLeft = displayBox.topLeft();
topLeft.move(positionOffset);
displayBox.setTopLeft(topLeft);
};
computeInFlowPositionedPosition(layoutBox);
}
LOG_WITH_STREAM(FormattingContextLayout, stream << "End: move in-flow positioned children -> parent: " << &container);
}
void BlockFormattingContext::computeStaticPosition(const Box& layoutBox) const
{
auto& layoutState = this->layoutState();
layoutState.displayBoxForLayoutBox(layoutBox).setTopLeft(Geometry::staticPosition(layoutState, layoutBox));
}
void BlockFormattingContext::computeEstimatedMarginBefore(const Box& layoutBox) const
{
auto& layoutState = this->layoutState();
auto estimatedMarginBefore = Geometry::estimatedMarginBefore(layoutState, layoutBox);
auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
displayBox.setEstimatedMarginBefore(estimatedMarginBefore);
displayBox.moveVertically(estimatedMarginBefore);
}
void BlockFormattingContext::computeEstimatedMarginBeforeForAncestors(const Box& layoutBox) const
{
ASSERT(layoutBox.isFloatingPositioned() || layoutBox.hasFloatClear() || layoutBox.establishesBlockFormattingContext() || layoutBox.establishesInlineFormattingContext());
auto& layoutState = this->layoutState();
for (auto* ancestor = layoutBox.containingBlock(); ancestor && !ancestor->establishesBlockFormattingContext(); ancestor = ancestor->containingBlock()) {
auto& displayBox = layoutState.displayBoxForLayoutBox(*ancestor);
if (displayBox.estimatedMarginBefore())
return;
computeEstimatedMarginBefore(*ancestor);
}
}
void BlockFormattingContext::precomputeVerticalPositionForFormattingRootIfNeeded(const Box& layoutBox) const
{
ASSERT(layoutBox.establishesFormattingContext());
auto avoidsFloats = layoutBox.isFloatingPositioned() || layoutBox.establishesBlockFormattingContext() || layoutBox.hasFloatClear();
if (avoidsFloats)
computeEstimatedMarginBeforeForAncestors(layoutBox);
auto inlineContextInheritsFloats = layoutBox.establishesInlineFormattingContext() && !layoutBox.establishesBlockFormattingContext();
if (inlineContextInheritsFloats) {
computeEstimatedMarginBefore(layoutBox);
computeEstimatedMarginBeforeForAncestors(layoutBox);
}
}
#ifndef NDEBUG
static bool hasPrecomputedMarginBefore(const LayoutState& layoutState, const Box& layoutBox)
{
for (auto* ancestor = layoutBox.containingBlock(); ancestor && !ancestor->establishesBlockFormattingContext(); ancestor = ancestor->containingBlock()) {
if (layoutState.displayBoxForLayoutBox(*ancestor).estimatedMarginBefore())
continue;
return false;
}
return true;
}
#endif
void BlockFormattingContext::computeFloatingPosition(const FloatingContext& floatingContext, const Box& layoutBox) const
{
auto& layoutState = this->layoutState();
ASSERT(layoutBox.isFloatingPositioned());
ASSERT(hasPrecomputedMarginBefore(layoutState, layoutBox));
auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
if (auto* previousInFlowBox = layoutBox.previousInFlowSibling()) {
auto& previousDisplayBox = layoutState.displayBoxForLayoutBox(*previousInFlowBox);
displayBox.moveVertically(previousDisplayBox.nonCollapsedMarginAfter() - previousDisplayBox.marginAfter());
}
displayBox.setTopLeft(floatingContext.positionForFloat(layoutBox));
}
void BlockFormattingContext::computePositionToAvoidFloats(const FloatingContext& floatingContext, const Box& layoutBox) const
{
auto& layoutState = this->layoutState();
ASSERT(layoutBox.establishesBlockFormattingContext());
ASSERT(!layoutBox.isFloatingPositioned());
ASSERT(!layoutBox.hasFloatClear());
ASSERT(hasPrecomputedMarginBefore(layoutState, layoutBox));
if (floatingContext.floatingState().isEmpty())
return;
if (auto adjustedPosition = floatingContext.positionForFloatAvoiding(layoutBox))
layoutState.displayBoxForLayoutBox(layoutBox).setTopLeft(*adjustedPosition);
}
void BlockFormattingContext::computeVerticalPositionForFloatClear(const FloatingContext& floatingContext, const Box& layoutBox) const
{
ASSERT(layoutBox.hasFloatClear());
if (floatingContext.floatingState().isEmpty())
return;
auto& layoutState = this->layoutState();
if (!layoutBox.establishesFormattingContext())
computeEstimatedMarginBeforeForAncestors(layoutBox);
ASSERT(hasPrecomputedMarginBefore(layoutState, layoutBox));
if (auto verticalPositionWithClearance = floatingContext.verticalPositionWithClearance(layoutBox))
layoutState.displayBoxForLayoutBox(layoutBox).setTop(*verticalPositionWithClearance);
}
void BlockFormattingContext::computeWidthAndMargin(const Box& layoutBox) const
{
auto& layoutState = this->layoutState();
auto compute = [&](Optional<LayoutUnit> usedWidth) -> WidthAndMargin {
if (layoutBox.isInFlow())
return Geometry::inFlowWidthAndMargin(layoutState, layoutBox, usedWidth);
if (layoutBox.isFloatingPositioned())
return Geometry::floatingWidthAndMargin(layoutState, layoutBox, usedWidth);
ASSERT_NOT_REACHED();
return { };
};
auto widthAndMargin = compute({ });
auto containingBlockWidth = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
if (auto maxWidth = Geometry::computedValueIfNotAuto(layoutBox.style().logicalMaxWidth(), containingBlockWidth)) {
auto maxWidthAndMargin = compute(maxWidth);
if (widthAndMargin.width > maxWidthAndMargin.width)
widthAndMargin = maxWidthAndMargin;
}
if (auto minWidth = Geometry::computedValueIfNotAuto(layoutBox.style().logicalMinWidth(), containingBlockWidth)) {
auto minWidthAndMargin = compute(minWidth);
if (widthAndMargin.width < minWidthAndMargin.width)
widthAndMargin = minWidthAndMargin;
}
auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
displayBox.setContentBoxWidth(widthAndMargin.width);
displayBox.moveHorizontally(widthAndMargin.usedMargin.start);
displayBox.setHorizontalMargin(widthAndMargin.usedMargin);
displayBox.setHorizontalComputedMargin(widthAndMargin.computedMargin);
}
void BlockFormattingContext::computeHeightAndMargin(const Box& layoutBox) const
{
auto& layoutState = this->layoutState();
auto compute = [&](Optional<LayoutUnit> usedHeight) -> HeightAndMargin {
if (layoutBox.isInFlow())
return Geometry::inFlowHeightAndMargin(layoutState, layoutBox, usedHeight);
if (layoutBox.isFloatingPositioned())
return Geometry::floatingHeightAndMargin(layoutState, layoutBox, usedHeight);
ASSERT_NOT_REACHED();
return { };
};
auto heightAndMargin = compute({ });
if (auto maxHeight = Geometry::computedMaxHeight(layoutState, layoutBox)) {
if (heightAndMargin.height > *maxHeight) {
auto maxHeightAndMargin = compute(maxHeight);
ASSERT((layoutState.inQuirksMode() && (layoutBox.isBodyBox() || layoutBox.isDocumentBox())) || maxHeightAndMargin.height == *maxHeight);
heightAndMargin = { *maxHeight, maxHeightAndMargin.nonCollapsedMargin };
}
}
if (auto minHeight = Geometry::computedMinHeight(layoutState, layoutBox)) {
if (heightAndMargin.height < *minHeight) {
auto minHeightAndMargin = compute(minHeight);
ASSERT((layoutState.inQuirksMode() && (layoutBox.isBodyBox() || layoutBox.isDocumentBox())) || minHeightAndMargin.height == *minHeight);
heightAndMargin = { *minHeight, minHeightAndMargin.nonCollapsedMargin };
}
}
auto collapsedMargin = UsedVerticalMargin::CollapsedValues { MarginCollapse::marginBefore(layoutState, layoutBox), MarginCollapse::marginAfter(layoutState, layoutBox) };
auto verticalMargin = UsedVerticalMargin { heightAndMargin.nonCollapsedMargin, collapsedMargin };
auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
displayBox.setContentBoxHeight(heightAndMargin.height);
displayBox.setVerticalMargin(verticalMargin);
if (layoutBox.isFloatingPositioned() || !displayBox.estimatedMarginBefore())
displayBox.moveVertically(verticalMargin.before());
}
FormattingContext::InstrinsicWidthConstraints BlockFormattingContext::instrinsicWidthConstraints() const
{
auto& layoutState = this->layoutState();
auto& formattingRoot = root();
auto& formattingStateForRoot = layoutState.formattingStateForBox(formattingRoot);
if (auto instrinsicWidthConstraints = formattingStateForRoot.instrinsicWidthConstraints(formattingRoot))
return *instrinsicWidthConstraints;
if (!Geometry::instrinsicWidthConstraintsNeedChildrenWidth(formattingRoot)) {
auto instrinsicWidthConstraints = Geometry::instrinsicWidthConstraints(layoutState, formattingRoot);
formattingStateForRoot.setInstrinsicWidthConstraints(formattingRoot, instrinsicWidthConstraints);
return instrinsicWidthConstraints;
}
Vector<const Box*> queue;
ASSERT(is<Container>(formattingRoot));
if (auto* firstChild = downcast<Container>(formattingRoot).firstInFlowOrFloatingChild())
queue.append(firstChild);
auto& formattingState = this->formattingState();
while (!queue.isEmpty()) {
while (true) {
auto& childBox = *queue.last();
auto skipDescendants = formattingState.instrinsicWidthConstraints(childBox) || !Geometry::instrinsicWidthConstraintsNeedChildrenWidth(childBox) || childBox.establishesFormattingContext();
if (skipDescendants) {
InstrinsicWidthConstraints instrinsicWidthConstraints;
if (!Geometry::instrinsicWidthConstraintsNeedChildrenWidth(childBox))
instrinsicWidthConstraints = Geometry::instrinsicWidthConstraints(layoutState, childBox);
else if (childBox.establishesFormattingContext())
instrinsicWidthConstraints = layoutState.createFormattingStateForFormattingRootIfNeeded(childBox).createFormattingContext(childBox)->instrinsicWidthConstraints();
formattingState.setInstrinsicWidthConstraints(childBox, instrinsicWidthConstraints);
queue.removeLast();
if (!childBox.nextInFlowOrFloatingSibling())
break;
queue.append(childBox.nextInFlowOrFloatingSibling());
continue;
}
}
while (!queue.isEmpty()) {
auto& childBox = *queue.takeLast();
formattingState.setInstrinsicWidthConstraints(childBox, Geometry::instrinsicWidthConstraints(layoutState, childBox));
if (!is<Container>(childBox) || !downcast<Container>(childBox).nextInFlowOrFloatingSibling())
continue;
queue.append(downcast<Container>(childBox).nextInFlowOrFloatingSibling());
}
}
auto instrinsicWidthConstraints = Geometry::instrinsicWidthConstraints(layoutState, formattingRoot);
formattingStateForRoot.setInstrinsicWidthConstraints(formattingRoot, instrinsicWidthConstraints);
return instrinsicWidthConstraints;
}
}
}
#endif