/* -*- 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 http://mozilla.org/MPL/2.0/. */

/* 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/FloatingPoint.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 "nsCSSAnonBoxes.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;

static mozilla::LazyLogModule gFlexContainerLog("FlexContainer");
#define FLEX_LOG(...) \
  MOZ_LOG(gFlexContainerLog, LogLevel::Debug, (__VA_ARGS__));
#define FLEX_LOGV(...) \
  MOZ_LOG(gFlexContainerLog, LogLevel::Verbose, (__VA_ARGS__));

// 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) {
  MOZ_ASSERT(aFlexContainer->IsFlexContainerFrame(),
             "only flex containers may be passed to this function");
  return aFlexContainer->HasAnyStateBits(
      NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX);
}

// Returns the OrderState enum we should pass to CSSOrderAwareFrameIterator
// (depending on whether aFlexContainer has
// NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER state bit).
static CSSOrderAwareFrameIterator::OrderState OrderStateForIter(
    const nsFlexContainerFrame* aFlexContainer) {
  return aFlexContainer->HasAnyStateBits(
             NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER)
             ? 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 {
 public:
  explicit FlexboxAxisTracker(const nsFlexContainerFrame* aFlexContainer);

  // Accessors:
  LogicalAxis MainAxis() const {
    return IsRowOriented() ? eLogicalAxisInline : eLogicalAxisBlock;
  }
  LogicalAxis CrossAxis() const {
    return IsRowOriented() ? eLogicalAxisBlock : eLogicalAxisInline;
  }

  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,
                                          logicalCoordInCrossAxis)
                           : LogicalPoint(mWM, logicalCoordInCrossAxis,
                                          logicalCoordInMainAxis);
  }

  /**
   * 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 {
    MOZ_ASSERT(
        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()) == eAxisHorizontal,
                 "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() == eLogicalAxisInline,
               "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;

 private:
  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 {
 public:
  // 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 {
    MOZ_ASSERT(!mNeedsMinSizeAutoResolution,
               "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 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(
        LengthPercentage::FromAppUnits(mainSize));
  }

  StyleSize StyleCrossSize() const {
    nscoord crossSize = CrossSize();
    if (Frame()->StylePosition()->mBoxSizing == StyleBoxSizing::Border) {
      crossSize += BorderPaddingSizeInCrossAxis();
    }
    return StyleSize::LengthPercentage(
        LengthPercentage::FromAppUnits(crossSize));
  }

  // 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; }

  // 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?");
    MOZ_ASSERT(
        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");
    MOZ_ASSERT(
        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) {
    MOZ_ASSERT(!mIsFrozen || mFlexBaseSize == NS_UNCONSTRAINEDSIZE,
               "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_LOGV(
        "Set flex base size: %d, hypothetical main size: %d for flex item %p",
        mFlexBaseSize, mMainSize, mFrame);
  }

  // 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() {
    MOZ_ASSERT(!mIsFrozen,
               "shouldn't be changing main size & having violations "
               "after we're frozen");
    mHadMinViolation = true;
  }
  void SetHadMaxViolation() {
    MOZ_ASSERT(!mIsFrozen,
               "shouldn't be changing main size & having violations "
               "after we're frozen");
    mHadMaxViolation = true;
  }
  void ClearViolationFlags() {
    MOZ_ASSERT(!mIsFrozen,
               "shouldn't be altering violation flags after we're "
               "frozen");
    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) {
    MOZ_ASSERT(!mIsStretched,
               "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;
  }

  // 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;

 protected:
  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;

  // 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 {
 public:
  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()) {
      mNumFrozenItems++;
    }

    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.
  // https://drafts.csswg.org/css-flexbox-1/#flex-baselines
  //
  // 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.
  // https://drafts.csswg.org/css-flexbox-1/#resolve-flexible-lengths
  void ResolveFlexibleLengths(nscoord aFlexContainerMainSize,
                              ComputedFlexLineInfo* aLineInfo);

  void PositionItemsInMainAxis(const StyleContentDistribution& aJustifyContent,
                               nscoord aContentBoxMainSize,
                               const FlexboxAxisTracker& aAxisTracker);

  void PositionItemsInCrossAxis(nscoord aLineStartPosition,
                                const FlexboxAxisTracker& aAxisTracker);

 private:
  // 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.
  NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, SharedFlexData)
};

// 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().
  NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, PerFragmentFlexData)
};

static void BuildStrutInfoFromCollapsedItems(const nsTArray<FlexLine>& aLines,
                                             nsTArray<StrutInfo>& aStruts) {
  MOZ_ASSERT(aStruts.IsEmpty(),
             "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.
        aStruts.AppendElement(
            StrutInfo(itemIdxInContainer, line.LineCrossSize()));
      }
      itemIdxInContainer++;
    }
  }
}

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.
    // https://drafts.csswg.org/css-align-3/#distribution-flex
    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)."
    // https://drafts.csswg.org/css-align-3/#distribution-flex
    // 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 https://github.com/w3c/csswg-drafts/issues/1002.

  // 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,
                                 std::move(aFrameList));
}

void nsFlexContainerFrame::RemoveFrame(DestroyContext& aContext,
                                       ChildListID aListID,
                                       nsIFrame* aOldFrame) {
  MOZ_ASSERT(aListID == FrameChildListID::Principal, "unexpected child list");

#ifdef DEBUG
  SetDidPushItemsBitIfNeeded(aListID, aOldFrame);
#endif

  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 == eLogicalAxisInline));
  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
    // containers, unless https://github.com/w3c/csswg-drafts/issues/7644
    // changes that.)
    alignment = SimplifyAlignOrJustifyContentForOneItem(
        containerStylePos->mJustifyContent,
        /*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
    // children, per https://github.com/w3c/csswg-drafts/issues/7596 )
    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."
      // https://drafts.csswg.org/css-align/#align-abspos
      alignment = aChildRI.mFrame->IsFrameOfType(nsIFrame::eReplaced)
                      ? 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 =
      nsLayoutUtils::GetStyleFrame(aChildFrame)->StylePosition();

  // 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).
      styleFlexBaseSize.emplace(StyleSize::MaxContent());
    } 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.
      styleFlexBaseSize.emplace(flexBasis.AsSize());
    } else {
      // else: flex-basis is 'auto', which is deferring to some explicit value
      // in the preferred main size.
      MOZ_ASSERT(flexBasis.IsAuto());
      styleFlexBaseSize.emplace(styleMainSize);
    }

    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);

  // FLEX GROW & SHRINK WEIGHTS
  // --------------------------
  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 =
      aAxisTracker.MainComponent(computedMinSizeInFlexWM);
  const nscoord mainMaxSize =
      aAxisTracker.MainComponent(computedMaxSizeInFlexWM);

  // 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 =
      aAxisTracker.CrossComponent(computedSizeInFlexWM);
  const nscoord crossMinSize =
      aAxisTracker.CrossComponent(computedMinSizeInFlexWM);
  const nscoord crossMaxSize =
      aAxisTracker.CrossComponent(computedMaxSizeInFlexWM);

  // 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.
      item.ResolveStretchedCrossSize(aTentativeContentBoxCrossSize);
    }
  }

  // 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)
  item.ResolveFlexBaseSizeFromAspectRatio(childRI);

  // If we're inflexible, we can just freeze to our hypothetical main-size
  // up-front.
  if (flexGrow == 0.0f && flexShrink == 0.0f) {
    item.Freeze();
    if (flexBaseSize < mainMinSize) {
      item.SetWasMinClamped();
    } else if (flexBaseSize > mainMaxSize) {
      item.SetWasMaxClamped();
    }
  }

  // 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.
//
// Spec reference: https://drafts.csswg.org/css-flexbox-1/#min-size-auto
static nscoord PartiallyResolveAutoMinSize(
    const FlexItem& aFlexItem, const ReflowInput& aItemReflowInput,
    const FlexboxAxisTracker& aAxisTracker) {
  MOZ_ASSERT(aFlexItem.NeedsMinSizeAutoResolution(),
             "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 =
      aFlexItem.Frame()->IsPercentageResolvedAgainstZero(mainStyleSize,
                                                         maxMainStyleSize)
          ? 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;

  if (aAxisTracker.IsRowOriented()) {
    if (mainStyleSize.IsLengthPercentage()) {
      // NOTE: We ignore extremum inline-size. This is OK because the caller is
      // responsible for computing the min-content inline-size and min()'ing it
      // with the value we return.
      specifiedSizeSuggestion = aFlexItem.Frame()->ComputeISizeValue(
          cbWM, percentBasis, boxSizingAdjust,
          mainStyleSize.AsLengthPercentage());
    }
  } else {
    if (!nsLayoutUtils::IsAutoBSize(mainStyleSize, percentBasis.BSize(cbWM))) {
      // NOTE: We ignore auto and extremum block-size. This is OK because the
      // caller is responsible for computing the min-content block-size and
      // min()'ing it with the value we return.
      specifiedSizeSuggestion = nsLayoutUtils::ComputeBSizeValue(
          percentBasis.BSize(cbWM), boxSizingAdjust.BSize(cbWM),
          mainStyleSize.AsLengthPercentage());
    }
  }

  if (specifiedSizeSuggestion != nscoord_MAX) {
    // We have the specified size suggestion. Return it now since we don't need
    // to consider transferred size suggestion.
    FLEX_LOGV(" Specified size suggestion: %d", specifiedSizeSuggestion);
    return specifiedSizeSuggestion;
  }

  // Compute the transferred size suggestion, which is the cross size converted
  // through the aspect ratio (if the item is replaced, and it has an aspect
  // ratio and a definite cross size).
  if (const auto& aspectRatio = aFlexItem.GetAspectRatio();
      aFlexItem.Frame()->IsFrameOfType(nsIFrame::eReplaced) && aspectRatio &&
      aFlexItem.IsCrossSizeDefinite(aItemReflowInput)) {
    // We have a usable aspect ratio. (not going to divide by 0)
    nscoord transferredSizeSuggestion = aspectRatio.ComputeRatioDependentSize(
        aFlexItem.MainAxis(), cbWM, aFlexItem.CrossSize(), boxSizingAdjust);

    // Clamp the transferred size suggestion by any definite min and max
    // cross size converted through the aspect ratio.
    transferredSizeSuggestion = aFlexItem.ClampMainSizeViaCrossAxisConstraints(
        transferredSizeSuggestion, aItemReflowInput);

    FLEX_LOGV(" Transferred size suggestion: %d", transferredSizeSuggestion);
    return transferredSizeSuggestion;
  }

  return nscoord_MAX;
}

// Note: If & when we handle "min-height: min-content" for flex items,
// we may want to resolve that in this function, too.
void nsFlexContainerFrame::ResolveAutoFlexBasisAndMinSize(
    FlexItem& aFlexItem, const ReflowInput& aItemReflowInput,
    const FlexboxAxisTracker& aAxisTracker) {
  // (Note: We can guarantee that the flex-basis will have already been
  // resolved if the main axis is the same as the item's inline
  // axis. Inline-axis values should always be resolvable without reflow.)
  const bool isMainSizeAuto =
      (!aFlexItem.IsInlineAxisMainAxis() &&
       NS_UNCONSTRAINEDSIZE == aFlexItem.FlexBaseSize());

  const bool isMainMinSizeAuto = aFlexItem.NeedsMinSizeAutoResolution();

  if (!isMainSizeAuto && !isMainMinSizeAuto) {
    // Nothing to do; this function is only needed for flex items
    // with a used flex-basis of "auto" or a min-main-size of "auto".
    return;
  }

  FLEX_LOGV("Resolving auto main size or auto min main size for flex item %p",
            aFlexItem.Frame());

  nscoord resolvedMinSize;  // (only set/used if isMainMinSizeAuto==true)
  bool minSizeNeedsToMeasureContent = false;  // assume the best
  if (isMainMinSizeAuto) {
    // Resolve the min-size, except for considering the min-content size.
    // (We'll consider that later, if we need to.)
    resolvedMinSize =
        PartiallyResolveAutoMinSize(aFlexItem, aItemReflowInput, aAxisTracker);
    if (resolvedMinSize > 0) {
      // If resolvedMinSize were already at 0, we could skip calculating content
      // size suggestion because it can't go any lower.
      minSizeNeedsToMeasureContent = true;
    }
  }

  const bool flexBasisNeedsToMeasureContent = isMainSizeAuto;

  // Measure content, if needed (w/ intrinsic-width method or a reflow)
  if (minSizeNeedsToMeasureContent || flexBasisNeedsToMeasureContent) {
    // Compute the content size suggestion, which is the min-content size in the
    // main axis.
    nscoord contentSizeSuggestion = nscoord_MAX;

    if (aFlexItem.IsInlineAxisMainAxis()) {
      if (minSizeNeedsToMeasureContent) {
        // Compute the flex item's content size suggestion, which is the
        // 'min-content' size on the main axis.
        // https://drafts.csswg.org/css-flexbox-1/#content-size-suggestion
        const auto cbWM = aAxisTracker.GetWritingMode();
        const auto itemWM = aFlexItem.GetWritingMode();
        const nscoord availISize = 0;  // for min-content size
        StyleSizeOverrides sizeOverrides;
        sizeOverrides.mStyleISize.emplace(StyleSize::Auto());
        const auto sizeInItemWM = aFlexItem.Frame()->ComputeSize(
            aItemReflowInput.mRenderingContext, itemWM,
            aItemReflowInput.mContainingBlockSize, availISize,
            aItemReflowInput.ComputedLogicalMargin(itemWM).Size(itemWM),
            aItemReflowInput.ComputedLogicalBorderPadding(itemWM).Size(itemWM),
            sizeOverrides, {ComputeSizeFlag::ShrinkWrap});

        contentSizeSuggestion = aAxisTracker.MainComponent(
            sizeInItemWM.mLogicalSize.ConvertTo(cbWM, itemWM));
      }
      NS_ASSERTION(!flexBasisNeedsToMeasureContent,
                   "flex-basis:auto should have been resolved in the "
                   "reflow input, for horizontal flexbox. It shouldn't need "
                   "special handling here");
    } else {
      // If this item is flexible (in its block axis)...
      // OR if we're measuring its 'auto' min-BSize, with its main-size (in its
      // block axis) being something non-"auto"...
      // THEN: we assume that the computed BSize that we're reflowing with now
      // could be different from the one we'll use for this flex item's
      // "actual" reflow later on.  In that case, we need to be sure the flex
      // item treats this as a block-axis resize (regardless of whether there
      // are actually any ancestors being resized in that axis).
      // (Note: We don't have to do this for the inline axis, because
      // InitResizeFlags will always turn on mIsIResize on when it sees that
      // the computed ISize is different from current ISize, and that's all we
      // need.)
      bool forceBResizeForMeasuringReflow =
          !aFlexItem.IsFrozen() ||          // Is the item flexible?
          !flexBasisNeedsToMeasureContent;  // Are we *only* measuring it for
                                            // 'min-block-size:auto'?

      const ReflowInput& flexContainerRI = *aItemReflowInput.mParentReflowInput;
      nscoord contentBSize = MeasureFlexItemContentBSize(
          aFlexItem, forceBResizeForMeasuringReflow, flexContainerRI);
      if (minSizeNeedsToMeasureContent) {
        contentSizeSuggestion = contentBSize;
      }
      if (flexBasisNeedsToMeasureContent) {
        aFlexItem.SetFlexBaseSizeAndMainSize(contentBSize);
      }
    }

    if (minSizeNeedsToMeasureContent) {
      // Clamp the content size suggestion by any definite min and max cross
      // size converted through the aspect ratio.
      if (aFlexItem.HasAspectRatio()) {
        contentSizeSuggestion = aFlexItem.ClampMainSizeViaCrossAxisConstraints(
            contentSizeSuggestion, aItemReflowInput);
      }

      FLEX_LOGV(" Content size suggestion: %d", contentSizeSuggestion);
      resolvedMinSize = std::min(resolvedMinSize, contentSizeSuggestion);

      // Clamp the resolved min main size by the max main size if it's definite.
      if (aFlexItem.MainMaxSize() != NS_UNCONSTRAINEDSIZE) {
        resolvedMinSize = std::min(resolvedMinSize, aFlexItem.MainMaxSize());
      } else if (MOZ_UNLIKELY(resolvedMinSize > nscoord_MAX)) {
        NS_WARNING("Bogus resolved auto min main size!");
        // Our resolved min-size is bogus, probably due to some huge sizes in
        // the content. Clamp it to the valid nscoord range, so that we can at
        // least depend on it being <= the max-size (which is also the
        // nscoord_MAX sentinel value if we reach this point).
        resolvedMinSize = nscoord_MAX;
      }
      FLEX_LOGV(" Resolved auto min main size: %d", resolvedMinSize);
    }
  }

  if (isMainMinSizeAuto) {
    aFlexItem.UpdateMainMinSize(resolvedMinSize);
  }
}

/**
 * A cached result for a flex item's block-axis measuring reflow. This cache
 * prevents us from doing exponential reflows in cases of deeply nested flex
 * and scroll frames.
 *
 * We store the cached value in the flex item's frame property table, for
 * simplicity.
 *
 * Right now, we cache the following as a "key", from the item's ReflowInput:
 *   - its ComputedSize
 *   - its min/max block size (in case its ComputedBSize is unconstrained)
 *   - its AvailableBSize
 * ...and we cache the following as the "value", from the item's ReflowOutput:
 *   - its final content-box BSize
 *
 * The assumption here is that a given flex item measurement from our "value"
 * won't change unless one of the pieces of the "key" change, or the flex
 * item's intrinsic size is marked as dirty (due to a style or DOM change).
 * (The latter will cause the cached value to be discarded, in
 * nsIFrame::MarkIntrinsicISizesDirty.)
 *
 * Note that the components of "Key" (mComputed{MinB,MaxB,}Size and
 * mAvailableBSize) are sufficient to catch any changes to the flex container's
 * size that the item may care about for its measuring reflow. Specifically:
 *  - If the item cares about the container's size (e.g. if it has a percent
 *    height and the container's height changes, in a horizontal-WM container)
 *    then that'll be detectable via the item's ReflowInput's "ComputedSize()"
 *    differing from the value in our Key.  And the same applies for the
 *    inline axis.
 *  - If the item is fragmentable (pending bug 939897) and its measured BSize
 *    depends on where it gets fragmented, then that sort of change can be
 *    detected due to the item's ReflowInput's "AvailableBSize()" differing
 *    from the value in our Key.
 *
 * One particular case to consider (& need to be sure not to break when
 * changing this class): the flex item's computed BSize may change between
 * measuring reflows due to how the mIsFlexContainerMeasuringBSize flag affects
 * size computation (see bug 1336708). This is one reason we need to use the
 * computed BSize as part of the key.
 */
class nsFlexContainerFrame::CachedBAxisMeasurement {
  struct Key {
    const LogicalSize mComputedSize;
    const nscoord mComputedMinBSize;
    const nscoord mComputedMaxBSize;
    const nscoord mAvailableBSize;

    explicit Key(const ReflowInput& aRI)
        : mComputedSize(aRI.ComputedSize()),
          mComputedMinBSize(aRI.ComputedMinBSize()),
          mComputedMaxBSize(aRI.ComputedMaxBSize()),
          mAvailableBSize(aRI.AvailableBSize()) {}

    bool operator==(const Key& aOther) const {
      return mComputedSize == aOther.mComputedSize &&
             mComputedMinBSize == aOther.mComputedMinBSize &&
             mComputedMaxBSize == aOther.mComputedMaxBSize &&
             mAvailableBSize == aOther.mAvailableBSize;
    }
  };

  const Key mKey;

  // This could/should be const, but it's non-const for now just because it's
  // assigned via a series of steps in the constructor body:
  nscoord mBSize;

 public:
  CachedBAxisMeasurement(const ReflowInput& aReflowInput,
                         const ReflowOutput& aReflowOutput)
      : mKey(aReflowInput) {
    // To get content-box bsize, we have to subtract off border & padding
    // (and floor at 0 in case the border/padding are too large):
    WritingMode itemWM = aReflowInput.GetWritingMode();
    nscoord borderBoxBSize = aReflowOutput.BSize(itemWM);
    mBSize =
        borderBoxBSize -
        aReflowInput.ComputedLogicalBorderPadding(itemWM).BStartEnd(itemWM);
    mBSize = std::max(0, mBSize);
  }

  /**
   * Returns true if this cached flex item measurement is valid for (i.e. can
   * be expected to match the output of) a measuring reflow whose input
   * parameters are given via aReflowInput.
   */
  bool IsValidFor(const ReflowInput& aReflowInput) const {
    return mKey == Key(aReflowInput);
  }

  nscoord BSize() const { return mBSize; }
};

/**
 * A cached copy of various metrics from a flex item's most recent final reflow.
 * It can be used to determine whether we can optimize away the flex item's
 * final reflow, when we perform an incremental reflow of its flex container.
 */
class CachedFinalReflowMetrics final {
 public:
  CachedFinalReflowMetrics(const ReflowInput& aReflowInput,
                           const ReflowOutput& aReflowOutput)
      : CachedFinalReflowMetrics(aReflowInput.GetWritingMode(), aReflowInput,
                                 aReflowOutput) {}

  CachedFinalReflowMetrics(const FlexItem& aItem, const LogicalSize& aSize)
      : mBorderPadding(aItem.BorderPadding().ConvertTo(
            aItem.GetWritingMode(), aItem.ContainingBlockWM())),
        mSize(aSize),
        mTreatBSizeAsIndefinite(aItem.TreatBSizeAsIndefinite()) {}

  const LogicalSize& Size() const { return mSize; }
  const LogicalMargin& BorderPadding() const { return mBorderPadding; }
  bool TreatBSizeAsIndefinite() const { return mTreatBSizeAsIndefinite; }

 private:
  // A convenience constructor with a WritingMode argument.
  CachedFinalReflowMetrics(WritingMode aWM, const ReflowInput& aReflowInput,
                           const ReflowOutput& aReflowOutput)
      : mBorderPadding(aReflowInput.ComputedLogicalBorderPadding(aWM)),
        mSize(aReflowOutput.Size(aWM) - mBorderPadding.Size(aWM)),
        mTreatBSizeAsIndefinite(aReflowInput.mFlags.mTreatBSizeAsIndefinite) {}

  // The flex item's border and padding, in its own writing-mode, that it used
  // used during its most recent "final reflow".
  LogicalMargin mBorderPadding;

  // The flex item's content-box size, in its own writing-mode, that it used
  // during its most recent "final reflow".
  LogicalSize mSize;

  // True if the flex item's BSize was considered "indefinite" in its most
  // recent "final reflow". (For a flex item "final reflow", this is fully
  // determined by the mTreatBSizeAsIndefinite flag in ReflowInput. See the
  // flag's documentation for more information.)
  bool mTreatBSizeAsIndefinite;
};

/**
 * When we instantiate/update a CachedFlexItemData, this enum must be used to
 * indicate the sort of reflow whose results we're capturing. This impacts
 * what we cache & how we use the cached information.
 */
enum class FlexItemReflowType {
  // A reflow to measure the block-axis size of a flex item (as an input to the
  // flex layout algorithm).
  Measuring,

  // A reflow with the flex item's "final" size at the end of the flex layout
  // algorithm.
  Final,
};

/**
 * This class stores information about the conditions and results for the most
 * recent ReflowChild call that we made on a given flex item.  This information
 * helps us reason about whether we can assume that a subsequent ReflowChild()
 * invocation is unnecessary & skippable.
 */
class nsFlexContainerFrame::CachedFlexItemData {
 public:
  CachedFlexItemData(const ReflowInput& aReflowInput,
                     const ReflowOutput& aReflowOutput,
                     FlexItemReflowType aType) {
    Update(aReflowInput, aReflowOutput, aType);
  }

  // This method is intended to be called after we perform either a "measuring
  // reflow" or a "final reflow" for a given flex item.
  void Update(const ReflowInput& aReflowInput,
              const ReflowOutput& aReflowOutput, FlexItemReflowType aType) {
    if (aType == FlexItemReflowType::Measuring) {
      mBAxisMeasurement.reset();
      mBAxisMeasurement.emplace(aReflowInput, aReflowOutput);
      // Clear any cached "last final reflow metrics", too, because now the most
      // recent reflow was *not* a "final reflow".
      mFinalReflowMetrics.reset();
      return;
    }

    MOZ_ASSERT(aType == FlexItemReflowType::Final);
    mFinalReflowMetrics.reset();
    mFinalReflowMetrics.emplace(aReflowInput, aReflowOutput);
  }

  // This method is intended to be called for situations where we decide to
  // skip a final reflow because we've just done a measuring reflow which left
  // us (and our descendants) with the correct sizes. In this scenario, we
  // still want to cache the size as if we did a final reflow (because we've
  // determined that the recent measuring reflow was sufficient).  That way,
  // our flex container can still skip a final reflow for this item in the
  // future as long as conditions are right.
  void Update(const FlexItem& aItem, const LogicalSize& aSize) {
    MOZ_ASSERT(!mFinalReflowMetrics,
               "This version of the method is only intended to be called when "
               "the most recent reflow was a 'measuring reflow'; and that "
               "should have cleared out mFinalReflowMetrics");

    mFinalReflowMetrics.reset();  // Just in case this assert^ fails.
    mFinalReflowMetrics.emplace(aItem, aSize);
  }

  // If the flex container needs a measuring reflow for the flex item, then the
  // resulting block-axis measurements can be cached here.  If no measurement
  // has been needed so far, then this member will be Nothing().
  Maybe<CachedBAxisMeasurement> mBAxisMeasurement;

  // The metrics that the corresponding flex item used in its most recent
  // "final reflow". (Note: the assumption here is that this reflow was this
  // item's most recent reflow of any type.  If the item ends up undergoing a
  // subsequent measuring reflow, then this value needs to be cleared, because
  // at that point it's no longer an accurate way of reasoning about the
  // current state of the frame tree.)
  Maybe<CachedFinalReflowMetrics> mFinalReflowMetrics;

  // Instances of this class are stored under this frame property, on
  // frames that are flex items:
  NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, CachedFlexItemData)
};

void nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(
    nsIFrame* aItemFrame) {
  MOZ_ASSERT(aItemFrame->IsFlexItem());
  if (auto* cache = aItemFrame->GetProperty(CachedFlexItemData::Prop())) {
    cache->mBAxisMeasurement.reset();
    cache->mFinalReflowMetrics.reset();
  }
}

