/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code is subject to the terms of the Mozilla Public License
 * version 2.0 (the "License"). You can obtain a copy of the License at
 * http://mozilla.org/MPL/2.0/. */

/* rendering object for CSS "display: grid | inline-grid" */

#include "nsGridContainerFrame.h"

#include <algorithm> // for std::stable_sort
#include <limits>
#include "mozilla/Maybe.h"
#include "mozilla/PodOperations.h" // for PodZero
#include "nsAbsoluteContainingBlock.h"
#include "nsAlgorithm.h" // for clamped()
#include "nsAutoPtr.h"
#include "nsCSSAnonBoxes.h"
#include "nsDataHashtable.h"
#include "nsDisplayList.h"
#include "nsHashKeys.h"
#include "nsIFrameInlines.h"
#include "nsPresContext.h"
#include "nsRenderingContext.h"
#include "nsReadableUtils.h"
#include "nsRuleNode.h"
#include "nsStyleContext.h"

using namespace mozilla;
typedef nsGridContainerFrame::TrackSize TrackSize;
const uint32_t nsGridContainerFrame::kTranslatedMaxLine =
  uint32_t(nsStyleGridLine::kMaxLine - nsStyleGridLine::kMinLine);
const uint32_t nsGridContainerFrame::kAutoLine = kTranslatedMaxLine + 3457U;

MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(TrackSize::StateBits)

class nsGridContainerFrame::GridItemCSSOrderIterator
{
public:
  enum OrderState { eUnknownOrder, eKnownOrdered, eKnownUnordered };
  enum ChildFilter { eSkipPlaceholders, eIncludeAll };
  GridItemCSSOrderIterator(nsIFrame* aGridContainer,
                           nsIFrame::ChildListID aListID,
                           ChildFilter aFilter = eSkipPlaceholders,
                           OrderState aState = eUnknownOrder)
    : mChildren(aGridContainer->GetChildList(aListID))
    , mArrayIndex(0)
    , mGridItemIndex(0)
    , mSkipPlaceholders(aFilter == eSkipPlaceholders)
#ifdef DEBUG
    , mGridContainer(aGridContainer)
    , mListID(aListID)
#endif
  {
    size_t count = 0;
    bool isOrdered = aState != eKnownUnordered;
    if (aState == eUnknownOrder) {
      auto maxOrder = std::numeric_limits<int32_t>::min();
      for (nsFrameList::Enumerator e(mChildren); !e.AtEnd(); e.Next()) {
        ++count;
        int32_t order = e.get()->StylePosition()->mOrder;
        if (order < maxOrder) {
          isOrdered = false;
          break;
        }
        maxOrder = order;
      }
    }
    if (isOrdered) {
      mEnumerator.emplace(mChildren);
    } else {
      count *= 2; // XXX somewhat arbitrary estimate for now...
      mArray.emplace(count);
      for (nsFrameList::Enumerator e(mChildren); !e.AtEnd(); e.Next()) {
        mArray->AppendElement(e.get());
      }
      // XXX replace this with nsTArray::StableSort when bug 1147091 is fixed.
      std::stable_sort(mArray->begin(), mArray->end(), IsCSSOrderLessThan);
    }

    if (mSkipPlaceholders) {
      SkipPlaceholders();
    }
  }

  nsIFrame* operator*() const
  {
    MOZ_ASSERT(!AtEnd());
    if (mEnumerator) {
      return mEnumerator->get();
    }
    return (*mArray)[mArrayIndex];
  }

  /**
   * Return the child index of the current item, placeholders not counted.
   * It's forbidden to call this method when the current frame is placeholder.
   */
  size_t GridItemIndex() const
  {
    MOZ_ASSERT(!AtEnd());
    MOZ_ASSERT((**this)->GetType() != nsGkAtoms::placeholderFrame,
               "MUST not call this when at a placeholder");
    return mGridItemIndex;
  }

  /**
   * Skip over placeholder children.
   */
  void SkipPlaceholders()
  {
    if (mEnumerator) {
      for (; !mEnumerator->AtEnd(); mEnumerator->Next()) {
        nsIFrame* child = mEnumerator->get();
        if (child->GetType() != nsGkAtoms::placeholderFrame) {
          return;
        }
      }
    } else {
      for (; mArrayIndex < mArray->Length(); ++mArrayIndex) {
        nsIFrame* child = (*mArray)[mArrayIndex];
        if (child->GetType() != nsGkAtoms::placeholderFrame) {
          return;
        }
      }
    }
  }

  bool AtEnd() const
  {
    MOZ_ASSERT(mEnumerator || mArrayIndex <= mArray->Length());
    return mEnumerator ? mEnumerator->AtEnd() : mArrayIndex >= mArray->Length();
  }

  void Next()
  {
#ifdef DEBUG
    MOZ_ASSERT(!AtEnd());
    nsFrameList list = mGridContainer->GetChildList(mListID);
    MOZ_ASSERT(list.FirstChild() == mChildren.FirstChild() &&
               list.LastChild() == mChildren.LastChild(),
               "the list of child frames must not change while iterating!");
#endif
    if (mSkipPlaceholders ||
        (**this)->GetType() != nsGkAtoms::placeholderFrame) {
      ++mGridItemIndex;
    }
    if (mEnumerator) {
      mEnumerator->Next();
    } else {
      ++mArrayIndex;
    }
    if (mSkipPlaceholders) {
      SkipPlaceholders();
    }
  }

  void Reset(ChildFilter aFilter = eSkipPlaceholders)
  {
    if (mEnumerator) {
      mEnumerator.reset();
      mEnumerator.emplace(mChildren);
    } else {
      mArrayIndex = 0;
    }
    mGridItemIndex = 0;
    mSkipPlaceholders = aFilter == eSkipPlaceholders;
    if (mSkipPlaceholders) {
      SkipPlaceholders();
    }
  }

  bool ItemsAreAlreadyInOrder() const { return mEnumerator.isSome(); }

private:
  static bool IsCSSOrderLessThan(nsIFrame* const& a, nsIFrame* const& b)
    { return a->StylePosition()->mOrder < b->StylePosition()->mOrder; }

  nsFrameList mChildren;
  // Used if child list is already in ascending 'order'.
  Maybe<nsFrameList::Enumerator> mEnumerator;
  // Used if child list is *not* in ascending 'order'.
  Maybe<nsTArray<nsIFrame*>> mArray;
  size_t mArrayIndex;
  // The index of the current grid item (placeholders excluded).
  size_t mGridItemIndex;
  // Skip placeholder children in the iteration?
  bool mSkipPlaceholders;
#ifdef DEBUG
  nsIFrame* mGridContainer;
  nsIFrame::ChildListID mListID;
#endif
};

/**
 * Encapsulates CSS track-sizing functions.
 */
struct MOZ_STACK_CLASS nsGridContainerFrame::TrackSizingFunctions
{
  const nsStyleCoord& MinSizingFor(uint32_t aTrackIndex) const
  {
    if (MOZ_UNLIKELY(aTrackIndex < mExplicitGridOffset)) {
      return mAutoMinSizing;
    }
    uint32_t index = aTrackIndex - mExplicitGridOffset;
    return index < mMinSizingFunctions.Length() ?
      mMinSizingFunctions[index] : mAutoMinSizing;
  }
  const nsStyleCoord& MaxSizingFor(uint32_t aTrackIndex) const
  {
    if (MOZ_UNLIKELY(aTrackIndex < mExplicitGridOffset)) {
      return mAutoMaxSizing;
    }
    uint32_t index = aTrackIndex - mExplicitGridOffset;
    return index < mMaxSizingFunctions.Length() ?
      mMaxSizingFunctions[index] : mAutoMaxSizing;
  }

  const nsTArray<nsStyleCoord>& mMinSizingFunctions;
  const nsTArray<nsStyleCoord>& mMaxSizingFunctions;
  const nsStyleCoord& mAutoMinSizing;
  const nsStyleCoord& mAutoMaxSizing;
  uint32_t mExplicitGridOffset;
};

/**
 * State for the tracks in one dimension.
 */
struct MOZ_STACK_CLASS nsGridContainerFrame::Tracks
{
  explicit Tracks(LogicalAxis aAxis) : mAxis(aAxis) {}

  void Initialize(const TrackSizingFunctions& aFunctions,
                  nscoord                     aGridGap,
                  uint32_t                    aNumTracks,
                  nscoord                     aContentBoxSize);

  /**
   * Return true if aRange spans at least one track with an intrinsic sizing
   * function and does not span any tracks with a <flex> max-sizing function.
   * @param aRange the span of tracks to check
   * @param aConstraint if MIN_ISIZE, treat a <flex> min-sizing as 'min-content'
   * @param aState will be set to the union of the state bits of all the spanned
   *               tracks, unless a flex track is found - then it only contains
   *               the union of the tracks up to and including the flex track.
   */
  bool HasIntrinsicButNoFlexSizingInRange(const LineRange&      aRange,
                                          IntrinsicISizeType    aConstraint,
                                          TrackSize::StateBits* aState) const;

  /**
   * Resolve Intrinsic Track Sizes.
   * http://dev.w3.org/csswg/css-grid/#algo-content
   */
  void ResolveIntrinsicSize(GridReflowState&            aState,
                            nsTArray<GridItemInfo>&     aGridItems,
                            const TrackSizingFunctions& aFunctions,
                            LineRange GridArea::*       aRange,
                            nscoord                     aPercentageBasis,
                            IntrinsicISizeType          aConstraint);

  /**
   * Helper for ResolveIntrinsicSize.  It implements step 1 "size tracks to fit
   * non-spanning items" in the spec.  Return true if the track has a <flex>
   * max-sizing function, false otherwise.
   */
  bool ResolveIntrinsicSizeStep1(GridReflowState&            aState,
                                 const TrackSizingFunctions& aFunctions,
                                 nscoord                     aPercentageBasis,
                                 IntrinsicISizeType          aConstraint,
                                 const LineRange&            aRange,
                                 nsIFrame*                   aGridItem);
  /**
   * Collect the tracks which are growable (matching aSelector) into
   * aGrowableTracks, and return the amount of space that can be used
   * to grow those tracks.  Specifically, we return aAvailableSpace minus
   * the sum of mBase's in aPlan (clamped to 0) for the tracks in aRange,
   * or zero when there are no growable tracks.
   * @note aPlan[*].mBase represents a planned new base or limit.
   */
  static nscoord CollectGrowable(nscoord                    aAvailableSpace,
                                 const nsTArray<TrackSize>& aPlan,
                                 const LineRange&           aRange,
                                 TrackSize::StateBits       aSelector,
                                 nsTArray<uint32_t>&        aGrowableTracks)
  {
    MOZ_ASSERT(aAvailableSpace > 0, "why call me?");
    nscoord space = aAvailableSpace;
    const uint32_t start = aRange.mStart;
    const uint32_t end = aRange.mEnd;
    for (uint32_t i = start; i < end; ++i) {
      const TrackSize& sz = aPlan[i];
      space -= sz.mBase;
      if (space <= 0) {
        return 0;
      }
      if ((sz.mState & aSelector) && !sz.IsFrozen()) {
        aGrowableTracks.AppendElement(i);
      }
    }
    return aGrowableTracks.IsEmpty() ? 0 : space;
  }

  void SetupGrowthPlan(nsTArray<TrackSize>&      aPlan,
                       const nsTArray<uint32_t>& aTracks) const
  {
    for (uint32_t track : aTracks) {
      aPlan[track] = mSizes[track];
    }
  }

  void CopyPlanToBase(const nsTArray<TrackSize>& aPlan,
                      const nsTArray<uint32_t>&  aTracks)
  {
    for (uint32_t track : aTracks) {
      MOZ_ASSERT(mSizes[track].mBase <= aPlan[track].mBase);
      mSizes[track].mBase = aPlan[track].mBase;
    }
  }

  void CopyPlanToLimit(const nsTArray<TrackSize>& aPlan,
                       const nsTArray<uint32_t>&  aTracks)
  {
    for (uint32_t track : aTracks) {
      MOZ_ASSERT(mSizes[track].mLimit == NS_UNCONSTRAINEDSIZE ||
                 mSizes[track].mLimit <= aPlan[track].mBase);
      mSizes[track].mLimit = aPlan[track].mBase;
    }
  }

  /**
   * Grow the planned size for tracks in aGrowableTracks up to their limit
   * and then freeze them (all aGrowableTracks must be unfrozen on entry).
   * Subtract the space added from aAvailableSpace and return that.
   */
  nscoord GrowTracksToLimit(nscoord                   aAvailableSpace,
                            nsTArray<TrackSize>&      aPlan,
                            const nsTArray<uint32_t>& aGrowableTracks) const
  {
    MOZ_ASSERT(aAvailableSpace > 0 && aGrowableTracks.Length() > 0);
    nscoord space = aAvailableSpace;
    uint32_t numGrowable = aGrowableTracks.Length();
    while (true) {
      nscoord spacePerTrack = std::max<nscoord>(space / numGrowable, 1);
      for (uint32_t track : aGrowableTracks) {
        TrackSize& sz = aPlan[track];
        if (sz.IsFrozen()) {
          continue;
        }
        nscoord newBase = sz.mBase + spacePerTrack;
        if (newBase > sz.mLimit) {
          nscoord consumed = sz.mLimit - sz.mBase;
          if (consumed > 0) {
            space -= consumed;
            sz.mBase = sz.mLimit;
          }
          sz.mState |= TrackSize::eFrozen;
          if (--numGrowable == 0) {
            return space;
          }
        } else {
          sz.mBase = newBase;
          space -= spacePerTrack;
        }
        MOZ_ASSERT(space >= 0);
        if (space == 0) {
          return 0;
        }
      }
    }
    MOZ_ASSERT_UNREACHABLE("we don't exit the loop above except by return");
    return 0;
  }

  /**
   * Helper for GrowSelectedTracksUnlimited.  For the set of tracks (S) that
   * match aMinSizingSelector: if a track in S doesn't match aMaxSizingSelector
   * then mark it with aSkipFlag.  If all tracks in S were marked then unmark
   * them.  Return aNumGrowable minus the number of tracks marked.  It is
   * assumed that aPlan have no aSkipFlag set for tracks in aGrowableTracks
   * on entry to this method.
   */
   uint32_t MarkExcludedTracks(nsTArray<TrackSize>&      aPlan,
                               uint32_t                  aNumGrowable,
                               const nsTArray<uint32_t>& aGrowableTracks,
                               TrackSize::StateBits      aMinSizingSelector,
                               TrackSize::StateBits      aMaxSizingSelector,
                               TrackSize::StateBits      aSkipFlag) const
  {
    bool foundOneSelected = false;
    bool foundOneGrowable = false;
    uint32_t numGrowable = aNumGrowable;
    for (uint32_t track : aGrowableTracks) {
      TrackSize& sz = aPlan[track];
      const auto state = sz.mState;
      if (state & aMinSizingSelector) {
        foundOneSelected = true;
        if (state & aMaxSizingSelector) {
          foundOneGrowable = true;
          continue;
        }
        sz.mState |= aSkipFlag;
        MOZ_ASSERT(numGrowable != 0);
        --numGrowable;
      }
    }
    // 12.5 "if there are no such tracks, then all affected tracks"
    if (foundOneSelected && !foundOneGrowable) {
      for (uint32_t track : aGrowableTracks) {
        aPlan[track].mState &= ~aSkipFlag;
      }
      numGrowable = aNumGrowable;
    }
    return numGrowable;
  }

  /**
   * Increase the planned size for tracks in aGrowableTracks that match
   * aSelector (or all tracks if aSelector is zero) beyond their limit.
   * This implements the "Distribute space beyond growth limits" step in
   * https://drafts.csswg.org/css-grid/#distribute-extra-space
   */
  void GrowSelectedTracksUnlimited(nscoord                   aAvailableSpace,
                                   nsTArray<TrackSize>&      aPlan,
                                   const nsTArray<uint32_t>& aGrowableTracks,
                                   TrackSize::StateBits      aSelector) const
  {
    MOZ_ASSERT(aAvailableSpace > 0 && aGrowableTracks.Length() > 0);
    uint32_t numGrowable = aGrowableTracks.Length();
    if (aSelector) {
      DebugOnly<TrackSize::StateBits> withoutFlexMin =
        TrackSize::StateBits(aSelector & ~TrackSize::eFlexMinSizing);
      MOZ_ASSERT(withoutFlexMin == TrackSize::eIntrinsicMinSizing ||
                 withoutFlexMin == TrackSize::eMinOrMaxContentMinSizing ||
                 withoutFlexMin == TrackSize::eMaxContentMinSizing);
      // Note that eMaxContentMinSizing is always included. We do those first:
      numGrowable = MarkExcludedTracks(aPlan, numGrowable, aGrowableTracks,
                                       TrackSize::eMaxContentMinSizing,
                                       TrackSize::eMaxContentMaxSizing,
                                       TrackSize::eSkipGrowUnlimited1);
      // Now mark min-content/auto/<flex> min-sizing tracks if requested.
      auto minOrAutoSelector = aSelector & ~TrackSize::eMaxContentMinSizing;
      if (minOrAutoSelector) {
        numGrowable = MarkExcludedTracks(aPlan, numGrowable, aGrowableTracks,
                                         minOrAutoSelector,
                                         TrackSize::eIntrinsicMaxSizing,
                                         TrackSize::eSkipGrowUnlimited2);
      }
    }
    nscoord space = aAvailableSpace;
    while (true) {
      nscoord spacePerTrack = std::max<nscoord>(space / numGrowable, 1);
      for (uint32_t track : aGrowableTracks) {
        TrackSize& sz = aPlan[track];
        if (sz.mState & TrackSize::eSkipGrowUnlimited) {
          continue; // an excluded track
        }
        sz.mBase += spacePerTrack;
        space -= spacePerTrack;
        MOZ_ASSERT(space >= 0);
        if (space == 0) {
          return;
        }
      }
    }
    MOZ_ASSERT_UNREACHABLE("we don't exit the loop above except by return");
  }

