blob: 648ff5a06290b99b0985eaf66b91a6a6f91a9dca [file] [log] [blame]
/*
* Copyright (C) 2011 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/layout/LayoutFlexibleBox.h"
#include "core/frame/UseCounter.h"
#include "core/layout/FlexibleBoxAlgorithm.h"
#include "core/layout/LayoutState.h"
#include "core/layout/LayoutView.h"
#include "core/layout/TextAutosizer.h"
#include "core/paint/BlockPainter.h"
#include "core/paint/PaintLayer.h"
#include "core/style/ComputedStyle.h"
#include "platform/LengthFunctions.h"
#include "wtf/MathExtras.h"
#include <limits>
namespace blink {
static bool hasAspectRatio(const LayoutBox& child) {
return child.isImage() || child.isCanvas() || child.isVideo();
}
struct LayoutFlexibleBox::LineContext {
LineContext(LayoutUnit crossAxisOffset,
LayoutUnit crossAxisExtent,
LayoutUnit maxAscent,
Vector<FlexItem>&& flexItems)
: crossAxisOffset(crossAxisOffset),
crossAxisExtent(crossAxisExtent),
maxAscent(maxAscent),
flexItems(flexItems) {}
LayoutUnit crossAxisOffset;
LayoutUnit crossAxisExtent;
LayoutUnit maxAscent;
Vector<FlexItem> flexItems;
};
LayoutFlexibleBox::LayoutFlexibleBox(Element* element)
: LayoutBlock(element),
m_orderIterator(this),
m_numberOfInFlowChildrenOnFirstLine(-1),
m_hasDefiniteHeight(SizeDefiniteness::Unknown) {
DCHECK(!childrenInline());
if (!isAnonymous())
UseCounter::count(document(), UseCounter::CSSFlexibleBox);
}
LayoutFlexibleBox::~LayoutFlexibleBox() {}
LayoutFlexibleBox* LayoutFlexibleBox::createAnonymous(Document* document) {
LayoutFlexibleBox* layoutObject = new LayoutFlexibleBox(nullptr);
layoutObject->setDocumentForAnonymous(document);
return layoutObject;
}
void LayoutFlexibleBox::computeIntrinsicLogicalWidths(
LayoutUnit& minLogicalWidth,
LayoutUnit& maxLogicalWidth) const {
// FIXME: We're ignoring flex-basis here and we shouldn't. We can't start
// honoring it though until the flex shorthand stops setting it to 0. See
// https://bugs.webkit.org/show_bug.cgi?id=116117 and
// https://crbug.com/240765.
float previousMaxContentFlexFraction = -1;
for (LayoutBox* child = firstChildBox(); child;
child = child->nextSiblingBox()) {
if (child->isOutOfFlowPositioned())
continue;
LayoutUnit margin = marginIntrinsicLogicalWidthForChild(*child);
LayoutUnit minPreferredLogicalWidth;
LayoutUnit maxPreferredLogicalWidth;
computeChildPreferredLogicalWidths(*child, minPreferredLogicalWidth,
maxPreferredLogicalWidth);
DCHECK_GE(minPreferredLogicalWidth, LayoutUnit());
DCHECK_GE(maxPreferredLogicalWidth, LayoutUnit());
minPreferredLogicalWidth += margin;
maxPreferredLogicalWidth += margin;
if (!isColumnFlow()) {
maxLogicalWidth += maxPreferredLogicalWidth;
if (isMultiline()) {
// For multiline, the min preferred width is if you put a break between
// each item.
minLogicalWidth = std::max(minLogicalWidth, minPreferredLogicalWidth);
} else {
minLogicalWidth += minPreferredLogicalWidth;
}
} else {
minLogicalWidth = std::max(minPreferredLogicalWidth, minLogicalWidth);
maxLogicalWidth = std::max(maxPreferredLogicalWidth, maxLogicalWidth);
}
previousMaxContentFlexFraction = countIntrinsicSizeForAlgorithmChange(
maxPreferredLogicalWidth, child, previousMaxContentFlexFraction);
}
maxLogicalWidth = std::max(minLogicalWidth, maxLogicalWidth);
// Due to negative margins, it is possible that we calculated a negative
// intrinsic width. Make sure that we never return a negative width.
minLogicalWidth = std::max(LayoutUnit(), minLogicalWidth);
maxLogicalWidth = std::max(LayoutUnit(), maxLogicalWidth);
LayoutUnit scrollbarWidth(scrollbarLogicalWidth());
maxLogicalWidth += scrollbarWidth;
minLogicalWidth += scrollbarWidth;
}
float LayoutFlexibleBox::countIntrinsicSizeForAlgorithmChange(
LayoutUnit maxPreferredLogicalWidth,
LayoutBox* child,
float previousMaxContentFlexFraction) const {
// Determine whether the new version of the intrinsic size algorithm of the
// flexbox spec would produce a different result than our above algorithm.
// The algorithm produces a different result iff the max-content flex
// fraction (as defined in the new algorithm) is not identical for each flex
// item.
if (isColumnFlow())
return previousMaxContentFlexFraction;
Length flexBasis = child->styleRef().flexBasis();
float flexGrow = child->styleRef().flexGrow();
// A flex-basis of auto will lead to a max-content flex fraction of zero, so
// just like an inflexible item it would compute to a size of max-content, so
// we ignore it here.
if (flexBasis.isAuto() || flexGrow == 0)
return previousMaxContentFlexFraction;
flexGrow = std::max(1.0f, flexGrow);
float maxContentFlexFraction = maxPreferredLogicalWidth.toFloat() / flexGrow;
if (previousMaxContentFlexFraction != -1 &&
maxContentFlexFraction != previousMaxContentFlexFraction)
UseCounter::count(document(),
UseCounter::FlexboxIntrinsicSizeAlgorithmIsDifferent);
return maxContentFlexFraction;
}
static int synthesizedBaselineFromContentBox(const LayoutBox& box,
LineDirectionMode direction) {
if (direction == HorizontalLine) {
return (box.size().height() - box.borderBottom() - box.paddingBottom() -
box.verticalScrollbarWidth())
.toInt();
}
return (box.size().width() - box.borderLeft() - box.paddingLeft() -
box.horizontalScrollbarHeight())
.toInt();
}
int LayoutFlexibleBox::baselinePosition(FontBaseline,
bool,
LineDirectionMode direction,
LinePositionMode mode) const {
DCHECK_EQ(mode, PositionOnContainingLine);
int baseline = firstLineBoxBaseline();
if (baseline == -1)
baseline = synthesizedBaselineFromContentBox(*this, direction);
return beforeMarginInLineDirection(direction) + baseline;
}
static const StyleContentAlignmentData& contentAlignmentNormalBehavior() {
// The justify-content property applies along the main axis, but since
// flexing in the main axis is controlled by flex, stretch behaves as
// flex-start (ignoring the specified fallback alignment, if any).
// https://drafts.csswg.org/css-align/#distribution-flex
static const StyleContentAlignmentData normalBehavior = {
ContentPositionNormal, ContentDistributionStretch};
return normalBehavior;
}
int LayoutFlexibleBox::firstLineBoxBaseline() const {
if (isWritingModeRoot() || m_numberOfInFlowChildrenOnFirstLine <= 0)
return -1;
LayoutBox* baselineChild = nullptr;
int childNumber = 0;
for (LayoutBox* child = m_orderIterator.first(); child;
child = m_orderIterator.next()) {
if (child->isOutOfFlowPositioned())
continue;
if (alignmentForChild(*child) == ItemPositionBaseline &&
!hasAutoMarginsInCrossAxis(*child)) {
baselineChild = child;
break;
}
if (!baselineChild)
baselineChild = child;
++childNumber;
if (childNumber == m_numberOfInFlowChildrenOnFirstLine)
break;
}
if (!baselineChild)
return -1;
if (!isColumnFlow() && hasOrthogonalFlow(*baselineChild))
return (crossAxisExtentForChild(*baselineChild) +
baselineChild->logicalTop())
.toInt();
if (isColumnFlow() && !hasOrthogonalFlow(*baselineChild))
return (mainAxisExtentForChild(*baselineChild) +
baselineChild->logicalTop())
.toInt();
int baseline = baselineChild->firstLineBoxBaseline();
if (baseline == -1) {
// FIXME: We should pass |direction| into firstLineBoxBaseline and stop
// bailing out if we're a writing mode root. This would also fix some
// cases where the flexbox is orthogonal to its container.
LineDirectionMode direction =
isHorizontalWritingMode() ? HorizontalLine : VerticalLine;
return (synthesizedBaselineFromContentBox(*baselineChild, direction) +
baselineChild->logicalTop())
.toInt();
}
return (baseline + baselineChild->logicalTop()).toInt();
}
int LayoutFlexibleBox::inlineBlockBaseline(LineDirectionMode direction) const {
int baseline = firstLineBoxBaseline();
if (baseline != -1)
return baseline;
int marginAscent =
(direction == HorizontalLine ? marginTop() : marginRight()).toInt();
return synthesizedBaselineFromContentBox(*this, direction) + marginAscent;
}
IntSize LayoutFlexibleBox::originAdjustmentForScrollbars() const {
IntSize size;
int adjustmentWidth = verticalScrollbarWidth();
int adjustmentHeight = horizontalScrollbarHeight();
if (!adjustmentWidth && !adjustmentHeight)
return size;
EFlexDirection flexDirection = style()->flexDirection();
TextDirection textDirection = style()->direction();
WritingMode writingMode = style()->getWritingMode();
if (flexDirection == FlowRow) {
if (textDirection == RTL) {
if (writingMode == TopToBottomWritingMode)
size.expand(adjustmentWidth, 0);
else
size.expand(0, adjustmentHeight);
}
if (writingMode == RightToLeftWritingMode)
size.expand(adjustmentWidth, 0);
} else if (flexDirection == FlowRowReverse) {
if (textDirection == LTR) {
if (writingMode == TopToBottomWritingMode)
size.expand(adjustmentWidth, 0);
else
size.expand(0, adjustmentHeight);
}
if (writingMode == RightToLeftWritingMode)
size.expand(adjustmentWidth, 0);
} else if (flexDirection == FlowColumn) {
if (writingMode == RightToLeftWritingMode)
size.expand(adjustmentWidth, 0);
} else {
if (writingMode == TopToBottomWritingMode)
size.expand(0, adjustmentHeight);
else if (writingMode == LeftToRightWritingMode)
size.expand(adjustmentWidth, 0);
}
return size;
}
bool LayoutFlexibleBox::hasTopOverflow() const {
EFlexDirection flexDirection = style()->flexDirection();
if (isHorizontalWritingMode())
return flexDirection == FlowColumnReverse;
return flexDirection ==
(style()->isLeftToRightDirection() ? FlowRowReverse : FlowRow);
}
bool LayoutFlexibleBox::hasLeftOverflow() const {
EFlexDirection flexDirection = style()->flexDirection();
if (isHorizontalWritingMode())
return flexDirection ==
(style()->isLeftToRightDirection() ? FlowRowReverse : FlowRow);
return flexDirection == FlowColumnReverse;
}
void LayoutFlexibleBox::removeChild(LayoutObject* child) {
LayoutBlock::removeChild(child);
m_intrinsicSizeAlongMainAxis.remove(child);
}
// TODO (lajava): Is this function still needed ? Every time the flex
// container's align-items value changes we propagate the diff to its children
// (see ComputedStyle::stylePropagationDiff).
void LayoutFlexibleBox::styleDidChange(StyleDifference diff,
const ComputedStyle* oldStyle) {
LayoutBlock::styleDidChange(diff, oldStyle);
if (oldStyle && oldStyle->alignItemsPosition() == ItemPositionStretch &&
diff.needsFullLayout()) {
// Flex items that were previously stretching need to be relayed out so we
// can compute new available cross axis space. This is only necessary for
// stretching since other alignment values don't change the size of the
// box.
for (LayoutBox* child = firstChildBox(); child;
child = child->nextSiblingBox()) {
ItemPosition previousAlignment =
child->styleRef()
.resolvedAlignSelf(selfAlignmentNormalBehavior(), oldStyle)
.position();
if (previousAlignment == ItemPositionStretch &&
previousAlignment !=
child->styleRef()
.resolvedAlignSelf(selfAlignmentNormalBehavior(), style())
.position())
child->setChildNeedsLayout(MarkOnlyThis);
}
}
}
void LayoutFlexibleBox::layoutBlock(bool relayoutChildren) {
DCHECK(needsLayout());
if (!relayoutChildren && simplifiedLayout())
return;
m_relaidOutChildren.clear();
if (updateLogicalWidthAndColumnWidth())
relayoutChildren = true;
SubtreeLayoutScope layoutScope(*this);
LayoutUnit previousHeight = logicalHeight();
setLogicalHeight(borderAndPaddingLogicalHeight() + scrollbarLogicalHeight());
PaintLayerScrollableArea::DelayScrollOffsetClampScope delayClampScope;
{
TextAutosizer::LayoutScope textAutosizerLayoutScope(this, &layoutScope);
LayoutState state(*this);
m_numberOfInFlowChildrenOnFirstLine = -1;
prepareOrderIteratorAndMargins();
layoutFlexItems(relayoutChildren, layoutScope);
if (PaintLayerScrollableArea::PreventRelayoutScope::relayoutNeeded()) {
PaintLayerScrollableArea::FreezeScrollbarsScope freezeScrollbarsScope;
prepareOrderIteratorAndMargins();
layoutFlexItems(true, layoutScope);
PaintLayerScrollableArea::PreventRelayoutScope::resetRelayoutNeeded();
}
if (logicalHeight() != previousHeight)
relayoutChildren = true;
layoutPositionedObjects(relayoutChildren || isDocumentElement());
// FIXME: css3/flexbox/repaint-rtl-column.html seems to issue paint
// invalidations for more overflow than it needs to.
computeOverflow(clientLogicalBottomAfterRepositioning());
}
updateLayerTransformAfterLayout();
// Update our scroll information if we're overflow:auto/scroll/hidden now
// that we know if we overflow or not.
updateAfterLayout();
clearNeedsLayout();
// We have to reset this, because changes to our ancestors' style can affect
// this value.
m_hasDefiniteHeight = SizeDefiniteness::Unknown;
}
void LayoutFlexibleBox::paintChildren(const PaintInfo& paintInfo,
const LayoutPoint& paintOffset) const {
BlockPainter::paintChildrenOfFlexibleBox(*this, paintInfo, paintOffset);
}
void LayoutFlexibleBox::repositionLogicalHeightDependentFlexItems(
Vector<LineContext>& lineContexts) {
LayoutUnit crossAxisStartEdge =
lineContexts.isEmpty() ? LayoutUnit() : lineContexts[0].crossAxisOffset;
alignFlexLines(lineContexts);
alignChildren(lineContexts);
if (style()->flexWrap() == FlexWrapReverse)
flipForWrapReverse(lineContexts, crossAxisStartEdge);
// direction:rtl + flex-direction:column means the cross-axis direction is
// flipped.
flipForRightToLeftColumn(lineContexts);
}
DISABLE_CFI_PERF
LayoutUnit LayoutFlexibleBox::clientLogicalBottomAfterRepositioning() {
LayoutUnit maxChildLogicalBottom;
for (LayoutBox* child = firstChildBox(); child;
child = child->nextSiblingBox()) {
if (child->isOutOfFlowPositioned())
continue;
LayoutUnit childLogicalBottom = logicalTopForChild(*child) +
logicalHeightForChild(*child) +
marginAfterForChild(*child);
maxChildLogicalBottom = std::max(maxChildLogicalBottom, childLogicalBottom);
}
return std::max(clientLogicalBottom(),
maxChildLogicalBottom + paddingAfter());
}
bool LayoutFlexibleBox::hasOrthogonalFlow(const LayoutBox& child) const {
return isHorizontalFlow() != child.isHorizontalWritingMode();
}
bool LayoutFlexibleBox::isColumnFlow() const {
return style()->isColumnFlexDirection();
}
bool LayoutFlexibleBox::isHorizontalFlow() const {
if (isHorizontalWritingMode())
return !isColumnFlow();
return isColumnFlow();
}
bool LayoutFlexibleBox::isLeftToRightFlow() const {
if (isColumnFlow())
return style()->getWritingMode() == TopToBottomWritingMode ||
style()->getWritingMode() == LeftToRightWritingMode;
return style()->isLeftToRightDirection() ^
(style()->flexDirection() == FlowRowReverse);
}
bool LayoutFlexibleBox::isMultiline() const {
return style()->flexWrap() != FlexNoWrap;
}
Length LayoutFlexibleBox::flexBasisForChild(const LayoutBox& child) const {
Length flexLength = child.style()->flexBasis();
if (flexLength.isAuto())
flexLength =
isHorizontalFlow() ? child.style()->width() : child.style()->height();
return flexLength;
}
LayoutUnit LayoutFlexibleBox::crossAxisExtentForChild(
const LayoutBox& child) const {
return isHorizontalFlow() ? child.size().height() : child.size().width();
}
static inline LayoutUnit constrainedChildIntrinsicContentLogicalHeight(
const LayoutBox& child,
LayoutUnit childIntrinsicContentLogicalHeight) {
// TODO(cbiesinger): scrollbar height?
return child.constrainLogicalHeightByMinMax(
childIntrinsicContentLogicalHeight +
child.borderAndPaddingLogicalHeight(),
childIntrinsicContentLogicalHeight);
}
LayoutUnit LayoutFlexibleBox::childIntrinsicLogicalHeight(
const LayoutBox& child) const {
// This should only be called if the logical height is the cross size
DCHECK(!hasOrthogonalFlow(child));
if (needToStretchChildLogicalHeight(child)) {
LayoutUnit childIntrinsicContentLogicalHeight;
if (!child.styleRef().containsSize())
childIntrinsicContentLogicalHeight =
child.intrinsicContentLogicalHeight();
return constrainedChildIntrinsicContentLogicalHeight(
child, childIntrinsicContentLogicalHeight);
}
return child.logicalHeight();
}
DISABLE_CFI_PERF
LayoutUnit LayoutFlexibleBox::childIntrinsicLogicalWidth(
const LayoutBox& child) const {
// This should only be called if the logical width is the cross size
DCHECK(hasOrthogonalFlow(child));
// If our height is auto, make sure that our returned height is unaffected by
// earlier layouts by returning the max preferred logical width
if (!crossAxisLengthIsDefinite(child, child.styleRef().logicalWidth()))
return child.maxPreferredLogicalWidth();
return child.logicalWidth();
}
LayoutUnit LayoutFlexibleBox::crossAxisIntrinsicExtentForChild(
const LayoutBox& child) const {
return hasOrthogonalFlow(child) ? childIntrinsicLogicalWidth(child)
: childIntrinsicLogicalHeight(child);
}
LayoutUnit LayoutFlexibleBox::mainAxisExtentForChild(
const LayoutBox& child) const {
return isHorizontalFlow() ? child.size().width() : child.size().height();
}
LayoutUnit LayoutFlexibleBox::mainAxisContentExtentForChildIncludingScrollbar(
const LayoutBox& child) const {
return isHorizontalFlow()
? child.contentWidth() + child.verticalScrollbarWidth()
: child.contentHeight() + child.horizontalScrollbarHeight();
}
LayoutUnit LayoutFlexibleBox::crossAxisExtent() const {
return isHorizontalFlow() ? size().height() : size().width();
}
LayoutUnit LayoutFlexibleBox::mainAxisExtent() const {
return isHorizontalFlow() ? size().width() : size().height();
}
LayoutUnit LayoutFlexibleBox::crossAxisContentExtent() const {
return isHorizontalFlow() ? contentHeight() : contentWidth();
}
LayoutUnit LayoutFlexibleBox::mainAxisContentExtent(
LayoutUnit contentLogicalHeight) {
if (isColumnFlow()) {
LogicalExtentComputedValues computedValues;
LayoutUnit borderPaddingAndScrollbar =
borderAndPaddingLogicalHeight() + scrollbarLogicalHeight();
LayoutUnit borderBoxLogicalHeight =
contentLogicalHeight + borderPaddingAndScrollbar;
computeLogicalHeight(borderBoxLogicalHeight, logicalTop(), computedValues);
if (computedValues.m_extent == LayoutUnit::max())
return computedValues.m_extent;
return std::max(LayoutUnit(),
computedValues.m_extent - borderPaddingAndScrollbar);
}
return contentLogicalWidth();
}
LayoutUnit LayoutFlexibleBox::computeMainAxisExtentForChild(
const LayoutBox& child,
SizeType sizeType,
const Length& size) {
// If we have a horizontal flow, that means the main size is the width.
// That's the logical width for horizontal writing modes, and the logical
// height in vertical writing modes. For a vertical flow, main size is the
// height, so it's the inverse. So we need the logical width if we have a
// horizontal flow and horizontal writing mode, or vertical flow and vertical
// writing mode. Otherwise we need the logical height.
if (isHorizontalFlow() != child.styleRef().isHorizontalWritingMode()) {
// We don't have to check for "auto" here - computeContentLogicalHeight
// will just return -1 for that case anyway. It's safe to access
// scrollbarLogicalHeight here because ComputeNextFlexLine will have
// already forced layout on the child. We previously layed out the child
// if necessary (see ComputeNextFlexLine and the call to
// childHasIntrinsicMainAxisSize) so we can be sure that the two height
// calls here will return up-to-date data.
return child.computeContentLogicalHeight(
sizeType, size, child.intrinsicContentLogicalHeight()) +
child.scrollbarLogicalHeight();
}
// computeLogicalWidth always re-computes the intrinsic widths. However, when
// our logical width is auto, we can just use our cached value. So let's do
// that here. (Compare code in LayoutBlock::computePreferredLogicalWidths)
LayoutUnit borderAndPadding = child.borderAndPaddingLogicalWidth();
if (child.styleRef().logicalWidth().isAuto() && !hasAspectRatio(child)) {
if (size.type() == MinContent)
return child.minPreferredLogicalWidth() - borderAndPadding;
if (size.type() == MaxContent)
return child.maxPreferredLogicalWidth() - borderAndPadding;
}
return child.computeLogicalWidthUsing(sizeType, size, contentLogicalWidth(),
this) -
borderAndPadding;
}
LayoutFlexibleBox::TransformedWritingMode
LayoutFlexibleBox::getTransformedWritingMode() const {
WritingMode mode = style()->getWritingMode();
if (!isColumnFlow()) {
static_assert(
static_cast<TransformedWritingMode>(TopToBottomWritingMode) ==
TransformedWritingMode::TopToBottomWritingMode &&
static_cast<TransformedWritingMode>(LeftToRightWritingMode) ==
TransformedWritingMode::LeftToRightWritingMode &&
static_cast<TransformedWritingMode>(RightToLeftWritingMode) ==
TransformedWritingMode::RightToLeftWritingMode,
"WritingMode and TransformedWritingMode must match values.");
return static_cast<TransformedWritingMode>(mode);
}
switch (mode) {
case TopToBottomWritingMode:
return style()->isLeftToRightDirection()
? TransformedWritingMode::LeftToRightWritingMode
: TransformedWritingMode::RightToLeftWritingMode;
case LeftToRightWritingMode:
case RightToLeftWritingMode:
return style()->isLeftToRightDirection()
? TransformedWritingMode::TopToBottomWritingMode
: TransformedWritingMode::BottomToTopWritingMode;
}
NOTREACHED();
return TransformedWritingMode::TopToBottomWritingMode;
}
LayoutUnit LayoutFlexibleBox::flowAwareBorderStart() const {
if (isHorizontalFlow())
return LayoutUnit(isLeftToRightFlow() ? borderLeft() : borderRight());
return LayoutUnit(isLeftToRightFlow() ? borderTop() : borderBottom());
}
LayoutUnit LayoutFlexibleBox::flowAwareBorderEnd() const {
if (isHorizontalFlow())
return LayoutUnit(isLeftToRightFlow() ? borderRight() : borderLeft());
return LayoutUnit(isLeftToRightFlow() ? borderBottom() : borderTop());
}
LayoutUnit LayoutFlexibleBox::flowAwareBorderBefore() const {
switch (getTransformedWritingMode()) {
case TransformedWritingMode::TopToBottomWritingMode:
return LayoutUnit(borderTop());
case TransformedWritingMode::BottomToTopWritingMode:
return LayoutUnit(borderBottom());
case TransformedWritingMode::LeftToRightWritingMode:
return LayoutUnit(borderLeft());
case TransformedWritingMode::RightToLeftWritingMode:
return LayoutUnit(borderRight());
}
NOTREACHED();
return LayoutUnit(borderTop());
}
DISABLE_CFI_PERF
LayoutUnit LayoutFlexibleBox::flowAwareBorderAfter() const {
switch (getTransformedWritingMode()) {
case TransformedWritingMode::TopToBottomWritingMode:
return LayoutUnit(borderBottom());
case TransformedWritingMode::BottomToTopWritingMode:
return LayoutUnit(borderTop());
case TransformedWritingMode::LeftToRightWritingMode:
return LayoutUnit(borderRight());
case TransformedWritingMode::RightToLeftWritingMode:
return LayoutUnit(borderLeft());
}
NOTREACHED();
return LayoutUnit(borderTop());
}
LayoutUnit LayoutFlexibleBox::flowAwarePaddingStart() const {
if (isHorizontalFlow())
return isLeftToRightFlow() ? paddingLeft() : paddingRight();
return isLeftToRightFlow() ? paddingTop() : paddingBottom();
}
LayoutUnit LayoutFlexibleBox::flowAwarePaddingEnd() const {
if (isHorizontalFlow())
return isLeftToRightFlow() ? paddingRight() : paddingLeft();
return isLeftToRightFlow() ? paddingBottom() : paddingTop();
}
LayoutUnit LayoutFlexibleBox::flowAwarePaddingBefore() const {
switch (getTransformedWritingMode()) {
case TransformedWritingMode::TopToBottomWritingMode:
return paddingTop();
case TransformedWritingMode::BottomToTopWritingMode:
return paddingBottom();
case TransformedWritingMode::LeftToRightWritingMode:
return paddingLeft();
case TransformedWritingMode::RightToLeftWritingMode:
return paddingRight();
}
NOTREACHED();
return paddingTop();
}
DISABLE_CFI_PERF
LayoutUnit LayoutFlexibleBox::flowAwarePaddingAfter() const {
switch (getTransformedWritingMode()) {
case TransformedWritingMode::TopToBottomWritingMode:
return paddingBottom();
case TransformedWritingMode::BottomToTopWritingMode:
return paddingTop();
case TransformedWritingMode::LeftToRightWritingMode:
return paddingRight();
case TransformedWritingMode::RightToLeftWritingMode:
return paddingLeft();
}
NOTREACHED();
return paddingTop();
}
DISABLE_CFI_PERF
LayoutUnit LayoutFlexibleBox::flowAwareMarginStartForChild(
const LayoutBox& child) const {
if (isHorizontalFlow())
return isLeftToRightFlow() ? child.marginLeft() : child.marginRight();
return isLeftToRightFlow() ? child.marginTop() : child.marginBottom();
}
DISABLE_CFI_PERF
LayoutUnit LayoutFlexibleBox::flowAwareMarginEndForChild(
const LayoutBox& child) const {
if (isHorizontalFlow())
return isLeftToRightFlow() ? child.marginRight() : child.marginLeft();
return isLeftToRightFlow() ? child.marginBottom() : child.marginTop();
}
DISABLE_CFI_PERF
LayoutUnit LayoutFlexibleBox::flowAwareMarginBeforeForChild(
const LayoutBox& child) const {
switch (getTransformedWritingMode()) {
case TransformedWritingMode::TopToBottomWritingMode:
return child.marginTop();
case TransformedWritingMode::BottomToTopWritingMode:
return child.marginBottom();
case TransformedWritingMode::LeftToRightWritingMode:
return child.marginLeft();
case TransformedWritingMode::RightToLeftWritingMode:
return child.marginRight();
}
NOTREACHED();
return marginTop();
}
LayoutUnit LayoutFlexibleBox::crossAxisMarginExtentForChild(
const LayoutBox& child) const {
return isHorizontalFlow() ? child.marginHeight() : child.marginWidth();
}
LayoutUnit LayoutFlexibleBox::crossAxisScrollbarExtent() const {
return LayoutUnit(isHorizontalFlow() ? horizontalScrollbarHeight()
: verticalScrollbarWidth());
}
LayoutUnit LayoutFlexibleBox::crossAxisScrollbarExtentForChild(
const LayoutBox& child) const {
return LayoutUnit(isHorizontalFlow() ? child.horizontalScrollbarHeight()
: child.verticalScrollbarWidth());
}
LayoutPoint LayoutFlexibleBox::flowAwareLocationForChild(
const LayoutBox& child) const {
return isHorizontalFlow() ? child.location()
: child.location().transposedPoint();
}
bool LayoutFlexibleBox::useChildAspectRatio(const LayoutBox& child) const {
if (!hasAspectRatio(child))
return false;
if (child.intrinsicSize().height() == 0) {
// We can't compute a ratio in this case.
return false;
}
Length crossSize;
if (isHorizontalFlow())
crossSize = child.styleRef().height();
else
crossSize = child.styleRef().width();
return crossAxisLengthIsDefinite(child, crossSize);
}
LayoutUnit LayoutFlexibleBox::computeMainSizeFromAspectRatioUsing(
const LayoutBox& child,
Length crossSizeLength) const {
DCHECK(hasAspectRatio(child));
DCHECK_NE(child.intrinsicSize().height(), 0);
LayoutUnit crossSize;
if (crossSizeLength.isFixed()) {
crossSize = LayoutUnit(crossSizeLength.value());
} else {
DCHECK(crossSizeLength.isPercentOrCalc());
crossSize = hasOrthogonalFlow(child)
? adjustBorderBoxLogicalWidthForBoxSizing(
valueForLength(crossSizeLength, contentWidth()))
: child.computePercentageLogicalHeight(crossSizeLength);
}
const LayoutSize& childIntrinsicSize = child.intrinsicSize();
double ratio = childIntrinsicSize.width().toFloat() /
childIntrinsicSize.height().toFloat();
if (isHorizontalFlow())
return LayoutUnit(crossSize * ratio);
return LayoutUnit(crossSize / ratio);
}
void LayoutFlexibleBox::setFlowAwareLocationForChild(
LayoutBox& child,
const LayoutPoint& location) {
if (isHorizontalFlow())
child.setLocationAndUpdateOverflowControlsIfNeeded(location);
else
child.setLocationAndUpdateOverflowControlsIfNeeded(
location.transposedPoint());
}
bool LayoutFlexibleBox::mainAxisLengthIsDefinite(
const LayoutBox& child,
const Length& flexBasis) const {
if (flexBasis.isAuto())
return false;
if (flexBasis.isPercentOrCalc()) {
if (!isColumnFlow() || m_hasDefiniteHeight == SizeDefiniteness::Definite)
return true;
if (m_hasDefiniteHeight == SizeDefiniteness::Indefinite)
return false;
bool definite = child.computePercentageLogicalHeight(flexBasis) != -1;
m_hasDefiniteHeight =
definite ? SizeDefiniteness::Definite : SizeDefiniteness::Indefinite;
return definite;
}
return true;
}
bool LayoutFlexibleBox::crossAxisLengthIsDefinite(const LayoutBox& child,
const Length& length) const {
if (length.isAuto())
return false;
if (length.isPercentOrCalc()) {
if (hasOrthogonalFlow(child) ||
m_hasDefiniteHeight == SizeDefiniteness::Definite)
return true;
if (m_hasDefiniteHeight == SizeDefiniteness::Indefinite)
return false;
bool definite = child.computePercentageLogicalHeight(length) != -1;
m_hasDefiniteHeight =
definite ? SizeDefiniteness::Definite : SizeDefiniteness::Indefinite;
return definite;
}
// TODO(cbiesinger): Eventually we should support other types of sizes here.
// Requires updating computeMainSizeFromAspectRatioUsing.
return length.isFixed();
}
void LayoutFlexibleBox::cacheChildMainSize(const LayoutBox& child) {
DCHECK(!child.needsLayout());
LayoutUnit mainSize;
if (hasOrthogonalFlow(child)) {
mainSize = child.logicalHeight();
} else {
// The max preferred logical width includes the intrinsic scrollbar logical
// width, which is only set for overflow: scroll. To handle overflow: auto,
// we have to take scrollbarLogicalWidth() into account, and then subtract
// the intrinsic width again so as to not double-count overflow: scroll
// scrollbars.
mainSize = child.maxPreferredLogicalWidth() +
child.scrollbarLogicalWidth() - child.scrollbarLogicalWidth();
}
m_intrinsicSizeAlongMainAxis.set(&child, mainSize);
m_relaidOutChildren.add(&child);
}
void LayoutFlexibleBox::clearCachedMainSizeForChild(const LayoutBox& child) {
m_intrinsicSizeAlongMainAxis.remove(&child);
}
DISABLE_CFI_PERF
LayoutUnit LayoutFlexibleBox::computeInnerFlexBaseSizeForChild(
LayoutBox& child,
LayoutUnit mainAxisBorderAndPadding,
ChildLayoutType childLayoutType) {
child.clearOverrideSize();
if (child.isImage() || child.isVideo() || child.isCanvas())
UseCounter::count(document(), UseCounter::AspectRatioFlexItem);
Length flexBasis = flexBasisForChild(child);
if (mainAxisLengthIsDefinite(child, flexBasis))
return std::max(LayoutUnit(), computeMainAxisExtentForChild(
child, MainOrPreferredSize, flexBasis));
if (child.styleRef().containsSize())
return LayoutUnit();
// The flex basis is indefinite (=auto), so we need to compute the actual
// width of the child. For the logical width axis we just use the preferred
// width; for the height we need to lay out the child.
LayoutUnit mainAxisExtent;
if (hasOrthogonalFlow(child)) {
if (childLayoutType == NeverLayout)
return LayoutUnit();
updateBlockChildDirtyBitsBeforeLayout(childLayoutType == ForceLayout,
child);
if (child.needsLayout() || childLayoutType == ForceLayout ||
!m_intrinsicSizeAlongMainAxis.contains(&child)) {
child.forceChildLayout();
cacheChildMainSize(child);
}
mainAxisExtent = m_intrinsicSizeAlongMainAxis.get(&child);
} else {
// We don't need to add scrollbarLogicalWidth here because the preferred
// width includes the scrollbar, even for overflow: auto.
mainAxisExtent = child.maxPreferredLogicalWidth();
}
DCHECK_GE(mainAxisExtent - mainAxisBorderAndPadding, LayoutUnit())
<< mainAxisExtent << " - " << mainAxisBorderAndPadding;
return mainAxisExtent - mainAxisBorderAndPadding;
}
void LayoutFlexibleBox::layoutFlexItems(bool relayoutChildren,
SubtreeLayoutScope& layoutScope) {
Vector<LineContext> lineContexts;
LayoutUnit sumFlexBaseSize;
double totalFlexGrow;
double totalFlexShrink;
double totalWeightedFlexShrink;
LayoutUnit sumHypotheticalMainSize;
PaintLayerScrollableArea::PreventRelayoutScope preventRelayoutScope(
layoutScope);
// Set up our master list of flex items. All of the rest of the algorithm
// should work off this list of a subset.
// TODO(cbiesinger): That second part is not yet true.
ChildLayoutType layoutType = relayoutChildren ? ForceLayout : LayoutIfNeeded;
Vector<FlexItem> allItems;
m_orderIterator.first();
for (LayoutBox* child = m_orderIterator.currentChild(); child;
child = m_orderIterator.next()) {
if (child->isOutOfFlowPositioned()) {
// Out-of-flow children are not flex items, so we skip them here.
prepareChildForPositionedLayout(*child);
continue;
}
allItems.append(constructFlexItem(*child, layoutType));
}
const LayoutUnit lineBreakLength = mainAxisContentExtent(LayoutUnit::max());
FlexLayoutAlgorithm flexAlgorithm(style(), lineBreakLength, allItems);
LayoutUnit crossAxisOffset =
flowAwareBorderBefore() + flowAwarePaddingBefore();
Vector<FlexItem> lineItems;
size_t nextIndex = 0;
while (flexAlgorithm.ComputeNextFlexLine(
nextIndex, lineItems, sumFlexBaseSize, totalFlexGrow, totalFlexShrink,
totalWeightedFlexShrink, sumHypotheticalMainSize)) {
DCHECK_GE(lineItems.size(), 0ULL);
LayoutUnit containerMainInnerSize =
mainAxisContentExtent(sumHypotheticalMainSize);
// availableFreeSpace is the initial amount of free space in this flexbox.
// remainingFreeSpace starts out at the same value but as we place and lay
// out flex items we subtract from it. Note that both values can be
// negative.
LayoutUnit remainingFreeSpace = containerMainInnerSize - sumFlexBaseSize;
FlexSign flexSign = (sumHypotheticalMainSize < containerMainInnerSize)
? PositiveFlexibility
: NegativeFlexibility;
freezeInflexibleItems(flexSign, lineItems, remainingFreeSpace,
totalFlexGrow, totalFlexShrink,
totalWeightedFlexShrink);
// The initial free space gets calculated after freezing inflexible items.
// https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths step 3
const LayoutUnit initialFreeSpace = remainingFreeSpace;
while (!resolveFlexibleLengths(flexSign, lineItems, initialFreeSpace,
remainingFreeSpace, totalFlexGrow,
totalFlexShrink, totalWeightedFlexShrink)) {
DCHECK_GE(totalFlexGrow, 0);
DCHECK_GE(totalWeightedFlexShrink, 0);
}
// Recalculate the remaining free space. The adjustment for flex factors
// between 0..1 means we can't just use remainingFreeSpace here.
remainingFreeSpace = containerMainInnerSize;
for (size_t i = 0; i < lineItems.size(); ++i) {
FlexItem& flexItem = lineItems[i];
DCHECK(!flexItem.box->isOutOfFlowPositioned());
remainingFreeSpace -= flexItem.flexedMarginBoxSize();
}
// This will std::move lineItems into a newly-created LineContext.
layoutAndPlaceChildren(crossAxisOffset, lineItems, remainingFreeSpace,
relayoutChildren, layoutScope, lineContexts);
}
if (hasLineIfEmpty()) {
// Even if ComputeNextFlexLine returns true, the flexbox might not have
// a line because all our children might be out of flow positioned.
// Instead of just checking if we have a line, make sure the flexbox
// has at least a line's worth of height to cover this case.
LayoutUnit minHeight = minimumLogicalHeightForEmptyLine();
if (size().height() < minHeight)
setLogicalHeight(minHeight);
}
updateLogicalHeight();
repositionLogicalHeightDependentFlexItems(lineContexts);
}
LayoutUnit LayoutFlexibleBox::autoMarginOffsetInMainAxis(
const Vector<FlexItem>& children,
LayoutUnit& availableFreeSpace) {
if (availableFreeSpace <= LayoutUnit())
return LayoutUnit();
int numberOfAutoMargins = 0;
bool isHorizontal = isHorizontalFlow();
for (size_t i = 0; i < children.size(); ++i) {
LayoutBox* child = children[i].box;
DCHECK(!child->isOutOfFlowPositioned());
if (isHorizontal) {
if (child->style()->marginLeft().isAuto())
++numberOfAutoMargins;
if (child->style()->marginRight().isAuto())
++numberOfAutoMargins;
} else {
if (child->style()->marginTop().isAuto())
++numberOfAutoMargins;
if (child->style()->marginBottom().isAuto())
++numberOfAutoMargins;
}
}
if (!numberOfAutoMargins)
return LayoutUnit();
LayoutUnit sizeOfAutoMargin = availableFreeSpace / numberOfAutoMargins;
availableFreeSpace = LayoutUnit();
return sizeOfAutoMargin;
}
void LayoutFlexibleBox::updateAutoMarginsInMainAxis(
LayoutBox& child,
LayoutUnit autoMarginOffset) {
DCHECK_GE(autoMarginOffset, LayoutUnit());
if (isHorizontalFlow()) {
if (child.style()->marginLeft().isAuto())
child.setMarginLeft(autoMarginOffset);
if (child.style()->marginRight().isAuto())
child.setMarginRight(autoMarginOffset);
} else {
if (child.style()->marginTop().isAuto())
child.setMarginTop(autoMarginOffset);
if (child.style()->marginBottom().isAuto())
child.setMarginBottom(autoMarginOffset);
}
}
bool LayoutFlexibleBox::hasAutoMarginsInCrossAxis(
const LayoutBox& child) const {
if (isHorizontalFlow())
return child.style()->marginTop().isAuto() ||
child.style()->marginBottom().isAuto();
return child.style()->marginLeft().isAuto() ||
child.style()->marginRight().isAuto();
}
LayoutUnit LayoutFlexibleBox::availableAlignmentSpaceForChild(
LayoutUnit lineCrossAxisExtent,
const LayoutBox& child) {
DCHECK(!child.isOutOfFlowPositioned());
LayoutUnit childCrossExtent =
crossAxisMarginExtentForChild(child) + crossAxisExtentForChild(child);
return lineCrossAxisExtent - childCrossExtent;
}
LayoutUnit LayoutFlexibleBox::availableAlignmentSpaceForChildBeforeStretching(
LayoutUnit lineCrossAxisExtent,
const LayoutBox& child) {
DCHECK(!child.isOutOfFlowPositioned());
LayoutUnit childCrossExtent = crossAxisMarginExtentForChild(child) +
crossAxisIntrinsicExtentForChild(child);
return lineCrossAxisExtent - childCrossExtent;
}
bool LayoutFlexibleBox::updateAutoMarginsInCrossAxis(
LayoutBox& child,
LayoutUnit availableAlignmentSpace) {
DCHECK(!child.isOutOfFlowPositioned());
DCHECK_GE(availableAlignmentSpace, LayoutUnit());
bool isHorizontal = isHorizontalFlow();
Length topOrLeft =
isHorizontal ? child.style()->marginTop() : child.style()->marginLeft();
Length bottomOrRight = isHorizontal ? child.style()->marginBottom()
: child.style()->marginRight();
if (topOrLeft.isAuto() && bottomOrRight.isAuto()) {
adjustAlignmentForChild(child, availableAlignmentSpace / 2);
if (isHorizontal) {
child.setMarginTop(availableAlignmentSpace / 2);
child.setMarginBottom(availableAlignmentSpace / 2);
} else {
child.setMarginLeft(availableAlignmentSpace / 2);
child.setMarginRight(availableAlignmentSpace / 2);
}
return true;
}
bool shouldAdjustTopOrLeft = true;
if (isColumnFlow() && !child.style()->isLeftToRightDirection()) {
// For column flows, only make this adjustment if topOrLeft corresponds to
// the "before" margin, so that flipForRightToLeftColumn will do the right
// thing.
shouldAdjustTopOrLeft = false;
}
if (!isColumnFlow() && child.style()->isFlippedBlocksWritingMode()) {
// If we are a flipped writing mode, we need to adjust the opposite side.
// This is only needed for row flows because this only affects the
// block-direction axis.
shouldAdjustTopOrLeft = false;
}
if (topOrLeft.isAuto()) {
if (shouldAdjustTopOrLeft)
adjustAlignmentForChild(child, availableAlignmentSpace);
if (isHorizontal)
child.setMarginTop(availableAlignmentSpace);
else
child.setMarginLeft(availableAlignmentSpace);
return true;
}
if (bottomOrRight.isAuto()) {
if (!shouldAdjustTopOrLeft)
adjustAlignmentForChild(child, availableAlignmentSpace);
if (isHorizontal)
child.setMarginBottom(availableAlignmentSpace);
else
child.setMarginRight(availableAlignmentSpace);
return true;
}
return false;
}
DISABLE_CFI_PERF
LayoutUnit LayoutFlexibleBox::marginBoxAscentForChild(const LayoutBox& child) {
LayoutUnit ascent(child.firstLineBoxBaseline());
if (ascent == -1)
ascent = crossAxisExtentForChild(child);
return ascent + flowAwareMarginBeforeForChild(child);
}
LayoutUnit LayoutFlexibleBox::computeChildMarginValue(Length margin) {
// When resolving the margins, we use the content size for resolving percent
// and calc (for percents in calc expressions) margins. Fortunately, percent
// margins are always computed with respect to the block's width, even for
// margin-top and margin-bottom.
LayoutUnit availableSize = contentLogicalWidth();
return minimumValueForLength(margin, availableSize);
}
void LayoutFlexibleBox::prepareOrderIteratorAndMargins() {
OrderIteratorPopulator populator(m_orderIterator);
for (LayoutBox* child = firstChildBox(); child;
child = child->nextSiblingBox()) {
populator.collectChild(child);
if (child->isOutOfFlowPositioned())
continue;
// Before running the flex algorithm, 'auto' has a margin of 0.
// Also, if we're not auto sizing, we don't do a layout that computes the
// start/end margins.
if (isHorizontalFlow()) {
child->setMarginLeft(
computeChildMarginValue(child->style()->marginLeft()));
child->setMarginRight(
computeChildMarginValue(child->style()->marginRight()));
} else {
child->setMarginTop(computeChildMarginValue(child->style()->marginTop()));
child->setMarginBottom(
computeChildMarginValue(child->style()->marginBottom()));
}
}
}
DISABLE_CFI_PERF
LayoutUnit LayoutFlexibleBox::adjustChildSizeForMinAndMax(
const LayoutBox& child,
LayoutUnit childSize) {
Length max = isHorizontalFlow() ? child.style()->maxWidth()
: child.style()->maxHeight();
LayoutUnit maxExtent(-1);
if (max.isSpecifiedOrIntrinsic()) {
maxExtent = computeMainAxisExtentForChild(child, MaxSize, max);
DCHECK_GE(maxExtent, LayoutUnit(-1));
if (maxExtent != -1 && childSize > maxExtent)
childSize = maxExtent;
}
Length min = isHorizontalFlow() ? child.style()->minWidth()
: child.style()->minHeight();
LayoutUnit minExtent;
if (min.isSpecifiedOrIntrinsic()) {
minExtent = computeMainAxisExtentForChild(child, MinSize, min);
// computeMainAxisExtentForChild can return -1 when the child has a
// percentage min size, but we have an indefinite size in that axis.
minExtent = std::max(LayoutUnit(), minExtent);
} else if (min.isAuto() && !child.styleRef().containsSize() &&
mainAxisOverflowForChild(child) == EOverflow::Visible &&
!(isColumnFlow() && child.isFlexibleBox())) {
// TODO(cbiesinger): For now, we do not handle min-height: auto for nested
// column flexboxes. We need to implement
// https://drafts.csswg.org/css-flexbox/#intrinsic-sizes before that
// produces reasonable results. Tracking bug: https://crbug.com/581553
// css-flexbox section 4.5
LayoutUnit contentSize =
computeMainAxisExtentForChild(child, MinSize, Length(MinContent));
DCHECK_GE(contentSize, LayoutUnit());
if (hasAspectRatio(child) && child.intrinsicSize().height() > 0)
contentSize =
adjustChildSizeForAspectRatioCrossAxisMinAndMax(child, contentSize);
if (maxExtent != -1 && contentSize > maxExtent)
contentSize = maxExtent;
Length mainSize = isHorizontalFlow() ? child.styleRef().width()
: child.styleRef().height();
if (mainAxisLengthIsDefinite(child, mainSize)) {
LayoutUnit resolvedMainSize =
computeMainAxisExtentForChild(child, MainOrPreferredSize, mainSize);
DCHECK_GE(resolvedMainSize, LayoutUnit());
LayoutUnit specifiedSize = maxExtent != -1
? std::min(resolvedMainSize, maxExtent)
: resolvedMainSize;
minExtent = std::min(specifiedSize, contentSize);
} else if (useChildAspectRatio(child)) {
Length crossSizeLength = isHorizontalFlow() ? child.styleRef().height()
: child.styleRef().width();
LayoutUnit transferredSize =
computeMainSizeFromAspectRatioUsing(child, crossSizeLength);
transferredSize = adjustChildSizeForAspectRatioCrossAxisMinAndMax(
child, transferredSize);
minExtent = std::min(transferredSize, contentSize);
} else {
minExtent = contentSize;
}
}
DCHECK_GE(minExtent, LayoutUnit());
return std::max(childSize, minExtent);
}
LayoutUnit LayoutFlexibleBox::crossSizeForPercentageResolution(
const LayoutBox& child) {
if (alignmentForChild(child) != ItemPositionStretch)
return LayoutUnit(-1);
// Here we implement https://drafts.csswg.org/css-flexbox/#algo-stretch
if (hasOrthogonalFlow(child) && child.hasOverrideLogicalContentWidth())
return child.overrideLogicalContentWidth();
if (!hasOrthogonalFlow(child) && child.hasOverrideLogicalContentHeight())
return child.overrideLogicalContentHeight();
// We don't currently implement the optimization from
// https://drafts.csswg.org/css-flexbox/#definite-sizes case 1. While that
// could speed up a specialized case, it requires determining if we have a
// definite size, which itself is not cheap. We can consider implementing it
// at a later time. (The correctness is ensured by redoing layout in
// applyStretchAlignmentToChild)
return LayoutUnit(-1);
}
LayoutUnit LayoutFlexibleBox::mainSizeForPercentageResolution(
const LayoutBox& child) {
// This function implements section 9.8. Definite and Indefinite Sizes, case
// 2) of the flexbox spec.
// We need to check for the flexbox to have a definite main size, and for the
// flex item to have a definite flex basis.
const Length& flexBasis = flexBasisForChild(child);
if (!mainAxisLengthIsDefinite(child, flexBasis))
return LayoutUnit(-1);
if (!flexBasis.isPercentOrCalc()) {
// If flex basis had a percentage, our size is guaranteed to be definite or
// the flex item's size could not be definite. Otherwise, we make up a
// percentage to check whether we have a definite size.
if (!mainAxisLengthIsDefinite(child, Length(0, Percent)))
return LayoutUnit(-1);
}
if (hasOrthogonalFlow(child))
return child.hasOverrideLogicalContentHeight()
? child.overrideLogicalContentHeight()
: LayoutUnit(-1);
return child.hasOverrideLogicalContentWidth()
? child.overrideLogicalContentWidth()
: LayoutUnit(-1);
}
LayoutUnit LayoutFlexibleBox::childLogicalHeightForPercentageResolution(
const LayoutBox& child) {
if (!hasOrthogonalFlow(child))
return crossSizeForPercentageResolution(child);
return mainSizeForPercentageResolution(child);
}
LayoutUnit LayoutFlexibleBox::adjustChildSizeForAspectRatioCrossAxisMinAndMax(
const LayoutBox& child,
LayoutUnit childSize) {
Length crossMin = isHorizontalFlow() ? child.style()->minHeight()
: child.style()->minWidth();
Length crossMax = isHorizontalFlow() ? child.style()->maxHeight()
: child.style()->maxWidth();
if (crossAxisLengthIsDefinite(child, crossMax)) {
LayoutUnit maxValue = computeMainSizeFromAspectRatioUsing(child, crossMax);
childSize = std::min(maxValue, childSize);
}
if (crossAxisLengthIsDefinite(child, crossMin)) {
LayoutUnit minValue = computeMainSizeFromAspectRatioUsing(child, crossMin);
childSize = std::max(minValue, childSize);
}
return childSize;
}
DISABLE_CFI_PERF
FlexItem LayoutFlexibleBox::constructFlexItem(LayoutBox& child,
ChildLayoutType layoutType) {
// If this condition is true, then computeMainAxisExtentForChild will call
// child.intrinsicContentLogicalHeight() and
// child.scrollbarLogicalHeight(), so if the child has intrinsic
// min/max/preferred size, run layout on it now to make sure its logical
// height and scroll bars are up to date.
if (layoutType != NeverLayout && childHasIntrinsicMainAxisSize(child) &&
child.needsLayout()) {
child.clearOverrideSize();
child.forceChildLayout();
cacheChildMainSize(child);
layoutType = LayoutIfNeeded;
}
LayoutUnit borderAndPadding = isHorizontalFlow()
? child.borderAndPaddingWidth()
: child.borderAndPaddingHeight();
LayoutUnit childInnerFlexBaseSize =
computeInnerFlexBaseSizeForChild(child, borderAndPadding, layoutType);
LayoutUnit childMinMaxAppliedMainAxisExtent =
adjustChildSizeForMinAndMax(child, childInnerFlexBaseSize);
LayoutUnit margin =
isHorizontalFlow() ? child.marginWidth() : child.marginHeight();
return FlexItem(&child, childInnerFlexBaseSize,
childMinMaxAppliedMainAxisExtent, borderAndPadding, margin);
}
void LayoutFlexibleBox::freezeViolations(Vector<FlexItem*>& violations,
LayoutUnit& availableFreeSpace,
double& totalFlexGrow,
double& totalFlexShrink,
double& totalWeightedFlexShrink) {
for (size_t i = 0; i < violations.size(); ++i) {
DCHECK(!violations[i]->frozen) << i;
LayoutBox* child = violations[i]->box;
LayoutUnit childSize = violations[i]->flexedContentSize;
availableFreeSpace -= childSize - violations[i]->flexBaseContentSize;
totalFlexGrow -= child->style()->flexGrow();
totalFlexShrink -= child->style()->flexShrink();
totalWeightedFlexShrink -=
child->style()->flexShrink() * violations[i]->flexBaseContentSize;
// totalWeightedFlexShrink can be negative when we exceed the precision of
// a double when we initially calcuate totalWeightedFlexShrink. We then
// subtract each child's weighted flex shrink with full precision, now
// leading to a negative result. See
// css3/flexbox/large-flex-shrink-assert.html
totalWeightedFlexShrink = std::max(totalWeightedFlexShrink, 0.0);
violations[i]->frozen = true;
}
}
void LayoutFlexibleBox::freezeInflexibleItems(FlexSign flexSign,
Vector<FlexItem>& children,
LayoutUnit& remainingFreeSpace,
double& totalFlexGrow,
double& totalFlexShrink,
double& totalWeightedFlexShrink) {
// Per https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths step 2,
// we freeze all items with a flex factor of 0 as well as those with a min/max
// size violation.
Vector<FlexItem*> newInflexibleItems;
for (size_t i = 0; i < children.size(); ++i) {
FlexItem& flexItem = children[i];
LayoutBox* child = flexItem.box;
DCHECK(!flexItem.box->isOutOfFlowPositioned());
DCHECK(!flexItem.frozen) << i;
float flexFactor = (flexSign == PositiveFlexibility)
? child->style()->flexGrow()
: child->style()->flexShrink();
if (flexFactor == 0 ||
(flexSign == PositiveFlexibility &&
flexItem.flexBaseContentSize > flexItem.hypotheticalMainContentSize) ||
(flexSign == NegativeFlexibility &&
flexItem.flexBaseContentSize < flexItem.hypotheticalMainContentSize)) {
flexItem.flexedContentSize = flexItem.hypotheticalMainContentSize;
newInflexibleItems.append(&flexItem);
}
}
freezeViolations(newInflexibleItems, remainingFreeSpace, totalFlexGrow,
totalFlexShrink, totalWeightedFlexShrink);
}
// Returns true if we successfully ran the algorithm and sized the flex items.
bool LayoutFlexibleBox::resolveFlexibleLengths(
FlexSign flexSign,
Vector<FlexItem>& children,
LayoutUnit initialFreeSpace,
LayoutUnit& remainingFreeSpace,
double& totalFlexGrow,
double& totalFlexShrink,
double& totalWeightedFlexShrink) {
LayoutUnit totalViolation;
LayoutUnit usedFreeSpace;
Vector<FlexItem*> minViolations;
Vector<FlexItem*> maxViolations;
double sumFlexFactors =
(flexSign == PositiveFlexibility) ? totalFlexGrow : totalFlexShrink;
if (sumFlexFactors > 0 && sumFlexFactors < 1) {
LayoutUnit fractional(initialFreeSpace * sumFlexFactors);
if (fractional.abs() < remainingFreeSpace.abs())
remainingFreeSpace = fractional;
}
for (size_t i = 0; i < children.size(); ++i) {
FlexItem& flexItem = children[i];
LayoutBox* child = flexItem.box;
// This check also covers out-of-flow children.
if (flexItem.frozen)
continue;
LayoutUnit childSize = flexItem.flexBaseContentSize;
double extraSpace = 0;
if (remainingFreeSpace > 0 && totalFlexGrow > 0 &&
flexSign == PositiveFlexibility && std::isfinite(totalFlexGrow)) {
extraSpace =
remainingFreeSpace * child->style()->flexGrow() / totalFlexGrow;
} else if (remainingFreeSpace < 0 && totalWeightedFlexShrink > 0 &&
flexSign == NegativeFlexibility &&
std::isfinite(totalWeightedFlexShrink) &&
child->style()->flexShrink()) {
extraSpace = remainingFreeSpace * child->style()->flexShrink() *
flexItem.flexBaseContentSize / totalWeightedFlexShrink;
}
if (std::isfinite(extraSpace))
childSize += LayoutUnit::fromFloatRound(extraSpace);
LayoutUnit adjustedChildSize =
adjustChildSizeForMinAndMax(*child, childSize);
DCHECK_GE(adjustedChildSize, 0);
flexItem.flexedContentSize = adjustedChildSize;
usedFreeSpace += adjustedChildSize - flexItem.flexBaseContentSize;
LayoutUnit violation = adjustedChildSize - childSize;
if (violation > 0)
minViolations.append(&flexItem);
else if (violation < 0)
maxViolations.append(&flexItem);
totalViolation += violation;
}
if (totalViolation)
freezeViolations(totalViolation < 0 ? maxViolations : minViolations,
remainingFreeSpace, totalFlexGrow, totalFlexShrink,
totalWeightedFlexShrink);
else
remainingFreeSpace -= usedFreeSpace;
return !totalViolation;
}
static LayoutUnit initialJustifyContentOffset(
LayoutUnit availableFreeSpace,
ContentPosition justifyContent,
ContentDistributionType justifyContentDistribution,
unsigned numberOfChildren) {
if (justifyContent == ContentPositionFlexEnd)
return availableFreeSpace;
if (justifyContent == ContentPositionCenter)
return availableFreeSpace / 2;
if (justifyContentDistribution == ContentDistributionSpaceAround) {
if (availableFreeSpace > 0 && numberOfChildren)
return availableFreeSpace / (2 * numberOfChildren);
return availableFreeSpace / 2;
}
return LayoutUnit();
}
static LayoutUnit justifyContentSpaceBetweenChildren(
LayoutUnit availableFreeSpace,
ContentDistributionType justifyContentDistribution,
unsigned numberOfChildren) {
if (availableFreeSpace > 0 && numberOfChildren > 1) {
if (justifyContentDistribution == ContentDistributionSpaceBetween)
return availableFreeSpace / (numberOfChildren - 1);
if (justifyContentDistribution == ContentDistributionSpaceAround)
return availableFreeSpace / numberOfChildren;
}
return LayoutUnit();
}
static LayoutUnit alignmentOffset(LayoutUnit availableFreeSpace,
ItemPosition position,
LayoutUnit ascent,
LayoutUnit maxAscent,
bool isWrapReverse) {
switch (position) {
case ItemPositionAuto:
case ItemPositionNormal:
NOTREACHED();
break;
case ItemPositionStretch:
// Actual stretching must be handled by the caller. Since wrap-reverse
// flips cross start and cross end, stretch children should be aligned
// with the cross end. This matters because applyStretchAlignment
// doesn't always stretch or stretch fully (explicit cross size given, or
// stretching constrained by max-height/max-width). For flex-start and
// flex-end this is handled by alignmentForChild().
if (isWrapReverse)
return availableFreeSpace;
break;
case ItemPositionFlexStart:
break;
case ItemPositionFlexEnd:
return availableFreeSpace;
case ItemPositionCenter:
return availableFreeSpace / 2;
case ItemPositionBaseline:
// FIXME: If we get here in columns, we want the use the descent, except
// we currently can't get the ascent/descent of orthogonal children.
// https://bugs.webkit.org/show_bug.cgi?id=98076
return maxAscent - ascent;
case ItemPositionLastBaseline:
case ItemPositionSelfStart:
case ItemPositionSelfEnd:
case ItemPositionStart:
case ItemPositionEnd:
case ItemPositionLeft:
case ItemPositionRight:
// FIXME: Implement these (https://crbug.com/507690). The extended grammar
// is not enabled by default so we shouldn't hit this codepath.
// The new grammar is only used when Grid Layout feature is enabled.
DCHECK(RuntimeEnabledFeatures::cssGridLayoutEnabled());
break;
}
return LayoutUnit();
}
void LayoutFlexibleBox::setOverrideMainAxisContentSizeForChild(
LayoutBox& child,
LayoutUnit childPreferredSize) {
if (hasOrthogonalFlow(child))
child.setOverrideLogicalContentHeight(childPreferredSize);
else
child.setOverrideLogicalContentWidth(childPreferredSize);
}
LayoutUnit LayoutFlexibleBox::staticMainAxisPositionForPositionedChild(
const LayoutBox& child) {
const LayoutUnit availableSpace =
mainAxisContentExtent(contentLogicalHeight()) -
mainAxisExtentForChild(child);
ContentPosition position = styleRef().resolvedJustifyContentPosition(
contentAlignmentNormalBehavior());
ContentDistributionType distribution =
styleRef().resolvedJustifyContentDistribution(
contentAlignmentNormalBehavior());
LayoutUnit offset =
initialJustifyContentOffset(availableSpace, position, distribution, 1);
if (styleRef().flexDirection() == FlowRowReverse ||
styleRef().flexDirection() == FlowColumnReverse)
offset = availableSpace - offset;
return offset;
}
LayoutUnit LayoutFlexibleBox::staticCrossAxisPositionForPositionedChild(
const LayoutBox& child) {
LayoutUnit availableSpace =
crossAxisContentExtent() - crossAxisExtentForChild(child);
return alignmentOffset(availableSpace, alignmentForChild(child), LayoutUnit(),
LayoutUnit(),
styleRef().flexWrap() == FlexWrapReverse);
}
LayoutUnit LayoutFlexibleBox::staticInlinePositionForPositionedChild(
const LayoutBox& child) {
return startOffsetForContent() +
(isColumnFlow() ? staticCrossAxisPositionForPositionedChild(child)
: staticMainAxisPositionForPositionedChild(child));
}
LayoutUnit LayoutFlexibleBox::staticBlockPositionForPositionedChild(
const LayoutBox& child) {
return borderAndPaddingBefore() +
(isColumnFlow() ? staticMainAxisPositionForPositionedChild(child)
: staticCrossAxisPositionForPositionedChild(child));
}
bool LayoutFlexibleBox::setStaticPositionForPositionedLayout(LayoutBox& child) {
bool positionChanged = false;
PaintLayer* childLayer = child.layer();
if (child.styleRef().hasStaticInlinePosition(
styleRef().isHorizontalWritingMode())) {
LayoutUnit inlinePosition = staticInlinePositionForPositionedChild(child);
if (childLayer->staticInlinePosition() != inlinePosition) {
childLayer->setStaticInlinePosition(inlinePosition);
positionChanged = true;
}
}
if (child.styleRef().hasStaticBlockPosition(
styleRef().isHorizontalWritingMode())) {
LayoutUnit blockPosition = staticBlockPositionForPositionedChild(child);
if (childLayer->staticBlockPosition() != blockPosition) {
childLayer->setStaticBlockPosition(blockPosition);
positionChanged = true;
}
}
return positionChanged;
}
void LayoutFlexibleBox::prepareChildForPositionedLayout(LayoutBox& child) {
DCHECK(child.isOutOfFlowPositioned());
child.containingBlock()->insertPositionedObject(&child);
PaintLayer* childLayer = child.layer();
LayoutUnit staticInlinePosition =
flowAwareBorderStart() + flowAwarePaddingStart();
if (childLayer->staticInlinePosition() != staticInlinePosition) {
childLayer->setStaticInlinePosition(staticInlinePosition);
if (child.style()->hasStaticInlinePosition(
style()->isHorizontalWritingMode()))
child.setChildNeedsLayout(MarkOnlyThis);
}
LayoutUnit staticBlockPosition =
flowAwareBorderBefore() + flowAwarePaddingBefore();
if (childLayer->staticBlockPosition() != staticBlockPosition) {
childLayer->setStaticBlockPosition(staticBlockPosition);
if (child.style()->hasStaticBlockPosition(
style()->isHorizontalWritingMode()))
child.setChildNeedsLayout(MarkOnlyThis);
}
}
ItemPosition LayoutFlexibleBox::alignmentForChild(
const LayoutBox& child) const {
ItemPosition align =
child.styleRef()
.resolvedAlignSelf(selfAlignmentNormalBehavior(),
child.isAnonymous() ? style() : nullptr)
.position();
DCHECK(align != ItemPositionAuto && align != ItemPositionNormal);
if (align == ItemPositionBaseline && hasOrthogonalFlow(child))
align = ItemPositionFlexStart;
if (style()->flexWrap() == FlexWrapReverse) {
if (align == ItemPositionFlexStart)
align = ItemPositionFlexEnd;
else if (align == ItemPositionFlexEnd)
align = ItemPositionFlexStart;
}
return align;
}
void LayoutFlexibleBox::resetAutoMarginsAndLogicalTopInCrossAxis(
LayoutBox& child) {
if (hasAutoMarginsInCrossAxis(child)) {
child.updateLogicalHeight();
if (isHorizontalFlow()) {
if (child.style()->marginTop().isAuto())
child.setMarginTop(LayoutUnit());
if (child.style()->marginBottom().isAuto())
child.setMarginBottom(LayoutUnit());
} else {
if (child.style()->marginLeft().isAuto())
child.setMarginLeft(LayoutUnit());
if (child.style()->marginRight().isAuto())
child.setMarginRight(LayoutUnit());
}
}
}
bool LayoutFlexibleBox::needToStretchChildLogicalHeight(
const LayoutBox& child) const {
// This function is a little bit magical. It relies on the fact that blocks
// intrinsically "stretch" themselves in their inline axis, i.e. a <div> has
// an implicit width: 100%. So the child will automatically stretch if our
// cross axis is the child's inline axis. That's the case if:
// - We are horizontal and the child is in vertical writing mode
// - We are vertical and the child is in horizontal writing mode
// Otherwise, we need to stretch if the cross axis size is auto.
if (alignmentForChild(child) != ItemPositionStretch)
return false;
if (isHorizontalFlow() != child.styleRef().isHorizontalWritingMode())
return false;
return child.styleRef().logicalHeight().isAuto();
}
bool LayoutFlexibleBox::childHasIntrinsicMainAxisSize(
const LayoutBox& child) const {
bool result = false;
if (isHorizontalFlow() != child.styleRef().isHorizontalWritingMode()) {
Length childFlexBasis = flexBasisForChild(child);
Length childMinSize = isHorizontalFlow() ? child.style()->minWidth()
: child.style()->minHeight();
Length childMaxSize = isHorizontalFlow() ? child.style()->maxWidth()
: child.style()->maxHeight();
if (childFlexBasis.isIntrinsic() || childMinSize.isIntrinsicOrAuto() ||
childMaxSize.isIntrinsic())
result = true;
}
return result;
}
EOverflow LayoutFlexibleBox::mainAxisOverflowForChild(
const LayoutBox& child) const {
if (isHorizontalFlow())
return child.styleRef().overflowX();
return child.styleRef().overflowY();
}
EOverflow LayoutFlexibleBox::crossAxisOverflowForChild(
const LayoutBox& child) const {
if (isHorizontalFlow())
return child.styleRef().overflowY();
return child.styleRef().overflowX();
}
DISABLE_CFI_PERF
void LayoutFlexibleBox::layoutAndPlaceChildren(
LayoutUnit& crossAxisOffset,
Vector<FlexItem>& children,
LayoutUnit availableFreeSpace,
bool relayoutChildren,
SubtreeLayoutScope& layoutScope,
Vector<LineContext>& lineContexts) {
ContentPosition position = styleRef().resolvedJustifyContentPosition(
contentAlignmentNormalBehavior());
ContentDistributionType distribution =
styleRef().resolvedJustifyContentDistribution(
contentAlignmentNormalBehavior());
LayoutUnit autoMarginOffset =
autoMarginOffsetInMainAxis(children, availableFreeSpace);
LayoutUnit mainAxisOffset = flowAwareBorderStart() + flowAwarePaddingStart();
mainAxisOffset += initialJustifyContentOffset(availableFreeSpace, position,
distribution, children.size());
if (style()->flexDirection() == FlowRowReverse &&
shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
mainAxisOffset += isHorizontalFlow() ? verticalScrollbarWidth()
: horizontalScrollbarHeight();
LayoutUnit totalMainExtent = mainAxisExtent();
if (!shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
totalMainExtent -= isHorizontalFlow() ? verticalScrollbarWidth()
: horizontalScrollbarHeight();
LayoutUnit maxAscent, maxDescent; // Used when align-items: baseline.
LayoutUnit maxChildCrossAxisExtent;
bool shouldFlipMainAxis = !isColumnFlow() && !isLeftToRightFlow();
bool isPaginated = view()->layoutState()->isPaginated();
for (size_t i = 0; i < children.size(); ++i) {
const FlexItem& flexItem = children[i];
LayoutBox* child = flexItem.box;
DCHECK(!flexItem.box->isOutOfFlowPositioned());
child->setMayNeedPaintInvalidation();
setOverrideMainAxisContentSizeForChild(*child, flexItem.flexedContentSize);
// The flexed content size and the override size include the scrollbar
// width, so we need to compare to the size including the scrollbar.
// TODO(cbiesinger): Should it include the scrollbar?
if (flexItem.flexedContentSize !=
mainAxisContentExtentForChildIncludingScrollbar(*child)) {
child->setChildNeedsLayout(MarkOnlyThis);
} else {
// To avoid double applying margin changes in
// updateAutoMarginsInCrossAxis, we reset the margins here.
resetAutoMarginsAndLogicalTopInCrossAxis(*child);
}
// We may have already forced relayout for orthogonal flowing children in
// computeInnerFlexBaseSizeForChild.
bool forceChildRelayout =
relayoutChildren && !m_relaidOutChildren.contains(child);
if (child->isLayoutBlock() &&
toLayoutBlock(*child).hasPercentHeightDescendants()) {
// Have to force another relayout even though the child is sized
// correctly, because its descendants are not sized correctly yet. Our
// previous layout of the child was done without an override height set.
// So, redo it here.
forceChildRelayout = true;
}
updateBlockChildDirtyBitsBeforeLayout(forceChildRelayout, *child);
if (!child->needsLayout())
markChildForPaginationRelayoutIfNeeded(*child, layoutScope);
if (child->needsLayout())
m_relaidOutChildren.add(child);
child->layoutIfNeeded();
updateAutoMarginsInMainAxis(*child, autoMarginOffset);
LayoutUnit childCrossAxisMarginBoxExtent;
if (alignmentForChild(*child) == ItemPositionBaseline &&
!hasAutoMarginsInCrossAxis(*child)) {
LayoutUnit ascent = marginBoxAscentForChild(*child);
LayoutUnit descent = (crossAxisMarginExtentForChild(*child) +
crossAxisExtentForChild(*child)) -
ascent;
maxAscent = std::max(maxAscent, ascent);
maxDescent = std::max(maxDescent, descent);
// TODO(cbiesinger): Take scrollbar into account
childCrossAxisMarginBoxExtent = maxAscent + maxDescent;
} else {
childCrossAxisMarginBoxExtent = crossAxisIntrinsicExtentForChild(*child) +
crossAxisMarginExtentForChild(*child) +
crossAxisScrollbarExtentForChild(*child);
}
if (!isColumnFlow())
setLogicalHeight(std::max(
logicalHeight(),
crossAxisOffset + flowAwareBorderAfter() + flowAwarePaddingAfter() +
childCrossAxisMarginBoxExtent + crossAxisScrollbarExtent()));
maxChildCrossAxisExtent =
std::max(maxChildCrossAxisExtent, childCrossAxisMarginBoxExtent);
mainAxisOffset += flowAwareMarginStartForChild(*child);
LayoutUnit childMainExtent = mainAxisExtentForChild(*child);
// In an RTL column situation, this will apply the margin-right/margin-end
// on the left. This will be fixed later in flipForRightToLeftColumn.
LayoutPoint childLocation(
shouldFlipMainAxis ? totalMainExtent - mainAxisOffset - childMainExtent
: mainAxisOffset,
crossAxisOffset + flowAwareMarginBeforeForChild(*child));
setFlowAwareLocationForChild(*child, childLocation);
mainAxisOffset += childMainExtent + flowAwareMarginEndForChild(*child);
mainAxisOffset += justifyContentSpaceBetweenChildren(
availableFreeSpace, distribution, children.size());
if (isPaginated)
updateFragmentationInfoForChild(*child);
}
if (isColumnFlow())
setLogicalHeight(std::max(
logicalHeight(), mainAxisOffset + flowAwareBorderEnd() +
flowAwarePaddingEnd() + scrollbarLogicalHeight()));
if (style()->flexDirection() == FlowColumnReverse) {
// We have to do an extra pass for column-reverse to reposition the flex
// items since the start depends on the height of the flexbox, which we
// only know after we've positioned all the flex items.
updateLogicalHeight();
layoutColumnReverse(children, crossAxisOffset, availableFreeSpace);
}
if (m_numberOfInFlowChildrenOnFirstLine == -1)
m_numberOfInFlowChildrenOnFirstLine = children.size();
lineContexts.append(LineContext(crossAxisOffset, maxChildCrossAxisExtent,
maxAscent, std::move(children)));
crossAxisOffset += maxChildCrossAxisExtent;
}
void LayoutFlexibleBox::layoutColumnReverse(const Vector<FlexItem>& children,
LayoutUnit crossAxisOffset,
LayoutUnit availableFreeSpace) {
ContentPosition position = styleRef().resolvedJustifyContentPosition(
contentAlignmentNormalBehavior());
ContentDistributionType distribution =
styleRef().resolvedJustifyContentDistribution(
contentAlignmentNormalBehavior());
// This is similar to the logic in layoutAndPlaceChildren, except we place
// the children starting from the end of the flexbox. We also don't need to
// layout anything since we're just moving the children to a new position.
LayoutUnit mainAxisOffset =
logicalHeight() - flowAwareBorderEnd() - flowAwarePaddingEnd();
mainAxisOffset -= initialJustifyContentOffset(availableFreeSpace, position,
distribution, children.size());
mainAxisOffset -= isHorizontalFlow() ? verticalScrollbarWidth()
: horizontalScrollbarHeight();
for (size_t i = 0; i < children.size(); ++i) {
LayoutBox* child = children[i].box;
DCHECK(!child->isOutOfFlowPositioned());
mainAxisOffset -=
mainAxisExtentForChild(*child) + flowAwareMarginEndForChild(*child);
setFlowAwareLocationForChild(
*child,
LayoutPoint(mainAxisOffset,
crossAxisOffset + flowAwareMarginBeforeForChild(*child)));
mainAxisOffset -= flowAwareMarginStartForChild(*child);
mainAxisOffset -= justifyContentSpaceBetweenChildren(
availableFreeSpace, distribution, children.size());
}
}
static LayoutUnit initialAlignContentOffset(
LayoutUnit availableFreeSpace,
ContentPosition alignContent,
ContentDistributionType alignContentDistribution,
unsigned numberOfLines) {
if (numberOfLines <= 1)
return LayoutUnit();
if (alignContent == ContentPositionFlexEnd)
return availableFreeSpace;
if (alignContent == ContentPositionCenter)
return availableFreeSpace / 2;
if (alignContentDistribution == ContentDistributionSpaceAround) {
if (availableFreeSpace > 0 && numberOfLines)
return availableFreeSpace / (2 * numberOfLines);
if (availableFreeSpace < 0)
return availableFreeSpace / 2;
}
return LayoutUnit();
}
static LayoutUnit alignContentSpaceBetweenChildren(
LayoutUnit availableFreeSpace,
ContentDistributionType alignContentDistribution,
unsigned numberOfLines) {
if (availableFreeSpace > 0 && numberOfLines > 1) {
if (alignContentDistribution == ContentDistributionSpaceBetween)
return availableFreeSpace / (numberOfLines - 1);
if (alignContentDistribution == ContentDistributionSpaceAround ||
alignContentDistribution == ContentDistributionStretch)
return availableFreeSpace / numberOfLines;
}
return LayoutUnit();
}
void LayoutFlexibleBox::alignFlexLines(Vector<LineContext>& lineContexts) {
ContentPosition position =
styleRef().resolvedAlignContentPosition(contentAlignmentNormalBehavior());
ContentDistributionType distribution =
styleRef().resolvedAlignContentDistribution(
contentAlignmentNormalBehavior());
// If we have a single line flexbox or a multiline line flexbox with only one
// flex line, the line height is all the available space. For
// flex-direction: row, this means we need to use the height, so we do this
// after calling updateLogicalHeight.
if (lineContexts.size() == 1) {
lineContexts[0].crossAxisExtent = crossAxisContentExtent();
return;
}
if (position == ContentPositionFlexStart)
return;
LayoutUnit availableCrossAxisSpace = crossAxisContentExtent();
for (size_t i = 0; i < lineContexts.size(); ++i)
availableCrossAxisSpace -= lineContexts[i].crossAxisExtent;
LayoutUnit lineOffset = initialAlignContentOffset(
availableCrossAxisSpace, position, distribution, lineContexts.size());
for (unsigned lineNumber = 0; lineNumber < lineContexts.size();
++lineNumber) {
LineContext& lineContext = lineContexts[lineNumber];
lineContext.crossAxisOffset += lineOffset;
for (size_t childNumber = 0; childNumber < lineContext.flexItems.size();
++childNumber) {
FlexItem& flexItem = lineContext.flexItems[childNumber];
adjustAlignmentForChild(*flexItem.box, lineOffset);
}
if (distribution == ContentDistributionStretch &&
availableCrossAxisSpace > 0)
lineContexts[lineNumber].crossAxisExtent +=
availableCrossAxisSpace / static_cast<unsigned>(lineContexts.size());
lineOffset += alignContentSpaceBetweenChildren(
availableCrossAxisSpace, distribution, lineContexts.size());
}
}
void LayoutFlexibleBox::adjustAlignmentForChild(LayoutBox& child,
LayoutUnit delta) {
DCHECK(!child.isOutOfFlowPositioned());
setFlowAwareLocationForChild(child, flowAwareLocationForChild(child) +
LayoutSize(LayoutUnit(), delta));
}
void LayoutFlexibleBox::alignChildren(const Vector<LineContext>& lineContexts) {
// Keep track of the space between the baseline edge and the after edge of
// the box for each line.
Vector<LayoutUnit> minMarginAfterBaselines;
for (size_t lineNumber = 0; lineNumber < lineContexts.size(); ++lineNumber) {
const LineContext& lineContext = lineContexts[lineNumber];
LayoutUnit minMarginAfterBaseline = LayoutUnit::max();
LayoutUnit lineCrossAxisExtent = lineContext.crossAxisExtent;
LayoutUnit maxAscent = lineContext.maxAscent;
for (size_t childNumber = 0; childNumber < lineContext.flexItems.size();
++childNumber) {
const FlexItem& flexItem = lineContext.flexItems[childNumber];
DCHECK(!flexItem.box->isOutOfFlowPositioned());
if (updateAutoMarginsInCrossAxis(
*flexItem.box,
std::max(LayoutUnit(), availableAlignmentSpaceForChild(
lineCrossAxisExtent, *flexItem.box))))
continue;
ItemPosition position = alignmentForChild(*flexItem.box);
if (position == ItemPositionStretch)
applyStretchAlignmentToChild(*flexItem.box, lineCrossAxisExtent);
LayoutUnit availableSpace =
availableAlignmentSpaceForChild(lineCrossAxisExtent, *flexItem.box);
LayoutUnit offset = alignmentOffset(
availableSpace, position, marginBoxAscentForChild(*flexItem.box),
maxAscent, styleRef().flexWrap() == FlexWrapReverse);
adjustAlignmentForChild(*flexItem.box, offset);
if (position == ItemPositionBaseline &&
styleRef().flexWrap() == FlexWrapReverse) {
minMarginAfterBaseline = std::min(
minMarginAfterBaseline, availableAlignmentSpaceForChild(
lineCrossAxisExtent, *flexItem.box) -
offset);
}
}
minMarginAfterBaselines.append(minMarginAfterBaseline);
}
if (style()->flexWrap() != FlexWrapReverse)
return;
// wrap-reverse flips the cross axis start and end. For baseline alignment,
// this means we need to align the after edge of baseline elements with the
// after edge of the flex line.
for (size_t lineNumber = 0; lineNumber < lineContexts.size(); ++lineNumber) {
const LineContext& lineContext = lineContexts[lineNumber];
LayoutUnit minMarginAfterBaseline = minMarginAfterBaselines[lineNumber];
for (size_t childNumber = 0; childNumber < lineContext.flexItems.size();
++childNumber) {
const FlexItem& flexItem = lineContext.flexItems[childNumber];
if (alignmentForChild(*flexItem.box) == ItemPositionBaseline &&
!hasAutoMarginsInCrossAxis(*flexItem.box) && minMarginAfterBaseline)
adjustAlignmentForChild(*flexItem.box, minMarginAfterBaseline);
}
}
}
void LayoutFlexibleBox::applyStretchAlignmentToChild(
LayoutBox& child,
LayoutUnit lineCrossAxisExtent) {
if (!hasOrthogonalFlow(child) && child.style()->logicalHeight().isAuto()) {
LayoutUnit heightBeforeStretching = childIntrinsicLogicalHeight(child);
LayoutUnit stretchedLogicalHeight =
std::max(child.borderAndPaddingLogicalHeight(),
heightBeforeStretching +
availableAlignmentSpaceForChildBeforeStretching(
lineCrossAxisExtent, child));
DCHECK(!child.needsLayout());
LayoutUnit desiredLogicalHeight = child.constrainLogicalHeightByMinMax(
stretchedLogicalHeight,
heightBeforeStretching - child.borderAndPaddingLogicalHeight());
// FIXME: Can avoid laying out here in some cases. See
// https://webkit.org/b/87905.
bool childNeedsRelayout = desiredLogicalHeight != child.logicalHeight();
if (child.isLayoutBlock() &&
toLayoutBlock(child).hasPercentHeightDescendants() &&
m_relaidOutChildren.contains(&child)) {
// Have to force another relayout even though the child is sized
// correctly, because its descendants are not sized correctly yet. Our
// previous layout of the child was done without an override height set.
// So, redo it here.
childNeedsRelayout = true;
}
if (childNeedsRelayout || !child.hasOverrideLogicalContentHeight())
child.setOverrideLogicalContentHeight(
desiredLogicalHeight - child.borderAndPaddingLogicalHeight());
if (childNeedsRelayout) {
child.setLogicalHeight(LayoutUnit());
// We cache the child's intrinsic content logical height to avoid it being
// reset to the stretched height.
// FIXME: This is fragile. LayoutBoxes should be smart enough to
// determine their intrinsic content logical height correctly even when
// there's an overrideHeight.
LayoutUnit childIntrinsicContentLogicalHeight =
child.intrinsicContentLogicalHeight();
child.forceChildLayout();
child.setIntrinsicContentLogicalHeight(
childIntrinsicContentLogicalHeight);
}
} else if (hasOrthogonalFlow(child) &&
child.style()->logicalWidth().isAuto()) {
LayoutUnit childWidth =
(lineCrossAxisExtent - crossAxisMarginExtentForChild(child))
.clampNegativeToZero();
childWidth =
child.constrainLogicalWidthByMinMax(childWidth, childWidth, this);
if (childWidth != child.logicalWidth()) {
child.setOverrideLogicalContentWidth(
childWidth - child.borderAndPaddingLogicalWidth());
child.forceChildLayout();
}
}
}
void LayoutFlexibleBox::flipForRightToLeftColumn(
const Vector<LineContext>& lineContexts) {
if (style()->isLeftToRightDirection() || !isColumnFlow())
return;
LayoutUnit crossExtent = crossAxisExtent();
for (size_t lineNumber = 0; lineNumber < lineContexts.size(); ++lineNumber) {
const LineContext& lineContext = lineContexts[lineNumber];
for (size_t childNumber = 0; childNumber < lineContext.flexItems.size();
++childNumber) {
const FlexItem& flexItem = lineContext.flexItems[childNumber];
DCHECK(!flexItem.box->isOutOfFlowPositioned());
LayoutPoint location = flowAwareLocationForChild(*flexItem.box);
// For vertical flows, setFlowAwareLocationForChild will transpose x and
// y,
// so using the y axis for a column cross axis extent is correct.
location.setY(crossExtent - crossAxisExtentForChild(*flexItem.box) -
location.y());
if (!isHorizontalWritingMode())
location.move(LayoutSize(0, -horizontalScrollbarHeight()));
setFlowAwareLocationForChild(*flexItem.box, location);
}
}
}
void LayoutFlexibleBox::flipForWrapReverse(
const Vector<LineContext>& lineContexts,
LayoutUnit crossAxisStartEdge) {
LayoutUnit contentExtent = crossAxisContentExtent();
for (size_t lineNumber = 0; lineNumber < lineContexts.size(); ++lineNumber) {
const LineContext& lineContext = lineContexts[lineNumber];
for (size_t childNumber = 0; childNumber < lineContext.flexItems.size();
++childNumber) {
const FlexItem& flexItem = lineContext.flexItems[childNumber];
LayoutUnit lineCrossAxisExtent = lineContexts[lineNumber].crossAxisExtent;
LayoutUnit originalOffset =
lineContexts[lineNumber].crossAxisOffset - crossAxisStartEdge;
LayoutUnit newOffset =
contentExtent - originalOffset - lineCrossAxisExtent;
adjustAlignmentForChild(*flexItem.box, newOffset - originalOffset);
}
}
}
} // namespace blink