const CachedBAxisMeasurement& nsFlexContainerFrame::MeasureBSizeForFlexItem(
    FlexItem& aItem, ReflowInput& aChildReflowInput) {
  auto* cachedData = aItem.Frame()->GetProperty(CachedFlexItemData::Prop());

  if (cachedData && cachedData->mBAxisMeasurement) {
    if (!aItem.Frame()->IsSubtreeDirty() &&
        cachedData->mBAxisMeasurement->IsValidFor(aChildReflowInput)) {
      FLEX_LOG("[perf] MeasureBSizeForFlexItem accepted cached value");
      return *(cachedData->mBAxisMeasurement);
    }
    FLEX_LOG("[perf] MeasureBSizeForFlexItem rejected cached value");
  } else {
    FLEX_LOG("[perf] MeasureBSizeForFlexItem didn't have a cached value");
  }

  // CachedFlexItemData is stored in item's writing mode, so we pass
  // aChildReflowInput into ReflowOutput's constructor.
  ReflowOutput childReflowOutput(aChildReflowInput);
  nsReflowStatus childReflowStatus;

  const ReflowChildFlags flags = ReflowChildFlags::NoMoveFrame;
  const WritingMode outerWM = GetWritingMode();
  const LogicalPoint dummyPosition(outerWM);
  const nsSize dummyContainerSize;

  // We use NoMoveFrame, so the position and container size used here are
  // unimportant.
  ReflowChild(aItem.Frame(), PresContext(), childReflowOutput,
              aChildReflowInput, outerWM, dummyPosition, dummyContainerSize,
              flags, childReflowStatus);
  aItem.SetHadMeasuringReflow();

  // We always use unconstrained available block-size to measure flex items,
  // which means they should always complete.
  MOZ_ASSERT(childReflowStatus.IsComplete(),
             "We gave flex item unconstrained available block-size, so it "
             "should be complete");

  // Tell the child we're done with its initial reflow.
  // (Necessary for e.g. GetBaseline() to work below w/out asserting)
  FinishReflowChild(aItem.Frame(), PresContext(), childReflowOutput,
                    &aChildReflowInput, outerWM, dummyPosition,
                    dummyContainerSize, flags);

  aItem.SetAscent(childReflowOutput.BlockStartAscent());

  // Update (or add) our cached measurement, so that we can hopefully skip this
  // measuring reflow the next time around:
  if (cachedData) {
    cachedData->Update(aChildReflowInput, childReflowOutput,
                       FlexItemReflowType::Measuring);
  } else {
    cachedData = new CachedFlexItemData(aChildReflowInput, childReflowOutput,
                                        FlexItemReflowType::Measuring);
    aItem.Frame()->SetProperty(CachedFlexItemData::Prop(), cachedData);
  }
  return *(cachedData->mBAxisMeasurement);
}

/* virtual */
void nsFlexContainerFrame::MarkIntrinsicISizesDirty() {
  mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
  mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;

  nsContainerFrame::MarkIntrinsicISizesDirty();
}

nscoord nsFlexContainerFrame::MeasureFlexItemContentBSize(
    FlexItem& aFlexItem, bool aForceBResizeForMeasuringReflow,
    const ReflowInput& aParentReflowInput) {
  FLEX_LOG("Measuring flex item's content block-size");

  // Set up a reflow input for measuring the flex item's content block-size:
  WritingMode wm = aFlexItem.Frame()->GetWritingMode();
  LogicalSize availSize = aParentReflowInput.ComputedSize(wm);
  availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;

  StyleSizeOverrides sizeOverrides;
  if (aFlexItem.IsStretched()) {
    sizeOverrides.mStyleISize.emplace(aFlexItem.StyleCrossSize());
    // Suppress any AspectRatio that we might have to prevent ComputeSize() from
    // transferring our inline-size override through the aspect-ratio to set the
    // block-size, because that would prevent us from measuring the content
    // block-size.
    sizeOverrides.mAspectRatio.emplace(AspectRatio());
    FLEX_LOGV(" Cross size override: %d", aFlexItem.CrossSize());
  }
  sizeOverrides.mStyleBSize.emplace(StyleSize::Auto());

  ReflowInput childRIForMeasuringBSize(
      PresContext(), aParentReflowInput, aFlexItem.Frame(), availSize,
      Nothing(), ReflowInput::InitFlag::CallerWillInit, sizeOverrides);
  childRIForMeasuringBSize.Init(PresContext());

  // When measuring flex item's content block-size, disregard the item's
  // min-block-size and max-block-size by resetting both to to their
  // unconstraining (extreme) values. The flexbox layout algorithm does still
  // explicitly clamp both sizes when resolving the target main size.
  childRIForMeasuringBSize.SetComputedMinBSize(0);
  childRIForMeasuringBSize.SetComputedMaxBSize(NS_UNCONSTRAINEDSIZE);

  if (aForceBResizeForMeasuringReflow) {
    childRIForMeasuringBSize.SetBResize(true);
    // Not 100% sure this is needed, but be conservative for now:
    childRIForMeasuringBSize.mFlags.mIsBResizeForPercentages = true;
  }

  const CachedBAxisMeasurement& measurement =
      MeasureBSizeForFlexItem(aFlexItem, childRIForMeasuringBSize);

  return measurement.BSize();
}

FlexItem::FlexItem(ReflowInput& aFlexItemReflowInput, float aFlexGrow,
                   float aFlexShrink, nscoord aFlexBaseSize,
                   nscoord aMainMinSize, nscoord aMainMaxSize,
                   nscoord aTentativeCrossSize, nscoord aCrossMinSize,
                   nscoord aCrossMaxSize,
                   const FlexboxAxisTracker& aAxisTracker)
    : mFrame(aFlexItemReflowInput.mFrame),
      mFlexGrow(aFlexGrow),
      mFlexShrink(aFlexShrink),
      mAspectRatio(mFrame->GetAspectRatio()),
      mWM(aFlexItemReflowInput.GetWritingMode()),
      mCBWM(aAxisTracker.GetWritingMode()),
      mMainAxis(aAxisTracker.MainAxis()),
      mBorderPadding(aFlexItemReflowInput.ComputedLogicalBorderPadding(mCBWM)),
      mMargin(aFlexItemReflowInput.ComputedLogicalMargin(mCBWM)),
      mMainMinSize(aMainMinSize),
      mMainMaxSize(aMainMaxSize),
      mCrossMinSize(aCrossMinSize),
      mCrossMaxSize(aCrossMaxSize),
      mCrossSize(aTentativeCrossSize),
      mIsInlineAxisMainAxis(aAxisTracker.IsInlineAxisMainAxis(mWM)),
      mNeedsMinSizeAutoResolution(IsMinSizeAutoResolutionNeeded())
// mAlignSelf, mHasAnyAutoMargin see below
{
  MOZ_ASSERT(mFrame, "expecting a non-null child frame");
  MOZ_ASSERT(!mFrame->IsPlaceholderFrame(),
             "placeholder frames should not be treated as flex items");
  MOZ_ASSERT(!mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
             "out-of-flow frames should not be treated as flex items");
  MOZ_ASSERT(mIsInlineAxisMainAxis ==
                 nsFlexContainerFrame::IsItemInlineAxisMainAxis(mFrame),
             "public API should be consistent with internal state (about "
             "whether flex item's inline axis is flex container's main axis)");

  const ReflowInput* containerRS = aFlexItemReflowInput.mParentReflowInput;
  if (IsLegacyBox(containerRS->mFrame)) {
    // For -webkit-{inline-}box and -moz-{inline-}box, we need to:
    // (1) Use prefixed "box-align" instead of "align-items" to determine the
    //     container's cross-axis alignment behavior.
    // (2) Suppress the ability for flex items to override that with their own
    //     cross-axis alignment. (The legacy box model doesn't support this.)
    // So, each FlexItem simply copies the container's converted "align-items"
    // value and disregards their own "align-self" property.
    const nsStyleXUL* containerStyleXUL = containerRS->mFrame->StyleXUL();
    mAlignSelf = {ConvertLegacyStyleToAlignItems(containerStyleXUL)};
    mAlignSelfFlags = {0};
  } else {
    mAlignSelf = aFlexItemReflowInput.mStylePosition->UsedAlignSelf(
        containerRS->mFrame->Style());
    if (MOZ_LIKELY(mAlignSelf._0 == StyleAlignFlags::NORMAL)) {
      mAlignSelf = {StyleAlignFlags::STRETCH};
    }

    // Store and strip off the <overflow-position> bits
    mAlignSelfFlags = mAlignSelf._0 & StyleAlignFlags::FLAG_BITS;
    mAlignSelf._0 &= ~StyleAlignFlags::FLAG_BITS;
  }

  // Our main-size is considered definite if any of these are true:
  // (a) main axis is the item's inline axis.
  // (b) flex container has definite main size.
  // (c) flex item has a definite flex basis.
  //
  // Hence, we need to take care to treat the final main-size as *indefinite*
  // if none of these conditions are satisfied.
  if (mIsInlineAxisMainAxis) {
    // The item's block-axis is the flex container's cross axis. We don't need
    // any special handling to treat cross sizes as indefinite, because the
    // cases where we stomp on the cross size with a definite value are all...
    // - situations where the spec requires us to treat the cross size as
    // definite; specifically, `align-self:stretch` whose cross size is
    // definite.
    // - situations where definiteness doesn't matter (e.g. for an element with
    // an aspect ratio, which for now are all leaf nodes and hence
    // can't have any percent-height descendants that would care about the
    // definiteness of its size. (Once bug 1528375 is fixed, we might need to
    // be more careful about definite vs. indefinite sizing on flex items with
    // aspect ratios.)
    mTreatBSizeAsIndefinite = false;
  } else {
    // The item's block-axis is the flex container's main axis. So, the flex
    // item's main size is its BSize, and is considered definite under certain
    // conditions laid out for definite flex-item main-sizes in the spec.
    if (aAxisTracker.IsRowOriented() ||
        (containerRS->ComputedBSize() != NS_UNCONSTRAINEDSIZE &&
         !containerRS->mFlags.mTreatBSizeAsIndefinite)) {
      // The flex *container* has a definite main-size (either by being
      // row-oriented [and using its own inline size which is by definition
      // definite, or by being column-oriented and having a definite
      // block-size).  The spec says this means all of the flex items'
      // post-flexing main sizes should *also* be treated as definite.
      mTreatBSizeAsIndefinite = false;
    } else if (aFlexBaseSize != NS_UNCONSTRAINEDSIZE) {
      // The flex item has a definite flex basis, which we'll treat as making
      // its main-size definite.
      mTreatBSizeAsIndefinite = false;
    } else {
      // Otherwise, we have to treat the item's BSize as indefinite.
      mTreatBSizeAsIndefinite = true;
    }
  }

  SetFlexBaseSizeAndMainSize(aFlexBaseSize);

  const nsStyleMargin* styleMargin = aFlexItemReflowInput.mStyleMargin;
  mHasAnyAutoMargin = styleMargin->HasInlineAxisAuto(mCBWM) ||
                      styleMargin->HasBlockAxisAuto(mCBWM);

  // Assert that any "auto" margin components are set to 0.
  // (We'll resolve them later; until then, we want to treat them as 0-sized.)
#ifdef DEBUG
  {
    for (const auto side : AllLogicalSides()) {
      if (styleMargin->mMargin.Get(mCBWM, side).IsAuto()) {
        MOZ_ASSERT(GetMarginComponentForSide(side) == 0,
                   "Someone else tried to resolve our auto margin");
      }
    }
  }
#endif  // DEBUG

  if (mAlignSelf._0 == StyleAlignFlags::BASELINE ||
      mAlignSelf._0 == StyleAlignFlags::LAST_BASELINE) {
    // Check which of the item's baselines we're meant to use (first vs. last)
    const bool usingItemFirstBaseline =
        (mAlignSelf._0 == StyleAlignFlags::BASELINE);
    if (IsBlockAxisCrossAxis()) {
      // The flex item wants to be aligned in the cross axis using one of its
      // baselines; and the cross axis is the item's block axis, so
      // baseline-alignment in that axis makes sense.

      // To determine the item's baseline sharing group, we check whether the
      // item's block axis has the same vs. opposite flow direction as the
      // corresponding LogicalAxis on the flex container.  We do this by
      // getting the physical side that corresponds to these axes' "logical
      // start" sides, and we compare those physical sides to find out if
      // they're the same vs. opposite.
      mozilla::Side itemBlockStartSide = mWM.PhysicalSide(eLogicalSideBStart);

      // (Note: this is *not* the "flex-start" side; rather, it's the *logical*
      // i.e. WM-relative block-start or inline-start side.)
      mozilla::Side containerStartSideInCrossAxis = mCBWM.PhysicalSide(
          MakeLogicalSide(aAxisTracker.CrossAxis(), eLogicalEdgeStart));

      // We already know these two Sides (the item's block-start and the
      // container's 'logical start' side for its cross axis) are in the same
      // physical axis, since we're inside of a check for
      // FlexItem::IsBlockAxisCrossAxis().  So these two Sides must be either
      // the same physical side or opposite from each other.  If the Sides are
      // the same, then the flow direction is the same, which means the item's
      // {first,last} baseline participates in the {first,last}
      // baseline-sharing group in its FlexLine.  Otherwise, the flow direction
      // is opposite, and so the item's {first,last} baseline participates in
      // the opposite i.e. {last,first} baseline-sharing group.  This is
      // roughly per css-align-3 section 9.2, specifically the definition of
      // what makes baseline alignment preferences "compatible".
      bool itemBlockAxisFlowDirMatchesContainer =
          (itemBlockStartSide == containerStartSideInCrossAxis);
      mBaselineSharingGroup =
          (itemBlockAxisFlowDirMatchesContainer == usingItemFirstBaseline)
              ? BaselineSharingGroup::First
              : BaselineSharingGroup::Last;
    } else {
      // The flex item wants to be aligned in the cross axis using one of its
      // baselines, but we cannot get its baseline because the FlexItem's block
      // axis is *orthogonal* to the container's cross axis. To handle this, we
      // are supposed to synthesize a baseline from the item's border box and
      // using that for baseline alignment.
      mBaselineSharingGroup = usingItemFirstBaseline
                                  ? BaselineSharingGroup::First
                                  : BaselineSharingGroup::Last;
    }
  }
}

// Simplified constructor for creating a special "strut" FlexItem, for a child
// with visibility:collapse. The strut has 0 main-size, and it only exists to
// impose a minimum cross size on whichever FlexLine it ends up in.
FlexItem::FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize,
                   WritingMode aContainerWM,
                   const FlexboxAxisTracker& aAxisTracker)
    : mFrame(aChildFrame),
      mWM(aChildFrame->GetWritingMode()),
      mCBWM(aContainerWM),
      mMainAxis(aAxisTracker.MainAxis()),
      mBorderPadding(mCBWM),
      mMargin(mCBWM),
      mCrossSize(aCrossSize),
      // Struts don't do layout, so its WM doesn't matter at this point. So, we
      // just share container's WM for simplicity:
      mIsFrozen(true),
      mIsStrut(true),  // (this is the constructor for making struts, after all)
      mAlignSelf({StyleAlignFlags::FLEX_START}) {
  MOZ_ASSERT(mFrame, "expecting a non-null child frame");
  MOZ_ASSERT(mFrame->StyleVisibility()->IsCollapse(),
             "Should only make struts for children with 'visibility:collapse'");
  MOZ_ASSERT(!mFrame->IsPlaceholderFrame(),
             "placeholder frames should not be treated as flex items");
  MOZ_ASSERT(!mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
             "out-of-flow frames should not be treated as flex items");
}

bool FlexItem::IsMinSizeAutoResolutionNeeded() const {
  // We'll need special behavior for "min-[width|height]:auto" (whichever is in
  // the flex container's main axis) iff:
  // (a) its computed value is "auto", and
  // (b) the item is *not* a scroll container. (A scroll container's automatic
  //     minimum size is zero.)
  // https://drafts.csswg.org/css-flexbox-1/#min-size-auto
  const auto& mainMinSize =
      Frame()->StylePosition()->MinSize(MainAxis(), ContainingBlockWM());

  return IsAutoOrEnumOnBSize(mainMinSize, IsInlineAxisMainAxis()) &&
         !Frame()->StyleDisplay()->IsScrollableOverflow();
}

nscoord FlexItem::BaselineOffsetFromOuterCrossEdge(
    mozilla::Side aStartSide, bool aUseFirstLineBaseline) const {
  // NOTE:
  //  * We only use baselines for aligning in the flex container's cross axis.
  //  * Baselines are a measurement in the item's block axis.
  if (IsBlockAxisMainAxis()) {
    // We get here if the item's block axis is *orthogonal* the container's
    // cross axis. For example, a flex item with writing-mode:horizontal-tb in a
    // column-oriented flex container. We need to synthesize the item's baseline
    // from its border-box edge.
    const bool isMainAxisHorizontal =
        mCBWM.PhysicalAxis(MainAxis()) == mozilla::eAxisHorizontal;

    // When the main axis is horizontal, the synthesized baseline is the bottom
    // edge of the item's border-box. Otherwise, when the main axis is vertical,
    // the left edge. This is for compatibility with Google Chrome.
    nscoord marginTopOrLeftToBaseline =
        isMainAxisHorizontal ? PhysicalMargin().top : PhysicalMargin().left;
    if (mCBWM.IsAlphabeticalBaseline()) {
      marginTopOrLeftToBaseline += (isMainAxisHorizontal ? CrossSize() : 0);
    } else {
      MOZ_ASSERT(mCBWM.IsCentralBaseline());
      marginTopOrLeftToBaseline += CrossSize() / 2;
    }

    return aStartSide == mozilla::eSideTop || aStartSide == mozilla::eSideLeft
               ? marginTopOrLeftToBaseline
               : OuterCrossSize() - marginTopOrLeftToBaseline;
  }

  // We get here if the item's block axis is parallel (or antiparallel) to the
  // container's cross axis. We call ResolvedAscent() to get the item's
  // baseline. If the item has no baseline, the method will synthesize one from
  // the border-box edge.
  MOZ_ASSERT(IsBlockAxisCrossAxis(),
             "Only expecting to be doing baseline computations when the "
             "cross axis is the block axis");

  mozilla::Side itemBlockStartSide = mWM.PhysicalSide(eLogicalSideBStart);

  nscoord marginBStartToBaseline = ResolvedAscent(aUseFirstLineBaseline) +
                                   PhysicalMargin().Side(itemBlockStartSide);

  return (aStartSide == itemBlockStartSide)
             ? marginBStartToBaseline
             : OuterCrossSize() - marginBStartToBaseline;
}

bool FlexItem::IsCrossSizeAuto() const {
  const nsStylePosition* stylePos =
      nsLayoutUtils::GetStyleFrame(mFrame)->StylePosition();
  // Check whichever component is in the flex container's cross axis.
  // (IsInlineAxisCrossAxis() tells us whether that's our ISize or BSize, in
  // terms of our own WritingMode, mWM.)
  return IsInlineAxisCrossAxis() ? stylePos->ISize(mWM).IsAuto()
                                 : stylePos->BSize(mWM).IsAuto();
}

bool FlexItem::IsCrossSizeDefinite(const ReflowInput& aItemReflowInput) const {
  if (IsStretched()) {
    // Definite cross-size, imposed via 'align-self:stretch' & flex container.
    return true;
  }

  const nsStylePosition* pos = aItemReflowInput.mStylePosition;
  const auto itemWM = GetWritingMode();

  // The logic here should be similar to the logic for isAutoISize/isAutoBSize
  // in nsContainerFrame::ComputeSizeWithIntrinsicDimensions().
  if (IsInlineAxisCrossAxis()) {
    return !pos->ISize(itemWM).IsAuto();
  }

  nscoord cbBSize = aItemReflowInput.mContainingBlockSize.BSize(itemWM);
  return !nsLayoutUtils::IsAutoBSize(pos->BSize(itemWM), cbBSize);
}

void FlexItem::ResolveFlexBaseSizeFromAspectRatio(
    const ReflowInput& aItemReflowInput) {
  // This implements the Flex Layout Algorithm Step 3B:
  // https://drafts.csswg.org/css-flexbox-1/#algo-main-item
  // If the flex item has ...
  //  - an aspect ratio,
  //  - a [used] flex-basis of 'content', and
  //  - a definite cross size
  // then the flex base size is calculated from its inner cross size and the
  // flex item's preferred aspect ratio.
  if (HasAspectRatio() &&
      nsFlexContainerFrame::IsUsedFlexBasisContent(
          aItemReflowInput.mStylePosition->mFlexBasis,
          aItemReflowInput.mStylePosition->Size(MainAxis(), mCBWM)) &&
      IsCrossSizeDefinite(aItemReflowInput)) {
    const LogicalSize contentBoxSizeToBoxSizingAdjust =
        aItemReflowInput.mStylePosition->mBoxSizing == StyleBoxSizing::Border
            ? BorderPadding().Size(mCBWM)
            : LogicalSize(mCBWM);
    const nscoord mainSizeFromRatio = mAspectRatio.ComputeRatioDependentSize(
        MainAxis(), mCBWM, CrossSize(), contentBoxSizeToBoxSizingAdjust);
    SetFlexBaseSizeAndMainSize(mainSizeFromRatio);
  }
}

uint32_t FlexItem::NumAutoMarginsInAxis(LogicalAxis aAxis) const {
  uint32_t numAutoMargins = 0;
  const auto& styleMargin = mFrame->StyleMargin()->mMargin;
  for (const auto edge : {eLogicalEdgeStart, eLogicalEdgeEnd}) {
    const auto side = MakeLogicalSide(aAxis, edge);
    if (styleMargin.Get(mCBWM, side).IsAuto()) {
      numAutoMargins++;
    }
  }

  // Mostly for clarity:
  MOZ_ASSERT(numAutoMargins <= 2,
             "We're just looking at one item along one dimension, so we "
             "should only have examined 2 margins");

  return numAutoMargins;
}

bool FlexItem::CanMainSizeInfluenceCrossSize() const {
  if (mIsStretched) {
    // We've already had our cross-size stretched for "align-self:stretch").
    // The container is imposing its cross size on us.
    return false;
  }

  if (mIsStrut) {
    // Struts (for visibility:collapse items) have a predetermined size;
    // no need to measure anything.
    return false;
  }

  if (HasAspectRatio()) {
    // For flex items that have an aspect ratio (and maintain it, i.e. are
    // not stretched, which we already checked above): changes to main-size
    // *do* influence the cross size.
    return true;
  }

  if (IsInlineAxisCrossAxis()) {
    // If we get here, this function is really asking: "can changes to this
    // item's block size have an influence on its inline size"?  For blocks and
    // tables, the answer is "no".
    if (mFrame->IsBlockFrame() || mFrame->IsTableWrapperFrame()) {
      // XXXdholbert (Maybe use an IsFrameOfType query or something more
      // general to test this across all frame types? For now, I'm just
      // optimizing for block and table, since those are common containers that
      // can contain arbitrarily-large subtrees (and that reliably have ISize
      // being unaffected by BSize, per CSS2).  So optimizing away needless
      // relayout is possible & especially valuable for these containers.)
      return false;
    }
    // Other opt-outs can go here, as they're identified as being useful
    // (particularly for containers where an extra reflow is expensive). But in
    // general, we have to assume that a flexed BSize *could* influence the
    // ISize. Some examples where this can definitely happen:
    // * Intrinsically-sized multicol with fixed-ISize columns, which adds
    // columns (i.e. grows in inline axis) depending on its block size.
    // * Intrinsically-sized multi-line column-oriented flex container, which
    // adds flex lines (i.e. grows in inline axis) depending on its block size.
  }

  // Default assumption, if we haven't proven otherwise: the resolved main size
  // *can* change the cross size.
  return true;
}

nscoord FlexItem::ClampMainSizeViaCrossAxisConstraints(
    nscoord aMainSize, const ReflowInput& aItemReflowInput) const {
  MOZ_ASSERT(HasAspectRatio(), "Caller should've checked the ratio is valid!");

  const LogicalSize contentBoxSizeToBoxSizingAdjust =
      aItemReflowInput.mStylePosition->mBoxSizing == StyleBoxSizing::Border
          ? BorderPadding().Size(mCBWM)
          : LogicalSize(mCBWM);

  const nscoord mainMinSizeFromRatio = mAspectRatio.ComputeRatioDependentSize(
      MainAxis(), mCBWM, CrossMinSize(), contentBoxSizeToBoxSizingAdjust);
  nscoord clampedMainSize = std::max(aMainSize, mainMinSizeFromRatio);

  if (CrossMaxSize() != NS_UNCONSTRAINEDSIZE) {
    const nscoord mainMaxSizeFromRatio = mAspectRatio.ComputeRatioDependentSize(
        MainAxis(), mCBWM, CrossMaxSize(), contentBoxSizeToBoxSizingAdjust);
    clampedMainSize = std::min(clampedMainSize, mainMaxSizeFromRatio);
  }

  return clampedMainSize;
}

/**
 * Returns true if aFrame or any of its children have the
 * NS_FRAME_CONTAINS_RELATIVE_BSIZE flag set -- i.e. if any of these frames (or
 * their descendants) might have a relative-BSize dependency on aFrame (or its
 * ancestors).
 */
static bool FrameHasRelativeBSizeDependency(nsIFrame* aFrame) {
  if (aFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
    return true;
  }
  for (const auto& childList : aFrame->ChildLists()) {
    for (nsIFrame* childFrame : childList.mList) {
      if (childFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
        return true;
      }
    }
  }
  return false;
}