  /**
   * Distribute aAvailableSpace to the planned base size for aGrowableTracks
   * up to their limits, then distribute the remaining space beyond the limits.
   */
  void DistributeToTrackBases(nscoord              aAvailableSpace,
                              nsTArray<TrackSize>& aPlan,
                              nsTArray<uint32_t>&  aGrowableTracks,
                              TrackSize::StateBits aSelector)
  {
    SetupGrowthPlan(aPlan, aGrowableTracks);
    nscoord space = GrowTracksToLimit(aAvailableSpace, aPlan, aGrowableTracks);
    if (space > 0) {
      GrowSelectedTracksUnlimited(space, aPlan, aGrowableTracks, aSelector);
    }
    CopyPlanToBase(aPlan, aGrowableTracks);
  }

  /**
   * Distribute aAvailableSpace to the planned limits for aGrowableTracks.
   */
  void DistributeToTrackLimits(nscoord              aAvailableSpace,
                               nsTArray<TrackSize>& aPlan,
                               nsTArray<uint32_t>&  aGrowableTracks)
  {
    nscoord space = GrowTracksToLimit(aAvailableSpace, aPlan, aGrowableTracks);
    if (space > 0) {
      GrowSelectedTracksUnlimited(aAvailableSpace, aPlan, aGrowableTracks,
                                  TrackSize::StateBits(0));
    }
    CopyPlanToLimit(aPlan, aGrowableTracks);
  }

  /**
   * Distribute aAvailableSize to the tracks.  This implements 12.6 at:
   * http://dev.w3.org/csswg/css-grid/#algo-grow-tracks
   */
  void DistributeFreeSpace(nscoord aAvailableSize)
  {
    const uint32_t numTracks = mSizes.Length();
    if (MOZ_UNLIKELY(numTracks == 0 || aAvailableSize <= 0)) {
      return;
    }
    if (aAvailableSize == NS_UNCONSTRAINEDSIZE) {
      for (TrackSize& sz : mSizes) {
        sz.mBase = sz.mLimit;
      }
    } else {
      // Compute free space and count growable tracks.
      nscoord space = aAvailableSize;
      uint32_t numGrowable = numTracks;
      for (const TrackSize& sz : mSizes) {
        space -= sz.mBase;
        MOZ_ASSERT(sz.mBase <= sz.mLimit);
        if (sz.mBase == sz.mLimit) {
          --numGrowable;
        }
      }
      // Distribute the free space evenly to the growable tracks. If not exactly
      // divisable the remainder is added to the leading tracks.
      while (space > 0 && numGrowable) {
        nscoord spacePerTrack =
          std::max<nscoord>(space / numGrowable, 1);
        for (uint32_t i = 0; i < numTracks && space > 0; ++i) {
          TrackSize& sz = mSizes[i];
          if (sz.mBase == sz.mLimit) {
            continue;
          }
          nscoord newBase = sz.mBase + spacePerTrack;
          if (newBase >= sz.mLimit) {
            space -= sz.mLimit - sz.mBase;
            sz.mBase = sz.mLimit;
            --numGrowable;
          } else {
            space -= spacePerTrack;
            sz.mBase = newBase;
          }
        }
      }
    }
  }

  /**
   * Implements "12.7.1. Find the Size of an 'fr'".
   * http://dev.w3.org/csswg/css-grid/#algo-find-fr-size
   * (The returned value is a 'nscoord' divided by a factor - a floating type
   * is used to avoid intermediary rounding errors.)
   */
  float FindFrUnitSize(const LineRange&            aRange,
                       const nsTArray<uint32_t>&   aFlexTracks,
                       const TrackSizingFunctions& aFunctions,
                       nscoord                     aSpaceToFill) const;

  /**
   * Implements the "find the used flex fraction" part of StretchFlexibleTracks.
   * (The returned value is a 'nscoord' divided by a factor - a floating type
   * is used to avoid intermediary rounding errors.)
   */
  float FindUsedFlexFraction(GridReflowState&            aState,
                             nsTArray<GridItemInfo>&     aGridItems,
                             const nsTArray<uint32_t>&   aFlexTracks,
                             const TrackSizingFunctions& aFunctions,
                             nscoord                     aAvailableSize) const;

  /**
   * Implements "12.7. Stretch Flexible Tracks"
   * http://dev.w3.org/csswg/css-grid/#algo-flex-tracks
   */
  void StretchFlexibleTracks(GridReflowState&            aState,
                             nsTArray<GridItemInfo>&     aGridItems,
                             const TrackSizingFunctions& aFunctions,
                             nscoord                     aAvailableSize);

  /**
   * Implements "12.3. Track Sizing Algorithm"
   * http://dev.w3.org/csswg/css-grid/#algo-track-sizing
   */
  void CalculateSizes(GridReflowState&            aState,
                      nsTArray<GridItemInfo>&     aGridItems,
                      const TrackSizingFunctions& aFunctions,
                      nscoord                     aContentSize,
                      LineRange GridArea::*       aRange,
                      IntrinsicISizeType          aConstraint);

  /**
   * Apply 'align/justify-content', whichever is relevant for this axis.
   * https://drafts.csswg.org/css-align-3/#propdef-align-content
   */
  void AlignJustifyContent(const nsHTMLReflowState& aReflowState,
                           const LogicalSize& aContainerSize);


  nscoord SumOfGridGaps() const {
    auto len = mSizes.Length();
    return MOZ_LIKELY(len > 1) ? (len - 1) * mGridGap : 0;
  }

#ifdef DEBUG
  void Dump() const
  {
    for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) {
      printf("  %d: ", i);
      mSizes[i].Dump();
      printf("\n");
    }
  }
#endif

  nsAutoTArray<TrackSize, 32> mSizes;
  nscoord mGridGap;
  LogicalAxis mAxis;
};

struct MOZ_STACK_CLASS nsGridContainerFrame::GridReflowState
{
  GridReflowState(nsGridContainerFrame*    aFrame,
                  const nsHTMLReflowState& aRS)
    : GridReflowState(aFrame, *aRS.rendContext, &aRS, aRS.mStylePosition,
                      aRS.GetWritingMode())
  {}
  GridReflowState(nsGridContainerFrame* aFrame,
                  nsRenderingContext&   aRC)
    : GridReflowState(aFrame, aRC, nullptr, aFrame->StylePosition(),
                      aFrame->GetWritingMode())
  {}

  GridItemCSSOrderIterator mIter;
  const nsStylePosition* const mGridStyle;
  Tracks mCols;
  Tracks mRows;
  TrackSizingFunctions mColFunctions;
  TrackSizingFunctions mRowFunctions;
  /**
   * @note mReflowState may be null when using the 2nd ctor above. In this case
   * we'll construct a dummy parent reflow state if we need it to calculate
   * min/max-content contributions when sizing tracks.
   */
  const nsHTMLReflowState* mReflowState;
  nsRenderingContext& mRenderingContext;
  const WritingMode mWM;

private:
  GridReflowState(nsGridContainerFrame*    aFrame,
                  nsRenderingContext&      aRenderingContext,
                  const nsHTMLReflowState* aReflowState,
                  const nsStylePosition*   aGridStyle,
                  const WritingMode&       aWM)
    : mIter(aFrame, kPrincipalList)
    , mGridStyle(aGridStyle)
    , mCols(eLogicalAxisInline)
    , mRows(eLogicalAxisBlock)
    , mColFunctions({
        mGridStyle->mGridTemplateColumns.mMinTrackSizingFunctions,
        mGridStyle->mGridTemplateColumns.mMaxTrackSizingFunctions,
        mGridStyle->mGridAutoColumnsMin,
        mGridStyle->mGridAutoColumnsMax,
      })
    , mRowFunctions({
        mGridStyle->mGridTemplateRows.mMinTrackSizingFunctions,
        mGridStyle->mGridTemplateRows.mMaxTrackSizingFunctions,
        mGridStyle->mGridAutoRowsMin,
        mGridStyle->mGridAutoRowsMax,
      })
    , mReflowState(aReflowState)
    , mRenderingContext(aRenderingContext)
    , mWM(aWM)
  {}
};

static
bool IsMinContent(const nsStyleCoord& aCoord)
{
  return aCoord.GetUnit() == eStyleUnit_Enumerated &&
         aCoord.GetIntValue() == NS_STYLE_GRID_TRACK_BREADTH_MIN_CONTENT;
}

/**
 * Search for the aNth occurrence of aName in aNameList (forward), starting at
 * the zero-based aFromIndex, and return the 1-based index (line number).
 * Also take into account there is an unconditional match at aImplicitLine
 * unless it's zero.
 * Return zero if aNth occurrences can't be found.  In that case, aNth has
 * been decremented with the number of occurrences that were found (if any).
 */
static uint32_t
FindLine(const nsString& aName, int32_t* aNth,
         uint32_t aFromIndex, uint32_t aImplicitLine,
         const nsTArray<nsTArray<nsString>>& aNameList)
{
  MOZ_ASSERT(aNth && *aNth > 0);
  int32_t nth = *aNth;
  const uint32_t len = aNameList.Length();
  uint32_t line;
  uint32_t i = aFromIndex;
  for (; i < len; i = line) {
    line = i + 1;
    if (line == aImplicitLine || aNameList[i].Contains(aName)) {
      if (--nth == 0) {
        return line;
      }
    }
  }
  if (aImplicitLine > i) {
    // aImplicitLine is after the lines we searched above so it's last.
    // (grid-template-areas has more tracks than grid-template-[rows|columns])
    if (--nth == 0) {
      return aImplicitLine;
    }
  }
  MOZ_ASSERT(nth > 0, "should have returned a valid line above already");
  *aNth = nth;
  return 0;
}

/**
 * @see FindLine, this function does the same but searches in reverse.
 */
static uint32_t
RFindLine(const nsString& aName, int32_t* aNth,
          uint32_t aFromIndex, uint32_t aImplicitLine,
          const nsTArray<nsTArray<nsString>>& aNameList)
{
  MOZ_ASSERT(aNth && *aNth > 0);
  int32_t nth = *aNth;
  const uint32_t len = aNameList.Length();
  // The implicit line may be beyond the length of aNameList so we match this
  // line first if it's within the len..aFromIndex range.
  if (aImplicitLine > len && aImplicitLine < aFromIndex) {
    if (--nth == 0) {
      return aImplicitLine;
    }
  }
  uint32_t i = aFromIndex == 0 ? len : std::min(aFromIndex, len);
  for (; i; --i) {
    if (i == aImplicitLine || aNameList[i - 1].Contains(aName)) {
      if (--nth == 0) {
        return i;
      }
    }
  }
  MOZ_ASSERT(nth > 0, "should have returned a valid line above already");
  *aNth = nth;
  return 0;
}

static uint32_t
FindNamedLine(const nsString& aName, int32_t* aNth,
              uint32_t aFromIndex, uint32_t aImplicitLine,
              const nsTArray<nsTArray<nsString>>& aNameList)
{
  MOZ_ASSERT(aNth && *aNth != 0);
  if (*aNth > 0) {
    return ::FindLine(aName, aNth, aFromIndex, aImplicitLine, aNameList);
  }
  int32_t nth = -*aNth;
  int32_t line = ::RFindLine(aName, &nth, aFromIndex, aImplicitLine, aNameList);
  *aNth = -nth;
  return line;
}

/**
 * A convenience method to lookup a name in 'grid-template-areas'.
 * @param aStyle the StylePosition() for the grid container
 * @return null if not found
 */
static const css::GridNamedArea*
FindNamedArea(const nsSubstring& aName, const nsStylePosition* aStyle)
{
  if (!aStyle->mGridTemplateAreas) {
    return nullptr;
  }
  const nsTArray<css::GridNamedArea>& areas =
    aStyle->mGridTemplateAreas->mNamedAreas;
  size_t len = areas.Length();
  for (size_t i = 0; i < len; ++i) {
    const css::GridNamedArea& area = areas[i];
    if (area.mName == aName) {
      return &area;
    }
  }
  return nullptr;
}

// Return true if aString ends in aSuffix and has at least one character before
// the suffix. Assign aIndex to where the suffix starts.
static bool
IsNameWithSuffix(const nsString& aString, const nsString& aSuffix,
                 uint32_t* aIndex)
{
  if (StringEndsWith(aString, aSuffix)) {
    *aIndex = aString.Length() - aSuffix.Length();
    return *aIndex != 0;
  }
  return false;
}

static bool
IsNameWithEndSuffix(const nsString& aString, uint32_t* aIndex)
{
  return IsNameWithSuffix(aString, NS_LITERAL_STRING("-end"), aIndex);
}

static bool
IsNameWithStartSuffix(const nsString& aString, uint32_t* aIndex)
{
  return IsNameWithSuffix(aString, NS_LITERAL_STRING("-start"), aIndex);
}

enum class GridLineSide {
  eBeforeGridGap,
  eAfterGridGap,
};

static nscoord
GridLineEdge(uint32_t aLine, const nsTArray<TrackSize>& aTrackSizes,
             GridLineSide aSide)
{
  if (MOZ_UNLIKELY(aTrackSizes.IsEmpty())) {
    // https://drafts.csswg.org/css-grid/#grid-definition
    // "... the explicit grid still contains one grid line in each axis."
    MOZ_ASSERT(aLine == 0, "We should only resolve line 1 in an empty grid");
    return nscoord(0);
  }
  MOZ_ASSERT(aLine <= aTrackSizes.Length(), "aTrackSizes is too small");
  if (aSide == GridLineSide::eBeforeGridGap) {
    if (aLine == 0) {
      return aTrackSizes[0].mPosition;
    }
    const TrackSize& sz = aTrackSizes[aLine - 1];
    return sz.mPosition + sz.mBase;
  }
  if (aLine == aTrackSizes.Length()) {
    const TrackSize& sz = aTrackSizes[aLine - 1];
    return sz.mPosition + sz.mBase;
  }
  return aTrackSizes[aLine].mPosition;
}

/**
 * (XXX share this utility function with nsFlexContainerFrame at some point)
 *
 * Helper for BuildDisplayList, to implement this special-case for grid
 * items from the spec:
 *   The painting order of grid items is exactly the same as inline blocks,
 *   except that [...] 'z-index' values other than 'auto' create a stacking
 *   context even if 'position' is 'static'.
 * http://dev.w3.org/csswg/css-grid/#z-order
 */
static uint32_t
GetDisplayFlagsForGridItem(nsIFrame* aFrame)
{
  const nsStylePosition* pos = aFrame->StylePosition();
  if (pos->mZIndex.GetUnit() == eStyleUnit_Integer) {
    return nsIFrame::DISPLAY_CHILD_FORCE_STACKING_CONTEXT;
  }
  return nsIFrame::DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT;
}

static nscoord
SpaceToFill(WritingMode aWM, const LogicalSize& aSize, nscoord aMargin,
            LogicalAxis aAxis, nscoord aCBSize)
{
  nscoord size = aAxis == eLogicalAxisBlock ? aSize.BSize(aWM)
                                            : aSize.ISize(aWM);
  return aCBSize - (size + aMargin);
}

