FormattingContext.cpp [plain text]
#include "config.h"
#include "FormattingContext.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "FormattingState.h"
#include "InvalidationState.h"
#include "LayoutBox.h"
#include "LayoutBoxGeometry.h"
#include "LayoutContainerBox.h"
#include "LayoutContext.h"
#include "LayoutDescendantIterator.h"
#include "LayoutInitialContainingBlock.h"
#include "LayoutReplacedBox.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(FormattingContext);
FormattingContext::FormattingContext(const ContainerBox& formattingContextRoot, FormattingState& formattingState)
: m_root(makeWeakPtr(formattingContextRoot))
, m_formattingState(formattingState)
{
ASSERT(formattingContextRoot.hasChild());
#ifndef NDEBUG
layoutState().registerFormattingContext(*this);
#endif
}
FormattingContext::~FormattingContext()
{
#ifndef NDEBUG
layoutState().deregisterFormattingContext(*this);
#endif
}
LayoutState& FormattingContext::layoutState() const
{
return m_formattingState.layoutState();
}
void FormattingContext::computeOutOfFlowHorizontalGeometry(const Box& layoutBox, const ConstraintsForOutOfFlowContent& constraints)
{
ASSERT(layoutBox.isOutOfFlowPositioned());
auto compute = [&](Optional<LayoutUnit> usedWidth) {
return geometry().outOfFlowHorizontalGeometry(layoutBox, constraints.horizontal, constraints.vertical, { usedWidth, { } });
};
auto containingBlockWidth = constraints.horizontal.logicalWidth;
auto horizontalGeometry = compute({ });
if (auto maxWidth = geometry().computedMaxWidth(layoutBox, containingBlockWidth)) {
auto maxHorizontalGeometry = compute(maxWidth);
if (horizontalGeometry.contentWidthAndMargin.contentWidth > maxHorizontalGeometry.contentWidthAndMargin.contentWidth)
horizontalGeometry = maxHorizontalGeometry;
}
if (auto minWidth = geometry().computedMinWidth(layoutBox, containingBlockWidth)) {
auto minHorizontalGeometry = compute(minWidth);
if (horizontalGeometry.contentWidthAndMargin.contentWidth < minHorizontalGeometry.contentWidthAndMargin.contentWidth)
horizontalGeometry = minHorizontalGeometry;
}
auto& boxGeometry = formattingState().boxGeometry(layoutBox);
boxGeometry.setLogicalLeft(horizontalGeometry.left + horizontalGeometry.contentWidthAndMargin.usedMargin.start);
boxGeometry.setContentBoxWidth(horizontalGeometry.contentWidthAndMargin.contentWidth);
auto& usedHorizontalMargin = horizontalGeometry.contentWidthAndMargin.usedMargin;
boxGeometry.setHorizontalMargin({ usedHorizontalMargin.start, usedHorizontalMargin.end });
}
void FormattingContext::computeOutOfFlowVerticalGeometry(const Box& layoutBox, const ConstraintsForOutOfFlowContent& constraints)
{
ASSERT(layoutBox.isOutOfFlowPositioned());
auto compute = [&](Optional<LayoutUnit> usedHeight) {
return geometry().outOfFlowVerticalGeometry(layoutBox, constraints.horizontal, constraints.vertical, { usedHeight });
};
auto containingBlockHeight = *constraints.vertical.logicalHeight;
auto verticalGeometry = compute({ });
if (auto maxHeight = geometry().computedMaxHeight(layoutBox, containingBlockHeight)) {
auto maxVerticalGeometry = compute(maxHeight);
if (verticalGeometry.contentHeightAndMargin.contentHeight > maxVerticalGeometry.contentHeightAndMargin.contentHeight)
verticalGeometry = maxVerticalGeometry;
}
if (auto minHeight = geometry().computedMinHeight(layoutBox, containingBlockHeight)) {
auto minVerticalGeometry = compute(minHeight);
if (verticalGeometry.contentHeightAndMargin.contentHeight < minVerticalGeometry.contentHeightAndMargin.contentHeight)
verticalGeometry = minVerticalGeometry;
}
auto& boxGeometry = formattingState().boxGeometry(layoutBox);
auto nonCollapsedVerticalMargin = verticalGeometry.contentHeightAndMargin.nonCollapsedMargin;
boxGeometry.setLogicalTop(verticalGeometry.top + nonCollapsedVerticalMargin.before);
boxGeometry.setContentBoxHeight(verticalGeometry.contentHeightAndMargin.contentHeight);
boxGeometry.setVerticalMargin({ nonCollapsedVerticalMargin.before, nonCollapsedVerticalMargin.after });
}
void FormattingContext::computeBorderAndPadding(const Box& layoutBox, const HorizontalConstraints& horizontalConstraint)
{
auto& boxGeometry = formattingState().boxGeometry(layoutBox);
boxGeometry.setBorder(geometry().computedBorder(layoutBox));
boxGeometry.setPadding(geometry().computedPadding(layoutBox, horizontalConstraint.logicalWidth));
}
void FormattingContext::layoutOutOfFlowContent(InvalidationState& invalidationState, const ConstraintsForOutOfFlowContent& constraints)
{
LOG_WITH_STREAM(FormattingContextLayout, stream << "Start: layout out-of-flow content -> context: " << &layoutState() << " root: " << &root());
collectOutOfFlowDescendantsIfNeeded();
auto constraintsForLayoutBox = [&] (const auto& outOfFlowBox) {
auto& containingBlock = outOfFlowBox.containingBlock();
return &containingBlock == &root() ? constraints : geometry().constraintsForOutOfFlowContent(containingBlock);
};
for (auto& outOfFlowBox : formattingState().outOfFlowBoxes()) {
ASSERT(outOfFlowBox->establishesFormattingContext());
if (!invalidationState.needsLayout(*outOfFlowBox))
continue;
auto containingBlockConstraints = constraintsForLayoutBox(*outOfFlowBox);
auto horizontalConstraintsForBorderAndPadding = HorizontalConstraints { containingBlockConstraints.horizontal.logicalLeft, containingBlockConstraints.borderAndPaddingConstraints };
computeBorderAndPadding(*outOfFlowBox, horizontalConstraintsForBorderAndPadding);
computeOutOfFlowHorizontalGeometry(*outOfFlowBox, containingBlockConstraints);
auto outOfFlowBoxHasContent = is<ContainerBox>(*outOfFlowBox) && downcast<ContainerBox>(*outOfFlowBox).hasChild();
if (outOfFlowBoxHasContent) {
auto& containerBox = downcast<ContainerBox>(*outOfFlowBox);
auto formattingContext = LayoutContext::createFormattingContext(containerBox, layoutState());
if (containerBox.hasInFlowOrFloatingChild())
formattingContext->layoutInFlowContent(invalidationState, geometry().constraintsForInFlowContent(containerBox));
computeOutOfFlowVerticalGeometry(containerBox, containingBlockConstraints);
formattingContext->layoutOutOfFlowContent(invalidationState, geometry().constraintsForOutOfFlowContent(containerBox));
} else
computeOutOfFlowVerticalGeometry(*outOfFlowBox, containingBlockConstraints);
}
LOG_WITH_STREAM(FormattingContextLayout, stream << "End: layout out-of-flow content -> context: " << &layoutState() << " root: " << &root());
}
const BoxGeometry& FormattingContext::geometryForBox(const Box& layoutBox, Optional<EscapeReason> escapeReason) const
{
UNUSED_PARAM(escapeReason);
#if ASSERT_ENABLED
auto isOkToAccessBoxGeometry = [&] {
if (!is<InitialContainingBlock>(layoutBox) && &layoutBox.formattingContextRoot() == &root()) {
return true;
}
if (!escapeReason) {
return false;
}
if (*escapeReason == EscapeReason::DocumentBoxStretchesToViewportQuirk) {
ASSERT(layoutState().inQuirksMode());
return is<InitialContainingBlock>(layoutBox);
}
if (*escapeReason == EscapeReason::BodyStretchesToViewportQuirk) {
ASSERT(layoutState().inQuirksMode());
return is<InitialContainingBlock>(layoutBox) || layoutBox.isDocumentBox();
}
if (*escapeReason == EscapeReason::TableQuirkNeedsGeometryFromEstablishedFormattingContext) {
ASSERT(root().establishesTableFormattingContext());
return &layoutBox.formattingContextRoot().formattingContextRoot() == &root();
}
if (*escapeReason == EscapeReason::OutOfFlowBoxNeedsInFlowGeometry) {
return true;
}
if (*escapeReason == EscapeReason::FloatBoxIsAlwaysRelativeToFloatStateRoot) {
auto& formattingContextRoot = layoutBox.formattingContextRoot();
return &formattingContextRoot == &root() || &formattingContextRoot == &root().formattingContextRoot();
}
if (*escapeReason == EscapeReason::FindFixedHeightAncestorQuirk) {
ASSERT(layoutState().inQuirksMode());
if (is<InitialContainingBlock>(layoutBox))
return true;
auto& targetFormattingRoot = layoutBox.formattingContextRoot();
auto* ancestorFormattingContextRoot = &root().formattingContextRoot();
while (true) {
if (&targetFormattingRoot == ancestorFormattingContextRoot)
return true;
ancestorFormattingContextRoot = &ancestorFormattingContextRoot->formattingContextRoot();
if (is<InitialContainingBlock>(*ancestorFormattingContextRoot))
return true;
}
return false;
}
if (*escapeReason == EscapeReason::TableNeedsAccessToTableWrapper) {
return &layoutBox == &root().formattingContextRoot();
}
ASSERT_NOT_REACHED();
return false;
};
#endif
ASSERT(isOkToAccessBoxGeometry());
ASSERT(layoutState().hasBoxGeometry(layoutBox));
return layoutState().geometryForBox(layoutBox);
}
void FormattingContext::collectOutOfFlowDescendantsIfNeeded()
{
if (!formattingState().outOfFlowBoxes().isEmpty())
return;
auto& root = this->root();
if (!root.hasChild())
return;
if (!root.isPositioned() && !is<InitialContainingBlock>(root))
return;
for (auto& descendant : descendantsOfType<Box>(root)) {
if (!descendant.isOutOfFlowPositioned())
continue;
if (&descendant.formattingContextRoot() != &root)
continue;
formattingState().addOutOfFlowBox(descendant);
}
}
#ifndef NDEBUG
void FormattingContext::validateGeometryConstraintsAfterLayout() const
{
auto& formattingContextRoot = root();
for (auto& layoutBox : descendantsOfType<Box>(formattingContextRoot)) {
if (&layoutBox.formattingContextRoot() != &formattingContextRoot)
continue;
auto& containingBlockGeometry = geometryForBox(layoutBox.containingBlock());
auto& boxGeometry = geometryForBox(layoutBox);
if ((layoutBox.isBlockLevelBox() || layoutBox.isOutOfFlowPositioned()) && !layoutBox.isReplacedBox()) {
auto containingBlockWidth = containingBlockGeometry.contentBoxWidth();
ASSERT(boxGeometry.horizontalMarginBorderAndPadding() + boxGeometry.contentBoxWidth() == containingBlockWidth);
}
if (layoutBox.isOutOfFlowPositioned() && !layoutBox.isReplacedBox()) {
auto containingBlockHeight = containingBlockGeometry.contentBoxHeight();
ASSERT(BoxGeometry::borderBoxTop(boxGeometry) + boxGeometry.marginBefore() + boxGeometry.borderTop() + boxGeometry.paddingTop().valueOr(0) + boxGeometry.contentBoxHeight()
+ boxGeometry.paddingBottom().valueOr(0) + boxGeometry.borderBottom() + boxGeometry.marginAfter() == containingBlockHeight);
}
}
}
#endif
}
}
#endif