bool FlexItem::NeedsFinalReflow(const ReflowInput& aParentReflowInput) const {
  if (!StaticPrefs::layout_flexbox_item_final_reflow_optimization_enabled()) {
    FLEX_LOG(
        "[perf] Flex item %p needed a final reflow due to optimization being "
        "disabled via the preference",
        mFrame);
    return true;
  }

  // NOTE: We can have continuations from an earlier constrained reflow.
  if (mFrame->GetPrevInFlow() || mFrame->GetNextInFlow()) {
    // This is an item has continuation(s). Reflow it.
    FLEX_LOG("[frag] Flex item %p needed a final reflow due to continuation(s)",
             mFrame);
    return true;
  }

  // A flex item can grow its block-size in a fragmented context if there's any
  // force break within it (bug 1663079), or if it has a repeated table header
  // or footer (bug 1744363). We currently always reflow it.
  //
  // Bug 1815294: investigate if we can design a more specific condition to
  // prevent triggering O(n^2) behavior when printing a deeply-nested flex
  // container.
  if (aParentReflowInput.IsInFragmentedContext()) {
    FLEX_LOG(
        "[frag] Flex item %p needed both a measuring reflow and a final "
        "reflow due to being in a fragmented context.",
        mFrame);
    return true;
  }

  // Flex item's final content-box size (in terms of its own writing-mode):
  const LogicalSize finalSize = mIsInlineAxisMainAxis
                                    ? LogicalSize(mWM, mMainSize, mCrossSize)
                                    : LogicalSize(mWM, mCrossSize, mMainSize);

  if (HadMeasuringReflow()) {
    // We've already reflowed this flex item once, to measure it. In that
    // reflow, did its frame happen to end up with the correct final size
    // that the flex container would like it to have?
    if (finalSize != mFrame->ContentSize(mWM)) {
      // The measuring reflow left the item with a different size than its
      // final flexed size. So, we need to reflow to give it the correct size.
      FLEX_LOG(
          "[perf] Flex item %p needed both a measuring reflow and a final "
          "reflow due to measured size disagreeing with final size",
          mFrame);
      return true;
    }

    if (FrameHasRelativeBSizeDependency(mFrame)) {
      // This item has descendants with relative BSizes who may care that its
      // size may now be considered "definite" in the final reflow (whereas it
      // was indefinite during the measuring reflow).
      FLEX_LOG(
          "[perf] Flex item %p needed both a measuring reflow and a final "
          "reflow due to BSize potentially becoming definite",
          mFrame);
      return true;
    }

    // If we get here, then this flex item had a measuring reflow, it left us
    // with the correct size, none of its descendants care that its BSize may
    // now be considered definite, and it can fit into the available block-size.
    // So it doesn't need a final reflow.
    //
    // We now cache this size as if we had done a final reflow (because we've
    // determined that the measuring reflow was effectively equivalent).  This
    // way, in our next time through flex layout, we may be able to skip both
    // the measuring reflow *and* the final reflow (if conditions are the same
    // as they are now).
    if (auto* cache = mFrame->GetProperty(CachedFlexItemData::Prop())) {
      cache->Update(*this, finalSize);
    }

    return false;
  }

  // This item didn't receive a measuring reflow (at least, not during this
  // reflow of our flex container).  We may still be able to skip reflowing it
  // (i.e. return false from this function), if its subtree is clean & its most
  // recent "final reflow" had it at the correct content-box size &
  // definiteness.
  // Let's check for each condition that would still require us to reflow:
  if (mFrame->IsSubtreeDirty()) {
    FLEX_LOG(
        "[perf] Flex item %p needed a final reflow due to its subtree "
        "being dirty",
        mFrame);
    return true;
  }

  // Cool; this item & its subtree haven't experienced any style/content
  // changes that would automatically require a reflow.

  // Did we cache the metrics from its most recent "final reflow"?
  auto* cache = mFrame->GetProperty(CachedFlexItemData::Prop());
  if (!cache || !cache->mFinalReflowMetrics) {
    FLEX_LOG(
        "[perf] Flex item %p needed a final reflow due to lacking a "
        "cached mFinalReflowMetrics (maybe cache was cleared)",
        mFrame);
    return true;
  }

  // Does the cached size match our current size?
  if (cache->mFinalReflowMetrics->Size() != finalSize) {
    FLEX_LOG(
        "[perf] Flex item %p needed a final reflow due to having a "
        "different content box size vs. its most recent final reflow",
        mFrame);
    return true;
  }

  // Does the cached border and padding match our current ones?
  //
  // Note: this is just to detect cases where we have a percent padding whose
  // basis has changed. Any other sort of change to BorderPadding() (e.g. a new
  // specified value) should result in the frame being marked dirty via proper
  // change hint (see nsStylePadding::CalcDifference()), which will force it to
  // reflow.
  if (cache->mFinalReflowMetrics->BorderPadding() !=
      BorderPadding().ConvertTo(mWM, mCBWM)) {
    FLEX_LOG(
        "[perf] Flex item %p needed a final reflow due to having a "
        "different border and padding vs. its most recent final reflow",
        mFrame);
    return true;
  }

  // The flex container is giving this flex item the same size that the item
  // had on its most recent "final reflow". But if its definiteness changed and
  // one of the descendants cares, then it would still need a reflow.
  if (cache->mFinalReflowMetrics->TreatBSizeAsIndefinite() !=
          mTreatBSizeAsIndefinite &&
      FrameHasRelativeBSizeDependency(mFrame)) {
    FLEX_LOG(
        "[perf] Flex item %p needed a final reflow due to having "
        "its BSize change definiteness & having a rel-BSize child",
        mFrame);
    return true;
  }

  // If we get here, we can skip the final reflow! (The item's subtree isn't
  // dirty, and our current conditions are sufficiently similar to the most
  // recent "final reflow" that it should have left our subtree in the correct
  // state.)
  FLEX_LOG("[perf] Flex item %p didn't need a final reflow", mFrame);
  return false;
}

// Keeps track of our position along a particular axis (where a '0' position
// corresponds to the 'start' edge of that axis).
// This class shouldn't be instantiated directly -- rather, it should only be
// instantiated via its subclasses defined below.
class MOZ_STACK_CLASS PositionTracker {
 public:
  // Accessor for the current value of the position that we're tracking.
  inline nscoord Position() const { return mPosition; }
  inline LogicalAxis Axis() const { return mAxis; }

  inline LogicalSide StartSide() {
    return MakeLogicalSide(
        mAxis, mIsAxisReversed ? eLogicalEdgeEnd : eLogicalEdgeStart);
  }

  inline LogicalSide EndSide() {
    return MakeLogicalSide(
        mAxis, mIsAxisReversed ? eLogicalEdgeStart : eLogicalEdgeEnd);
  }

  // Advances our position across the start edge of the given margin, in the
  // axis we're tracking.
  void EnterMargin(const LogicalMargin& aMargin) {
    mPosition += aMargin.Side(StartSide(), mWM);
  }

  // Advances our position across the end edge of the given margin, in the axis
  // we're tracking.
  void ExitMargin(const LogicalMargin& aMargin) {
    mPosition += aMargin.Side(EndSide(), mWM);
  }

  // Advances our current position from the start side of a child frame's
  // border-box to the frame's upper or left edge (depending on our axis).
  // (Note that this is a no-op if our axis grows in the same direction as
  // the corresponding logical axis.)
  void EnterChildFrame(nscoord aChildFrameSize) {
    if (mIsAxisReversed) {
      mPosition += aChildFrameSize;
    }
  }

  // Advances our current position from a frame's upper or left border-box edge
  // (whichever is in the axis we're tracking) to the 'end' side of the frame
  // in the axis that we're tracking. (Note that this is a no-op if our axis
  // is reversed with respect to the corresponding logical axis.)
  void ExitChildFrame(nscoord aChildFrameSize) {
    if (!mIsAxisReversed) {
      mPosition += aChildFrameSize;
    }
  }

  // Delete copy-constructor & reassignment operator, to prevent accidental
  // (unnecessary) copying.
  PositionTracker(const PositionTracker&) = delete;
  PositionTracker& operator=(const PositionTracker&) = delete;

 protected:
  // Protected constructor, to be sure we're only instantiated via a subclass.
  PositionTracker(WritingMode aWM, LogicalAxis aAxis, bool aIsAxisReversed)
      : mWM(aWM), mAxis(aAxis), mIsAxisReversed(aIsAxisReversed) {}

  // Member data:
  // The position we're tracking.
  nscoord mPosition = 0;

  // The flex container's writing mode.
  const WritingMode mWM;

  // The axis along which we're moving.
  const LogicalAxis mAxis = eLogicalAxisInline;

  // Is the axis along which we're moving reversed (e.g. LTR vs RTL) with
  // respect to the corresponding axis on the flex container's WM?
  const bool mIsAxisReversed = false;
};

// Tracks our position in the main axis, when we're laying out flex items.
// The "0" position represents the main-start edge of the flex container's
// content-box.
class MOZ_STACK_CLASS MainAxisPositionTracker : public PositionTracker {
 public:
  MainAxisPositionTracker(const FlexboxAxisTracker& aAxisTracker,
                          const FlexLine* aLine,
                          const StyleContentDistribution& aJustifyContent,
                          nscoord aContentBoxMainSize);

  ~MainAxisPositionTracker() {
    MOZ_ASSERT(mNumPackingSpacesRemaining == 0,
               "miscounted the number of packing spaces");
    MOZ_ASSERT(mNumAutoMarginsInMainAxis == 0,
               "miscounted the number of auto margins");
  }

  // Advances past the gap space (if any) between two flex items
  void TraverseGap(nscoord aGapSize) { mPosition += aGapSize; }

  // Advances past the packing space (if any) between two flex items
  void TraversePackingSpace();

  // If aItem has any 'auto' margins in the main axis, this method updates the
  // corresponding values in its margin.
  void ResolveAutoMarginsInMainAxis(FlexItem& aItem);

 private:
  nscoord mPackingSpaceRemaining = 0;
  uint32_t mNumAutoMarginsInMainAxis = 0;
  uint32_t mNumPackingSpacesRemaining = 0;
  StyleContentDistribution mJustifyContent = {StyleAlignFlags::AUTO};
};

// Utility class for managing our position along the cross axis along
// the whole flex container (at a higher level than a single line).
// The "0" position represents the cross-start edge of the flex container's
// content-box.
class MOZ_STACK_CLASS CrossAxisPositionTracker : public PositionTracker {
 public:
  CrossAxisPositionTracker(nsTArray<FlexLine>& aLines,
                           const ReflowInput& aReflowInput,
                           nscoord aContentBoxCrossSize,
                           bool aIsCrossSizeDefinite,
                           const FlexboxAxisTracker& aAxisTracker,
                           const nscoord aCrossGapSize);

  // Advances past the gap (if any) between two flex lines
  void TraverseGap() { mPosition += mCrossGapSize; }

  // Advances past the packing space (if any) between two flex lines
  void TraversePackingSpace();

  // Advances past the given FlexLine
  void TraverseLine(FlexLine& aLine) { mPosition += aLine.LineCrossSize(); }

  // Redeclare the frame-related methods from PositionTracker with
  // = delete, to be sure (at compile time) that no client code can invoke
  // them. (Unlike the other PositionTracker derived classes, this class here
  // deals with FlexLines, not with individual FlexItems or frames.)
  void EnterMargin(const LogicalMargin& aMargin) = delete;
  void ExitMargin(const LogicalMargin& aMargin) = delete;
  void EnterChildFrame(nscoord aChildFrameSize) = delete;
  void ExitChildFrame(nscoord aChildFrameSize) = delete;

 private:
  nscoord mPackingSpaceRemaining = 0;
  uint32_t mNumPackingSpacesRemaining = 0;
  StyleContentDistribution mAlignContent = {StyleAlignFlags::AUTO};

  const nscoord mCrossGapSize;
};

// Utility class for managing our position along the cross axis, *within* a
// single flex line.
class MOZ_STACK_CLASS SingleLineCrossAxisPositionTracker
    : public PositionTracker {
 public:
  explicit SingleLineCrossAxisPositionTracker(
      const FlexboxAxisTracker& aAxisTracker);

  void ResolveAutoMarginsInCrossAxis(const FlexLine& aLine, FlexItem& aItem);

  void EnterAlignPackingSpace(const FlexLine& aLine, const FlexItem& aItem,
                              const FlexboxAxisTracker& aAxisTracker);

  // Resets our position to the cross-start edge of this line.
  inline void ResetPosition() { mPosition = 0; }
};

//----------------------------------------------------------------------

// Frame class boilerplate
// =======================

NS_QUERYFRAME_HEAD(nsFlexContainerFrame)
  NS_QUERYFRAME_ENTRY(nsFlexContainerFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)

NS_IMPL_FRAMEARENA_HELPERS(nsFlexContainerFrame)

nsContainerFrame* NS_NewFlexContainerFrame(PresShell* aPresShell,
                                           ComputedStyle* aStyle) {
  return new (aPresShell)
      nsFlexContainerFrame(aStyle, aPresShell->GetPresContext());
}

//----------------------------------------------------------------------

// nsFlexContainerFrame Method Implementations
// ===========================================

/* virtual */
nsFlexContainerFrame::~nsFlexContainerFrame() = default;

/* virtual */
void nsFlexContainerFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
                                nsIFrame* aPrevInFlow) {
  nsContainerFrame::Init(aContent, aParent, aPrevInFlow);

  if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER)) {
    AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
  }

  auto displayInside = StyleDisplay()->DisplayInside();
  // If this frame is for a scrollable element, then it will actually have
  // "display:block", and its *parent frame* will have the real
  // flex-flavored display value. So in that case, check the parent frame to
  // find out if we're legacy.
  //
  // TODO(emilio): Maybe ::-moz-scrolled-content and co should inherit `display`
  // (or a blockified version thereof, to not hit bug 456484).
  if (displayInside == StyleDisplayInside::Flow) {
    MOZ_ASSERT(StyleDisplay()->mDisplay == StyleDisplay::Block);
    MOZ_ASSERT(Style()->GetPseudoType() == PseudoStyleType::buttonContent ||
                   Style()->GetPseudoType() == PseudoStyleType::scrolledContent,
               "The only way a nsFlexContainerFrame can have 'display:block' "
               "should be if it's the inner part of a scrollable or button "
               "element");
    displayInside = GetParent()->StyleDisplay()->DisplayInside();
  }

  // Figure out if we should set a frame state bit to indicate that this frame
  // represents a legacy -moz-{inline-}box or -webkit-{inline-}box container.
  if (displayInside == StyleDisplayInside::WebkitBox) {
    AddStateBits(NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX);
  }
}

#ifdef DEBUG_FRAME_DUMP
nsresult nsFlexContainerFrame::GetFrameName(nsAString& aResult) const {
  return MakeFrameName(u"FlexContainer"_ns, aResult);
}
#endif

void nsFlexContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
                                            const nsDisplayListSet& aLists) {
  nsDisplayListCollection tempLists(aBuilder);

  DisplayBorderBackgroundOutline(aBuilder, tempLists);
  if (GetPrevInFlow()) {
    DisplayOverflowContainers(aBuilder, tempLists);
  }

  // Our children are all block-level, so their borders/backgrounds all go on
  // the BlockBorderBackgrounds list.
  nsDisplayListSet childLists(tempLists, tempLists.BlockBorderBackgrounds());

  CSSOrderAwareFrameIterator iter(
      this, FrameChildListID::Principal,
      CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
      OrderStateForIter(this), OrderingPropertyForIter(this));

  const auto flags = DisplayFlagsForFlexOrGridItem();
  for (; !iter.AtEnd(); iter.Next()) {
    nsIFrame* childFrame = *iter;
    BuildDisplayListForChild(aBuilder, childFrame, childLists, flags);
  }

  tempLists.MoveTo(aLists);
}

void FlexLine::FreezeItemsEarly(bool aIsUsingFlexGrow,
                                ComputedFlexLineInfo* aLineInfo) {
  // After we've established the type of flexing we're doing (growing vs.
  // shrinking), and before we try to flex any items, we freeze items that
  // obviously *can't* flex.
  //
  // Quoting the spec:
  //  # Freeze, setting its target main size to its hypothetical main size...
  //  #  - any item that has a flex factor of zero
  //  #  - if using the flex grow factor: any item that has a flex base size
  //  #    greater than its hypothetical main size
  //  #  - if using the flex shrink factor: any item that has a flex base size
  //  #    smaller than its hypothetical main size
  //  https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths
  //
  // (NOTE: At this point, item->MainSize() *is* the item's hypothetical
  // main size, since SetFlexBaseSizeAndMainSize() sets it up that way, and the
  // item hasn't had a chance to flex away from that yet.)

  // Since this loop only operates on unfrozen flex items, we can break as
  // soon as we have seen all of them.
  uint32_t numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems;
  for (FlexItem& item : Items()) {
    if (numUnfrozenItemsToBeSeen == 0) {
      break;
    }

    if (!item.IsFrozen()) {
      numUnfrozenItemsToBeSeen--;
      bool shouldFreeze = (0.0f == item.GetFlexFactor(aIsUsingFlexGrow));
      if (!shouldFreeze) {
        if (aIsUsingFlexGrow) {
          if (item.FlexBaseSize() > item.MainSize()) {
            shouldFreeze = true;
          }
        } else {  // using flex-shrink
          if (item.FlexBaseSize() < item.MainSize()) {
            shouldFreeze = true;
          }
        }
      }
      if (shouldFreeze) {
        // Freeze item! (at its hypothetical main size)
        item.Freeze();
        if (item.FlexBaseSize() < item.MainSize()) {
          item.SetWasMinClamped();
        } else if (item.FlexBaseSize() > item.MainSize()) {
          item.SetWasMaxClamped();
        }
        mNumFrozenItems++;
      }
    }
  }

  MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");
}

// Based on the sign of aTotalViolation, this function freezes a subset of our
// flexible sizes, and restores the remaining ones to their initial pref sizes.
void FlexLine::FreezeOrRestoreEachFlexibleSize(const nscoord aTotalViolation,
                                               bool aIsFinalIteration) {
  enum FreezeType {
    eFreezeEverything,
    eFreezeMinViolations,
    eFreezeMaxViolations
  };

  FreezeType freezeType;
  if (aTotalViolation == 0) {
    freezeType = eFreezeEverything;
  } else if (aTotalViolation > 0) {
    freezeType = eFreezeMinViolations;
  } else {  // aTotalViolation < 0
    freezeType = eFreezeMaxViolations;
  }

  // Since this loop only operates on unfrozen flex items, we can break as
  // soon as we have seen all of them.
  uint32_t numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems;
  for (FlexItem& item : Items()) {
    if (numUnfrozenItemsToBeSeen == 0) {
      break;
    }

    if (!item.IsFrozen()) {
      numUnfrozenItemsToBeSeen--;

      MOZ_ASSERT(!item.HadMinViolation() || !item.HadMaxViolation(),
                 "Can have either min or max violation, but not both");

      bool hadMinViolation = item.HadMinViolation();
      bool hadMaxViolation = item.HadMaxViolation();
      if (eFreezeEverything == freezeType ||
          (eFreezeMinViolations == freezeType && hadMinViolation) ||
          (eFreezeMaxViolations == freezeType && hadMaxViolation)) {
        MOZ_ASSERT(item.MainSize() >= item.MainMinSize(),
                   "Freezing item at a size below its minimum");
        MOZ_ASSERT(item.MainSize() <= item.MainMaxSize(),
                   "Freezing item at a size above its maximum");

        item.Freeze();
        if (hadMinViolation) {
          item.SetWasMinClamped();
        } else if (hadMaxViolation) {
          item.SetWasMaxClamped();
        }
        mNumFrozenItems++;
      } else if (MOZ_UNLIKELY(aIsFinalIteration)) {
        // XXXdholbert If & when bug 765861 is fixed, we should upgrade this
        // assertion to be fatal except in documents with enormous lengths.
        NS_ERROR(
            "Final iteration still has unfrozen items, this shouldn't"
            " happen unless there was nscoord under/overflow.");
        item.Freeze();
        mNumFrozenItems++;
      }  // else, we'll reset this item's main size to its flex base size on the
         // next iteration of this algorithm.

      if (!item.IsFrozen()) {
        // Clear this item's violation(s), now that we've dealt with them
        item.ClearViolationFlags();
      }
    }
  }

  MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");
}

void FlexLine::ResolveFlexibleLengths(nscoord aFlexContainerMainSize,
                                      ComputedFlexLineInfo* aLineInfo) {
  // In this function, we use 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. Here we
  // promote the container's main size to 64-bit to make the arithmetic
  // convenient.
  AuCoord64 flexContainerMainSize(aFlexContainerMainSize);

  // Before we start resolving sizes: if we have an aLineInfo structure to fill
  // out, we inform it of each item's base size, and we initialize the "delta"
  // for each item to 0. (And if the flex algorithm wants to grow or shrink the
  // item, we'll update this delta further down.)
  if (aLineInfo) {
    uint32_t itemIndex = 0;
    for (FlexItem& item : Items()) {
      aLineInfo->mItems[itemIndex].mMainBaseSize = item.FlexBaseSize();
      aLineInfo->mItems[itemIndex].mMainDeltaSize = 0;
      ++itemIndex;
    }
  }

  // Determine whether we're going to be growing or shrinking items.
  const bool isUsingFlexGrow =
      (mTotalOuterHypotheticalMainSize < flexContainerMainSize);

  if (aLineInfo) {
    aLineInfo->mGrowthState =
        isUsingFlexGrow ? mozilla::dom::FlexLineGrowthState::Growing
                        : mozilla::dom::FlexLineGrowthState::Shrinking;
  }

  // Do an "early freeze" for flex items that obviously can't flex in the
  // direction we've chosen:
  FreezeItemsEarly(isUsingFlexGrow, aLineInfo);

  if ((mNumFrozenItems == NumItems()) && !aLineInfo) {
    // All our items are frozen, so we have no flexible lengths to resolve,
    // and we aren't being asked to generate computed line info.
    FLEX_LOG("No flexible length to resolve");
    return;
  }
  MOZ_ASSERT(!IsEmpty() || aLineInfo,
             "empty lines should take the early-return above");

  FLEX_LOG("Resolving flexible lengths for items");

  // Subtract space occupied by our items' margins/borders/padding/gaps, so
  // we can just be dealing with the space available for our flex items' content
  // boxes.
  const AuCoord64 totalItemMBPAndGaps = mTotalItemMBP + SumOfGaps();
  const AuCoord64 spaceAvailableForFlexItemsContentBoxes =
      flexContainerMainSize - totalItemMBPAndGaps;

  Maybe<AuCoord64> origAvailableFreeSpace;

  // NOTE: I claim that this chunk of the algorithm (the looping part) needs to
  // run the loop at MOST NumItems() times.  This claim should hold up
  // because we'll freeze at least one item on each loop iteration, and once
  // we've run out of items to freeze, there's nothing left to do.  However,
  // in most cases, we'll break out of this loop long before we hit that many
  // iterations.
  for (uint32_t iterationCounter = 0; iterationCounter < NumItems();
       iterationCounter++) {
    // Set every not-yet-frozen item's used main size to its
    // flex base size, and subtract all the used main sizes from our
    // total amount of space to determine the 'available free space'
    // (positive or negative) to be distributed among our flexible items.
    AuCoord64 availableFreeSpace = spaceAvailableForFlexItemsContentBoxes;
    for (FlexItem& item : Items()) {
      if (!item.IsFrozen()) {
        item.SetMainSize(item.FlexBaseSize());
      }
      availableFreeSpace -= item.MainSize();
    }

    FLEX_LOG(" available free space: %" PRId64 "; flex items should \"%s\"",
             availableFreeSpace.value, isUsingFlexGrow ? "grow" : "shrink");

    // The sign of our free space should agree with the type of flexing
    // (grow/shrink) that we're doing. Any disagreement should've made us use
    // the other type of flexing, or should've been resolved in
    // FreezeItemsEarly.
    //
    // Note: it's possible that an individual flex item has huge
    // margin/border/padding that makes either its
    // MarginBorderPaddingSizeInMainAxis() or OuterMainSize() negative due to
    // integer overflow. If that happens, the accumulated
    // mTotalOuterHypotheticalMainSize or mTotalItemMBP could be negative due to
    // that one item's negative (overflowed) size. Likewise, a huge main gap
    // size between flex items can also make our accumulated SumOfGaps()
    // negative. In these case, we throw up our hands and don't require
    // isUsingFlexGrow to agree with availableFreeSpace. Luckily, we won't get
    // stuck in the algorithm below, and just distribute the wrong
    // availableFreeSpace with the wrong grow/shrink factors.
    MOZ_ASSERT(!(mTotalOuterHypotheticalMainSize >= 0 && mTotalItemMBP >= 0 &&
                 totalItemMBPAndGaps >= 0) ||
                   (isUsingFlexGrow && availableFreeSpace >= 0) ||
                   (!isUsingFlexGrow && availableFreeSpace <= 0),
               "availableFreeSpace's sign should match isUsingFlexGrow");

    // If we have any free space available, give each flexible item a portion
    // of availableFreeSpace.
    if (availableFreeSpace != AuCoord64(0)) {
      // The first time we do this, we initialize origAvailableFreeSpace.
      if (!origAvailableFreeSpace) {
        origAvailableFreeSpace.emplace(availableFreeSpace);
      }

      // STRATEGY: On each item, we compute & store its "share" of the total
      // weight that we've seen so far:
      //   curWeight / weightSum
      //
      // Then, when we go to actually distribute the space (in the next loop),
      // we can simply walk backwards through the elements and give each item
      // its "share" multiplied by the remaining available space.
      //
      // SPECIAL CASE: If the sum of the weights is larger than the
      // maximum representable double (overflowing to infinity), then we can't
      // sensibly divide out proportional shares anymore. In that case, we
      // simply treat the flex item(s) with the largest weights as if
      // their weights were infinite (dwarfing all the others), and we
      // distribute all of the available space among them.
      double weightSum = 0.0;
      double flexFactorSum = 0.0;
      double largestWeight = 0.0;
      uint32_t numItemsWithLargestWeight = 0;

      // Since this loop only operates on unfrozen flex items, we can break as
      // soon as we have seen all of them.
      uint32_t numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems;
      for (FlexItem& item : Items()) {
        if (numUnfrozenItemsToBeSeen == 0) {
          break;
        }

        if (!item.IsFrozen()) {
          numUnfrozenItemsToBeSeen--;

          const double curWeight = item.GetWeight(isUsingFlexGrow);
          const double curFlexFactor = item.GetFlexFactor(isUsingFlexGrow);
          MOZ_ASSERT(curWeight >= 0.0, "weights are non-negative");
          MOZ_ASSERT(curFlexFactor >= 0.0, "flex factors are non-negative");

          weightSum += curWeight;
          flexFactorSum += curFlexFactor;

          if (std::isfinite(weightSum)) {
            if (curWeight == 0.0) {
              item.SetShareOfWeightSoFar(0.0);
            } else {
              item.SetShareOfWeightSoFar(curWeight / weightSum);
            }
          }  // else, the sum of weights overflows to infinity, in which
             // case we don't bother with "SetShareOfWeightSoFar" since
             // we know we won't use it. (instead, we'll just give every
             // item with the largest weight an equal share of space.)

          // Update our largest-weight tracking vars
          if (curWeight > largestWeight) {
            largestWeight = curWeight;
            numItemsWithLargestWeight = 1;
          } else if (curWeight == largestWeight) {
            numItemsWithLargestWeight++;
          }
        }
      }

      MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");

      if (weightSum != 0.0) {
        MOZ_ASSERT(flexFactorSum != 0.0,
                   "flex factor sum can't be 0, if a weighted sum "
                   "of its components (weightSum) is nonzero");
        if (flexFactorSum < 1.0) {
          // Our unfrozen flex items don't want all of the original free space!
          // (Their flex factors add up to something less than 1.)
          // Hence, make sure we don't distribute any more than the portion of
          // our original free space that these items actually want.
          auto totalDesiredPortionOfOrigFreeSpace =
              AuCoord64::FromRound(*origAvailableFreeSpace * flexFactorSum);

          // Clamp availableFreeSpace to be no larger than that ^^.
          // (using min or max, depending on sign).
          // This should not change the sign of availableFreeSpace (except
          // possibly by setting it to 0), as enforced by this assertion:
          NS_ASSERTION(totalDesiredPortionOfOrigFreeSpace == AuCoord64(0) ||
                           ((totalDesiredPortionOfOrigFreeSpace > 0) ==
                            (availableFreeSpace > 0)),
                       "When we reduce available free space for flex "
                       "factors < 1, we shouldn't change the sign of the "
                       "free space...");

          if (availableFreeSpace > 0) {
            availableFreeSpace = std::min(availableFreeSpace,
                                          totalDesiredPortionOfOrigFreeSpace);
          } else {
            availableFreeSpace = std::max(availableFreeSpace,
                                          totalDesiredPortionOfOrigFreeSpace);
          }
        }

        FLEX_LOG(" Distributing available space:");
        // Since this loop only operates on unfrozen flex items, we can break as
        // soon as we have seen all of them.
        numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems;

        // NOTE: It's important that we traverse our items in *reverse* order
        // here, for correct width distribution according to the items'
        // "ShareOfWeightSoFar" progressively-calculated values.
        for (FlexItem& item : Reversed(Items())) {
          if (numUnfrozenItemsToBeSeen == 0) {
            break;
          }

          if (!item.IsFrozen()) {
            numUnfrozenItemsToBeSeen--;

            // To avoid rounding issues, we compute the change in size for this
            // item, and then subtract it from the remaining available space.
            AuCoord64 sizeDelta = 0;
            if (std::isfinite(weightSum)) {
              double myShareOfRemainingSpace = item.ShareOfWeightSoFar();

              MOZ_ASSERT(myShareOfRemainingSpace >= 0.0 &&
                             myShareOfRemainingSpace <= 1.0,
                         "my share should be nonnegative fractional amount");

              if (myShareOfRemainingSpace == 1.0) {
                // (We special-case 1.0 to avoid float error from converting
                // availableFreeSpace from integer*1.0 --> double --> integer)
                sizeDelta = availableFreeSpace;
              } else if (myShareOfRemainingSpace > 0.0) {
                sizeDelta = AuCoord64::FromRound(availableFreeSpace *
                                                 myShareOfRemainingSpace);
              }
            } else if (item.GetWeight(isUsingFlexGrow) == largestWeight) {
              // Total flexibility is infinite, so we're just distributing
              // the available space equally among the items that are tied for
              // having the largest weight (and this is one of those items).
              sizeDelta = AuCoord64::FromRound(
                  availableFreeSpace / double(numItemsWithLargestWeight));
              numItemsWithLargestWeight--;
            }

            availableFreeSpace -= sizeDelta;

            item.SetMainSize(item.MainSize() +
                             nscoord(sizeDelta.ToMinMaxClamped()));
            FLEX_LOG("  flex item %p receives %" PRId64 ", for a total of %d",
                     item.Frame(), sizeDelta.value, item.MainSize());
          }
        }

        MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");

        // If we have an aLineInfo structure to fill out, capture any
        // size changes that may have occurred in the previous loop.
        // We don't do this inside the previous loop, because we don't
        // want to burden layout when aLineInfo is null.
        if (aLineInfo) {
          uint32_t itemIndex = 0;
          for (FlexItem& item : Items()) {
            if (!item.IsFrozen()) {
              // Calculate a deltaSize that represents how much the flex sizing
              // algorithm "wants" to stretch or shrink this item during this
              // pass through the algorithm. Later passes through the algorithm
              // may overwrite this, until this item is frozen. Note that this
              // value may not reflect how much the size of the item is
              // actually changed, since the size of the item will be clamped
              // to min and max values later in this pass. That's intentional,
              // since we want to report the value that the sizing algorithm
              // tried to stretch or shrink the item.
              nscoord deltaSize =
                  item.MainSize() - aLineInfo->mItems[itemIndex].mMainBaseSize;

              aLineInfo->mItems[itemIndex].mMainDeltaSize = deltaSize;
            }
            ++itemIndex;
          }
        }
      }
    }

    // Fix min/max violations:
    nscoord totalViolation = 0;  // keeps track of adjustments for min/max
    FLEX_LOG(" Checking for violations:");

    // Since this loop only operates on unfrozen flex items, we can break as
    // soon as we have seen all of them.
    uint32_t numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems;
    for (FlexItem& item : Items()) {
      if (numUnfrozenItemsToBeSeen == 0) {
        break;
      }

      if (!item.IsFrozen()) {
        numUnfrozenItemsToBeSeen--;

        if (item.MainSize() < item.MainMinSize()) {
          // min violation
          totalViolation += item.MainMinSize() - item.MainSize();
          item.SetMainSize(item.MainMinSize());
          item.SetHadMinViolation();
        } else if (item.MainSize() > item.MainMaxSize()) {
          // max violation
          totalViolation += item.MainMaxSize() - item.MainSize();
          item.SetMainSize(item.MainMaxSize());
          item.SetHadMaxViolation();
        }
      }
    }

    MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");

    FreezeOrRestoreEachFlexibleSize(totalViolation,
                                    iterationCounter + 1 == NumItems());

    FLEX_LOG(" Total violation: %d", totalViolation);

    if (mNumFrozenItems == NumItems()) {
      break;
    }

    MOZ_ASSERT(totalViolation != 0,
               "Zero violation should've made us freeze all items & break");
  }

#ifdef DEBUG
  // Post-condition: all items should've been frozen.
  // Make sure the counts match:
  MOZ_ASSERT(mNumFrozenItems == NumItems(), "All items should be frozen");

  // For good measure, check each item directly, in case our counts are busted:
  for (const FlexItem& item : Items()) {
    MOZ_ASSERT(item.IsFrozen(), "All items should be frozen");
  }
#endif  // DEBUG
}