// Align (or stretch) an item's margin box in its aAxis inside aCBSize.
static bool
AlignJustifySelf(uint8_t aAlignment, bool aOverflowSafe, LogicalAxis aAxis,
                 bool aSameSide, nscoord aCBSize, const nsHTMLReflowState& aRS,
                 const LogicalSize& aChildSize, LogicalSize* aContentSize,
                 LogicalPoint* aPos)
{
  MOZ_ASSERT(aAlignment != NS_STYLE_ALIGN_AUTO, "unexpected 'auto' "
             "computed value for normal flow grid item");
  MOZ_ASSERT(aAlignment != NS_STYLE_ALIGN_LEFT &&
             aAlignment != NS_STYLE_ALIGN_RIGHT,
             "caller should map that to the corresponding START/END");

  // Map some alignment values to 'start' / 'end'.
  switch (aAlignment) {
    case NS_STYLE_ALIGN_SELF_START: // align/justify-self: self-start
      aAlignment = MOZ_LIKELY(aSameSide) ? NS_STYLE_ALIGN_START
                                         : NS_STYLE_ALIGN_END;
      break;
    case NS_STYLE_ALIGN_SELF_END: // align/justify-self: self-end
      aAlignment = MOZ_LIKELY(aSameSide) ? NS_STYLE_ALIGN_END
                                         : NS_STYLE_ALIGN_START;
      break;
    case NS_STYLE_ALIGN_FLEX_START: // same as 'start' for Grid
      aAlignment = NS_STYLE_ALIGN_START;
      break;
    case NS_STYLE_ALIGN_FLEX_END: // same as 'end' for Grid
      aAlignment = NS_STYLE_ALIGN_END;
      break;
  }

  // XXX try to condense this code a bit by adding the necessary convenience
  // methods? (bug 1209710)

  // Get the item's margin corresponding to the container's start/end side.
  const LogicalMargin margin = aRS.ComputedLogicalMargin();
  WritingMode wm = aRS.GetWritingMode();
  nscoord marginStart, marginEnd;
  if (aAxis == eLogicalAxisBlock) {
    if (MOZ_LIKELY(aSameSide)) {
      marginStart = margin.BStart(wm);
      marginEnd = margin.BEnd(wm);
    } else {
      marginStart = margin.BEnd(wm);
      marginEnd = margin.BStart(wm);
    }
  } else {
    if (MOZ_LIKELY(aSameSide)) {
      marginStart = margin.IStart(wm);
      marginEnd = margin.IEnd(wm);
    } else {
      marginStart = margin.IEnd(wm);
      marginEnd = margin.IStart(wm);
    }
  }

  const auto& styleMargin = aRS.mStyleMargin->mMargin;
  bool hasAutoMarginStart;
  bool hasAutoMarginEnd;
  if (aAxis == eLogicalAxisBlock) {
    hasAutoMarginStart = styleMargin.GetBStartUnit(wm) == eStyleUnit_Auto;
    hasAutoMarginEnd = styleMargin.GetBEndUnit(wm) == eStyleUnit_Auto;
  } else {
    hasAutoMarginStart = styleMargin.GetIStartUnit(wm) == eStyleUnit_Auto;
    hasAutoMarginEnd = styleMargin.GetIEndUnit(wm) == eStyleUnit_Auto;
  }

  // https://drafts.csswg.org/css-align-3/#overflow-values
  // This implements <overflow-position> = 'safe'.
  // And auto-margins: https://drafts.csswg.org/css-grid/#auto-margins
  if ((MOZ_UNLIKELY(aOverflowSafe) && aAlignment != NS_STYLE_ALIGN_START) ||
      hasAutoMarginStart || hasAutoMarginEnd) {
    nscoord space = SpaceToFill(wm, aChildSize, marginStart + marginEnd,
                                aAxis, aCBSize);
    // XXX we might want to include == 0 here as an optimization -
    // I need to see what the baseline/last-baseline code looks like first.
    if (space < 0) {
      // "Overflowing elements ignore their auto margins and overflow
      // in the end directions"
      aAlignment = NS_STYLE_ALIGN_START;
    } else if (hasAutoMarginEnd) {
      aAlignment = hasAutoMarginStart ? NS_STYLE_ALIGN_CENTER
                                      : (aSameSide ? NS_STYLE_ALIGN_START
                                                   : NS_STYLE_ALIGN_END);
    } else if (hasAutoMarginStart) {
      aAlignment = aSameSide ? NS_STYLE_ALIGN_END : NS_STYLE_ALIGN_START;
    }
  }

  // Set the position and size (aPos/aContentSize) for the requested alignment.
  bool didResize = false;
  nscoord offset = 0; // NOTE: this is the resulting frame offset (border box).
  switch (aAlignment) {
    case NS_STYLE_ALIGN_BASELINE:
    case NS_STYLE_ALIGN_LAST_BASELINE:
      NS_WARNING("NYI: baseline/last-baseline for grid (bug 1151204)"); // XXX
    case NS_STYLE_ALIGN_START:
      offset = marginStart;
      break;
    case NS_STYLE_ALIGN_END: {
      nscoord size = aAxis == eLogicalAxisBlock ? aChildSize.BSize(wm)
                                                : aChildSize.ISize(wm);
      offset = aCBSize - (size + marginEnd);
      break;
    }
    case NS_STYLE_ALIGN_CENTER: {
      nscoord size = aAxis == eLogicalAxisBlock ? aChildSize.BSize(wm)
                                                : aChildSize.ISize(wm);
      offset = (aCBSize - size + marginStart - marginEnd) / 2;
      break;
    }
    case NS_STYLE_ALIGN_STRETCH: {
      MOZ_ASSERT(!hasAutoMarginStart && !hasAutoMarginEnd);
      offset = marginStart;
      if (aAxis == eLogicalAxisBlock
            ? (aRS.mStylePosition->BSize(wm).GetUnit() == eStyleUnit_Auto)
            : (aRS.mStylePosition->ISize(wm).GetUnit() == eStyleUnit_Auto)) {
        nscoord size = aAxis == eLogicalAxisBlock ? aChildSize.BSize(wm)
                                                  : aChildSize.ISize(wm);
        nscoord gap = aCBSize - (size + marginStart + marginEnd);
        if (gap > 0) {
          // Note: The ComputedMax* values are always content-box max values,
          // even for box-sizing:border-box.
          LogicalMargin bp = aRS.ComputedLogicalBorderPadding();
          // XXX ApplySkipSides is probably not very useful here since we
          // might not have created any next-in-flow yet.  Use the reflow status
          // instead?  Do all fragments stretch? (bug 1144096).
          bp.ApplySkipSides(aRS.frame->GetLogicalSkipSides());
          nscoord bpInAxis = aAxis == eLogicalAxisBlock ? bp.BStartEnd(wm)
                                                        : bp.IStartEnd(wm);
          nscoord contentSize = size - bpInAxis;
          const nscoord unstretchedContentSize = contentSize;
          contentSize += gap;
          nscoord max = aAxis == eLogicalAxisBlock ? aRS.ComputedMaxBSize()
                                                   : aRS.ComputedMaxISize();
          if (MOZ_UNLIKELY(contentSize > max)) {
            contentSize = max;
            gap = contentSize - unstretchedContentSize;
          }
          // |gap| is now how much the content size is actually allowed to grow.
          didResize = gap > 0;
          // (nscoord overflow can make |contentSize| negative, bug 1225118)
          if (didResize && MOZ_LIKELY(contentSize >= 0)) {
            (aAxis == eLogicalAxisBlock ? aContentSize->BSize(wm)
                                        : aContentSize->ISize(wm)) = contentSize;
            if (MOZ_UNLIKELY(!aSameSide)) {
              offset += gap;
            }
          }
        }
      }
      break;
    }
    default:
      MOZ_ASSERT_UNREACHABLE("unknown align-/justify-self value");
  }
  if (offset != 0) {
    nscoord& pos = aAxis == eLogicalAxisBlock ? aPos->B(wm) : aPos->I(wm);
    pos += MOZ_LIKELY(aSameSide) ? offset : -offset;
  }
  return didResize;
}

static bool
SameSide(WritingMode aContainerWM, LogicalSide aContainerSide,
         WritingMode aChildWM, LogicalSide aChildSide)
{
  MOZ_ASSERT(aContainerWM.PhysicalAxis(GetAxis(aContainerSide)) ==
                 aChildWM.PhysicalAxis(GetAxis(aChildSide)));
  return aContainerWM.PhysicalSide(aContainerSide) ==
             aChildWM.PhysicalSide(aChildSide);
}

static Maybe<LogicalAxis>
AlignSelf(uint8_t aAlignSelf, const LogicalRect& aCB, const WritingMode aCBWM,
          const nsHTMLReflowState& aRS, const LogicalSize& aSize,
          LogicalSize* aContentSize, LogicalPoint* aPos)
{
  Maybe<LogicalAxis> resizedAxis;
  auto alignSelf = aAlignSelf;
  bool overflowSafe = alignSelf & NS_STYLE_ALIGN_SAFE;
  alignSelf &= ~NS_STYLE_ALIGN_FLAG_BITS;
  // Grid's 'align-self' axis is never parallel to the container's inline axis.
  if (alignSelf == NS_STYLE_ALIGN_LEFT || alignSelf == NS_STYLE_ALIGN_RIGHT) {
    alignSelf = NS_STYLE_ALIGN_START;
  }
  if (MOZ_UNLIKELY(alignSelf == NS_STYLE_ALIGN_AUTO)) {
    // Happens in rare edge cases when 'position' was ignored by the frame
    // constructor (and the style system computed 'auto' based on 'position').
    alignSelf = NS_STYLE_ALIGN_STRETCH;
  }
  WritingMode childWM = aRS.GetWritingMode();
  bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM);
  // |sameSide| is true if the container's start side in this axis is the same
  // as the child's start side, in the child's parallel axis.
  bool sameSide = SameSide(aCBWM, eLogicalSideBStart,
                           childWM, isOrthogonal ? eLogicalSideIStart
                                                 : eLogicalSideBStart);
  LogicalAxis axis = isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock;
  if (AlignJustifySelf(alignSelf, overflowSafe, axis, sameSide,
                       aCB.BSize(aCBWM), aRS, aSize, aContentSize, aPos)) {
    resizedAxis.emplace(axis);
  }
  return resizedAxis;
}

static Maybe<LogicalAxis>
JustifySelf(uint8_t aJustifySelf, const LogicalRect& aCB, const WritingMode aCBWM,
            const nsHTMLReflowState& aRS, const LogicalSize& aSize,
            LogicalSize* aContentSize, LogicalPoint* aPos)
{
  Maybe<LogicalAxis> resizedAxis;
  auto justifySelf = aJustifySelf;
  bool overflowSafe = justifySelf & NS_STYLE_JUSTIFY_SAFE;
  justifySelf &= ~NS_STYLE_JUSTIFY_FLAG_BITS;
  if (MOZ_UNLIKELY(justifySelf == NS_STYLE_ALIGN_AUTO)) {
    // Happens in rare edge cases when 'position' was ignored by the frame
    // constructor (and the style system computed 'auto' based on 'position').
    justifySelf = NS_STYLE_ALIGN_STRETCH;
  }
  WritingMode childWM = aRS.GetWritingMode();
  bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM);
  // |sameSide| is true if the container's start side in this axis is the same
  // as the child's start side, in the child's parallel axis.
  bool sameSide = SameSide(aCBWM, eLogicalSideIStart,
                           childWM, isOrthogonal ? eLogicalSideBStart
                                                 : eLogicalSideIStart);
  // Grid's 'justify-self' axis is always parallel to the container's inline
  // axis, so justify-self:left|right always applies.
  switch (justifySelf) {
    case NS_STYLE_JUSTIFY_LEFT:
      justifySelf = aCBWM.IsBidiLTR() ? NS_STYLE_JUSTIFY_START
                                      : NS_STYLE_JUSTIFY_END;
    break;
    case NS_STYLE_JUSTIFY_RIGHT:
      justifySelf = aCBWM.IsBidiLTR() ? NS_STYLE_JUSTIFY_END
                                      : NS_STYLE_JUSTIFY_START;
    break;
  }

  LogicalAxis axis = isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline;
  if (AlignJustifySelf(justifySelf, overflowSafe, axis, sameSide,
                       aCB.ISize(aCBWM), aRS, aSize, aContentSize, aPos)) {
    resizedAxis.emplace(axis);
  }
  return resizedAxis;
}

static uint16_t
GetAlignJustifyValue(uint16_t aAlignment, const WritingMode aWM,
                     const bool aIsAlign, bool* aOverflowSafe)
{
  *aOverflowSafe = aAlignment & NS_STYLE_ALIGN_SAFE;
  aAlignment &= (NS_STYLE_ALIGN_ALL_BITS & ~NS_STYLE_ALIGN_FLAG_BITS);

  // Map some alignment values to 'start' / 'end'.
  switch (aAlignment) {
    case NS_STYLE_ALIGN_LEFT:
    case NS_STYLE_ALIGN_RIGHT: {
      if (aIsAlign) {
        // Grid's 'align-content' axis is never parallel to the inline axis.
        return NS_STYLE_ALIGN_START;
      }
      bool isStart = aWM.IsBidiLTR() == (aAlignment == NS_STYLE_ALIGN_LEFT);
      return isStart ? NS_STYLE_ALIGN_START : NS_STYLE_ALIGN_END;
    }
    case NS_STYLE_ALIGN_FLEX_START: // same as 'start' for Grid
      return NS_STYLE_ALIGN_START;
    case NS_STYLE_ALIGN_FLEX_END: // same as 'end' for Grid
      return NS_STYLE_ALIGN_END;
  }
  return aAlignment;
}

static uint16_t
GetAlignJustifyFallbackIfAny(uint16_t aAlignment, const WritingMode aWM,
                             const bool aIsAlign, bool* aOverflowSafe)
{
  uint16_t fallback = aAlignment >> NS_STYLE_ALIGN_ALL_SHIFT;
  if (fallback) {
    return GetAlignJustifyValue(fallback, aWM, aIsAlign, aOverflowSafe);
  }
  // https://drafts.csswg.org/css-align-3/#fallback-alignment
  switch (aAlignment) {
    case NS_STYLE_ALIGN_STRETCH:
    case NS_STYLE_ALIGN_SPACE_BETWEEN:
      return NS_STYLE_ALIGN_START;
    case NS_STYLE_ALIGN_SPACE_AROUND:
    case NS_STYLE_ALIGN_SPACE_EVENLY:
      return NS_STYLE_ALIGN_CENTER;
  }
  return 0;
}

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

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

NS_QUERYFRAME_HEAD(nsGridContainerFrame)
  NS_QUERYFRAME_ENTRY(nsGridContainerFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)

NS_IMPL_FRAMEARENA_HELPERS(nsGridContainerFrame)

nsContainerFrame*
NS_NewGridContainerFrame(nsIPresShell* aPresShell,
                         nsStyleContext* aContext)
{
  return new (aPresShell) nsGridContainerFrame(aContext);
}


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

// nsGridContainerFrame Method Implementations
// ===========================================

