BlockMarginCollapse.cpp [plain text]
#include "config.h"
#include "BlockFormattingContext.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "BlockFormattingState.h"
#include "FloatingState.h"
#include "InlineFormattingState.h"
#include "LayoutBox.h"
#include "LayoutContainerBox.h"
#include "LayoutUnit.h"
#include "RenderStyle.h"
namespace WebCore {
namespace Layout {
static bool hasBorder(const BorderValue& borderValue)
{
if (borderValue.style() == BorderStyle::None || borderValue.style() == BorderStyle::Hidden)
return false;
return !!borderValue.width();
}
static bool hasPadding(const Length& paddingValue)
{
return !paddingValue.isZero();
}
static bool hasBorderBefore(const Box& layoutBox)
{
return hasBorder(layoutBox.style().borderBefore());
}
static bool hasBorderAfter(const Box& layoutBox)
{
return hasBorder(layoutBox.style().borderAfter());
}
static bool hasPaddingBefore(const Box& layoutBox)
{
return hasPadding(layoutBox.style().paddingBefore());
}
static bool hasPaddingAfter(const Box& layoutBox)
{
return hasPadding(layoutBox.style().paddingAfter());
}
static bool establishesBlockFormattingContext(const Box& layoutBox)
{
if (layoutBox.isDocumentBox())
return true;
return layoutBox.establishesBlockFormattingContext();
}
bool BlockFormattingContext::MarginCollapse::hasClearance(const Box& layoutBox) const
{
if (!layoutBox.hasFloatClear())
return false;
if (!layoutState().hasDisplayBox(layoutBox))
return false;
return formattingContext().geometryForBox(layoutBox).hasClearance();
}
bool BlockFormattingContext::MarginCollapse::marginBeforeCollapsesWithParentMarginAfter(const Box& layoutBox) const
{
auto* lastInFlowChild = layoutBox.containingBlock().lastInFlowChild();
for (auto* currentBox = &layoutBox; currentBox; currentBox = currentBox->nextInFlowSibling()) {
if (!marginsCollapseThrough(*currentBox))
return false;
if (currentBox == lastInFlowChild)
return marginAfterCollapsesWithParentMarginAfter(*currentBox);
if (!marginAfterCollapsesWithNextSiblingMarginBefore(*currentBox))
return false;
}
ASSERT_NOT_REACHED();
return false;
}
bool BlockFormattingContext::MarginCollapse::marginBeforeCollapsesWithParentMarginBefore(const Box& layoutBox) const
{
ASSERT(layoutBox.isBlockLevelBox());
if (formattingContext().quirks().shouldCollapseMarginBeforeWithParentMarginBefore(layoutBox))
return true;
if (layoutBox.isFloatingPositioned())
return false;
if (layoutBox.isOutOfFlowPositioned())
return false;
if (layoutBox.isInlineBlockBox())
return false;
if (layoutBox.previousInFlowSibling())
return false;
auto& containingBlock = layoutBox.containingBlock();
if (establishesBlockFormattingContext(containingBlock))
return false;
if (hasBorderBefore(containingBlock))
return false;
if (hasPaddingBefore(containingBlock))
return false;
if (hasClearance(layoutBox))
return false;
return true;
}
bool BlockFormattingContext::MarginCollapse::marginBeforeCollapsesWithPreviousSiblingMarginAfter(const Box& layoutBox) const
{
ASSERT(layoutBox.isBlockLevelBox());
if (!layoutBox.previousInFlowSibling())
return false;
auto& previousInFlowSibling = *layoutBox.previousInFlowSibling();
if (layoutBox.isFloatingPositioned() || previousInFlowSibling.isFloatingPositioned())
return false;
if ((layoutBox.isOutOfFlowPositioned() && !layoutBox.style().top().isAuto())
|| (previousInFlowSibling.isOutOfFlowPositioned() && !previousInFlowSibling.style().bottom().isAuto()))
return false;
if (layoutBox.isInlineBlockBox() || previousInFlowSibling.isInlineBlockBox())
return false;
if (hasClearance(layoutBox))
return false;
return true;
}
bool BlockFormattingContext::MarginCollapse::marginBeforeCollapsesWithFirstInFlowChildMarginBefore(const Box& layoutBox) const
{
ASSERT(layoutBox.isBlockLevelBox());
if (establishesBlockFormattingContext(layoutBox))
return false;
if (hasBorderBefore(layoutBox))
return false;
if (hasPaddingBefore(layoutBox))
return false;
if (!is<ContainerBox>(layoutBox) || !downcast<ContainerBox>(layoutBox).hasInFlowChild())
return false;
auto& firstInFlowChild = *downcast<ContainerBox>(layoutBox).firstInFlowChild();
if (!firstInFlowChild.isBlockLevelBox())
return false;
if (hasClearance(firstInFlowChild))
return false;
if (firstInFlowChild.isInlineBlockBox())
return false;
return true;
}
bool BlockFormattingContext::MarginCollapse::marginAfterCollapsesWithSiblingMarginBeforeWithClearance(const Box& layoutBox) const
{
if (!marginsCollapseThrough(layoutBox))
return false;
for (auto* previousSibling = layoutBox.previousInFlowSibling(); previousSibling; previousSibling = previousSibling->previousInFlowSibling()) {
if (!marginsCollapseThrough(*previousSibling))
return false;
if (hasClearance(*previousSibling))
return true;
}
return false;
}
bool BlockFormattingContext::MarginCollapse::marginAfterCollapsesWithParentMarginBefore(const Box& layoutBox) const
{
auto* firstInFlowChild = layoutBox.containingBlock().firstInFlowChild();
for (auto* currentBox = &layoutBox; currentBox; currentBox = currentBox->previousInFlowSibling()) {
if (!marginsCollapseThrough(*currentBox))
return false;
if (currentBox == firstInFlowChild)
return marginBeforeCollapsesWithParentMarginBefore(*currentBox);
if (!marginBeforeCollapsesWithPreviousSiblingMarginAfter(*currentBox))
return false;
}
ASSERT_NOT_REACHED();
return false;
}
bool BlockFormattingContext::MarginCollapse::marginAfterCollapsesWithParentMarginAfter(const Box& layoutBox) const
{
ASSERT(layoutBox.isBlockLevelBox());
if (formattingContext().quirks().shouldCollapseMarginAfterWithParentMarginAfter(layoutBox))
return true;
if (layoutBox.isFloatingPositioned())
return false;
if (layoutBox.isOutOfFlowPositioned())
return false;
if (layoutBox.isInlineBlockBox())
return false;
if (layoutBox.nextInFlowSibling())
return false;
auto& containingBlock = layoutBox.containingBlock();
if (establishesBlockFormattingContext(containingBlock))
return false;
if (!containingBlock.style().height().isAuto())
return false;
if (hasPaddingAfter(containingBlock))
return false;
if (hasBorderAfter(containingBlock))
return false;
if (marginAfterCollapsesWithSiblingMarginBeforeWithClearance(layoutBox))
return false;
auto computedMinHeight = containingBlock.style().logicalMinHeight();
if (!computedMinHeight.isAuto() && computedMinHeight.value() && marginAfterCollapsesWithParentMarginBefore(layoutBox))
return false;
return true;
}
bool BlockFormattingContext::MarginCollapse::marginAfterCollapsesWithLastInFlowChildMarginAfter(const Box& layoutBox) const
{
ASSERT(layoutBox.isBlockLevelBox());
if (establishesBlockFormattingContext(layoutBox))
return false;
if (!is<ContainerBox>(layoutBox) || !downcast<ContainerBox>(layoutBox).hasInFlowChild())
return false;
auto& lastInFlowChild = *downcast<ContainerBox>(layoutBox).lastInFlowChild();
if (!lastInFlowChild.isBlockLevelBox())
return false;
if (!layoutBox.style().height().isAuto())
return false;
if (hasPaddingAfter(layoutBox))
return false;
if (hasBorderAfter(layoutBox))
return false;
if (marginAfterCollapsesWithSiblingMarginBeforeWithClearance(lastInFlowChild))
return false;
auto computedMinHeight = layoutBox.style().logicalMinHeight();
if (!computedMinHeight.isAuto() && computedMinHeight.value()
&& (marginAfterCollapsesWithParentMarginBefore(lastInFlowChild) || hasClearance(lastInFlowChild)))
return false;
if (lastInFlowChild.isInlineBlockBox())
return false;
if (formattingContext().quirks().shouldIgnoreCollapsedQuirkMargin(layoutBox) && marginAfterCollapsesWithParentMarginBefore(lastInFlowChild))
return false;
return true;
}
bool BlockFormattingContext::MarginCollapse::marginAfterCollapsesWithNextSiblingMarginBefore(const Box& layoutBox) const
{
ASSERT(layoutBox.isBlockLevelBox());
if (!layoutBox.nextInFlowSibling())
return false;
return marginBeforeCollapsesWithPreviousSiblingMarginAfter(*layoutBox.nextInFlowSibling());
}
bool BlockFormattingContext::MarginCollapse::marginsCollapseThrough(const Box& layoutBox) const
{
ASSERT(layoutBox.isBlockLevelBox());
if (hasBorderBefore(layoutBox) || hasBorderAfter(layoutBox))
return false;
if (hasPaddingBefore(layoutBox) || hasPaddingAfter(layoutBox))
return false;
if (hasClearance(layoutBox))
return false;
auto& style = layoutBox.style();
auto computedHeightValueIsZero = style.height().isFixed() && !style.height().value();
if (!(style.height().isAuto() || computedHeightValueIsZero))
return false;
if (!style.minHeight().isAuto())
return false;
if (layoutBox.isReplacedBox())
return false;
if (!is<ContainerBox>(layoutBox))
return true;
if (!downcast<ContainerBox>(layoutBox).hasInFlowChild())
return !establishesBlockFormattingContext(layoutBox);
if (layoutBox.establishesFormattingContext()) {
if (layoutBox.establishesInlineFormattingContext()) {
auto& layoutState = this->layoutState();
auto& containerBox = downcast<ContainerBox>(layoutBox);
if (!layoutState.hasInlineFormattingState(containerBox))
return false;
auto isConsideredEmpty = [&] {
auto& formattingState = layoutState.establishedInlineFormattingState(containerBox);
if (auto* inlineContent = formattingState.displayInlineContent(); inlineContent && !inlineContent->lineBoxes.isEmpty())
return false;
auto& floats = formattingState.floatingState().floats();
for (auto& floatItem : floats) {
if (floatItem.isInFormattingContextOf(containerBox))
return false;
}
return true;
};
return isConsideredEmpty();
}
return false;
}
for (auto* inflowChild = downcast<ContainerBox>(layoutBox).firstInFlowOrFloatingChild(); inflowChild; inflowChild = inflowChild->nextInFlowOrFloatingSibling()) {
if (establishesBlockFormattingContext(*inflowChild))
return false;
if (!marginsCollapseThrough(*inflowChild))
return false;
}
return true;
}
UsedVerticalMargin::PositiveAndNegativePair::Values BlockFormattingContext::MarginCollapse::computedPositiveAndNegativeMargin(UsedVerticalMargin::PositiveAndNegativePair::Values a, UsedVerticalMargin::PositiveAndNegativePair::Values b) const
{
UsedVerticalMargin::PositiveAndNegativePair::Values computedValues;
if (a.positive && b.positive)
computedValues.positive = std::max(*a.positive, *b.positive);
else
computedValues.positive = a.positive ? a.positive : b.positive;
if (a.negative && b.negative)
computedValues.negative = std::min(*a.negative, *b.negative);
else
computedValues.negative = a.negative ? a.negative : b.negative;
if (a.isNonZero() && b.isNonZero())
computedValues.isQuirk = a.isQuirk || b.isQuirk;
else if (a.isNonZero())
computedValues.isQuirk = a.isQuirk;
else
computedValues.isQuirk = b.isQuirk;
return computedValues;
}
Optional<LayoutUnit> BlockFormattingContext::MarginCollapse::marginValue(UsedVerticalMargin::PositiveAndNegativePair::Values marginValues) const
{
if (!marginValues.negative)
return marginValues.positive;
if (!marginValues.positive)
return marginValues.negative;
return *marginValues.positive + *marginValues.negative;
}
void BlockFormattingContext::MarginCollapse::updateMarginAfterForPreviousSibling(BlockFormattingContext& blockFormattingContext, const MarginCollapse& marginCollapse, const Box& layoutBox)
{
auto* currentBox = &layoutBox;
auto& blockFormattingState = blockFormattingContext.formattingState();
while (marginCollapse.marginBeforeCollapsesWithPreviousSiblingMarginAfter(*currentBox)) {
auto& previousSibling = *currentBox->previousInFlowSibling();
auto previousSiblingVerticalMargin = blockFormattingState.usedVerticalMargin(previousSibling);
auto collapsedVerticalMarginBefore = previousSiblingVerticalMargin.collapsedValues.before;
auto collapsedVerticalMarginAfter = blockFormattingContext.geometryForBox(*currentBox).marginBefore();
auto marginsCollapseThrough = marginCollapse.marginsCollapseThrough(previousSibling);
if (marginsCollapseThrough)
collapsedVerticalMarginBefore = collapsedVerticalMarginAfter;
auto previousSiblingPositiveNegativeMargin = blockFormattingState.usedVerticalMargin(previousSibling).positiveAndNegativeValues;
auto positiveNegativeMarginBefore = blockFormattingState.usedVerticalMargin(*currentBox).positiveAndNegativeValues.before;
auto adjustedPreviousSiblingVerticalMargin = previousSiblingVerticalMargin;
adjustedPreviousSiblingVerticalMargin.positiveAndNegativeValues.after = marginCollapse.computedPositiveAndNegativeMargin(positiveNegativeMarginBefore, previousSiblingPositiveNegativeMargin.after);
if (marginsCollapseThrough) {
adjustedPreviousSiblingVerticalMargin.positiveAndNegativeValues.before = marginCollapse.computedPositiveAndNegativeMargin(previousSiblingPositiveNegativeMargin.before, adjustedPreviousSiblingVerticalMargin.positiveAndNegativeValues.after);
adjustedPreviousSiblingVerticalMargin.positiveAndNegativeValues.after = adjustedPreviousSiblingVerticalMargin.positiveAndNegativeValues.before;
}
blockFormattingState.setUsedVerticalMargin(previousSibling, adjustedPreviousSiblingVerticalMargin);
if (!marginsCollapseThrough)
break;
currentBox = &previousSibling;
}
}
UsedVerticalMargin::PositiveAndNegativePair::Values BlockFormattingContext::MarginCollapse::positiveNegativeValues(const Box& layoutBox, MarginType marginType) const
{
auto& blockFormattingState = downcast<BlockFormattingState>(layoutState().formattingStateForBox(layoutBox));
ASSERT(blockFormattingState.hasUsedVerticalMargin(layoutBox));
auto positiveAndNegativeVerticalMargin = blockFormattingState.usedVerticalMargin(layoutBox).positiveAndNegativeValues;
return marginType == MarginType::Before ? positiveAndNegativeVerticalMargin.before : positiveAndNegativeVerticalMargin.after;
}
UsedVerticalMargin::PositiveAndNegativePair::Values BlockFormattingContext::MarginCollapse::positiveNegativeMarginBefore(const Box& layoutBox, UsedVerticalMargin::NonCollapsedValues nonCollapsedValues) const
{
auto firstChildCollapsedMarginBefore = [&]() -> UsedVerticalMargin::PositiveAndNegativePair::Values {
if (!marginBeforeCollapsesWithFirstInFlowChildMarginBefore(layoutBox))
return { };
return positiveNegativeValues(*downcast<ContainerBox>(layoutBox).firstInFlowChild(), MarginType::Before);
};
auto previouSiblingCollapsedMarginAfter = [&]() -> UsedVerticalMargin::PositiveAndNegativePair::Values {
if (!marginBeforeCollapsesWithPreviousSiblingMarginAfter(layoutBox))
return { };
return positiveNegativeValues(*layoutBox.previousInFlowSibling(), MarginType::After);
};
auto collapsedMarginBefore = computedPositiveAndNegativeMargin(firstChildCollapsedMarginBefore(), previouSiblingCollapsedMarginAfter());
if (collapsedMarginBefore.isQuirk && formattingContext().quirks().shouldIgnoreCollapsedQuirkMargin(layoutBox))
collapsedMarginBefore = { };
UsedVerticalMargin::PositiveAndNegativePair::Values nonCollapsedBefore;
if (nonCollapsedValues.before > 0)
nonCollapsedBefore = { nonCollapsedValues.before, { }, layoutBox.style().hasMarginBeforeQuirk() };
else if (nonCollapsedValues.before < 0)
nonCollapsedBefore = { { }, nonCollapsedValues.before, layoutBox.style().hasMarginBeforeQuirk() };
return computedPositiveAndNegativeMargin(collapsedMarginBefore, nonCollapsedBefore);
}
UsedVerticalMargin::PositiveAndNegativePair::Values BlockFormattingContext::MarginCollapse::positiveNegativeMarginAfter(const Box& layoutBox, UsedVerticalMargin::NonCollapsedValues nonCollapsedValues) const
{
auto lastChildCollapsedMarginAfter = [&]() -> UsedVerticalMargin::PositiveAndNegativePair::Values {
if (!marginAfterCollapsesWithLastInFlowChildMarginAfter(layoutBox))
return { };
return positiveNegativeValues(*downcast<ContainerBox>(layoutBox).lastInFlowChild(), MarginType::After);
};
UsedVerticalMargin::PositiveAndNegativePair::Values nonCollapsedAfter;
if (nonCollapsedValues.after > 0)
nonCollapsedAfter = { nonCollapsedValues.after, { }, layoutBox.style().hasMarginAfterQuirk() };
else if (nonCollapsedValues.after < 0)
nonCollapsedAfter = { { }, nonCollapsedValues.after, layoutBox.style().hasMarginAfterQuirk() };
return computedPositiveAndNegativeMargin(lastChildCollapsedMarginAfter(), nonCollapsedAfter);
}
LayoutUnit BlockFormattingContext::MarginCollapse::marginBeforeIgnoringCollapsingThrough(const Box& layoutBox, UsedVerticalMargin::NonCollapsedValues nonCollapsedValues)
{
ASSERT(layoutBox.isBlockLevelBox());
return marginValue(positiveNegativeMarginBefore(layoutBox, nonCollapsedValues)).valueOr(nonCollapsedValues.before);
}
UsedVerticalMargin BlockFormattingContext::MarginCollapse::collapsedVerticalValues(const Box& layoutBox, UsedVerticalMargin::NonCollapsedValues nonCollapsedValues)
{
ASSERT(layoutBox.isBlockLevelBox());
auto positiveAndNegativeVerticalMargin = UsedVerticalMargin::PositiveAndNegativePair { this->positiveNegativeMarginBefore(layoutBox, nonCollapsedValues), this->positiveNegativeMarginAfter(layoutBox, nonCollapsedValues) };
auto marginsCollapseThrough = this->marginsCollapseThrough(layoutBox);
if (marginsCollapseThrough) {
positiveAndNegativeVerticalMargin.before = computedPositiveAndNegativeMargin(positiveAndNegativeVerticalMargin.before, positiveAndNegativeVerticalMargin.after);
positiveAndNegativeVerticalMargin.after = positiveAndNegativeVerticalMargin.before;
}
auto hasCollapsedMarginBefore = marginBeforeCollapsesWithFirstInFlowChildMarginBefore(layoutBox) || marginBeforeCollapsesWithPreviousSiblingMarginAfter(layoutBox);
auto hasCollapsedMarginAfter = marginAfterCollapsesWithLastInFlowChildMarginAfter(layoutBox);
auto usedVerticalMargin = UsedVerticalMargin { nonCollapsedValues, { }, positiveAndNegativeVerticalMargin };
if ((hasCollapsedMarginBefore && hasCollapsedMarginAfter) || marginsCollapseThrough)
usedVerticalMargin.collapsedValues = { marginValue(positiveAndNegativeVerticalMargin.before), marginValue(positiveAndNegativeVerticalMargin.after), marginsCollapseThrough };
else if (hasCollapsedMarginBefore)
usedVerticalMargin.collapsedValues = { marginValue(positiveAndNegativeVerticalMargin.before), { }, false };
else if (hasCollapsedMarginAfter)
usedVerticalMargin.collapsedValues = { { }, marginValue(positiveAndNegativeVerticalMargin.after), false };
return usedVerticalMargin;
}
}
}
#endif