MainAxisPositionTracker::MainAxisPositionTracker(
    const FlexboxAxisTracker& aAxisTracker, const FlexLine* aLine,
    const StyleContentDistribution& aJustifyContent,
    nscoord aContentBoxMainSize)
    : PositionTracker(aAxisTracker.GetWritingMode(), aAxisTracker.MainAxis(),
                      aAxisTracker.IsMainAxisReversed()),
      // we chip away at this below
      mPackingSpaceRemaining(aContentBoxMainSize),
      mJustifyContent(aJustifyContent) {
  // Extract the flag portion of mJustifyContent and strip off the flag bits
  // NOTE: This must happen before any assignment to mJustifyContent to
  // avoid overwriting the flag bits.
  StyleAlignFlags justifyContentFlags =
      mJustifyContent.primary & StyleAlignFlags::FLAG_BITS;
  mJustifyContent.primary &= ~StyleAlignFlags::FLAG_BITS;

  // 'normal' behaves as 'stretch', and 'stretch' behaves as 'flex-start',
  // in the main axis
  // https://drafts.csswg.org/css-align-3/#propdef-justify-content
  if (mJustifyContent.primary == StyleAlignFlags::NORMAL ||
      mJustifyContent.primary == StyleAlignFlags::STRETCH) {
    mJustifyContent.primary = StyleAlignFlags::FLEX_START;
  }

  // mPackingSpaceRemaining is initialized to the container's main size.  Now
  // we'll subtract out the main sizes of our flex items, so that it ends up
  // with the *actual* amount of packing space.
  for (const FlexItem& item : aLine->Items()) {
    mPackingSpaceRemaining -= item.OuterMainSize();
    mNumAutoMarginsInMainAxis += item.NumAutoMarginsInMainAxis();
  }

  // Subtract space required for row/col gap from the remaining packing space
  mPackingSpaceRemaining -= aLine->SumOfGaps();

  if (mPackingSpaceRemaining <= 0) {
    // No available packing space to use for resolving auto margins.
    mNumAutoMarginsInMainAxis = 0;
    // If packing space is negative and <overflow-position> is set to 'safe'
    // all justify options fall back to 'start'
    if (justifyContentFlags & StyleAlignFlags::SAFE) {
      mJustifyContent.primary = StyleAlignFlags::START;
    }
  }

  // If packing space is negative or we only have one item, 'space-between'
  // falls back to 'flex-start', and 'space-around' & 'space-evenly' fall back
  // to 'center'. In those cases, it's simplest to just pretend we have a
  // different 'justify-content' value and share code.
  if (mPackingSpaceRemaining < 0 || aLine->NumItems() == 1) {
    if (mJustifyContent.primary == StyleAlignFlags::SPACE_BETWEEN) {
      mJustifyContent.primary = StyleAlignFlags::FLEX_START;
    } else if (mJustifyContent.primary == StyleAlignFlags::SPACE_AROUND ||
               mJustifyContent.primary == StyleAlignFlags::SPACE_EVENLY) {
      mJustifyContent.primary = StyleAlignFlags::CENTER;
    }
  }

  // Map 'left'/'right' to 'start'/'end'
  if (mJustifyContent.primary == StyleAlignFlags::LEFT ||
      mJustifyContent.primary == StyleAlignFlags::RIGHT) {
    mJustifyContent.primary =
        aAxisTracker.ResolveJustifyLeftRight(mJustifyContent.primary);
  }

  // Map 'start'/'end' to 'flex-start'/'flex-end'.
  if (mJustifyContent.primary == StyleAlignFlags::START) {
    mJustifyContent.primary = aAxisTracker.IsMainAxisReversed()
                                  ? StyleAlignFlags::FLEX_END
                                  : StyleAlignFlags::FLEX_START;
  } else if (mJustifyContent.primary == StyleAlignFlags::END) {
    mJustifyContent.primary = aAxisTracker.IsMainAxisReversed()
                                  ? StyleAlignFlags::FLEX_START
                                  : StyleAlignFlags::FLEX_END;
  }

  // Figure out how much space we'll set aside for auto margins or
  // packing spaces, and advance past any leading packing-space.
  if (mNumAutoMarginsInMainAxis == 0 && mPackingSpaceRemaining != 0 &&
      !aLine->IsEmpty()) {
    if (mJustifyContent.primary == StyleAlignFlags::FLEX_START) {
      // All packing space should go at the end --> nothing to do here.
    } else if (mJustifyContent.primary == StyleAlignFlags::FLEX_END) {
      // All packing space goes at the beginning
      mPosition += mPackingSpaceRemaining;
    } else if (mJustifyContent.primary == StyleAlignFlags::CENTER) {
      // Half the packing space goes at the beginning
      mPosition += mPackingSpaceRemaining / 2;
    } else if (mJustifyContent.primary == StyleAlignFlags::SPACE_BETWEEN ||
               mJustifyContent.primary == StyleAlignFlags::SPACE_AROUND ||
               mJustifyContent.primary == StyleAlignFlags::SPACE_EVENLY) {
      nsFlexContainerFrame::CalculatePackingSpace(
          aLine->NumItems(), mJustifyContent, &mPosition,
          &mNumPackingSpacesRemaining, &mPackingSpaceRemaining);
    } else {
      MOZ_ASSERT_UNREACHABLE("Unexpected justify-content value");
    }
  }

  MOZ_ASSERT(mNumPackingSpacesRemaining == 0 || mNumAutoMarginsInMainAxis == 0,
             "extra space should either go to packing space or to "
             "auto margins, but not to both");
}

void MainAxisPositionTracker::ResolveAutoMarginsInMainAxis(FlexItem& aItem) {
  if (mNumAutoMarginsInMainAxis) {
    const auto& styleMargin = aItem.Frame()->StyleMargin()->mMargin;
    for (const auto side : {StartSide(), EndSide()}) {
      if (styleMargin.Get(mWM, side).IsAuto()) {
        // NOTE: This integer math will skew the distribution of remainder
        // app-units towards the end, which is fine.
        nscoord curAutoMarginSize =
            mPackingSpaceRemaining / mNumAutoMarginsInMainAxis;

        MOZ_ASSERT(aItem.GetMarginComponentForSide(side) == 0,
                   "Expecting auto margins to have value '0' before we "
                   "resolve them");
        aItem.SetMarginComponentForSide(side, curAutoMarginSize);

        mNumAutoMarginsInMainAxis--;
        mPackingSpaceRemaining -= curAutoMarginSize;
      }
    }
  }
}

void MainAxisPositionTracker::TraversePackingSpace() {
  if (mNumPackingSpacesRemaining) {
    MOZ_ASSERT(mJustifyContent.primary == StyleAlignFlags::SPACE_BETWEEN ||
                   mJustifyContent.primary == StyleAlignFlags::SPACE_AROUND ||
                   mJustifyContent.primary == StyleAlignFlags::SPACE_EVENLY,
               "mNumPackingSpacesRemaining only applies for "
               "space-between/space-around/space-evenly");

    MOZ_ASSERT(mPackingSpaceRemaining >= 0,
               "ran out of packing space earlier than we expected");

    // NOTE: This integer math will skew the distribution of remainder
    // app-units towards the end, which is fine.
    nscoord curPackingSpace =
        mPackingSpaceRemaining / mNumPackingSpacesRemaining;

    mPosition += curPackingSpace;
    mNumPackingSpacesRemaining--;
    mPackingSpaceRemaining -= curPackingSpace;
  }
}

CrossAxisPositionTracker::CrossAxisPositionTracker(
    nsTArray<FlexLine>& aLines, const ReflowInput& aReflowInput,
    nscoord aContentBoxCrossSize, bool aIsCrossSizeDefinite,
    const FlexboxAxisTracker& aAxisTracker, const nscoord aCrossGapSize)
    : PositionTracker(aAxisTracker.GetWritingMode(), aAxisTracker.CrossAxis(),
                      aAxisTracker.IsCrossAxisReversed()),
      mAlignContent(aReflowInput.mStylePosition->mAlignContent),
      mCrossGapSize(aCrossGapSize) {
  // Extract and strip the flag bits from alignContent
  StyleAlignFlags alignContentFlags =
      mAlignContent.primary & StyleAlignFlags::FLAG_BITS;
  mAlignContent.primary &= ~StyleAlignFlags::FLAG_BITS;

  // 'normal' behaves as 'stretch'
  if (mAlignContent.primary == StyleAlignFlags::NORMAL) {
    mAlignContent.primary = StyleAlignFlags::STRETCH;
  }

  const bool isSingleLine =
      StyleFlexWrap::Nowrap == aReflowInput.mStylePosition->mFlexWrap;
  if (isSingleLine) {
    MOZ_ASSERT(aLines.Length() == 1,
               "If we're styled as single-line, we should only have 1 line");
    // "If the flex container is single-line and has a definite cross size, the
    // cross size of the flex line is the flex container's inner cross size."
    //
    // SOURCE: https://drafts.csswg.org/css-flexbox/#algo-cross-line
    // NOTE: This means (by definition) that there's no packing space, which
    // means we don't need to be concerned with "align-content" at all and we
    // can return early. This is handy, because this is the usual case (for
    // single-line flexbox).
    if (aIsCrossSizeDefinite) {
      aLines[0].SetLineCrossSize(aContentBoxCrossSize);
      return;
    }

    // "If the flex container is single-line, then clamp the line's
    // cross-size to be within the container's computed min and max cross-size
    // properties."
    aLines[0].SetLineCrossSize(NS_CSS_MINMAX(aLines[0].LineCrossSize(),
                                             aReflowInput.ComputedMinBSize(),
                                             aReflowInput.ComputedMaxBSize()));
  }

  // NOTE: The rest of this function should essentially match
  // MainAxisPositionTracker's constructor, though with FlexLines instead of
  // FlexItems, and with the additional value "stretch" (and of course with
  // cross sizes instead of main sizes.)

  // Figure out how much packing space we have (container's cross size minus
  // all the lines' cross sizes).  Also, share this loop to count how many
  // lines we have. (We need that count in some cases below.)
  mPackingSpaceRemaining = aContentBoxCrossSize;
  uint32_t numLines = 0;
  for (FlexLine& line : aLines) {
    mPackingSpaceRemaining -= line.LineCrossSize();
    numLines++;
  }

  // Subtract space required for row/col gap from the remaining packing space
  MOZ_ASSERT(numLines >= 1,
             "GenerateFlexLines should've produced at least 1 line");
  mPackingSpaceRemaining -= aCrossGapSize * (numLines - 1);

  // If <overflow-position> is 'safe' and packing space is negative
  // all align options fall back to 'start'
  if ((alignContentFlags & StyleAlignFlags::SAFE) &&
      mPackingSpaceRemaining < 0) {
    mAlignContent.primary = StyleAlignFlags::START;
  }

  // If packing space is negative, 'space-between' and 'stretch' behave like
  // 'flex-start', and 'space-around' and 'space-evenly' behave like 'center'.
  // In those cases, it's simplest to just pretend we have a different
  // 'align-content' value and share code. (If we only have one line, all of
  // the 'space-*' keywords fall back as well, but 'stretch' doesn't because
  // even a single line can still stretch.)
  if (mPackingSpaceRemaining < 0 &&
      mAlignContent.primary == StyleAlignFlags::STRETCH) {
    mAlignContent.primary = StyleAlignFlags::FLEX_START;
  } else if (mPackingSpaceRemaining < 0 || numLines == 1) {
    if (mAlignContent.primary == StyleAlignFlags::SPACE_BETWEEN) {
      mAlignContent.primary = StyleAlignFlags::FLEX_START;
    } else if (mAlignContent.primary == StyleAlignFlags::SPACE_AROUND ||
               mAlignContent.primary == StyleAlignFlags::SPACE_EVENLY) {
      mAlignContent.primary = StyleAlignFlags::CENTER;
    }
  }

  // Map 'start'/'end' to 'flex-start'/'flex-end'.
  if (mAlignContent.primary == StyleAlignFlags::START) {
    mAlignContent.primary = aAxisTracker.IsCrossAxisReversed()
                                ? StyleAlignFlags::FLEX_END
                                : StyleAlignFlags::FLEX_START;
  } else if (mAlignContent.primary == StyleAlignFlags::END) {
    mAlignContent.primary = aAxisTracker.IsCrossAxisReversed()
                                ? StyleAlignFlags::FLEX_START
                                : StyleAlignFlags::FLEX_END;
  }

  // Figure out how much space we'll set aside for packing spaces, and advance
  // past any leading packing-space.
  if (mPackingSpaceRemaining != 0) {
    if (mAlignContent.primary == StyleAlignFlags::BASELINE ||
        mAlignContent.primary == StyleAlignFlags::LAST_BASELINE) {
      // TODO: Bug 1480850 will implement 'align-content: [first/last] baseline'
      // for flexbox. Until then, behaves as if align-content is 'flex-start' by
      // doing nothing.
    } else if (mAlignContent.primary == StyleAlignFlags::FLEX_START) {
      // All packing space should go at the end --> nothing to do here.
    } else if (mAlignContent.primary == StyleAlignFlags::FLEX_END) {
      // All packing space goes at the beginning
      mPosition += mPackingSpaceRemaining;
    } else if (mAlignContent.primary == StyleAlignFlags::CENTER) {
      // Half the packing space goes at the beginning
      mPosition += mPackingSpaceRemaining / 2;
    } else if (mAlignContent.primary == StyleAlignFlags::SPACE_BETWEEN ||
               mAlignContent.primary == StyleAlignFlags::SPACE_AROUND ||
               mAlignContent.primary == StyleAlignFlags::SPACE_EVENLY) {
      nsFlexContainerFrame::CalculatePackingSpace(
          numLines, mAlignContent, &mPosition, &mNumPackingSpacesRemaining,
          &mPackingSpaceRemaining);
    } else if (mAlignContent.primary == StyleAlignFlags::STRETCH) {
      // Split space equally between the lines:
      MOZ_ASSERT(mPackingSpaceRemaining > 0,
                 "negative packing space should make us use 'flex-start' "
                 "instead of 'stretch' (and we shouldn't bother with this "
                 "code if we have 0 packing space)");

      uint32_t numLinesLeft = numLines;
      for (FlexLine& line : aLines) {
        // Our share is the amount of space remaining, divided by the number
        // of lines remainig.
        MOZ_ASSERT(numLinesLeft > 0, "miscalculated num lines");
        nscoord shareOfExtraSpace = mPackingSpaceRemaining / numLinesLeft;
        nscoord newSize = line.LineCrossSize() + shareOfExtraSpace;
        line.SetLineCrossSize(newSize);

        mPackingSpaceRemaining -= shareOfExtraSpace;
        numLinesLeft--;
      }
      MOZ_ASSERT(numLinesLeft == 0, "miscalculated num lines");
    } else {
      MOZ_ASSERT_UNREACHABLE("Unexpected align-content value");
    }
  }
}

void CrossAxisPositionTracker::TraversePackingSpace() {
  if (mNumPackingSpacesRemaining) {
    MOZ_ASSERT(mAlignContent.primary == StyleAlignFlags::SPACE_BETWEEN ||
                   mAlignContent.primary == StyleAlignFlags::SPACE_AROUND ||
                   mAlignContent.primary == StyleAlignFlags::SPACE_EVENLY,
               "mNumPackingSpacesRemaining only applies for "
               "space-between/space-around/space-evenly");

    MOZ_ASSERT(mPackingSpaceRemaining >= 0,
               "ran out of packing space earlier than we expected");

    // NOTE: This integer math will skew the distribution of remainder
    // app-units towards the end, which is fine.
    nscoord curPackingSpace =
        mPackingSpaceRemaining / mNumPackingSpacesRemaining;

    mPosition += curPackingSpace;
    mNumPackingSpacesRemaining--;
    mPackingSpaceRemaining -= curPackingSpace;
  }
}

SingleLineCrossAxisPositionTracker::SingleLineCrossAxisPositionTracker(
    const FlexboxAxisTracker& aAxisTracker)
    : PositionTracker(aAxisTracker.GetWritingMode(), aAxisTracker.CrossAxis(),
                      aAxisTracker.IsCrossAxisReversed()) {}

void FlexLine::ComputeCrossSizeAndBaseline(
    const FlexboxAxisTracker& aAxisTracker) {
  // NOTE: in these "cross{Start,End}ToFurthest{First,Last}Baseline" variables,
  // the "first/last" term is referring to the flex *line's* baseline-sharing
  // groups, which may or may not match any flex *item's* exact align-self
  // value. See the code that sets FlexItem::mBaselineSharingGroup for more
  // details.
  nscoord crossStartToFurthestFirstBaseline = nscoord_MIN;
  nscoord crossEndToFurthestFirstBaseline = nscoord_MIN;
  nscoord crossStartToFurthestLastBaseline = nscoord_MIN;
  nscoord crossEndToFurthestLastBaseline = nscoord_MIN;

  nscoord largestOuterCrossSize = 0;
  for (const FlexItem& item : Items()) {
    nscoord curOuterCrossSize = item.OuterCrossSize();

    if ((item.AlignSelf()._0 == StyleAlignFlags::BASELINE ||
         item.AlignSelf()._0 == StyleAlignFlags::LAST_BASELINE) &&
        item.NumAutoMarginsInCrossAxis() == 0) {
      const bool usingItemFirstBaseline =
          (item.AlignSelf()._0 == StyleAlignFlags::BASELINE);

      // Find distance from our item's cross-start and cross-end margin-box
      // edges to its baseline.
      //
      // Here's a diagram of a flex-item that we might be doing this on.
      // "mmm" is the margin-box, "bbb" is the border-box. The bottom of
      // the text "BASE" is the baseline.
      //
      // ---(cross-start)---
      //                ___              ___            ___
      //   mmmmmmmmmmmm  |                |margin-start  |
      //   m          m  |               _|_   ___       |
      //   m bbbbbbbb m  |curOuterCrossSize     |        |crossStartToBaseline
      //   m b      b m  |                      |ascent  |
      //   m b BASE b m  |                     _|_      _|_
      //   m b      b m  |                               |
      //   m bbbbbbbb m  |                               |crossEndToBaseline
      //   m          m  |                               |
      //   mmmmmmmmmmmm _|_                             _|_
      //
      // ---(cross-end)---
      //
      // We already have the curOuterCrossSize, margin-start, and the ascent.
      // * We can get crossStartToBaseline by adding margin-start + ascent.
      // * If we subtract that from the curOuterCrossSize, we get
      //   crossEndToBaseline.

      nscoord crossStartToBaseline = item.BaselineOffsetFromOuterCrossEdge(
          aAxisTracker.CrossAxisPhysicalStartSide(), usingItemFirstBaseline);
      nscoord crossEndToBaseline = curOuterCrossSize - crossStartToBaseline;

      // Now, update our "largest" values for these (across all the flex items
      // in this flex line), so we can use them in computing the line's cross
      // size below:
      if (item.ItemBaselineSharingGroup() == BaselineSharingGroup::First) {
        crossStartToFurthestFirstBaseline =
            std::max(crossStartToFurthestFirstBaseline, crossStartToBaseline);
        crossEndToFurthestFirstBaseline =
            std::max(crossEndToFurthestFirstBaseline, crossEndToBaseline);
      } else {
        crossStartToFurthestLastBaseline =
            std::max(crossStartToFurthestLastBaseline, crossStartToBaseline);
        crossEndToFurthestLastBaseline =
            std::max(crossEndToFurthestLastBaseline, crossEndToBaseline);
      }
    } else {
      largestOuterCrossSize =
          std::max(largestOuterCrossSize, curOuterCrossSize);
    }
  }

  // The line's baseline offset is the distance from the line's edge to the
  // furthest item-baseline. The item(s) with that baseline will be exactly
  // aligned with the line's edge.
  mFirstBaselineOffset = crossStartToFurthestFirstBaseline;
  mLastBaselineOffset = crossEndToFurthestLastBaseline;

  // The line's cross-size is the larger of:
  //  (a) [largest cross-start-to-baseline + largest baseline-to-cross-end] of
  //      all baseline-aligned items with no cross-axis auto margins...
  // and
  //  (b) [largest cross-start-to-baseline + largest baseline-to-cross-end] of
  //      all last baseline-aligned items with no cross-axis auto margins...
  // and
  //  (c) largest cross-size of all other children.
  mLineCrossSize = std::max(
      std::max(
          crossStartToFurthestFirstBaseline + crossEndToFurthestFirstBaseline,
          crossStartToFurthestLastBaseline + crossEndToFurthestLastBaseline),
      largestOuterCrossSize);
}

nscoord FlexLine::ExtractBaselineOffset(
    BaselineSharingGroup aBaselineGroup) const {
  auto LastBaselineOffsetFromStartEdge = [this]() {
    // Convert the distance to be relative from the line's cross-start edge.
    const nscoord offset = LastBaselineOffset();
    return offset != nscoord_MIN ? LineCrossSize() - offset : offset;
  };

  auto PrimaryBaseline = [=]() {
    return aBaselineGroup == BaselineSharingGroup::First
               ? FirstBaselineOffset()
               : LastBaselineOffsetFromStartEdge();
  };
  auto SecondaryBaseline = [=]() {
    return aBaselineGroup == BaselineSharingGroup::First
               ? LastBaselineOffsetFromStartEdge()
               : FirstBaselineOffset();
  };

  const nscoord primaryBaseline = PrimaryBaseline();
  if (primaryBaseline != nscoord_MIN) {
    return primaryBaseline;
  }
  return SecondaryBaseline();
}