/*static*/ const nsRect&
nsGridContainerFrame::GridItemCB(nsIFrame* aChild)
{
  MOZ_ASSERT((aChild->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
             aChild->IsAbsolutelyPositioned());
  nsRect* cb = static_cast<nsRect*>(aChild->Properties().Get(
                 GridItemContainingBlockRect()));
  MOZ_ASSERT(cb, "this method must only be called on grid items, and the grid "
                 "container should've reflowed this item by now and set up cb");
  return *cb;
}

void
nsGridContainerFrame::AddImplicitNamedAreas(
  const nsTArray<nsTArray<nsString>>& aLineNameLists)
{
  // http://dev.w3.org/csswg/css-grid/#implicit-named-areas
  // Note: recording these names for fast lookup later is just an optimization.
  const uint32_t len =
    std::min(aLineNameLists.Length(), size_t(nsStyleGridLine::kMaxLine));
  nsTHashtable<nsStringHashKey> currentStarts;
  ImplicitNamedAreas* areas = GetImplicitNamedAreas();
  for (uint32_t i = 0; i < len; ++i) {
    for (const nsString& name : aLineNameLists[i]) {
      uint32_t index;
      if (::IsNameWithStartSuffix(name, &index) ||
          ::IsNameWithEndSuffix(name, &index)) {
        nsDependentSubstring area(name, 0, index);
        if (!areas) {
          areas = new ImplicitNamedAreas;
          Properties().Set(ImplicitNamedAreasProperty(), areas);
        }
        areas->PutEntry(area);
      }
    }
  }
}

void
nsGridContainerFrame::InitImplicitNamedAreas(const nsStylePosition* aStyle)
{
  ImplicitNamedAreas* areas = GetImplicitNamedAreas();
  if (areas) {
    // Clear it, but reuse the hashtable itself for now.  We'll remove it
    // below if it isn't needed anymore.
    areas->Clear();
  }
  AddImplicitNamedAreas(aStyle->mGridTemplateColumns.mLineNameLists);
  AddImplicitNamedAreas(aStyle->mGridTemplateRows.mLineNameLists);
  if (areas && areas->Count() == 0) {
    Properties().Delete(ImplicitNamedAreasProperty());
  }
}

int32_t
nsGridContainerFrame::ResolveLine(
  const nsStyleGridLine& aLine,
  int32_t aNth,
  uint32_t aFromIndex,
  const nsTArray<nsTArray<nsString>>& aLineNameList,
  uint32_t GridNamedArea::* aAreaStart,
  uint32_t GridNamedArea::* aAreaEnd,
  uint32_t aExplicitGridEnd,
  LineRangeSide aSide,
  const nsStylePosition* aStyle)
{
  MOZ_ASSERT(!aLine.IsAuto());
  int32_t line = 0;
  if (aLine.mLineName.IsEmpty()) {
    MOZ_ASSERT(aNth != 0, "css-grid 9.2: <integer> must not be zero.");
    line = int32_t(aFromIndex) + aNth;
  } else {
    if (aNth == 0) {
      // <integer> was omitted; treat it as 1.
      aNth = 1;
    }
    bool isNameOnly = !aLine.mHasSpan && aLine.mInteger == 0;
    if (isNameOnly) {
      const GridNamedArea* area = ::FindNamedArea(aLine.mLineName, aStyle);
      if (area || HasImplicitNamedArea(aLine.mLineName)) {
        // The given name is a named area - look for explicit lines named
        // <name>-start/-end depending on which side we're resolving.
        // http://dev.w3.org/csswg/css-grid/#grid-placement-slot
        uint32_t implicitLine = 0;
        nsAutoString lineName(aLine.mLineName);
        if (aSide == eLineRangeSideStart) {
          lineName.AppendLiteral("-start");
          implicitLine = area ? area->*aAreaStart : 0;
        } else {
          lineName.AppendLiteral("-end");
          implicitLine = area ? area->*aAreaEnd : 0;
        }
        line = ::FindNamedLine(lineName, &aNth, aFromIndex, implicitLine,
                               aLineNameList);
      }
    }

    if (line == 0) {
      // If mLineName ends in -start/-end, try the prefix as a named area.
      uint32_t implicitLine = 0;
      uint32_t index;
      auto GridNamedArea::* areaEdge = aAreaStart;
      bool found = ::IsNameWithStartSuffix(aLine.mLineName, &index);
      if (!found) {
        found = ::IsNameWithEndSuffix(aLine.mLineName, &index);
        areaEdge = aAreaEnd;
      }
      if (found) {
        const GridNamedArea* area =
          ::FindNamedArea(nsDependentSubstring(aLine.mLineName, 0, index),
                          aStyle);
        if (area) {
          implicitLine = area->*areaEdge;
        }
      }
      line = ::FindNamedLine(aLine.mLineName, &aNth, aFromIndex, implicitLine,
                             aLineNameList);
    }

    if (line == 0) {
      MOZ_ASSERT(aNth != 0, "we found all N named lines but 'line' is zero!");
      int32_t edgeLine;
      if (aLine.mHasSpan) {
        // http://dev.w3.org/csswg/css-grid/#grid-placement-span-int
        // 'span <custom-ident> N'
        edgeLine = aSide == eLineRangeSideStart ? 1 : aExplicitGridEnd;
      } else {
        // http://dev.w3.org/csswg/css-grid/#grid-placement-int
        // '<custom-ident> N'
        edgeLine = aNth < 0 ? 1 : aExplicitGridEnd;
      }
      // "If not enough lines with that name exist, all lines in the implicit
      // grid are assumed to have that name..."
      line = edgeLine + aNth;
    }
  }
  return clamped(line, nsStyleGridLine::kMinLine, nsStyleGridLine::kMaxLine);
}

nsGridContainerFrame::LinePair
nsGridContainerFrame::ResolveLineRangeHelper(
  const nsStyleGridLine& aStart,
  const nsStyleGridLine& aEnd,
  const nsTArray<nsTArray<nsString>>& aLineNameList,
  uint32_t GridNamedArea::* aAreaStart,
  uint32_t GridNamedArea::* aAreaEnd,
  uint32_t aExplicitGridEnd,
  const nsStylePosition* aStyle)
{
  MOZ_ASSERT(int32_t(nsGridContainerFrame::kAutoLine) > nsStyleGridLine::kMaxLine);

  if (aStart.mHasSpan) {
    if (aEnd.mHasSpan || aEnd.IsAuto()) {
      // http://dev.w3.org/csswg/css-grid/#grid-placement-errors
      if (aStart.mLineName.IsEmpty()) {
        // span <integer> / span *
        // span <integer> / auto
        return LinePair(kAutoLine, aStart.mInteger);
      }
      // span <custom-ident> / span *
      // span <custom-ident> / auto
      return LinePair(kAutoLine, 1); // XXX subgrid explicit size instead of 1?
    }

    uint32_t from = aEnd.mInteger < 0 ? aExplicitGridEnd : 0;
    auto end = ResolveLine(aEnd, aEnd.mInteger, from, aLineNameList, aAreaStart,
                           aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd,
                           aStyle);
    int32_t span = aStart.mInteger == 0 ? 1 : aStart.mInteger;
    if (end <= 1) {
      // The end is at or before the first explicit line, thus all lines before
      // it match <custom-ident> since they're implicit.
      int32_t start = std::max(end - span, nsStyleGridLine::kMinLine);
      return LinePair(start, end);
    }
    auto start = ResolveLine(aStart, -span, end, aLineNameList, aAreaStart,
                             aAreaEnd, aExplicitGridEnd, eLineRangeSideStart,
                             aStyle);
    return LinePair(start, end);
  }

  int32_t start = kAutoLine;
  if (aStart.IsAuto()) {
    if (aEnd.IsAuto()) {
      // auto / auto
      return LinePair(start, 1); // XXX subgrid explicit size instead of 1?
    }
    if (aEnd.mHasSpan) {
      if (aEnd.mLineName.IsEmpty()) {
        // auto / span <integer>
        MOZ_ASSERT(aEnd.mInteger != 0);
        return LinePair(start, aEnd.mInteger);
      }
      // http://dev.w3.org/csswg/css-grid/#grid-placement-errors
      // auto / span <custom-ident>
      return LinePair(start, 1); // XXX subgrid explicit size instead of 1?
    }
  } else {
    uint32_t from = aStart.mInteger < 0 ? aExplicitGridEnd : 0;
    start = ResolveLine(aStart, aStart.mInteger, from, aLineNameList,
                        aAreaStart, aAreaEnd, aExplicitGridEnd,
                        eLineRangeSideStart, aStyle);
    if (aEnd.IsAuto()) {
      // A "definite line / auto" should resolve the auto to 'span 1'.
      // The error handling in ResolveLineRange will make that happen and also
      // clamp the end line correctly if we return "start / start".
      return LinePair(start, start);
    }
  }

  uint32_t from;
  int32_t nth = aEnd.mInteger == 0 ? 1 : aEnd.mInteger;
  if (aEnd.mHasSpan) {
    if (MOZ_UNLIKELY(start < 0)) {
      if (aEnd.mLineName.IsEmpty()) {
        return LinePair(start, start + nth);
      }
      from = 0;
    } else {
      if (start >= int32_t(aExplicitGridEnd)) {
        // The start is at or after the last explicit line, thus all lines
        // after it match <custom-ident> since they're implicit.
        return LinePair(start, std::min(start + nth, nsStyleGridLine::kMaxLine));
      }
      from = start;
    }
  } else {
    from = aEnd.mInteger < 0 ? aExplicitGridEnd : 0;
  }
  auto end = ResolveLine(aEnd, nth, from, aLineNameList, aAreaStart,
                         aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd, aStyle);
  if (start == int32_t(kAutoLine)) {
    // auto / definite line
    start = std::max(nsStyleGridLine::kMinLine, end - 1);
  }
  return LinePair(start, end);
}

nsGridContainerFrame::LineRange
nsGridContainerFrame::ResolveLineRange(
  const nsStyleGridLine& aStart,
  const nsStyleGridLine& aEnd,
  const nsTArray<nsTArray<nsString>>& aLineNameList,
  uint32_t GridNamedArea::* aAreaStart,
  uint32_t GridNamedArea::* aAreaEnd,
  uint32_t aExplicitGridEnd,
  const nsStylePosition* aStyle)
{
  LinePair r = ResolveLineRangeHelper(aStart, aEnd, aLineNameList, aAreaStart,
                                      aAreaEnd, aExplicitGridEnd, aStyle);
  MOZ_ASSERT(r.second != int32_t(kAutoLine));

  if (r.first == int32_t(kAutoLine)) {
    // r.second is a span, clamp it to kMaxLine - 1 so that the returned
    // range has a HypotheticalEnd <= kMaxLine.
    // http://dev.w3.org/csswg/css-grid/#overlarge-grids
    r.second = std::min(r.second, nsStyleGridLine::kMaxLine - 1);
  } else {
    // http://dev.w3.org/csswg/css-grid/#grid-placement-errors
    if (r.first > r.second) {
      Swap(r.first, r.second);
    } else if (r.first == r.second) {
      if (MOZ_UNLIKELY(r.first == nsStyleGridLine::kMaxLine)) {
        r.first = nsStyleGridLine::kMaxLine - 1;
      }
      r.second = r.first + 1; // XXX subgrid explicit size instead of 1?
    }
  }
  return LineRange(r.first, r.second);
}

nsGridContainerFrame::GridArea
nsGridContainerFrame::PlaceDefinite(nsIFrame* aChild,
                                    const nsStylePosition* aStyle)
{
  const nsStylePosition* itemStyle = aChild->StylePosition();
  return GridArea(
    ResolveLineRange(itemStyle->mGridColumnStart, itemStyle->mGridColumnEnd,
                     aStyle->mGridTemplateColumns.mLineNameLists,
                     &GridNamedArea::mColumnStart, &GridNamedArea::mColumnEnd,
                     mExplicitGridColEnd, aStyle),
    ResolveLineRange(itemStyle->mGridRowStart, itemStyle->mGridRowEnd,
                     aStyle->mGridTemplateRows.mLineNameLists,
                     &GridNamedArea::mRowStart, &GridNamedArea::mRowEnd,
                     mExplicitGridRowEnd, aStyle));
}

nsGridContainerFrame::LineRange
nsGridContainerFrame::ResolveAbsPosLineRange(
  const nsStyleGridLine& aStart,
  const nsStyleGridLine& aEnd,
  const nsTArray<nsTArray<nsString>>& aLineNameList,
  uint32_t GridNamedArea::* aAreaStart,
  uint32_t GridNamedArea::* aAreaEnd,
  uint32_t aExplicitGridEnd,
  int32_t aGridStart,
  int32_t aGridEnd,
  const nsStylePosition* aStyle)
{
  if (aStart.IsAuto()) {
    if (aEnd.IsAuto()) {
      return LineRange(kAutoLine, kAutoLine);
    }
    int32_t end = ResolveLine(aEnd, aEnd.mInteger, 0, aLineNameList, aAreaStart,
                              aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd,
                              aStyle);
    if (aEnd.mHasSpan) {
      ++end;
    }
    // A line outside the existing grid is treated as 'auto' for abs.pos (10.1).
    end = AutoIfOutside(end, aGridStart, aGridEnd);
    return LineRange(kAutoLine, end);
  }

  if (aEnd.IsAuto()) {
    int32_t start =
      ResolveLine(aStart, aStart.mInteger, 0, aLineNameList, aAreaStart,
                  aAreaEnd, aExplicitGridEnd, eLineRangeSideStart, aStyle);
    if (aStart.mHasSpan) {
      start = std::max(aGridEnd - start, aGridStart);
    }
    start = AutoIfOutside(start, aGridStart, aGridEnd);
    return LineRange(start, kAutoLine);
  }

  LineRange r = ResolveLineRange(aStart, aEnd, aLineNameList, aAreaStart,
                                 aAreaEnd, aExplicitGridEnd, aStyle);
  if (r.IsAuto()) {
    MOZ_ASSERT(aStart.mHasSpan && aEnd.mHasSpan, "span / span is the only case "
               "leading to IsAuto here -- we dealt with the other cases above");
    // The second span was ignored per 9.2.1.  For abs.pos., 10.1 says that this
    // case should result in "auto / auto" unlike normal flow grid items.
    return LineRange(kAutoLine, kAutoLine);
  }

  return LineRange(AutoIfOutside(r.mUntranslatedStart, aGridStart, aGridEnd),
                   AutoIfOutside(r.mUntranslatedEnd, aGridStart, aGridEnd));
}

uint32_t
nsGridContainerFrame::FindAutoCol(uint32_t aStartCol, uint32_t aLockedRow,
                                  const GridArea* aArea) const
{
  const uint32_t extent = aArea->mCols.Extent();
  const uint32_t iStart = aLockedRow;
  const uint32_t iEnd = iStart + aArea->mRows.Extent();
  uint32_t candidate = aStartCol;
  for (uint32_t i = iStart; i < iEnd; ) {
    if (i >= mCellMap.mCells.Length()) {
      break;
    }
    const nsTArray<CellMap::Cell>& cellsInRow = mCellMap.mCells[i];
    const uint32_t len = cellsInRow.Length();
    const uint32_t lastCandidate = candidate;
    // Find the first gap in the current row that's at least 'extent' wide.
    // ('gap' tracks how wide the current column gap is.)
    for (uint32_t j = candidate, gap = 0; j < len && gap < extent; ++j) {
      if (!cellsInRow[j].mIsOccupied) {
        ++gap;
        continue;
      }
      candidate = j + 1;
      gap = 0;
    }
    if (lastCandidate < candidate && i != iStart) {
      // Couldn't fit 'extent' tracks at 'lastCandidate' here so we must
      // restart from the beginning with the new 'candidate'.
      i = iStart;
    } else {
      ++i;
    }
  }
  return candidate;
}

nsGridContainerFrame::GridArea
nsGridContainerFrame::PlaceAbsPos(nsIFrame* aChild,
                                  const nsStylePosition* aStyle)
{
  const nsStylePosition* itemStyle = aChild->StylePosition();
  int32_t gridColStart = 1 - mExplicitGridOffsetCol;
  int32_t gridRowStart = 1 - mExplicitGridOffsetRow;
  return GridArea(
    ResolveAbsPosLineRange(itemStyle->mGridColumnStart,
                           itemStyle->mGridColumnEnd,
                           aStyle->mGridTemplateColumns.mLineNameLists,
                           &GridNamedArea::mColumnStart,
                           &GridNamedArea::mColumnEnd,
                           mExplicitGridColEnd, gridColStart, mGridColEnd,
                           aStyle),
    ResolveAbsPosLineRange(itemStyle->mGridRowStart,
                           itemStyle->mGridRowEnd,
                           aStyle->mGridTemplateRows.mLineNameLists,
                           &GridNamedArea::mRowStart,
                           &GridNamedArea::mRowEnd,
                           mExplicitGridRowEnd, gridRowStart, mGridRowEnd,
                           aStyle));
}

void
nsGridContainerFrame::PlaceAutoCol(uint32_t aStartCol, GridArea* aArea) const
{
  MOZ_ASSERT(aArea->mRows.IsDefinite() && aArea->mCols.IsAuto());
  uint32_t col = FindAutoCol(aStartCol, aArea->mRows.mStart, aArea);
  aArea->mCols.ResolveAutoPosition(col, mExplicitGridOffsetCol);
  MOZ_ASSERT(aArea->IsDefinite());
}

uint32_t
nsGridContainerFrame::FindAutoRow(uint32_t aLockedCol, uint32_t aStartRow,
                                  const GridArea* aArea) const
{
  const uint32_t extent = aArea->mRows.Extent();
  const uint32_t jStart = aLockedCol;
  const uint32_t jEnd = jStart + aArea->mCols.Extent();
  const uint32_t iEnd = mCellMap.mCells.Length();
  uint32_t candidate = aStartRow;
  // Find the first gap in the rows that's at least 'extent' tall.
  // ('gap' tracks how tall the current row gap is.)
  for (uint32_t i = candidate, gap = 0; i < iEnd && gap < extent; ++i) {
    ++gap; // tentative, but we may reset it below if a column is occupied
    const nsTArray<CellMap::Cell>& cellsInRow = mCellMap.mCells[i];
    const uint32_t clampedJEnd = std::min<uint32_t>(jEnd, cellsInRow.Length());
    // Check if the current row is unoccupied from jStart to jEnd.
    for (uint32_t j = jStart; j < clampedJEnd; ++j) {
      if (cellsInRow[j].mIsOccupied) {
        // Couldn't fit 'extent' rows at 'candidate' here; we hit something
        // at row 'i'.  So, try the row after 'i' as our next candidate.
        candidate = i + 1;
        gap = 0;
        break;
      }
    }
  }
  return candidate;
}

void
nsGridContainerFrame::PlaceAutoRow(uint32_t aStartRow, GridArea* aArea) const
{
  MOZ_ASSERT(aArea->mCols.IsDefinite() && aArea->mRows.IsAuto());
  uint32_t row = FindAutoRow(aArea->mCols.mStart, aStartRow, aArea);
  aArea->mRows.ResolveAutoPosition(row, mExplicitGridOffsetRow);
  MOZ_ASSERT(aArea->IsDefinite());
}

void
nsGridContainerFrame::PlaceAutoAutoInRowOrder(uint32_t aStartCol,
                                              uint32_t aStartRow,
                                              GridArea* aArea) const
{
  MOZ_ASSERT(aArea->mCols.IsAuto() && aArea->mRows.IsAuto());
  const uint32_t colExtent = aArea->mCols.Extent();
  const uint32_t gridRowEnd = mGridRowEnd;
  const uint32_t gridColEnd = mGridColEnd;
  uint32_t col = aStartCol;
  uint32_t row = aStartRow;
  for (; row < gridRowEnd; ++row) {
    col = FindAutoCol(col, row, aArea);
    if (col + colExtent <= gridColEnd) {
      break;
    }
    col = 0;
  }
  MOZ_ASSERT(row < gridRowEnd || col == 0,
             "expected column 0 for placing in a new row");
  aArea->mCols.ResolveAutoPosition(col, mExplicitGridOffsetCol);
  aArea->mRows.ResolveAutoPosition(row, mExplicitGridOffsetRow);
  MOZ_ASSERT(aArea->IsDefinite());
}

void
nsGridContainerFrame::PlaceAutoAutoInColOrder(uint32_t aStartCol,
                                              uint32_t aStartRow,
                                              GridArea* aArea) const
{
  MOZ_ASSERT(aArea->mCols.IsAuto() && aArea->mRows.IsAuto());
  const uint32_t rowExtent = aArea->mRows.Extent();
  const uint32_t gridRowEnd = mGridRowEnd;
  const uint32_t gridColEnd = mGridColEnd;
  uint32_t col = aStartCol;
  uint32_t row = aStartRow;
  for (; col < gridColEnd; ++col) {
    row = FindAutoRow(col, row, aArea);
    if (row + rowExtent <= gridRowEnd) {
      break;
    }
    row = 0;
  }
  MOZ_ASSERT(col < gridColEnd || row == 0,
             "expected row 0 for placing in a new column");
  aArea->mCols.ResolveAutoPosition(col, mExplicitGridOffsetCol);
  aArea->mRows.ResolveAutoPosition(row, mExplicitGridOffsetRow);
  MOZ_ASSERT(aArea->IsDefinite());
}

void
nsGridContainerFrame::InitializeGridBounds(const nsStylePosition* aStyle)
{
  // http://dev.w3.org/csswg/css-grid/#grid-definition
  // Note that this is for a grid with a 1,1 origin.  We'll change that
  // to a 0,0 based grid after placing definite lines.
  uint32_t colEnd = aStyle->mGridTemplateColumns.mLineNameLists.Length();
  uint32_t rowEnd = aStyle->mGridTemplateRows.mLineNameLists.Length();
  auto areas = aStyle->mGridTemplateAreas.get();
  mExplicitGridColEnd = std::max(colEnd, areas ? areas->mNColumns + 1 : 1);
  mExplicitGridRowEnd = std::max(rowEnd, areas ? areas->NRows() + 1 : 1);
  mExplicitGridColEnd =
    std::min(mExplicitGridColEnd, uint32_t(nsStyleGridLine::kMaxLine));
  mExplicitGridRowEnd =
    std::min(mExplicitGridRowEnd, uint32_t(nsStyleGridLine::kMaxLine));
  mGridColEnd = mExplicitGridColEnd;
  mGridRowEnd = mExplicitGridRowEnd;
}

void
nsGridContainerFrame::PlaceGridItems(GridReflowState& aState)
{
  const nsStylePosition* const gridStyle = aState.mGridStyle;

  mCellMap.ClearOccupied();
  InitializeGridBounds(gridStyle);

  // http://dev.w3.org/csswg/css-grid/#line-placement
  // Resolve definite positions per spec chap 9.2.
  int32_t minCol = 1;
  int32_t minRow = 1;
  mGridItems.ClearAndRetainStorage();
  for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
    nsIFrame* child = *aState.mIter;
    GridItemInfo* info =
      mGridItems.AppendElement(GridItemInfo(PlaceDefinite(child, gridStyle)));
#ifdef DEBUG
    MOZ_ASSERT(aState.mIter.GridItemIndex() == mGridItems.Length() - 1,
               "GridItemIndex() is broken");
    info->mFrame = child;
#endif
    GridArea& area = info->mArea;
    if (area.mCols.IsDefinite()) {
      minCol = std::min(minCol, area.mCols.mUntranslatedStart);
    }
    if (area.mRows.IsDefinite()) {
      minRow = std::min(minRow, area.mRows.mUntranslatedStart);
    }
  }

  // Translate the whole grid so that the top-/left-most area is at 0,0.
  mExplicitGridOffsetCol = 1 - minCol; // minCol/Row is always <= 1, see above
  mExplicitGridOffsetRow = 1 - minRow;
  aState.mColFunctions.mExplicitGridOffset = mExplicitGridOffsetCol;
  aState.mRowFunctions.mExplicitGridOffset = mExplicitGridOffsetRow;
  const int32_t offsetToColZero = int32_t(mExplicitGridOffsetCol) - 1;
  const int32_t offsetToRowZero = int32_t(mExplicitGridOffsetRow) - 1;
  mGridColEnd += offsetToColZero;
  mGridRowEnd += offsetToRowZero;
  aState.mIter.Reset();
  for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
    GridArea& area = mGridItems[aState.mIter.GridItemIndex()].mArea;
    if (area.mCols.IsDefinite()) {
      area.mCols.mStart = area.mCols.mUntranslatedStart + offsetToColZero;
      area.mCols.mEnd = area.mCols.mUntranslatedEnd + offsetToColZero;
    }
    if (area.mRows.IsDefinite()) {
      area.mRows.mStart = area.mRows.mUntranslatedStart + offsetToRowZero;
      area.mRows.mEnd = area.mRows.mUntranslatedEnd + offsetToRowZero;
    }
    if (area.IsDefinite()) {
      mCellMap.Fill(area);
      InflateGridFor(area);
    }
  }

  // http://dev.w3.org/csswg/css-grid/#auto-placement-algo
  // Step 1, place 'auto' items that have one definite position -
  // definite row (column) for grid-auto-flow:row (column).
  auto flowStyle = gridStyle->mGridAutoFlow;
  const bool isRowOrder = (flowStyle & NS_STYLE_GRID_AUTO_FLOW_ROW);
  const bool isSparse = !(flowStyle & NS_STYLE_GRID_AUTO_FLOW_DENSE);
  // We need 1 cursor per row (or column) if placement is sparse.
  {
    Maybe<nsDataHashtable<nsUint32HashKey, uint32_t>> cursors;
    if (isSparse) {
      cursors.emplace();
    }
    auto placeAutoMinorFunc = isRowOrder ? &nsGridContainerFrame::PlaceAutoCol
                                         : &nsGridContainerFrame::PlaceAutoRow;
    aState.mIter.Reset();
    for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
      GridArea& area = mGridItems[aState.mIter.GridItemIndex()].mArea;
      LineRange& major = isRowOrder ? area.mRows : area.mCols;
      LineRange& minor = isRowOrder ? area.mCols : area.mRows;
      if (major.IsDefinite() && minor.IsAuto()) {
        // Items with 'auto' in the minor dimension only.
        uint32_t cursor = 0;
        if (isSparse) {
          cursors->Get(major.mStart, &cursor);
        }
        (this->*placeAutoMinorFunc)(cursor, &area);
        mCellMap.Fill(area);
        if (isSparse) {
          cursors->Put(major.mStart, minor.mEnd);
        }
      }
      InflateGridFor(area);  // Step 2, inflating for auto items too
    }
  }

  // XXX NOTE possible spec issue.
  // XXX It's unclear if the remaining major-dimension auto and
  // XXX auto in both dimensions should use the same cursor or not,
  // XXX https://www.w3.org/Bugs/Public/show_bug.cgi?id=16044
  // XXX seems to indicate it shouldn't.
  // XXX http://dev.w3.org/csswg/css-grid/#auto-placement-cursor
  // XXX now says it should (but didn't in earlier versions)

  // Step 3, place the remaining grid items
  uint32_t cursorMajor = 0; // for 'dense' these two cursors will stay at 0,0
  uint32_t cursorMinor = 0;
  auto placeAutoMajorFunc = isRowOrder ? &nsGridContainerFrame::PlaceAutoRow
                                       : &nsGridContainerFrame::PlaceAutoCol;
  aState.mIter.Reset();
  for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
    GridArea& area = mGridItems[aState.mIter.GridItemIndex()].mArea;
    MOZ_ASSERT(*aState.mIter == mGridItems[aState.mIter.GridItemIndex()].mFrame,
               "iterator out of sync with mGridItems");
    LineRange& major = isRowOrder ? area.mRows : area.mCols;
    LineRange& minor = isRowOrder ? area.mCols : area.mRows;
    if (major.IsAuto()) {
      if (minor.IsDefinite()) {
        // Items with 'auto' in the major dimension only.
        if (isSparse) {
          if (minor.mStart < cursorMinor) {
            ++cursorMajor;
          }
          cursorMinor = minor.mStart;
        }
        (this->*placeAutoMajorFunc)(cursorMajor, &area);
        if (isSparse) {
          cursorMajor = major.mStart;
        }
      } else {
        // Items with 'auto' in both dimensions.
        if (isRowOrder) {
          PlaceAutoAutoInRowOrder(cursorMinor, cursorMajor, &area);
        } else {
          PlaceAutoAutoInColOrder(cursorMajor, cursorMinor, &area);
        }
        if (isSparse) {
          cursorMajor = major.mStart;
          cursorMinor = minor.mEnd;
#ifdef DEBUG
          uint32_t gridMajorEnd = isRowOrder ? mGridRowEnd : mGridColEnd;
          uint32_t gridMinorEnd = isRowOrder ? mGridColEnd : mGridRowEnd;
          MOZ_ASSERT(cursorMajor <= gridMajorEnd,
                     "we shouldn't need to place items further than 1 track "
                     "past the current end of the grid, in major dimension");
          MOZ_ASSERT(cursorMinor <= gridMinorEnd,
                     "we shouldn't add implicit minor tracks for auto/auto");
#endif
        }
      }
      mCellMap.Fill(area);
      InflateGridFor(area);
    }
  }

  if (IsAbsoluteContainer()) {
    // 9.4 Absolutely-positioned Grid Items
    // http://dev.w3.org/csswg/css-grid/#abspos-items
    // We only resolve definite lines here; we'll align auto positions to the
    // grid container later during reflow.
    nsFrameList children(GetChildList(GetAbsoluteListID()));
    const int32_t offsetToColZero = int32_t(mExplicitGridOffsetCol) - 1;
    const int32_t offsetToRowZero = int32_t(mExplicitGridOffsetRow) - 1;
    // Untranslate the grid again temporarily while resolving abs.pos. lines.
    AutoRestore<uint32_t> save1(mGridColEnd);
    AutoRestore<uint32_t> save2(mGridRowEnd);
    mGridColEnd -= offsetToColZero;
    mGridRowEnd -= offsetToRowZero;
    mAbsPosItems.ClearAndRetainStorage();
    size_t i = 0;
    for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next(), ++i) {
      nsIFrame* child = e.get();
      GridItemInfo* info =
        mAbsPosItems.AppendElement(GridItemInfo(PlaceAbsPos(child, gridStyle)));
#ifdef DEBUG
      info->mFrame = child;
#endif
      GridArea& area = info->mArea;
      if (area.mCols.mUntranslatedStart != int32_t(kAutoLine)) {
        area.mCols.mStart = area.mCols.mUntranslatedStart + offsetToColZero;
      }
      if (area.mCols.mUntranslatedEnd != int32_t(kAutoLine)) {
        area.mCols.mEnd = area.mCols.mUntranslatedEnd + offsetToColZero;
      }
      if (area.mRows.mUntranslatedStart != int32_t(kAutoLine)) {
        area.mRows.mStart = area.mRows.mUntranslatedStart + offsetToRowZero;
      }
      if (area.mRows.mUntranslatedEnd != int32_t(kAutoLine)) {
        area.mRows.mEnd = area.mRows.mUntranslatedEnd + offsetToRowZero;
      }
    }
  }
}

