Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at */
/* rendering object for CSS "display: flex" */
#include "nsFlexContainerFrame.h"
#include <algorithm>
#include "gfxContext.h"
#include "mozilla/Baseline.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/CSSOrderAwareFrameIterator.h"
#include "mozilla/Logging.h"
#include "mozilla/PresShell.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/WritingModes.h"
#include "nsBlockFrame.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsDisplayList.h"
#include "nsFieldSetFrame.h"
#include "nsIFrameInlines.h"
#include "nsLayoutUtils.h"
#include "nsPlaceholderFrame.h"
#include "nsPresContext.h"
using namespace mozilla;
using namespace mozilla::layout;
// Convenience typedefs for helper classes that we forward-declare in .h file
// (so that nsFlexContainerFrame methods can use them as parameters):
using FlexItem = nsFlexContainerFrame::FlexItem;
using FlexLine = nsFlexContainerFrame::FlexLine;
using FlexboxAxisTracker = nsFlexContainerFrame::FlexboxAxisTracker;
using StrutInfo = nsFlexContainerFrame::StrutInfo;
using CachedBAxisMeasurement = nsFlexContainerFrame::CachedBAxisMeasurement;
using CachedFlexItemData = nsFlexContainerFrame::CachedFlexItemData;
static mozilla::LazyLogModule gFlexContainerLog("FlexContainer");
// FLEX_LOG is a top-level general log print.
#define FLEX_LOG(message, ...) \
MOZ_LOG(gFlexContainerLog, LogLevel::Debug, (message, ##__VA_ARGS__));
// FLEX_ITEM_LOG is a top-level log print for flex item.
#define FLEX_ITEM_LOG(item_frame, message, ...) \
MOZ_LOG(gFlexContainerLog, LogLevel::Debug, \
("Flex item %p: " message, item_frame, ##__VA_ARGS__));
// FLEX_LOGV is a verbose log print with built-in two spaces indentation. The
// convention to use FLEX_LOGV is that FLEX_LOGV statements should generally be
// preceded by one FLEX_LOG or FLEX_ITEM_LOG so that there's no need to repeat
// information presented in the preceding LOG statement. If you want extra level
// of indentation, just add two extra spaces at the start of the message string.
#define FLEX_LOGV(message, ...) \
MOZ_LOG(gFlexContainerLog, LogLevel::Verbose, (" " message, ##__VA_ARGS__));
static const char* BoolToYesNo(bool aArg) { return aArg ? "yes" : "no"; }
// Returns true if aFlexContainer is a frame for some element that has
// display:-webkit-{inline-}box (or -moz-{inline-}box). aFlexContainer is
// expected to be an instance of nsFlexContainerFrame (enforced with an assert);
// otherwise, this function's state-bit-check here is bogus.
static bool IsLegacyBox(const nsIFrame* aFlexContainer) {
"only flex containers may be passed to this function");
return aFlexContainer->HasAnyStateBits(
// Returns the OrderState enum we should pass to CSSOrderAwareFrameIterator
// (depending on whether aFlexContainer has
static CSSOrderAwareFrameIterator::OrderState OrderStateForIter(
const nsFlexContainerFrame* aFlexContainer) {
return aFlexContainer->HasAnyStateBits(
? CSSOrderAwareFrameIterator::OrderState::Ordered
: CSSOrderAwareFrameIterator::OrderState::Unordered;
// Returns the OrderingProperty enum that we should pass to
// CSSOrderAwareFrameIterator (depending on whether it's a legacy box).
static CSSOrderAwareFrameIterator::OrderingProperty OrderingPropertyForIter(
const nsFlexContainerFrame* aFlexContainer) {
return IsLegacyBox(aFlexContainer)
? CSSOrderAwareFrameIterator::OrderingProperty::BoxOrdinalGroup
: CSSOrderAwareFrameIterator::OrderingProperty::Order;
// Returns the "align-items" value that's equivalent to the legacy "box-align"
// value in the given style struct.
static StyleAlignFlags ConvertLegacyStyleToAlignItems(
const nsStyleXUL* aStyleXUL) {
// -[moz|webkit]-box-align corresponds to modern "align-items"
switch (aStyleXUL->mBoxAlign) {
case StyleBoxAlign::Stretch:
return StyleAlignFlags::STRETCH;
case StyleBoxAlign::Start:
return StyleAlignFlags::FLEX_START;
case StyleBoxAlign::Center:
return StyleAlignFlags::CENTER;
case StyleBoxAlign::Baseline:
return StyleAlignFlags::BASELINE;
case StyleBoxAlign::End:
return StyleAlignFlags::FLEX_END;
MOZ_ASSERT_UNREACHABLE("Unrecognized mBoxAlign enum value");
// Fall back to default value of "align-items" property:
return StyleAlignFlags::STRETCH;
// Returns the "justify-content" value that's equivalent to the legacy
// "box-pack" value in the given style struct.
static StyleContentDistribution ConvertLegacyStyleToJustifyContent(
const nsStyleXUL* aStyleXUL) {
// -[moz|webkit]-box-pack corresponds to modern "justify-content"
switch (aStyleXUL->mBoxPack) {
case StyleBoxPack::Start:
return {StyleAlignFlags::FLEX_START};
case StyleBoxPack::Center:
return {StyleAlignFlags::CENTER};
case StyleBoxPack::End:
return {StyleAlignFlags::FLEX_END};
case StyleBoxPack::Justify:
return {StyleAlignFlags::SPACE_BETWEEN};
MOZ_ASSERT_UNREACHABLE("Unrecognized mBoxPack enum value");
// Fall back to default value of "justify-content" property:
return {StyleAlignFlags::FLEX_START};
// Check if the size is auto or it is a keyword in the block axis.
// |aIsInline| should represent whether aSize is in the inline axis, from the
// perspective of the writing mode of the flex item that the size comes from.
// max-content and min-content should behave as property's initial value.
// Bug 567039: We treat -moz-fit-content and -moz-available as property's
// initial value for now.
static inline bool IsAutoOrEnumOnBSize(const StyleSize& aSize, bool aIsInline) {
return aSize.IsAuto() || (!aIsInline && !aSize.IsLengthPercentage());
// Encapsulates our flex container's main & cross axes. This class is backed by
// a FlexboxAxisInfo helper member variable, and it adds some convenience APIs
// on top of what that struct offers.
class MOZ_STACK_CLASS nsFlexContainerFrame::FlexboxAxisTracker {
explicit FlexboxAxisTracker(const nsFlexContainerFrame* aFlexContainer);
// Accessors:
LogicalAxis MainAxis() const {
return IsRowOriented() ? LogicalAxis::Inline : LogicalAxis::Block;
LogicalAxis CrossAxis() const {
return IsRowOriented() ? LogicalAxis::Block : LogicalAxis::Inline;
LogicalSide MainAxisStartSide() const;
LogicalSide MainAxisEndSide() const {
return GetOppositeSide(MainAxisStartSide());
LogicalSide CrossAxisStartSide() const;
LogicalSide CrossAxisEndSide() const {
return GetOppositeSide(CrossAxisStartSide());
mozilla::Side MainAxisPhysicalStartSide() const {
return mWM.PhysicalSide(MainAxisStartSide());
mozilla::Side MainAxisPhysicalEndSide() const {
return mWM.PhysicalSide(MainAxisEndSide());
mozilla::Side CrossAxisPhysicalStartSide() const {
return mWM.PhysicalSide(CrossAxisStartSide());
mozilla::Side CrossAxisPhysicalEndSide() const {
return mWM.PhysicalSide(CrossAxisEndSide());
// Returns the flex container's writing mode.
WritingMode GetWritingMode() const { return mWM; }
// Returns true if our main axis is in the reverse direction of our
// writing mode's corresponding axis. (From 'flex-direction: *-reverse')
bool IsMainAxisReversed() const { return mAxisInfo.mIsMainAxisReversed; }
// Returns true if our cross axis is in the reverse direction of our
// writing mode's corresponding axis. (From 'flex-wrap: *-reverse')
bool IsCrossAxisReversed() const { return mAxisInfo.mIsCrossAxisReversed; }
bool IsRowOriented() const { return mAxisInfo.mIsRowOriented; }
bool IsColumnOriented() const { return !IsRowOriented(); }
// aSize is expected to match the flex container's WritingMode.
nscoord MainComponent(const LogicalSize& aSize) const {
return IsRowOriented() ? aSize.ISize(mWM) : aSize.BSize(mWM);
int32_t MainComponent(const LayoutDeviceIntSize& aIntSize) const {
return IsMainAxisHorizontal() ? aIntSize.width : aIntSize.height;
// aSize is expected to match the flex container's WritingMode.
nscoord CrossComponent(const LogicalSize& aSize) const {
return IsRowOriented() ? aSize.BSize(mWM) : aSize.ISize(mWM);
int32_t CrossComponent(const LayoutDeviceIntSize& aIntSize) const {
return IsMainAxisHorizontal() ? aIntSize.height : aIntSize.width;
// NOTE: aMargin is expected to use the flex container's WritingMode.
nscoord MarginSizeInMainAxis(const LogicalMargin& aMargin) const {
// If we're row-oriented, our main axis is the inline axis.
return IsRowOriented() ? aMargin.IStartEnd(mWM) : aMargin.BStartEnd(mWM);
nscoord MarginSizeInCrossAxis(const LogicalMargin& aMargin) const {
// If we're row-oriented, our cross axis is the block axis.
return IsRowOriented() ? aMargin.BStartEnd(mWM) : aMargin.IStartEnd(mWM);
* Converts a "flex-relative" point (a main-axis & cross-axis coordinate)
* into a LogicalPoint, using the flex container's writing mode.
* @arg aMainCoord The main-axis coordinate -- i.e an offset from the
* main-start edge of the flex container's content box.
* @arg aCrossCoord The cross-axis coordinate -- i.e an offset from the
* cross-start edge of the flex container's content box.
* @arg aContainerMainSize The main size of flex container's content box.
* @arg aContainerCrossSize The cross size of flex container's content box.
* @return A LogicalPoint, with the flex container's writing mode, that
* represents the same position. The logical coordinates are
* relative to the flex container's content box.
LogicalPoint LogicalPointFromFlexRelativePoint(
nscoord aMainCoord, nscoord aCrossCoord, nscoord aContainerMainSize,
nscoord aContainerCrossSize) const {
nscoord logicalCoordInMainAxis =
IsMainAxisReversed() ? aContainerMainSize - aMainCoord : aMainCoord;
nscoord logicalCoordInCrossAxis =
IsCrossAxisReversed() ? aContainerCrossSize - aCrossCoord : aCrossCoord;
return IsRowOriented() ? LogicalPoint(mWM, logicalCoordInMainAxis,
: LogicalPoint(mWM, logicalCoordInCrossAxis,
* Converts a "flex-relative" size (a main-axis & cross-axis size)
* into a LogicalSize, using the flex container's writing mode.
* @arg aMainSize The main-axis size.
* @arg aCrossSize The cross-axis size.
* @return A LogicalSize, with the flex container's writing mode, that
* represents the same size.
LogicalSize LogicalSizeFromFlexRelativeSizes(nscoord aMainSize,
nscoord aCrossSize) const {
return IsRowOriented() ? LogicalSize(mWM, aMainSize, aCrossSize)
: LogicalSize(mWM, aCrossSize, aMainSize);
* Converts a "flex-relative" ascent (the distance from the flex container's
* content-box cross-start edge to its baseline) into a logical ascent (the
* distance from the flex container's content-box block-start edge to its
* baseline).
nscoord LogicalAscentFromFlexRelativeAscent(
nscoord aFlexRelativeAscent, nscoord aContentBoxCrossSize) const {
return (IsCrossAxisReversed() ? aContentBoxCrossSize - aFlexRelativeAscent
: aFlexRelativeAscent);
bool IsMainAxisHorizontal() const {
// If we're row-oriented, and our writing mode is NOT vertical,
// or we're column-oriented and our writing mode IS vertical,
// then our main axis is horizontal. This handles all cases:
return IsRowOriented() != mWM.IsVertical();
// Returns true if this flex item's inline axis in aItemWM is parallel (or
// antiparallel) to the container's main axis. Returns false, otherwise.
// Note: this is a helper used before constructing FlexItem. Inside of flex
// reflow code, FlexItem::IsInlineAxisMainAxis() is equivalent & more optimal.
bool IsInlineAxisMainAxis(WritingMode aItemWM) const {
return IsRowOriented() != GetWritingMode().IsOrthogonalTo(aItemWM);
// Maps justify-*: 'left' or 'right' to 'start' or 'end'.
StyleAlignFlags ResolveJustifyLeftRight(const StyleAlignFlags& aFlags) const {
aFlags == StyleAlignFlags::LEFT || aFlags == StyleAlignFlags::RIGHT,
"This helper accepts only 'LEFT' or 'RIGHT' flags!");
const auto wm = GetWritingMode();
const bool isJustifyLeft = aFlags == StyleAlignFlags::LEFT;
if (IsColumnOriented()) {
if (!wm.IsVertical()) {
// Container's alignment axis (main axis) is *not* parallel to the
// line-left <-> line-right axis or the physical left <-> physical right
// axis, so we map both 'left' and 'right' to 'start'.
return StyleAlignFlags::START;
MOZ_ASSERT(wm.PhysicalAxis(MainAxis()) == PhysicalAxis::Horizontal,
"Vertical column-oriented flex container's main axis should "
"be parallel to physical left <-> right axis!");
// Map 'left' or 'right' to 'start' or 'end', depending on its block flow
// direction.
return isJustifyLeft == wm.IsVerticalLR() ? StyleAlignFlags::START
: StyleAlignFlags::END;
MOZ_ASSERT(MainAxis() == LogicalAxis::Inline,
"Row-oriented flex container's main axis should be parallel to "
"line-left <-> line-right axis!");
// If we get here, we're operating on the flex container's inline axis,
// so we map 'left' to whichever of 'start' or 'end' corresponds to the
// *line-relative* left side; and similar for 'right'.
return isJustifyLeft == wm.IsBidiLTR() ? StyleAlignFlags::START
: StyleAlignFlags::END;
// Delete copy-constructor & reassignment operator, to prevent accidental
// (unnecessary) copying.
FlexboxAxisTracker(const FlexboxAxisTracker&) = delete;
FlexboxAxisTracker& operator=(const FlexboxAxisTracker&) = delete;
const WritingMode mWM; // The flex container's writing mode.
const FlexboxAxisInfo mAxisInfo;
* Represents a flex item.
* Includes the various pieces of input that the Flexbox Layout Algorithm uses
* to resolve a flexible width.
class nsFlexContainerFrame::FlexItem final {
// Normal constructor:
FlexItem(ReflowInput& aFlexItemReflowInput, float aFlexGrow,
float aFlexShrink, nscoord aFlexBaseSize, nscoord aMainMinSize,
nscoord aMainMaxSize, nscoord aTentativeCrossSize,
nscoord aCrossMinSize, nscoord aCrossMaxSize,
const FlexboxAxisTracker& aAxisTracker);
// Simplified constructor, to be used only for generating "struts":
// (NOTE: This "strut" constructor uses the *container's* writing mode, which
// we'll use on this FlexItem instead of the child frame's real writing mode.
// This is fine - it doesn't matter what writing mode we use for a
// strut, since it won't render any content and we already know its size.)
FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize, WritingMode aContainerWM,
const FlexboxAxisTracker& aAxisTracker);
// Clone existing FlexItem for its underlying frame's continuation.
// @param aContinuation a continuation in our next-in-flow chain.
FlexItem CloneFor(nsIFrame* const aContinuation) const {
MOZ_ASSERT(Frame() == aContinuation->FirstInFlow(),
"aContinuation should be in aItem's continuation chain!");
FlexItem item(*this);
item.mFrame = aContinuation;
item.mHadMeasuringReflow = false;
return item;
// Accessors
nsIFrame* Frame() const { return mFrame; }
nscoord FlexBaseSize() const { return mFlexBaseSize; }
nscoord MainMinSize() const {
"Someone's using an unresolved 'auto' main min-size");
return mMainMinSize;
nscoord MainMaxSize() const { return mMainMaxSize; }
// Note: These return the main-axis position and size of our *content box*.
nscoord MainSize() const { return mMainSize; }
nscoord MainPosition() const { return mMainPosn; }
nscoord CrossMinSize() const { return mCrossMinSize; }
nscoord CrossMaxSize() const { return mCrossMaxSize; }
// Note: These return the cross-axis position and size of our *content box*.
nscoord CrossSize() const { return mCrossSize; }
nscoord CrossPosition() const { return mCrossPosn; }
// Lazy getter for mAscent or mAscentForLast.
nscoord ResolvedAscent(bool aUseFirstBaseline) const {
// XXX We should be using the *container's* writing-mode (mCBWM) here,
// instead of the item's (mWM). This is essentially bug 1155322.
nscoord& ascent = aUseFirstBaseline ? mAscent : mAscentForLast;
if (ascent != ReflowOutput::ASK_FOR_BASELINE) {
return ascent;
// Use GetFirstLineBaseline() or GetLastLineBaseline() as appropriate:
bool found = aUseFirstBaseline
? nsLayoutUtils::GetFirstLineBaseline(mWM, mFrame, &ascent)
: nsLayoutUtils::GetLastLineBaseline(mWM, mFrame, &ascent);
if (found) {
return ascent;
// If the nsLayoutUtils getter fails, then ask the frame directly:
auto baselineGroup = aUseFirstBaseline ? BaselineSharingGroup::First
: BaselineSharingGroup::Last;
if (auto baseline = mFrame->GetNaturalBaselineBOffset(
mWM, baselineGroup, BaselineExportContext::Other)) {
// Offset for last baseline from `GetNaturalBaselineBOffset` originates
// from the frame's block end, so convert it back.
ascent = baselineGroup == BaselineSharingGroup::First
? *baseline
: mFrame->BSize(mWM) - *baseline;
return ascent;
// We couldn't determine a baseline, so we synthesize one from border box:
ascent = Baseline::SynthesizeBOffsetFromBorderBox(
mFrame, mWM, BaselineSharingGroup::First);
return ascent;
// Convenience methods to compute the main & cross size of our *margin-box*.
nscoord OuterMainSize() const {
return mMainSize + MarginBorderPaddingSizeInMainAxis();
nscoord OuterCrossSize() const {
return mCrossSize + MarginBorderPaddingSizeInCrossAxis();
// Convenience method to return the content-box block-size.
nscoord BSize() const {
return IsBlockAxisMainAxis() ? MainSize() : CrossSize();
// Convenience method to return the measured content-box block-size computed
// in nsFlexContainerFrame::MeasureBSizeForFlexItem().
Maybe<nscoord> MeasuredBSize() const;
// Convenience methods to synthesize a style main size or a style cross size
// with box-size considered, to provide the size overrides when constructing
// ReflowInput for flex items.
StyleSize StyleMainSize() const {
nscoord mainSize = MainSize();
if (Frame()->StylePosition()->mBoxSizing == StyleBoxSizing::Border) {
mainSize += BorderPaddingSizeInMainAxis();
return StyleSize::LengthPercentage(
StyleSize StyleCrossSize() const {
nscoord crossSize = CrossSize();
if (Frame()->StylePosition()->mBoxSizing == StyleBoxSizing::Border) {
crossSize += BorderPaddingSizeInCrossAxis();
return StyleSize::LengthPercentage(
// Returns the distance between this FlexItem's baseline and the cross-start
// edge of its margin-box. Used in baseline alignment.
// (This function needs to be told which physical start side we're measuring
// the baseline from, so that it can look up the appropriate components from
// margin.)
nscoord BaselineOffsetFromOuterCrossEdge(mozilla::Side aStartSide,
bool aUseFirstLineBaseline) const;
double ShareOfWeightSoFar() const { return mShareOfWeightSoFar; }
bool IsFrozen() const { return mIsFrozen; }
bool HadMinViolation() const {
MOZ_ASSERT(!mIsFrozen, "min violation has no meaning for frozen items.");
return mHadMinViolation;
bool HadMaxViolation() const {
MOZ_ASSERT(!mIsFrozen, "max violation has no meaning for frozen items.");
return mHadMaxViolation;
bool WasMinClamped() const {
MOZ_ASSERT(mIsFrozen, "min clamping has no meaning for unfrozen items.");
return mHadMinViolation;
bool WasMaxClamped() const {
MOZ_ASSERT(mIsFrozen, "max clamping has no meaning for unfrozen items.");
return mHadMaxViolation;
// Indicates whether this item received a preliminary "measuring" reflow
// before its actual reflow.
bool HadMeasuringReflow() const { return mHadMeasuringReflow; }
// Indicates whether this item's computed cross-size property is 'auto'.
bool IsCrossSizeAuto() const;
// Indicates whether the cross-size property is set to something definite,
// for the purpose of preferred aspect ratio calculations.
bool IsCrossSizeDefinite(const ReflowInput& aItemReflowInput) const;
// Indicates whether this item's cross-size has been stretched (from having
// "align-self: stretch" with an auto cross-size and no auto margins in the
// cross axis).
bool IsStretched() const { return mIsStretched; }
bool IsFlexBaseSizeContentBSize() const {
return mIsFlexBaseSizeContentBSize;
bool IsMainMinSizeContentBSize() const { return mIsMainMinSizeContentBSize; }
// Indicates whether we need to resolve an 'auto' value for the main-axis
// min-[width|height] property.
bool NeedsMinSizeAutoResolution() const {
return mNeedsMinSizeAutoResolution;
bool HasAnyAutoMargin() const { return mHasAnyAutoMargin; }
BaselineSharingGroup ItemBaselineSharingGroup() const {
MOZ_ASSERT(mAlignSelf._0 == StyleAlignFlags::BASELINE ||
mAlignSelf._0 == StyleAlignFlags::LAST_BASELINE,
"mBaselineSharingGroup only gets a meaningful value "
"for baseline-aligned items");
return mBaselineSharingGroup;
// Indicates whether this item is a "strut" left behind by an element with
// visibility:collapse.
bool IsStrut() const { return mIsStrut; }
// The main axis and cross axis are relative to mCBWM.
LogicalAxis MainAxis() const { return mMainAxis; }
LogicalAxis CrossAxis() const { return GetOrthogonalAxis(mMainAxis); }
// IsInlineAxisMainAxis() returns true if this item's inline axis is parallel
// (or antiparallel) to the container's main axis. Otherwise (i.e. if this
// item's inline axis is orthogonal to the container's main axis), this
// function returns false. The next 3 methods are all other ways of asking
// the same question, and only exist for readability at callsites (depending
// on which axes those callsites are reasoning about).
bool IsInlineAxisMainAxis() const { return mIsInlineAxisMainAxis; }
bool IsInlineAxisCrossAxis() const { return !mIsInlineAxisMainAxis; }
bool IsBlockAxisMainAxis() const { return !mIsInlineAxisMainAxis; }
bool IsBlockAxisCrossAxis() const { return mIsInlineAxisMainAxis; }
WritingMode GetWritingMode() const { return mWM; }
WritingMode ContainingBlockWM() const { return mCBWM; }
StyleAlignSelf AlignSelf() const { return mAlignSelf; }
StyleAlignFlags AlignSelfFlags() const { return mAlignSelfFlags; }
// Returns the flex factor (flex-grow or flex-shrink), depending on
// 'aIsUsingFlexGrow'.
// Asserts fatally if called on a frozen item (since frozen items are not
// flexible).
float GetFlexFactor(bool aIsUsingFlexGrow) {
MOZ_ASSERT(!IsFrozen(), "shouldn't need flex factor after item is frozen");
return aIsUsingFlexGrow ? mFlexGrow : mFlexShrink;
// Returns the weight that we should use in the "resolving flexible lengths"
// algorithm. If we're using the flex grow factor, we just return that;
// otherwise, we return the "scaled flex shrink factor" (scaled by our flex
// base size, so that when both large and small items are shrinking, the large
// items shrink more).
// I'm calling this a "weight" instead of a "[scaled] flex-[grow|shrink]
// factor", to more clearly distinguish it from the actual flex-grow &
// flex-shrink factors.
// Asserts fatally if called on a frozen item (since frozen items are not
// flexible).
float GetWeight(bool aIsUsingFlexGrow) {
MOZ_ASSERT(!IsFrozen(), "shouldn't need weight after item is frozen");
if (aIsUsingFlexGrow) {
return mFlexGrow;
// We're using flex-shrink --> return mFlexShrink * mFlexBaseSize
if (mFlexBaseSize == 0) {
// Special-case for mFlexBaseSize == 0 -- we have no room to shrink, so
// regardless of mFlexShrink, we should just return 0.
// (This is really a special-case for when mFlexShrink is infinity, to
// avoid performing mFlexShrink * mFlexBaseSize = inf * 0 = undefined.)
return 0.0f;
return mFlexShrink * mFlexBaseSize;
bool TreatBSizeAsIndefinite() const { return mTreatBSizeAsIndefinite; }
const AspectRatio& GetAspectRatio() const { return mAspectRatio; }
bool HasAspectRatio() const { return !!mAspectRatio; }
// Getters for margin:
// ===================
LogicalMargin Margin() const { return mMargin; }
nsMargin PhysicalMargin() const { return mMargin.GetPhysicalMargin(mCBWM); }
// Returns the margin component for a given LogicalSide in flex container's
// writing-mode.
nscoord GetMarginComponentForSide(LogicalSide aSide) const {
return mMargin.Side(aSide, mCBWM);
// Returns the total space occupied by this item's margins in the given axis
nscoord MarginSizeInMainAxis() const {
return mMargin.StartEnd(MainAxis(), mCBWM);
nscoord MarginSizeInCrossAxis() const {
return mMargin.StartEnd(CrossAxis(), mCBWM);
// Getters for border/padding
// ==========================
// Returns the total space occupied by this item's borders and padding in
// the given axis
LogicalMargin BorderPadding() const { return mBorderPadding; }
nscoord BorderPaddingSizeInMainAxis() const {
return mBorderPadding.StartEnd(MainAxis(), mCBWM);
nscoord BorderPaddingSizeInCrossAxis() const {
return mBorderPadding.StartEnd(CrossAxis(), mCBWM);
// Getter for combined margin/border/padding
// =========================================
// Returns the total space occupied by this item's margins, borders and
// padding in the given axis
nscoord MarginBorderPaddingSizeInMainAxis() const {
return MarginSizeInMainAxis() + BorderPaddingSizeInMainAxis();
nscoord MarginBorderPaddingSizeInCrossAxis() const {
return MarginSizeInCrossAxis() + BorderPaddingSizeInCrossAxis();
// Setters
// =======
// Helper to set the resolved value of min-[width|height]:auto for the main
// axis. (Should only be used if NeedsMinSizeAutoResolution() returns true.)
void UpdateMainMinSize(nscoord aNewMinSize) {
NS_ASSERTION(aNewMinSize >= 0,
"How did we end up with a negative min-size?");
mMainMaxSize == NS_UNCONSTRAINEDSIZE || mMainMaxSize >= aNewMinSize,
"Should only use this function for resolving min-size:auto, "
"and main max-size should be an upper-bound for resolved val");
mNeedsMinSizeAutoResolution &&
(mMainMinSize == 0 || mFrame->IsThemed(mFrame->StyleDisplay())),
"Should only use this function for resolving min-size:auto, "
"so we shouldn't already have a nonzero min-size established "
"(unless it's a themed-widget-imposed minimum size)");
if (aNewMinSize > mMainMinSize) {
mMainMinSize = aNewMinSize;
// Also clamp main-size to be >= new min-size:
mMainSize = std::max(mMainSize, aNewMinSize);
mNeedsMinSizeAutoResolution = false;
// This sets our flex base size, and then sets our main size to the
// resulting "hypothetical main size" (the base size clamped to our
// main-axis [min,max] sizing constraints).
void SetFlexBaseSizeAndMainSize(nscoord aNewFlexBaseSize) {
"flex base size shouldn't change after we're frozen "
"(unless we're just resolving an intrinsic size)");
mFlexBaseSize = aNewFlexBaseSize;
// Before we've resolved flexible lengths, we keep mMainSize set to
// the 'hypothetical main size', which is the flex base size, clamped
// to the [min,max] range:
mMainSize = NS_CSS_MINMAX(mFlexBaseSize, mMainMinSize, mMainMaxSize);
FLEX_ITEM_LOG(mFrame, "Set flex base size: %d, hypothetical main size: %d",
mFlexBaseSize, mMainSize);
// Setters used while we're resolving flexible lengths
// ---------------------------------------------------
// Sets the main-size of our flex item's content-box.
void SetMainSize(nscoord aNewMainSize) {
MOZ_ASSERT(!mIsFrozen, "main size shouldn't change after we're frozen");
mMainSize = aNewMainSize;
void SetShareOfWeightSoFar(double aNewShare) {
MOZ_ASSERT(!mIsFrozen || aNewShare == 0.0,
"shouldn't be giving this item any share of the weight "
"after it's frozen");
mShareOfWeightSoFar = aNewShare;
void Freeze() {
mIsFrozen = true;
// Now that we are frozen, the meaning of mHadMinViolation and
// mHadMaxViolation changes to indicate min and max clamping. Clear
// both of the member variables so that they are ready to be set
// as clamping state later, if necessary.
mHadMinViolation = false;
mHadMaxViolation = false;
void SetHadMinViolation() {
"shouldn't be changing main size & having violations "
"after we're frozen");
mHadMinViolation = true;
void SetHadMaxViolation() {
"shouldn't be changing main size & having violations "
"after we're frozen");
mHadMaxViolation = true;
void ClearViolationFlags() {
"shouldn't be altering violation flags after we're "
mHadMinViolation = mHadMaxViolation = false;
void SetWasMinClamped() {
MOZ_ASSERT(!mHadMinViolation && !mHadMaxViolation, "only clamp once");
// This reuses the mHadMinViolation member variable to track clamping
// events. This is allowable because mHadMinViolation only reflects
// a violation up until the item is frozen.
MOZ_ASSERT(mIsFrozen, "shouldn't set clamping state when we are unfrozen");
mHadMinViolation = true;
void SetWasMaxClamped() {
MOZ_ASSERT(!mHadMinViolation && !mHadMaxViolation, "only clamp once");
// This reuses the mHadMaxViolation member variable to track clamping
// events. This is allowable because mHadMaxViolation only reflects
// a violation up until the item is frozen.
MOZ_ASSERT(mIsFrozen, "shouldn't set clamping state when we are unfrozen");
mHadMaxViolation = true;
// Setters for values that are determined after we've resolved our main size
// -------------------------------------------------------------------------
// Sets the main-axis position of our flex item's content-box.
// (This is the distance between the main-start edge of the flex container
// and the main-start edge of the flex item's content-box.)
void SetMainPosition(nscoord aPosn) {
MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
mMainPosn = aPosn;
// Sets the cross-size of our flex item's content-box.
void SetCrossSize(nscoord aCrossSize) {
"Cross size shouldn't be modified after it's been stretched");
mCrossSize = aCrossSize;
// Sets the cross-axis position of our flex item's content-box.
// (This is the distance between the cross-start edge of the flex container
// and the cross-start edge of the flex item.)
void SetCrossPosition(nscoord aPosn) {
MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
mCrossPosn = aPosn;
// After a FlexItem has had a reflow, this method can be used to cache its
// (possibly-unresolved) ascent, in case it's needed later for
// baseline-alignment or to establish the container's baseline.
// (NOTE: This can be marked 'const' even though it's modifying mAscent,
// because mAscent is mutable. It's nice for this to be 'const', because it
// means our final reflow can iterate over const FlexItem pointers, and we
// can be sure it's not modifying those FlexItems, except via this method.)
void SetAscent(nscoord aAscent) const {
mAscent = aAscent; // NOTE: this may be ASK_FOR_BASELINE
void SetHadMeasuringReflow() { mHadMeasuringReflow = true; }
void SetIsStretched() {
MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
mIsStretched = true;
void SetIsFlexBaseSizeContentBSize() { mIsFlexBaseSizeContentBSize = true; }
void SetIsMainMinSizeContentBSize() { mIsMainMinSizeContentBSize = true; }
// Setter for margin components (for resolving "auto" margins)
void SetMarginComponentForSide(LogicalSide aSide, nscoord aLength) {
MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
mMargin.Side(aSide, mCBWM) = aLength;
void ResolveStretchedCrossSize(nscoord aLineCrossSize);
// Resolves flex base size if flex-basis' used value is 'content', using this
// item's preferred aspect ratio and cross size.
void ResolveFlexBaseSizeFromAspectRatio(const ReflowInput& aItemReflowInput);
uint32_t NumAutoMarginsInMainAxis() const {
return NumAutoMarginsInAxis(MainAxis());
uint32_t NumAutoMarginsInCrossAxis() const {
return NumAutoMarginsInAxis(CrossAxis());
// Once the main size has been resolved, should we bother doing layout to
// establish the cross size?
bool CanMainSizeInfluenceCrossSize() const;
// Returns a main size, clamped by any definite min and max cross size
// converted through the preferred aspect ratio. The caller is responsible for
// ensuring that the flex item's preferred aspect ratio is not zero.
nscoord ClampMainSizeViaCrossAxisConstraints(
nscoord aMainSize, const ReflowInput& aItemReflowInput) const;
// Indicates whether we think this flex item needs a "final" reflow
// (after its final flexed size & final position have been determined).
// @param aParentReflowInput the flex container's reflow input.
// @return true if such a reflow is needed, or false if we believe it can
// simply be moved to its final position and skip the reflow.
bool NeedsFinalReflow(const ReflowInput& aParentReflowInput) const;
// Gets the block frame that contains the flex item's content. This is
// Frame() itself or one of its descendants.
nsBlockFrame* BlockFrame() const;
bool IsMinSizeAutoResolutionNeeded() const;
uint32_t NumAutoMarginsInAxis(LogicalAxis aAxis) const;
// Values that we already know in constructor, and remain unchanged:
// The flex item's frame.
nsIFrame* mFrame = nullptr;
float mFlexGrow = 0.0f;
float mFlexShrink = 0.0f;
AspectRatio mAspectRatio;
// The flex item's writing mode.
WritingMode mWM;
// The flex container's writing mode.
WritingMode mCBWM;
// The flex container's main axis in flex container's writing mode.
LogicalAxis mMainAxis;
// Stored in flex container's writing mode.
LogicalMargin mBorderPadding;
// Stored in flex container's writing mode. Its value can change when we
// resolve "auto" marigns.
LogicalMargin mMargin;
// These are non-const so that we can lazily update them with the item's
// intrinsic size (obtained via a "measuring" reflow), when necessary.
// (e.g. for "flex-basis:auto;height:auto" & "min-height:auto")
nscoord mFlexBaseSize = 0;
nscoord mMainMinSize = 0;
nscoord mMainMaxSize = 0;
// mCrossMinSize and mCrossMaxSize are not changed after constructor.
nscoord mCrossMinSize = 0;
nscoord mCrossMaxSize = 0;
// Values that we compute after constructor:
nscoord mMainSize = 0;
nscoord mMainPosn = 0;
nscoord mCrossSize = 0;
nscoord mCrossPosn = 0;
// Mutable b/c it's set & resolved lazily, sometimes via const pointer. See
// comment above SetAscent().
// We initialize this to ASK_FOR_BASELINE, and opportunistically fill it in
// with a real value if we end up reflowing this flex item. (But if we don't
// reflow this flex item, then this sentinel tells us that we don't know it
// yet & anyone who cares will need to explicitly request it.)
// Both mAscent and mAscentForLast are distance from the frame's border-box
// block-start edge.
mutable nscoord mAscent = ReflowOutput::ASK_FOR_BASELINE;
mutable nscoord mAscentForLast = ReflowOutput::ASK_FOR_BASELINE;
// Temporary state, while we're resolving flexible widths (for our main size)
// XXXdholbert To save space, we could use a union to make these variables
// overlay the same memory as some other member vars that aren't touched
// until after main-size has been resolved. In particular, these could share
// memory with mMainPosn through mAscent, and mIsStretched.
double mShareOfWeightSoFar = 0.0;
bool mIsFrozen = false;
bool mHadMinViolation = false;
bool mHadMaxViolation = false;
// Did this item get a preliminary reflow, to measure its desired height?
bool mHadMeasuringReflow = false;
// See IsStretched() documentation.
bool mIsStretched = false;
// Is this item a "strut" left behind by an element with visibility:collapse?
bool mIsStrut = false;
// See IsInlineAxisMainAxis() documentation. This is not changed after
// constructor.
bool mIsInlineAxisMainAxis = true;
// Does this item need to resolve a min-[width|height]:auto (in main-axis)?
// Note: mNeedsMinSizeAutoResolution needs to be declared towards the end of
// the member variables since it's initialized in a method that depends on
// other members declared above such as mCBWM, mMainAxis, and
// mIsInlineAxisMainAxis.
bool mNeedsMinSizeAutoResolution = false;
// Should we take care to treat this item's resolved BSize as indefinite?
bool mTreatBSizeAsIndefinite = false;
// Does this item have an auto margin in either main or cross axis?
bool mHasAnyAutoMargin = false;
// Does this item have a content-based flex base size (and is that a size in
// its block-axis)?
bool mIsFlexBaseSizeContentBSize = false;
// Does this item have a content-based resolved auto min size (and is that a
// size in its block-axis)?
bool mIsMainMinSizeContentBSize = false;
// If this item is {first,last}-baseline-aligned using 'align-self', which of
// its FlexLine's baseline sharing groups does it participate in?
BaselineSharingGroup mBaselineSharingGroup = BaselineSharingGroup::First;
// My "align-self" computed value (with "auto" swapped out for parent"s
// "align-items" value, in our constructor).
StyleAlignSelf mAlignSelf{StyleAlignFlags::AUTO};
// Flags for 'align-self' (safe/unsafe/legacy).
StyleAlignFlags mAlignSelfFlags{0};
* Represents a single flex line in a flex container.
* Manages an array of the FlexItems that are in the line.
class nsFlexContainerFrame::FlexLine final {
explicit FlexLine(nscoord aMainGapSize) : mMainGapSize(aMainGapSize) {}
nscoord SumOfGaps() const {
return NumItems() > 0 ? (NumItems() - 1) * mMainGapSize : 0;
// Returns the sum of our FlexItems' outer hypothetical main sizes plus the
// sum of main axis {row,column}-gaps between items.
// ("outer" = margin-box, and "hypothetical" = before flexing)
AuCoord64 TotalOuterHypotheticalMainSize() const {
return mTotalOuterHypotheticalMainSize;
// Accessors for our FlexItems & information about them:
// Note: Callers must use IsEmpty() to ensure that the FlexLine is non-empty
// before calling accessors that return FlexItem.
FlexItem& FirstItem() { return mItems[0]; }
const FlexItem& FirstItem() const { return mItems[0]; }
FlexItem& LastItem() { return mItems.LastElement(); }
const FlexItem& LastItem() const { return mItems.LastElement(); }
// The "startmost"/"endmost" is from the perspective of the flex container's
// writing-mode, not from the perspective of the flex-relative main axis.
const FlexItem& StartmostItem(const FlexboxAxisTracker& aAxisTracker) const {
return aAxisTracker.IsMainAxisReversed() ? LastItem() : FirstItem();
const FlexItem& EndmostItem(const FlexboxAxisTracker& aAxisTracker) const {
return aAxisTracker.IsMainAxisReversed() ? FirstItem() : LastItem();
bool IsEmpty() const { return mItems.IsEmpty(); }
uint32_t NumItems() const { return mItems.Length(); }
nsTArray<FlexItem>& Items() { return mItems; }
const nsTArray<FlexItem>& Items() const { return mItems; }
// Adds the last flex item's hypothetical outer main-size and
// margin/border/padding to our totals. This should be called exactly once for
// each flex item, after we've determined that this line is the correct home
// for that item.
void AddLastItemToMainSizeTotals() {
const FlexItem& lastItem = Items().LastElement();
// Update our various bookkeeping member-vars:
if (lastItem.IsFrozen()) {
mTotalItemMBP += lastItem.MarginBorderPaddingSizeInMainAxis();
mTotalOuterHypotheticalMainSize += lastItem.OuterMainSize();
// If the item added was not the first item in the line, we add in any gap
// space as needed.
if (NumItems() >= 2) {
mTotalOuterHypotheticalMainSize += mMainGapSize;
// Computes the cross-size and baseline position of this FlexLine, based on
// its FlexItems.
void ComputeCrossSizeAndBaseline(const FlexboxAxisTracker& aAxisTracker);
// Returns the cross-size of this line.
nscoord LineCrossSize() const { return mLineCrossSize; }
// Setter for line cross-size -- needed for cases where the flex container
// imposes a cross-size on the line. (e.g. for single-line flexbox, or for
// multi-line flexbox with 'align-content: stretch')
void SetLineCrossSize(nscoord aLineCrossSize) {
mLineCrossSize = aLineCrossSize;
* Returns the offset within this line where any baseline-aligned FlexItems
* should place their baseline. The return value represents a distance from
* the line's cross-start edge.
* If there are no baseline-aligned FlexItems, returns nscoord_MIN.
nscoord FirstBaselineOffset() const { return mFirstBaselineOffset; }
* Returns the offset within this line where any last baseline-aligned
* FlexItems should place their baseline. Opposite the case of the first
* baseline offset, this represents a distance from the line's cross-end
* edge (since last baseline-aligned items are flush to the cross-end edge).
* If there are no last baseline-aligned FlexItems, returns nscoord_MIN.
nscoord LastBaselineOffset() const { return mLastBaselineOffset; }
// Extract a baseline from this line, which would be suitable for use as the
// flex container's 'aBaselineGroup' (i.e. first/last) baseline.
// The return value always represents a distance from the line's cross-start
// edge, even if we are querying last baseline. If this line has no flex items
// in its aBaselineGroup group, this method falls back to trying the opposite
// group. If this line has no baseline-aligned items at all, this returns
// nscoord_MIN.
nscoord ExtractBaselineOffset(BaselineSharingGroup aBaselineGroup) const;
* Returns the gap size in the main axis for this line. Used for gap
* calculations.
nscoord MainGapSize() const { return mMainGapSize; }
// Runs the "Resolving Flexible Lengths" algorithm from section 9.7 of the
// CSS flexbox spec to distribute aFlexContainerMainSize among our flex items.
void ResolveFlexibleLengths(nscoord aFlexContainerMainSize,
ComputedFlexLineInfo* aLineInfo);
void PositionItemsInMainAxis(const StyleContentDistribution& aJustifyContent,
nscoord aContentBoxMainSize,
const FlexboxAxisTracker& aAxisTracker);
void PositionItemsInCrossAxis(nscoord aLineStartPosition,
const FlexboxAxisTracker& aAxisTracker);
// Helpers for ResolveFlexibleLengths():
void FreezeItemsEarly(bool aIsUsingFlexGrow, ComputedFlexLineInfo* aLineInfo);
void FreezeOrRestoreEachFlexibleSize(const nscoord aTotalViolation,
bool aIsFinalIteration);
// Stores this line's flex items.
nsTArray<FlexItem> mItems;
// Number of *frozen* FlexItems in this line, based on FlexItem::IsFrozen().
// Mostly used for optimization purposes, e.g. to bail out early from loops
// when we can tell they have nothing left to do.
uint32_t mNumFrozenItems = 0;
// Sum of margin/border/padding for the FlexItems in this FlexLine.
nscoord mTotalItemMBP = 0;
// Sum of FlexItems' outer hypothetical main sizes and all main-axis
// {row,columnm}-gaps between items.
// (i.e. their flex base sizes, clamped via their min/max-size properties,
// plus their main-axis margin/border/padding, plus the sum of the gaps.)
// This variable uses a 64-bit coord type to avoid integer overflow in case
// several of the individual items have huge hypothetical main sizes, which
// can happen with percent-width table-layout:fixed descendants. We have to
// avoid integer overflow in order to shrink items properly in that scenario.
AuCoord64 mTotalOuterHypotheticalMainSize = 0;
nscoord mLineCrossSize = 0;
nscoord mFirstBaselineOffset = nscoord_MIN;
nscoord mLastBaselineOffset = nscoord_MIN;
// Maintain size of each {row,column}-gap in the main axis
const nscoord mMainGapSize;
// The "startmost"/"endmost" is from the perspective of the flex container's
// writing-mode, not from the perspective of the flex-relative cross axis.
const FlexLine& StartmostLine(const nsTArray<FlexLine>& aLines,
const FlexboxAxisTracker& aAxisTracker) {
return aAxisTracker.IsCrossAxisReversed() ? aLines.LastElement() : aLines[0];
const FlexLine& EndmostLine(const nsTArray<FlexLine>& aLines,
const FlexboxAxisTracker& aAxisTracker) {
return aAxisTracker.IsCrossAxisReversed() ? aLines[0] : aLines.LastElement();
// Information about a strut left behind by a FlexItem that's been collapsed
// using "visibility:collapse".
struct nsFlexContainerFrame::StrutInfo {
StrutInfo(uint32_t aItemIdx, nscoord aStrutCrossSize)
: mItemIdx(aItemIdx), mStrutCrossSize(aStrutCrossSize) {}
uint32_t mItemIdx; // Index in the child list.
nscoord mStrutCrossSize; // The cross-size of this strut.
// Flex data shared by the flex container frames in a continuation chain, owned
// by the first-in-flow. The data is initialized at the end of the
// first-in-flow's Reflow().
struct nsFlexContainerFrame::SharedFlexData final {
// The flex lines generated in DoFlexLayout() by our first-in-flow.
nsTArray<FlexLine> mLines;
// The final content main/cross size computed by DoFlexLayout.
nscoord mContentBoxMainSize = NS_UNCONSTRAINEDSIZE;
nscoord mContentBoxCrossSize = NS_UNCONSTRAINEDSIZE;
// Update this struct. Called by the first-in-flow.
void Update(FlexLayoutResult&& aFlr) {
mLines = std::move(aFlr.mLines);
mContentBoxMainSize = aFlr.mContentBoxMainSize;
mContentBoxCrossSize = aFlr.mContentBoxCrossSize;
// The frame property under which this struct is stored. Set only on the
// first-in-flow.
// Flex data stored in every flex container's in-flow fragment (continuation).
// It's intended to prevent quadratic operations resulting from each fragment
// having to walk its full prev-in-flow chain, and also serves as an argument to
// the flex container next-in-flow's ReflowChildren(), to compute the position
// offset for each flex item.
struct nsFlexContainerFrame::PerFragmentFlexData final {
// Suppose D is the distance from a flex container fragment's content-box
// block-start edge to whichever is larger of either (a) the block-end edge of
// its children, or (b) the available space's block-end edge. (Note: in case
// (b), D is conceptually the sum of the block-size of the children, the
// packing space before & in between them, and part of the packing space after
// them.)
// This variable stores the sum of the D values for the current flex container
// fragments and for all its previous fragments
nscoord mCumulativeContentBoxBSize = 0;
// This variable accumulates FirstLineOrFirstItemBAxisMetrics::mBEndEdgeShift,
// for the current flex container fragment and for all its previous fragments.
// See the comment of mBEndEdgeShift for its computation details. In short,
// this value is the net block-end edge shift, accumulated for the children in
// all the previous fragments. This number is non-negative.
// This value is also used to grow a flex container's block-size if the
// container's computed block-size is unconstrained. For example: a tall item
// may be pushed to the next page/column, which leaves some wasted area at the
// bottom of the current flex container fragment, and causes the flex
// container fragments to be (collectively) larger than the hypothetical
// unfragmented size. Another example: a tall flex item may be broken into
// multiple fragments, and those fragments may have a larger collective
// block-size as compared to the item's original unfragmented size; the
// container would need to increase its block-size to account for this.
nscoord mCumulativeBEndEdgeShift = 0;
// The frame property under which this struct is stored. Cached on every
// in-flow fragment (continuation) at the end of the flex container's
// Reflow().
static void BuildStrutInfoFromCollapsedItems(const nsTArray<FlexLine>& aLines,
nsTArray<StrutInfo>& aStruts) {
"We should only build up StrutInfo once per reflow, so "
"aStruts should be empty when this is called");
uint32_t itemIdxInContainer = 0;
for (const FlexLine& line : aLines) {
for (const FlexItem& item : line.Items()) {
if (item.Frame()->StyleVisibility()->IsCollapse()) {
// Note the cross size of the line as the item's strut size.
StrutInfo(itemIdxInContainer, line.LineCrossSize()));
static mozilla::StyleAlignFlags SimplifyAlignOrJustifyContentForOneItem(
const StyleContentDistribution& aAlignmentVal, bool aIsAlign) {
// Mask away any explicit fallback, to get the main (non-fallback) part of
// the specified value:
StyleAlignFlags specified = aAlignmentVal.primary;
// XXX strip off <overflow-position> bits until we implement it (bug 1311892)
specified &= ~StyleAlignFlags::FLAG_BITS;
// FIRST: handle a special-case for "justify-content:stretch" (or equivalent),
// which requires that we ignore any author-provided explicit fallback value.
if (specified == StyleAlignFlags::NORMAL) {
// In a flex container, *-content: "'normal' behaves as 'stretch'".
// Do that conversion early, so it benefits from our 'stretch' special-case.
specified = StyleAlignFlags::STRETCH;
if (!aIsAlign && specified == StyleAlignFlags::STRETCH) {
// In a flex container, in "justify-content Axis: [...] 'stretch' behaves
// as 'flex-start' (ignoring the specified fallback alignment, if any)."
// So, we just directly return 'flex-start', & ignore explicit fallback..
return StyleAlignFlags::FLEX_START;
// TODO: Check for an explicit fallback value (and if it's present, use it)
// here once we parse it, see
// If there's no explicit fallback, use the implied fallback values for
// space-{between,around,evenly} (since those values only make sense with
// multiple alignment subjects), and otherwise just use the specified value:
if (specified == StyleAlignFlags::SPACE_BETWEEN) {
return StyleAlignFlags::FLEX_START;
if (specified == StyleAlignFlags::SPACE_AROUND ||
specified == StyleAlignFlags::SPACE_EVENLY) {
return StyleAlignFlags::CENTER;
return specified;
bool nsFlexContainerFrame::DrainSelfOverflowList() {
return DrainAndMergeSelfOverflowList();
void nsFlexContainerFrame::AppendFrames(ChildListID aListID,
nsFrameList&& aFrameList) {
NoteNewChildren(aListID, aFrameList);
nsContainerFrame::AppendFrames(aListID, std::move(aFrameList));
void nsFlexContainerFrame::InsertFrames(
ChildListID aListID, nsIFrame* aPrevFrame,
const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) {
NoteNewChildren(aListID, aFrameList);
nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
void nsFlexContainerFrame::RemoveFrame(DestroyContext& aContext,
ChildListID aListID,
nsIFrame* aOldFrame) {
MOZ_ASSERT(aListID == FrameChildListID::Principal, "unexpected child list");
#ifdef DEBUG
SetDidPushItemsBitIfNeeded(aListID, aOldFrame);
nsContainerFrame::RemoveFrame(aContext, aListID, aOldFrame);
StyleAlignFlags nsFlexContainerFrame::CSSAlignmentForAbsPosChild(
const ReflowInput& aChildRI, LogicalAxis aLogicalAxis) const {
const FlexboxAxisTracker axisTracker(this);
// If we're row-oriented and the caller is asking about our inline axis (or
// alternately, if we're column-oriented and the caller is asking about our
// block axis), then the caller is really asking about our *main* axis.
// Otherwise, the caller is asking about our cross axis.
const bool isMainAxis =
(axisTracker.IsRowOriented() == (aLogicalAxis == LogicalAxis::Inline));
const nsStylePosition* containerStylePos = StylePosition();
const bool isAxisReversed = isMainAxis ? axisTracker.IsMainAxisReversed()
: axisTracker.IsCrossAxisReversed();
StyleAlignFlags alignment{0};
StyleAlignFlags alignmentFlags{0};
if (isMainAxis) {
// We're aligning in the main axis: align according to 'justify-content'.
// (We don't care about justify-self; it has no effect on children of flex
// changes that.)
alignment = SimplifyAlignOrJustifyContentForOneItem(
/*aIsAlign = */ false);
} else {
// We're aligning in the cross axis: align according to 'align-self'.
// (We don't care about align-content; it has no effect on abspos flex
alignment = aChildRI.mStylePosition->UsedAlignSelf(Style())._0;
// Extract and strip align flag bits
alignmentFlags = alignment & StyleAlignFlags::FLAG_BITS;
alignment &= ~StyleAlignFlags::FLAG_BITS;
if (alignment == StyleAlignFlags::NORMAL) {
// "the 'normal' keyword behaves as 'start' on replaced
// absolutely-positioned boxes, and behaves as 'stretch' on all other
// absolutely-positioned boxes."
alignment = aChildRI.mFrame->IsReplaced() ? StyleAlignFlags::START
: StyleAlignFlags::STRETCH;
if (alignment == StyleAlignFlags::STRETCH) {
// The default fallback alignment for 'stretch' is 'flex-start'.
alignment = StyleAlignFlags::FLEX_START;
// Resolve flex-start, flex-end, auto, left, right, baseline, last baseline;
if (alignment == StyleAlignFlags::FLEX_START) {
alignment = isAxisReversed ? StyleAlignFlags::END : StyleAlignFlags::START;
} else if (alignment == StyleAlignFlags::FLEX_END) {
alignment = isAxisReversed ? StyleAlignFlags::START : StyleAlignFlags::END;
} else if (alignment == StyleAlignFlags::LEFT ||
alignment == StyleAlignFlags::RIGHT) {
MOZ_ASSERT(isMainAxis, "Only justify-* can have 'left' and 'right'!");
alignment = axisTracker.ResolveJustifyLeftRight(alignment);
} else if (alignment == StyleAlignFlags::BASELINE) {
alignment = StyleAlignFlags::START;
} else if (alignment == StyleAlignFlags::LAST_BASELINE) {
alignment = StyleAlignFlags::END;
MOZ_ASSERT(alignment != StyleAlignFlags::STRETCH,
"We should've converted 'stretch' to the fallback alignment!");
MOZ_ASSERT(alignment != StyleAlignFlags::FLEX_START &&
alignment != StyleAlignFlags::FLEX_END,
"nsAbsoluteContainingBlock doesn't know how to handle "
"flex-relative axis for flex containers!");
return (alignment | alignmentFlags);
void nsFlexContainerFrame::GenerateFlexItemForChild(
FlexLine& aLine, nsIFrame* aChildFrame,
const ReflowInput& aParentReflowInput,
const FlexboxAxisTracker& aAxisTracker,
const nscoord aTentativeContentBoxCrossSize) {
const auto flexWM = aAxisTracker.GetWritingMode();
const auto childWM = aChildFrame->GetWritingMode();
// Note: we use GetStyleFrame() to access the sizing & flex properties here.
// This lets us correctly handle table wrapper frames as flex items since
// their inline-size and block-size properties are always 'auto'. In order for
// 'flex-basis:auto' to actually resolve to the author's specified inline-size
// or block-size, we need to dig through to the inner table.
const auto* stylePos =
// Construct a StyleSizeOverrides for this flex item so that its ReflowInput
// below will use and resolve its flex base size rather than its corresponding
// preferred main size property (only for modern CSS flexbox).
StyleSizeOverrides sizeOverrides;
if (!IsLegacyBox(this)) {
Maybe<StyleSize> styleFlexBaseSize;
// When resolving flex base size, flex items use their 'flex-basis' property
// in place of their preferred main size (e.g. 'width') for sizing purposes,
// *unless* they have 'flex-basis:auto' in which case they use their
// preferred main size after all.
const auto& flexBasis = stylePos->mFlexBasis;
const auto& styleMainSize = stylePos->Size(aAxisTracker.MainAxis(), flexWM);
if (IsUsedFlexBasisContent(flexBasis, styleMainSize)) {
// If we get here, we're resolving the flex base size for a flex item, and
// we fall into the flexbox spec section 9.2 step 3, substep C (if we have
// a definite cross size) or E (if not).
} else if (flexBasis.IsSize() && !flexBasis.IsAuto()) {
// For all other non-'auto' flex-basis values, we just swap in the
// flex-basis itself for the preferred main-size property.
} else {
// else: flex-basis is 'auto', which is deferring to some explicit value
// in the preferred main size.
MOZ_ASSERT(styleFlexBaseSize, "We should've emplace styleFlexBaseSize!");
// Provide the size override for the preferred main size property.
if (aAxisTracker.IsInlineAxisMainAxis(childWM)) {
sizeOverrides.mStyleISize = std::move(styleFlexBaseSize);
} else {
sizeOverrides.mStyleBSize = std::move(styleFlexBaseSize);
// 'flex-basis' should works on the inner table frame for a table flex item,
// just like how 'height' works on a table element.
sizeOverrides.mApplyOverridesVerbatim = true;
// Create temporary reflow input just for sizing -- to get hypothetical
// main-size and the computed values of min / max main-size property.
// (This reflow input will _not_ be used for reflow.)
ReflowInput childRI(PresContext(), aParentReflowInput, aChildFrame,
aParentReflowInput.ComputedSize(childWM), Nothing(), {},
sizeOverrides, {ComputeSizeFlag::ShrinkWrap});
// --------------------------
float flexGrow, flexShrink;
if (IsLegacyBox(this)) {
flexGrow = flexShrink = aChildFrame->StyleXUL()->mBoxFlex;
} else {
flexGrow = stylePos->mFlexGrow;
flexShrink = stylePos->mFlexShrink;
// MAIN SIZES (flex base size, min/max size)
// -----------------------------------------
const LogicalSize computedSizeInFlexWM = childRI.ComputedSize(flexWM);
const LogicalSize computedMinSizeInFlexWM = childRI.ComputedMinSize(flexWM);
const LogicalSize computedMaxSizeInFlexWM = childRI.ComputedMaxSize(flexWM);
const nscoord flexBaseSize = aAxisTracker.MainComponent(computedSizeInFlexWM);
const nscoord mainMinSize =
const nscoord mainMaxSize =
// This is enforced by the ReflowInput where these values come from:
MOZ_ASSERT(mainMinSize <= mainMaxSize, "min size is larger than max size");
// CROSS SIZES (tentative cross size, min/max cross size)
// ------------------------------------------------------
// Grab the cross size from the reflow input. This might be the right value,
// or we might resolve it to something else in SizeItemInCrossAxis(); hence,
// it's tentative. See comment under "Cross Size Determination" for more.
const nscoord tentativeCrossSize =
const nscoord crossMinSize =
const nscoord crossMaxSize =
// Construct the flex item!
FlexItem& item = *aLine.Items().EmplaceBack(
childRI, flexGrow, flexShrink, flexBaseSize, mainMinSize, mainMaxSize,
tentativeCrossSize, crossMinSize, crossMaxSize, aAxisTracker);
// We may be about to do computations based on our item's cross-size
// (e.g. using it as a constraint when measuring our content in the
// main axis, or using it with the preferred aspect ratio to obtain a main
// size). BEFORE WE DO THAT, we need let the item "pre-stretch" its cross size
// (if it's got 'align-self:stretch'), for a certain case where the spec says
// the stretched cross size is considered "definite". That case is if we
// have a single-line (nowrap) flex container which itself has a definite
// cross-size. Otherwise, we'll wait to do stretching, since (in other
// cases) we don't know how much the item should stretch yet.
const bool isSingleLine =
StyleFlexWrap::Nowrap == aParentReflowInput.mStylePosition->mFlexWrap;
if (isSingleLine) {
// Is container's cross size "definite"?
// - If it's column-oriented, then "yes", because its cross size is its
// inline-size which is always definite from its descendants' perspective.
// - Otherwise (if it's row-oriented), then we check the actual size
// and call it definite if it's not NS_UNCONSTRAINEDSIZE.
if (aAxisTracker.IsColumnOriented() ||
aTentativeContentBoxCrossSize != NS_UNCONSTRAINEDSIZE) {
// Container's cross size is "definite", so we can resolve the item's
// stretched cross size using that.
// Before thinking about freezing the item at its base size, we need to give
// it a chance to recalculate the base size from its cross size and aspect
// ratio (since its cross size might've *just* now become definite due to
// 'stretch' above)
// If we're inflexible, we can just freeze to our hypothetical main-size
// up-front.
if (flexGrow == 0.0f && flexShrink == 0.0f) {
if (flexBaseSize < mainMinSize) {
} else if (flexBaseSize > mainMaxSize) {
// Resolve "flex-basis:auto" and/or "min-[width|height]:auto" (which might
// require us to reflow the item to measure content height)
ResolveAutoFlexBasisAndMinSize(item, childRI, aAxisTracker);
// Static helper-functions for ResolveAutoFlexBasisAndMinSize():
// -------------------------------------------------------------
// Partially resolves "min-[width|height]:auto" and returns the resulting value.
// By "partially", I mean we don't consider the min-content size (but we do
// consider the main-size and main max-size properties, and the preferred aspect
// ratio). The caller is responsible for computing & considering the min-content
// size in combination with the partially-resolved value that this function
// returns.
// Basically, this function gets the specified size suggestion; if not, the
// transferred size suggestion; if both sizes do not exist, return nscoord_MAX.
static nscoord PartiallyResolveAutoMinSize(
const FlexItem& aFlexItem, const ReflowInput& aItemReflowInput,
const FlexboxAxisTracker& aAxisTracker) {
"only call for FlexItems that need min-size auto resolution");
const auto itemWM = aFlexItem.GetWritingMode();
const auto cbWM = aAxisTracker.GetWritingMode();
const auto& mainStyleSize =
aItemReflowInput.mStylePosition->Size(aAxisTracker.MainAxis(), cbWM);
const auto& maxMainStyleSize =
aItemReflowInput.mStylePosition->MaxSize(aAxisTracker.MainAxis(), cbWM);
const auto boxSizingAdjust =
aItemReflowInput.mStylePosition->mBoxSizing == StyleBoxSizing::Border
? aFlexItem.BorderPadding().Size(cbWM)
: LogicalSize(cbWM);
// If this flex item is a compressible replaced element list in CSS Sizing 3
// §5.2.2, CSS Sizing 3 §5.2.1c requires us to resolve the percentage part of
// the preferred main size property against zero, yielding a definite
// specified size suggestion. Here we can use a zero percentage basis to
// fulfill this requirement.
const auto percentBasis =
? LogicalSize(cbWM, 0, 0)
: aItemReflowInput.mContainingBlockSize.ConvertTo(cbWM, itemWM);
// Compute the specified size suggestion, which is the main-size property if
// it's definite.
nscoord specifiedSizeSuggestion = nscoord_MAX;