void FlexItem::ResolveStretchedCrossSize(nscoord aLineCrossSize) {
  // We stretch IFF we are align-self:stretch, have no auto margins in
  // cross axis, and have cross-axis size property == "auto". If any of those
  // conditions don't hold up, we won't stretch.
  if (mAlignSelf._0 != StyleAlignFlags::STRETCH ||
      NumAutoMarginsInCrossAxis() != 0 || !IsCrossSizeAuto()) {
    return;
  }

  // If we've already been stretched, we can bail out early, too.
  // No need to redo the calculation.
  if (mIsStretched) {
    return;
  }

  // Reserve space for margins & border & padding, and then use whatever
  // remains as our item's cross-size (clamped to its min/max range).
  nscoord stretchedSize = aLineCrossSize - MarginBorderPaddingSizeInCrossAxis();

  stretchedSize = NS_CSS_MINMAX(stretchedSize, mCrossMinSize, mCrossMaxSize);

  // Update the cross-size & make a note that it's stretched, so we know to
  // override the reflow input's computed cross-size in our final reflow.
  SetCrossSize(stretchedSize);
  mIsStretched = true;
}

static nsBlockFrame* FindFlexItemBlockFrame(nsIFrame* aFrame) {
  if (nsBlockFrame* block = do_QueryFrame(aFrame)) {
    return block;
  }
  for (nsIFrame* f : aFrame->PrincipalChildList()) {
    if (nsBlockFrame* block = FindFlexItemBlockFrame(f)) {
      return block;
    }
  }
  return nullptr;
}

nsBlockFrame* FlexItem::BlockFrame() const {
  return FindFlexItemBlockFrame(Frame());
}

void SingleLineCrossAxisPositionTracker::ResolveAutoMarginsInCrossAxis(
    const FlexLine& aLine, FlexItem& aItem) {
  // Subtract the space that our item is already occupying, to see how much
  // space (if any) is available for its auto margins.
  nscoord spaceForAutoMargins = aLine.LineCrossSize() - aItem.OuterCrossSize();

  if (spaceForAutoMargins <= 0) {
    return;  // No available space  --> nothing to do
  }

  uint32_t numAutoMargins = aItem.NumAutoMarginsInCrossAxis();
  if (numAutoMargins == 0) {
    return;  // No auto margins --> nothing to do.
  }

  // OK, we have at least one auto margin and we have some available space.
  // Give each auto margin a share of the space.
  const auto& styleMargin = aItem.Frame()->StyleMargin()->mMargin;
  for (const auto side : {StartSide(), EndSide()}) {
    if (styleMargin.Get(mWM, side).IsAuto()) {
      MOZ_ASSERT(aItem.GetMarginComponentForSide(side) == 0,
                 "Expecting auto margins to have value '0' before we "
                 "update them");

      // NOTE: integer divison is fine here; numAutoMargins is either 1 or 2.
      // If it's 2 & spaceForAutoMargins is odd, 1st margin gets smaller half.
      nscoord curAutoMarginSize = spaceForAutoMargins / numAutoMargins;
      aItem.SetMarginComponentForSide(side, curAutoMarginSize);
      numAutoMargins--;
      spaceForAutoMargins -= curAutoMarginSize;
    }
  }
}

void SingleLineCrossAxisPositionTracker::EnterAlignPackingSpace(
    const FlexLine& aLine, const FlexItem& aItem,
    const FlexboxAxisTracker& aAxisTracker) {
  // We don't do align-self alignment on items that have auto margins
  // in the cross axis.
  if (aItem.NumAutoMarginsInCrossAxis()) {
    return;
  }

  StyleAlignFlags alignSelf = aItem.AlignSelf()._0;
  // NOTE: 'stretch' behaves like 'flex-start' once we've stretched any
  // auto-sized items (which we've already done).
  if (alignSelf == StyleAlignFlags::STRETCH) {
    alignSelf = StyleAlignFlags::FLEX_START;
  }

  // Map 'self-start'/'self-end' to 'start'/'end'
  if (alignSelf == StyleAlignFlags::SELF_START ||
      alignSelf == StyleAlignFlags::SELF_END) {
    const LogicalAxis logCrossAxis =
        aAxisTracker.IsRowOriented() ? eLogicalAxisBlock : eLogicalAxisInline;
    const WritingMode cWM = aAxisTracker.GetWritingMode();
    const bool sameStart =
        cWM.ParallelAxisStartsOnSameSide(logCrossAxis, aItem.GetWritingMode());
    alignSelf = sameStart == (alignSelf == StyleAlignFlags::SELF_START)
                    ? StyleAlignFlags::START
                    : StyleAlignFlags::END;
  }

  // Map 'start'/'end' to 'flex-start'/'flex-end'.
  if (alignSelf == StyleAlignFlags::START) {
    alignSelf = aAxisTracker.IsCrossAxisReversed()
                    ? StyleAlignFlags::FLEX_END
                    : StyleAlignFlags::FLEX_START;
  } else if (alignSelf == StyleAlignFlags::END) {
    alignSelf = aAxisTracker.IsCrossAxisReversed() ? StyleAlignFlags::FLEX_START
                                                   : StyleAlignFlags::FLEX_END;
  }

  // 'align-self' falls back to 'flex-start' if it is 'center'/'flex-end' and we
  // have cross axis overflow
  // XXX we should really be falling back to 'start' as of bug 1472843
  if (aLine.LineCrossSize() < aItem.OuterCrossSize() &&
      (aItem.AlignSelfFlags() & StyleAlignFlags::SAFE)) {
    alignSelf = StyleAlignFlags::FLEX_START;
  }

  if (alignSelf == StyleAlignFlags::FLEX_START) {
    // No space to skip over -- we're done.
  } else if (alignSelf == StyleAlignFlags::FLEX_END) {
    mPosition += aLine.LineCrossSize() - aItem.OuterCrossSize();
  } else if (alignSelf == StyleAlignFlags::CENTER) {
    // Note: If cross-size is odd, the "after" space will get the extra unit.
    mPosition += (aLine.LineCrossSize() - aItem.OuterCrossSize()) / 2;
  } else if (alignSelf == StyleAlignFlags::BASELINE ||
             alignSelf == StyleAlignFlags::LAST_BASELINE) {
    const bool usingItemFirstBaseline =
        (alignSelf == StyleAlignFlags::BASELINE);

    // The first-baseline sharing group gets (collectively) aligned to the
    // FlexLine's cross-start side, and similarly the last-baseline sharing
    // group gets snapped to the cross-end side.
    const bool isFirstBaselineSharingGroup =
        aItem.ItemBaselineSharingGroup() == BaselineSharingGroup::First;
    const mozilla::Side alignSide =
        isFirstBaselineSharingGroup ? aAxisTracker.CrossAxisPhysicalStartSide()
                                    : aAxisTracker.CrossAxisPhysicalEndSide();

    // To compute the aligned position for our flex item, we determine:
    // (1) The distance from the item's alignSide edge to the item's relevant
    //     baseline.
    nscoord itemBaselineOffset = aItem.BaselineOffsetFromOuterCrossEdge(
        alignSide, usingItemFirstBaseline);

    // (2) The distance between the FlexLine's alignSide edge and the relevant
    //     baseline-sharing-group's baseline position.
    nscoord lineBaselineOffset = isFirstBaselineSharingGroup
                                     ? aLine.FirstBaselineOffset()
                                     : aLine.LastBaselineOffset();

    NS_ASSERTION(lineBaselineOffset >= itemBaselineOffset,
                 "failed at finding largest baseline offset");

    // (3) The difference between the above offsets, which tells us how far we
    //     need to shift the item away from the FlexLine's alignSide edge so
    //     that its baseline is at the proper position for its group.
    nscoord itemOffsetFromLineEdge = lineBaselineOffset - itemBaselineOffset;

    if (isFirstBaselineSharingGroup) {
      // alignSide is the line's cross-start edge. mPosition is already there.
      // From there, we step *forward* by the baseline adjustment:
      mPosition += itemOffsetFromLineEdge;
    } else {
      // alignSide is the line's cross-end edge. Advance mPosition to align
      // item with that edge (as in FLEX_END case)...
      mPosition += aLine.LineCrossSize() - aItem.OuterCrossSize();
      // ...and step *back* by the baseline adjustment:
      mPosition -= itemOffsetFromLineEdge;
    }
  } else {
    MOZ_ASSERT_UNREACHABLE("Unexpected align-self value");
  }
}

FlexboxAxisInfo::FlexboxAxisInfo(const nsIFrame* aFlexContainer) {
  MOZ_ASSERT(aFlexContainer && aFlexContainer->IsFlexContainerFrame(),
             "Only flex containers may be passed to this constructor!");
  if (IsLegacyBox(aFlexContainer)) {
    InitAxesFromLegacyProps(aFlexContainer);
  } else {
    InitAxesFromModernProps(aFlexContainer);
  }
}

void FlexboxAxisInfo::InitAxesFromLegacyProps(const nsIFrame* aFlexContainer) {
  const nsStyleXUL* styleXUL = aFlexContainer->StyleXUL();

  const bool boxOrientIsVertical =
      styleXUL->mBoxOrient == StyleBoxOrient::Vertical;
  const bool wmIsVertical = aFlexContainer->GetWritingMode().IsVertical();

  // If box-orient agrees with our writing-mode, then we're "row-oriented"
  // (i.e. the flexbox main axis is the same as our writing mode's inline
  // direction).  Otherwise, we're column-oriented (i.e. the flexbox's main
  // axis is perpendicular to the writing-mode's inline direction).
  mIsRowOriented = (boxOrientIsVertical == wmIsVertical);

  // Legacy flexbox can use "-webkit-box-direction: reverse" to reverse the
  // main axis (so it runs in the reverse direction of the inline axis):
  mIsMainAxisReversed = styleXUL->mBoxDirection == StyleBoxDirection::Reverse;

  // Legacy flexbox does not support reversing the cross axis -- it has no
  // equivalent of modern flexbox's "flex-wrap: wrap-reverse".
  mIsCrossAxisReversed = false;
}

void FlexboxAxisInfo::InitAxesFromModernProps(const nsIFrame* aFlexContainer) {
  const nsStylePosition* stylePos = aFlexContainer->StylePosition();
  StyleFlexDirection flexDirection = stylePos->mFlexDirection;

  // Determine main axis:
  switch (flexDirection) {
    case StyleFlexDirection::Row:
      mIsRowOriented = true;
      mIsMainAxisReversed = false;
      break;
    case StyleFlexDirection::RowReverse:
      mIsRowOriented = true;
      mIsMainAxisReversed = true;
      break;
    case StyleFlexDirection::Column:
      mIsRowOriented = false;
      mIsMainAxisReversed = false;
      break;
    case StyleFlexDirection::ColumnReverse:
      mIsRowOriented = false;
      mIsMainAxisReversed = true;
      break;
  }

  // "flex-wrap: wrap-reverse" reverses our cross axis.
  mIsCrossAxisReversed = stylePos->mFlexWrap == StyleFlexWrap::WrapReverse;
}

FlexboxAxisTracker::FlexboxAxisTracker(
    const nsFlexContainerFrame* aFlexContainer)
    : mWM(aFlexContainer->GetWritingMode()), mAxisInfo(aFlexContainer) {}

LogicalSide FlexboxAxisTracker::MainAxisStartSide() const {
  return MakeLogicalSide(
      MainAxis(), IsMainAxisReversed() ? eLogicalEdgeEnd : eLogicalEdgeStart);
}

LogicalSide FlexboxAxisTracker::CrossAxisStartSide() const {
  return MakeLogicalSide(
      CrossAxis(), IsCrossAxisReversed() ? eLogicalEdgeEnd : eLogicalEdgeStart);
}

void nsFlexContainerFrame::GenerateFlexLines(
    const ReflowInput& aReflowInput, const nscoord aTentativeContentBoxMainSize,
    const nscoord aTentativeContentBoxCrossSize,
    const nsTArray<StrutInfo>& aStruts, const FlexboxAxisTracker& aAxisTracker,
    nscoord aMainGapSize, nsTArray<nsIFrame*>& aPlaceholders,
    nsTArray<FlexLine>& aLines, bool& aHasCollapsedItems) {
  MOZ_ASSERT(aLines.IsEmpty(), "Expecting outparam to start out empty");

  auto ConstructNewFlexLine = [&aLines, aMainGapSize]() {
    return aLines.EmplaceBack(aMainGapSize);
  };

  const bool isSingleLine =
      StyleFlexWrap::Nowrap == aReflowInput.mStylePosition->mFlexWrap;

  // We have at least one FlexLine. Even an empty flex container has a single
  // (empty) flex line.
  FlexLine* curLine = ConstructNewFlexLine();

  nscoord wrapThreshold;
  if (isSingleLine) {
    // Not wrapping. Set threshold to sentinel value that tells us not to wrap.
    wrapThreshold = NS_UNCONSTRAINEDSIZE;
  } else {
    // Wrapping! Set wrap threshold to flex container's content-box main-size.
    wrapThreshold = aTentativeContentBoxMainSize;

    // If the flex container doesn't have a definite content-box main-size
    // (e.g. if main axis is vertical & 'height' is 'auto'), make sure we at
    // least wrap when we hit its max main-size.
    if (wrapThreshold == NS_UNCONSTRAINEDSIZE) {
      const nscoord flexContainerMaxMainSize =
          aAxisTracker.MainComponent(aReflowInput.ComputedMaxSize());
      wrapThreshold = flexContainerMaxMainSize;
    }
  }

  // Tracks the index of the next strut, in aStruts (and when this hits
  // aStruts.Length(), that means there are no more struts):
  uint32_t nextStrutIdx = 0;

  // Overall index of the current flex item in the flex container. (This gets
  // checked against entries in aStruts.)
  uint32_t itemIdxInContainer = 0;

  CSSOrderAwareFrameIterator iter(
      this, FrameChildListID::Principal,
      CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
      CSSOrderAwareFrameIterator::OrderState::Unknown,
      OrderingPropertyForIter(this));

  AddOrRemoveStateBits(NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER,
                       iter.ItemsAreAlreadyInOrder());

  const bool useMozBoxCollapseBehavior =
      StyleVisibility()->UseLegacyCollapseBehavior();

  for (; !iter.AtEnd(); iter.Next()) {
    nsIFrame* childFrame = *iter;
    // Don't create flex items / lines for placeholder frames:
    if (childFrame->IsPlaceholderFrame()) {
      aPlaceholders.AppendElement(childFrame);
      continue;
    }

    const bool collapsed = childFrame->StyleVisibility()->IsCollapse();
    aHasCollapsedItems = aHasCollapsedItems || collapsed;

    if (useMozBoxCollapseBehavior && collapsed) {
      // Legacy visibility:collapse behavior: make a 0-sized strut. (No need to
      // bother with aStruts and remembering cross size.)
      curLine->Items().EmplaceBack(childFrame, 0, aReflowInput.GetWritingMode(),
                                   aAxisTracker);
    } else if (nextStrutIdx < aStruts.Length() &&
               aStruts[nextStrutIdx].mItemIdx == itemIdxInContainer) {
      // Use the simplified "strut" FlexItem constructor:
      curLine->Items().EmplaceBack(childFrame,
                                   aStruts[nextStrutIdx].mStrutCrossSize,
                                   aReflowInput.GetWritingMode(), aAxisTracker);
      nextStrutIdx++;
    } else {
      GenerateFlexItemForChild(*curLine, childFrame, aReflowInput, aAxisTracker,
                               aTentativeContentBoxCrossSize);
    }

    // Check if we need to wrap the newly appended item to a new line, i.e. if
    // its outer hypothetical main size pushes our line over the threshold.
    // But we don't wrap if the line-length is unconstrained, nor do we wrap if
    // this was the first item on the line.
    if (wrapThreshold != NS_UNCONSTRAINEDSIZE &&
        curLine->Items().Length() > 1) {
      // If the line will be longer than wrapThreshold or at least as long as
      // nscoord_MAX because of the newly appended item, then wrap and move the
      // item to a new line.
      auto newOuterSize = curLine->TotalOuterHypotheticalMainSize();
      newOuterSize += curLine->Items().LastElement().OuterMainSize();

      // Account for gap between this line's previous item and this item.
      newOuterSize += aMainGapSize;

      if (newOuterSize >= nscoord_MAX || newOuterSize > wrapThreshold) {
        curLine = ConstructNewFlexLine();

        // Get the previous line after adding a new line because the address can
        // change if nsTArray needs to reallocate a new space for the new line.
        FlexLine& prevLine = aLines[aLines.Length() - 2];

        // Move the item from the end of prevLine to the end of curLine.
        curLine->Items().AppendElement(prevLine.Items().PopLastElement());
      }
    }

    // Update the line's bookkeeping about how large its items collectively are.
    curLine->AddLastItemToMainSizeTotals();
    itemIdxInContainer++;
  }
}

nsFlexContainerFrame::FlexLayoutResult
nsFlexContainerFrame::GenerateFlexLayoutResult() {
  MOZ_ASSERT(GetPrevInFlow(), "This should be called by non-first-in-flows!");

  auto* data = FirstInFlow()->GetProperty(SharedFlexData::Prop());
  MOZ_ASSERT(data, "SharedFlexData should be set by our first-in-flow!");

  FlexLayoutResult flr;

  // The order state of the children is consistent across entire continuation
  // chain due to calling nsContainerFrame::NormalizeChildLists() at the
  // beginning of Reflow(), so we can align our state bit with our
  // prev-in-flow's state. Setup here before calling OrderStateForIter() below.
  AddOrRemoveStateBits(NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER,
                       GetPrevInFlow()->HasAnyStateBits(
                           NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER));

  // Construct flex items for this flex container fragment from existing flex
  // items in SharedFlexData.
  CSSOrderAwareFrameIterator iter(
      this, FrameChildListID::Principal,
      CSSOrderAwareFrameIterator::ChildFilter::SkipPlaceholders,
      OrderStateForIter(this), OrderingPropertyForIter(this));

  auto ConstructNewFlexLine = [&flr]() {
    // Use zero main gap size since it doesn't matter in flex container's
    // next-in-flows. We've computed flex items' positions in first-in-flow.
    return flr.mLines.EmplaceBack(0);
  };

  // We have at least one FlexLine. Even an empty flex container has a single
  // (empty) flex line.
  FlexLine* currentLine = ConstructNewFlexLine();

  if (!iter.AtEnd()) {
    nsIFrame* child = *iter;
    nsIFrame* childFirstInFlow = child->FirstInFlow();

    // We are iterating nested for-loops over the FlexLines and FlexItems
    // generated by GenerateFlexLines() and cached in flex container's
    // first-in-flow. For each flex item, check if its frame (must be a
    // first-in-flow) is the first-in-flow of the first child frame in this flex
    // container continuation. If so, clone the data from that FlexItem into a
    // FlexLine. When we find a match for the item, we know that the next child
    // frame might have its first-in-flow as the next item in the same original
    // line. In this case, we'll put the cloned data in the same line here as
    // well.
    for (const FlexLine& line : data->mLines) {
      // If currentLine is empty, either it is the first line, or all the items
      // in the previous line have been placed in our prev-in-flows. No need to
      // construct a new line.
      if (!currentLine->IsEmpty()) {
        currentLine = ConstructNewFlexLine();
      }
      for (const FlexItem& item : line.Items()) {
        if (item.Frame() == childFirstInFlow) {
          currentLine->Items().AppendElement(item.CloneFor(child));
          iter.Next();
          if (iter.AtEnd()) {
            // We've constructed flex items for all children. No need to check
            // rest of the items.
            child = childFirstInFlow = nullptr;
            break;
          }
          child = *iter;
          childFirstInFlow = child->FirstInFlow();
        }
      }
      if (iter.AtEnd()) {
        // We've constructed flex items for all children. No need to check
        // rest of the lines.
        break;
      }
    }
  }

  flr.mContentBoxMainSize = data->mContentBoxMainSize;
  flr.mContentBoxCrossSize = data->mContentBoxCrossSize;

  return flr;
}

// Returns the largest outer hypothetical main-size of any line in |aLines|.
// (i.e. the hypothetical main-size of the largest line)
static AuCoord64 GetLargestLineMainSize(nsTArray<FlexLine>& aLines) {
  AuCoord64 largestLineOuterSize = 0;
  for (const FlexLine& line : aLines) {
    largestLineOuterSize =
        std::max(largestLineOuterSize, line.TotalOuterHypotheticalMainSize());
  }
  return largestLineOuterSize;
}

nscoord nsFlexContainerFrame::ComputeMainSize(
    const ReflowInput& aReflowInput, const FlexboxAxisTracker& aAxisTracker,
    const nscoord aTentativeContentBoxMainSize,
    nsTArray<FlexLine>& aLines) const {
  if (aAxisTracker.IsRowOriented()) {
    // Row-oriented --> our main axis is the inline axis, so our main size
    // is our inline size (which should already be resolved).
    return aTentativeContentBoxMainSize;
  }

  const bool shouldApplyAutomaticMinimumOnBlockAxis =
      aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis();
  if (aTentativeContentBoxMainSize != NS_UNCONSTRAINEDSIZE &&
      !shouldApplyAutomaticMinimumOnBlockAxis) {
    // Column-oriented case, with fixed BSize:
    // Just use our fixed block-size because we always assume the available
    // block-size is unconstrained, and the reflow input has already done the
    // appropriate min/max-BSize clamping.
    return aTentativeContentBoxMainSize;
  }

  // Column-oriented case, with size-containment in block axis:
  // Behave as if we had no content and just use our MinBSize.
  if (Maybe<nscoord> containBSize =
          aReflowInput.mFrame->ContainIntrinsicBSize()) {
    return NS_CSS_MINMAX(*containBSize, aReflowInput.ComputedMinBSize(),
                         aReflowInput.ComputedMaxBSize());
  }

  const AuCoord64 largestLineMainSize = GetLargestLineMainSize(aLines);
  const nscoord contentBSize = NS_CSS_MINMAX(
      nscoord(largestLineMainSize.ToMinMaxClamped()),
      aReflowInput.ComputedMinBSize(), aReflowInput.ComputedMaxBSize());
  // If the clamped largest FlexLine length is larger than the tentative main
  // size (which is resolved by aspect-ratio), we extend it to contain the
  // entire FlexLine.
  // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
  if (shouldApplyAutomaticMinimumOnBlockAxis) {
    // Column-oriented case, with auto BSize which is resolved by
    // aspect-ratio.
    return std::max(contentBSize, aTentativeContentBoxMainSize);
  }

  // Column-oriented case, with auto BSize:
  // Resolve auto BSize to the largest FlexLine length, clamped to our
  // computed min/max main-size properties.
  return contentBSize;
}

nscoord nsFlexContainerFrame::ComputeCrossSize(
    const ReflowInput& aReflowInput, const FlexboxAxisTracker& aAxisTracker,
    const nscoord aTentativeContentBoxCrossSize, nscoord aSumLineCrossSizes,
    bool* aIsDefinite) const {
  MOZ_ASSERT(aIsDefinite, "outparam pointer must be non-null");

  if (aAxisTracker.IsColumnOriented()) {
    // Column-oriented --> our cross axis is the inline axis, so our cross size
    // is our inline size (which should already be resolved).
    *aIsDefinite = true;
    // FIXME: Bug 1661847 - there are cases where aTentativeContentBoxCrossSize
    // (i.e. aReflowInput.ComputedISize()) might not be the right thing to
    // return here. Specifically: if our cross size is an intrinsic size, and we
    // have flex items that are flexible and have aspect ratios, then we may
    // need to take their post-flexing main sizes into account (multiplied
    // through their aspect ratios to get their cross sizes), in order to
    // determine their flex line's size & the flex container's cross size (e.g.
    // as `aSumLineCrossSizes`).
    return aTentativeContentBoxCrossSize;
  }

  const bool shouldApplyAutomaticMinimumOnBlockAxis =
      aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis();
  const nscoord computedBSize = aReflowInput.ComputedBSize();
  if (computedBSize != NS_UNCONSTRAINEDSIZE &&
      !shouldApplyAutomaticMinimumOnBlockAxis) {
    // Row-oriented case (cross axis is block-axis), with fixed BSize:
    *aIsDefinite = true;

    // Just use our fixed block-size because we always assume the available
    // block-size is unconstrained, and the reflow input has already done the
    // appropriate min/max-BSize clamping.
    return computedBSize;
  }

  // Row-oriented case, with size-containment in block axis:
  // Behave as if we had no content and just use our MinBSize.
  if (Maybe<nscoord> containBSize =
          aReflowInput.mFrame->ContainIntrinsicBSize()) {
    *aIsDefinite = true;
    return NS_CSS_MINMAX(*containBSize, aReflowInput.ComputedMinBSize(),
                         aReflowInput.ComputedMaxBSize());
  }

  // The cross size must not be definite in the following cases.
  *aIsDefinite = false;

  const nscoord contentBSize =
      NS_CSS_MINMAX(aSumLineCrossSizes, aReflowInput.ComputedMinBSize(),
                    aReflowInput.ComputedMaxBSize());
  // If the content block-size is larger than the effective computed
  // block-size, we extend the block-size to contain all the content.
  // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
  if (shouldApplyAutomaticMinimumOnBlockAxis) {
    // Row-oriented case (cross axis is block-axis), with auto BSize which is
    // resolved by aspect-ratio or content size.
    return std::max(contentBSize, computedBSize);
  }

  // Row-oriented case (cross axis is block axis), with auto BSize:
  // Shrink-wrap our line(s), subject to our min-size / max-size
  // constraints in that (block) axis.
  return contentBSize;
}

LogicalSize nsFlexContainerFrame::ComputeAvailableSizeForItems(
    const ReflowInput& aReflowInput,
    const mozilla::LogicalMargin& aBorderPadding) const {
  const WritingMode wm = GetWritingMode();
  nscoord availableBSize = aReflowInput.AvailableBSize();

  if (availableBSize != NS_UNCONSTRAINEDSIZE) {
    // Available block-size is constrained. Subtract block-start border and
    // padding from it.
    availableBSize -= aBorderPadding.BStart(wm);

    if (aReflowInput.mStyleBorder->mBoxDecorationBreak ==
        StyleBoxDecorationBreak::Clone) {
      // We have box-decoration-break:clone. Subtract block-end border and
      // padding from the available block-size as well.
      availableBSize -= aBorderPadding.BEnd(wm);
    }

    // Available block-size can became negative after subtracting block-axis
    // border and padding. Per spec, to guarantee progress, fragmentainers are
    // assumed to have a minimum block size of 1px regardless of their used
    // size. https://drafts.csswg.org/css-break/#breaking-rules
    availableBSize =
        std::max(nsPresContext::CSSPixelsToAppUnits(1), availableBSize);
  }

  return LogicalSize(wm, aReflowInput.ComputedISize(), availableBSize);
}