void
nsGridContainerFrame::TrackSize::Initialize(nscoord aPercentageBasis,
                                            const nsStyleCoord& aMinCoord,
                                            const nsStyleCoord& aMaxCoord)
{
  MOZ_ASSERT(mBase == 0 && mLimit == 0 && mState == 0,
             "track size data is expected to be initialized to zero");
  // http://dev.w3.org/csswg/css-grid/#algo-init
  switch (aMinCoord.GetUnit()) {
    case eStyleUnit_Auto:
      mState = eAutoMinSizing;
      break;
    case eStyleUnit_Enumerated:
      mState = IsMinContent(aMinCoord) ? eMinContentMinSizing
                                       : eMaxContentMinSizing;
      break;
    case eStyleUnit_FlexFraction:
      mState = eFlexMinSizing;
      break;
    default:
      mBase = nsRuleNode::ComputeCoordPercentCalc(aMinCoord, aPercentageBasis);
  }
  switch (aMaxCoord.GetUnit()) {
    case eStyleUnit_Auto:
      mState |= eAutoMaxSizing;
      mLimit = NS_UNCONSTRAINEDSIZE;
      break;
    case eStyleUnit_Enumerated:
      mState |= IsMinContent(aMaxCoord) ? eMinContentMaxSizing
                                        : eMaxContentMaxSizing;
      mLimit = NS_UNCONSTRAINEDSIZE;
      break;
    case eStyleUnit_FlexFraction:
      mState |= eFlexMaxSizing;
      mLimit = mBase;
      break;
    default:
      mLimit = nsRuleNode::ComputeCoordPercentCalc(aMaxCoord, aPercentageBasis);
      if (mLimit < mBase) {
        mLimit = mBase;
      }
  }
}

void
nsGridContainerFrame::Tracks::Initialize(
  const TrackSizingFunctions& aFunctions,
  nscoord                     aGridGap,
  uint32_t                    aNumTracks,
  nscoord                     aContentBoxSize)
{
  mSizes.SetLength(aNumTracks);
  PodZero(mSizes.Elements(), mSizes.Length());
  nscoord percentageBasis = aContentBoxSize;
  if (percentageBasis == NS_UNCONSTRAINEDSIZE) {
    percentageBasis = 0;
  }

  const uint32_t explicitGridOffset = aFunctions.mExplicitGridOffset;
  MOZ_ASSERT(mSizes.Length() >=
               explicitGridOffset + aFunctions.mMinSizingFunctions.Length());
  MOZ_ASSERT(aFunctions.mMinSizingFunctions.Length() ==
               aFunctions.mMaxSizingFunctions.Length());
  // First we initialize the implicit tracks before the explicit grid starts.
  uint32_t i = 0;
  uint32_t sentinel = std::min<uint32_t>(explicitGridOffset, mSizes.Length());
  for (; i < sentinel; ++i) {
    mSizes[i].Initialize(percentageBasis,
                         aFunctions.mAutoMinSizing,
                         aFunctions.mAutoMaxSizing);
  }
  // Now initialize the explicit grid tracks.
  sentinel = std::min<uint32_t>(i + aFunctions.mMinSizingFunctions.Length(),
                                mSizes.Length());
  for (uint32_t j = 0; i < sentinel; ++i, ++j) {
    mSizes[i].Initialize(percentageBasis,
                         aFunctions.mMinSizingFunctions[j],
                         aFunctions.mMaxSizingFunctions[j]);
  }
  // Finally, initialize the implicit tracks that comes after the explicit grid.
  sentinel = mSizes.Length();
  for (; i < sentinel; ++i) {
    mSizes[i].Initialize(percentageBasis,
                         aFunctions.mAutoMinSizing,
                         aFunctions.mAutoMaxSizing);
  }

  mGridGap = aGridGap;
  MOZ_ASSERT(mGridGap >= nscoord(0), "negative grid gap");
}

/**
 * Return the [min|max]-content contribution of aChild to its parent (i.e.
 * the child's margin-box) in aAxis.
 */
static nscoord
ContentContribution(nsIFrame*                         aChild,
                    const nsHTMLReflowState*          aReflowState,
                    nsRenderingContext*               aRC,
                    WritingMode                       aCBWM,
                    LogicalAxis                       aAxis,
                    nsLayoutUtils::IntrinsicISizeType aConstraint,
                    uint32_t                          aFlags = 0)
{
  PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis));
  nscoord size = nsLayoutUtils::IntrinsicForAxis(axis, aRC, aChild, aConstraint,
                   aFlags | nsLayoutUtils::BAIL_IF_REFLOW_NEEDED);
  if (size == NS_INTRINSIC_WIDTH_UNKNOWN) {
    // We need to reflow the child to find its BSize contribution.
    WritingMode wm = aChild->GetWritingMode();
    nsContainerFrame* parent = aChild->GetParent();
    nsPresContext* pc = aChild->PresContext();
    Maybe<nsHTMLReflowState> dummyParentState;
    const nsHTMLReflowState* rs = aReflowState;
    if (!aReflowState) {
      MOZ_ASSERT(!parent->HasAnyStateBits(NS_FRAME_IN_REFLOW));
      dummyParentState.emplace(pc, parent, aRC,
                               LogicalSize(parent->GetWritingMode(), 0,
                                           NS_UNCONSTRAINEDSIZE),
                               nsHTMLReflowState::DUMMY_PARENT_REFLOW_STATE);
      rs = dummyParentState.ptr();
    }
#ifdef DEBUG
    // This will suppress various CRAZY_SIZE warnings for this reflow.
    parent->Properties().Set(nsContainerFrame::DebugReflowingWithInfiniteISize(),
                             parent /* anything non-null will do */);
#endif
    // XXX this will give mostly correct results for now (until bug 1174569).
    LogicalSize availableSize(wm, INFINITE_ISIZE_COORD, NS_UNCONSTRAINEDSIZE);
    nsHTMLReflowState childRS(pc, *rs, aChild, availableSize, nullptr,
                              nsHTMLReflowState::COMPUTE_SIZE_SHRINK_WRAP);
    nsHTMLReflowMetrics childSize(childRS);
    nsReflowStatus childStatus;
    const uint32_t flags = NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_SIZE_VIEW;
    parent->ReflowChild(aChild, pc, childSize, childRS, wm,
                        LogicalPoint(wm), nsSize(), flags, childStatus);
    parent->FinishReflowChild(aChild, pc, childSize, &childRS, wm,
                              LogicalPoint(wm), nsSize(), flags);
    size = childSize.BSize(wm);
    nsIFrame::IntrinsicISizeOffsetData offsets = aChild->IntrinsicBSizeOffsets();
    size += offsets.hMargin;
    size = nsLayoutUtils::AddPercents(aConstraint, size, offsets.hPctMargin);
#ifdef DEBUG
    parent->Properties().Delete(nsContainerFrame::DebugReflowingWithInfiniteISize());
#endif
  }
  return std::max(size, 0);
}

static nscoord
MinContentContribution(nsIFrame*                aChild,
                       const nsHTMLReflowState* aRS,
                       nsRenderingContext*      aRC,
                       WritingMode              aCBWM,
                       LogicalAxis              aAxis)
{
  return ContentContribution(aChild, aRS, aRC, aCBWM, aAxis,
                             nsLayoutUtils::MIN_ISIZE);
}

