blob: 79ca989986b700afb12c74e39484c1ddff478e20 [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 <limits>
#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 "platform/wtf/AutoReset.h"
#include "platform/wtf/MathExtras.h"
namespace blink {
static bool HasAspectRatio(const LayoutBox& child) {
return child.IsImage() || child.IsCanvas() || child.IsVideo();
}
struct LayoutFlexibleBox::LineContext {
LineContext(LayoutUnit cross_axis_offset,
LayoutUnit cross_axis_extent,
LayoutUnit max_ascent,
Vector<FlexItem>&& flex_items)
: cross_axis_offset(cross_axis_offset),
cross_axis_extent(cross_axis_extent),
max_ascent(max_ascent),
flex_items(flex_items) {}
LayoutUnit cross_axis_offset;
LayoutUnit cross_axis_extent;
LayoutUnit max_ascent;
Vector<FlexItem> flex_items;
};
LayoutFlexibleBox::LayoutFlexibleBox(Element* element)
: LayoutBlock(element),
order_iterator_(this),
number_of_in_flow_children_on_first_line_(-1),
has_definite_height_(SizeDefiniteness::kUnknown),
in_layout_(false) {
DCHECK(!ChildrenInline());
if (!IsAnonymous())
UseCounter::Count(GetDocument(), UseCounter::kCSSFlexibleBox);
}
LayoutFlexibleBox::~LayoutFlexibleBox() {}
LayoutFlexibleBox* LayoutFlexibleBox::CreateAnonymous(Document* document) {
LayoutFlexibleBox* layout_object = new LayoutFlexibleBox(nullptr);
layout_object->SetDocumentForAnonymous(document);
return layout_object;
}
void LayoutFlexibleBox::ComputeIntrinsicLogicalWidths(
LayoutUnit& min_logical_width,
LayoutUnit& max_logical_width) 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 previous_max_content_flex_fraction = -1;
for (LayoutBox* child = FirstChildBox(); child;
child = child->NextSiblingBox()) {
if (child->IsOutOfFlowPositioned())
continue;
LayoutUnit margin = MarginIntrinsicLogicalWidthForChild(*child);
LayoutUnit min_preferred_logical_width;
LayoutUnit max_preferred_logical_width;
ComputeChildPreferredLogicalWidths(*child, min_preferred_logical_width,
max_preferred_logical_width);
DCHECK_GE(min_preferred_logical_width, LayoutUnit());
DCHECK_GE(max_preferred_logical_width, LayoutUnit());
min_preferred_logical_width += margin;
max_preferred_logical_width += margin;
if (!IsColumnFlow()) {
max_logical_width += max_preferred_logical_width;
if (IsMultiline()) {
// For multiline, the min preferred width is if you put a break between
// each item.
min_logical_width =
std::max(min_logical_width, min_preferred_logical_width);
} else {
min_logical_width += min_preferred_logical_width;
}
} else {
min_logical_width =
std::max(min_preferred_logical_width, min_logical_width);
max_logical_width =
std::max(max_preferred_logical_width, max_logical_width);
}
previous_max_content_flex_fraction = CountIntrinsicSizeForAlgorithmChange(
max_preferred_logical_width, child, previous_max_content_flex_fraction);
}
max_logical_width = std::max(min_logical_width, max_logical_width);
// Due to negative margins, it is possible that we calculated a negative
// intrinsic width. Make sure that we never return a negative width.
min_logical_width = std::max(LayoutUnit(), min_logical_width);
max_logical_width = std::max(LayoutUnit(), max_logical_width);
LayoutUnit scrollbar_width(ScrollbarLogicalWidth());
max_logical_width += scrollbar_width;
min_logical_width += scrollbar_width;
}
float LayoutFlexibleBox::CountIntrinsicSizeForAlgorithmChange(
LayoutUnit max_preferred_logical_width,
LayoutBox* child,
float previous_max_content_flex_fraction) 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 previous_max_content_flex_fraction;
Length flex_basis = child->StyleRef().FlexBasis();
float flex_grow = 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 (flex_basis.IsAuto() || flex_grow == 0)
return previous_max_content_flex_fraction;
flex_grow = std::max(1.0f, flex_grow);
float max_content_flex_fraction =
max_preferred_logical_width.ToFloat() / flex_grow;
if (previous_max_content_flex_fraction != -1 &&
max_content_flex_fraction != previous_max_content_flex_fraction)
UseCounter::Count(GetDocument(),
UseCounter::kFlexboxIntrinsicSizeAlgorithmIsDifferent);
return max_content_flex_fraction;
}
int LayoutFlexibleBox::SynthesizedBaselineFromContentBox(
const LayoutBox& box,
LineDirectionMode direction) {
if (direction == kHorizontalLine) {
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, kPositionOnContainingLine);
int baseline = FirstLineBoxBaseline();
if (baseline == -1)
baseline = SynthesizedBaselineFromContentBox(*this, direction);
return BeforeMarginInLineDirection(direction) + baseline;
}
const StyleContentAlignmentData&
LayoutFlexibleBox::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 kNormalBehavior = {
kContentPositionNormal, kContentDistributionStretch};
return kNormalBehavior;
}
int LayoutFlexibleBox::FirstLineBoxBaseline() const {
if (IsWritingModeRoot() || number_of_in_flow_children_on_first_line_ <= 0)
return -1;
LayoutBox* baseline_child = nullptr;
int child_number = 0;
for (LayoutBox* child = order_iterator_.First(); child;
child = order_iterator_.Next()) {
if (child->IsOutOfFlowPositioned())
continue;
if (AlignmentForChild(*child) == kItemPositionBaseline &&
!HasAutoMarginsInCrossAxis(*child)) {
baseline_child = child;
break;
}
if (!baseline_child)
baseline_child = child;
++child_number;
if (child_number == number_of_in_flow_children_on_first_line_)
break;
}
if (!baseline_child)
return -1;
if (!IsColumnFlow() && HasOrthogonalFlow(*baseline_child))
return (CrossAxisExtentForChild(*baseline_child) +
baseline_child->LogicalTop())
.ToInt();
if (IsColumnFlow() && !HasOrthogonalFlow(*baseline_child))
return (MainAxisExtentForChild(*baseline_child) +
baseline_child->LogicalTop())
.ToInt();
int baseline = baseline_child->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() ? kHorizontalLine : kVerticalLine;
return (SynthesizedBaselineFromContentBox(*baseline_child, direction) +
baseline_child->LogicalTop())
.ToInt();
}
return (baseline + baseline_child->LogicalTop()).ToInt();
}
int LayoutFlexibleBox::InlineBlockBaseline(LineDirectionMode direction) const {
int baseline = FirstLineBoxBaseline();
if (baseline != -1)
return baseline;
int margin_ascent =
(direction == kHorizontalLine ? MarginTop() : MarginRight()).ToInt();
return SynthesizedBaselineFromContentBox(*this, direction) + margin_ascent;
}
IntSize LayoutFlexibleBox::OriginAdjustmentForScrollbars() const {
IntSize size;
int adjustment_width = VerticalScrollbarWidth();
int adjustment_height = HorizontalScrollbarHeight();
if (!adjustment_width && !adjustment_height)
return size;
EFlexDirection flex_direction = Style()->FlexDirection();
TextDirection text_direction = Style()->Direction();
WritingMode writing_mode = Style()->GetWritingMode();
if (flex_direction == kFlowRow) {
if (text_direction == TextDirection::kRtl) {
if (blink::IsHorizontalWritingMode(writing_mode))
size.Expand(adjustment_width, 0);
else
size.Expand(0, adjustment_height);
}
if (IsFlippedBlocksWritingMode(writing_mode))
size.Expand(adjustment_width, 0);
} else if (flex_direction == kFlowRowReverse) {
if (text_direction == TextDirection::kLtr) {
if (blink::IsHorizontalWritingMode(writing_mode))
size.Expand(adjustment_width, 0);
else
size.Expand(0, adjustment_height);
}
if (IsFlippedBlocksWritingMode(writing_mode))
size.Expand(adjustment_width, 0);
} else if (flex_direction == kFlowColumn) {
if (IsFlippedBlocksWritingMode(writing_mode))
size.Expand(adjustment_width, 0);
} else {
if (blink::IsHorizontalWritingMode(writing_mode))
size.Expand(0, adjustment_height);
else if (IsFlippedLinesWritingMode(writing_mode))
size.Expand(adjustment_width, 0);
}
return size;
}
bool LayoutFlexibleBox::HasTopOverflow() const {
EFlexDirection flex_direction = Style()->FlexDirection();
if (IsHorizontalWritingMode())
return flex_direction == kFlowColumnReverse;
return flex_direction ==
(Style()->IsLeftToRightDirection() ? kFlowRowReverse : kFlowRow);
}
bool LayoutFlexibleBox::HasLeftOverflow() const {
EFlexDirection flex_direction = Style()->FlexDirection();
if (IsHorizontalWritingMode())
return flex_direction ==
(Style()->IsLeftToRightDirection() ? kFlowRowReverse : kFlowRow);
return flex_direction == kFlowColumnReverse;
}
void LayoutFlexibleBox::RemoveChild(LayoutObject* child) {
LayoutBlock::RemoveChild(child);
intrinsic_size_along_main_axis_.erase(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* old_style) {
LayoutBlock::StyleDidChange(diff, old_style);
if (old_style && old_style->AlignItemsPosition() == kItemPositionStretch &&
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 previous_alignment =
child->StyleRef()
.ResolvedAlignSelf(SelfAlignmentNormalBehavior(), old_style)
.GetPosition();
if (previous_alignment == kItemPositionStretch &&
previous_alignment !=
child->StyleRef()
.ResolvedAlignSelf(SelfAlignmentNormalBehavior(), Style())
.GetPosition())
child->SetChildNeedsLayout(kMarkOnlyThis);
}
}
}
void LayoutFlexibleBox::UpdateBlockLayout(bool relayout_children) {
DCHECK(NeedsLayout());
if (!relayout_children && SimplifiedLayout())
return;
relaid_out_children_.clear();
WTF::AutoReset<bool> reset1(&in_layout_, true);
DCHECK_EQ(has_definite_height_, SizeDefiniteness::kUnknown);
if (UpdateLogicalWidthAndColumnWidth())
relayout_children = true;
SubtreeLayoutScope layout_scope(*this);
LayoutUnit previous_height = LogicalHeight();
SetLogicalHeight(BorderAndPaddingLogicalHeight() + ScrollbarLogicalHeight());
PaintLayerScrollableArea::DelayScrollOffsetClampScope delay_clamp_scope;
{
TextAutosizer::LayoutScope text_autosizer_layout_scope(this, &layout_scope);
LayoutState state(*this);
number_of_in_flow_children_on_first_line_ = -1;
PrepareOrderIteratorAndMargins();
LayoutFlexItems(relayout_children, layout_scope);
if (PaintLayerScrollableArea::PreventRelayoutScope::RelayoutNeeded()) {
PaintLayerScrollableArea::FreezeScrollbarsScope freeze_scrollbars_scope;
PrepareOrderIteratorAndMargins();
LayoutFlexItems(true, layout_scope);
PaintLayerScrollableArea::PreventRelayoutScope::ResetRelayoutNeeded();
}
if (LogicalHeight() != previous_height)
relayout_children = true;
LayoutPositionedObjects(relayout_children || IsDocumentElement());
// FIXME: css3/flexbox/repaint-rtl-column.html seems to issue paint
// invalidations for more overflow than it needs to.
ComputeOverflow(ClientLogicalBottomAfterRepositioning());
}
// We have to reset this, because changes to our ancestors' style can affect
// this value. Also, this needs to be before we call updateAfterLayout, as
// that function may re-enter this one.
has_definite_height_ = SizeDefiniteness::kUnknown;
// Update our scroll information if we're overflow:auto/scroll/hidden now
// that we know if we overflow or not.
UpdateAfterLayout();
ClearNeedsLayout();
}
void LayoutFlexibleBox::PaintChildren(const PaintInfo& paint_info,
const LayoutPoint& paint_offset) const {
BlockPainter::PaintChildrenOfFlexibleBox(*this, paint_info, paint_offset);
}
void LayoutFlexibleBox::RepositionLogicalHeightDependentFlexItems(
Vector<LineContext>& line_contexts) {
LayoutUnit cross_axis_start_edge = line_contexts.IsEmpty()
? LayoutUnit()
: line_contexts[0].cross_axis_offset;
AlignFlexLines(line_contexts);
AlignChildren(line_contexts);
if (Style()->FlexWrap() == kFlexWrapReverse)
FlipForWrapReverse(line_contexts, cross_axis_start_edge);
// direction:rtl + flex-direction:column means the cross-axis direction is
// flipped.
FlipForRightToLeftColumn(line_contexts);
}
DISABLE_CFI_PERF
LayoutUnit LayoutFlexibleBox::ClientLogicalBottomAfterRepositioning() {
LayoutUnit max_child_logical_bottom;
for (LayoutBox* child = FirstChildBox(); child;
child = child->NextSiblingBox()) {
if (child->IsOutOfFlowPositioned())
continue;
LayoutUnit child_logical_bottom = LogicalTopForChild(*child) +
LogicalHeightForChild(*child) +
MarginAfterForChild(*child);
max_child_logical_bottom =
std::max(max_child_logical_bottom, child_logical_bottom);
}
return std::max(ClientLogicalBottom(),
max_child_logical_bottom + 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 blink::IsHorizontalWritingMode(Style()->GetWritingMode()) ||
IsFlippedLinesWritingMode(Style()->GetWritingMode());
}
return Style()->IsLeftToRightDirection() ^
(Style()->FlexDirection() == kFlowRowReverse);
}
bool LayoutFlexibleBox::IsMultiline() const {
return Style()->FlexWrap() != kFlexNoWrap;
}
Length LayoutFlexibleBox::FlexBasisForChild(const LayoutBox& child) const {
Length flex_length = child.Style()->FlexBasis();
if (flex_length.IsAuto())
flex_length =
IsHorizontalFlow() ? child.Style()->Width() : child.Style()->Height();
return flex_length;
}
LayoutUnit LayoutFlexibleBox::CrossAxisExtentForChild(
const LayoutBox& child) const {
return IsHorizontalFlow() ? child.Size().Height() : child.Size().Width();
}
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 child_intrinsic_content_logical_height;
if (!child.StyleRef().ContainsSize()) {
child_intrinsic_content_logical_height =
child.IntrinsicContentLogicalHeight();
}
LayoutUnit child_intrinsic_logical_height =
child_intrinsic_content_logical_height +
child.ScrollbarLogicalHeight() + child.BorderAndPaddingLogicalHeight();
return child.ConstrainLogicalHeightByMinMax(
child_intrinsic_logical_height, child_intrinsic_content_logical_height);
}
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 content_logical_height) {
if (IsColumnFlow()) {
LogicalExtentComputedValues computed_values;
LayoutUnit border_padding_and_scrollbar =
BorderAndPaddingLogicalHeight() + ScrollbarLogicalHeight();
LayoutUnit border_box_logical_height =
content_logical_height + border_padding_and_scrollbar;
ComputeLogicalHeight(border_box_logical_height, LogicalTop(),
computed_values);
if (computed_values.extent_ == LayoutUnit::Max())
return computed_values.extent_;
return std::max(LayoutUnit(),
computed_values.extent_ - border_padding_and_scrollbar);
}
return ContentLogicalWidth();
}
LayoutUnit LayoutFlexibleBox::ComputeMainAxisExtentForChild(
const LayoutBox& child,
SizeType size_type,
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(
size_type, 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 border_and_padding = child.BorderAndPaddingLogicalWidth();
if (child.StyleRef().LogicalWidth().IsAuto() && !HasAspectRatio(child)) {
if (size.GetType() == kMinContent)
return child.MinPreferredLogicalWidth() - border_and_padding;
if (size.GetType() == kMaxContent)
return child.MaxPreferredLogicalWidth() - border_and_padding;
}
return child.ComputeLogicalWidthUsing(size_type, size, ContentLogicalWidth(),
this) -
border_and_padding;
}
LayoutFlexibleBox::TransformedWritingMode
LayoutFlexibleBox::GetTransformedWritingMode() const {
WritingMode mode = Style()->GetWritingMode();
if (!IsColumnFlow()) {
static_assert(
static_cast<TransformedWritingMode>(WritingMode::kHorizontalTb) ==
TransformedWritingMode::kTopToBottomWritingMode &&
static_cast<TransformedWritingMode>(WritingMode::kVerticalLr) ==
TransformedWritingMode::kLeftToRightWritingMode &&
static_cast<TransformedWritingMode>(WritingMode::kVerticalRl) ==
TransformedWritingMode::kRightToLeftWritingMode,
"WritingMode and TransformedWritingMode must match values.");
return static_cast<TransformedWritingMode>(mode);
}
switch (mode) {
case WritingMode::kHorizontalTb:
return Style()->IsLeftToRightDirection()
? TransformedWritingMode::kLeftToRightWritingMode
: TransformedWritingMode::kRightToLeftWritingMode;
case WritingMode::kVerticalLr:
case WritingMode::kVerticalRl:
return Style()->IsLeftToRightDirection()
? TransformedWritingMode::kTopToBottomWritingMode
: TransformedWritingMode::kBottomToTopWritingMode;
}
NOTREACHED();
return TransformedWritingMode::kTopToBottomWritingMode;
}
StyleContentAlignmentData LayoutFlexibleBox::ResolvedJustifyContent() const {
ContentPosition position = StyleRef().ResolvedJustifyContentPosition(
ContentAlignmentNormalBehavior());
ContentDistributionType distribution =
StyleRef().ResolvedJustifyContentDistribution(
ContentAlignmentNormalBehavior());
OverflowAlignment overflow = StyleRef().JustifyContentOverflowAlignment();
// For flex, justify-content: stretch behaves as flex-start:
// https://drafts.csswg.org/css-align/#distribution-flex
if (distribution == kContentDistributionStretch) {
position = kContentPositionFlexStart;
distribution = kContentDistributionDefault;
}
return StyleContentAlignmentData(position, distribution, overflow);
}
StyleContentAlignmentData LayoutFlexibleBox::ResolvedAlignContent() const {
ContentPosition position =
StyleRef().ResolvedAlignContentPosition(ContentAlignmentNormalBehavior());
ContentDistributionType distribution =
StyleRef().ResolvedAlignContentDistribution(
ContentAlignmentNormalBehavior());
OverflowAlignment overflow = StyleRef().AlignContentOverflowAlignment();
return StyleContentAlignmentData(position, distribution, overflow);
}
LayoutUnit LayoutFlexibleBox::FlowAwareBorderStart() const {
if (IsHorizontalFlow())
return IsLeftToRightFlow() ? BorderLeft() : BorderRight();
return IsLeftToRightFlow() ? BorderTop() : BorderBottom();
}
LayoutUnit LayoutFlexibleBox::FlowAwareBorderEnd() const {
if (IsHorizontalFlow())
return IsLeftToRightFlow() ? BorderRight() : BorderLeft();
return IsLeftToRightFlow() ? BorderBottom() : BorderTop();
}
LayoutUnit LayoutFlexibleBox::FlowAwareBorderBefore() const {
switch (GetTransformedWritingMode()) {
case TransformedWritingMode::kTopToBottomWritingMode:
return BorderTop();
case TransformedWritingMode::kBottomToTopWritingMode:
return BorderBottom();
case TransformedWritingMode::kLeftToRightWritingMode:
return BorderLeft();
case TransformedWritingMode::kRightToLeftWritingMode:
return BorderRight();
}
NOTREACHED();
return BorderTop();
}
DISABLE_CFI_PERF
LayoutUnit LayoutFlexibleBox::FlowAwareBorderAfter() const {
switch (GetTransformedWritingMode()) {
case TransformedWritingMode::kTopToBottomWritingMode:
return BorderBottom();
case TransformedWritingMode::kBottomToTopWritingMode:
return BorderTop();
case TransformedWritingMode::kLeftToRightWritingMode:
return BorderRight();
case TransformedWritingMode::kRightToLeftWritingMode:
return BorderLeft();
}
NOTREACHED();
return 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::kTopToBottomWritingMode:
return PaddingTop();
case TransformedWritingMode::kBottomToTopWritingMode:
return PaddingBottom();
case TransformedWritingMode::kLeftToRightWritingMode:
return PaddingLeft();
case TransformedWritingMode::kRightToLeftWritingMode:
return PaddingRight();
}
NOTREACHED();
return PaddingTop();
}
DISABLE_CFI_PERF
LayoutUnit LayoutFlexibleBox::FlowAwarePaddingAfter() const {
switch (GetTransformedWritingMode()) {
case TransformedWritingMode::kTopToBottomWritingMode:
return PaddingBottom();
case TransformedWritingMode::kBottomToTopWritingMode:
return PaddingTop();
case TransformedWritingMode::kLeftToRightWritingMode:
return PaddingRight();
case TransformedWritingMode::kRightToLeftWritingMode:
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::kTopToBottomWritingMode:
return child.MarginTop();
case TransformedWritingMode::kBottomToTopWritingMode:
return child.MarginBottom();
case TransformedWritingMode::kLeftToRightWritingMode:
return child.MarginLeft();
case TransformedWritingMode::kRightToLeftWritingMode:
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 cross_size;
if (IsHorizontalFlow())
cross_size = child.StyleRef().Height();
else
cross_size = child.StyleRef().Width();
return CrossAxisLengthIsDefinite(child, cross_size);
}
LayoutUnit LayoutFlexibleBox::ComputeMainSizeFromAspectRatioUsing(
const LayoutBox& child,
Length cross_size_length) const {
DCHECK(HasAspectRatio(child));
DCHECK_NE(child.IntrinsicSize().Height(), 0);
LayoutUnit cross_size;
if (cross_size_length.IsFixed()) {
cross_size = LayoutUnit(cross_size_length.Value());
} else {
DCHECK(cross_size_length.IsPercentOrCalc());
cross_size = HasOrthogonalFlow(child)
? AdjustBorderBoxLogicalWidthForBoxSizing(
ValueForLength(cross_size_length, ContentWidth()))
: child.ComputePercentageLogicalHeight(cross_size_length);
}
const LayoutSize& child_intrinsic_size = child.IntrinsicSize();
double ratio = child_intrinsic_size.Width().ToFloat() /
child_intrinsic_size.Height().ToFloat();
if (IsHorizontalFlow())
return LayoutUnit(cross_size * ratio);
return LayoutUnit(cross_size / 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& flex_basis) const {
if (flex_basis.IsAuto())
return false;
if (flex_basis.IsPercentOrCalc()) {
if (!IsColumnFlow() || has_definite_height_ == SizeDefiniteness::kDefinite)
return true;
if (has_definite_height_ == SizeDefiniteness::kIndefinite)
return false;
bool definite = child.ComputePercentageLogicalHeight(flex_basis) != -1;
if (in_layout_) {
// We can reach this code even while we're not laying ourselves out, such
// as from mainSizeForPercentageResolution.
has_definite_height_ = definite ? SizeDefiniteness::kDefinite
: SizeDefiniteness::kIndefinite;
}
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) ||
has_definite_height_ == SizeDefiniteness::kDefinite)
return true;
if (has_definite_height_ == SizeDefiniteness::kIndefinite)
return false;
bool definite = child.ComputePercentageLogicalHeight(length) != -1;
has_definite_height_ =
definite ? SizeDefiniteness::kDefinite : SizeDefiniteness::kIndefinite;
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 main_size;
if (HasOrthogonalFlow(child)) {
main_size = 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.
main_size = child.MaxPreferredLogicalWidth() +
child.ScrollbarLogicalWidth() - child.ScrollbarLogicalWidth();
}
intrinsic_size_along_main_axis_.Set(&child, main_size);
relaid_out_children_.insert(&child);
}
void LayoutFlexibleBox::ClearCachedMainSizeForChild(const LayoutBox& child) {
intrinsic_size_along_main_axis_.erase(&child);
}
DISABLE_CFI_PERF
LayoutUnit LayoutFlexibleBox::ComputeInnerFlexBaseSizeForChild(
LayoutBox& child,
LayoutUnit main_axis_border_and_padding,
ChildLayoutType child_layout_type) {
child.ClearOverrideSize();
if (child.IsImage() || child.IsVideo() || child.IsCanvas())
UseCounter::Count(GetDocument(), UseCounter::kAspectRatioFlexItem);
Length flex_basis = FlexBasisForChild(child);
if (MainAxisLengthIsDefinite(child, flex_basis))
return std::max(LayoutUnit(), ComputeMainAxisExtentForChild(
child, kMainOrPreferredSize, flex_basis));
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 main_axis_extent;
if (HasOrthogonalFlow(child)) {
if (child_layout_type == kNeverLayout)
return LayoutUnit();
UpdateBlockChildDirtyBitsBeforeLayout(child_layout_type == kForceLayout,
child);
if (child.NeedsLayout() || child_layout_type == kForceLayout ||
!intrinsic_size_along_main_axis_.Contains(&child)) {
child.ForceChildLayout();
CacheChildMainSize(child);
}
main_axis_extent = intrinsic_size_along_main_axis_.at(&child);
} else {
// We don't need to add scrollbarLogicalWidth here because the preferred
// width includes the scrollbar, even for overflow: auto.
main_axis_extent = child.MaxPreferredLogicalWidth();
}
DCHECK_GE(main_axis_extent - main_axis_border_and_padding, LayoutUnit())
<< main_axis_extent << " - " << main_axis_border_and_padding;
return main_axis_extent - main_axis_border_and_padding;
}
void LayoutFlexibleBox::LayoutFlexItems(bool relayout_children,
SubtreeLayoutScope& layout_scope) {
Vector<LineContext> line_contexts;
LayoutUnit sum_flex_base_size;
double total_flex_grow;
double total_flex_shrink;
double total_weighted_flex_shrink;
LayoutUnit sum_hypothetical_main_size;
PaintLayerScrollableArea::PreventRelayoutScope prevent_relayout_scope(
layout_scope);
// 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 layout_type =
relayout_children ? kForceLayout : kLayoutIfNeeded;
Vector<FlexItem> all_items;
order_iterator_.First();
for (LayoutBox* child = order_iterator_.CurrentChild(); child;
child = order_iterator_.Next()) {
if (child->IsOutOfFlowPositioned()) {
// Out-of-flow children are not flex items, so we skip them here.
PrepareChildForPositionedLayout(*child);
continue;
}
all_items.push_back(ConstructFlexItem(*child, layout_type));
}
const LayoutUnit line_break_length = MainAxisContentExtent(LayoutUnit::Max());
FlexLayoutAlgorithm flex_algorithm(Style(), line_break_length, all_items);
LayoutUnit cross_axis_offset =
FlowAwareBorderBefore() + FlowAwarePaddingBefore();
Vector<FlexItem> line_items;
size_t next_index = 0;
while (flex_algorithm.ComputeNextFlexLine(
next_index, line_items, sum_flex_base_size, total_flex_grow,
total_flex_shrink, total_weighted_flex_shrink,
sum_hypothetical_main_size)) {
DCHECK_GE(line_items.size(), 0ULL);
LayoutUnit container_main_inner_size =
MainAxisContentExtent(sum_hypothetical_main_size);
// 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 remaining_free_space =
container_main_inner_size - sum_flex_base_size;
FlexSign flex_sign =
(sum_hypothetical_main_size < container_main_inner_size)
? kPositiveFlexibility
: kNegativeFlexibility;
FreezeInflexibleItems(flex_sign, line_items, remaining_free_space,
total_flex_grow, total_flex_shrink,
total_weighted_flex_shrink);
// The initial free space gets calculated after freezing inflexible items.
// https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths step 3
const LayoutUnit initial_free_space = remaining_free_space;
while (!ResolveFlexibleLengths(
flex_sign, line_items, initial_free_space, remaining_free_space,
total_flex_grow, total_flex_shrink, total_weighted_flex_shrink)) {
DCHECK_GE(total_flex_grow, 0);
DCHECK_GE(total_weighted_flex_shrink, 0);
}
// Recalculate the remaining free space. The adjustment for flex factors
// between 0..1 means we can't just use remainingFreeSpace here.
remaining_free_space = container_main_inner_size;
for (size_t i = 0; i < line_items.size(); ++i) {
FlexItem& flex_item = line_items[i];
DCHECK(!flex_item.box->IsOutOfFlowPositioned());
remaining_free_space -= flex_item.FlexedMarginBoxSize();
}
// This will std::move lineItems into a newly-created LineContext.
LayoutAndPlaceChildren(cross_axis_offset, line_items, remaining_free_space,
relayout_children, layout_scope, line_contexts);
}
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 min_height = MinimumLogicalHeightForEmptyLine();
if (Size().Height() < min_height)
SetLogicalHeight(min_height);
}
UpdateLogicalHeight();
RepositionLogicalHeightDependentFlexItems(line_contexts);
}
LayoutUnit LayoutFlexibleBox::AutoMarginOffsetInMainAxis(
const Vector<FlexItem>& children,
LayoutUnit& available_free_space) {
if (available_free_space <= LayoutUnit())
return LayoutUnit();
int number_of_auto_margins = 0;
bool is_horizontal = IsHorizontalFlow();
for (size_t i = 0; i < children.size(); ++i) {
LayoutBox* child = children[i].box;
DCHECK(!child->IsOutOfFlowPositioned());
if (is_horizontal) {
if (child->Style()->MarginLeft().IsAuto())
++number_of_auto_margins;
if (child->Style()->MarginRight().IsAuto())
++number_of_auto_margins;
} else {
if (child->Style()->MarginTop().IsAuto())
++number_of_auto_margins;
if (child->Style()->MarginBottom().IsAuto())
++number_of_auto_margins;
}
}
if (!number_of_auto_margins)
return LayoutUnit();
LayoutUnit size_of_auto_margin =
available_free_space / number_of_auto_margins;
available_free_space = LayoutUnit();
return size_of_auto_margin;
}
void LayoutFlexibleBox::UpdateAutoMarginsInMainAxis(
LayoutBox& child,
LayoutUnit auto_margin_offset) {
DCHECK_GE(auto_margin_offset, LayoutUnit());
if (IsHorizontalFlow()) {
if (child.Style()->MarginLeft().IsAuto())
child.SetMarginLeft(auto_margin_offset);
if (child.Style()->MarginRight().IsAuto())
child.SetMarginRight(auto_margin_offset);
} else {
if (child.Style()->MarginTop().IsAuto())
child.SetMarginTop(auto_margin_offset);
if (child.Style()->MarginBottom().IsAuto())
child.SetMarginBottom(auto_margin_offset);
}
}
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 line_cross_axis_extent,
const LayoutBox& child) {
DCHECK(!child.IsOutOfFlowPositioned());
LayoutUnit child_cross_extent =
CrossAxisMarginExtentForChild(child) + CrossAxisExtentForChild(child);
return line_cross_axis_extent - child_cross_extent;
}
bool LayoutFlexibleBox::UpdateAutoMarginsInCrossAxis(
LayoutBox& child,
LayoutUnit available_alignment_space) {
DCHECK(!child.IsOutOfFlowPositioned());
DCHECK_GE(available_alignment_space, LayoutUnit());
bool is_horizontal = IsHorizontalFlow();
Length top_or_left =
is_horizontal ? child.Style()->MarginTop() : child.Style()->MarginLeft();
Length bottom_or_right = is_horizontal ? child.Style()->MarginBottom()
: child.Style()->MarginRight();
if (top_or_left.IsAuto() && bottom_or_right.IsAuto()) {
AdjustAlignmentForChild(child, available_alignment_space / 2);
if (is_horizontal) {
child.SetMarginTop(available_alignment_space / 2);
child.SetMarginBottom(available_alignment_space / 2);
} else {
child.SetMarginLeft(available_alignment_space / 2);
child.SetMarginRight(available_alignment_space / 2);
}
return true;
}
bool should_adjust_top_or_left = 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.
should_adjust_top_or_left = 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.
should_adjust_top_or_left = false;
}
if (top_or_left.IsAuto()) {
if (should_adjust_top_or_left)
AdjustAlignmentForChild(child, available_alignment_space);
if (is_horizontal)
child.SetMarginTop(available_alignment_space);
else
child.SetMarginLeft(available_alignment_space);
return true;
}
if (bottom_or_right.IsAuto()) {
if (!should_adjust_top_or_left)
AdjustAlignmentForChild(child, available_alignment_space);
if (is_horizontal)
child.SetMarginBottom(available_alignment_space);
else
child.SetMarginRight(available_alignment_space);
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 available_size = ContentLogicalWidth();
return MinimumValueForLength(margin, available_size);
}
void LayoutFlexibleBox::PrepareOrderIteratorAndMargins() {
OrderIteratorPopulator populator(order_iterator_);
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 child_size) {
Length max = IsHorizontalFlow() ? child.Style()->MaxWidth()
: child.Style()->MaxHeight();
LayoutUnit max_extent(-1);
if (max.IsSpecifiedOrIntrinsic()) {
max_extent = ComputeMainAxisExtentForChild(child, kMaxSize, max);
DCHECK_GE(max_extent, LayoutUnit(-1));
if (max_extent != -1 && child_size > max_extent)
child_size = max_extent;
}
Length min = IsHorizontalFlow() ? child.Style()->MinWidth()
: child.Style()->MinHeight();
LayoutUnit min_extent;
if (min.IsSpecifiedOrIntrinsic()) {
min_extent = ComputeMainAxisExtentForChild(child, kMinSize, min);
// computeMainAxisExtentForChild can return -1 when the child has a
// percentage min size, but we have an indefinite size in that axis.
min_extent = std::max(LayoutUnit(), min_extent);
} else if (min.IsAuto() && !child.StyleRef().ContainsSize() &&
MainAxisOverflowForChild(child) == EOverflow::kVisible &&
!(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 content_size =
ComputeMainAxisExtentForChild(child, kMinSize, Length(kMinContent));
DCHECK_GE(content_size, LayoutUnit());
if (HasAspectRatio(child) && child.IntrinsicSize().Height() > 0)
content_size =
AdjustChildSizeForAspectRatioCrossAxisMinAndMax(child, content_size);
if (max_extent != -1 && content_size > max_extent)
content_size = max_extent;
Length main_size = IsHorizontalFlow() ? child.StyleRef().Width()
: child.StyleRef().Height();
if (MainAxisLengthIsDefinite(child, main_size)) {
LayoutUnit resolved_main_size =
ComputeMainAxisExtentForChild(child, kMainOrPreferredSize, main_size);
DCHECK_GE(resolved_main_size, LayoutUnit());
LayoutUnit specified_size = max_extent != -1
? std::min(resolved_main_size, max_extent)
: resolved_main_size;
min_extent = std::min(specified_size, content_size);
} else if (UseChildAspectRatio(child)) {
Length cross_size_length = IsHorizontalFlow() ? child.StyleRef().Height()
: child.StyleRef().Width();
LayoutUnit transferred_size =
ComputeMainSizeFromAspectRatioUsing(child, cross_size_length);
transferred_size = AdjustChildSizeForAspectRatioCrossAxisMinAndMax(
child, transferred_size);
min_extent = std::min(transferred_size, content_size);
} else {
min_extent = content_size;
}
}
DCHECK_GE(min_extent, LayoutUnit());
return std::max(child_size, min_extent);
}
LayoutUnit LayoutFlexibleBox::CrossSizeForPercentageResolution(
const LayoutBox& child) {
if (AlignmentForChild(child) != kItemPositionStretch)
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& flex_basis = FlexBasisForChild(child);
if (!MainAxisLengthIsDefinite(child, flex_basis))
return LayoutUnit(-1);
if (!flex_basis.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, kPercent)))
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 child_size) {
Length cross_min = IsHorizontalFlow() ? child.Style()->MinHeight()
: child.Style()->MinWidth();
Length cross_max = IsHorizontalFlow() ? child.Style()->MaxHeight()
: child.Style()->MaxWidth();
if (CrossAxisLengthIsDefinite(child, cross_max)) {
LayoutUnit max_value =
ComputeMainSizeFromAspectRatioUsing(child, cross_max);
child_size = std::min(max_value, child_size);
}
if (CrossAxisLengthIsDefinite(child, cross_min)) {
LayoutUnit min_value =
ComputeMainSizeFromAspectRatioUsing(child, cross_min);
child_size = std::max(min_value, child_size);
}
return child_size;
}
DISABLE_CFI_PERF
FlexItem LayoutFlexibleBox::ConstructFlexItem(LayoutBox& child,
ChildLayoutType layout_type) {
// 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 (layout_type != kNeverLayout && ChildHasIntrinsicMainAxisSize(child) &&
child.NeedsLayout()) {
child.ClearOverrideSize();
child.ForceChildLayout();
CacheChildMainSize(child);
layout_type = kLayoutIfNeeded;
}
LayoutUnit border_and_padding = IsHorizontalFlow()
? child.BorderAndPaddingWidth()
: child.BorderAndPaddingHeight();
LayoutUnit child_inner_flex_base_size =
ComputeInnerFlexBaseSizeForChild(child, border_and_padding, layout_type);
LayoutUnit child_min_max_applied_main_axis_extent =
AdjustChildSizeForMinAndMax(child, child_inner_flex_base_size);
LayoutUnit margin =
IsHorizontalFlow() ? child.MarginWidth() : child.MarginHeight();
return FlexItem(&child, child_inner_flex_base_size,
child_min_max_applied_main_axis_extent, border_and_padding,
margin);
}
void LayoutFlexibleBox::FreezeViolations(Vector<FlexItem*>& violations,
LayoutUnit& available_free_space,
double& total_flex_grow,
double& total_flex_shrink,
double& total_weighted_flex_shrink) {
for (size_t i = 0; i < violations.size(); ++i) {
DCHECK(!violations[i]->frozen) << i;
LayoutBox* child = violations[i]->box;
LayoutUnit child_size = violations[i]->flexed_content_size;
available_free_space -= child_size - violations[i]->flex_base_content_size;
total_flex_grow -= child->Style()->FlexGrow();
total_flex_shrink -= child->Style()->FlexShrink();
total_weighted_flex_shrink -=
child->Style()->FlexShrink() * violations[i]->flex_base_content_size;
// 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
total_weighted_flex_shrink = std::max(total_weighted_flex_shrink, 0.0);
violations[i]->frozen = true;
}
}
void LayoutFlexibleBox::FreezeInflexibleItems(
FlexSign flex_sign,
Vector<FlexItem>& children,
LayoutUnit& remaining_free_space,
double& total_flex_grow,
double& total_flex_shrink,
double& total_weighted_flex_shrink) {
// 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*> new_inflexible_items;
for (size_t i = 0; i < children.size(); ++i) {
FlexItem& flex_item = children[i];
LayoutBox* child = flex_item.box;
DCHECK(!flex_item.box->IsOutOfFlowPositioned());
DCHECK(!flex_item.frozen) << i;
float flex_factor = (flex_sign == kPositiveFlexibility)
? child->Style()->FlexGrow()
: child->Style()->FlexShrink();
if (flex_factor == 0 ||
(flex_sign == kPositiveFlexibility &&
flex_item.flex_base_content_size >
flex_item.hypothetical_main_content_size) ||
(flex_sign == kNegativeFlexibility &&
flex_item.flex_base_content_size <
flex_item.hypothetical_main_content_size)) {
flex_item.flexed_content_size = flex_item.hypothetical_main_content_size;
new_inflexible_items.push_back(&flex_item);
}
}
FreezeViolations(new_inflexible_items, remaining_free_space, total_flex_grow,
total_flex_shrink, total_weighted_flex_shrink);
}
// Returns true if we successfully ran the algorithm and sized the flex items.
bool LayoutFlexibleBox::ResolveFlexibleLengths(
FlexSign flex_sign,
Vector<FlexItem>& children,
LayoutUnit initial_free_space,
LayoutUnit& remaining_free_space,
double& total_flex_grow,
double& total_flex_shrink,
double& total_weighted_flex_shrink) {
LayoutUnit total_violation;
LayoutUnit used_free_space;
Vector<FlexItem*> min_violations;
Vector<FlexItem*> max_violations;
double sum_flex_factors =
(flex_sign == kPositiveFlexibility) ? total_flex_grow : total_flex_shrink;
if (sum_flex_factors > 0 && sum_flex_factors < 1) {
LayoutUnit fractional(initial_free_space * sum_flex_factors);
if (fractional.Abs() < remaining_free_space.Abs())
remaining_free_space = fractional;
}
for (size_t i = 0; i < children.size(); ++i) {
FlexItem& flex_item = children[i];
LayoutBox* child = flex_item.box;
// This check also covers out-of-flow children.
if (flex_item.frozen)
continue;
LayoutUnit child_size = flex_item.flex_base_content_size;
double extra_space = 0;
if (remaining_free_space > 0 && total_flex_grow > 0 &&
flex_sign == kPositiveFlexibility && std::isfinite(total_flex_grow)) {
extra_space =
remaining_free_space * child->Style()->FlexGrow() / total_flex_grow;
} else if (remaining_free_space < 0 && total_weighted_flex_shrink > 0 &&
flex_sign == kNegativeFlexibility &&
std::isfinite(total_weighted_flex_shrink) &&
child->Style()->FlexShrink()) {
extra_space = remaining_free_space * child->Style()->FlexShrink() *
flex_item.flex_base_content_size /
total_weighted_flex_shrink;
}
if (std::isfinite(extra_space))
child_size += LayoutUnit::FromFloatRound(extra_space);
LayoutUnit adjusted_child_size =
AdjustChildSizeForMinAndMax(*child, child_size);
DCHECK_GE(adjusted_child_size, 0);
flex_item.flexed_content_size = adjusted_child_size;
used_free_space += adjusted_child_size - flex_item.flex_base_content_size;
LayoutUnit violation = adjusted_child_size - child_size;
if (violation > 0)
min_violations.push_back(&flex_item);
else if (violation < 0)
max_violations.push_back(&flex_item);
total_violation += violation;
}
if (total_violation)
FreezeViolations(total_violation < 0 ? max_violations : min_violations,
remaining_free_space, total_flex_grow, total_flex_shrink,
total_weighted_flex_shrink);
else
remaining_free_space -= used_free_space;
return !total_violation;
}
static LayoutUnit InitialContentPositionOffset(
LayoutUnit available_free_space,
const StyleContentAlignmentData& data,
unsigned number_of_items) {
if (data.GetPosition() == kContentPositionFlexEnd)
return available_free_space;
if (data.GetPosition() == kContentPositionCenter)
return available_free_space / 2;
if (data.Distribution() == kContentDistributionSpaceAround) {
if (available_free_space > 0 && number_of_items)
return available_free_space / (2 * number_of_items);
return available_free_space / 2;
}
if (data.Distribution() == kContentDistributionSpaceEvenly) {
if (available_free_space > 0 && number_of_items)
return available_free_space / (number_of_items + 1);
// Fallback to 'center'
return available_free_space / 2;
}
return LayoutUnit();
}
static LayoutUnit ContentDistributionSpaceBetweenChildren(
LayoutUnit available_free_space,
const StyleContentAlignmentData& data,
unsigned number_of_items) {
if (available_free_space > 0 && number_of_items > 1) {
if (data.Distribution() == kContentDistributionSpaceBetween)
return available_free_space / (number_of_items - 1);
if (data.Distribution() == kContentDistributionSpaceAround ||
data.Distribution() == kContentDistributionStretch)
return available_free_space / number_of_items;
if (data.Distribution() == kContentDistributionSpaceEvenly)
return available_free_space / (number_of_items + 1);
}
return LayoutUnit();
}
static LayoutUnit AlignmentOffset(LayoutUnit available_free_space,
ItemPosition position,
LayoutUnit ascent,
LayoutUnit max_ascent,
bool is_wrap_reverse) {
switch (position) {
case kItemPositionAuto:
case kItemPositionNormal:
NOTREACHED();
break;
case kItemPositionStretch:
// 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 (is_wrap_reverse)
return available_free_space;
break;
case kItemPositionFlexStart:
break;
case kItemPositionFlexEnd:
return available_free_space;
case kItemPositionCenter:
return available_free_space / 2;
case kItemPositionBaseline:
// 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 max_ascent - ascent;
case kItemPositionLastBaseline:
case kItemPositionSelfStart:
case kItemPositionSelfEnd:
case kItemPositionStart:
case kItemPositionEnd:
case kItemPositionLeft:
case kItemPositionRight:
// 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 child_preferred_size) {
if (HasOrthogonalFlow(child))
child.SetOverrideLogicalContentHeight(child_preferred_size);
else
child.SetOverrideLogicalContentWidth(child_preferred_size);
}
LayoutUnit LayoutFlexibleBox::StaticMainAxisPositionForPositionedChild(
const LayoutBox& child) {
const LayoutUnit available_space =
MainAxisContentExtent(ContentLogicalHeight()) -
MainAxisExtentForChild(child);
LayoutUnit offset = InitialContentPositionOffset(available_space,
ResolvedJustifyContent(), 1);
if (StyleRef().FlexDirection() == kFlowRowReverse ||
StyleRef().FlexDirection() == kFlowColumnReverse)
offset = available_space - offset;
return offset;
}
LayoutUnit LayoutFlexibleBox::StaticCrossAxisPositionForPositionedChild(
const LayoutBox& child) {
LayoutUnit available_space =
CrossAxisContentExtent() - CrossAxisExtentForChild(child);
return AlignmentOffset(available_space, AlignmentForChild(child),
LayoutUnit(), LayoutUnit(),
StyleRef().FlexWrap() == kFlexWrapReverse);
}
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 position_changed = false;
PaintLayer* child_layer = child.Layer();
if (child.StyleRef().HasStaticInlinePosition(
StyleRef().IsHorizontalWritingMode())) {
LayoutUnit inline_position = StaticInlinePositionForPositionedChild(child);
if (child_layer->StaticInlinePosition() != inline_position) {
child_layer->SetStaticInlinePosition(inline_position);
position_changed = true;
}
}
if (child.StyleRef().HasStaticBlockPosition(
StyleRef().IsHorizontalWritingMode())) {
LayoutUnit block_position = StaticBlockPositionForPositionedChild(child);
if (child_layer->StaticBlockPosition() != block_position) {
child_layer->SetStaticBlockPosition(block_position);
position_changed = true;
}
}
return position_changed;
}
void LayoutFlexibleBox::PrepareChildForPositionedLayout(LayoutBox& child) {
DCHECK(child.IsOutOfFlowPositioned());
child.ContainingBlock()->InsertPositionedObject(&child);
PaintLayer* child_layer = child.Layer();
LayoutUnit static_inline_position =
FlowAwareBorderStart() + FlowAwarePaddingStart();
if (child_layer->StaticInlinePosition() != static_inline_position) {
child_layer->SetStaticInlinePosition(static_inline_position);
if (child.Style()->HasStaticInlinePosition(
Style()->IsHorizontalWritingMode()))
child.SetChildNeedsLayout(kMarkOnlyThis);
}
LayoutUnit static_block_position =
FlowAwareBorderBefore() + FlowAwarePaddingBefore();
if (child_layer->StaticBlockPosition() != static_block_position) {
child_layer->SetStaticBlockPosition(static_block_position);
if (child.Style()->HasStaticBlockPosition(
Style()->IsHorizontalWritingMode()))
child.SetChildNeedsLayout(kMarkOnlyThis);
}
}
ItemPosition LayoutFlexibleBox::AlignmentForChild(
const LayoutBox& child) const {
ItemPosition align =
child.StyleRef()
.ResolvedAlignSelf(SelfAlignmentNormalBehavior(),
child.IsAnonymous() ? Style() : nullptr)
.GetPosition();
DCHECK_NE(align, kItemPositionAuto);
DCHECK_NE(align, kItemPositionNormal);
if (align == kItemPositionBaseline && HasOrthogonalFlow(child))
align = kItemPositionFlexStart;
if (Style()->FlexWrap() == kFlexWrapReverse) {
if (align == kItemPositionFlexStart)
align = kItemPositionFlexEnd;
else if (align == kItemPositionFlexEnd)
align = kItemPositionFlexStart;
}
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) != kItemPositionStretch)
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 child_flex_basis = FlexBasisForChild(child);
Length child_min_size = IsHorizontalFlow() ? child.Style()->MinWidth()
: child.Style()->MinHeight();
Length child_max_size = IsHorizontalFlow() ? child.Style()->MaxWidth()
: child.Style()->MaxHeight();
if (child_flex_basis.IsIntrinsic() || child_min_size.IsIntrinsicOrAuto() ||
child_max_size.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& cross_axis_offset,
Vector<FlexItem>& children,
LayoutUnit available_free_space,
bool relayout_children,
SubtreeLayoutScope& layout_scope,
Vector<LineContext>& line_contexts) {
const StyleContentAlignmentData justify_content = ResolvedJustifyContent();
LayoutUnit auto_margin_offset =
AutoMarginOffsetInMainAxis(children, available_free_space);
LayoutUnit main_axis_offset =
FlowAwareBorderStart() + FlowAwarePaddingStart();
main_axis_offset += InitialContentPositionOffset(
available_free_space, justify_content, children.size());
if (Style()->FlexDirection() == kFlowRowReverse &&
ShouldPlaceBlockDirectionScrollbarOnLogicalLeft())
main_axis_offset += IsHorizontalFlow() ? VerticalScrollbarWidth()
: HorizontalScrollbarHeight();
LayoutUnit total_main_extent = MainAxisExtent();
if (!ShouldPlaceBlockDirectionScrollbarOnLogicalLeft())
total_main_extent -= IsHorizontalFlow() ? VerticalScrollbarWidth()
: HorizontalScrollbarHeight();
LayoutUnit max_ascent, max_descent; // Used when align-items: baseline.
LayoutUnit max_child_cross_axis_extent;
bool should_flip_main_axis = !IsColumnFlow() && !IsLeftToRightFlow();
bool is_paginated = View()->GetLayoutState()->IsPaginated();
for (size_t i = 0; i < children.size(); ++i) {
const FlexItem& flex_item = children[i];
LayoutBox* child = flex_item.box;
DCHECK(!flex_item.box->IsOutOfFlowPositioned());
child->SetMayNeedPaintInvalidation();
SetOverrideMainAxisContentSizeForChild(*child,
flex_item.flexed_content_size);
// 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 (flex_item.flexed_content_size !=
MainAxisContentExtentForChildIncludingScrollbar(*child)) {
child->SetChildNeedsLayout(kMarkOnlyThis);
} 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 force_child_relayout =
relayout_children && !relaid_out_children_.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.
force_child_relayout = true;
}
UpdateBlockChildDirtyBitsBeforeLayout(force_child_relayout, *child);
if (!child->NeedsLayout())
MarkChildForPaginationRelayoutIfNeeded(*child, layout_scope);
if (child->NeedsLayout())
relaid_out_children_.insert(child);
child->LayoutIfNeeded();
UpdateAutoMarginsInMainAxis(*child, auto_margin_offset);
LayoutUnit child_cross_axis_margin_box_extent;
if (AlignmentForChild(*child) == kItemPositionBaseline &&
!HasAutoMarginsInCrossAxis(*child)) {
LayoutUnit ascent = MarginBoxAscentForChild(*child);
LayoutUnit descent = (CrossAxisMarginExtentForChild(*child) +
CrossAxisExtentForChild(*child)) -
ascent;
max_ascent = std::max(max_ascent, ascent);
max_descent = std::max(max_descent, descent);
// TODO(cbiesinger): Take scrollbar into account
child_cross_axis_margin_box_extent = max_ascent + max_descent;
} else {
child_cross_axis_margin_box_extent =
CrossAxisIntrinsicExtentForChild(*child) +
CrossAxisMarginExtentForChild(*child);
}
if (!IsColumnFlow())
SetLogicalHeight(std::max(
LogicalHeight(),
cross_axis_offset + FlowAwareBorderAfter() + FlowAwarePaddingAfter() +
child_cross_axis_margin_box_extent + CrossAxisScrollbarExtent()));
max_child_cross_axis_extent = std::max(max_child_cross_axis_extent,
child_cross_axis_margin_box_extent);
main_axis_offset += FlowAwareMarginStartForChild(*child);
LayoutUnit child_main_extent = 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 child_location(
should_flip_main_axis
? total_main_extent - main_axis_offset - child_main_extent
: main_axis_offset,
cross_axis_offset + FlowAwareMarginBeforeForChild(*child));
SetFlowAwareLocationForChild(*child, child_location);
main_axis_offset += child_main_extent + FlowAwareMarginEndForChild(*child);
if (i != children.size() - 1) {
// The last item does not get extra space added.
main_axis_offset += ContentDistributionSpaceBetweenChildren(
available_free_space, justify_content, children.size());
}
if (is_paginated)
UpdateFragmentationInfoForChild(*child);
}
if (IsColumnFlow())
SetLogicalHeight(std::max(
LogicalHeight(), main_axis_offset + FlowAwareBorderEnd() +
FlowAwarePaddingEnd() + ScrollbarLogicalHeight()));
if (Style()->FlexDirection() == kFlowColumnReverse) {
// 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, cross_axis_offset, available_free_space);
}
if (number_of_in_flow_children_on_first_line_ == -1)
number_of_in_flow_children_on_first_line_ = children.size();
line_contexts.push_back(LineContext(cross_axis_offset,
max_child_cross_axis_extent, max_ascent,
std::move(children)));
cross_axis_offset += max_child_cross_axis_extent;
}
void LayoutFlexibleBox::LayoutColumnReverse(const Vector<FlexItem>& children,
LayoutUnit cross_axis_offset,
LayoutUnit available_free_space) {
const StyleContentAlignmentData justify_content = ResolvedJustifyContent();
// 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 main_axis_offset =
LogicalHeight() - FlowAwareBorderEnd() - FlowAwarePaddingEnd();
main_axis_offset -= InitialContentPositionOffset(
available_free_space, justify_content, children.size());
main_axis_offset -= IsHorizontalFlow() ? VerticalScrollbarWidth()
: HorizontalScrollbarHeight();
for (size_t i = 0; i < children.size(); ++i) {
LayoutBox* child = children[i].box;
DCHECK(!child->IsOutOfFlowPositioned());
main_axis_offset -=
MainAxisExtentForChild(*child) + FlowAwareMarginEndForChild(*child);
SetFlowAwareLocationForChild(
*child,
LayoutPoint(main_axis_offset,
cross_axis_offset + FlowAwareMarginBeforeForChild(*child)));
main_axis_offset -= FlowAwareMarginStartForChild(*child);
main_axis_offset -= ContentDistributionSpaceBetweenChildren(
available_free_space, justify_content, children.size());
}
}
void LayoutFlexibleBox::AlignFlexLines(Vector<LineContext>& line_contexts) {
const StyleContentAlignmentData align_content = ResolvedAlignContent();
// 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 (line_contexts.size() == 1) {
line_contexts[0].cross_axis_extent = CrossAxisContentExtent();
return;
}
if (align_content.GetPosition() == kContentPositionFlexStart)
return;
LayoutUnit available_cross_axis_space = CrossAxisContentExtent();
for (size_t i = 0; i < line_contexts.size(); ++i)
available_cross_axis_space -= line_contexts[i].cross_axis_extent;
LayoutUnit line_offset;
if (line_contexts.size() > 1) {
line_offset = InitialContentPositionOffset(
available_cross_axis_space, align_content, line_contexts.size());
}
for (unsigned line_number = 0; line_number < line_contexts.size();
++line_number) {
LineContext& line_context = line_contexts[line_number];
line_context.cross_axis_offset += line_offset;
for (size_t child_number = 0; child_number < line_context.flex_items.size();
++child_number) {
FlexItem& flex_item = line_context.flex_items[child_number];
AdjustAlignmentForChild(*flex_item.box, line_offset);
}
if (align_content.Distribution() == kContentDistributionStretch &&
available_cross_axis_space > 0)
line_contexts[line_number].cross_axis_extent +=
available_cross_axis_space /
static_cast<unsigned>(line_contexts.size());
line_offset += ContentDistributionSpaceBetweenChildren(
available_cross_axis_space, align_content, line_contexts.size());
}
}
void LayoutFlexibleBox::AdjustAlignmentForChild(LayoutBox& child,
LayoutUnit delta) {
DCHECK(!child.IsOutOfFlowPositioned());
SetFlowAwareLocationForChild(child, FlowAwareLocationForChild(child) +
LayoutSize(LayoutUnit(), delta));
}
void LayoutFlexibleBox::AlignChildren(
const Vector<LineContext>& line_contexts) {
// Keep track of the space between the baseline edge and the after edge of
// the box for each line.
Vector<LayoutUnit> min_margin_after_baselines;
for (size_t line_number = 0; line_number < line_contexts.size();
++line_number) {
const LineContext& line_context = line_contexts[line_number];
LayoutUnit min_margin_after_baseline = LayoutUnit::Max();
LayoutUnit line_cross_axis_extent = line_context.cross_axis_extent;
LayoutUnit max_ascent = line_context.max_ascent;
for (size_t child_number = 0; child_number < line_context.flex_items.size();
++child_number) {
const FlexItem& flex_item = line_context.flex_items[child_number];
DCHECK(!flex_item.box->IsOutOfFlowPositioned());
if (UpdateAutoMarginsInCrossAxis(
*flex_item.box,
std::max(LayoutUnit(),
AvailableAlignmentSpaceForChild(line_cross_axis_extent,
*flex_item.box))))
continue;
ItemPosition position = AlignmentForChild(*flex_item.box);
if (position == kItemPositionStretch)
ApplyStretchAlignmentToChild(*flex_item.box, line_cross_axis_extent);
LayoutUnit available_space = AvailableAlignmentSpaceForChild(
line_cross_axis_extent, *flex_item.box);
LayoutUnit offset = AlignmentOffset(
available_space, position, MarginBoxAscentForChild(*flex_item.box),
max_ascent, StyleRef().FlexWrap() == kFlexWrapReverse);
AdjustAlignmentForChild(*flex_item.box, offset);
if (position == kItemPositionBaseline &&
StyleRef().FlexWrap() == kFlexWrapReverse) {
min_margin_after_baseline =
std::min(min_margin_after_baseline,
AvailableAlignmentSpaceForChild(line_cross_axis_extent,
*flex_item.box) -
offset);
}
}
min_margin_after_baselines.push_back(min_margin_after_baseline);
}
if (Style()->FlexWrap() != kFlexWrapReverse)
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 line_number = 0; line_number < line_contexts.size();
++line_number) {
const LineContext& line_context = line_contexts[line_number];
LayoutUnit min_margin_after_baseline =
min_margin_after_baselines[line_number];
for (size_t child_number = 0; child_number < line_context.flex_items.size();
++child_number) {
const FlexItem& flex_item = line_context.flex_items[child_number];
if (AlignmentForChild(*flex_item.box) == kItemPositionBaseline &&
!HasAutoMarginsInCrossAxis(*flex_item.box) &&
min_margin_after_baseline)
AdjustAlignmentForChild(*flex_item.box, min_margin_after_baseline);
}
}
}
void LayoutFlexibleBox::ApplyStretchAlignmentToChild(
LayoutBox& child,
LayoutUnit line_cross_axis_extent) {
if (!HasOrthogonalFlow(child) && child.Style()->LogicalHeight().IsAuto()) {
LayoutUnit stretched_logical_height =
std::max(child.BorderAndPaddingLogicalHeight(),
line_cross_axis_extent - CrossAxisMarginExtentForChild(child));
DCHECK(!child.NeedsLayout());
LayoutUnit desired_logical_height = child.ConstrainLogicalHeightByMinMax(
stretched_logical_height, child.IntrinsicContentLogicalHeight());
// FIXME: Can avoid laying out here in some cases. See
// https://webkit.org/b/87905.
bool child_needs_relayout = desired_logical_height != child.LogicalHeight();
if (child.IsLayoutBlock() &&
ToLayoutBlock(child).HasPercentHeightDescendants() &&
relaid_out_children_.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.
child_needs_relayout = true;
}
if (child_needs_relayout || !child.HasOverrideLogicalContentHeight())
child.SetOverrideLogicalContentHeight(
desired_logical_height - child.BorderAndPaddingLogicalHeight());
if (child_needs_relayout) {
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 child_intrinsic_content_logical_height =
child.IntrinsicContentLogicalHeight();
child.ForceChildLayout();
child.SetIntrinsicContentLogicalHeight(
child_intrinsic_content_logical_height);
}
} else if (HasOrthogonalFlow(child) &&
child.Style()->LogicalWidth().IsAuto()) {
LayoutUnit child_width =
(line_cross_axis_extent - CrossAxisMarginExtentForChild(child))
.ClampNegativeToZero();
child_width = child.ConstrainLogicalWidthByMinMax(
child_width, CrossAxisContentExtent(), this);
if (child_width != child.LogicalWidth()) {
child.SetOverrideLogicalContentWidth(
child_width - child.BorderAndPaddingLogicalWidth());
child.ForceChildLayout();
}
}
}
void LayoutFlexibleBox::FlipForRightToLeftColumn(
const Vector<LineContext>& line_contexts) {
if (Style()->IsLeftToRightDirection() || !IsColumnFlow())
return;
LayoutUnit cross_extent = CrossAxisExtent();
for (size_t line_number = 0; line_number < line_contexts.size();
++line_number) {
const LineContext& line_context = line_contexts[line_number];
for (size_t child_number = 0; child_number < line_context.flex_items.size();
++child_number) {
const FlexItem& flex_item = line_context.flex_items[child_number];
DCHECK(!flex_item.box->IsOutOfFlowPositioned());
LayoutPoint location = FlowAwareLocationForChild(*flex_item.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(cross_extent - CrossAxisExtentForChild(*flex_item.box) -
location.Y());
if (!IsHorizontalWritingMode())
location.Move(LayoutSize(0, -HorizontalScrollbarHeight()));
SetFlowAwareLocationForChild(*flex_item.box, location);
}
}
}
void LayoutFlexibleBox::FlipForWrapReverse(
const Vector<LineContext>& line_contexts,
LayoutUnit cross_axis_start_edge) {
LayoutUnit content_extent = CrossAxisContentExtent();
for (size_t line_number = 0; line_number < line_contexts.size();
++line_number) {
const LineContext& line_context = line_contexts[line_number];
for (size_t child_number = 0; child_number < line_context.flex_items.size();
++child_number) {
const FlexItem& flex_item = line_context.flex_items[child_number];
LayoutUnit line_cross_axis_extent =
line_contexts[line_number].cross_axis_extent;
LayoutUnit original_offset =
line_contexts[line_number].cross_axis_offset - cross_axis_start_edge;
LayoutUnit new_offset =
content_extent - original_offset - line_cross_axis_extent;
AdjustAlignmentForChild(*flex_item.box, new_offset - original_offset);
}
}
}
} // namespace blink