void FlexLine::PositionItemsInMainAxis(
    const StyleContentDistribution& aJustifyContent,
    nscoord aContentBoxMainSize, const FlexboxAxisTracker& aAxisTracker) {
  MainAxisPositionTracker mainAxisPosnTracker(
      aAxisTracker, this, aJustifyContent, aContentBoxMainSize);
  for (FlexItem& item : Items()) {
    nscoord itemMainBorderBoxSize =
        item.MainSize() + item.BorderPaddingSizeInMainAxis();

    // Resolve any main-axis 'auto' margins on aChild to an actual value.
    mainAxisPosnTracker.ResolveAutoMarginsInMainAxis(item);

    // Advance our position tracker to child's upper-left content-box corner,
    // and use that as its position in the main axis.
    mainAxisPosnTracker.EnterMargin(item.Margin());
    mainAxisPosnTracker.EnterChildFrame(itemMainBorderBoxSize);

    item.SetMainPosition(mainAxisPosnTracker.Position());

    mainAxisPosnTracker.ExitChildFrame(itemMainBorderBoxSize);
    mainAxisPosnTracker.ExitMargin(item.Margin());
    mainAxisPosnTracker.TraversePackingSpace();
    if (&item != &Items().LastElement()) {
      mainAxisPosnTracker.TraverseGap(mMainGapSize);
    }
  }
}

void nsFlexContainerFrame::SizeItemInCrossAxis(ReflowInput& aChildReflowInput,
                                               FlexItem& aItem) {
  // If cross axis is the item's inline axis, just use ISize from reflow input,
  // and don't bother with a full reflow.
  if (aItem.IsInlineAxisCrossAxis()) {
    aItem.SetCrossSize(aChildReflowInput.ComputedISize());
    return;
  }

  MOZ_ASSERT(!aItem.HadMeasuringReflow(),
             "We shouldn't need more than one measuring reflow");

  if (aItem.AlignSelf()._0 == StyleAlignFlags::STRETCH) {
    // This item's got "align-self: stretch", so we probably imposed a
    // stretched computed cross-size on it during its previous
    // reflow. We're not imposing that BSize for *this* "measuring" reflow, so
    // we need to tell it to treat this reflow as a resize in its block axis
    // (regardless of whether any of its ancestors are actually being resized).
    // (Note: we know that the cross axis is the item's *block* axis -- if it
    // weren't, then we would've taken the early-return above.)
    aChildReflowInput.SetBResize(true);
    // Not 100% sure this is needed, but be conservative for now:
    aChildReflowInput.mFlags.mIsBResizeForPercentages = true;
  }

  // Potentially reflow the item, and get the sizing info.
  const CachedBAxisMeasurement& measurement =
      MeasureBSizeForFlexItem(aItem, aChildReflowInput);

  // Save the sizing info that we learned from this reflow
  // -----------------------------------------------------

  // Tentatively store the child's desired content-box cross-size.
  aItem.SetCrossSize(measurement.BSize());
}

void FlexLine::PositionItemsInCrossAxis(
    nscoord aLineStartPosition, const FlexboxAxisTracker& aAxisTracker) {
  SingleLineCrossAxisPositionTracker lineCrossAxisPosnTracker(aAxisTracker);

  for (FlexItem& item : Items()) {
    // First, stretch the item's cross size (if appropriate), and resolve any
    // auto margins in this axis.
    item.ResolveStretchedCrossSize(mLineCrossSize);
    lineCrossAxisPosnTracker.ResolveAutoMarginsInCrossAxis(*this, item);

    // Compute the cross-axis position of this item
    nscoord itemCrossBorderBoxSize =
        item.CrossSize() + item.BorderPaddingSizeInCrossAxis();
    lineCrossAxisPosnTracker.EnterAlignPackingSpace(*this, item, aAxisTracker);
    lineCrossAxisPosnTracker.EnterMargin(item.Margin());
    lineCrossAxisPosnTracker.EnterChildFrame(itemCrossBorderBoxSize);

    item.SetCrossPosition(aLineStartPosition +
                          lineCrossAxisPosnTracker.Position());

    // Back out to cross-axis edge of the line.
    lineCrossAxisPosnTracker.ResetPosition();
  }
}

void nsFlexContainerFrame::Reflow(nsPresContext* aPresContext,
                                  ReflowOutput& aReflowOutput,
                                  const ReflowInput& aReflowInput,
                                  nsReflowStatus& aStatus) {
  if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
    return;
  }

  MarkInReflow();
  DO_GLOBAL_REFLOW_COUNT("nsFlexContainerFrame");
  DISPLAY_REFLOW(aPresContext, this, aReflowInput, aReflowOutput, aStatus);
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
  MOZ_ASSERT(aPresContext == PresContext());
  NS_WARNING_ASSERTION(
      aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE,
      "Unconstrained inline size; this should only result from huge sizes "
      "(not intrinsic sizing w/ orthogonal flows)");

  FLEX_LOG("Reflow() for nsFlexContainerFrame %p", this);

  if (IsFrameTreeTooDeep(aReflowInput, aReflowOutput, aStatus)) {
    return;
  }

  NormalizeChildLists();

#ifdef DEBUG
  mDidPushItemsBitMayLie = false;
  SanityCheckChildListsBeforeReflow();
#endif  // DEBUG

  // We (and our children) can only depend on our ancestor's bsize if we have
  // a percent-bsize, or if we're positioned and we have "block-start" and
  // "block-end" set and have block-size:auto.  (There are actually other cases,
  // too -- e.g. if our parent is itself a block-dir flex container and we're
  // flexible -- but we'll let our ancestors handle those sorts of cases.)
  //
  // TODO(emilio): the !bsize.IsLengthPercentage() preserves behavior, but it's
  // too conservative. min/max-content don't really depend on the container.
  WritingMode wm = aReflowInput.GetWritingMode();
  const nsStylePosition* stylePos = StylePosition();
  const auto& bsize = stylePos->BSize(wm);
  if (bsize.HasPercent() || (StyleDisplay()->IsAbsolutelyPositionedStyle() &&
                             (bsize.IsAuto() || !bsize.IsLengthPercentage()) &&
                             !stylePos->mOffset.GetBStart(wm).IsAuto() &&
                             !stylePos->mOffset.GetBEnd(wm).IsAuto())) {
    AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
  }

  const FlexboxAxisTracker axisTracker(this);

  // Check to see if we need to create a computed info structure, to
  // be filled out for use by devtools.
  ComputedFlexContainerInfo* containerInfo = CreateOrClearFlexContainerInfo();

  FlexLayoutResult flr;
  PerFragmentFlexData fragmentData;
  const nsIFrame* prevInFlow = GetPrevInFlow();
  if (!prevInFlow) {
    const LogicalSize tentativeContentBoxSize = aReflowInput.ComputedSize();
    const nscoord tentativeContentBoxMainSize =
        axisTracker.MainComponent(tentativeContentBoxSize);
    const nscoord tentativeContentBoxCrossSize =
        axisTracker.CrossComponent(tentativeContentBoxSize);

    // Calculate gap sizes for main and cross axis. We only need them in
    // DoFlexLayout in the first-in-flow, so no need to worry about consumed
    // block-size.
    const auto& mainGapStyle =
        axisTracker.IsRowOriented() ? stylePos->mColumnGap : stylePos->mRowGap;
    const auto& crossGapStyle =
        axisTracker.IsRowOriented() ? stylePos->mRowGap : stylePos->mColumnGap;
    const nscoord mainGapSize = nsLayoutUtils::ResolveGapToLength(
        mainGapStyle, tentativeContentBoxMainSize);
    const nscoord crossGapSize = nsLayoutUtils::ResolveGapToLength(
        crossGapStyle, tentativeContentBoxCrossSize);

    // When fragmenting a flex container, we run the flex algorithm without
    // regards to pagination in order to compute the flex container's desired
    // content-box size. https://drafts.csswg.org/css-flexbox-1/#pagination-algo
    //
    // Note: For a multi-line column-oriented flex container, the sample
    // algorithm suggests we wrap the flex line at the block-end edge of a
    // column/page, but we do not implement it intentionally. This brings the
    // layout result closer to the one as if there's no fragmentation.
    AutoTArray<StrutInfo, 1> struts;
    flr = DoFlexLayout(aReflowInput, tentativeContentBoxMainSize,
                       tentativeContentBoxCrossSize, axisTracker, mainGapSize,
                       crossGapSize, struts, containerInfo);

    if (!struts.IsEmpty()) {
      // We're restarting flex layout, with new knowledge of collapsed items.
      flr.mLines.Clear();
      flr.mPlaceholders.Clear();
      flr = DoFlexLayout(aReflowInput, tentativeContentBoxMainSize,
                         tentativeContentBoxCrossSize, axisTracker, mainGapSize,
                         crossGapSize, struts, containerInfo);
    }
  } else {
    flr = GenerateFlexLayoutResult();
    auto* fragmentDataProp =
        prevInFlow->GetProperty(PerFragmentFlexData::Prop());
    MOZ_ASSERT(fragmentDataProp,
               "PerFragmentFlexData should be set in our prev-in-flow!");
    fragmentData = *fragmentDataProp;
  }

  LogicalSize contentBoxSize = axisTracker.LogicalSizeFromFlexRelativeSizes(
      flr.mContentBoxMainSize, flr.mContentBoxCrossSize);

  const nscoord consumedBSize = CalcAndCacheConsumedBSize();
  const nscoord effectiveContentBSize =
      contentBoxSize.BSize(wm) - consumedBSize;
  LogicalMargin borderPadding = aReflowInput.ComputedLogicalBorderPadding(wm);
  if (MOZ_UNLIKELY(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE)) {
    // We assume we are the last fragment by using
    // PreReflowBlockLevelLogicalSkipSides(), and skip block-end border and
    // padding if needed.
    borderPadding.ApplySkipSides(PreReflowBlockLevelLogicalSkipSides());
  }

  // Determine this frame's tentative border-box size. This is used for logical
  // to physical coordinate conversion when positioning children.
  //
  // Note that vertical-rl writing-mode is the only case where the block flow
  // direction progresses in a negative physical direction, and therefore block
  // direction coordinate conversion depends on knowing the width of the
  // coordinate space in order to translate between the logical and physical
  // origins. As a result, if our final border-box block-size is different from
  // this tentative one, and we are in vertical-rl writing mode, we need to
  // adjust our children's position after reflowing them.
  const LogicalSize tentativeBorderBoxSize(
      wm, contentBoxSize.ISize(wm) + borderPadding.IStartEnd(wm),
      std::min(effectiveContentBSize + borderPadding.BStartEnd(wm),
               aReflowInput.AvailableBSize()));
  const nsSize containerSize = tentativeBorderBoxSize.GetPhysicalSize(wm);

  OverflowAreas ocBounds;
  nsReflowStatus ocStatus;
  if (prevInFlow) {
    ReflowOverflowContainerChildren(
        aPresContext, aReflowInput, ocBounds, ReflowChildFlags::Default,
        ocStatus, MergeSortedFrameListsFor, Some(containerSize));
  }

  const LogicalSize availableSizeForItems =
      ComputeAvailableSizeForItems(aReflowInput, borderPadding);
  const auto [maxBlockEndEdgeOfChildren, anyChildIncomplete] =
      ReflowChildren(aReflowInput, containerSize, availableSizeForItems,
                     borderPadding, axisTracker, flr, fragmentData);

  bool mayNeedNextInFlow = false;
  if (aReflowInput.IsInFragmentedContext()) {
    // maxBlockEndEdgeOfChildren is relative to border-box, so we need to
    // subtract block-start border and padding to make it relative to our
    // content-box. Note that if there is a packing space in between the last
    // flex item's block-end edge and the available space's block-end edge, we
    // want to record the available size of item to consume part of the packing
    // space.
    fragmentData.mCumulativeContentBoxBSize +=
        std::max(maxBlockEndEdgeOfChildren - borderPadding.BStart(wm),
                 availableSizeForItems.BSize(wm));

    // mCumulativeBEndEdgeShift was updated in ReflowChildren(). If our
    // block-size in unconstrained, use that to grow our block-size, subject to
    // min/max constraints.
    if (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE) {
      contentBoxSize.BSize(wm) = aReflowInput.ApplyMinMaxBSize(
          contentBoxSize.BSize(wm) + fragmentData.mCumulativeBEndEdgeShift);
    }

    // Check if we may need a next-in-flow. If so, we'll need to skip block-end
    // border and padding.
    mayNeedNextInFlow = contentBoxSize.BSize(wm) - consumedBSize >
                        availableSizeForItems.BSize(wm);
    if (mayNeedNextInFlow && aReflowInput.mStyleBorder->mBoxDecorationBreak ==
                                 StyleBoxDecorationBreak::Slice) {
      borderPadding.BEnd(wm) = 0;
    }
  }

  PopulateReflowOutput(aReflowOutput, aReflowInput, aStatus, contentBoxSize,
                       borderPadding, consumedBSize, mayNeedNextInFlow,
                       maxBlockEndEdgeOfChildren, anyChildIncomplete,
                       axisTracker, flr);

  if (wm.IsVerticalRL()) {
    // If the final border-box block-size is different from the tentative one,
    // adjust our children's position.
    const nscoord deltaBCoord =
        tentativeBorderBoxSize.BSize(wm) - aReflowOutput.Size(wm).BSize(wm);
    if (deltaBCoord != 0) {
      const LogicalPoint delta(wm, 0, deltaBCoord);
      for (const FlexLine& line : flr.mLines) {
        for (const FlexItem& item : line.Items()) {
          item.Frame()->MovePositionBy(wm, delta);
        }
      }
    }
  }

  // Overflow area = union(my overflow area, children's overflow areas)
  aReflowOutput.SetOverflowAreasToDesiredBounds();
  UnionInFlowChildOverflow(aReflowOutput.mOverflowAreas);

  // Merge overflow container bounds and status.
  aReflowOutput.mOverflowAreas.UnionWith(ocBounds);
  aStatus.MergeCompletionStatusFrom(ocStatus);

  FinishReflowWithAbsoluteFrames(PresContext(), aReflowOutput, aReflowInput,
                                 aStatus);

  // Finally update our line and item measurements in our containerInfo.
  if (MOZ_UNLIKELY(containerInfo)) {
    UpdateFlexLineAndItemInfo(*containerInfo, flr.mLines);
  }

  // If we are the first-in-flow, we want to store data for our next-in-flows,
  // or clear the existing data if it is not needed.
  if (!prevInFlow) {
    SharedFlexData* sharedData = GetProperty(SharedFlexData::Prop());
    if (!aStatus.IsFullyComplete()) {
      if (!sharedData) {
        sharedData = new SharedFlexData;
        SetProperty(SharedFlexData::Prop(), sharedData);
      }
      sharedData->Update(std::move(flr));
    } else if (sharedData && !GetNextInFlow()) {
      // We are fully-complete, so no next-in-flow is needed. However, if we
      // report SetInlineLineBreakBeforeAndReset() in an incremental reflow, our
      // next-in-flow might still exist. It can be reflowed again before us if
      // it is an overflow container. Delete the existing data only if we don't
      // have a next-in-flow.
      RemoveProperty(SharedFlexData::Prop());
    }
  }

  PerFragmentFlexData* fragmentDataProp =
      GetProperty(PerFragmentFlexData::Prop());
  if (!aStatus.IsFullyComplete()) {
    if (!fragmentDataProp) {
      fragmentDataProp = new PerFragmentFlexData;
      SetProperty(PerFragmentFlexData::Prop(), fragmentDataProp);
    }
    *fragmentDataProp = fragmentData;
  } else if (fragmentDataProp && !GetNextInFlow()) {
    // Similar to the condition to remove SharedFlexData, delete the
    // existing data only if we don't have a next-in-flow.
    RemoveProperty(PerFragmentFlexData::Prop());
  }
}

Maybe<nscoord> nsFlexContainerFrame::GetNaturalBaselineBOffset(
    WritingMode aWM, BaselineSharingGroup aBaselineGroup,
    BaselineExportContext) const {
  if (StyleDisplay()->IsContainLayout() ||
      HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE)) {
    return Nothing{};
  }
  return Some(aBaselineGroup == BaselineSharingGroup::First ? mFirstBaseline
                                                            : mLastBaseline);
}

void nsFlexContainerFrame::UnionInFlowChildOverflow(
    OverflowAreas& aOverflowAreas) {
  // The CSS Overflow spec [1] requires that a scrollable container's
  // scrollable overflow should include the following areas.
  //
  // a) "the box's own content and padding areas": we treat the *content* as
  // the scrolled inner frame's theoretical content-box that's intrinsically
  // sized to the union of all the flex items' margin boxes, _without_
  // relative positioning applied. The *padding areas* is just inflation on
  // top of the theoretical content-box by the flex container's padding.
  //
  // b) "the margin areas of grid item and flex item boxes for which the box
  // establishes a containing block": a) already includes the flex items'
  // normal-positioned margin boxes into the scrollable overflow, but their
  // relative-positioned margin boxes should also be included because relpos
  // children are still flex items.
  //
  // [1] https://drafts.csswg.org/css-overflow-3/#scrollable.
  const bool isScrolledContent =
      Style()->GetPseudoType() == PseudoStyleType::scrolledContent;
  bool anyScrolledContentItem = false;
  // Union of normal-positioned margin boxes for all the items.
  nsRect itemMarginBoxes;
  // Union of relative-positioned margin boxes for the relpos items only.
  nsRect relPosItemMarginBoxes;
  const bool useMozBoxCollapseBehavior =
      StyleVisibility()->UseLegacyCollapseBehavior();
  for (nsIFrame* f : mFrames) {
    if (useMozBoxCollapseBehavior && f->StyleVisibility()->IsCollapse()) {
      continue;
    }
    ConsiderChildOverflow(aOverflowAreas, f);
    if (!isScrolledContent) {
      continue;
    }
    if (f->IsPlaceholderFrame()) {
      continue;
    }
    anyScrolledContentItem = true;
    if (MOZ_UNLIKELY(f->IsRelativelyOrStickyPositioned())) {
      const nsRect marginRect = f->GetMarginRectRelativeToSelf();
      itemMarginBoxes =
          itemMarginBoxes.Union(marginRect + f->GetNormalPosition());
      relPosItemMarginBoxes =
          relPosItemMarginBoxes.Union(marginRect + f->GetPosition());
    } else {
      itemMarginBoxes = itemMarginBoxes.Union(f->GetMarginRect());
    }
  }

  if (anyScrolledContentItem) {
    itemMarginBoxes.Inflate(GetUsedPadding());
    aOverflowAreas.UnionAllWith(itemMarginBoxes);
    aOverflowAreas.UnionAllWith(relPosItemMarginBoxes);
  }
}

void nsFlexContainerFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) {
  UnionInFlowChildOverflow(aOverflowAreas);
  // Union with child frames, skipping the principal list since we already
  // handled those above.
  nsLayoutUtils::UnionChildOverflow(this, aOverflowAreas,
                                    {FrameChildListID::Principal});
}

void nsFlexContainerFrame::CalculatePackingSpace(
    uint32_t aNumThingsToPack, const StyleContentDistribution& aAlignVal,
    nscoord* aFirstSubjectOffset, uint32_t* aNumPackingSpacesRemaining,
    nscoord* aPackingSpaceRemaining) {
  StyleAlignFlags val = aAlignVal.primary;
  MOZ_ASSERT(val == StyleAlignFlags::SPACE_BETWEEN ||
                 val == StyleAlignFlags::SPACE_AROUND ||
                 val == StyleAlignFlags::SPACE_EVENLY,
             "Unexpected alignment value");

  MOZ_ASSERT(*aPackingSpaceRemaining >= 0,
             "Should not be called with negative packing space");

  // Note: In the aNumThingsToPack==1 case, the fallback behavior for
  // 'space-between' depends on precise information about the axes that we
  // don't have here. So, for that case, we just depend on the caller to
  // explicitly convert 'space-{between,around,evenly}' keywords to the
  // appropriate fallback alignment and skip this function.
  MOZ_ASSERT(aNumThingsToPack > 1,
             "Should not be called unless there's more than 1 thing to pack");

  // Packing spaces between items:
  *aNumPackingSpacesRemaining = aNumThingsToPack - 1;

  if (val == StyleAlignFlags::SPACE_BETWEEN) {
    // No need to reserve space at beginning/end, so we're done.
    return;
  }

  // We need to add 1 or 2 packing spaces, split between beginning/end, for
  // space-around / space-evenly:
  size_t numPackingSpacesForEdges =
      val == StyleAlignFlags::SPACE_AROUND ? 1 : 2;

  // How big will each "full" packing space be:
  nscoord packingSpaceSize =
      *aPackingSpaceRemaining /
      (*aNumPackingSpacesRemaining + numPackingSpacesForEdges);
  // How much packing-space are we allocating to the edges:
  nscoord totalEdgePackingSpace = numPackingSpacesForEdges * packingSpaceSize;

  // Use half of that edge packing space right now:
  *aFirstSubjectOffset += totalEdgePackingSpace / 2;
  // ...but we need to subtract all of it right away, so that we won't
  // hand out any of it to intermediate packing spaces.
  *aPackingSpaceRemaining -= totalEdgePackingSpace;
}

ComputedFlexContainerInfo*
nsFlexContainerFrame::CreateOrClearFlexContainerInfo() {
  if (!ShouldGenerateComputedInfo()) {
    return nullptr;
  }

  // The flag that sets ShouldGenerateComputedInfo() will never be cleared.
  // That's acceptable because it's only set in a Chrome API invoked by
  // devtools, and won't impact normal browsing.

  // Re-use the ComputedFlexContainerInfo, if it exists.
  ComputedFlexContainerInfo* info = GetProperty(FlexContainerInfo());
  if (info) {
    // We can reuse, as long as we clear out old data.
    info->mLines.Clear();
  } else {
    info = new ComputedFlexContainerInfo();
    SetProperty(FlexContainerInfo(), info);
  }

  return info;
}

void nsFlexContainerFrame::CreateFlexLineAndFlexItemInfo(
    ComputedFlexContainerInfo& aContainerInfo,
    const nsTArray<FlexLine>& aLines) {
  for (const FlexLine& line : aLines) {
    ComputedFlexLineInfo* lineInfo = aContainerInfo.mLines.AppendElement();
    // Most of the remaining lineInfo properties will be filled out in
    // UpdateFlexLineAndItemInfo (some will be provided by other functions),
    // when we have real values. But we still add all the items here, so
    // we can capture computed data for each item as we proceed.
    for (const FlexItem& item : line.Items()) {
      nsIFrame* frame = item.Frame();

      // The frame may be for an element, or it may be for an
      // anonymous flex item, e.g. wrapping one or more text nodes.
      // DevTools wants the content node for the actual child in
      // the DOM tree, so we descend through anonymous boxes.
      nsIFrame* targetFrame = GetFirstNonAnonBoxInSubtree(frame);
      nsIContent* content = targetFrame->GetContent();

      // Skip over content that is only whitespace, which might
      // have been broken off from a text node which is our real
      // target.
      while (content && content->TextIsOnlyWhitespace()) {
        // If content is only whitespace, try the frame sibling.
        targetFrame = targetFrame->GetNextSibling();
        if (targetFrame) {
          content = targetFrame->GetContent();
        } else {
          content = nullptr;
        }
      }

      ComputedFlexItemInfo* itemInfo = lineInfo->mItems.AppendElement();

      itemInfo->mNode = content;

      // itemInfo->mMainBaseSize and mMainDeltaSize will be filled out
      // in ResolveFlexibleLengths(). Other measurements will be captured in
      // UpdateFlexLineAndItemInfo.
    }
  }
}

void nsFlexContainerFrame::ComputeFlexDirections(
    ComputedFlexContainerInfo& aContainerInfo,
    const FlexboxAxisTracker& aAxisTracker) {
  auto ConvertPhysicalStartSideToFlexPhysicalDirection =
      [](mozilla::Side aStartSide) {
        switch (aStartSide) {
          case eSideLeft:
            return dom::FlexPhysicalDirection::Horizontal_lr;
          case eSideRight:
            return dom::FlexPhysicalDirection::Horizontal_rl;
          case eSideTop:
            return dom::FlexPhysicalDirection::Vertical_tb;
          case eSideBottom:
            return dom::FlexPhysicalDirection::Vertical_bt;
        }

        MOZ_ASSERT_UNREACHABLE("We should handle all sides!");
        return dom::FlexPhysicalDirection::Horizontal_lr;
      };

  aContainerInfo.mMainAxisDirection =
      ConvertPhysicalStartSideToFlexPhysicalDirection(
          aAxisTracker.MainAxisPhysicalStartSide());
  aContainerInfo.mCrossAxisDirection =
      ConvertPhysicalStartSideToFlexPhysicalDirection(
          aAxisTracker.CrossAxisPhysicalStartSide());
}

void nsFlexContainerFrame::UpdateFlexLineAndItemInfo(
    ComputedFlexContainerInfo& aContainerInfo,
    const nsTArray<FlexLine>& aLines) {
  uint32_t lineIndex = 0;
  for (const FlexLine& line : aLines) {
    ComputedFlexLineInfo& lineInfo = aContainerInfo.mLines[lineIndex];

    lineInfo.mCrossSize = line.LineCrossSize();
    lineInfo.mFirstBaselineOffset = line.FirstBaselineOffset();
    lineInfo.mLastBaselineOffset = line.LastBaselineOffset();

    uint32_t itemIndex = 0;
    for (const FlexItem& item : line.Items()) {
      ComputedFlexItemInfo& itemInfo = lineInfo.mItems[itemIndex];
      itemInfo.mFrameRect = item.Frame()->GetRect();
      itemInfo.mMainMinSize = item.MainMinSize();
      itemInfo.mMainMaxSize = item.MainMaxSize();
      itemInfo.mCrossMinSize = item.CrossMinSize();
      itemInfo.mCrossMaxSize = item.CrossMaxSize();
      itemInfo.mClampState =
          item.WasMinClamped()
              ? mozilla::dom::FlexItemClampState::Clamped_to_min
              : (item.WasMaxClamped()
                     ? mozilla::dom::FlexItemClampState::Clamped_to_max
                     : mozilla::dom::FlexItemClampState::Unclamped);
      ++itemIndex;
    }
    ++lineIndex;
  }
}