static nscoord
MaxContentContribution(nsIFrame*                aChild,
                       const nsHTMLReflowState* aRS,
                       nsRenderingContext*      aRC,
                       WritingMode              aCBWM,
                       LogicalAxis              aAxis)
{
  return ContentContribution(aChild, aRS, aRC, aCBWM, aAxis,
                             nsLayoutUtils::PREF_ISIZE);
}

static nscoord
MinSize(nsIFrame*                aChild,
        const nsHTMLReflowState* aRS,
        nsRenderingContext*      aRC,
        WritingMode              aCBWM,
        LogicalAxis              aAxis)
{
  PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis));
  const nsStylePosition* stylePos = aChild->StylePosition();
  const nsStyleCoord& style = axis == eAxisHorizontal ? stylePos->mMinWidth
                                                      : stylePos->mMinHeight;
  // https://drafts.csswg.org/css-grid/#min-size-auto
  // This calculates the min-content contribution from either a definite
  // min-width (or min-height depending on aAxis), or the "specified /
  // transferred size" for min-width:auto if overflow == visible (as min-width:0
  // otherwise), or NS_UNCONSTRAINEDSIZE for other min-width intrinsic values
  // (which results in always taking the "content size" part below).
  nscoord sz =
    nsLayoutUtils::MinSizeContributionForAxis(axis, aRC, aChild,
                                              nsLayoutUtils::MIN_ISIZE);
  auto unit = style.GetUnit();
  if (unit == eStyleUnit_Enumerated ||
      (unit == eStyleUnit_Auto &&
       aChild->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE)) {
    // Now calculate the "content size" part and return whichever is smaller.
    MOZ_ASSERT(unit != eStyleUnit_Enumerated || sz == NS_UNCONSTRAINEDSIZE);
    sz = std::min(sz, ContentContribution(aChild, aRS, aRC, aCBWM, aAxis,
                                          nsLayoutUtils::MIN_ISIZE,
                                          nsLayoutUtils::MIN_INTRINSIC_ISIZE));
  }
  return sz;
}

void
nsGridContainerFrame::Tracks::CalculateSizes(
  GridReflowState&            aState,
  nsTArray<GridItemInfo>&     aGridItems,
  const TrackSizingFunctions& aFunctions,
  nscoord                     aContentBoxSize,
  LineRange GridArea::*       aRange,
  IntrinsicISizeType          aConstraint)
{
  nscoord percentageBasis = aContentBoxSize;
  if (percentageBasis == NS_UNCONSTRAINEDSIZE) {
    percentageBasis = 0;
  }
  ResolveIntrinsicSize(aState, aGridItems, aFunctions, aRange, percentageBasis,
                       aConstraint);
  if (aConstraint != nsLayoutUtils::MIN_ISIZE) {
    nscoord freeSpace = aContentBoxSize;
    if (freeSpace != NS_UNCONSTRAINEDSIZE) {
      freeSpace -= SumOfGridGaps();
    }
    DistributeFreeSpace(freeSpace);
    StretchFlexibleTracks(aState, aGridItems, aFunctions, freeSpace);
  }
}

void
nsGridContainerFrame::CalculateTrackSizes(GridReflowState&   aState,
                                          const LogicalSize& aContentBox,
                                          IntrinsicISizeType aConstraint)
{
  const WritingMode& wm = aState.mWM;
  const nsStylePosition* stylePos = aState.mGridStyle;
  aState.mCols.Initialize(aState.mColFunctions, stylePos->mGridColumnGap,
                          mGridColEnd, aContentBox.ISize(wm));
  aState.mRows.Initialize(aState.mRowFunctions, stylePos->mGridRowGap,
                          mGridRowEnd, aContentBox.BSize(wm));

  aState.mCols.CalculateSizes(aState, mGridItems, aState.mColFunctions,
                              aContentBox.ISize(wm), &GridArea::mCols,
                              aConstraint);

  aState.mIter.Reset(); // XXX cleanup this Reset mess!
  aState.mRows.CalculateSizes(aState, mGridItems, aState.mRowFunctions,
                              aContentBox.BSize(wm), &GridArea::mRows,
                              aConstraint);
}

bool
nsGridContainerFrame::Tracks::HasIntrinsicButNoFlexSizingInRange(
  const LineRange&      aRange,
  IntrinsicISizeType    aConstraint,
  TrackSize::StateBits* aState) const
{
  MOZ_ASSERT(!aRange.IsAuto(), "must have a definite range");
  const uint32_t start = aRange.mStart;
  const uint32_t end = aRange.mEnd;
  const TrackSize::StateBits selector =
    TrackSize::eIntrinsicMinSizing |
    TrackSize::eIntrinsicMaxSizing |
    (aConstraint == nsLayoutUtils::MIN_ISIZE ? TrackSize::eFlexMinSizing
                                             : TrackSize::StateBits(0));
  bool foundIntrinsic = false;
  for (uint32_t i = start; i < end; ++i) {
    TrackSize::StateBits state = mSizes[i].mState;
    *aState |= state;
    if (state & TrackSize::eFlexMaxSizing) {
      return false;
    }
    if (state & selector) {
      foundIntrinsic = true;
    }
  }
  return foundIntrinsic;
}

bool
nsGridContainerFrame::Tracks::ResolveIntrinsicSizeStep1(
  GridReflowState&            aState,
  const TrackSizingFunctions& aFunctions,
  nscoord                     aPercentageBasis,
  IntrinsicISizeType          aConstraint,
  const LineRange&            aRange,
  nsIFrame*                   aGridItem)
{
  Maybe<nscoord> minContentContribution;
  Maybe<nscoord> maxContentContribution;
  // min sizing
  TrackSize& sz = mSizes[aRange.mStart];
  WritingMode wm = aState.mWM;
  const nsHTMLReflowState* rs = aState.mReflowState;
  nsRenderingContext* rc = &aState.mRenderingContext;
  if (sz.mState & TrackSize::eAutoMinSizing) {
    nscoord s = MinSize(aGridItem, rs, rc, wm, mAxis);
    sz.mBase = std::max(sz.mBase, s);
  } else if ((sz.mState & TrackSize::eMinContentMinSizing) ||
             (aConstraint == nsLayoutUtils::MIN_ISIZE &&
              (sz.mState & TrackSize::eFlexMinSizing))) {
    nscoord s = MinContentContribution(aGridItem, rs, rc, wm, mAxis);
    minContentContribution.emplace(s);
    sz.mBase = std::max(sz.mBase, minContentContribution.value());
  } else if (sz.mState & TrackSize::eMaxContentMinSizing) {
    nscoord s = MaxContentContribution(aGridItem, rs, rc, wm, mAxis);
    maxContentContribution.emplace(s);
    sz.mBase = std::max(sz.mBase, maxContentContribution.value());
  }
  // max sizing
  if (sz.mState & TrackSize::eMinContentMaxSizing) {
    if (minContentContribution.isNothing()) {
      nscoord s = MinContentContribution(aGridItem, rs, rc, wm, mAxis);
      minContentContribution.emplace(s);
    }
    if (sz.mLimit == NS_UNCONSTRAINEDSIZE) {
      sz.mLimit = minContentContribution.value();
    } else {
      sz.mLimit = std::max(sz.mLimit, minContentContribution.value());
    }
  } else if (sz.mState & (TrackSize::eAutoMaxSizing |
                          TrackSize::eMaxContentMaxSizing)) {
    if (maxContentContribution.isNothing()) {
      nscoord s = MaxContentContribution(aGridItem, rs, rc, wm, mAxis);
      maxContentContribution.emplace(s);
    }
    if (sz.mLimit == NS_UNCONSTRAINEDSIZE) {
      sz.mLimit = maxContentContribution.value();
    } else {
      sz.mLimit = std::max(sz.mLimit, maxContentContribution.value());
    }
  }
  if (sz.mLimit < sz.mBase) {
    sz.mLimit = sz.mBase;
  }
  return sz.mState & TrackSize::eFlexMaxSizing;
}

void
nsGridContainerFrame::Tracks::ResolveIntrinsicSize(
  GridReflowState&            aState,
  nsTArray<GridItemInfo>&     aGridItems,
  const TrackSizingFunctions& aFunctions,
  LineRange GridArea::*       aRange,
  nscoord                     aPercentageBasis,
  IntrinsicISizeType          aConstraint)
{
  // Some data we collect on each item for Step 2 of the algorithm below.
  struct Step2ItemData
  {
    uint32_t mSpan;
    TrackSize::StateBits mState;
    LineRange mLineRange;
    nscoord mMinSize;
    nscoord mMinContentContribution;
    nscoord mMaxContentContribution;
    nsIFrame* mFrame;
    static bool IsSpanLessThan(const Step2ItemData& a, const Step2ItemData& b)
    {
      return a.mSpan < b.mSpan;
    }
  };

  // Resolve Intrinsic Track Sizes
  // http://dev.w3.org/csswg/css-grid/#algo-content
  // We're also setting mIsFlexing on the item here to speed up
  // FindUsedFlexFraction later.
  nsAutoTArray<TrackSize::StateBits, 16> stateBitsPerSpan;
  nsTArray<Step2ItemData> step2Items;
  GridItemCSSOrderIterator& iter = aState.mIter;
  nsRenderingContext* rc = &aState.mRenderingContext;
  WritingMode wm = aState.mWM;
  uint32_t maxSpan = 0; // max span of the step2Items items
  const TrackSize::StateBits flexMin =
    aConstraint == nsLayoutUtils::MIN_ISIZE ? TrackSize::eFlexMinSizing
                                            : TrackSize::StateBits(0);
  for (; !iter.AtEnd(); iter.Next()) {
    nsIFrame* child = *iter;
    const GridArea& area = aGridItems[iter.GridItemIndex()].mArea;
    const LineRange& lineRange = area.*aRange;
    uint32_t span = lineRange.Extent();
    if (span == 1) {
      // Step 1. Size tracks to fit non-spanning items.
      aGridItems[iter.GridItemIndex()].mIsFlexing[mAxis] =
        ResolveIntrinsicSizeStep1(aState, aFunctions, aPercentageBasis,
                                  aConstraint, lineRange, child);
    } else {
      TrackSize::StateBits state = TrackSize::StateBits(0);
      if (HasIntrinsicButNoFlexSizingInRange(lineRange, aConstraint, &state)) {
        // Collect data for Step 2.
        maxSpan = std::max(maxSpan, span);
        if (span >= stateBitsPerSpan.Length()) {
          uint32_t len = 2 * span;
          stateBitsPerSpan.SetCapacity(len);
          for (uint32_t i = stateBitsPerSpan.Length(); i < len; ++i) {
            stateBitsPerSpan.AppendElement(TrackSize::StateBits(0));
          }
        }
        stateBitsPerSpan[span] |= state;
        nscoord minSize = 0;
        if (state & (flexMin | TrackSize::eIntrinsicMinSizing)) { // for 2.1
          minSize = MinSize(child, aState.mReflowState, rc, wm, mAxis);
        }
        nscoord minContent = 0;
        if (state & (flexMin | TrackSize::eMinOrMaxContentMinSizing | // for 2.2
                     TrackSize::eIntrinsicMaxSizing)) {               // for 2.5
          minContent = MinContentContribution(child, aState.mReflowState,
                                              rc, wm, mAxis);
        }
        nscoord maxContent = 0;
        if (state & (TrackSize::eMaxContentMinSizing |         // for 2.3
                     TrackSize::eAutoOrMaxContentMaxSizing)) { // for 2.6
          maxContent = MaxContentContribution(child, aState.mReflowState,
                                              rc, wm, mAxis);
        }
        step2Items.AppendElement(
          Step2ItemData({span, state, lineRange, minSize,
                         minContent, maxContent, child}));
      } else {
        aGridItems[iter.GridItemIndex()].mIsFlexing[mAxis] =
          !!(state & TrackSize::eFlexMaxSizing);
      }
    }
  }

  // Step 2.
  if (maxSpan) {
    // Sort the collected items on span length, shortest first.
    std::stable_sort(step2Items.begin(), step2Items.end(),
                     Step2ItemData::IsSpanLessThan);

    nsTArray<uint32_t> tracks(maxSpan);
    nsTArray<TrackSize> plan(mSizes.Length());
    plan.SetLength(mSizes.Length());
    for (uint32_t i = 0, len = step2Items.Length(); i < len; ) {
      // Start / end index for items of the same span length:
      const uint32_t spanGroupStartIndex = i;
      uint32_t spanGroupEndIndex = len;
      const uint32_t span = step2Items[i].mSpan;
      for (++i; i < len; ++i) {
        if (step2Items[i].mSpan != span) {
          spanGroupEndIndex = i;
          break;
        }
      }

      bool updatedBase = false; // Did we update any mBase in step 2.1 - 2.3?
      TrackSize::StateBits selector(flexMin | TrackSize::eIntrinsicMinSizing);
      if (stateBitsPerSpan[span] & selector) {
        // Step 2.1 MinSize to intrinsic min-sizing.
        for (i = spanGroupStartIndex; i < spanGroupEndIndex; ++i) {
          Step2ItemData& item = step2Items[i];
          if (!(item.mState & selector)) {
            continue;
          }
          nscoord space = item.mMinSize;
          if (space <= 0) {
            continue;
          }
          tracks.ClearAndRetainStorage();
          space = CollectGrowable(space, mSizes, item.mLineRange, selector,
                                  tracks);
          if (space > 0) {
            DistributeToTrackBases(space, plan, tracks, selector);
            updatedBase = true;
          }
        }
      }

      selector = flexMin | TrackSize::eMinOrMaxContentMinSizing;
      if (stateBitsPerSpan[span] & selector) {
        // Step 2.2 MinContentContribution to min-/max-content min-sizing.
        for (i = spanGroupStartIndex; i < spanGroupEndIndex; ++i) {
          Step2ItemData& item = step2Items[i];
          if (!(item.mState & selector)) {
            continue;
          }
          nscoord space = item.mMinContentContribution;
          if (space <= 0) {
            continue;
          }
          tracks.ClearAndRetainStorage();
          space = CollectGrowable(space, mSizes, item.mLineRange, selector,
                                  tracks);
          if (space > 0) {
            DistributeToTrackBases(space, plan, tracks, selector);
            updatedBase = true;
          }
        }
      }

      if (stateBitsPerSpan[span] & TrackSize::eMaxContentMinSizing) {
        // Step 2.3 MaxContentContribution to max-content min-sizing.
        for (i = spanGroupStartIndex; i < spanGroupEndIndex; ++i) {
          Step2ItemData& item = step2Items[i];
          if (!(item.mState & TrackSize::eMaxContentMinSizing)) {
            continue;
          }
          nscoord space = item.mMaxContentContribution;
          if (space <= 0) {
            continue;
          }
          tracks.ClearAndRetainStorage();
          space = CollectGrowable(space, mSizes, item.mLineRange,
                                  TrackSize::eMaxContentMinSizing,
                                  tracks);
          if (space > 0) {
            DistributeToTrackBases(space, plan, tracks,
                                   TrackSize::eMaxContentMinSizing);
            updatedBase = true;
          }
        }
      }

      if (updatedBase) {
        // Step 2.4
        for (TrackSize& sz : mSizes) {
          if (sz.mBase > sz.mLimit) {
            sz.mLimit = sz.mBase;
          }
        }
      }
      if (stateBitsPerSpan[span] & TrackSize::eIntrinsicMaxSizing) {
        plan = mSizes;
        for (TrackSize& sz : plan) {
          if (sz.mLimit == NS_UNCONSTRAINEDSIZE) {
            // use mBase as the planned limit
          } else {
            sz.mBase = sz.mLimit;
          }
        }

        // Step 2.5 MinContentContribution to intrinsic max-sizing.
        for (i = spanGroupStartIndex; i < spanGroupEndIndex; ++i) {
          Step2ItemData& item = step2Items[i];
          if (!(item.mState & TrackSize::eIntrinsicMaxSizing)) {
            continue;
          }
          nscoord space = item.mMinContentContribution;
          if (space <= 0) {
            continue;
          }
          tracks.ClearAndRetainStorage();
          space = CollectGrowable(space, plan, item.mLineRange,
                                  TrackSize::eIntrinsicMaxSizing,
                                  tracks);
          if (space > 0) {
            DistributeToTrackLimits(space, plan, tracks);
          }
        }
        for (size_t j = 0, len = mSizes.Length(); j < len; ++j) {
          TrackSize& sz = plan[j];
          sz.mState &= ~(TrackSize::eFrozen | TrackSize::eSkipGrowUnlimited);
          if (sz.mLimit != NS_UNCONSTRAINEDSIZE) {
            sz.mLimit = sz.mBase;  // collect the results from 2.5
          }
        }

        if (stateBitsPerSpan[span] & TrackSize::eAutoOrMaxContentMaxSizing) {
          // Step 2.6 MaxContentContribution to max-content max-sizing.
          for (i = spanGroupStartIndex; i < spanGroupEndIndex; ++i) {
            Step2ItemData& item = step2Items[i];
            if (!(item.mState & TrackSize::eAutoOrMaxContentMaxSizing)) {
              continue;
            }
            nscoord space = item.mMaxContentContribution;
            if (space <= 0) {
              continue;
            }
            tracks.ClearAndRetainStorage();
            space = CollectGrowable(space, plan, item.mLineRange,
                                    TrackSize::eAutoOrMaxContentMaxSizing,
                                    tracks);
            if (space > 0) {
              DistributeToTrackLimits(space, plan, tracks);
            }
          }
        }
      }
    }
  }

  // Step 3.
  for (TrackSize& sz : mSizes) {
    if (sz.mLimit == NS_UNCONSTRAINEDSIZE) {
      sz.mLimit = sz.mBase;
    }
  }
}

