blob: aa5f1aae6f597734f3f45ad34a2ece921b1fb9c5 [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 "third_party/blink/renderer/core/layout/layout_flexible_box.h"
#include <limits>
#include "base/auto_reset.h"
#include "third_party/blink/renderer/core/frame/web_feature.h"
#include "third_party/blink/renderer/core/layout/flexible_box_algorithm.h"
#include "third_party/blink/renderer/core/layout/layout_state.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/layout/min_max_size.h"
#include "third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h"
#include "third_party/blink/renderer/core/layout/text_autosizer.h"
#include "third_party/blink/renderer/core/paint/block_painter.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/geometry/length_functions.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
namespace blink {
static bool HasAspectRatio(const LayoutBox& child) {
return child.IsImage() || child.IsCanvas() || child.IsVideo();
}
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());
}
LayoutFlexibleBox::~LayoutFlexibleBox() = default;
void LayoutFlexibleBox::ComputeIntrinsicLogicalWidths(
LayoutUnit& min_logical_width,
LayoutUnit& max_logical_width) const {
LayoutUnit scrollbar_width(ScrollbarLogicalWidth());
if (ShouldApplySizeContainment()) {
max_logical_width = min_logical_width =
ContentLogicalWidthForSizeContainment() + scrollbar_width;
return;
}
if (DisplayLockInducesSizeContainment()) {
min_logical_width = max_logical_width =
scrollbar_width +
GetDisplayLockContext()->GetLockedContentLogicalWidth();
return;
}
// 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;
if (child->NeedsPreferredWidthsRecalculation())
child->SetPreferredLogicalWidthsDirty();
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);
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;
const 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(),
WebFeature::kFlexboxIntrinsicSizeAlgorithmIsDifferent);
}
return max_content_flex_fraction;
}
LayoutUnit LayoutFlexibleBox::SynthesizedBaselineFromBorderBox(
const LayoutBox& box,
LineDirectionMode direction) {
return direction == kHorizontalLine ? box.Size().Height()
: box.Size().Width();
}
LayoutUnit LayoutFlexibleBox::BaselinePosition(FontBaseline,
bool,
LineDirectionMode direction,
LinePositionMode mode) const {
DCHECK_EQ(mode, kPositionOnContainingLine);
LayoutUnit baseline = FirstLineBoxBaseline();
if (baseline == -1) {
return SynthesizedBaselineFromBorderBox(*this, direction) +
MarginLogicalHeight();
}
return BeforeMarginInLineDirection(direction) + baseline;
}
LayoutUnit LayoutFlexibleBox::FirstLineBoxBaseline() const {
if (IsWritingModeRoot() || number_of_in_flow_children_on_first_line_ <= 0 ||
ShouldApplyLayoutContainment())
return LayoutUnit(-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 (FlexLayoutAlgorithm::AlignmentForChild(StyleRef(), child->StyleRef()) ==
ItemPosition::kBaseline &&
!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 LayoutUnit(-1);
if (!IsColumnFlow() && !MainAxisIsInlineAxis(*baseline_child)) {
// TODO(cbiesinger): Should LogicalTop here be LogicalLeft?
return CrossAxisExtentForChild(*baseline_child) +
baseline_child->LogicalTop();
}
if (IsColumnFlow() && MainAxisIsInlineAxis(*baseline_child)) {
return MainAxisExtentForChild(*baseline_child) +
baseline_child->LogicalTop();
}
LayoutUnit 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 SynthesizedBaselineFromBorderBox(*baseline_child, direction) +
baseline_child->LogicalTop();
}
return baseline + baseline_child->LogicalTop();
}
LayoutUnit LayoutFlexibleBox::InlineBlockBaseline(
LineDirectionMode direction) const {
return FirstLineBoxBaseline();
}
bool LayoutFlexibleBox::HasTopOverflow() const {
if (IsHorizontalWritingMode())
return StyleRef().ResolvedIsColumnReverseFlexDirection();
return !StyleRef().IsLeftToRightDirection() ^
StyleRef().ResolvedIsRowReverseFlexDirection();
}
bool LayoutFlexibleBox::HasLeftOverflow() const {
if (IsHorizontalWritingMode()) {
return !StyleRef().IsLeftToRightDirection() ^
StyleRef().ResolvedIsRowReverseFlexDirection();
}
return StyleRef().ResolvedIsColumnReverseFlexDirection();
}
void LayoutFlexibleBox::MergeAnonymousFlexItems(LayoutObject* remove_child) {
// When we remove a flex item, and the previous and next siblings of the item
// are text nodes wrapped in anonymous flex items, the adjacent text nodes
// need to be merged into the same flex item.
LayoutObject* prev = remove_child->PreviousSibling();
if (!prev || !prev->IsAnonymousBlock())
return;
LayoutObject* next = remove_child->NextSibling();
if (!next || !next->IsAnonymousBlock())
return;
ToLayoutBoxModelObject(next)->MoveAllChildrenTo(ToLayoutBoxModelObject(prev));
To<LayoutBlockFlow>(next)->DeleteLineBoxTree();
next->Destroy();
intrinsic_size_along_main_axis_.erase(next);
}
void LayoutFlexibleBox::RemoveChild(LayoutObject* child) {
if (!DocumentBeingDestroyed() &&
!StyleRef().IsDeprecatedFlexboxUsingFlexLayout()) {
MergeAnonymousFlexItems(child);
}
LayoutBlock::RemoveChild(child);
intrinsic_size_along_main_axis_.erase(child);
}
bool LayoutFlexibleBox::HitTestChildren(
HitTestResult& result,
const HitTestLocation& hit_test_location,
const PhysicalOffset& accumulated_offset,
HitTestAction hit_test_action) {
if (hit_test_action != kHitTestForeground)
return false;
PhysicalOffset scrolled_offset = accumulated_offset;
if (HasOverflowClip())
scrolled_offset -= PhysicalOffset(ScrolledContentOffset());
for (LayoutBox* child = LastChildBox(); child;
child = child->PreviousSiblingBox()) {
if (child->HasSelfPaintingLayer())
continue;
PhysicalOffset child_accumulated_offset =
scrolled_offset + child->PhysicalLocation(this);
bool child_hit = child->HitTestAllPhases(result, hit_test_location,
child_accumulated_offset);
if (child_hit) {
UpdateHitTestResult(result,
hit_test_location.Point() - accumulated_offset);
return true;
}
}
return false;
}
void LayoutFlexibleBox::StyleDidChange(StyleDifference diff,
const ComputedStyle* old_style) {
LayoutBlock::StyleDidChange(diff, old_style);
if (old_style &&
old_style->ResolvedAlignItems(SelfAlignmentNormalBehavior())
.GetPosition() == ItemPosition::kStretch &&
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 == ItemPosition::kStretch &&
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();
base::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()) {
// Recompute the logical width, because children may have added or removed
// scrollbars.
UpdateLogicalWidthAndColumnWidth();
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.
ComputeLayoutOverflow(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 PhysicalOffset&) const {
BlockPainter(*this).PaintChildrenAtomically(this->GetOrderIterator(),
paint_info);
}
void LayoutFlexibleBox::RepositionLogicalHeightDependentFlexItems(
FlexLayoutAlgorithm& algorithm) {
Vector<FlexLine>& line_contexts = algorithm.FlexLines();
LayoutUnit cross_axis_start_edge = line_contexts.IsEmpty()
? LayoutUnit()
: line_contexts[0].cross_axis_offset;
// If we have a single line flexbox, 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 (!IsMultiline() && !line_contexts.IsEmpty()) {
line_contexts[0].cross_axis_extent = CrossAxisContentExtent();
}
AlignFlexLines(algorithm);
AlignChildren(algorithm);
if (StyleRef().FlexWrap() == EFlexWrap::kWrapReverse) {
algorithm.FlipForWrapReverse(cross_axis_start_edge,
CrossAxisContentExtent());
for (FlexLine& line_context : line_contexts) {
for (FlexItem& flex_item : line_context.line_items)
ResetAlignmentForChild(*flex_item.box, flex_item.desired_location.Y());
}
}
// 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::MainAxisIsInlineAxis(const LayoutBox& child) const {
// If we have a horizontal flow, that means the main size is the width.
// That's the inline size for horizontal writing modes, and the block
// size in vertical writing modes. For a vertical flow, main size is the
// height, so it's the inverse. So we need the inline size if we have a
// horizontal flow and horizontal writing mode, or vertical flow and vertical
// writing mode. Otherwise we need the block size.
return IsHorizontalFlow() == child.IsHorizontalWritingMode();
}
bool LayoutFlexibleBox::IsColumnFlow() const {
return StyleRef().ResolvedIsColumnFlexDirection();
}
bool LayoutFlexibleBox::IsHorizontalFlow() const {
if (IsHorizontalWritingMode())
return !IsColumnFlow();
return IsColumnFlow();
}
bool LayoutFlexibleBox::IsLeftToRightFlow() const {
if (IsColumnFlow()) {
return blink::IsHorizontalWritingMode(StyleRef().GetWritingMode()) ||
IsFlippedLinesWritingMode(StyleRef().GetWritingMode());
}
return StyleRef().IsLeftToRightDirection() ^
StyleRef().ResolvedIsRowReverseFlexDirection();
}
bool LayoutFlexibleBox::IsMultiline() const {
return StyleRef().FlexWrap() != EFlexWrap::kNowrap;
}
Length LayoutFlexibleBox::FlexBasisForChild(const LayoutBox& child) const {
Length flex_length = child.StyleRef().FlexBasis();
if (flex_length.IsAuto()) {
flex_length = IsHorizontalFlow() ? child.StyleRef().Width()
: child.StyleRef().Height();
}
return flex_length;
}
LayoutUnit LayoutFlexibleBox::CrossAxisExtentForChild(
const LayoutBox& child) const {
return IsHorizontalFlow() ? child.Size().Height() : child.Size().Width();
}
LayoutUnit LayoutFlexibleBox::ChildUnstretchedLogicalHeight(
const LayoutBox& child) const {
// This should only be called if the logical height is the cross size
DCHECK(MainAxisIsInlineAxis(child));
if (NeedToStretchChildLogicalHeight(child)) {
AutoClearOverrideLogicalHeight clear(const_cast<LayoutBox*>(&child));
LayoutUnit child_intrinsic_content_logical_height;
if (child.ShouldApplySizeContainment()) {
child_intrinsic_content_logical_height =
child.ContentLogicalHeightForSizeContainment();
} else if (child.DisplayLockInducesSizeContainment()) {
child_intrinsic_content_logical_height =
child.GetDisplayLockContext()->GetLockedContentLogicalHeight();
} else {
child_intrinsic_content_logical_height =
child.IntrinsicContentLogicalHeight();
}
LayoutUnit child_intrinsic_logical_height =
child_intrinsic_content_logical_height +
child.ScrollbarLogicalHeight() + child.BorderAndPaddingLogicalHeight();
LogicalExtentComputedValues values;
child.ComputeLogicalHeight(child_intrinsic_logical_height, LayoutUnit(),
values);
return values.extent_;
}
return child.LogicalHeight();
}
DISABLE_CFI_PERF
LayoutUnit LayoutFlexibleBox::ChildUnstretchedLogicalWidth(
const LayoutBox& child) const {
// This should only be called if the logical width is the cross size
DCHECK(!MainAxisIsInlineAxis(child));
// We compute the width as if we were unstretched. Only the main axis
// override size is set at this point.
// However, if our cross axis length is definite we don't need to recompute
// and can just return the already-set logical width.
if (!CrossAxisLengthIsDefinite(child, child.StyleRef().LogicalWidth())) {
AutoClearOverrideLogicalWidth clear(const_cast<LayoutBox*>(&child));
LogicalExtentComputedValues values;
child.ComputeLogicalWidth(values);
return values.extent_;
}
return child.LogicalWidth();
}
LayoutUnit LayoutFlexibleBox::CrossAxisUnstretchedExtentForChild(
const LayoutBox& child) const {
return MainAxisIsInlineAxis(child) ? ChildUnstretchedLogicalHeight(child)
: ChildUnstretchedLogicalWidth(child);
}
LayoutUnit LayoutFlexibleBox::MainAxisExtentForChild(
const LayoutBox& child) const {
return IsHorizontalFlow() ? child.Size().Width() : child.Size().Height();
}
LayoutUnit LayoutFlexibleBox::MainAxisContentExtentForChild(
const LayoutBox& child) const {
return IsHorizontalFlow() ? child.ContentWidth() : child.ContentHeight();
}
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::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,
LayoutUnit border_and_padding) const {
if (!MainAxisIsInlineAxis(child)) {
// 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.
LayoutUnit logical_height = child.ComputeContentLogicalHeight(
size_type, size, child.IntrinsicContentLogicalHeight());
if (logical_height == -1)
return logical_height;
return logical_height + 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)
if (child.StyleRef().LogicalWidth().IsAuto() && !HasAspectRatio(child)) {
if (size.IsMinContent())
return child.MinPreferredLogicalWidth() - border_and_padding;
if (size.IsMaxContent())
return child.MaxPreferredLogicalWidth() - border_and_padding;
}
return child.ComputeLogicalWidthUsing(size_type, size, ContentLogicalWidth(),
this) -
border_and_padding;
}
LayoutUnit LayoutFlexibleBox::ContentInsetRight() const {
return BorderRight() + PaddingRight() + RightScrollbarWidth();
}
LayoutUnit LayoutFlexibleBox::ContentInsetBottom() const {
return BorderBottom() + PaddingBottom() + BottomScrollbarHeight();
}
LayoutUnit LayoutFlexibleBox::FlowAwareContentInsetStart() const {
if (IsHorizontalFlow())
return IsLeftToRightFlow() ? ContentLeft() : ContentInsetRight();
return IsLeftToRightFlow() ? ContentTop() : ContentInsetBottom();
}
LayoutUnit LayoutFlexibleBox::FlowAwareContentInsetEnd() const {
if (IsHorizontalFlow())
return IsLeftToRightFlow() ? ContentInsetRight() : ContentLeft();
return IsLeftToRightFlow() ? ContentInsetBottom() : ContentTop();
}
LayoutUnit LayoutFlexibleBox::FlowAwareContentInsetBefore() const {
switch (FlexLayoutAlgorithm::GetTransformedWritingMode(StyleRef())) {
case TransformedWritingMode::kTopToBottomWritingMode:
return ContentTop();
case TransformedWritingMode::kBottomToTopWritingMode:
return ContentInsetBottom();
case TransformedWritingMode::kLeftToRightWritingMode:
return ContentLeft();
case TransformedWritingMode::kRightToLeftWritingMode:
return ContentInsetRight();
}
NOTREACHED();
}
DISABLE_CFI_PERF
LayoutUnit LayoutFlexibleBox::FlowAwareContentInsetAfter() const {
switch (FlexLayoutAlgorithm::GetTransformedWritingMode(StyleRef())) {
case TransformedWritingMode::kTopToBottomWritingMode:
return ContentInsetBottom();
case TransformedWritingMode::kBottomToTopWritingMode:
return ContentTop();
case TransformedWritingMode::kLeftToRightWritingMode:
return ContentInsetRight();
case TransformedWritingMode::kRightToLeftWritingMode:
return ContentLeft();
}
NOTREACHED();
}
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;
}
const Length& cross_size =
IsHorizontalFlow() ? child.StyleRef().Height() : child.StyleRef().Width();
return CrossAxisLengthIsDefinite(child, cross_size);
}
LayoutUnit LayoutFlexibleBox::ComputeMainSizeFromAspectRatioUsing(
const LayoutBox& child,
const 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 = MainAxisIsInlineAxis(child)
? child.ComputePercentageLogicalHeight(cross_size_length)
: AdjustBorderBoxLogicalWidthForBoxSizing(
ValueForLength(cross_size_length, ContentWidth()));
}
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,
bool add_to_cb) const {
if (flex_basis.IsAuto())
return false;
if (IsColumnFlow() && flex_basis.IsIntrinsic())
return false;
if (flex_basis.IsPercentOrCalc()) {
if (!IsColumnFlow() || has_definite_height_ == SizeDefiniteness::kDefinite)
return true;
if (has_definite_height_ == SizeDefiniteness::kIndefinite)
return false;
LayoutBlock* cb = nullptr;
bool definite =
child.ContainingBlockLogicalHeightForPercentageResolution(&cb) != -1;
if (add_to_cb)
cb->AddPercentHeightDescendant(const_cast<LayoutBox*>(&child));
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 (!MainAxisIsInlineAxis(child) ||
has_definite_height_ == SizeDefiniteness::kDefinite)
return true;
if (has_definite_height_ == SizeDefiniteness::kIndefinite)
return false;
bool definite =
child.ContainingBlockLogicalHeightForPercentageResolution() != -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.SelfNeedsLayout());
DCHECK(!child.NeedsLayout() || child.LayoutBlockedByDisplayLock(
DisplayLockLifecycleTarget::kChildren));
LayoutUnit main_size;
if (MainAxisIsInlineAxis(child)) {
main_size = child.MaxPreferredLogicalWidth();
} else {
if (FlexBasisForChild(child).IsPercentOrCalc() &&
!MainAxisLengthIsDefinite(child, FlexBasisForChild(child))) {
main_size = child.IntrinsicContentLogicalHeight() +
child.BorderAndPaddingLogicalHeight() +
child.ScrollbarLogicalHeight();
} else {
main_size = child.LogicalHeight();
}
}
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);
}
bool LayoutFlexibleBox::CanAvoidLayoutForNGChild(const LayoutBox& child) const {
if (!child.IsLayoutNGMixin())
return false;
// If the last layout was done with a different override size, or different
// definite-ness, we need to force-relayout so that percentage sizes are
// resolved correctly.
const NGLayoutResult* cached_layout_result = child.GetCachedLayoutResult();
if (!cached_layout_result)
return false;
const NGConstraintSpace& old_space =
cached_layout_result->GetConstraintSpaceForCaching();
if (old_space.IsFixedInlineSize() != child.HasOverrideLogicalWidth())
return false;
if (old_space.IsFixedBlockSize() != child.HasOverrideLogicalHeight())
return false;
if (!old_space.IsFixedBlockSizeIndefinite() !=
UseOverrideLogicalHeightForPerentageResolution(child))
return false;
if (child.HasOverrideLogicalWidth() &&
old_space.AvailableSize().inline_size != child.OverrideLogicalWidth())
return false;
if (child.HasOverrideLogicalHeight() &&
old_space.AvailableSize().block_size != child.OverrideLogicalHeight())
return false;
return true;
}
DISABLE_CFI_PERF
LayoutUnit LayoutFlexibleBox::ComputeInnerFlexBaseSizeForChild(
LayoutBox& child,
LayoutUnit main_axis_border_and_padding,
ChildLayoutType child_layout_type) {
if (child.IsImage() || child.IsVideo() || child.IsCanvas())
UseCounter::Count(GetDocument(), WebFeature::kAspectRatioFlexItem);
Length flex_basis = FlexBasisForChild(child);
// -webkit-box sizes as fit-content instead of max-content.
if (flex_basis.IsAuto() &&
(StyleRef().IsDeprecatedWebkitBox() &&
(StyleRef().BoxOrient() == EBoxOrient::kHorizontal ||
StyleRef().BoxAlign() != EBoxAlignment::kStretch))) {
flex_basis = Length(Length::kFitContent);
}
if (MainAxisLengthIsDefinite(child, flex_basis)) {
return std::max(LayoutUnit(), ComputeMainAxisExtentForChild(
child, kMainOrPreferredSize, flex_basis,
main_axis_border_and_padding));
}
if (UseChildAspectRatio(child)) {
const Length& cross_size_length = IsHorizontalFlow()
? child.StyleRef().Height()
: child.StyleRef().Width();
LayoutUnit result =
ComputeMainSizeFromAspectRatioUsing(child, cross_size_length);
result = AdjustChildSizeForAspectRatioCrossAxisMinAndMax(child, result);
return result - main_axis_border_and_padding;
}
// 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 (MainAxisIsInlineAxis(child)) {
// We don't need to add ScrollbarLogicalWidth here because the preferred
// width includes the scrollbar, even for overflow: auto.
main_axis_extent = child.MaxPreferredLogicalWidth();
} else {
if (child.ShouldApplySizeContainment()) {
return child.ContentLogicalHeightForSizeContainment() +
LayoutUnit(child.ScrollbarLogicalHeight());
}
// The needed value here is the logical height. This value does not include
// the border/scrollbar/padding size, so we have to add the scrollbar.
if (child.DisplayLockInducesSizeContainment()) {
return child.GetDisplayLockContext()->GetLockedContentLogicalHeight() +
child.ScrollbarLogicalHeight();
}
if (child_layout_type == kNeverLayout)
return LayoutUnit();
DCHECK(!child.NeedsLayout());
DCHECK(intrinsic_size_along_main_axis_.Contains(&child));
main_axis_extent = intrinsic_size_along_main_axis_.at(&child);
}
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) {
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;
const LayoutUnit line_break_length = MainAxisContentExtent(LayoutUnit::Max());
FlexLayoutAlgorithm flex_algorithm(Style(), line_break_length);
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;
}
ConstructAndAppendFlexItem(&flex_algorithm, *child, layout_type);
}
// Because we set the override containing block logical height to -1 in
// ConstructAndAppendFlexItem, any value we may have cached for definiteness
// is incorrect; just reset it here.
has_definite_height_ = SizeDefiniteness::kUnknown;
LayoutUnit cross_axis_offset = FlowAwareContentInsetBefore();
LayoutUnit logical_width = LogicalWidth();
FlexLine* current_line;
while ((current_line = flex_algorithm.ComputeNextFlexLine(logical_width))) {
DCHECK_GE(current_line->line_items.size(), 0ULL);
current_line->SetContainerMainInnerSize(
MainAxisContentExtent(current_line->sum_hypothetical_main_size));
current_line->FreezeInflexibleItems();
while (!current_line->ResolveFlexibleLengths()) {
DCHECK_GE(current_line->total_flex_grow, 0);
DCHECK_GE(current_line->total_weighted_flex_shrink, 0);
}
LayoutLineItems(current_line, relayout_children, layout_scope);
LayoutUnit main_axis_offset = FlowAwareContentInsetStart();
current_line->ComputeLineItemsPosition(main_axis_offset, cross_axis_offset);
ApplyLineItemsPosition(current_line);
if (number_of_in_flow_children_on_first_line_ == -1) {
number_of_in_flow_children_on_first_line_ =
current_line->line_items.size();
}
}
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();
if (!HasOverrideLogicalHeight() && IsColumnFlow()) {
SetIntrinsicContentLogicalHeight(
flex_algorithm.IntrinsicContentBlockSize());
}
RepositionLogicalHeightDependentFlexItems(flex_algorithm);
}
bool LayoutFlexibleBox::HasAutoMarginsInCrossAxis(
const LayoutBox& child) const {
if (IsHorizontalFlow()) {
return child.StyleRef().MarginTop().IsAuto() ||
child.StyleRef().MarginBottom().IsAuto();
}
return child.StyleRef().MarginLeft().IsAuto() ||
child.StyleRef().MarginRight().IsAuto();
}
LayoutUnit LayoutFlexibleBox::ComputeChildMarginValue(const 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->StyleRef().MarginLeft()));
child->SetMarginRight(
ComputeChildMarginValue(child->StyleRef().MarginRight()));
} else {
child->SetMarginTop(
ComputeChildMarginValue(child->StyleRef().MarginTop()));
child->SetMarginBottom(
ComputeChildMarginValue(child->StyleRef().MarginBottom()));
}
}
}
DISABLE_CFI_PERF
MinMaxSize LayoutFlexibleBox::ComputeMinAndMaxSizesForChild(
const FlexLayoutAlgorithm& algorithm,
const LayoutBox& child,
LayoutUnit border_and_padding) const {
MinMaxSize sizes{LayoutUnit(), LayoutUnit::Max()};
const Length& max = IsHorizontalFlow() ? child.StyleRef().MaxWidth()
: child.StyleRef().MaxHeight();
if (max.IsSpecifiedOrIntrinsic()) {
sizes.max_size =
ComputeMainAxisExtentForChild(child, kMaxSize, max, border_and_padding);
if (sizes.max_size == -1)
sizes.max_size = LayoutUnit::Max();
DCHECK_GE(sizes.max_size, LayoutUnit());
}
const Length& min = IsHorizontalFlow() ? child.StyleRef().MinWidth()
: child.StyleRef().MinHeight();
if (min.IsSpecifiedOrIntrinsic()) {
sizes.min_size =
ComputeMainAxisExtentForChild(child, kMinSize, min, border_and_padding);
// computeMainAxisExtentForChild can return -1 when the child has a
// percentage min size, but we have an indefinite size in that axis.
sizes.min_size = std::max(LayoutUnit(), sizes.min_size);
} else if (algorithm.ShouldApplyMinSizeAutoForChild(child)) {
LayoutUnit content_size = ComputeMainAxisExtentForChild(
child, kMinSize, Length::MinContent(), border_and_padding);
DCHECK_GE(content_size, LayoutUnit());
if (HasAspectRatio(child) && child.IntrinsicSize().Height() > 0)
content_size =
AdjustChildSizeForAspectRatioCrossAxisMinAndMax(child, content_size);
if (child.IsTable() && !IsColumnFlow()) {
// Avoid resolving minimum size to something narrower than the minimum
// preferred logical width of the table.
sizes.min_size = content_size;
} else {
if (sizes.max_size != -1 && content_size > sizes.max_size)
content_size = sizes.max_size;
const Length& main_size = IsHorizontalFlow() ? child.StyleRef().Width()
: child.StyleRef().Height();
if (MainAxisLengthIsDefinite(child, main_size)) {
LayoutUnit resolved_main_size = ComputeMainAxisExtentForChild(
child, kMainOrPreferredSize, main_size, border_and_padding);
DCHECK_GE(resolved_main_size, LayoutUnit());
LayoutUnit specified_size =
sizes.max_size != -1 ? std::min(resolved_main_size, sizes.max_size)
: resolved_main_size;
sizes.min_size = std::min(specified_size, content_size);
} else if (UseChildAspectRatio(child)) {
const 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);
sizes.min_size = std::min(transferred_size, content_size);
} else {
sizes.min_size = content_size;
}
}
}
DCHECK_GE(sizes.min_size, LayoutUnit());
return sizes;
}
bool LayoutFlexibleBox::CrossSizeIsDefiniteForPercentageResolution(
const LayoutBox& child) const {
if (FlexLayoutAlgorithm::AlignmentForChild(StyleRef(), child.StyleRef()) !=
ItemPosition::kStretch)
return false;
// Here we implement https://drafts.csswg.org/css-flexbox/#algo-stretch
if (!MainAxisIsInlineAxis(child) && child.HasOverrideLogicalWidth())
return true;
if (MainAxisIsInlineAxis(child) && child.HasOverrideLogicalHeight())
return true;
// 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 false;
}
bool LayoutFlexibleBox::MainSizeIsDefiniteForPercentageResolution(
const LayoutBox& child) const {
// 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.
// We make up a percentage to check whether we have a definite size.
if (!MainAxisLengthIsDefinite(child, Length::Percent(0), false))
return false;
if (MainAxisIsInlineAxis(child))
return child.HasOverrideLogicalWidth();
return child.HasOverrideLogicalHeight();
}
bool LayoutFlexibleBox::UseOverrideLogicalHeightForPerentageResolution(
const LayoutBox& child) const {
if (MainAxisIsInlineAxis(child))
return CrossSizeIsDefiniteForPercentageResolution(child);
return MainSizeIsDefiniteForPercentageResolution(child);
}
LayoutUnit LayoutFlexibleBox::AdjustChildSizeForAspectRatioCrossAxisMinAndMax(
const LayoutBox& child,
LayoutUnit child_size) const {
const Length& cross_min = IsHorizontalFlow() ? child.StyleRef().MinHeight()
: child.StyleRef().MinWidth();
const Length& cross_max = IsHorizontalFlow() ? child.StyleRef().MaxHeight()
: child.StyleRef().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
void LayoutFlexibleBox::ConstructAndAppendFlexItem(
FlexLayoutAlgorithm* algorithm,
LayoutBox& child,
ChildLayoutType layout_type) {
if (layout_type != kNeverLayout &&
ChildHasIntrinsicMainAxisSize(*algorithm, child)) {
// 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.
// For column flow flex containers, we even need to do this for children
// that don't need layout, if there's a chance that the logical width of
// the flex container has changed (because that may affect the intrinsic
// height of the child).
UpdateBlockChildDirtyBitsBeforeLayout(layout_type == kForceLayout, child);
if (child.NeedsLayout() || layout_type == kForceLayout ||
!intrinsic_size_along_main_axis_.Contains(&child)) {
// Don't resolve percentages in children. This is especially important
// for the min-height calculation, where we want percentages to be
// treated as auto. For flex-basis itself, this is not a problem because
// by definition we have an indefinite flex basis here and thus
// percentages should not resolve.
if (IsHorizontalWritingMode() == child.IsHorizontalWritingMode())
child.SetOverrideContainingBlockContentLogicalHeight(LayoutUnit(-1));
else
child.SetOverrideContainingBlockContentLogicalWidth(LayoutUnit(-1));
child.ClearOverrideSize();
child.ForceLayout();
CacheChildMainSize(child);
child.ClearOverrideContainingBlockContentSize();
}
}
LayoutUnit border_and_padding = IsHorizontalFlow()
? child.BorderAndPaddingWidth()
: child.BorderAndPaddingHeight();
LayoutUnit child_inner_flex_base_size =
ComputeInnerFlexBaseSizeForChild(child, border_and_padding, layout_type);
MinMaxSize sizes =
ComputeMinAndMaxSizesForChild(*algorithm, child, border_and_padding);
LayoutUnit margin =
IsHorizontalFlow() ? child.MarginWidth() : child.MarginHeight();
algorithm->emplace_back(&child, child_inner_flex_base_size, sizes,
/* cross axis min max sizes */ base::nullopt,
border_and_padding, margin);
}
void LayoutFlexibleBox::SetOverrideMainAxisContentSizeForChild(FlexItem& item) {
if (MainAxisIsInlineAxis(*item.box)) {
item.box->SetOverrideLogicalWidth(item.FlexedBorderBoxSize());
} else {
item.box->SetOverrideLogicalHeight(item.FlexedBorderBoxSize());
}
}
LayoutUnit LayoutFlexibleBox::StaticMainAxisPositionForPositionedChild(
const LayoutBox& child) {
const LayoutUnit available_space =
MainAxisContentExtent(ContentLogicalHeight()) -
MainAxisExtentForChild(child);
LayoutUnit offset = FlexLayoutAlgorithm::InitialContentPositionOffset(
available_space, FlexLayoutAlgorithm::ResolvedJustifyContent(StyleRef()),
1);
if (StyleRef().ResolvedIsRowReverseFlexDirection() ||
StyleRef().ResolvedIsColumnReverseFlexDirection())
offset = available_space - offset;
return offset;
}
LayoutUnit LayoutFlexibleBox::StaticCrossAxisPositionForPositionedChild(
const LayoutBox& child) {
LayoutUnit available_space =
CrossAxisContentExtent() - CrossAxisExtentForChild(child);
return FlexItem::AlignmentOffset(
available_space,
FlexLayoutAlgorithm::AlignmentForChild(StyleRef(), child.StyleRef()),
LayoutUnit(), LayoutUnit(),
StyleRef().FlexWrap() == EFlexWrap::kWrapReverse,
StyleRef().IsDeprecatedWebkitBox());
}
LayoutUnit LayoutFlexibleBox::StaticInlinePositionForPositionedChild(
const LayoutBox& child) {
const LayoutUnit start_offset = StartOffsetForContent();
if (StyleRef().IsDeprecatedWebkitBox())
return start_offset;
return start_offset + (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 = FlowAwareContentInsetStart();
if (child_layer->StaticInlinePosition() != static_inline_position) {
child_layer->SetStaticInlinePosition(static_inline_position);
if (child.StyleRef().HasStaticInlinePosition(
StyleRef().IsHorizontalWritingMode()))
child.SetChildNeedsLayout(kMarkOnlyThis);
}
LayoutUnit static_block_position = FlowAwareContentInsetBefore();
if (child_layer->StaticBlockPosition() != static_block_position) {
child_layer->SetStaticBlockPosition(static_block_position);
if (child.StyleRef().HasStaticBlockPosition(
StyleRef().IsHorizontalWritingMode()))
child.SetChildNeedsLayout(kMarkOnlyThis);
}
}
void LayoutFlexibleBox::ResetAutoMarginsAndLogicalTopInCrossAxis(
LayoutBox& child) {
if (HasAutoMarginsInCrossAxis(child)) {
child.UpdateLogicalHeight();
if (IsHorizontalFlow()) {
if (child.StyleRef().MarginTop().IsAuto())
child.SetMarginTop(LayoutUnit());
if (child.StyleRef().MarginBottom().IsAuto())
child.SetMarginBottom(LayoutUnit());
} else {
if (child.StyleRef().MarginLeft().IsAuto())
child.SetMarginLeft(LayoutUnit());
if (child.StyleRef().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 (FlexLayoutAlgorithm::AlignmentForChild(StyleRef(), child.StyleRef()) !=
ItemPosition::kStretch)
return false;
if (IsHorizontalFlow() != child.StyleRef().IsHorizontalWritingMode())
return false;
return child.StyleRef().LogicalHeight().IsAuto();
}
bool LayoutFlexibleBox::ChildHasIntrinsicMainAxisSize(
const FlexLayoutAlgorithm& algorithm,
const LayoutBox& child) const {
bool result = false;
if (!MainAxisIsInlineAxis(child) && !child.ShouldApplySizeContainment() &&
!child.DisplayLockInducesSizeContainment()) {
Length child_flex_basis = FlexBasisForChild(child);
const Length& child_min_size = IsHorizontalFlow()
? child.StyleRef().MinWidth()
: child.StyleRef().MinHeight();
const Length& child_max_size = IsHorizontalFlow()
? child.StyleRef().MaxWidth()
: child.StyleRef().MaxHeight();
if (!MainAxisLengthIsDefinite(child, child_flex_basis) ||
child_min_size.IsIntrinsic() || child_max_size.IsIntrinsic()) {
result = true;
} else if (algorithm.ShouldApplyMinSizeAutoForChild(child)) {
result = true;
}
}
return result;
}
EOverflow LayoutFlexibleBox::CrossAxisOverflowForChild(
const LayoutBox& child) const {
if (IsHorizontalFlow())
return child.StyleRef().OverflowY();
return child.StyleRef().OverflowX();
}
DISABLE_CFI_PERF
void LayoutFlexibleBox::LayoutLineItems(FlexLine* current_line,
bool relayout_children,
SubtreeLayoutScope& layout_scope) {
for (wtf_size_t i = 0; i < current_line->line_items.size(); ++i) {
FlexItem& flex_item = current_line->line_items[i];
LayoutBox* child = flex_item.box;
DCHECK(!flex_item.box->IsOutOfFlowPositioned());
child->SetShouldCheckForPaintInvalidation();
SetOverrideMainAxisContentSizeForChild(flex_item);
// The flexed content size and the override size include the scrollbar
// width, so we need to compare to the size including the scrollbar.
if (flex_item.flexed_content_size !=
MainAxisContentExtentForChildIncludingScrollbar(*child)) {
child->SetSelfNeedsLayoutForAvailableSpace(true);
} 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);
// TODO(dgrogan): Broaden the NG part of this check once NG types other
// than Mixin derivatives are cached.
auto* child_layout_block = DynamicTo<LayoutBlock>(child);
if (child_layout_block &&
child_layout_block->HasPercentHeightDescendants() &&
!CanAvoidLayoutForNGChild(*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.
force_child_relayout = true;
}
UpdateBlockChildDirtyBitsBeforeLayout(force_child_relayout, *child);
if (!child->NeedsLayout())
MarkChildForPaginationRelayoutIfNeeded(*child, layout_scope);
if (child->NeedsLayout()) {
relaid_out_children_.insert(child);
// It is very important that we only clear the cross axis override size
// if we are in fact going to lay out the child. Otherwise, the cross
// axis size and the actual laid out size get out of sync, which will
// cause problems if we later lay out the child in simplified layout,
// which does not go through regular flex layout and therefore would
// not reset the cross axis size.
if (MainAxisIsInlineAxis(*child))
child->ClearOverrideLogicalHeight();
else
child->ClearOverrideLogicalWidth();
}
child->LayoutIfNeeded();
// This shouldn't be necessary, because we set the override size to be
// the flexed_content_size and so the result should in fact be that size.
// But it turns out that tables ignore the override size, and so we have
// to re-check the size so that we place the flex item correctly.
flex_item.flexed_content_size =
MainAxisExtentForChild(*child) - flex_item.main_axis_border_padding;
flex_item.cross_axis_size = CrossAxisUnstretchedExtentForChild(*child);
}
}
void LayoutFlexibleBox::ApplyLineItemsPosition(FlexLine* current_line) {
bool is_paginated = View()->GetLayoutState()->IsPaginated();
for (wtf_size_t i = 0; i < current_line->line_items.size(); ++i) {
const FlexItem& flex_item = current_line->line_items[i];
LayoutBox* child = flex_item.box;
SetFlowAwareLocationForChild(*child, flex_item.desired_location);
if (is_paginated)
UpdateFragmentationInfoForChild(*child);
}
if (IsColumnFlow()) {
SetLogicalHeight(std::max(LogicalHeight(), current_line->main_axis_extent +
FlowAwareContentInsetEnd()));
} else {
SetLogicalHeight(
std::max(LogicalHeight(), current_line->cross_axis_offset +
FlowAwareContentInsetAfter() +
current_line->cross_axis_extent));
}
if (StyleRef().ResolvedIsColumnReverseFlexDirection()) {
// 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(current_line->line_items,
current_line->cross_axis_offset,
current_line->remaining_free_space);
}
}
void LayoutFlexibleBox::LayoutColumnReverse(FlexItemVectorView& children,
LayoutUnit cross_axis_offset,
LayoutUnit available_free_space) {
const StyleContentAlignmentData justify_content =
FlexLayoutAlgorithm::ResolvedJustifyContent(StyleRef());
// This is similar to the logic in FlexLine::ComputeLineItemsPosition, except
// we place the children starting from the end of the flexbox.
LayoutUnit main_axis_offset = LogicalHeight() - FlowAwareContentInsetEnd();
main_axis_offset -= FlexLayoutAlgorithm::InitialContentPositionOffset(
available_free_space, justify_content, children.size());
for (wtf_size_t i = 0; i < children.size(); ++i) {
FlexItem& flex_item = children[i];
LayoutBox* child = flex_item.box;
DCHECK(!child->IsOutOfFlowPositioned());
main_axis_offset -=
MainAxisExtentForChild(*child) + flex_item.FlowAwareMarginEnd();
SetFlowAwareLocationForChild(
*child,
LayoutPoint(main_axis_offset,
cross_axis_offset + flex_item.FlowAwareMarginBefore()));
main_axis_offset -= flex_item.FlowAwareMarginStart();
main_axis_offset -=
FlexLayoutAlgorithm::ContentDistributionSpaceBetweenChildren(
available_free_space, justify_content, children.size());
}
}
void LayoutFlexibleBox::AlignFlexLines(FlexLayoutAlgorithm& algorithm) {
Vector<FlexLine>& line_contexts = algorithm.FlexLines();
const StyleContentAlignmentData align_content =
FlexLayoutAlgorithm::ResolvedAlignContent(StyleRef());
if (align_content.GetPosition() == ContentPosition::kFlexStart)
return;
if (IsMultiline() && !line_contexts.IsEmpty()) {
UseCounter::Count(GetDocument(),
WebFeature::kFlexboxSingleLineAlignContent);
}
algorithm.AlignFlexLines(CrossAxisContentExtent());
for (unsigned line_number = 0; line_number < line_contexts.size();
++line_number) {
FlexLine& line_context = line_contexts[line_number];
for (FlexItem& flex_item : line_context.line_items) {
ResetAlignmentForChild(*flex_item.box, flex_item.desired_location.Y());
}
}
}
void LayoutFlexibleBox::AdjustAlignmentForChild(LayoutBox& child,
LayoutUnit delta) {
DCHECK(!child.IsOutOfFlowPositioned());
SetFlowAwareLocationForChild(child, FlowAwareLocationForChild(child) +
LayoutSize(LayoutUnit(), delta));
}
void LayoutFlexibleBox::ResetAlignmentForChild(
LayoutBox& child,
LayoutUnit new_cross_axis_position) {
SetFlowAwareLocationForChild(
child, {FlowAwareLocationForChild(child).X(), new_cross_axis_position});
}
void LayoutFlexibleBox::AlignChildren(FlexLayoutAlgorithm& algorithm) {
Vector<FlexLine>& line_contexts = algorithm.FlexLines();
algorithm.AlignChildren();
for (unsigned line_number = 0; line_number < line_contexts.size();
++line_number) {
FlexLine& line_context = line_contexts[line_number];
for (FlexItem& flex_item : line_context.line_items) {
if (flex_item.Alignment() == ItemPosition::kStretch)
ApplyStretchAlignmentToChild(flex_item);
ResetAlignmentForChild(*flex_item.box, flex_item.desired_location.Y());
}
}
}
void LayoutFlexibleBox::ApplyStretchAlignmentToChild(FlexItem& flex_item) {
LayoutBox& child = *flex_item.box;
if (flex_item.MainAxisIsInlineAxis() &&
child.StyleRef().LogicalHeight().IsAuto()) {
// FIXME: Can avoid laying out here in some cases. See
// https://webkit.org/b/87905.
bool child_needs_relayout =
flex_item.cross_axis_size != child.LogicalHeight();
child.SetOverrideLogicalHeight(flex_item.cross_axis_size);
auto* child_block = DynamicTo<LayoutBlock>(child);
if (child_block && child_block->HasPercentHeightDescendants() &&
!CanAvoidLayoutForNGChild(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 |= relaid_out_children_.Contains(&child);
}
if (child_needs_relayout)
child.ForceLayout();
} else if (!flex_item.MainAxisIsInlineAxis() &&
child.StyleRef().LogicalWidth().IsAuto()) {
if (flex_item.cross_axis_size != child.LogicalWidth()) {
child.SetOverrideLogicalWidth(flex_item.cross_axis_size);
child.ForceLayout();
}
}
}
void LayoutFlexibleBox::FlipForRightToLeftColumn(
const Vector<FlexLine>& line_contexts) {
if (StyleRef().IsLeftToRightDirection() || !IsColumnFlow())
return;
LayoutUnit cross_extent = CrossAxisExtent();
for (const FlexLine& line_context : line_contexts) {
for (const FlexItem& flex_item : line_context.line_items) {
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 - flex_item.cross_axis_size - location.Y());
SetFlowAwareLocationForChild(*flex_item.box, location);
}
}
}
} // namespace blink