nsFlexContainerFrame* nsFlexContainerFrame::GetFlexFrameWithComputedInfo(
    nsIFrame* aFrame) {
  // Prepare a lambda function that we may need to call multiple times.
  auto GetFlexContainerFrame = [](nsIFrame* aFrame) {
    // Return the aFrame's content insertion frame, iff it is
    // a flex container frame.
    nsFlexContainerFrame* flexFrame = nullptr;

    if (aFrame) {
      nsIFrame* inner = aFrame;
      if (MOZ_UNLIKELY(aFrame->IsFieldSetFrame())) {
        inner = static_cast<nsFieldSetFrame*>(aFrame)->GetInner();
      }
      // Since "Get" methods like GetInner and GetContentInsertionFrame can
      // return null, we check the return values before dereferencing. Our
      // calling pattern makes this unlikely, but we're being careful.
      nsIFrame* insertionFrame =
          inner ? inner->GetContentInsertionFrame() : nullptr;
      nsIFrame* possibleFlexFrame = insertionFrame ? insertionFrame : aFrame;
      flexFrame = possibleFlexFrame->IsFlexContainerFrame()
                      ? static_cast<nsFlexContainerFrame*>(possibleFlexFrame)
                      : nullptr;
    }
    return flexFrame;
  };

  nsFlexContainerFrame* flexFrame = GetFlexContainerFrame(aFrame);
  if (flexFrame) {
    // Generate the FlexContainerInfo data, if it's not already there.
    bool reflowNeeded = !flexFrame->HasProperty(FlexContainerInfo());

    if (reflowNeeded) {
      // Trigger a reflow that generates additional flex property data.
      // Hold onto aFrame while we do this, in case reflow destroys it.
      AutoWeakFrame weakFrameRef(aFrame);

      RefPtr<mozilla::PresShell> presShell = flexFrame->PresShell();
      flexFrame->SetShouldGenerateComputedInfo(true);
      presShell->FrameNeedsReflow(flexFrame, IntrinsicDirty::None,
                                  NS_FRAME_IS_DIRTY);
      presShell->FlushPendingNotifications(FlushType::Layout);

      // Since the reflow may have side effects, get the flex frame
      // again. But if the weakFrameRef is no longer valid, then we
      // must bail out.
      if (!weakFrameRef.IsAlive()) {
        return nullptr;
      }

      flexFrame = GetFlexContainerFrame(weakFrameRef.GetFrame());

      NS_WARNING_ASSERTION(
          !flexFrame || flexFrame->HasProperty(FlexContainerInfo()),
          "The state bit should've made our forced-reflow "
          "generate a FlexContainerInfo object");
    }
  }
  return flexFrame;
}

/* static */
bool nsFlexContainerFrame::IsItemInlineAxisMainAxis(nsIFrame* aFrame) {
  MOZ_ASSERT(aFrame && aFrame->IsFlexItem(), "expecting arg to be a flex item");
  const WritingMode flexItemWM = aFrame->GetWritingMode();
  const nsIFrame* flexContainer = aFrame->GetParent();

  if (IsLegacyBox(flexContainer)) {
    // For legacy boxes, the main axis is determined by "box-orient", and we can
    // just directly check if that's vertical, and compare that to whether the
    // item's WM is also vertical:
    bool boxOrientIsVertical =
        flexContainer->StyleXUL()->mBoxOrient == StyleBoxOrient::Vertical;
    return flexItemWM.IsVertical() == boxOrientIsVertical;
  }

  // For modern CSS flexbox, we get our return value by asking two questions
  // and comparing their answers.
  // Question 1: does aFrame have the same inline axis as its flex container?
  bool itemInlineAxisIsParallelToParent =
      !flexItemWM.IsOrthogonalTo(flexContainer->GetWritingMode());

  // Question 2: is aFrame's flex container row-oriented? (This tells us
  // whether the flex container's main axis is its inline axis.)
  auto flexDirection = flexContainer->StylePosition()->mFlexDirection;
  bool flexContainerIsRowOriented =
      flexDirection == StyleFlexDirection::Row ||
      flexDirection == StyleFlexDirection::RowReverse;

  // aFrame's inline axis is its flex container's main axis IFF the above
  // questions have the same answer.
  return flexContainerIsRowOriented == itemInlineAxisIsParallelToParent;
}

/* static */
bool nsFlexContainerFrame::IsUsedFlexBasisContent(
    const StyleFlexBasis& aFlexBasis, const StyleSize& aMainSize) {
  // We have a used flex-basis of 'content' if flex-basis explicitly has that
  // value, OR if flex-basis is 'auto' (deferring to the main-size property)
  // and the main-size property is also 'auto'.
  // See https://drafts.csswg.org/css-flexbox-1/#valdef-flex-basis-auto
  if (aFlexBasis.IsContent()) {
    return true;
  }
  return aFlexBasis.IsAuto() && aMainSize.IsAuto();
}

nsFlexContainerFrame::FlexLayoutResult nsFlexContainerFrame::DoFlexLayout(
    const ReflowInput& aReflowInput, const nscoord aTentativeContentBoxMainSize,
    const nscoord aTentativeContentBoxCrossSize,
    const FlexboxAxisTracker& aAxisTracker, nscoord aMainGapSize,
    nscoord aCrossGapSize, nsTArray<StrutInfo>& aStruts,
    ComputedFlexContainerInfo* const aContainerInfo) {
  FlexLayoutResult flr;

  GenerateFlexLines(aReflowInput, aTentativeContentBoxMainSize,
                    aTentativeContentBoxCrossSize, aStruts, aAxisTracker,
                    aMainGapSize, flr.mPlaceholders, flr.mLines,
                    flr.mHasCollapsedItems);

  if ((flr.mLines.Length() == 1 && flr.mLines[0].IsEmpty()) ||
      aReflowInput.mStyleDisplay->IsContainLayout()) {
    // We have no flex items, or we're layout-contained. So, we have no
    // baseline, and our parent should synthesize a baseline if needed.
    AddStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE);
  } else {
    RemoveStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE);
  }

  // Construct our computed info if we've been asked to do so. This is
  // necessary to do now so we can capture some computed values for
  // FlexItems during layout that would not otherwise be saved (like
  // size adjustments). We'll later fix up the line properties,
  // because the correct values aren't available yet.
  if (aContainerInfo) {
    MOZ_ASSERT(ShouldGenerateComputedInfo(),
               "We should only have the info struct if "
               "ShouldGenerateComputedInfo() is true!");

    if (!aStruts.IsEmpty()) {
      // We restarted DoFlexLayout, and may have stale mLines to clear:
      aContainerInfo->mLines.Clear();
    } else {
      MOZ_ASSERT(aContainerInfo->mLines.IsEmpty(), "Shouldn't have lines yet.");
    }

    CreateFlexLineAndFlexItemInfo(*aContainerInfo, flr.mLines);
    ComputeFlexDirections(*aContainerInfo, aAxisTracker);
  }

  flr.mContentBoxMainSize = ComputeMainSize(
      aReflowInput, aAxisTracker, aTentativeContentBoxMainSize, flr.mLines);

  uint32_t lineIndex = 0;
  for (FlexLine& line : flr.mLines) {
    ComputedFlexLineInfo* lineInfo =
        aContainerInfo ? &aContainerInfo->mLines[lineIndex] : nullptr;
    line.ResolveFlexibleLengths(flr.mContentBoxMainSize, lineInfo);
    ++lineIndex;
  }

  // Cross Size Determination - Flexbox spec section 9.4
  // https://drafts.csswg.org/css-flexbox-1/#cross-sizing
  // ===================================================
  // Calculate the hypothetical cross size of each item:

  // 'sumLineCrossSizes' includes the size of all gaps between lines. We
  // initialize it with the sum of all the gaps, and add each line's cross size
  // at the end of the following for-loop.
  nscoord sumLineCrossSizes = aCrossGapSize * (flr.mLines.Length() - 1);
  for (FlexLine& line : flr.mLines) {
    for (FlexItem& item : line.Items()) {
      // The item may already have the correct cross-size; only recalculate
      // if the item's main size resolution (flexing) could have influenced it:
      if (item.CanMainSizeInfluenceCrossSize()) {
        StyleSizeOverrides sizeOverrides;
        if (item.IsInlineAxisMainAxis()) {
          sizeOverrides.mStyleISize.emplace(item.StyleMainSize());
        } else {
          sizeOverrides.mStyleBSize.emplace(item.StyleMainSize());
        }
        FLEX_LOG("Sizing flex item %p in cross axis", item.Frame());
        FLEX_LOGV(" Main size override: %d", item.MainSize());

        const WritingMode wm = item.GetWritingMode();
        LogicalSize availSize = aReflowInput.ComputedSize(wm);
        availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
        ReflowInput childReflowInput(PresContext(), aReflowInput, item.Frame(),
                                     availSize, Nothing(), {}, sizeOverrides);
        if (item.IsBlockAxisMainAxis() && item.TreatBSizeAsIndefinite()) {
          childReflowInput.mFlags.mTreatBSizeAsIndefinite = true;
        }

        SizeItemInCrossAxis(childReflowInput, item);
      }
    }
    // Now that we've finished with this line's items, size the line itself:
    line.ComputeCrossSizeAndBaseline(aAxisTracker);
    sumLineCrossSizes += line.LineCrossSize();
  }

  bool isCrossSizeDefinite;
  flr.mContentBoxCrossSize = ComputeCrossSize(
      aReflowInput, aAxisTracker, aTentativeContentBoxCrossSize,
      sumLineCrossSizes, &isCrossSizeDefinite);

  // Set up state for cross-axis alignment, at a high level (outside the
  // scope of a particular flex line)
  CrossAxisPositionTracker crossAxisPosnTracker(
      flr.mLines, aReflowInput, flr.mContentBoxCrossSize, isCrossSizeDefinite,
      aAxisTracker, aCrossGapSize);

  // Now that we know the cross size of each line (including
  // "align-content:stretch" adjustments, from the CrossAxisPositionTracker
  // constructor), we can create struts for any flex items with
  // "visibility: collapse" (and restart flex layout).
  // Make sure to only do this if we had no struts.
  if (aStruts.IsEmpty() && flr.mHasCollapsedItems &&
      !StyleVisibility()->UseLegacyCollapseBehavior()) {
    BuildStrutInfoFromCollapsedItems(flr.mLines, aStruts);
    if (!aStruts.IsEmpty()) {
      // Restart flex layout, using our struts.
      return flr;
    }
  }

  // If the flex container is row-oriented, it should derive its first/last
  // baseline from the WM-relative startmost/endmost FlexLine if any items in
  // the line participate in baseline alignment.
  // https://drafts.csswg.org/css-flexbox-1/#flex-baselines
  //
  // Initialize the relevant variables here so that we can establish baselines
  // while iterating FlexLine later (while crossAxisPosnTracker is conveniently
  // pointing at the cross-start edge of that line, which the line's baseline
  // offset is measured from).
  const FlexLine* lineForFirstBaseline = nullptr;
  const FlexLine* lineForLastBaseline = nullptr;
  if (aAxisTracker.IsRowOriented()) {
    lineForFirstBaseline = &StartmostLine(flr.mLines, aAxisTracker);
    lineForLastBaseline = &EndmostLine(flr.mLines, aAxisTracker);
  } else {
    // For column-oriented flex container, use sentinel value to prompt us to
    // get baselines from the startmost/endmost items.
    flr.mAscent = nscoord_MIN;
    flr.mAscentForLast = nscoord_MIN;
  }

  const auto justifyContent =
      IsLegacyBox(aReflowInput.mFrame)
          ? ConvertLegacyStyleToJustifyContent(StyleXUL())
          : aReflowInput.mStylePosition->mJustifyContent;

  lineIndex = 0;
  for (FlexLine& line : flr.mLines) {
    // Main-Axis Alignment - Flexbox spec section 9.5
    // https://drafts.csswg.org/css-flexbox-1/#main-alignment
    // ==============================================
    line.PositionItemsInMainAxis(justifyContent, flr.mContentBoxMainSize,
                                 aAxisTracker);

    // See if we need to extract some computed info for this line.
    if (MOZ_UNLIKELY(aContainerInfo)) {
      ComputedFlexLineInfo& lineInfo = aContainerInfo->mLines[lineIndex];
      lineInfo.mCrossStart = crossAxisPosnTracker.Position();
    }

    // Cross-Axis Alignment - Flexbox spec section 9.6
    // https://drafts.csswg.org/css-flexbox-1/#cross-alignment
    // ===============================================
    line.PositionItemsInCrossAxis(crossAxisPosnTracker.Position(),
                                  aAxisTracker);

    // Flex Container Baselines - Flexbox spec section 8.5
    // https://drafts.csswg.org/css-flexbox-1/#flex-baselines
    auto ComputeAscentFromLine = [&](const FlexLine& aLine,
                                     BaselineSharingGroup aBaselineGroup) {
      MOZ_ASSERT(aAxisTracker.IsRowOriented(),
                 "This makes sense only if we are row-oriented!");

      // baselineOffsetInLine is a distance from the line's cross-start edge.
      const nscoord baselineOffsetInLine =
          aLine.ExtractBaselineOffset(aBaselineGroup);

      if (baselineOffsetInLine == nscoord_MIN) {
        // No "first baseline"-aligned or "last baseline"-aligned items in
        // aLine. Return a sentinel value to prompt us to get baseline from the
        // startmost or endmost FlexItem after we've reflowed it.
        return nscoord_MIN;
      }

      // This "ascent" variable is a distance from the flex container's
      // content-box block-start edge.
      const nscoord ascent = aAxisTracker.LogicalAscentFromFlexRelativeAscent(
          crossAxisPosnTracker.Position() + baselineOffsetInLine,
          flr.mContentBoxCrossSize);

      // Convert "ascent" variable to a distance from border-box start or end
      // edge, per documentation for FlexLayoutResult ascent members.
      const auto wm = aAxisTracker.GetWritingMode();
      if (aBaselineGroup == BaselineSharingGroup::First) {
        return ascent +
               aReflowInput.ComputedLogicalBorderPadding(wm).BStart(wm);
      }
      return flr.mContentBoxCrossSize - ascent +
             aReflowInput.ComputedLogicalBorderPadding(wm).BEnd(wm);
    };

    if (lineForFirstBaseline && lineForFirstBaseline == &line) {
      flr.mAscent = ComputeAscentFromLine(line, BaselineSharingGroup::First);
    }
    if (lineForLastBaseline && lineForLastBaseline == &line) {
      flr.mAscentForLast =
          ComputeAscentFromLine(line, BaselineSharingGroup::Last);
    }

    crossAxisPosnTracker.TraverseLine(line);
    crossAxisPosnTracker.TraversePackingSpace();

    if (&line != &flr.mLines.LastElement()) {
      crossAxisPosnTracker.TraverseGap();
    }
    ++lineIndex;
  }

  return flr;
}

// This data structure is used in fragmentation, storing the block coordinate
// metrics when reflowing 1) the BStart-most line in each fragment of a
// row-oriented flex container or, 2) the BStart-most item in each fragment of a
// single-line column-oriented flex container.
//
// When we lay out a row-oriented flex container fragment, its first line might
// contain one or more monolithic items that were pushed from the previous
// fragment specifically to avoid having those monolithic items overlap the
// page/column break. The situation is similar for single-row column-oriented
// flex container fragments, but a bit simpler; only their first item might have
// been pushed to avoid overlapping a page/column break.
//
// We'll have to place any such pushed items at the block-start edge of the
// current fragment's content-box, which is as close as we can get them to their
// theoretical/unfragmented position (without slicing them); but it does
// represent a shift away from their theoretical/unfragmented position (which
// was somewhere in the previous fragment).
//
// When that happens, we need to record the maximum such shift that we had to
// perform so that we can apply the same block-endwards shift to "downstream"
// items (items towards the block-end edge) that we could otherwise collide
// with. We also potentially apply the same shift when computing the block-end
// edge of this flex container fragment's content-box so that we don't
// inadvertently shift the last item (or line-of-items) to overlap the flex
// container's border, or content beyond the flex container.
//
// We use this structure to keep track of several metrics, in service of this
// goal. This structure is also necessary to adjust PerFragmentFlexData at the
// end of ReflowChildren().
//
// Note: "First" in the struct name means "BStart-most", not the order in the
// flex line array or flex item array.
struct FirstLineOrFirstItemBAxisMetrics final {
  // This value stores the block-end edge shift for 1) the BStart-most line in
  // the current fragment of a row-oriented flex container, or 2) the
  // BStart-most item in the current fragment of a single-line column-oriented
  // flex container. This number is non-negative.
  //
  // This value may become positive when any item is a first-in-flow and also
  // satisfies either the above condition 1) or 2), since that's a hint that it
  // could be monolithic or have a monolithic first descendant, and therefore an
  // item that might incur a page/column-break-dodging position-shift that this
  // variable needs to track.
  nscoord mBEndEdgeShift = 0;

  // The first and second value in the pair store the max block-end edges for
  // items before and after applying the per-item position-shift in the block
  // axis. We only record the block-end edges for items with first-in-flow
  // frames placed in the current flex container fragment. This is used only by
  // row-oriented flex containers.
  Maybe<std::pair<nscoord, nscoord>> mMaxBEndEdge;
};

std::tuple<nscoord, bool> nsFlexContainerFrame::ReflowChildren(
    const ReflowInput& aReflowInput, const nsSize& aContainerSize,
    const LogicalSize& aAvailableSizeForItems,
    const LogicalMargin& aBorderPadding, const FlexboxAxisTracker& aAxisTracker,
    FlexLayoutResult& aFlr, PerFragmentFlexData& aFragmentData) {
  if (HidesContentForLayout()) {
    return {0, false};
  }

  // Before giving each child a final reflow, calculate the origin of the
  // flex container's content box (with respect to its border-box), so that
  // we can compute our flex item's final positions.
  WritingMode flexWM = aReflowInput.GetWritingMode();
  const LogicalPoint containerContentBoxOrigin =
      aBorderPadding.StartOffset(flexWM);

  // The block-end of children is relative to the flex container's border-box.
  nscoord maxBlockEndEdgeOfChildren = containerContentBoxOrigin.B(flexWM);

  FirstLineOrFirstItemBAxisMetrics bAxisMetrics;
  FrameHashtable pushedItems;
  FrameHashtable incompleteItems;
  FrameHashtable overflowIncompleteItems;

  const bool isSingleLine =
      StyleFlexWrap::Nowrap == aReflowInput.mStylePosition->mFlexWrap;

  // FINAL REFLOW: Give each child frame another chance to reflow, now that
  // we know its final size and position.
  const FlexLine& startmostLine = StartmostLine(aFlr.mLines, aAxisTracker);
  const FlexItem* startmostItem =
      startmostLine.IsEmpty() ? nullptr
                              : &startmostLine.StartmostItem(aAxisTracker);

  const size_t numLines = aFlr.mLines.Length();
  for (size_t lineIdx = 0; lineIdx < numLines; ++lineIdx) {
    // Iterate flex lines from the startmost to endmost (relative to flex
    // container's writing-mode).
    const auto& line =
        aFlr.mLines[aAxisTracker.IsCrossAxisReversed() ? numLines - lineIdx - 1
                                                       : lineIdx];
    MOZ_ASSERT(lineIdx != 0 || &line == &startmostLine,
               "Logic for finding startmost line should be consistent!");

    const size_t numItems = line.Items().Length();
    for (size_t itemIdx = 0; itemIdx < numItems; ++itemIdx) {
      // Iterate flex items from the startmost to endmost (relative to flex
      // container's writing-mode).
      const FlexItem& item = line.Items()[aAxisTracker.IsMainAxisReversed()
                                              ? numItems - itemIdx - 1
                                              : itemIdx];
      MOZ_ASSERT(lineIdx != 0 || itemIdx != 0 || &item == startmostItem,
                 "Logic for finding startmost item should be consistent!");

      LogicalPoint framePos = aAxisTracker.LogicalPointFromFlexRelativePoint(
          item.MainPosition(), item.CrossPosition(), aFlr.mContentBoxMainSize,
          aFlr.mContentBoxCrossSize);
      // This variable records the item's block-end edge before we give it a
      // per-item-position-shift, if the item is a first-in-flow in the
      // startmost line of a row-oriented flex container fragment. It is used to
      // determine the block-end edge shift for the startmost line at the end of
      // the outer loop.
      Maybe<nscoord> frameBPosBeforePerItemShift;

      if (item.Frame()->GetPrevInFlow()) {
        // The item is a continuation. Lay it out at the beginning of the
        // available space.
        framePos.B(flexWM) = 0;
      } else if (GetPrevInFlow()) {
        // The item we're placing is not a continuation; though we're placing it
        // into a flex container fragment which *is* a continuation. To compute
        // the item's correct position in this fragment, we adjust the item's
        // theoretical/unfragmented block-direction position by subtracting the
        // cumulative content-box block-size for all the previous fragments and
        // adding the cumulative block-end edge shift.
        //
        // Note that the item's position in this fragment has not been finalized
        // yet. At this point, we've adjusted the item's
        // theoretical/unfragmented position to be relative to the block-end
        // edge of the previous container fragment's content-box. Later, we'll
        // compute per-item position-shift to finalize its position.
        framePos.B(flexWM) -= aFragmentData.mCumulativeContentBoxBSize;
        framePos.B(flexWM) += aFragmentData.mCumulativeBEndEdgeShift;

        // This helper gets the per-item position-shift in the block-axis.
        auto GetPerItemPositionShiftToBEnd = [&]() {
          if (framePos.B(flexWM) >= 0) {
            // The item final position might be in current flex container
            // fragment or in any of the later fragments. No adjustment needed.
            return 0;
          }

          // The item's block position is negative, but we want to place it at
          // the content-box block-start edge of this container fragment. To
          // achieve this, return a negated (positive) value to make the final
          // block position zero.
          //
          // This scenario occurs when fragmenting a row-oriented flex container
          // where this item is pushed to this container fragment.
          return -framePos.B(flexWM);
        };

        if (aAxisTracker.IsRowOriented()) {
          if (&line == &startmostLine) {
            frameBPosBeforePerItemShift.emplace(framePos.B(flexWM));
            framePos.B(flexWM) += GetPerItemPositionShiftToBEnd();
          } else {
            // We've computed how far the block-end edge of the startmost line
            // had to shift at the end of outer loop. Here, we just shift all
            // items in rest of the lines by the same amount.
            framePos.B(flexWM) += bAxisMetrics.mBEndEdgeShift;
          }
        } else {
          MOZ_ASSERT(aAxisTracker.IsColumnOriented());
          if (isSingleLine) {
            if (&item == startmostItem) {
              bAxisMetrics.mBEndEdgeShift = GetPerItemPositionShiftToBEnd();
            }
            framePos.B(flexWM) += bAxisMetrics.mBEndEdgeShift;
          } else {
            // Bug 1806717: We need a more sophisticated solution for multi-line
            // column-oriented flex container when each line has a different
            // position-shift value. For now, we don't shift them.
          }
        }
      }

      // Adjust available block-size for the item. (We compute it here because
      // framePos is still relative to the container's content-box.)
      //
      // Note: The available block-size can become negative if item's
      // block-direction position is below available space's block-end.
      const nscoord availableBSizeForItem =
          aAvailableSizeForItems.BSize(flexWM) == NS_UNCONSTRAINEDSIZE
              ? NS_UNCONSTRAINEDSIZE
              : aAvailableSizeForItems.BSize(flexWM) - framePos.B(flexWM);

      // Adjust framePos to be relative to the container's border-box
      // (i.e. its frame rect), instead of the container's content-box:
      framePos += containerContentBoxOrigin;

      // Check if we actually need to reflow the item -- if the item's position
      // is below the available space's block-end, push it to our next-in-flow;
      // if it does need a reflow, and we already reflowed it with the right
      // content-box size.
      const bool childBPosExceedAvailableSpaceBEnd =
          availableBSizeForItem != NS_UNCONSTRAINEDSIZE &&
          availableBSizeForItem <= 0;
      bool itemInPushedItems = false;
      if (childBPosExceedAvailableSpaceBEnd) {
        // Note: Even if all of our items are beyond the available space & get
        // pushed here, we'll be guaranteed to place at least one of them (and
        // make progress) in one of the flex container's *next* fragment. It's
        // because ComputeAvailableSizeForItems() always reserves at least 1px
        // available block-size for its children, and we consume all available
        // block-size and add it to
        // PerFragmentFlexData::mCumulativeContentBoxBSize even if we are not
        // laying out any child.
        FLEX_LOG(
            "[frag] Flex item %p needed to be pushed to container's "
            "next-in-flow due to position below available space's block-end",
            item.Frame());
        pushedItems.Insert(item.Frame());
        itemInPushedItems = true;
      } else if (item.NeedsFinalReflow(aReflowInput)) {
        // The available size must be in item's writing-mode.
        const WritingMode itemWM = item.GetWritingMode();
        const auto availableSize =
            LogicalSize(flexWM, aAvailableSizeForItems.ISize(flexWM),
                        availableBSizeForItem)
                .ConvertTo(itemWM, flexWM);

        const nsReflowStatus childReflowStatus =
            ReflowFlexItem(aAxisTracker, aReflowInput, item, framePos,
                           availableSize, aContainerSize);

        const bool shouldPushItem = [&]() {
          if (availableBSizeForItem == NS_UNCONSTRAINEDSIZE) {
            // If the available block-size is unconstrained, then we're not
            // fragmenting and we don't want to push the item.
            return false;
          }
          if (framePos.B(flexWM) == containerContentBoxOrigin.B(flexWM)) {
            // The flex item is adjacent with block-start of the container's
            // content-box. Don't push it, or we'll trap in an infinite loop.
            return false;
          }
          if (item.Frame()->BSize() <= availableBSizeForItem) {
            return false;
          }
          if (aAxisTracker.IsColumnOriented() &&
              item.Frame()->StyleDisplay()->mBreakBefore ==
                  StyleBreakBetween::Avoid) {
            return false;
          }
          return true;
        }();
        if (shouldPushItem) {
          FLEX_LOG(
              "[frag] Flex item %p needed to be pushed to container's "
              "next-in-flow because its block-size is larger than the "
              "available space",
              item.Frame());
          pushedItems.Insert(item.Frame());
          itemInPushedItems = true;
        } else if (childReflowStatus.IsIncomplete()) {
          incompleteItems.Insert(item.Frame());
        } else if (childReflowStatus.IsOverflowIncomplete()) {
          overflowIncompleteItems.Insert(item.Frame());
        }
      } else {
        MoveFlexItemToFinalPosition(item, framePos, aContainerSize);
      }

      if (!itemInPushedItems) {
        const nscoord itemBSize = item.Frame()->BSize(flexWM);
        const nscoord bEndEdgeAfterPerItemShift =
            framePos.B(flexWM) + itemBSize;

        // The item (or a fragment thereof) was placed in this flex container
        // fragment. Update the max block-end edge with the item's block-end
        // edge.
        maxBlockEndEdgeOfChildren =
            std::max(maxBlockEndEdgeOfChildren, bEndEdgeAfterPerItemShift);

        if (frameBPosBeforePerItemShift) {
          // Make the block-end edge relative to flex container's border-box
          // because bEndEdgeAfterPerItemShift is relative to the border-box.
          const nscoord bEndEdgeBeforePerItemShift =
              containerContentBoxOrigin.B(flexWM) +
              *frameBPosBeforePerItemShift + itemBSize;

          if (bAxisMetrics.mMaxBEndEdge) {
            auto& [before, after] = *bAxisMetrics.mMaxBEndEdge;
            before = std::max(before, bEndEdgeBeforePerItemShift);
            after = std::max(after, bEndEdgeAfterPerItemShift);
          } else {
            bAxisMetrics.mMaxBEndEdge.emplace(bEndEdgeBeforePerItemShift,
                                              bEndEdgeAfterPerItemShift);
          }
        }
      }

      // If the item has auto margins, and we were tracking the UsedMargin
      // property, set the property to the computed margin values.
      if (item.HasAnyAutoMargin()) {
        nsMargin* propValue =
            item.Frame()->GetProperty(nsIFrame::UsedMarginProperty());
        if (propValue) {
          *propValue = item.PhysicalMargin();
        }
      }
    }

    // Now we've finished processing all the items in the startmost line.
    // Determine the amount by which the startmost line's block-end edge has
    // shifted, so we can apply the same shift for the remaining lines.
    if (GetPrevInFlow() && aAxisTracker.IsRowOriented() &&
        &line == &startmostLine && bAxisMetrics.mMaxBEndEdge) {
      auto& [before, after] = *bAxisMetrics.mMaxBEndEdge;
      bAxisMetrics.mBEndEdgeShift = after - before;
    }
  }

  if (!aFlr.mPlaceholders.IsEmpty()) {
    ReflowPlaceholders(aReflowInput, aFlr.mPlaceholders,
                       containerContentBoxOrigin, aContainerSize);
  }

  const bool anyChildIncomplete = PushIncompleteChildren(
      pushedItems, incompleteItems, overflowIncompleteItems);

  // TODO: Try making this a fatal assertion after we fix bug 1751260.
  NS_ASSERTION(!anyChildIncomplete ||
                   aAvailableSizeForItems.BSize(flexWM) != NS_UNCONSTRAINEDSIZE,
               "We shouldn't have any incomplete children if the available "
               "block-size is unconstrained!");

  if (!pushedItems.IsEmpty()) {
    AddStateBits(NS_STATE_FLEX_DID_PUSH_ITEMS);
  }

  if (GetPrevInFlow()) {
    aFragmentData.mCumulativeBEndEdgeShift += bAxisMetrics.mBEndEdgeShift;
  }

  return {maxBlockEndEdgeOfChildren, anyChildIncomplete};
}