float
nsGridContainerFrame::Tracks::FindFrUnitSize(
  const LineRange&            aRange,
  const nsTArray<uint32_t>&   aFlexTracks,
  const TrackSizingFunctions& aFunctions,
  nscoord                     aSpaceToFill) const
{
  MOZ_ASSERT(aSpaceToFill > 0 && !aFlexTracks.IsEmpty());
  float flexFactorSum = 0.0f;
  nscoord leftOverSpace = aSpaceToFill;
  for (uint32_t i = aRange.mStart, end = aRange.mEnd; i < end; ++i) {
    const TrackSize& sz = mSizes[i];
    if (sz.mState & TrackSize::eFlexMaxSizing) {
      flexFactorSum += aFunctions.MaxSizingFor(i).GetFlexFractionValue();
    } else {
      leftOverSpace -= sz.mBase;
      if (leftOverSpace <= 0) {
        return 0.0f;
      }
    }
  }
  bool restart;
  float hypotheticalFrSize;
  nsTArray<uint32_t> flexTracks(aFlexTracks);
  uint32_t numFlexTracks = flexTracks.Length();
  do {
    restart = false;
    hypotheticalFrSize = leftOverSpace / std::max(flexFactorSum, 1.0f);
    for (uint32_t i = 0, len = flexTracks.Length(); i < len; ++i) {
      uint32_t track = flexTracks[i];
      if (track == kAutoLine) {
        continue; // Track marked as inflexible in a prev. iter of this loop.
      }
      float flexFactor = aFunctions.MaxSizingFor(track).GetFlexFractionValue();
      const nscoord base = mSizes[track].mBase;
      if (flexFactor * hypotheticalFrSize < base) {
        // 12.7.1.4: Treat this track as inflexible.
        flexTracks[i] = kAutoLine;
        flexFactorSum -= flexFactor;
        leftOverSpace -= base;
        --numFlexTracks;
        if (numFlexTracks == 0 || leftOverSpace <= 0) {
          return 0.0f;
        }
        restart = true;
        // break; XXX (bug 1176621 comment 16) measure which is more common
      }
    }
  } while (restart);
  return hypotheticalFrSize;
}

float
nsGridContainerFrame::Tracks::FindUsedFlexFraction(
  GridReflowState&            aState,
  nsTArray<GridItemInfo>&     aGridItems,
  const nsTArray<uint32_t>&   aFlexTracks,
  const TrackSizingFunctions& aFunctions,
  nscoord                     aAvailableSize) const
{
  if (aAvailableSize != NS_UNCONSTRAINEDSIZE) {
    // Use all of the grid tracks and a 'space to fill' of the available space.
    const TranslatedLineRange range(0, mSizes.Length());
    return FindFrUnitSize(range, aFlexTracks, aFunctions, aAvailableSize);
  }

  // The used flex fraction is the maximum of:
  // ... each flexible track's base size divided by its flex factor
  float fr = 0.0f;
  for (uint32_t track : aFlexTracks) {
    float flexFactor = aFunctions.MaxSizingFor(track).GetFlexFractionValue();
    if (flexFactor > 0.0f) {
      fr = std::max(fr, mSizes[track].mBase / flexFactor);
    }
  }
  WritingMode wm = aState.mWM;
  nsRenderingContext* rc = &aState.mRenderingContext;
  const nsHTMLReflowState* rs = aState.mReflowState;
  GridItemCSSOrderIterator& iter = aState.mIter;
  iter.Reset();
  // ... the result of 'finding the size of an fr' for each item that spans
  // a flex track with its max-content contribution as 'space to fill'
  for (; !iter.AtEnd(); iter.Next()) {
    const GridItemInfo& item = aGridItems[iter.GridItemIndex()];
    if (item.mIsFlexing[mAxis]) {
      nscoord spaceToFill = MaxContentContribution(*iter, rs, rc, wm, mAxis);
      if (spaceToFill <= 0) {
        continue;
      }
      // ... and all its spanned tracks as input.
      const LineRange& range =
        mAxis == eLogicalAxisInline ? item.mArea.mCols : item.mArea.mRows;
      nsTArray<uint32_t> itemFlexTracks;
      for (uint32_t i = range.mStart, end = range.mEnd; i < end; ++i) {
        if (mSizes[i].mState & TrackSize::eFlexMaxSizing) {
          itemFlexTracks.AppendElement(i);
        }
      }
      float itemFr =
        FindFrUnitSize(range, itemFlexTracks, aFunctions, spaceToFill);
      fr = std::max(fr, itemFr);
    }
  }
  return fr;
}

void
nsGridContainerFrame::Tracks::StretchFlexibleTracks(
  GridReflowState&            aState,
  nsTArray<GridItemInfo>&     aGridItems,
  const TrackSizingFunctions& aFunctions,
  nscoord                     aAvailableSize)
{
  if (aAvailableSize <= 0) {
    return;
  }
  nsTArray<uint32_t> flexTracks(mSizes.Length());
  for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) {
    if (mSizes[i].mState & TrackSize::eFlexMaxSizing) {
      flexTracks.AppendElement(i);
    }
  }
  if (flexTracks.IsEmpty()) {
    return;
  }
  float fr = FindUsedFlexFraction(aState, aGridItems, flexTracks,
                                  aFunctions, aAvailableSize);
  if (fr != 0.0f) {
    for (uint32_t i : flexTracks) {
      float flexFactor = aFunctions.MaxSizingFor(i).GetFlexFractionValue();
      nscoord flexLength = NSToCoordRound(flexFactor * fr);
      nscoord& base = mSizes[i].mBase;
      if (flexLength > base) {
        base = flexLength;
      }
    }
  }
}

void
nsGridContainerFrame::Tracks::AlignJustifyContent(
  const nsHTMLReflowState& aReflowState,
  const LogicalSize&     aContainerSize)
{
  if (mSizes.IsEmpty()) {
    return;
  }

  const bool isAlign = mAxis == eLogicalAxisBlock;
  auto stylePos = aReflowState.mStylePosition;
  const auto valueAndFallback = isAlign ?
    stylePos->ComputedAlignContent() :
    stylePos->ComputedJustifyContent(aReflowState.mStyleDisplay);
  WritingMode wm = aReflowState.GetWritingMode();
  bool overflowSafe;
  auto alignment = ::GetAlignJustifyValue(valueAndFallback, wm, isAlign,
                                          &overflowSafe);
  if (alignment == NS_STYLE_ALIGN_AUTO) {
    alignment = NS_STYLE_ALIGN_START;
  }

  // Compute the free space and count auto-sized tracks.
  size_t numAutoTracks = 0;
  nscoord space;
  if (alignment != NS_STYLE_ALIGN_START) {
    nscoord trackSizeSum = 0;
    for (const TrackSize& sz : mSizes) {
      trackSizeSum += sz.mBase;
      if (sz.mState & TrackSize::eAutoMaxSizing) {
        ++numAutoTracks;
      }
    }
    nscoord cbSize = isAlign ? aContainerSize.BSize(wm)
                             : aContainerSize.ISize(wm);
    space = cbSize - trackSizeSum - SumOfGridGaps();
    // Use the fallback value instead when applicable.
    if (space < 0 ||
        (alignment == NS_STYLE_ALIGN_SPACE_BETWEEN && mSizes.Length() == 1)) {
      auto fallback = ::GetAlignJustifyFallbackIfAny(valueAndFallback, wm,
                                                     isAlign, &overflowSafe);
      if (fallback) {
        alignment = fallback;
      }
    }
    if (space == 0 || (space < 0 && overflowSafe)) {
      // XXX check that this makes sense also for [last-]baseline (bug 1151204).
      alignment = NS_STYLE_ALIGN_START;
    }
  }

  // Optimize the cases where we just need to set each track's position.
  nscoord pos = 0;
  bool distribute = true;
  switch (alignment) {
    case NS_STYLE_ALIGN_BASELINE:
    case NS_STYLE_ALIGN_LAST_BASELINE:
      NS_WARNING("'NYI: baseline/last-baseline' (bug 1151204)"); // XXX
    case NS_STYLE_ALIGN_START:
      distribute = false;
      break;
    case NS_STYLE_ALIGN_END:
      pos = space;
      distribute = false;
      break;
    case NS_STYLE_ALIGN_CENTER:
      pos = space / 2;
      distribute = false;
      break;
    case NS_STYLE_ALIGN_STRETCH:
      distribute = numAutoTracks != 0;
      break;
  }
  if (!distribute) {
    for (TrackSize& sz : mSizes) {
      sz.mPosition = pos;
      pos += sz.mBase + mGridGap;
    }
    return;
  }

  // Distribute free space to/between tracks and set their position.
  MOZ_ASSERT(space > 0, "should've handled that on the fallback path above");
  nscoord between, roundingError;
  switch (alignment) {
    case NS_STYLE_ALIGN_STRETCH: {
      MOZ_ASSERT(numAutoTracks > 0, "we handled numAutoTracks == 0 above");
      nscoord spacePerTrack;
      roundingError = NSCoordDivRem(space, numAutoTracks, &spacePerTrack);
      for (TrackSize& sz : mSizes) {
        sz.mPosition = pos;
        if (!(sz.mState & TrackSize::eAutoMaxSizing)) {
          pos += sz.mBase + mGridGap;
          continue;
        }
        nscoord stretch = spacePerTrack;
        if (roundingError) {
          roundingError -= 1;
          stretch += 1;
        }
        nscoord newBase = sz.mBase + stretch;
        sz.mBase = newBase;
        pos += newBase + mGridGap;
      }
      MOZ_ASSERT(!roundingError, "we didn't distribute all rounding error?");
      return;
    }
    case NS_STYLE_ALIGN_SPACE_BETWEEN:
      MOZ_ASSERT(mSizes.Length() > 1, "should've used a fallback above");
      roundingError = NSCoordDivRem(space, mSizes.Length() - 1, &between);
      break;
    case NS_STYLE_ALIGN_SPACE_AROUND:
      roundingError = NSCoordDivRem(space, mSizes.Length(), &between);
      pos = between / 2;
      break;
    case NS_STYLE_ALIGN_SPACE_EVENLY:
      roundingError = NSCoordDivRem(space, mSizes.Length() + 1, &between);
      pos = between;
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("unknown align-/justify-content value");
      between = 0; // just to avoid a compiler warning
  }
  between += mGridGap;
  for (TrackSize& sz : mSizes) {
    sz.mPosition = pos;
    nscoord spacing = between;
    if (roundingError) {
      roundingError -= 1;
      spacing += 1;
    }
    pos += sz.mBase + spacing;
  }
  MOZ_ASSERT(!roundingError, "we didn't distribute all rounding error?");
}

void
nsGridContainerFrame::LineRange::ToPositionAndLength(
  const nsTArray<TrackSize>& aTrackSizes, nscoord* aPos, nscoord* aLength) const
{
  MOZ_ASSERT(mStart != kAutoLine && mEnd != kAutoLine,
             "expected a definite LineRange");
  MOZ_ASSERT(mStart < mEnd);
  nscoord startPos = aTrackSizes[mStart].mPosition;
  const TrackSize& sz = aTrackSizes[mEnd - 1];
  *aPos = startPos;
  *aLength = (sz.mPosition + sz.mBase) - startPos;
}

nscoord
nsGridContainerFrame::LineRange::ToLength(
  const nsTArray<TrackSize>& aTrackSizes) const
{
  MOZ_ASSERT(mStart != kAutoLine && mEnd != kAutoLine,
             "expected a definite LineRange");
  MOZ_ASSERT(mStart < mEnd);
  nscoord startPos = aTrackSizes[mStart].mPosition;
  const TrackSize& sz = aTrackSizes[mEnd - 1];
  return (sz.mPosition + sz.mBase) - startPos;
}

void
nsGridContainerFrame::LineRange::ToPositionAndLengthForAbsPos(
  const nsTArray<TrackSize>& aTrackSizes, nscoord aGridOrigin,
  nscoord* aPos, nscoord* aLength) const
{
  // kAutoLine for abspos children contributes the corresponding edge
  // of the grid container's padding-box.
  if (mEnd == kAutoLine) {
    if (mStart == kAutoLine) {
      // done
    } else {
      const nscoord endPos = *aPos + *aLength;
      nscoord startPos =
        ::GridLineEdge(mStart, aTrackSizes, GridLineSide::eAfterGridGap);
      *aPos = aGridOrigin + startPos;
      *aLength = std::max(endPos - *aPos, 0);
    }
  } else {
    if (mStart == kAutoLine) {
      nscoord endPos =
        ::GridLineEdge(mEnd, aTrackSizes, GridLineSide::eBeforeGridGap);
      *aLength = std::max(aGridOrigin + endPos, 0);
    } else {
      nscoord pos;
      ToPositionAndLength(aTrackSizes, &pos, aLength);
      *aPos = aGridOrigin + pos;
    }
  }
}

LogicalRect
nsGridContainerFrame::ContainingBlockFor(const GridReflowState& aState,
                                         const GridArea&        aArea) const
{
  nscoord i, b, iSize, bSize;
  MOZ_ASSERT(aArea.mCols.Extent() > 0, "grid items cover at least one track");
  MOZ_ASSERT(aArea.mRows.Extent() > 0, "grid items cover at least one track");
  aArea.mCols.ToPositionAndLength(aState.mCols.mSizes, &i, &iSize);
  aArea.mRows.ToPositionAndLength(aState.mRows.mSizes, &b, &bSize);
  return LogicalRect(aState.mWM, i, b, iSize, bSize);
}

LogicalRect
nsGridContainerFrame::ContainingBlockForAbsPos(const GridReflowState& aState,
                                               const GridArea&        aArea,
                                               const LogicalPoint& aGridOrigin,
                                               const LogicalRect& aGridCB) const
{
  const WritingMode& wm = aState.mWM;
  nscoord i = aGridCB.IStart(wm);
  nscoord b = aGridCB.BStart(wm);
  nscoord iSize = aGridCB.ISize(wm);
  nscoord bSize = aGridCB.BSize(wm);
  aArea.mCols.ToPositionAndLengthForAbsPos(aState.mCols.mSizes,
                                           aGridOrigin.I(wm),
                                           &i, &iSize);
  aArea.mRows.ToPositionAndLengthForAbsPos(aState.mRows.mSizes,
                                           aGridOrigin.B(wm),
                                           &b, &bSize);
  return LogicalRect(wm, i, b, iSize, bSize);
}