void nsFlexContainerFrame::PopulateReflowOutput(
    ReflowOutput& aReflowOutput, const ReflowInput& aReflowInput,
    nsReflowStatus& aStatus, const LogicalSize& aContentBoxSize,
    const LogicalMargin& aBorderPadding, const nscoord aConsumedBSize,
    const bool aMayNeedNextInFlow, const nscoord aMaxBlockEndEdgeOfChildren,
    const bool aAnyChildIncomplete, const FlexboxAxisTracker& aAxisTracker,
    FlexLayoutResult& aFlr) {
  const WritingMode flexWM = aReflowInput.GetWritingMode();

  // Compute flex container's desired size (in its own writing-mode).
  LogicalSize desiredSizeInFlexWM(flexWM);
  desiredSizeInFlexWM.ISize(flexWM) =
      aContentBoxSize.ISize(flexWM) + aBorderPadding.IStartEnd(flexWM);

  // Unconditionally skip adding block-end border and padding for now. We add it
  // lower down, after we've established baseline and decided whether bottom
  // border-padding fits (if we're fragmented).
  const nscoord effectiveContentBSizeWithBStartBP =
      aContentBoxSize.BSize(flexWM) - aConsumedBSize +
      aBorderPadding.BStart(flexWM);
  nscoord blockEndContainerBP = aBorderPadding.BEnd(flexWM);

  if (aMayNeedNextInFlow) {
    // We assume our status should be reported as incomplete because we may need
    // a next-in-flow.
    bool isStatusIncomplete = true;

    const nscoord availableBSizeMinusBEndBP =
        aReflowInput.AvailableBSize() - aBorderPadding.BEnd(flexWM);

    if (aMaxBlockEndEdgeOfChildren <= availableBSizeMinusBEndBP) {
      // Consume all the available block-size.
      desiredSizeInFlexWM.BSize(flexWM) = availableBSizeMinusBEndBP;
    } else {
      // This case happens if we have some tall unbreakable children exceeding
      // the available block-size.
      desiredSizeInFlexWM.BSize(flexWM) = std::min(
          effectiveContentBSizeWithBStartBP, aMaxBlockEndEdgeOfChildren);

      if ((aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE ||
           !aAnyChildIncomplete) &&
          aMaxBlockEndEdgeOfChildren >= effectiveContentBSizeWithBStartBP) {
        // We have some tall unbreakable child that's sticking off the end of
        // our fragment, *and* forcing us to consume all of our remaining
        // content block-size and call ourselves complete.
        //
        // - If we have a definite block-size: we get here if the tall child
        //   makes us reach that block-size.
        // - If we have a content-based block-size: we get here if the tall
        //   child makes us reach the content-based block-size from a
        //   theoretical unfragmented layout, *and* all our children are
        //   complete. (Note that if we have some incomplete child, then we
        //   instead prefer to return an incomplete status, so we can get a
        //   next-in-flow to include that child's requested next-in-flow, in the
        //   spirit of having a block-size that fits the content.)
        //
        // TODO: the auto-height case might need more subtlety; see bug 1828977.
        isStatusIncomplete = false;

        // We also potentially need to get the unskipped block-end border and
        // padding (if we assumed it'd be skipped as part of our tentative
        // assumption that we'd be incomplete).
        if (aReflowInput.mStyleBorder->mBoxDecorationBreak ==
            StyleBoxDecorationBreak::Slice) {
          blockEndContainerBP =
              aReflowInput.ComputedLogicalBorderPadding(flexWM).BEnd(flexWM);
        }
      }
    }

    if (isStatusIncomplete) {
      aStatus.SetIncomplete();
    }
  } else {
    // Our own effective content-box block-size can fit within the available
    // block-size.
    desiredSizeInFlexWM.BSize(flexWM) = effectiveContentBSizeWithBStartBP;
  }

  // Now, we account for how the block-end border and padding (if any) impacts
  // our desired size. If adding it pushes us over the available block-size,
  // then we become incomplete (unless we already weren't asking for any
  // block-size, in which case we stay complete to avoid looping forever).
  //
  // NOTE: If we have auto block-size, we allow our block-end border and padding
  // to push us over the available block-size without requesting a continuation,
  // for consistency with the behavior of "display:block" elements.
  const nscoord effectiveContentBSizeWithBStartEndBP =
      desiredSizeInFlexWM.BSize(flexWM) + blockEndContainerBP;

  if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
      effectiveContentBSizeWithBStartEndBP > aReflowInput.AvailableBSize() &&
      desiredSizeInFlexWM.BSize(flexWM) != 0 &&
      aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) {
    // We couldn't fit with the block-end border and padding included, so we'll
    // need a continuation.
    aStatus.SetIncomplete();

    if (aReflowInput.mStyleBorder->mBoxDecorationBreak ==
        StyleBoxDecorationBreak::Slice) {
      blockEndContainerBP = 0;
    }
  }

  // The variable "blockEndContainerBP" now accurately reflects how much (if
  // any) block-end border and padding we want for this frame, so we can proceed
  // to add it in.
  desiredSizeInFlexWM.BSize(flexWM) += blockEndContainerBP;

  if (aStatus.IsComplete() && aAnyChildIncomplete) {
    aStatus.SetOverflowIncomplete();
    aStatus.SetNextInFlowNeedsReflow();
  }

  // If we are the first-in-flow and not fully complete (either our block-size
  // or any of our flex items cannot fit in the available block-size), and the
  // style requires us to avoid breaking inside, set the status to prompt our
  // parent to push us to the next page/column.
  if (!GetPrevInFlow() && !aStatus.IsFullyComplete() &&
      ShouldAvoidBreakInside(aReflowInput)) {
    aStatus.SetInlineLineBreakBeforeAndReset();
    return;
  }

  // If we haven't established a baseline for the container yet, i.e. if we
  // don't have any flex item in the startmost flex line that participates in
  // baseline alignment, then use the startmost flex item to derive the
  // container's baseline.
  if (const FlexLine& line = StartmostLine(aFlr.mLines, aAxisTracker);
      aFlr.mAscent == nscoord_MIN && !line.IsEmpty()) {
    const FlexItem& item = line.StartmostItem(aAxisTracker);
    aFlr.mAscent = item.Frame()
                       ->GetLogicalPosition(
                           flexWM, desiredSizeInFlexWM.GetPhysicalSize(flexWM))
                       .B(flexWM) +
                   item.ResolvedAscent(true);
  }

  // Likewise, if we don't have any flex item in the endmost flex line that
  // participates in last baseline alignment, then use the endmost flex item to
  // derived the container's last baseline.
  if (const FlexLine& line = EndmostLine(aFlr.mLines, aAxisTracker);
      aFlr.mAscentForLast == nscoord_MIN && !line.IsEmpty()) {
    const FlexItem& item = line.EndmostItem(aAxisTracker);
    const nscoord lastAscent =
        item.Frame()
            ->GetLogicalPosition(flexWM,
                                 desiredSizeInFlexWM.GetPhysicalSize(flexWM))
            .B(flexWM) +
        item.ResolvedAscent(false);

    aFlr.mAscentForLast = desiredSizeInFlexWM.BSize(flexWM) - lastAscent;
  }

  if (aFlr.mAscent == nscoord_MIN) {
    // Still don't have our baseline set -- this happens if we have no
    // children, if our children are huge enough that they have nscoord_MIN
    // as their baseline, or our content is hidden in which case, we'll use the
    // wrong baseline (but no big deal).
    NS_WARNING_ASSERTION(
        HidesContentForLayout() || aFlr.mLines[0].IsEmpty(),
        "Have flex items but didn't get an ascent - that's odd (or there are "
        "just gigantic sizes involved)");
    // Per spec, synthesize baseline from the flex container's content box
    // (i.e. use block-end side of content-box)
    // XXXdholbert This only makes sense if parent's writing mode is
    // horizontal (& even then, really we should be using the BSize in terms
    // of the parent's writing mode, not ours). Clean up in bug 1155322.
    aFlr.mAscent = effectiveContentBSizeWithBStartBP;
  }

  if (aFlr.mAscentForLast == nscoord_MIN) {
    // Still don't have our last baseline set -- this happens if we have no
    // children, if our children are huge enough that they have nscoord_MIN
    // as their baseline, or our content is hidden in which case, we'll use the
    // wrong baseline (but no big deal).
    NS_WARNING_ASSERTION(
        HidesContentForLayout() || aFlr.mLines[0].IsEmpty(),
        "Have flex items but didn't get an ascent - that's odd (or there are "
        "just gigantic sizes involved)");
    // Per spec, synthesize baseline from the flex container's content box
    // (i.e. use block-end side of content-box)
    // XXXdholbert This only makes sense if parent's writing mode is
    // horizontal (& even then, really we should be using the BSize in terms
    // of the parent's writing mode, not ours). Clean up in bug 1155322.
    aFlr.mAscentForLast = blockEndContainerBP;
  }

  if (HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE)) {
    // This will force our parent to call GetLogicalBaseline, which will
    // synthesize a margin-box baseline.
    aReflowOutput.SetBlockStartAscent(ReflowOutput::ASK_FOR_BASELINE);
  } else {
    // XXXdholbert aFlr.mAscent needs to be in terms of our parent's
    // writing-mode here. See bug 1155322.
    aReflowOutput.SetBlockStartAscent(aFlr.mAscent);
  }

  // Cache the container baselines so that our parent can baseline-align us.
  mFirstBaseline = aFlr.mAscent;
  mLastBaseline = aFlr.mAscentForLast;

  // Convert flex container's final desired size to parent's WM, for outparam.
  aReflowOutput.SetSize(flexWM, desiredSizeInFlexWM);
}

void nsFlexContainerFrame::MoveFlexItemToFinalPosition(
    const FlexItem& aItem, const LogicalPoint& aFramePos,
    const nsSize& aContainerSize) {
  const WritingMode outerWM = aItem.ContainingBlockWM();
  const nsStyleDisplay* display = aItem.Frame()->StyleDisplay();
  LogicalPoint pos(aFramePos);
  if (display->IsRelativelyOrStickyPositionedStyle()) {
    // If the item is relatively positioned, look up its offsets (cached from
    // previous reflow). A sticky positioned item can pass a dummy
    // logicalOffsets into ApplyRelativePositioning().
    LogicalMargin logicalOffsets(outerWM);
    if (display->IsRelativelyPositionedStyle()) {
      nsMargin* cachedOffsets =
          aItem.Frame()->GetProperty(nsIFrame::ComputedOffsetProperty());
      MOZ_ASSERT(
          cachedOffsets,
          "relpos previously-reflowed frame should've cached its offsets");
      logicalOffsets = LogicalMargin(outerWM, *cachedOffsets);
    }
    ReflowInput::ApplyRelativePositioning(aItem.Frame(), outerWM,
                                          logicalOffsets, &pos, aContainerSize);
  }

  FLEX_LOG("Moving flex item %p to its desired position %s", aItem.Frame(),
           ToString(pos).c_str());
  aItem.Frame()->SetPosition(outerWM, pos, aContainerSize);
  PositionFrameView(aItem.Frame());
  PositionChildViews(aItem.Frame());
}

nsReflowStatus nsFlexContainerFrame::ReflowFlexItem(
    const FlexboxAxisTracker& aAxisTracker, const ReflowInput& aReflowInput,
    const FlexItem& aItem, const LogicalPoint& aFramePos,
    const LogicalSize& aAvailableSize, const nsSize& aContainerSize) {
  FLEX_LOG("Doing final reflow for flex item %p", aItem.Frame());

  WritingMode outerWM = aReflowInput.GetWritingMode();

  StyleSizeOverrides sizeOverrides;
  // Override flex item's main size.
  if (aItem.IsInlineAxisMainAxis()) {
    sizeOverrides.mStyleISize.emplace(aItem.StyleMainSize());
    FLEX_LOGV(" Main size (inline-size) override: %d", aItem.MainSize());
  } else {
    sizeOverrides.mStyleBSize.emplace(aItem.StyleMainSize());
    FLEX_LOGV(" Main size (block-size) override: %d", aItem.MainSize());
  }

  // Override flex item's cross size if it was stretched in the cross axis (in
  // which case we're imposing a cross size).
  if (aItem.IsStretched()) {
    if (aItem.IsInlineAxisCrossAxis()) {
      sizeOverrides.mStyleISize.emplace(aItem.StyleCrossSize());
      FLEX_LOGV(" Cross size (inline-size) override: %d", aItem.CrossSize());
    } else {
      sizeOverrides.mStyleBSize.emplace(aItem.StyleCrossSize());
      FLEX_LOGV(" Cross size (block-size) override: %d", aItem.CrossSize());
    }
  }
  if (sizeOverrides.mStyleBSize) {
    // We are overriding the block-size. For robustness, we always assume that
    // this represents a block-axis resize for the frame. This may be
    // conservative, but we do capture all the conditions in the block-axis
    // (checked in NeedsFinalReflow()) that make this item require a final
    // reflow. This sets relevant flags in ReflowInput::InitResizeFlags().
    aItem.Frame()->SetHasBSizeChange(true);
  }

  ReflowInput childReflowInput(PresContext(), aReflowInput, aItem.Frame(),
                               aAvailableSize, Nothing(), {}, sizeOverrides);

  if (aItem.TreatBSizeAsIndefinite() && aItem.IsBlockAxisMainAxis()) {
    childReflowInput.mFlags.mTreatBSizeAsIndefinite = true;
  }

  if (aItem.IsStretched() && aItem.IsBlockAxisCrossAxis()) {
    // This item is stretched (in the cross axis), and that axis is its block
    // axis.  That stretching effectively gives it a relative BSize.
    // XXXdholbert This flag only makes a difference if we use the flex items'
    // frame-state when deciding whether to reflow them -- and we don't, as of
    // the changes in bug 851607. So this has no effect right now, but it might
    // make a difference if we optimize to use dirty bits in the
    // future. (Reftests flexbox-resizeviewport-1.xhtml and -2.xhtml are
    // intended to catch any regressions here, if we end up relying on this bit
    // & neglecting to set it.)
    aItem.Frame()->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
  }

  // NOTE: Be very careful about doing anything else with childReflowInput
  // after this point, because some of its methods (e.g. SetComputedWidth)
  // internally call InitResizeFlags and stomp on mVResize & mHResize.

  FLEX_LOG("Reflowing flex item %p at its desired position %s", aItem.Frame(),
           ToString(aFramePos).c_str());

  // CachedFlexItemData is stored in item's writing mode, so we pass
  // aChildReflowInput into ReflowOutput's constructor.
  ReflowOutput childReflowOutput(childReflowInput);
  nsReflowStatus childReflowStatus;
  ReflowChild(aItem.Frame(), PresContext(), childReflowOutput, childReflowInput,
              outerWM, aFramePos, aContainerSize, ReflowChildFlags::Default,
              childReflowStatus);

  // XXXdholbert Perhaps we should call CheckForInterrupt here; see bug 1495532.

  FinishReflowChild(aItem.Frame(), PresContext(), childReflowOutput,
                    &childReflowInput, outerWM, aFramePos, aContainerSize,
                    ReflowChildFlags::ApplyRelativePositioning);

  aItem.SetAscent(childReflowOutput.BlockStartAscent());

  // Update our cached flex item info:
  if (auto* cached = aItem.Frame()->GetProperty(CachedFlexItemData::Prop())) {
    cached->Update(childReflowInput, childReflowOutput,
                   FlexItemReflowType::Final);
  } else {
    cached = new CachedFlexItemData(childReflowInput, childReflowOutput,
                                    FlexItemReflowType::Final);
    aItem.Frame()->SetProperty(CachedFlexItemData::Prop(), cached);
  }

  return childReflowStatus;
}

void nsFlexContainerFrame::ReflowPlaceholders(
    const ReflowInput& aReflowInput, nsTArray<nsIFrame*>& aPlaceholders,
    const LogicalPoint& aContentBoxOrigin, const nsSize& aContainerSize) {
  WritingMode outerWM = aReflowInput.GetWritingMode();

  // As noted in this method's documentation, we'll reflow every entry in
  // |aPlaceholders| at the container's content-box origin.
  for (nsIFrame* placeholder : aPlaceholders) {
    MOZ_ASSERT(placeholder->IsPlaceholderFrame(),
               "placeholders array should only contain placeholder frames");
    WritingMode wm = placeholder->GetWritingMode();
    LogicalSize availSize = aReflowInput.ComputedSize(wm);
    ReflowInput childReflowInput(PresContext(), aReflowInput, placeholder,
                                 availSize);
    // No need to set the -webkit-line-clamp related flags when reflowing
    // a placeholder.
    ReflowOutput childReflowOutput(outerWM);
    nsReflowStatus childReflowStatus;
    ReflowChild(placeholder, PresContext(), childReflowOutput, childReflowInput,
                outerWM, aContentBoxOrigin, aContainerSize,
                ReflowChildFlags::Default, childReflowStatus);

    FinishReflowChild(placeholder, PresContext(), childReflowOutput,
                      &childReflowInput, outerWM, aContentBoxOrigin,
                      aContainerSize, ReflowChildFlags::Default);

    // Mark the placeholder frame to indicate that it's not actually at the
    // element's static position, because we need to apply CSS Alignment after
    // we determine the OOF's size:
    placeholder->AddStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN);
  }
}

nscoord nsFlexContainerFrame::IntrinsicISize(gfxContext* aRenderingContext,
                                             IntrinsicISizeType aType) {
  nscoord containerISize = 0;
  const nsStylePosition* stylePos = StylePosition();
  const FlexboxAxisTracker axisTracker(this);

  nscoord mainGapSize;
  if (axisTracker.IsRowOriented()) {
    mainGapSize = nsLayoutUtils::ResolveGapToLength(stylePos->mColumnGap,
                                                    NS_UNCONSTRAINEDSIZE);
  } else {
    mainGapSize = nsLayoutUtils::ResolveGapToLength(stylePos->mRowGap,
                                                    NS_UNCONSTRAINEDSIZE);
  }

  const bool useMozBoxCollapseBehavior =
      StyleVisibility()->UseLegacyCollapseBehavior();

  // The loop below sets aside space for a gap before each item besides the
  // first. This bool helps us handle that special-case.
  bool onFirstChild = true;

  for (nsIFrame* childFrame : mFrames) {
    // Skip out-of-flow children because they don't participate in flex layout.
    if (childFrame->IsPlaceholderFrame()) {
      continue;
    }

    if (useMozBoxCollapseBehavior &&
        childFrame->StyleVisibility()->IsCollapse()) {
      // If we're using legacy "visibility:collapse" behavior, then we don't
      // care about the sizes of any collapsed children.
      continue;
    }

    nscoord childISize = nsLayoutUtils::IntrinsicForContainer(
        aRenderingContext, childFrame, aType);

    // * For a row-oriented single-line flex container, the intrinsic
    // {min/pref}-isize is the sum of its items' {min/pref}-isizes and
    // (n-1) column gaps.
    // * For a column-oriented flex container, the intrinsic min isize
    // is the max of its items' min isizes.
    // * For a row-oriented multi-line flex container, the intrinsic
    // pref isize is former (sum), and its min isize is the latter (max).
    bool isSingleLine = (StyleFlexWrap::Nowrap == stylePos->mFlexWrap);
    if (axisTracker.IsRowOriented() &&
        (isSingleLine || aType == IntrinsicISizeType::PrefISize)) {
      containerISize += childISize;
      if (!onFirstChild) {
        containerISize += mainGapSize;
      }
      onFirstChild = false;
    } else {  // (col-oriented, or MinISize for multi-line row flex container)
      containerISize = std::max(containerISize, childISize);
    }
  }

  return containerISize;
}

/* virtual */
nscoord nsFlexContainerFrame::GetMinISize(gfxContext* aRenderingContext) {
  DISPLAY_MIN_INLINE_SIZE(this, mCachedMinISize);
  if (mCachedMinISize == NS_INTRINSIC_ISIZE_UNKNOWN) {
    if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
      mCachedMinISize = *containISize;
    } else {
      mCachedMinISize =
          IntrinsicISize(aRenderingContext, IntrinsicISizeType::MinISize);
    }
  }

  return mCachedMinISize;
}

/* virtual */
nscoord nsFlexContainerFrame::GetPrefISize(gfxContext* aRenderingContext) {
  DISPLAY_PREF_INLINE_SIZE(this, mCachedPrefISize);
  if (mCachedPrefISize == NS_INTRINSIC_ISIZE_UNKNOWN) {
    if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
      mCachedPrefISize = *containISize;
    } else {
      mCachedPrefISize =
          IntrinsicISize(aRenderingContext, IntrinsicISizeType::PrefISize);
    }
  }

  return mCachedPrefISize;
}

int32_t nsFlexContainerFrame::GetNumLines() const {
  // TODO(emilio, bug 1793251): Treating all row oriented frames as single-lines
  // might not be great for flex-wrap'd containers, consider trying to do
  // better? We probably would need to persist more stuff than we do after
  // layout.
  return FlexboxAxisInfo(this).mIsRowOriented ? 1 : mFrames.GetLength();
}

bool nsFlexContainerFrame::IsLineIteratorFlowRTL() {
  FlexboxAxisInfo info(this);
  if (info.mIsRowOriented) {
    const bool isRtl = StyleVisibility()->mDirection == StyleDirection::Rtl;
    return info.mIsMainAxisReversed != isRtl;
  }
  return false;
}

Result<nsILineIterator::LineInfo, nsresult> nsFlexContainerFrame::GetLine(
    int32_t aLineNumber) {
  if (aLineNumber < 0 || aLineNumber >= GetNumLines()) {
    return Err(NS_ERROR_FAILURE);
  }
  FlexboxAxisInfo info(this);
  LineInfo lineInfo;
  if (info.mIsRowOriented) {
    lineInfo.mLineBounds = GetRect();
    lineInfo.mFirstFrameOnLine = mFrames.FirstChild();
    // This isn't quite ideal for multi-line row flexbox, see bug 1793251.
    lineInfo.mNumFramesOnLine = mFrames.GetLength();
  } else {
    // TODO(emilio, bug 1793322): Deal with column-reverse (mIsMainAxisReversed)
    nsIFrame* f = mFrames.FrameAt(aLineNumber);
    lineInfo.mLineBounds = f->GetRect();
    lineInfo.mFirstFrameOnLine = f;
    lineInfo.mNumFramesOnLine = 1;
  }
  return lineInfo;
}

int32_t nsFlexContainerFrame::FindLineContaining(nsIFrame* aFrame,
                                                 int32_t aStartLine) {
  const int32_t index = mFrames.IndexOf(aFrame);
  if (index < 0) {
    return -1;
  }
  const FlexboxAxisInfo info(this);
  if (info.mIsRowOriented) {
    return 0;
  }
  if (index < aStartLine) {
    return -1;
  }
  return index;
}

NS_IMETHODIMP
nsFlexContainerFrame::CheckLineOrder(int32_t aLine, bool* aIsReordered,
                                     nsIFrame** aFirstVisual,
                                     nsIFrame** aLastVisual) {
  *aIsReordered = false;
  *aFirstVisual = nullptr;
  *aLastVisual = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsFlexContainerFrame::FindFrameAt(int32_t aLineNumber, nsPoint aPos,
                                  nsIFrame** aFrameFound,
                                  bool* aPosIsBeforeFirstFrame,
                                  bool* aPosIsAfterLastFrame) {
  const auto wm = GetWritingMode();
  const LogicalPoint pos(wm, aPos, GetSize());
  const FlexboxAxisInfo info(this);

  *aFrameFound = nullptr;
  *aPosIsBeforeFirstFrame = true;
  *aPosIsAfterLastFrame = false;

  if (!info.mIsRowOriented) {
    nsIFrame* f = mFrames.FrameAt(aLineNumber);
    if (!f) {
      return NS_OK;
    }

    auto rect = f->GetLogicalRect(wm, GetSize());
    *aFrameFound = f;
    *aPosIsBeforeFirstFrame = pos.I(wm) < rect.IStart(wm);
    *aPosIsAfterLastFrame = pos.I(wm) > rect.IEnd(wm);
    return NS_OK;
  }

  LineFrameFinder finder(aPos, GetSize(), GetWritingMode(),
                         IsLineIteratorFlowRTL());
  for (nsIFrame* f : mFrames) {
    finder.Scan(f);
    if (finder.IsDone()) {
      break;
    }
  }
  finder.Finish(aFrameFound, aPosIsBeforeFirstFrame, aPosIsAfterLastFrame);
  return NS_OK;
}