void
nsGridContainerFrame::ReflowChildren(GridReflowState&     aState,
                                     const LogicalRect&   aContentArea,
                                     nsHTMLReflowMetrics& aDesiredSize,
                                     nsReflowStatus&      aStatus)
{
  MOZ_ASSERT(aState.mReflowState);

  WritingMode wm = aState.mReflowState->GetWritingMode();
  const LogicalPoint gridOrigin(aContentArea.Origin(wm));
  const nsSize containerSize =
    (aContentArea.Size(wm) +
     aState.mReflowState->ComputedLogicalBorderPadding().Size(wm)).GetPhysicalSize(wm);
  nsPresContext* pc = PresContext();
  nsStyleContext* containerSC = StyleContext();
  for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
    nsIFrame* child = *aState.mIter;
    const bool isGridItem = child->GetType() != nsGkAtoms::placeholderFrame;
    LogicalRect cb(wm);
    if (MOZ_LIKELY(isGridItem)) {
      MOZ_ASSERT(mGridItems[aState.mIter.GridItemIndex()].mFrame == child,
                 "iterator out of sync with mGridItems");
      GridArea& area = mGridItems[aState.mIter.GridItemIndex()].mArea;
      MOZ_ASSERT(area.IsDefinite());
      cb = ContainingBlockFor(aState, area);
      cb += gridOrigin;
    } else {
      cb = aContentArea;
    }
    WritingMode childWM = child->GetWritingMode();
    LogicalSize childCBSize = cb.Size(wm).ConvertTo(childWM, wm);
    LogicalSize percentBasis(childCBSize);
    // XXX temporary workaround to avoid being INCOMPLETE until we have
    // support for fragmentation (bug 1144096)
    childCBSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE;

    Maybe<nsHTMLReflowState> childRS; // Maybe<> so we can reuse the space
    childRS.emplace(pc, *aState.mReflowState, child, childCBSize, &percentBasis);
    // We need the width of the child before we can correctly convert
    // the writing-mode of its origin, so we reflow at (0, 0) using a dummy
    // containerSize, and then pass the correct position to FinishReflowChild.
    Maybe<nsHTMLReflowMetrics> childSize; // Maybe<> so we can reuse the space
    childSize.emplace(*childRS);
    nsReflowStatus childStatus;
    const nsSize dummyContainerSize;
    ReflowChild(child, pc, *childSize, *childRS, childWM, LogicalPoint(childWM),
                dummyContainerSize, 0, childStatus);
    LogicalPoint childPos =
      cb.Origin(wm).ConvertTo(childWM, wm,
                              containerSize - childSize->PhysicalSize());
    // Apply align/justify-self and reflow again if that affects the size.
    if (isGridItem) {
      LogicalSize oldSize = childSize->Size(childWM); // from the ReflowChild()
      LogicalSize newContentSize(childWM);
      auto align = childRS->mStylePosition->ComputedAlignSelf(
                     childRS->mStyleDisplay, containerSC);
      Maybe<LogicalAxis> alignResize =
        AlignSelf(align, cb, wm, *childRS, oldSize, &newContentSize, &childPos);
      auto justify = childRS->mStylePosition->ComputedJustifySelf(
                       childRS->mStyleDisplay, containerSC);
      Maybe<LogicalAxis> justifyResize =
        JustifySelf(justify, cb, wm, *childRS, oldSize, &newContentSize, &childPos);
      if (alignResize || justifyResize) {
        FinishReflowChild(child, pc, *childSize, childRS.ptr(), childWM,
                          LogicalPoint(childWM), containerSize,
                          NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_SIZE_VIEW);
        childSize.reset(); // In reverse declaration order since it runs
        childRS.reset();   // destructors.
        childRS.emplace(pc, *aState.mReflowState, child, childCBSize, &percentBasis);
        if ((alignResize && alignResize.value() == eLogicalAxisBlock) ||
            (justifyResize && justifyResize.value() == eLogicalAxisBlock)) {
          childRS->SetComputedBSize(newContentSize.BSize(childWM));
          childRS->SetBResize(true);
        }
        if ((alignResize && alignResize.value() == eLogicalAxisInline) ||
            (justifyResize && justifyResize.value() == eLogicalAxisInline)) {
          childRS->SetComputedISize(newContentSize.ISize(childWM));
          childRS->SetIResize(true);
        }
        childSize.emplace(*childRS);
        ReflowChild(child, pc, *childSize, *childRS, childWM,
                    LogicalPoint(childWM), dummyContainerSize, 0, childStatus);
      }
    }
    childRS->ApplyRelativePositioning(&childPos, containerSize);
    FinishReflowChild(child, pc, *childSize, childRS.ptr(), childWM, childPos,
                      containerSize, 0);
    ConsiderChildOverflow(aDesiredSize.mOverflowAreas, child);
    // XXX deal with 'childStatus' not being COMPLETE (bug 1144096)
  }

  if (IsAbsoluteContainer()) {
    nsFrameList children(GetChildList(GetAbsoluteListID()));
    if (!children.IsEmpty()) {
      LogicalMargin pad(aState.mReflowState->ComputedLogicalPadding());
      pad.ApplySkipSides(GetLogicalSkipSides(aState.mReflowState));
      // 'gridOrigin' is the origin of the grid (the start of the first track),
      // with respect to the grid container's padding-box (CB).
      const LogicalPoint gridOrigin(wm, pad.IStart(wm), pad.BStart(wm));
      const LogicalRect gridCB(wm, 0, 0,
                               aContentArea.ISize(wm) + pad.IStartEnd(wm),
                               aContentArea.BSize(wm) + pad.BStartEnd(wm));
      const nsSize gridCBPhysicalSize = gridCB.Size(wm).GetPhysicalSize(wm);
      size_t i = 0;
      for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next(), ++i) {
        nsIFrame* child = e.get();
        MOZ_ASSERT(i < mAbsPosItems.Length());
        MOZ_ASSERT(mAbsPosItems[i].mFrame == child);
        GridArea& area = mAbsPosItems[i].mArea;
        LogicalRect itemCB =
          ContainingBlockForAbsPos(aState, area, gridOrigin, gridCB);
        // nsAbsoluteContainingBlock::Reflow uses physical coordinates.
        nsRect* cb = static_cast<nsRect*>(child->Properties().Get(
                       GridItemContainingBlockRect()));
        if (!cb) {
          cb = new nsRect;
          child->Properties().Set(GridItemContainingBlockRect(), cb);
        }
        *cb = itemCB.GetPhysicalRect(wm, gridCBPhysicalSize);
      }
      // This rect isn't used at all for layout so we use it to optimize
      // away the virtual GetType() call in the callee in most cases.
      // @see nsAbsoluteContainingBlock::Reflow
      nsRect dummyRect(0, 0, VERY_LIKELY_A_GRID_CONTAINER, 0);
      GetAbsoluteContainingBlock()->Reflow(this, pc, *aState.mReflowState,
                                           aStatus, dummyRect, true,
                                           true, true, // XXX could be optimized
                                           &aDesiredSize.mOverflowAreas);
    }
  }
}

void
nsGridContainerFrame::Reflow(nsPresContext*           aPresContext,
                             nsHTMLReflowMetrics&     aDesiredSize,
                             const nsHTMLReflowState& aReflowState,
                             nsReflowStatus&          aStatus)
{
  MarkInReflow();
  DO_GLOBAL_REFLOW_COUNT("nsGridContainerFrame");
  DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);

  if (IsFrameTreeTooDeep(aReflowState, aDesiredSize, aStatus)) {
    return;
  }

#ifdef DEBUG
  SanityCheckAnonymousGridItems();
#endif // DEBUG

  LogicalMargin bp = aReflowState.ComputedLogicalBorderPadding();
  bp.ApplySkipSides(GetLogicalSkipSides());
  const nsStylePosition* stylePos = aReflowState.mStylePosition;
  InitImplicitNamedAreas(stylePos);
  GridReflowState gridReflowState(this, aReflowState);
  mIsNormalFlowInCSSOrder = gridReflowState.mIter.ItemsAreAlreadyInOrder();
  PlaceGridItems(gridReflowState);

  const nscoord computedBSize = aReflowState.ComputedBSize();
  const nscoord computedISize = aReflowState.ComputedISize();
  const WritingMode& wm = gridReflowState.mWM;
  gridReflowState.mIter.Reset();
  CalculateTrackSizes(gridReflowState,
                      LogicalSize(wm, computedISize, computedBSize),
                      nsLayoutUtils::PREF_ISIZE);

  // FIXME bug 1229180: Instead of doing this on every reflow, we should only
  // set these properties if they are needed.
  nsTArray<nscoord> colTrackSizes(gridReflowState.mCols.mSizes.Length());
  for (const TrackSize& sz : gridReflowState.mCols.mSizes) {
    colTrackSizes.AppendElement(sz.mBase);
  }
  Properties().Set(GridColTrackSizes(),
                   new nsTArray<nscoord>(mozilla::Move(colTrackSizes)));
  nsTArray<nscoord> rowTrackSizes(gridReflowState.mRows.mSizes.Length());
  for (const TrackSize& sz : gridReflowState.mRows.mSizes) {
    rowTrackSizes.AppendElement(sz.mBase);
  }
  Properties().Set(GridRowTrackSizes(),
                   new nsTArray<nscoord>(mozilla::Move(rowTrackSizes)));
  
  nscoord bSize = 0;
  if (computedBSize == NS_AUTOHEIGHT) {
    for (uint32_t i = 0; i < mGridRowEnd; ++i) {
      bSize += gridReflowState.mRows.mSizes[i].mBase;
    }
    bSize += gridReflowState.mRows.SumOfGridGaps();
    bSize = NS_CSS_MINMAX(bSize,
                          aReflowState.ComputedMinBSize(),
                          aReflowState.ComputedMaxBSize());
  } else {
    bSize = computedBSize;
  }
  bSize = std::max(bSize - GetConsumedBSize(), 0);
  LogicalSize desiredSize(wm, computedISize + bp.IStartEnd(wm),
                          bSize + bp.BStartEnd(wm));
  aDesiredSize.SetSize(wm, desiredSize);
  aDesiredSize.SetOverflowAreasToDesiredBounds();

  LogicalRect contentArea(wm, bp.IStart(wm), bp.BStart(wm),
                          computedISize, bSize);

  // Apply 'align/justify-content' to the grid.
  gridReflowState.mCols.AlignJustifyContent(aReflowState, contentArea.Size(wm));
  gridReflowState.mRows.AlignJustifyContent(aReflowState, contentArea.Size(wm));

  gridReflowState.mIter.Reset(GridItemCSSOrderIterator::eIncludeAll);
  ReflowChildren(gridReflowState, contentArea, aDesiredSize, aStatus);

  FinishAndStoreOverflow(&aDesiredSize);
  aStatus = NS_FRAME_COMPLETE;
  NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
}

nscoord
nsGridContainerFrame::IntrinsicISize(nsRenderingContext* aRenderingContext,
                                     IntrinsicISizeType  aConstraint)
{
  // Calculate the sum of column sizes under aConstraint.
  // http://dev.w3.org/csswg/css-grid/#intrinsic-sizes
  GridReflowState state(this, *aRenderingContext);
  InitImplicitNamedAreas(state.mGridStyle); // XXX optimize
  PlaceGridItems(state);                    // XXX optimize
  if (mGridColEnd == 0) {
    return 0;
  }
  state.mCols.Initialize(state.mColFunctions, state.mGridStyle->mGridColumnGap,
                         mGridColEnd, NS_UNCONSTRAINEDSIZE);
  state.mIter.Reset();
  state.mCols.CalculateSizes(state, mGridItems, state.mColFunctions,
                             NS_UNCONSTRAINEDSIZE, &GridArea::mCols,
                             aConstraint);
  nscoord length = 0;
  for (const TrackSize& sz : state.mCols.mSizes) {
    length += sz.mBase;
  }
  return length + state.mCols.SumOfGridGaps();
}

nscoord
nsGridContainerFrame::GetMinISize(nsRenderingContext* aRC)
{
  DISPLAY_MIN_WIDTH(this, mCachedMinISize);
  if (mCachedMinISize == NS_INTRINSIC_WIDTH_UNKNOWN) {
    mCachedMinISize = IntrinsicISize(aRC, nsLayoutUtils::MIN_ISIZE);
  }
  return mCachedMinISize;
}

nscoord
nsGridContainerFrame::GetPrefISize(nsRenderingContext* aRC)
{
  DISPLAY_PREF_WIDTH(this, mCachedPrefISize);
  if (mCachedPrefISize == NS_INTRINSIC_WIDTH_UNKNOWN) {
    mCachedPrefISize = IntrinsicISize(aRC, nsLayoutUtils::PREF_ISIZE);
  }
  return mCachedPrefISize;
}

void
nsGridContainerFrame::MarkIntrinsicISizesDirty()
{
  mCachedMinISize = NS_INTRINSIC_WIDTH_UNKNOWN;
  mCachedPrefISize = NS_INTRINSIC_WIDTH_UNKNOWN;
  nsContainerFrame::MarkIntrinsicISizesDirty();
}

nsIAtom*
nsGridContainerFrame::GetType() const
{
  return nsGkAtoms::gridContainerFrame;
}

void
nsGridContainerFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                       const nsRect&           aDirtyRect,
                                       const nsDisplayListSet& aLists)
{
  DisplayBorderBackgroundOutline(aBuilder, aLists);

  // Our children are all grid-level boxes, which behave the same as
  // inline-blocks in painting, so their borders/backgrounds all go on
  // the BlockBorderBackgrounds list.
  // Also, we capture positioned descendants so we can sort them by
  // CSS 'order'.
  nsDisplayList positionedDescendants;
  nsDisplayListSet childLists(aLists.BlockBorderBackgrounds(),
                              aLists.BlockBorderBackgrounds(),
                              aLists.Floats(),
                              aLists.Content(),
                              &positionedDescendants,
                              aLists.Outlines());
  typedef GridItemCSSOrderIterator::OrderState OrderState;
  OrderState order = mIsNormalFlowInCSSOrder ? OrderState::eKnownOrdered
                                             : OrderState::eKnownUnordered;
  GridItemCSSOrderIterator iter(this, kPrincipalList,
                                GridItemCSSOrderIterator::eIncludeAll, order);
  for (; !iter.AtEnd(); iter.Next()) {
    nsIFrame* child = *iter;
    BuildDisplayListForChild(aBuilder, child, aDirtyRect, childLists,
                             ::GetDisplayFlagsForGridItem(child));
  }
  positionedDescendants.SortByCSSOrder(aBuilder);
  aLists.PositionedDescendants()->AppendToTop(&positionedDescendants);
}

#ifdef DEBUG_FRAME_DUMP
nsresult
nsGridContainerFrame::GetFrameName(nsAString& aResult) const
{
  return MakeFrameName(NS_LITERAL_STRING("GridContainer"), aResult);
}
#endif

void
nsGridContainerFrame::CellMap::Fill(const GridArea& aGridArea)
{
  MOZ_ASSERT(aGridArea.IsDefinite());
  MOZ_ASSERT(aGridArea.mRows.mStart < aGridArea.mRows.mEnd);
  MOZ_ASSERT(aGridArea.mCols.mStart < aGridArea.mCols.mEnd);
  const auto numRows = aGridArea.mRows.mEnd;
  const auto numCols = aGridArea.mCols.mEnd;
  mCells.EnsureLengthAtLeast(numRows);
  for (auto i = aGridArea.mRows.mStart; i < numRows; ++i) {
    nsTArray<Cell>& cellsInRow = mCells[i];
    cellsInRow.EnsureLengthAtLeast(numCols);
    for (auto j = aGridArea.mCols.mStart; j < numCols; ++j) {
      cellsInRow[j].mIsOccupied = true;
    }
  }
}

void
nsGridContainerFrame::CellMap::ClearOccupied()
{
  const size_t numRows = mCells.Length();
  for (size_t i = 0; i < numRows; ++i) {
    nsTArray<Cell>& cellsInRow = mCells[i];
    const size_t numCols = cellsInRow.Length();
    for (size_t j = 0; j < numCols; ++j) {
      cellsInRow[j].mIsOccupied = false;
    }
  }
}

#ifdef DEBUG
void
nsGridContainerFrame::CellMap::Dump() const
{
  const size_t numRows = mCells.Length();
  for (size_t i = 0; i < numRows; ++i) {
    const nsTArray<Cell>& cellsInRow = mCells[i];
    const size_t numCols = cellsInRow.Length();
    printf("%lu:\t", (unsigned long)i + 1);
    for (size_t j = 0; j < numCols; ++j) {
      printf(cellsInRow[j].mIsOccupied ? "X " : ". ");
    }
    printf("\n");
  }
}

static bool
FrameWantsToBeInAnonymousGridItem(nsIFrame* aFrame)
{
  // Note: This needs to match the logic in
  // nsCSSFrameConstructor::FrameConstructionItem::NeedsAnonFlexOrGridItem()
  return aFrame->IsFrameOfType(nsIFrame::eLineParticipant);
}

// Debugging method, to let us assert that our anonymous grid items are
// set up correctly -- in particular, we assert:
//  (1) we don't have any inline non-replaced children
//  (2) we don't have any consecutive anonymous grid items
//  (3) we don't have any empty anonymous grid items
//  (4) all children are on the expected child lists
void
nsGridContainerFrame::SanityCheckAnonymousGridItems() const
{
  // XXX handle kOverflowContainersList / kExcessOverflowContainersList
  // when we implement fragmentation?
  ChildListIDs noCheckLists = kAbsoluteList | kFixedList;
  ChildListIDs checkLists = kPrincipalList | kOverflowList;
  for (nsIFrame::ChildListIterator childLists(this);
       !childLists.IsDone(); childLists.Next()) {
    if (!checkLists.Contains(childLists.CurrentID())) {
      MOZ_ASSERT(noCheckLists.Contains(childLists.CurrentID()),
                 "unexpected non-empty child list");
      continue;
    }

    bool prevChildWasAnonGridItem = false;
    nsFrameList children = childLists.CurrentList();
    for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next()) {
      nsIFrame* child = e.get();
      MOZ_ASSERT(!FrameWantsToBeInAnonymousGridItem(child),
                 "frame wants to be inside an anonymous grid item, "
                 "but it isn't");
      if (child->StyleContext()->GetPseudo() ==
            nsCSSAnonBoxes::anonymousGridItem) {
/*
  XXX haven't decided yet whether to reorder children or not.
  XXX If we do, we want this assertion instead of the one below.
        MOZ_ASSERT(!prevChildWasAnonGridItem ||
                   HasAnyStateBits(NS_STATE_GRID_CHILDREN_REORDERED),
                   "two anon grid items in a row (shouldn't happen, unless our "
                   "children have been reordered with the 'order' property)");
*/
        MOZ_ASSERT(!prevChildWasAnonGridItem, "two anon grid items in a row");
        nsIFrame* firstWrappedChild = child->GetFirstPrincipalChild();
        MOZ_ASSERT(firstWrappedChild,
                   "anonymous grid item is empty (shouldn't happen)");
        prevChildWasAnonGridItem = true;
      } else {
        prevChildWasAnonGridItem = false;
      }
    }
  }
}

void
nsGridContainerFrame::TrackSize::Dump() const
{
  printf("mBase=%d mLimit=%d", mBase, mLimit);

  printf(" min:");
  if (mState & eAutoMinSizing) {
    printf("auto ");
  } else if (mState & eMinContentMinSizing) {
    printf("min-content ");
  } else if (mState & eMaxContentMinSizing) {
    printf("max-content ");
  } else if (mState & eFlexMinSizing) {
    printf("flex ");
  }

  printf(" max:");
  if (mState & eAutoMaxSizing) {
    printf("auto ");
  } else if (mState & eMinContentMaxSizing) {
    printf("min-content ");
  } else if (mState & eMaxContentMaxSizing) {
    printf("max-content ");
  } else if (mState & eFlexMaxSizing) {
    printf("flex ");
  }

  if (mState & eFrozen) {
    printf("frozen ");
  }
}

#endif // DEBUG
