diff options
Diffstat (limited to 'layout/generic/nsGridContainerFrame.cpp')
-rw-r--r-- | layout/generic/nsGridContainerFrame.cpp | 7106 |
1 files changed, 7106 insertions, 0 deletions
diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp new file mode 100644 index 000000000..71d5bba21 --- /dev/null +++ b/layout/generic/nsGridContainerFrame.cpp @@ -0,0 +1,7106 @@ +/* -*- 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/CSSAlignUtils.h" +#include "mozilla/dom/GridBinding.h" +#include "mozilla/Function.h" +#include "mozilla/Maybe.h" +#include "mozilla/PodOperations.h" // for PodZero +#include "mozilla/Poison.h" +#include "nsAbsoluteContainingBlock.h" +#include "nsAlgorithm.h" // for clamped() +#include "nsCSSAnonBoxes.h" +#include "nsCSSFrameConstructor.h" +#include "nsDataHashtable.h" +#include "nsDisplayList.h" +#include "nsHashKeys.h" +#include "nsIFrameInlines.h" +#include "nsPresContext.h" +#include "nsReadableUtils.h" +#include "nsRenderingContext.h" +#include "nsRuleNode.h" +#include "nsStyleContext.h" +#include "nsTableWrapperFrame.h" + +#if defined(__clang__) && __clang_major__ == 3 && __clang_minor__ <= 8 +#define CLANG_CRASH_BUG 1 +#endif + +using namespace mozilla; + +typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags; +typedef nsGridContainerFrame::TrackSize TrackSize; +const uint32_t nsGridContainerFrame::kTranslatedMaxLine = + uint32_t(nsStyleGridLine::kMaxLine - nsStyleGridLine::kMinLine); +const uint32_t nsGridContainerFrame::kAutoLine = kTranslatedMaxLine + 3457U; +typedef nsTHashtable< nsPtrHashKey<nsIFrame> > FrameHashtable; +typedef mozilla::CSSAlignUtils::AlignJustifyFlags AlignJustifyFlags; +typedef nsLayoutUtils::IntrinsicISizeType IntrinsicISizeType; + +// https://drafts.csswg.org/css-sizing/#constraints +enum class SizingConstraint +{ + eMinContent, // sizing under min-content constraint + eMaxContent, // sizing under max-content constraint + eNoConstraint // no constraint, used during Reflow +}; + +static void +ReparentFrame(nsIFrame* aFrame, nsContainerFrame* aOldParent, + nsContainerFrame* aNewParent) +{ + NS_ASSERTION(aOldParent == aFrame->GetParent(), + "Parent not consistent with expectations"); + + aFrame->SetParent(aNewParent); + + // When pushing and pulling frames we need to check for whether any + // views need to be reparented + nsContainerFrame::ReparentFrameView(aFrame, aOldParent, aNewParent); +} + +static void +ReparentFrames(nsFrameList& aFrameList, nsContainerFrame* aOldParent, + nsContainerFrame* aNewParent) +{ + for (auto f : aFrameList) { + ReparentFrame(f, aOldParent, aNewParent); + } +} + +static nscoord +ClampToCSSMaxBSize(nscoord aSize, const ReflowInput* aReflowInput) +{ + auto maxSize = aReflowInput->ComputedMaxBSize(); + if (MOZ_UNLIKELY(maxSize != NS_UNCONSTRAINEDSIZE)) { + MOZ_ASSERT(aReflowInput->ComputedMinBSize() <= maxSize); + aSize = std::min(aSize, maxSize); + } + return aSize; +} + +// Same as above and set aStatus INCOMPLETE if aSize wasn't clamped. +// (If we clamp aSize it means our size is less than the break point, +// i.e. we're effectively breaking in our overflow, so we should leave +// aStatus as is (it will likely be set to OVERFLOW_INCOMPLETE later)). +static nscoord +ClampToCSSMaxBSize(nscoord aSize, const ReflowInput* aReflowInput, + nsReflowStatus* aStatus) +{ + auto maxSize = aReflowInput->ComputedMaxBSize(); + if (MOZ_UNLIKELY(maxSize != NS_UNCONSTRAINEDSIZE)) { + MOZ_ASSERT(aReflowInput->ComputedMinBSize() <= maxSize); + if (aSize < maxSize) { + NS_FRAME_SET_INCOMPLETE(*aStatus); + } else { + aSize = maxSize; + } + } else { + NS_FRAME_SET_INCOMPLETE(*aStatus); + } + return aSize; +} + +static bool +IsPercentOfIndefiniteSize(const nsStyleCoord& aCoord, nscoord aPercentBasis) +{ + return aPercentBasis == NS_UNCONSTRAINEDSIZE && aCoord.HasPercent(); +} + +static nscoord +ResolveToDefiniteSize(const nsStyleCoord& aCoord, nscoord aPercentBasis) +{ + MOZ_ASSERT(aCoord.IsCoordPercentCalcUnit()); + if (::IsPercentOfIndefiniteSize(aCoord, aPercentBasis)) { + return nscoord(0); + } + return std::max(nscoord(0), + nsRuleNode::ComputeCoordPercentCalc(aCoord, aPercentBasis)); +} + +static bool +GetPercentSizeParts(const nsStyleCoord& aCoord, nscoord* aLength, float* aPercent) +{ + switch (aCoord.GetUnit()) { + case eStyleUnit_Percent: + *aLength = 0; + *aPercent = aCoord.GetPercentValue(); + return true; + case eStyleUnit_Calc: { + nsStyleCoord::Calc* calc = aCoord.GetCalcValue(); + *aLength = calc->mLength; + *aPercent = calc->mPercent; + return true; + } + default: + return false; + } +} + +static void +ResolvePercentSizeParts(const nsStyleCoord& aCoord, nscoord aPercentBasis, + nscoord* aLength, float* aPercent) +{ + MOZ_ASSERT(aCoord.IsCoordPercentCalcUnit()); + if (aPercentBasis != NS_UNCONSTRAINEDSIZE) { + *aLength = std::max(nscoord(0), + nsRuleNode::ComputeCoordPercentCalc(aCoord, + aPercentBasis)); + *aPercent = 0.0f; + return; + } + if (!GetPercentSizeParts(aCoord, aLength, aPercent)) { + *aLength = aCoord.ToLength(); + *aPercent = 0.0f; + } +} + +// Synthesize a baseline from a border box. For an alphabetical baseline +// this is the end edge of the border box. For a central baseline it's +// the center of the border box. +// https://drafts.csswg.org/css-align-3/#synthesize-baselines +// For a 'first baseline' the measure is from the border-box start edge and +// for a 'last baseline' the measure is from the border-box end edge. +static nscoord +SynthesizeBaselineFromBorderBox(BaselineSharingGroup aGroup, + WritingMode aWM, + nscoord aBorderBoxSize) +{ + if (aGroup == BaselineSharingGroup::eFirst) { + return aWM.IsAlphabeticalBaseline() ? aBorderBoxSize : aBorderBoxSize / 2; + } + MOZ_ASSERT(aGroup == BaselineSharingGroup::eLast); + // Round up for central baseline offset, to be consistent with eFirst. + return aWM.IsAlphabeticalBaseline() ? 0 : + (aBorderBoxSize / 2) + (aBorderBoxSize % 2); +} + +enum class GridLineSide +{ + eBeforeGridGap, + eAfterGridGap, +}; + +struct nsGridContainerFrame::TrackSize +{ + enum StateBits : uint16_t { + eAutoMinSizing = 0x1, + eMinContentMinSizing = 0x2, + eMaxContentMinSizing = 0x4, + eMinOrMaxContentMinSizing = eMinContentMinSizing | eMaxContentMinSizing, + eIntrinsicMinSizing = eMinOrMaxContentMinSizing | eAutoMinSizing, + // 0x8 is unused, feel free to take it! + eAutoMaxSizing = 0x10, + eMinContentMaxSizing = 0x20, + eMaxContentMaxSizing = 0x40, + eAutoOrMaxContentMaxSizing = eAutoMaxSizing | eMaxContentMaxSizing, + eIntrinsicMaxSizing = eAutoOrMaxContentMaxSizing | eMinContentMaxSizing, + eFlexMaxSizing = 0x80, + eFrozen = 0x100, + eSkipGrowUnlimited1 = 0x200, + eSkipGrowUnlimited2 = 0x400, + eSkipGrowUnlimited = eSkipGrowUnlimited1 | eSkipGrowUnlimited2, + eBreakBefore = 0x800, + eFitContent = 0x1000, + }; + + StateBits Initialize(nscoord aPercentageBasis, + const nsStyleCoord& aMinCoord, + const nsStyleCoord& aMaxCoord); + bool IsFrozen() const { return mState & eFrozen; } +#ifdef DEBUG + void Dump() const; +#endif + + static bool IsMinContent(const nsStyleCoord& aCoord) + { + return aCoord.GetUnit() == eStyleUnit_Enumerated && + aCoord.GetIntValue() == NS_STYLE_GRID_TRACK_BREADTH_MIN_CONTENT; + } + static bool IsDefiniteMaxSizing(StateBits aStateBits) + { + return (aStateBits & (eIntrinsicMaxSizing | eFlexMaxSizing)) == 0; + } + + nscoord mBase; + nscoord mLimit; + nscoord mPosition; // zero until we apply 'align/justify-content' + // mBaselineSubtreeSize is the size of a baseline-aligned subtree within + // this track. One subtree per baseline-sharing group (per track). + nscoord mBaselineSubtreeSize[2]; + StateBits mState; +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(TrackSize::StateBits) + +namespace mozilla { +template <> +struct IsPod<nsGridContainerFrame::TrackSize> : TrueType {}; +} + +TrackSize::StateBits +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"); + auto minSizeUnit = aMinCoord.GetUnit(); + auto maxSizeUnit = aMaxCoord.GetUnit(); + if (minSizeUnit == eStyleUnit_None) { + // This track is sized using fit-content(size) (represented in style system + // with minCoord=None,maxCoord=size). In layout, fit-content(size) behaves + // as minmax(auto, max-content), with 'size' as an additional upper-bound. + mState = eFitContent; + minSizeUnit = eStyleUnit_Auto; + maxSizeUnit = eStyleUnit_Enumerated; // triggers max-content sizing below + } + if (::IsPercentOfIndefiniteSize(aMinCoord, aPercentageBasis)) { + // https://drafts.csswg.org/css-grid/#valdef-grid-template-columns-percentage + // "If the inline or block size of the grid container is indefinite, + // <percentage> values relative to that size are treated as 'auto'." + minSizeUnit = eStyleUnit_Auto; + } + if (::IsPercentOfIndefiniteSize(aMaxCoord, aPercentageBasis)) { + maxSizeUnit = eStyleUnit_Auto; + } + // http://dev.w3.org/csswg/css-grid/#algo-init + switch (minSizeUnit) { + case eStyleUnit_Auto: + mState |= eAutoMinSizing; + break; + case eStyleUnit_Enumerated: + mState |= IsMinContent(aMinCoord) ? eMinContentMinSizing + : eMaxContentMinSizing; + break; + default: + MOZ_ASSERT(minSizeUnit != eStyleUnit_FlexFraction, + "<flex> min-sizing is invalid as a track size"); + mBase = ::ResolveToDefiniteSize(aMinCoord, aPercentageBasis); + } + switch (maxSizeUnit) { + 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 = ::ResolveToDefiniteSize(aMaxCoord, aPercentageBasis); + if (mLimit < mBase) { + mLimit = mBase; + } + } + + mBaselineSubtreeSize[BaselineSharingGroup::eFirst] = nscoord(0); + mBaselineSubtreeSize[BaselineSharingGroup::eLast] = nscoord(0); + return mState; +} + +/** + * Is aFrame1 a prev-continuation of aFrame2? + */ +static bool +IsPrevContinuationOf(nsIFrame* aFrame1, nsIFrame* aFrame2) +{ + nsIFrame* prev = aFrame2; + while ((prev = prev->GetPrevContinuation())) { + if (prev == aFrame1) { + return true; + } + } + return false; +} + +/** + * Moves all frames from aSrc into aDest such that the resulting aDest + * is still sorted in document content order and continuation order. + * Precondition: both |aSrc| and |aDest| must be sorted to begin with. + * @param aCommonAncestor a hint for nsLayoutUtils::CompareTreePosition + */ +static void +MergeSortedFrameLists(nsFrameList& aDest, nsFrameList& aSrc, + nsIContent* aCommonAncestor) +{ + nsIFrame* dest = aDest.FirstChild(); + for (nsIFrame* src = aSrc.FirstChild(); src; ) { + if (!dest) { + aDest.AppendFrames(nullptr, aSrc); + break; + } + nsIContent* srcContent = src->GetContent(); + nsIContent* destContent = dest->GetContent(); + int32_t result = nsLayoutUtils::CompareTreePosition(srcContent, + destContent, + aCommonAncestor); + if (MOZ_UNLIKELY(result == 0)) { + // NOTE: we get here when comparing ::before/::after for the same element. + if (MOZ_UNLIKELY(srcContent->IsGeneratedContentContainerForBefore())) { + if (MOZ_LIKELY(!destContent->IsGeneratedContentContainerForBefore()) || + ::IsPrevContinuationOf(src, dest)) { + result = -1; + } + } else if (MOZ_UNLIKELY(srcContent->IsGeneratedContentContainerForAfter())) { + if (MOZ_UNLIKELY(destContent->IsGeneratedContentContainerForAfter()) && + ::IsPrevContinuationOf(src, dest)) { + result = -1; + } + } else if (::IsPrevContinuationOf(src, dest)) { + result = -1; + } + } + if (result < 0) { + // src should come before dest + nsIFrame* next = src->GetNextSibling(); + aSrc.RemoveFrame(src); + aDest.InsertFrame(nullptr, dest->GetPrevSibling(), src); + src = next; + } else { + dest = dest->GetNextSibling(); + } + } + MOZ_ASSERT(aSrc.IsEmpty()); +} + +static void +MergeSortedFrameListsFor(nsFrameList& aDest, nsFrameList& aSrc, + nsContainerFrame* aParent) +{ + MergeSortedFrameLists(aDest, aSrc, aParent->GetContent()); +} + +template<typename Iterator> +class nsGridContainerFrame::GridItemCSSOrderIteratorT +{ +public: + enum OrderState { eUnknownOrder, eKnownOrdered, eKnownUnordered }; + enum ChildFilter { eSkipPlaceholders, eIncludeAll }; + GridItemCSSOrderIteratorT(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 (auto child : mChildren) { + ++count; + int32_t order = child->StylePosition()->mOrder; + if (order < maxOrder) { + isOrdered = false; + break; + } + maxOrder = order; + } + } + if (isOrdered) { + mIter.emplace(begin(mChildren)); + mIterEnd.emplace(end(mChildren)); + } else { + count *= 2; // XXX somewhat arbitrary estimate for now... + mArray.emplace(count); + for (Iterator i(begin(mChildren)), iEnd(end(mChildren)); i != iEnd; ++i) { + mArray->AppendElement(*i); + } + // XXX replace this with nsTArray::StableSort when bug 1147091 is fixed. + std::stable_sort(mArray->begin(), mArray->end(), CSSOrderComparator); + } + + if (mSkipPlaceholders) { + SkipPlaceholders(); + } + } + ~GridItemCSSOrderIteratorT() + { + MOZ_ASSERT(IsForward() == mGridItemCount.isNothing()); + } + + bool IsForward() const; + Iterator begin(const nsFrameList& aList); + Iterator end(const nsFrameList& aList); + + nsIFrame* operator*() const + { + MOZ_ASSERT(!AtEnd()); + if (mIter.isSome()) { + return **mIter; + } + 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"); + MOZ_ASSERT(IsForward() || mGridItemIndex < *mGridItemCount, + "Returning an out-of-range mGridItemIndex..."); + return mGridItemIndex; + } + + void SetGridItemCount(size_t aGridItemCount) + { +#ifndef CLANG_CRASH_BUG + MOZ_ASSERT(mIter.isSome() || mArray->Length() == aGridItemCount, + "grid item count mismatch"); +#endif + mGridItemCount.emplace(aGridItemCount); + // Note: it's OK if mGridItemIndex underflows -- GridItemIndex() + // will not be called unless there is at least one item. + mGridItemIndex = IsForward() ? 0 : *mGridItemCount - 1; + } + + /** + * Skip over placeholder children. + */ + void SkipPlaceholders() + { + if (mIter.isSome()) { + for (; *mIter != *mIterEnd; ++*mIter) { + nsIFrame* child = **mIter; + 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 + { +#ifndef CLANG_CRASH_BUG + // Clang 3.6.2 crashes when compiling this assertion: + MOZ_ASSERT(mIter.isSome() || mArrayIndex <= mArray->Length()); +#endif + return mIter ? (*mIter == *mIterEnd) : 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) { + IsForward() ? ++mGridItemIndex : --mGridItemIndex; + } + if (mIter.isSome()) { + ++*mIter; + } else { + ++mArrayIndex; + } + if (mSkipPlaceholders) { + SkipPlaceholders(); + } + } + + void Reset(ChildFilter aFilter = eSkipPlaceholders) + { + if (mIter.isSome()) { + mIter.reset(); + mIter.emplace(begin(mChildren)); + mIterEnd.reset(); + mIterEnd.emplace(end(mChildren)); + } else { + mArrayIndex = 0; + } + mGridItemIndex = IsForward() ? 0 : *mGridItemCount - 1; + mSkipPlaceholders = aFilter == eSkipPlaceholders; + if (mSkipPlaceholders) { + SkipPlaceholders(); + } + } + + bool IsValid() const { return mIter.isSome() || mArray.isSome(); } + + void Invalidate() + { + mIter.reset(); + mArray.reset(); + mozWritePoison(&mChildren, sizeof(mChildren)); + } + + bool ItemsAreAlreadyInOrder() const { return mIter.isSome(); } + + static bool CSSOrderComparator(nsIFrame* const& a, nsIFrame* const& b); +private: + nsFrameList mChildren; + // Used if child list is already in ascending 'order'. + Maybe<Iterator> mIter; + Maybe<Iterator> mIterEnd; + // Used if child list is *not* in ascending 'order'. + // This array is pre-sorted in reverse order for a reverse iterator. + Maybe<nsTArray<nsIFrame*>> mArray; + size_t mArrayIndex; + // The index of the current grid item (placeholders excluded). + size_t mGridItemIndex; + // The number of grid items (placeholders excluded). + // It's only initialized and used in a reverse iterator. + Maybe<size_t> mGridItemCount; + // Skip placeholder children in the iteration? + bool mSkipPlaceholders; +#ifdef DEBUG + nsIFrame* mGridContainer; + nsIFrame::ChildListID mListID; +#endif +}; + +using GridItemCSSOrderIterator = nsGridContainerFrame::GridItemCSSOrderIterator; +using ReverseGridItemCSSOrderIterator = nsGridContainerFrame::ReverseGridItemCSSOrderIterator; + +template<> +bool +GridItemCSSOrderIterator::CSSOrderComparator(nsIFrame* const& a, + nsIFrame* const& b) +{ return a->StylePosition()->mOrder < b->StylePosition()->mOrder; } + +template<> +bool +GridItemCSSOrderIterator::IsForward() const { return true; } + +template<> +nsFrameList::iterator +GridItemCSSOrderIterator::begin(const nsFrameList& aList) +{ return aList.begin(); } + +template<> +nsFrameList::iterator GridItemCSSOrderIterator::end(const nsFrameList& aList) +{ return aList.end(); } + +template<> +bool +ReverseGridItemCSSOrderIterator::CSSOrderComparator(nsIFrame* const& a, + nsIFrame* const& b) +{ return a->StylePosition()->mOrder > b->StylePosition()->mOrder; } + +template<> +bool +ReverseGridItemCSSOrderIterator::IsForward() const +{ return false; } + +template<> +nsFrameList::reverse_iterator +ReverseGridItemCSSOrderIterator::begin(const nsFrameList& aList) +{ return aList.rbegin(); } + +template<> +nsFrameList::reverse_iterator +ReverseGridItemCSSOrderIterator::end(const nsFrameList& aList) +{ return aList.rend(); } + +/** + * A LineRange can be definite or auto - when it's definite it represents + * a consecutive set of tracks between a starting line and an ending line. + * Before it's definite it can also represent an auto position with a span, + * where mStart == kAutoLine and mEnd is the (non-zero positive) span. + * For normal-flow items, the invariant mStart < mEnd holds when both + * lines are definite. + * + * For abs.pos. grid items, mStart and mEnd may both be kAutoLine, meaning + * "attach this side to the grid container containing block edge". + * Additionally, mStart <= mEnd holds when both are definite (non-kAutoLine), + * i.e. the invariant is slightly relaxed compared to normal flow items. + */ +struct nsGridContainerFrame::LineRange +{ + LineRange(int32_t aStart, int32_t aEnd) + : mUntranslatedStart(aStart), mUntranslatedEnd(aEnd) + { +#ifdef DEBUG + if (!IsAutoAuto()) { + if (IsAuto()) { + MOZ_ASSERT(aEnd >= nsStyleGridLine::kMinLine && + aEnd <= nsStyleGridLine::kMaxLine, "invalid span"); + } else { + MOZ_ASSERT(aStart >= nsStyleGridLine::kMinLine && + aStart <= nsStyleGridLine::kMaxLine, "invalid start line"); + MOZ_ASSERT(aEnd == int32_t(kAutoLine) || + (aEnd >= nsStyleGridLine::kMinLine && + aEnd <= nsStyleGridLine::kMaxLine), "invalid end line"); + } + } +#endif + } + bool IsAutoAuto() const { return mStart == kAutoLine && mEnd == kAutoLine; } + bool IsAuto() const { return mStart == kAutoLine; } + bool IsDefinite() const { return mStart != kAutoLine; } + uint32_t Extent() const + { + MOZ_ASSERT(mEnd != kAutoLine, "Extent is undefined for abs.pos. 'auto'"); + if (IsAuto()) { + MOZ_ASSERT(mEnd >= 1 && mEnd < uint32_t(nsStyleGridLine::kMaxLine), + "invalid span"); + return mEnd; + } + return mEnd - mStart; + } + /** + * Resolve this auto range to start at aStart, making it definite. + * Precondition: this range IsAuto() + */ + void ResolveAutoPosition(uint32_t aStart, uint32_t aExplicitGridOffset) + { + MOZ_ASSERT(IsAuto(), "Why call me?"); + mStart = aStart; + mEnd += aStart; + // Clamping to where kMaxLine is in the explicit grid, per + // http://dev.w3.org/csswg/css-grid/#overlarge-grids : + uint32_t translatedMax = aExplicitGridOffset + nsStyleGridLine::kMaxLine; + if (MOZ_UNLIKELY(mStart >= translatedMax)) { + mEnd = translatedMax; + mStart = mEnd - 1; + } else if (MOZ_UNLIKELY(mEnd > translatedMax)) { + mEnd = translatedMax; + } + } + /** + * Translate the lines to account for (empty) removed tracks. This method + * is only for grid items and should only be called after placement. + * aNumRemovedTracks contains a count for each line in the grid how many + * tracks were removed between the start of the grid and that line. + */ + void AdjustForRemovedTracks(const nsTArray<uint32_t>& aNumRemovedTracks) + { + MOZ_ASSERT(mStart != kAutoLine, "invalid resolved line for a grid item"); + MOZ_ASSERT(mEnd != kAutoLine, "invalid resolved line for a grid item"); + uint32_t numRemovedTracks = aNumRemovedTracks[mStart]; + MOZ_ASSERT(numRemovedTracks == aNumRemovedTracks[mEnd], + "tracks that a grid item spans can't be removed"); + mStart -= numRemovedTracks; + mEnd -= numRemovedTracks; + } + /** + * Translate the lines to account for (empty) removed tracks. This method + * is only for abs.pos. children and should only be called after placement. + * Same as for in-flow items, but we don't touch 'auto' lines here and we + * also need to adjust areas that span into the removed tracks. + */ + void AdjustAbsPosForRemovedTracks(const nsTArray<uint32_t>& aNumRemovedTracks) + { + if (mStart != nsGridContainerFrame::kAutoLine) { + mStart -= aNumRemovedTracks[mStart]; + } + if (mEnd != nsGridContainerFrame::kAutoLine) { + MOZ_ASSERT(mStart == nsGridContainerFrame::kAutoLine || + mEnd > mStart, "invalid line range"); + mEnd -= aNumRemovedTracks[mEnd]; + } + if (mStart == mEnd) { + mEnd = nsGridContainerFrame::kAutoLine; + } + } + /** + * Return the contribution of this line range for step 2 in + * http://dev.w3.org/csswg/css-grid/#auto-placement-algo + */ + uint32_t HypotheticalEnd() const { return mEnd; } + /** + * Given an array of track sizes, return the starting position and length + * of the tracks in this line range. + */ + void ToPositionAndLength(const nsTArray<TrackSize>& aTrackSizes, + nscoord* aPos, nscoord* aLength) const; + /** + * Given an array of track sizes, return the length of the tracks in this + * line range. + */ + nscoord ToLength(const nsTArray<TrackSize>& aTrackSizes) const; + /** + * Given an array of track sizes and a grid origin coordinate, adjust the + * abs.pos. containing block along an axis given by aPos and aLength. + * aPos and aLength should already be initialized to the grid container + * containing block for this axis before calling this method. + */ + void ToPositionAndLengthForAbsPos(const Tracks& aTracks, + nscoord aGridOrigin, + nscoord* aPos, nscoord* aLength) const; + + /** + * @note We'll use the signed member while resolving definite positions + * to line numbers (1-based), which may become negative for implicit lines + * to the top/left of the explicit grid. PlaceGridItems() then translates + * the whole grid to a 0,0 origin and we'll use the unsigned member from + * there on. + */ + union { + uint32_t mStart; + int32_t mUntranslatedStart; + }; + union { + uint32_t mEnd; + int32_t mUntranslatedEnd; + }; +protected: + LineRange() {} +}; + +/** + * Helper class to construct a LineRange from translated lines. + * The ctor only accepts translated definite line numbers. + */ +struct nsGridContainerFrame::TranslatedLineRange : public LineRange +{ + TranslatedLineRange(uint32_t aStart, uint32_t aEnd) + { + MOZ_ASSERT(aStart < aEnd && aEnd <= kTranslatedMaxLine); + mStart = aStart; + mEnd = aEnd; + } +}; + +/** + * A GridArea is the area in the grid for a grid item. + * The area is represented by two LineRanges, both of which can be auto + * (@see LineRange) in intermediate steps while the item is being placed. + * @see PlaceGridItems + */ +struct nsGridContainerFrame::GridArea +{ + GridArea(const LineRange& aCols, const LineRange& aRows) + : mCols(aCols), mRows(aRows) {} + bool IsDefinite() const { return mCols.IsDefinite() && mRows.IsDefinite(); } + LineRange mCols; + LineRange mRows; +}; + +struct nsGridContainerFrame::GridItemInfo +{ + /** + * Item state per axis. + */ + enum StateBits : uint8_t { + eIsFlexing = 0x1, // does the item span a flex track? + eFirstBaseline = 0x2, // participate in 'first baseline' alignment? + // ditto 'last baseline', mutually exclusive w. eFirstBaseline + eLastBaseline = 0x4, + eIsBaselineAligned = eFirstBaseline | eLastBaseline, + // One of e[Self|Content]Baseline is set when eIsBaselineAligned is true + eSelfBaseline = 0x8, // is it *-self:[last ]baseline alignment? + // Ditto *-content:[last ]baseline. Mutually exclusive w. eSelfBaseline. + eContentBaseline = 0x10, + eAllBaselineBits = eIsBaselineAligned | eSelfBaseline | eContentBaseline, + // Clamp per https://drafts.csswg.org/css-grid/#min-size-auto + eClampMarginBoxMinSize = 0x20, + }; + + explicit GridItemInfo(nsIFrame* aFrame, + const GridArea& aArea) + : mFrame(aFrame) + , mArea(aArea) + { + mState[eLogicalAxisBlock] = StateBits(0); + mState[eLogicalAxisInline] = StateBits(0); + mBaselineOffset[eLogicalAxisBlock] = nscoord(0); + mBaselineOffset[eLogicalAxisInline] = nscoord(0); + } + + /** + * If the item is [align|justify]-self:[last ]baseline aligned in the given + * axis then set aBaselineOffset to the baseline offset and return aAlign. + * Otherwise, return a fallback alignment. + */ + uint8_t GetSelfBaseline(uint8_t aAlign, LogicalAxis aAxis, + nscoord* aBaselineOffset) const + { + MOZ_ASSERT(aAlign == NS_STYLE_ALIGN_BASELINE || + aAlign == NS_STYLE_ALIGN_LAST_BASELINE); + if (!(mState[aAxis] & eSelfBaseline)) { + return aAlign == NS_STYLE_ALIGN_BASELINE ? NS_STYLE_ALIGN_SELF_START + : NS_STYLE_ALIGN_SELF_END; + } + *aBaselineOffset = mBaselineOffset[aAxis]; + return aAlign; + } + + // Return true if we should we clamp this item's Automatic Minimum Size. + // https://drafts.csswg.org/css-grid/#min-size-auto + bool ShouldClampMinSize(WritingMode aContainerWM, + LogicalAxis aContainerAxis, + nscoord aPercentageBasis) const + { + const auto pos = mFrame->StylePosition(); + const auto& size = aContainerAxis == eLogicalAxisInline ? + pos->ISize(aContainerWM) : pos->BSize(aContainerWM); + // NOTE: if we have a definite or 'max-content' size then our automatic + // minimum size can't affect our size. Excluding these simplifies applying + // the clamping in the right cases later. + if (size.GetUnit() == eStyleUnit_Auto || + ::IsPercentOfIndefiniteSize(size, aPercentageBasis) || // same as 'auto' + (size.GetUnit() == eStyleUnit_Enumerated && + size.GetIntValue() != NS_STYLE_WIDTH_MAX_CONTENT)) { + const auto& minSize = aContainerAxis == eLogicalAxisInline ? + pos->MinISize(aContainerWM) : pos->MinBSize(aContainerWM); + return minSize.GetUnit() == eStyleUnit_Auto && + mFrame->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE; + } + return false; + } + +#ifdef DEBUG + void Dump() const; +#endif + + static bool IsStartRowLessThan(const GridItemInfo* a, const GridItemInfo* b) + { + return a->mArea.mRows.mStart < b->mArea.mRows.mStart; + } + + nsIFrame* const mFrame; + GridArea mArea; + // Offset from the margin edge to the baseline (LogicalAxis index). It's from + // the start edge when eFirstBaseline is set, end edge otherwise. It's mutable + // since we update the value fairly late (just before reflowing the item). + mutable nscoord mBaselineOffset[2]; + mutable StateBits mState[2]; // state bits per axis (LogicalAxis index) + static_assert(mozilla::eLogicalAxisBlock == 0, "unexpected index value"); + static_assert(mozilla::eLogicalAxisInline == 1, "unexpected index value"); +}; + +using GridItemInfo = nsGridContainerFrame::GridItemInfo; +using ItemState = GridItemInfo::StateBits; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ItemState) + +#ifdef DEBUG +void +nsGridContainerFrame::GridItemInfo::Dump() const +{ + auto Dump1 = [this] (const char* aMsg, LogicalAxis aAxis) { + auto state = mState[aAxis]; + if (!state) { + return; + } + printf("%s", aMsg); + if (state & ItemState::eIsFlexing) { + printf("flexing "); + } + if (state & ItemState::eFirstBaseline) { + printf("first baseline %s-alignment ", + (state & ItemState::eSelfBaseline) ? "self" : "content"); + } + if (state & ItemState::eLastBaseline) { + printf("last baseline %s-alignment ", + (state & ItemState::eSelfBaseline) ? "self" : "content"); + } + if (state & ItemState::eIsBaselineAligned) { + printf("%.2fpx", NSAppUnitsToFloatPixels(mBaselineOffset[aAxis], + AppUnitsPerCSSPixel())); + } + printf("\n"); + }; + printf("grid-row: %d %d\n", mArea.mRows.mStart, mArea.mRows.mEnd); + Dump1(" grid block-axis: ", eLogicalAxisBlock); + printf("grid-column: %d %d\n", mArea.mCols.mStart, mArea.mCols.mEnd); + Dump1(" grid inline-axis: ", eLogicalAxisInline); +} +#endif + +/** + * Utility class to find line names. It provides an interface to lookup line + * names with a dynamic number of repeat(auto-fill/fit) tracks taken into + * account. + */ +class MOZ_STACK_CLASS nsGridContainerFrame::LineNameMap +{ +public: + /** + * Create a LineNameMap. + * @param aGridTemplate is the grid-template-rows/columns data for this axis + * @param aNumRepeatTracks the number of actual tracks associated with + * a repeat(auto-fill/fit) track (zero or more), or zero if there is no + * specified repeat(auto-fill/fit) track + */ + LineNameMap(const nsStyleGridTemplate& aGridTemplate, + uint32_t aNumRepeatTracks) + : mLineNameLists(aGridTemplate.mLineNameLists) + , mRepeatAutoLineNameListBefore(aGridTemplate.mRepeatAutoLineNameListBefore) + , mRepeatAutoLineNameListAfter(aGridTemplate.mRepeatAutoLineNameListAfter) + , mRepeatAutoStart(aGridTemplate.HasRepeatAuto() ? + aGridTemplate.mRepeatAutoIndex : 0) + , mRepeatAutoEnd(mRepeatAutoStart + aNumRepeatTracks) + , mRepeatEndDelta(aGridTemplate.HasRepeatAuto() ? + int32_t(aNumRepeatTracks) - 1 : + 0) + , mTemplateLinesEnd(mLineNameLists.Length() + mRepeatEndDelta) + , mHasRepeatAuto(aGridTemplate.HasRepeatAuto()) + { + MOZ_ASSERT(mHasRepeatAuto || aNumRepeatTracks == 0); + MOZ_ASSERT(mRepeatAutoStart <= mLineNameLists.Length()); + MOZ_ASSERT(!mHasRepeatAuto || mLineNameLists.Length() >= 2); + } + + /** + * Find the aNth occurrence of aName, searching forward if aNth is positive, + * and in reverse if aNth is negative (aNth == 0 is invalid), starting from + * aFromIndex (not inclusive), and return a 1-based 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). + * + * E.g. to search for "A 2" forward from the start of the grid: aName is "A" + * aNth is 2 and aFromIndex is zero. To search for "A -2", aNth is -2 and + * aFromIndex is ExplicitGridEnd + 1 (which is the line "before" the last + * line when we're searching in reverse). For "span A 2", aNth is 2 when + * used on a grid-[row|column]-end property and -2 for a *-start property, + * and aFromIndex is the line (which we should skip) on the opposite property. + */ + uint32_t FindNamedLine(const nsString& aName, int32_t* aNth, + uint32_t aFromIndex, uint32_t aImplicitLine) const + { + MOZ_ASSERT(aNth && *aNth != 0); + if (*aNth > 0) { + return FindLine(aName, aNth, aFromIndex, aImplicitLine); + } + int32_t nth = -*aNth; + int32_t line = RFindLine(aName, &nth, aFromIndex, aImplicitLine); + *aNth = -nth; + return line; + } + +private: + /** + * @see FindNamedLine, this function searches forward. + */ + uint32_t FindLine(const nsString& aName, int32_t* aNth, + uint32_t aFromIndex, uint32_t aImplicitLine) const + { + MOZ_ASSERT(aNth && *aNth > 0); + int32_t nth = *aNth; + const uint32_t end = mTemplateLinesEnd; + uint32_t line; + uint32_t i = aFromIndex; + for (; i < end; i = line) { + line = i + 1; + if (line == aImplicitLine || Contains(i, 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 FindNamedLine, this function searches in reverse. + */ + uint32_t RFindLine(const nsString& aName, int32_t* aNth, + uint32_t aFromIndex, uint32_t aImplicitLine) const + { + MOZ_ASSERT(aNth && *aNth > 0); + if (MOZ_UNLIKELY(aFromIndex == 0)) { + return 0; // There are no named lines beyond the start of the explicit grid. + } + --aFromIndex; // (shift aFromIndex so we can treat it as inclusive) + int32_t nth = *aNth; + // The implicit line may be beyond the explicit grid so we match + // this line first if it's within the mTemplateLinesEnd..aFromIndex range. + const uint32_t end = mTemplateLinesEnd; + if (aImplicitLine > end && aImplicitLine < aFromIndex) { + if (--nth == 0) { + return aImplicitLine; + } + } + for (uint32_t i = std::min(aFromIndex, end); i; --i) { + if (i == aImplicitLine || Contains(i - 1, aName)) { + if (--nth == 0) { + return i; + } + } + } + MOZ_ASSERT(nth > 0, "should have returned a valid line above already"); + *aNth = nth; + return 0; + } + + // Return true if aName exists at aIndex. + bool Contains(uint32_t aIndex, const nsString& aName) const + { + if (!mHasRepeatAuto) { + return mLineNameLists[aIndex].Contains(aName); + } + if (aIndex < mRepeatAutoEnd && aIndex >= mRepeatAutoStart && + mRepeatAutoLineNameListBefore.Contains(aName)) { + return true; + } + if (aIndex <= mRepeatAutoEnd && aIndex > mRepeatAutoStart && + mRepeatAutoLineNameListAfter.Contains(aName)) { + return true; + } + if (aIndex <= mRepeatAutoStart) { + return mLineNameLists[aIndex].Contains(aName) || + (aIndex == mRepeatAutoEnd && + mLineNameLists[aIndex + 1].Contains(aName)); + } + return aIndex >= mRepeatAutoEnd && + mLineNameLists[aIndex - mRepeatEndDelta].Contains(aName); + } + + // Some style data references, for easy access. + const nsTArray<nsTArray<nsString>>& mLineNameLists; + const nsTArray<nsString>& mRepeatAutoLineNameListBefore; + const nsTArray<nsString>& mRepeatAutoLineNameListAfter; + // The index of the repeat(auto-fill/fit) track, or zero if there is none. + const uint32_t mRepeatAutoStart; + // The (hypothetical) index of the last such repeat() track. + const uint32_t mRepeatAutoEnd; + // The difference between mTemplateLinesEnd and mLineNameLists.Length(). + const int32_t mRepeatEndDelta; + // The end of the line name lists with repeat(auto-fill/fit) tracks accounted + // for. It is equal to mLineNameLists.Length() when a repeat() track + // generates one track (making mRepeatEndDelta == 0). + const uint32_t mTemplateLinesEnd; + // True if there is a specified repeat(auto-fill/fit) track. + const bool mHasRepeatAuto; +}; + +/** + * Encapsulates CSS track-sizing functions. + */ +struct nsGridContainerFrame::TrackSizingFunctions +{ + TrackSizingFunctions(const nsStyleGridTemplate& aGridTemplate, + const nsStyleCoord& aAutoMinSizing, + const nsStyleCoord& aAutoMaxSizing) + : mMinSizingFunctions(aGridTemplate.mMinTrackSizingFunctions) + , mMaxSizingFunctions(aGridTemplate.mMaxTrackSizingFunctions) + , mAutoMinSizing(aAutoMinSizing) + , mAutoMaxSizing(aAutoMaxSizing) + , mExplicitGridOffset(0) + , mRepeatAutoStart(aGridTemplate.HasRepeatAuto() ? + aGridTemplate.mRepeatAutoIndex : 0) + , mRepeatAutoEnd(mRepeatAutoStart) + , mRepeatEndDelta(0) + , mHasRepeatAuto(aGridTemplate.HasRepeatAuto()) + { + MOZ_ASSERT(mMinSizingFunctions.Length() == mMaxSizingFunctions.Length()); + MOZ_ASSERT(!mHasRepeatAuto || + (mMinSizingFunctions.Length() >= 1 && + mRepeatAutoStart < mMinSizingFunctions.Length())); + } + + /** + * Initialize the number of auto-fill/fit tracks to use and return that. + * (zero if no auto-fill/fit track was specified) + */ + uint32_t InitRepeatTracks(const nsStyleCoord& aGridGap, nscoord aMinSize, + nscoord aSize, nscoord aMaxSize) + { + uint32_t repeatTracks = + CalculateRepeatFillCount(aGridGap, aMinSize, aSize, aMaxSize); + SetNumRepeatTracks(repeatTracks); + // Blank out the removed flags for each of these tracks. + mRemovedRepeatTracks.SetLength(repeatTracks); + for (auto& track : mRemovedRepeatTracks) { + track = false; + } + return repeatTracks; + } + + uint32_t CalculateRepeatFillCount(const nsStyleCoord& aGridGap, + nscoord aMinSize, + nscoord aSize, + nscoord aMaxSize) const + { + if (!mHasRepeatAuto) { + return 0; + } + // Spec quotes are from https://drafts.csswg.org/css-grid/#repeat-notation + const uint32_t numTracks = mMinSizingFunctions.Length(); + MOZ_ASSERT(numTracks >= 1, "expected at least the repeat() track"); + nscoord maxFill = aSize != NS_UNCONSTRAINEDSIZE ? aSize : aMaxSize; + if (maxFill == NS_UNCONSTRAINEDSIZE && aMinSize == 0) { + // "Otherwise, the specified track list repeats only once." + return 1; + } + nscoord repeatTrackSize = 0; + // Note that the repeat() track size is included in |sum| in this loop. + nscoord sum = 0; + const nscoord percentBasis = aSize; + for (uint32_t i = 0; i < numTracks; ++i) { + // "treating each track as its max track sizing function if that is + // definite or as its minimum track sizing function otherwise" + // https://drafts.csswg.org/css-grid/#valdef-repeat-auto-fill + const auto& maxCoord = mMaxSizingFunctions[i]; + const auto* coord = &maxCoord; + if (!coord->IsCoordPercentCalcUnit()) { + coord = &mMinSizingFunctions[i]; + if (!coord->IsCoordPercentCalcUnit()) { + return 1; + } + } + nscoord trackSize = ::ResolveToDefiniteSize(*coord, percentBasis); + if (i == mRepeatAutoStart) { + if (percentBasis != NS_UNCONSTRAINEDSIZE) { + // Use a minimum 1px for the repeat() track-size. + if (trackSize < AppUnitsPerCSSPixel()) { + trackSize = AppUnitsPerCSSPixel(); + } + } + repeatTrackSize = trackSize; + } + sum += trackSize; + } + nscoord gridGap; + float percentSum = 0.0f; + float gridGapPercent; + ResolvePercentSizeParts(aGridGap, percentBasis, &gridGap, &gridGapPercent); + if (numTracks > 1) { + // Add grid-gaps for all the tracks including the repeat() track. + sum += gridGap * (numTracks - 1); + percentSum = gridGapPercent * (numTracks - 1); + } + // Calculate the max number of tracks that fits without overflow. + nscoord available = maxFill != NS_UNCONSTRAINEDSIZE ? maxFill : aMinSize; + nscoord size = nsLayoutUtils::AddPercents(sum, percentSum); + if (available - size < 0) { + // "if any number of repetitions would overflow, then 1 repetition" + return 1; + } + uint32_t numRepeatTracks = 1; + bool exactFit = false; + while (true) { + sum += gridGap + repeatTrackSize; + percentSum += gridGapPercent; + nscoord newSize = nsLayoutUtils::AddPercents(sum, percentSum); + if (newSize <= size) { + // Adding more repeat-tracks won't make forward progress. + return numRepeatTracks; + } + size = newSize; + nscoord remaining = available - size; + exactFit = remaining == 0; + if (remaining >= 0) { + ++numRepeatTracks; + } + if (remaining <= 0) { + break; + } + } + + if (!exactFit && maxFill == NS_UNCONSTRAINEDSIZE) { + // "Otherwise, if the grid container has a definite min size in + // the relevant axis, the number of repetitions is the largest possible + // positive integer that fulfills that minimum requirement." + ++numRepeatTracks; // one more to ensure the grid is at least min-size + } + // Clamp the number of repeat tracks so that the last line <= kMaxLine. + // (note that |numTracks| already includes one repeat() track) + const uint32_t maxRepeatTracks = nsStyleGridLine::kMaxLine - numTracks; + return std::min(numRepeatTracks, maxRepeatTracks); + } + + /** + * Compute the explicit grid end line number (in a zero-based grid). + * @param aGridTemplateAreasEnd 'grid-template-areas' end line in this axis + */ + uint32_t ComputeExplicitGridEnd(uint32_t aGridTemplateAreasEnd) + { + uint32_t end = NumExplicitTracks() + 1; + end = std::max(end, aGridTemplateAreasEnd); + end = std::min(end, uint32_t(nsStyleGridLine::kMaxLine)); + return end; + } + + const nsStyleCoord& MinSizingFor(uint32_t aTrackIndex) const + { + if (MOZ_UNLIKELY(aTrackIndex < mExplicitGridOffset)) { + return mAutoMinSizing; + } + uint32_t index = aTrackIndex - mExplicitGridOffset; + if (index >= mRepeatAutoStart) { + if (index < mRepeatAutoEnd) { + return mMinSizingFunctions[mRepeatAutoStart]; + } + index -= mRepeatEndDelta; + } + 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; + if (index >= mRepeatAutoStart) { + if (index < mRepeatAutoEnd) { + return mMaxSizingFunctions[mRepeatAutoStart]; + } + index -= mRepeatEndDelta; + } + return index < mMaxSizingFunctions.Length() ? + mMaxSizingFunctions[index] : mAutoMaxSizing; + } + uint32_t NumExplicitTracks() const + { + return mMinSizingFunctions.Length() + mRepeatEndDelta; + } + uint32_t NumRepeatTracks() const + { + return mRepeatAutoEnd - mRepeatAutoStart; + } + void SetNumRepeatTracks(uint32_t aNumRepeatTracks) + { + MOZ_ASSERT(mHasRepeatAuto || aNumRepeatTracks == 0); + mRepeatAutoEnd = mRepeatAutoStart + aNumRepeatTracks; + mRepeatEndDelta = mHasRepeatAuto ? + int32_t(aNumRepeatTracks) - 1 : + 0; +} + + // Some style data references, for easy access. + const nsTArray<nsStyleCoord>& mMinSizingFunctions; + const nsTArray<nsStyleCoord>& mMaxSizingFunctions; + const nsStyleCoord& mAutoMinSizing; + const nsStyleCoord& mAutoMaxSizing; + // Offset from the start of the implicit grid to the first explicit track. + uint32_t mExplicitGridOffset; + // The index of the repeat(auto-fill/fit) track, or zero if there is none. + const uint32_t mRepeatAutoStart; + // The (hypothetical) index of the last such repeat() track. + uint32_t mRepeatAutoEnd; + // The difference between mExplicitGridEnd and mMinSizingFunctions.Length(). + int32_t mRepeatEndDelta; + // True if there is a specified repeat(auto-fill/fit) track. + const bool mHasRepeatAuto; + // True if this track (relative to mRepeatAutoStart) is a removed auto-fit. + nsTArray<bool> mRemovedRepeatTracks; +}; + +/** + * State for the tracks in one dimension. + */ +struct nsGridContainerFrame::Tracks +{ + explicit Tracks(LogicalAxis aAxis) + : mStateUnion(TrackSize::StateBits(0)) + , mAxis(aAxis) + , mCanResolveLineRangeSize(false) + { + mBaselineSubtreeAlign[BaselineSharingGroup::eFirst] = NS_STYLE_ALIGN_AUTO; + mBaselineSubtreeAlign[BaselineSharingGroup::eLast] = NS_STYLE_ALIGN_AUTO; + mBaseline[BaselineSharingGroup::eFirst] = NS_INTRINSIC_WIDTH_UNKNOWN; + mBaseline[BaselineSharingGroup::eLast] = NS_INTRINSIC_WIDTH_UNKNOWN; + } + + void Initialize(const TrackSizingFunctions& aFunctions, + const nsStyleCoord& 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 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, + TrackSize::StateBits* aState) const; + + // Some data we collect for aligning baseline-aligned items. + struct ItemBaselineData + { + uint32_t mBaselineTrack; + nscoord mBaseline; + nscoord mSize; + GridItemInfo* mGridItem; + static bool IsBaselineTrackLessThan(const ItemBaselineData& a, + const ItemBaselineData& b) + { + return a.mBaselineTrack < b.mBaselineTrack; + } + }; + + /** + * Calculate baseline offsets for the given set of items. + * Helper for InitialzeItemBaselines. + */ + void CalculateItemBaselines(nsTArray<ItemBaselineData>& aBaselineItems, + BaselineSharingGroup aBaselineGroup); + + /** + * Initialize grid item baseline state and offsets. + */ + void InitializeItemBaselines(GridReflowInput& aState, + nsTArray<GridItemInfo>& aGridItems); + + /** + * Apply the additional alignment needed to align the baseline-aligned subtree + * the item belongs to within its baseline track. + */ + void AlignBaselineSubtree(const GridItemInfo& aGridItem) const; + + /** + * Resolve Intrinsic Track Sizes. + * http://dev.w3.org/csswg/css-grid/#algo-content + */ + void ResolveIntrinsicSize(GridReflowInput& aState, + nsTArray<GridItemInfo>& aGridItems, + const TrackSizingFunctions& aFunctions, + LineRange GridArea::* aRange, + nscoord aPercentageBasis, + SizingConstraint 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(GridReflowInput& aState, + const TrackSizingFunctions& aFunctions, + nscoord aPercentageBasis, + SizingConstraint aConstraint, + const LineRange& aRange, + const GridItemInfo& 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 (and corresponding grid gaps) 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. + */ + nscoord CollectGrowable(nscoord aAvailableSpace, + const nsTArray<TrackSize>& aPlan, + const LineRange& aRange, + TrackSize::StateBits aSelector, + nsTArray<uint32_t>& aGrowableTracks) const + { + MOZ_ASSERT(aAvailableSpace > 0, "why call me?"); + nscoord space = aAvailableSpace - mGridGap * (aRange.Extent() - 1); + 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; + } + } + + using FitContentClamper = + function<bool(uint32_t aTrack, nscoord aMinSize, nscoord* aSize)>; + /** + * 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, + FitContentClamper aFitContentClamper) 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; + nscoord limit = sz.mLimit; + if (MOZ_UNLIKELY((sz.mState & TrackSize::eFitContent) && + aFitContentClamper)) { + // Clamp the limit to the fit-content() size, for §12.5.2 step 5/6. + aFitContentClamper(track, sz.mBase, &limit); + } + if (newBase > limit) { + nscoord consumed = limit - sz.mBase; + if (consumed > 0) { + space -= consumed; + sz.mBase = limit; + } + 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, + FitContentClamper aFitContentClamper) const + { + MOZ_ASSERT(aAvailableSpace > 0 && aGrowableTracks.Length() > 0); + uint32_t numGrowable = aGrowableTracks.Length(); + if (aSelector) { + MOZ_ASSERT(aSelector == (aSelector & TrackSize::eIntrinsicMinSizing) && + (aSelector & TrackSize::eMaxContentMinSizing), + "Should only get here for track sizing steps 2.1 to 2.3"); + // 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 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; + DebugOnly<bool> didClamp = false; + while (numGrowable) { + 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 + } + nscoord delta = spacePerTrack; + nscoord newBase = sz.mBase + delta; + if (MOZ_UNLIKELY((sz.mState & TrackSize::eFitContent) && + aFitContentClamper)) { + // Clamp newBase to the fit-content() size, for §12.5.2 step 5/6. + if (aFitContentClamper(track, sz.mBase, &newBase)) { + didClamp = true; + delta = newBase - sz.mBase; + MOZ_ASSERT(delta >= 0, "track size shouldn't shrink"); + sz.mState |= TrackSize::eSkipGrowUnlimited1; + --numGrowable; + } + } + sz.mBase = newBase; + space -= delta; + MOZ_ASSERT(space >= 0); + if (space == 0) { + return; + } + } + } + MOZ_ASSERT(didClamp, "we don't exit the loop above except by return, " + "unless we clamped some track's size"); + } + + /** + * 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, nullptr); + if (space > 0) { + GrowSelectedTracksUnlimited(space, aPlan, aGrowableTracks, aSelector, nullptr); + } + CopyPlanToBase(aPlan, aGrowableTracks); + } + + /** + * Distribute aAvailableSpace to the planned limits for aGrowableTracks. + */ + void DistributeToTrackLimits(nscoord aAvailableSpace, + nsTArray<TrackSize>& aPlan, + nsTArray<uint32_t>& aGrowableTracks, + const TrackSizingFunctions& aFunctions, + nscoord aPercentageBasis) + { + auto fitContentClamper = [&aFunctions, aPercentageBasis] (uint32_t aTrack, + nscoord aMinSize, + nscoord* aSize) { + nscoord fitContentLimit = + ::ResolveToDefiniteSize(aFunctions.MaxSizingFor(aTrack), aPercentageBasis); + if (*aSize > fitContentLimit) { + *aSize = std::max(aMinSize, fitContentLimit); + return true; + } + return false; + }; + nscoord space = GrowTracksToLimit(aAvailableSpace, aPlan, aGrowableTracks, + fitContentClamper); + if (space > 0) { + GrowSelectedTracksUnlimited(aAvailableSpace, aPlan, aGrowableTracks, + TrackSize::StateBits(0), fitContentClamper); + } + 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(GridReflowInput& 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(GridReflowInput& 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(GridReflowInput& aState, + nsTArray<GridItemInfo>& aGridItems, + const TrackSizingFunctions& aFunctions, + nscoord aContentSize, + LineRange GridArea::* aRange, + SizingConstraint aConstraint); + + /** + * Apply 'align/justify-content', whichever is relevant for this axis. + * https://drafts.csswg.org/css-align-3/#propdef-align-content + */ + void AlignJustifyContent(const nsStylePosition* aStyle, + WritingMode aWM, + const LogicalSize& aContainerSize); + + /** + * Return the intrinsic size by back-computing percentages as: + * IntrinsicSize = SumOfCoordSizes / (1 - SumOfPercentages). + */ + nscoord BackComputedIntrinsicSize(const TrackSizingFunctions& aFunctions, + const nsStyleCoord& aGridGap) const; + + nscoord GridLineEdge(uint32_t aLine, GridLineSide aSide) const + { + if (MOZ_UNLIKELY(mSizes.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 <= mSizes.Length(), "mSizes is too small"); + if (aSide == GridLineSide::eBeforeGridGap) { + if (aLine == 0) { + return nscoord(0); + } + const TrackSize& sz = mSizes[aLine - 1]; + return sz.mPosition + sz.mBase; + } + if (aLine == mSizes.Length()) { + return mContentBoxSize; + } + return mSizes[aLine].mPosition; + } + + nscoord SumOfGridGaps() const + { + auto len = mSizes.Length(); + return MOZ_LIKELY(len > 1) ? (len - 1) * mGridGap : 0; + } + + /** + * Break before aRow, i.e. set the eBreakBefore flag on aRow and set the grid + * gap before aRow to zero (and shift all rows after it by the removed gap). + */ + void BreakBeforeRow(uint32_t aRow) + { + MOZ_ASSERT(mAxis == eLogicalAxisBlock, + "Should only be fragmenting in the block axis (between rows)"); + nscoord prevRowEndPos = 0; + if (aRow != 0) { + auto& prevSz = mSizes[aRow - 1]; + prevRowEndPos = prevSz.mPosition + prevSz.mBase; + } + auto& sz = mSizes[aRow]; + const nscoord gap = sz.mPosition - prevRowEndPos; + sz.mState |= TrackSize::eBreakBefore; + if (gap != 0) { + for (uint32_t i = aRow, len = mSizes.Length(); i < len; ++i) { + mSizes[i].mPosition -= gap; + } + } + } + + /** + * Set the size of aRow to aSize and adjust the position of all rows after it. + */ + void ResizeRow(uint32_t aRow, nscoord aNewSize) + { + MOZ_ASSERT(mAxis == eLogicalAxisBlock, + "Should only be fragmenting in the block axis (between rows)"); + MOZ_ASSERT(aNewSize >= 0); + auto& sz = mSizes[aRow]; + nscoord delta = aNewSize - sz.mBase; + NS_WARNING_ASSERTION(delta != nscoord(0), "Useless call to ResizeRow"); + sz.mBase = aNewSize; + const uint32_t numRows = mSizes.Length(); + for (uint32_t r = aRow + 1; r < numRows; ++r) { + mSizes[r].mPosition += delta; + } + } + + nscoord ResolveSize(const LineRange& aRange) const + { + MOZ_ASSERT(mCanResolveLineRangeSize); + MOZ_ASSERT(aRange.Extent() > 0, "grid items cover at least one track"); + nscoord pos, size; + aRange.ToPositionAndLength(mSizes, &pos, &size); + return size; + } + + nsTArray<nsString> GetExplicitLineNamesAtIndex( + const nsStyleGridTemplate& aGridTemplate, + const TrackSizingFunctions& aFunctions, + uint32_t aIndex) + { + nsTArray<nsString> lineNames; + + bool hasRepeatAuto = aGridTemplate.HasRepeatAuto(); + const nsTArray<nsTArray<nsString>>& lineNameLists( + aGridTemplate.mLineNameLists); + + if (!hasRepeatAuto) { + if (aIndex < lineNameLists.Length()) { + lineNames.AppendElements(lineNameLists[aIndex]); + } + } else { + const uint32_t repeatTrackCount = aFunctions.NumRepeatTracks(); + const uint32_t repeatAutoStart = aGridTemplate.mRepeatAutoIndex; + const uint32_t repeatAutoEnd = (repeatAutoStart + repeatTrackCount); + const int32_t repeatEndDelta = int32_t(repeatTrackCount - 1); + + if (aIndex <= repeatAutoStart) { + if (aIndex < lineNameLists.Length()) { + lineNames.AppendElements(lineNameLists[aIndex]); + } + if (aIndex == repeatAutoEnd) { + uint32_t i = aIndex + 1; + if (i < lineNameLists.Length()) { + lineNames.AppendElements(lineNameLists[i]); + } + } + } + if (aIndex <= repeatAutoEnd && aIndex > repeatAutoStart) { + lineNames.AppendElements(aGridTemplate.mRepeatAutoLineNameListAfter); + } + if (aIndex < repeatAutoEnd && aIndex >= repeatAutoStart) { + lineNames.AppendElements(aGridTemplate.mRepeatAutoLineNameListBefore); + } + if (aIndex >= repeatAutoEnd && aIndex > repeatAutoStart) { + uint32_t i = aIndex - repeatEndDelta; + if (i < lineNameLists.Length()) { + lineNames.AppendElements(lineNameLists[i]); + } + } + } + + return lineNames; + } + +#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 + + AutoTArray<TrackSize, 32> mSizes; + nscoord mContentBoxSize; + nscoord mGridGap; + // The first(last)-baseline for the first(last) track in this axis. + nscoord mBaseline[2]; // index by BaselineSharingGroup + // The union of the track min/max-sizing state bits in this axis. + TrackSize::StateBits mStateUnion; + LogicalAxis mAxis; + // Used for aligning a baseline-aligned subtree of items. The only possible + // values are NS_STYLE_ALIGN_{START,END,CENTER,AUTO}. AUTO means there are + // no baseline-aligned items in any track in that axis. + // There is one alignment value for each BaselineSharingGroup. + uint8_t mBaselineSubtreeAlign[2]; + // True if track positions and sizes are final in this axis. + bool mCanResolveLineRangeSize; +}; + +/** + * Grid data shared by all continuations, owned by the first-in-flow. + * The data is initialized from the first-in-flow's GridReflowInput at + * the end of its reflow. Fragmentation will modify mRows.mSizes - + * the mPosition to remove the row gap at the break boundary, the mState + * by setting the eBreakBefore flag, and mBase is modified when we decide + * to grow a row. mOriginalRowData is setup by the first-in-flow and + * not modified after that. It's used for undoing the changes to mRows. + * mCols, mGridItems, mAbsPosItems are used for initializing the grid + * reflow state for continuations, see GridReflowInput::Initialize below. + */ +struct nsGridContainerFrame::SharedGridData +{ + SharedGridData() : + mCols(eLogicalAxisInline), + mRows(eLogicalAxisBlock), + mGenerateComputedGridInfo(false) {} + Tracks mCols; + Tracks mRows; + struct RowData { + nscoord mBase; // the original track size + nscoord mGap; // the original gap before a track + }; + nsTArray<RowData> mOriginalRowData; + nsTArray<GridItemInfo> mGridItems; + nsTArray<GridItemInfo> mAbsPosItems; + bool mGenerateComputedGridInfo; + + /** + * Only set on the first-in-flow. Continuations will Initialize() their + * GridReflowInput from it. + */ + NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, SharedGridData) +}; + +struct MOZ_STACK_CLASS nsGridContainerFrame::GridReflowInput +{ + GridReflowInput(nsGridContainerFrame* aFrame, + const ReflowInput& aRI) + : GridReflowInput(aFrame, *aRI.mRenderingContext, &aRI, aRI.mStylePosition, + aRI.GetWritingMode()) + {} + GridReflowInput(nsGridContainerFrame* aFrame, + nsRenderingContext& aRC) + : GridReflowInput(aFrame, aRC, nullptr, aFrame->StylePosition(), + aFrame->GetWritingMode()) + {} + + /** + * Initialize our track sizes and grid item info using the shared + * state from aGridContainerFrame first-in-flow. + */ + void InitializeForContinuation(nsGridContainerFrame* aGridContainerFrame, + nscoord aConsumedBSize) + { + MOZ_ASSERT(aGridContainerFrame->GetPrevInFlow(), + "don't call this on the first-in-flow"); + MOZ_ASSERT(mGridItems.IsEmpty() && mAbsPosItems.IsEmpty(), + "shouldn't have any item data yet"); + + // Get the SharedGridData from the first-in-flow. Also calculate the number + // of fragments before this so that we can figure out our start row below. + uint32_t fragment = 0; + nsIFrame* firstInFlow = aGridContainerFrame; + for (auto pif = aGridContainerFrame->GetPrevInFlow(); + pif; pif = pif->GetPrevInFlow()) { + ++fragment; + firstInFlow = pif; + } + mSharedGridData = firstInFlow->Properties().Get(SharedGridData::Prop()); + MOZ_ASSERT(mSharedGridData, "first-in-flow must have SharedGridData"); + + // Find the start row for this fragment and undo breaks after that row + // since the breaks might be different from the last reflow. + auto& rowSizes = mSharedGridData->mRows.mSizes; + const uint32_t numRows = rowSizes.Length(); + mStartRow = numRows; + for (uint32_t row = 0, breakCount = 0; row < numRows; ++row) { + if (rowSizes[row].mState & TrackSize::eBreakBefore) { + if (fragment == ++breakCount) { + mStartRow = row; + mFragBStart = rowSizes[row].mPosition; + // Restore the original size for |row| and grid gaps / state after it. + const auto& origRowData = mSharedGridData->mOriginalRowData; + rowSizes[row].mBase = origRowData[row].mBase; + nscoord prevEndPos = rowSizes[row].mPosition + rowSizes[row].mBase; + while (++row < numRows) { + auto& sz = rowSizes[row]; + const auto& orig = origRowData[row]; + sz.mPosition = prevEndPos + orig.mGap; + sz.mBase = orig.mBase; + sz.mState &= ~TrackSize::eBreakBefore; + prevEndPos = sz.mPosition + sz.mBase; + } + break; + } + } + } + if (mStartRow == numRows) { + // All of the grid's rows fit inside of previous grid-container fragments. + mFragBStart = aConsumedBSize; + } + + // Copy the shared track state. + // XXX consider temporarily swapping the array elements instead and swapping + // XXX them back after we're done reflowing, for better performance. + // XXX (bug 1252002) + mCols = mSharedGridData->mCols; + mRows = mSharedGridData->mRows; + + // Copy item data from each child's first-in-flow data in mSharedGridData. + // XXX NOTE: This is O(n^2) in the number of items. (bug 1252186) + mIter.Reset(); + for (; !mIter.AtEnd(); mIter.Next()) { + nsIFrame* child = *mIter; + nsIFrame* childFirstInFlow = child->FirstInFlow(); + DebugOnly<size_t> len = mGridItems.Length(); + for (auto& itemInfo : mSharedGridData->mGridItems) { + if (itemInfo.mFrame == childFirstInFlow) { + auto item = mGridItems.AppendElement(GridItemInfo(child, itemInfo.mArea)); + // Copy the item's baseline data so that the item's last fragment can do + // 'last baseline' alignment if necessary. + item->mState[0] |= itemInfo.mState[0] & ItemState::eAllBaselineBits; + item->mState[1] |= itemInfo.mState[1] & ItemState::eAllBaselineBits; + item->mBaselineOffset[0] = itemInfo.mBaselineOffset[0]; + item->mBaselineOffset[1] = itemInfo.mBaselineOffset[1]; + break; + } + } + MOZ_ASSERT(mGridItems.Length() == len + 1, "can't find GridItemInfo"); + } + + // XXX NOTE: This is O(n^2) in the number of abs.pos. items. (bug 1252186) + nsFrameList absPosChildren(aGridContainerFrame->GetChildList( + aGridContainerFrame->GetAbsoluteListID())); + for (auto f : absPosChildren) { + nsIFrame* childFirstInFlow = f->FirstInFlow(); + DebugOnly<size_t> len = mAbsPosItems.Length(); + for (auto& itemInfo : mSharedGridData->mAbsPosItems) { + if (itemInfo.mFrame == childFirstInFlow) { + mAbsPosItems.AppendElement(GridItemInfo(f, itemInfo.mArea)); + break; + } + } + MOZ_ASSERT(mAbsPosItems.Length() == len + 1, "can't find GridItemInfo"); + } + + // Copy in the computed grid info state bit + if (mSharedGridData->mGenerateComputedGridInfo) { + aGridContainerFrame->AddStateBits(NS_STATE_GRID_GENERATE_COMPUTED_VALUES); + } + } + + /** + * Calculate our track sizes. If the given aContentBox block-axis size is + * unconstrained, it is assigned to the resulting intrinsic block-axis size. + */ + void CalculateTrackSizes(const Grid& aGrid, + LogicalSize& aContentBox, + SizingConstraint aConstraint); + + /** + * Return the containing block for a grid item occupying aArea. + */ + LogicalRect ContainingBlockFor(const GridArea& aArea) const; + + /** + * Return the containing block for an abs.pos. grid item occupying aArea. + * Any 'auto' lines in the grid area will be aligned with grid container + * containing block on that side. + * @param aGridOrigin the origin of the grid + * @param aGridCB the grid container containing block (its padding area) + */ + LogicalRect ContainingBlockForAbsPos(const GridArea& aArea, + const LogicalPoint& aGridOrigin, + const LogicalRect& aGridCB) const; + + GridItemCSSOrderIterator mIter; + const nsStylePosition* const mGridStyle; + Tracks mCols; + Tracks mRows; + TrackSizingFunctions mColFunctions; + TrackSizingFunctions mRowFunctions; + /** + * Info about each (normal flow) grid item. + */ + nsTArray<GridItemInfo> mGridItems; + /** + * Info about each grid-aligned abs.pos. child. + */ + nsTArray<GridItemInfo> mAbsPosItems; + + /** + * @note mReflowInput 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 ReflowInput* const mReflowInput; + nsRenderingContext& mRenderingContext; + nsGridContainerFrame* const mFrame; + SharedGridData* mSharedGridData; // [weak] owned by mFrame's first-in-flow. + /** Computed border+padding with mSkipSides applied. */ + LogicalMargin mBorderPadding; + /** + * BStart of this fragment in "grid space" (i.e. the concatenation of content + * areas of all fragments). Equal to mRows.mSizes[mStartRow].mPosition, + * or, if this fragment starts after the last row, the GetConsumedBSize(). + */ + nscoord mFragBStart; + /** The start row for this fragment. */ + uint32_t mStartRow; + /** + * The start row for the next fragment, if any. If mNextFragmentStartRow == + * mStartRow then there are no rows in this fragment. + */ + uint32_t mNextFragmentStartRow; + /** Our tentative ApplySkipSides bits. */ + LogicalSides mSkipSides; + const WritingMode mWM; + /** Initialized lazily, when we find the fragmentainer. */ + bool mInFragmentainer; + +private: + GridReflowInput(nsGridContainerFrame* aFrame, + nsRenderingContext& aRenderingContext, + const ReflowInput* aReflowInput, + const nsStylePosition* aGridStyle, + const WritingMode& aWM) + : mIter(aFrame, kPrincipalList) + , mGridStyle(aGridStyle) + , mCols(eLogicalAxisInline) + , mRows(eLogicalAxisBlock) + , mColFunctions(mGridStyle->mGridTemplateColumns, + mGridStyle->mGridAutoColumnsMin, + mGridStyle->mGridAutoColumnsMax) + , mRowFunctions(mGridStyle->mGridTemplateRows, + mGridStyle->mGridAutoRowsMin, + mGridStyle->mGridAutoRowsMax) + , mReflowInput(aReflowInput) + , mRenderingContext(aRenderingContext) + , mFrame(aFrame) + , mSharedGridData(nullptr) + , mBorderPadding(aWM) + , mFragBStart(0) + , mStartRow(0) + , mNextFragmentStartRow(0) + , mWM(aWM) + , mInFragmentainer(false) + { + MOZ_ASSERT(!aReflowInput || aReflowInput->mFrame == mFrame); + if (aReflowInput) { + mBorderPadding = aReflowInput->ComputedLogicalBorderPadding(); + mSkipSides = aFrame->PreReflowBlockLevelLogicalSkipSides(); + mBorderPadding.ApplySkipSides(mSkipSides); + } + } +}; + +using GridReflowInput = nsGridContainerFrame::GridReflowInput; + +/** + * The Grid implements grid item placement and the state of the grid - + * the size of the explicit/implicit grid, which cells are occupied etc. + */ +struct MOZ_STACK_CLASS nsGridContainerFrame::Grid +{ + /** + * Place all child frames into the grid and expand the (implicit) grid as + * needed. The allocated GridAreas are stored in the GridAreaProperty + * frame property on the child frame. + * @param aComputedMinSize the container's min-size - used to determine + * the number of repeat(auto-fill/fit) tracks. + * @param aComputedSize the container's size - used to determine + * the number of repeat(auto-fill/fit) tracks. + * @param aComputedMaxSize the container's max-size - used to determine + * the number of repeat(auto-fill/fit) tracks. + */ + void PlaceGridItems(GridReflowInput& aState, + const LogicalSize& aComputedMinSize, + const LogicalSize& aComputedSize, + const LogicalSize& aComputedMaxSize); + + /** + * As above but for an abs.pos. child. Any 'auto' lines will be represented + * by kAutoLine in the LineRange result. + * @param aGridStart the first line in the final, but untranslated grid + * @param aGridEnd the last line in the final, but untranslated grid + */ + LineRange ResolveAbsPosLineRange(const nsStyleGridLine& aStart, + const nsStyleGridLine& aEnd, + const LineNameMap& aNameMap, + uint32_t GridNamedArea::* aAreaStart, + uint32_t GridNamedArea::* aAreaEnd, + uint32_t aExplicitGridEnd, + int32_t aGridStart, + int32_t aGridEnd, + const nsStylePosition* aStyle); + + /** + * Return a GridArea for abs.pos. item with non-auto lines placed at + * a definite line (1-based) with placement errors resolved. One or both + * positions may still be 'auto'. + * @param aChild the abs.pos. grid item to place + * @param aStyle the StylePosition() for the grid container + */ + GridArea PlaceAbsPos(nsIFrame* aChild, + const LineNameMap& aColLineNameMap, + const LineNameMap& aRowLineNameMap, + const nsStylePosition* aStyle); + + /** + * Find the first column in row aLockedRow starting at aStartCol where aArea + * could be placed without overlapping other items. The returned column may + * cause aArea to overflow the current implicit grid bounds if placed there. + */ + uint32_t FindAutoCol(uint32_t aStartCol, uint32_t aLockedRow, + const GridArea* aArea) const; + + /** + * Place aArea in the first column (in row aArea->mRows.mStart) starting at + * aStartCol without overlapping other items. The resulting aArea may + * overflow the current implicit grid bounds. + * Pre-condition: aArea->mRows.IsDefinite() is true. + * Post-condition: aArea->IsDefinite() is true. + */ + void PlaceAutoCol(uint32_t aStartCol, GridArea* aArea) const; + + /** + * Find the first row in column aLockedCol starting at aStartRow where aArea + * could be placed without overlapping other items. The returned row may + * cause aArea to overflow the current implicit grid bounds if placed there. + */ + uint32_t FindAutoRow(uint32_t aLockedCol, uint32_t aStartRow, + const GridArea* aArea) const; + + /** + * Place aArea in the first row (in column aArea->mCols.mStart) starting at + * aStartRow without overlapping other items. The resulting aArea may + * overflow the current implicit grid bounds. + * Pre-condition: aArea->mCols.IsDefinite() is true. + * Post-condition: aArea->IsDefinite() is true. + */ + void PlaceAutoRow(uint32_t aStartRow, GridArea* aArea) const; + + /** + * Place aArea in the first column starting at aStartCol,aStartRow without + * causing it to overlap other items or overflow mGridColEnd. + * If there's no such column in aStartRow, continue in position 1,aStartRow+1. + * Pre-condition: aArea->mCols.IsAuto() && aArea->mRows.IsAuto() is true. + * Post-condition: aArea->IsDefinite() is true. + */ + void PlaceAutoAutoInRowOrder(uint32_t aStartCol, + uint32_t aStartRow, + GridArea* aArea) const; + + /** + * Place aArea in the first row starting at aStartCol,aStartRow without + * causing it to overlap other items or overflow mGridRowEnd. + * If there's no such row in aStartCol, continue in position aStartCol+1,1. + * Pre-condition: aArea->mCols.IsAuto() && aArea->mRows.IsAuto() is true. + * Post-condition: aArea->IsDefinite() is true. + */ + void PlaceAutoAutoInColOrder(uint32_t aStartCol, + uint32_t aStartRow, + GridArea* aArea) const; + + /** + * Return aLine if it's inside the aMin..aMax range (inclusive), + * otherwise return kAutoLine. + */ + static int32_t + AutoIfOutside(int32_t aLine, int32_t aMin, int32_t aMax) + { + MOZ_ASSERT(aMin <= aMax); + if (aLine < aMin || aLine > aMax) { + return kAutoLine; + } + return aLine; + } + + /** + * Inflate the implicit grid to include aArea. + * @param aArea may be definite or auto + */ + void InflateGridFor(const GridArea& aArea) + { + mGridColEnd = std::max(mGridColEnd, aArea.mCols.HypotheticalEnd()); + mGridRowEnd = std::max(mGridRowEnd, aArea.mRows.HypotheticalEnd()); + MOZ_ASSERT(mGridColEnd <= kTranslatedMaxLine && + mGridRowEnd <= kTranslatedMaxLine); + } + + enum LineRangeSide { + eLineRangeSideStart, eLineRangeSideEnd + }; + /** + * Return a line number for (non-auto) aLine, per: + * http://dev.w3.org/csswg/css-grid/#line-placement + * @param aLine style data for the line (must be non-auto) + * @param aNth a number of lines to find from aFromIndex, negative if the + * search should be in reverse order. In the case aLine has + * a specified line name, it's permitted to pass in zero which + * will be treated as one. + * @param aFromIndex the zero-based index to start counting from + * @param aLineNameList the explicit named lines + * @param aAreaStart a pointer to GridNamedArea::mColumnStart/mRowStart + * @param aAreaEnd a pointer to GridNamedArea::mColumnEnd/mRowEnd + * @param aExplicitGridEnd the last line in the explicit grid + * @param aEdge indicates whether we are resolving a start or end line + * @param aStyle the StylePosition() for the grid container + * @return a definite line (1-based), clamped to the kMinLine..kMaxLine range + */ + int32_t ResolveLine(const nsStyleGridLine& aLine, + int32_t aNth, + uint32_t aFromIndex, + const LineNameMap& aNameMap, + uint32_t GridNamedArea::* aAreaStart, + uint32_t GridNamedArea::* aAreaEnd, + uint32_t aExplicitGridEnd, + LineRangeSide aSide, + const nsStylePosition* aStyle); + + /** + * Helper method for ResolveLineRange. + * @see ResolveLineRange + * @return a pair (start,end) of lines + */ + typedef std::pair<int32_t, int32_t> LinePair; + LinePair ResolveLineRangeHelper(const nsStyleGridLine& aStart, + const nsStyleGridLine& aEnd, + const LineNameMap& aNameMap, + uint32_t GridNamedArea::* aAreaStart, + uint32_t GridNamedArea::* aAreaEnd, + uint32_t aExplicitGridEnd, + const nsStylePosition* aStyle); + + /** + * Return a LineRange based on the given style data. Non-auto lines + * are resolved to a definite line number (1-based) per: + * http://dev.w3.org/csswg/css-grid/#line-placement + * with placement errors corrected per: + * http://dev.w3.org/csswg/css-grid/#grid-placement-errors + * @param aStyle the StylePosition() for the grid container + * @param aStart style data for the start line + * @param aEnd style data for the end line + * @param aLineNameList the explicit named lines + * @param aAreaStart a pointer to GridNamedArea::mColumnStart/mRowStart + * @param aAreaEnd a pointer to GridNamedArea::mColumnEnd/mRowEnd + * @param aExplicitGridEnd the last line in the explicit grid + * @param aStyle the StylePosition() for the grid container + */ + LineRange ResolveLineRange(const nsStyleGridLine& aStart, + const nsStyleGridLine& aEnd, + const LineNameMap& aNameMap, + uint32_t GridNamedArea::* aAreaStart, + uint32_t GridNamedArea::* aAreaEnd, + uint32_t aExplicitGridEnd, + const nsStylePosition* aStyle); + + /** + * Return a GridArea with non-auto lines placed at a definite line (1-based) + * with placement errors resolved. One or both positions may still + * be 'auto'. + * @param aChild the grid item + * @param aStyle the StylePosition() for the grid container + */ + GridArea PlaceDefinite(nsIFrame* aChild, + const LineNameMap& aColLineNameMap, + const LineNameMap& aRowLineNameMap, + const nsStylePosition* aStyle); + + bool HasImplicitNamedArea(const nsString& aName) const + { + return mAreas && mAreas->Contains(aName); + } + + /** + * 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); + } + + /** + * A CellMap holds state for each cell in the grid. + * It's row major. It's sparse in the sense that it only has enough rows to + * cover the last row that has a grid item. Each row only has enough entries + * to cover columns that are occupied *on that row*, i.e. it's not a full + * matrix covering the entire implicit grid. An absent Cell means that it's + * unoccupied by any grid item. + */ + struct CellMap { + struct Cell { + Cell() : mIsOccupied(false) {} + bool mIsOccupied : 1; + }; + + void 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; + } + } + } + + uint32_t IsEmptyCol(uint32_t aCol) const + { + for (auto& row : mCells) { + if (aCol < row.Length() && row[aCol].mIsOccupied) { + return false; + } + } + return true; + } + uint32_t IsEmptyRow(uint32_t aRow) const + { + if (aRow >= mCells.Length()) { + return true; + } + for (const Cell& cell : mCells[aRow]) { + if (cell.mIsOccupied) { + return false; + } + } + return true; + } +#ifdef DEBUG + void 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"); + } + } +#endif + + nsTArray<nsTArray<Cell>> mCells; + }; + + /** + * State for each cell in the grid. + */ + CellMap mCellMap; + /** + * @see HasImplicitNamedArea. + */ + ImplicitNamedAreas* mAreas; + /** + * The last column grid line (1-based) in the explicit grid. + * (i.e. the number of explicit columns + 1) + */ + uint32_t mExplicitGridColEnd; + /** + * The last row grid line (1-based) in the explicit grid. + * (i.e. the number of explicit rows + 1) + */ + uint32_t mExplicitGridRowEnd; + // Same for the implicit grid, except these become zero-based after + // resolving definite lines. + uint32_t mGridColEnd; + uint32_t mGridRowEnd; + + /** + * Offsets from the start of the implicit grid to the start of the translated + * explicit grid. They are zero if there are no implicit lines before 1,1. + * e.g. "grid-column: span 3 / 1" makes mExplicitGridOffsetCol = 3 and the + * corresponding GridArea::mCols will be 0 / 3 in the zero-based translated + * grid. + */ + uint32_t mExplicitGridOffsetCol; + uint32_t mExplicitGridOffsetRow; +}; + +void +nsGridContainerFrame::GridReflowInput::CalculateTrackSizes( + const Grid& aGrid, + LogicalSize& aContentBox, + SizingConstraint aConstraint) +{ + mCols.Initialize(mColFunctions, mGridStyle->mGridColumnGap, + aGrid.mGridColEnd, aContentBox.ISize(mWM)); + mRows.Initialize(mRowFunctions, mGridStyle->mGridRowGap, + aGrid.mGridRowEnd, aContentBox.BSize(mWM)); + + mCols.CalculateSizes(*this, mGridItems, mColFunctions, + aContentBox.ISize(mWM), &GridArea::mCols, + aConstraint); + mCols.AlignJustifyContent(mGridStyle, mWM, aContentBox); + // Column positions and sizes are now final. + mCols.mCanResolveLineRangeSize = true; + + mRows.CalculateSizes(*this, mGridItems, mRowFunctions, + aContentBox.BSize(mWM), &GridArea::mRows, + aConstraint); + if (aContentBox.BSize(mWM) == NS_AUTOHEIGHT) { + aContentBox.BSize(mWM) = + mRows.BackComputedIntrinsicSize(mRowFunctions, mGridStyle->mGridRowGap); + mRows.mGridGap = + ::ResolveToDefiniteSize(mGridStyle->mGridRowGap, aContentBox.BSize(mWM)); + } +} + +/** + * (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; +} + +// Align an item's margin box in its aAxis inside aCBSize. +static void +AlignJustifySelf(uint8_t aAlignment, LogicalAxis aAxis, + AlignJustifyFlags aFlags, + nscoord aBaselineAdjust, nscoord aCBSize, + const ReflowInput& aRI, const LogicalSize& aChildSize, + LogicalPoint* aPos) +{ + MOZ_ASSERT(aAlignment != NS_STYLE_ALIGN_AUTO, "unexpected 'auto' " + "computed value for normal flow grid item"); + + // NOTE: this is the resulting frame offset (border box). + nscoord offset = + CSSAlignUtils::AlignJustifySelf(aAlignment, aAxis, aFlags, + aBaselineAdjust, aCBSize, + aRI, aChildSize); + + // Set the position (aPos) for the requested alignment. + if (offset != 0) { + WritingMode wm = aRI.GetWritingMode(); + nscoord& pos = aAxis == eLogicalAxisBlock ? aPos->B(wm) : aPos->I(wm); + pos += MOZ_LIKELY(aFlags & AlignJustifyFlags::eSameSide) ? offset : -offset; + } +} + +static void +AlignSelf(const nsGridContainerFrame::GridItemInfo& aGridItem, + uint8_t aAlignSelf, nscoord aCBSize, const WritingMode aCBWM, + const ReflowInput& aRI, const LogicalSize& aSize, + LogicalPoint* aPos) +{ + auto alignSelf = aAlignSelf; + + AlignJustifyFlags flags = AlignJustifyFlags::eNoFlags; + if (alignSelf & NS_STYLE_ALIGN_SAFE) { + flags |= AlignJustifyFlags::eOverflowSafe; + } + alignSelf &= ~NS_STYLE_ALIGN_FLAG_BITS; + + WritingMode childWM = aRI.GetWritingMode(); + if (aCBWM.ParallelAxisStartsOnSameSide(eLogicalAxisBlock, childWM)) { + flags |= AlignJustifyFlags::eSameSide; + } + + // 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_LIKELY(alignSelf == NS_STYLE_ALIGN_NORMAL)) { + alignSelf = NS_STYLE_ALIGN_STRETCH; + } + + nscoord baselineAdjust = 0; + if (alignSelf == NS_STYLE_ALIGN_BASELINE || + alignSelf == NS_STYLE_ALIGN_LAST_BASELINE) { + alignSelf = aGridItem.GetSelfBaseline(alignSelf, eLogicalAxisBlock, + &baselineAdjust); + } + + bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM); + LogicalAxis axis = isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock; + AlignJustifySelf(alignSelf, axis, flags, baselineAdjust, + aCBSize, aRI, aSize, aPos); +} + +static void +JustifySelf(const nsGridContainerFrame::GridItemInfo& aGridItem, + uint8_t aJustifySelf, nscoord aCBSize, const WritingMode aCBWM, + const ReflowInput& aRI, const LogicalSize& aSize, + LogicalPoint* aPos) +{ + auto justifySelf = aJustifySelf; + + AlignJustifyFlags flags = AlignJustifyFlags::eNoFlags; + if (justifySelf & NS_STYLE_JUSTIFY_SAFE) { + flags |= AlignJustifyFlags::eOverflowSafe; + } + justifySelf &= ~NS_STYLE_JUSTIFY_FLAG_BITS; + + WritingMode childWM = aRI.GetWritingMode(); + if (aCBWM.ParallelAxisStartsOnSameSide(eLogicalAxisInline, childWM)) { + flags |= AlignJustifyFlags::eSameSide; + } + + if (MOZ_LIKELY(justifySelf == NS_STYLE_ALIGN_NORMAL)) { + justifySelf = NS_STYLE_ALIGN_STRETCH; + } + + nscoord baselineAdjust = 0; + // 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; + case NS_STYLE_JUSTIFY_BASELINE: + case NS_STYLE_JUSTIFY_LAST_BASELINE: + justifySelf = aGridItem.GetSelfBaseline(justifySelf, eLogicalAxisInline, + &baselineAdjust); + break; + } + + bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM); + LogicalAxis axis = isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline; + AlignJustifySelf(justifySelf, axis, flags, baselineAdjust, + aCBSize, aRI, aSize, aPos); +} + +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 = 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 indexOfSuffix; + if (Grid::IsNameWithStartSuffix(name, &indexOfSuffix) || + Grid::IsNameWithEndSuffix(name, &indexOfSuffix)) { + // Extract the name that was found earlier. + nsDependentSubstring areaName(name, 0, indexOfSuffix); + + // Lazily create the ImplicitNamedAreas. + if (!areas) { + areas = new ImplicitNamedAreas; + Properties().Set(ImplicitNamedAreasProperty(), areas); + } + + mozilla::css::GridNamedArea area; + if (!areas->Get(areaName, &area)) { + // Not found, so prep the newly-seen area with a name and empty + // boundary information, which will get filled in later. + area.mName = areaName; + area.mRowStart = 0; + area.mRowEnd = 0; + area.mColumnStart = 0; + area.mColumnEnd = 0; + + areas->Put(areaName, 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::Grid::ResolveLine(const nsStyleGridLine& aLine, + int32_t aNth, + uint32_t aFromIndex, + const LineNameMap& aNameMap, + 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 = aNameMap.FindNamedLine(lineName, &aNth, aFromIndex, + implicitLine); + } + } + + 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 = aNameMap.FindNamedLine(aLine.mLineName, &aNth, aFromIndex, + implicitLine); + } + + 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::Grid::LinePair +nsGridContainerFrame::Grid::ResolveLineRangeHelper( + const nsStyleGridLine& aStart, + const nsStyleGridLine& aEnd, + const LineNameMap& aNameMap, + 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 + 1: 0; + auto end = ResolveLine(aEnd, aEnd.mInteger, from, aNameMap, 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, aNameMap, 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 + 1: 0; + start = ResolveLine(aStart, aStart.mInteger, from, aNameMap, + 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 + 1: 0; + } + auto end = ResolveLine(aEnd, nth, from, aNameMap, 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::Grid::ResolveLineRange( + const nsStyleGridLine& aStart, + const nsStyleGridLine& aEnd, + const LineNameMap& aNameMap, + uint32_t GridNamedArea::* aAreaStart, + uint32_t GridNamedArea::* aAreaEnd, + uint32_t aExplicitGridEnd, + const nsStylePosition* aStyle) +{ + LinePair r = ResolveLineRangeHelper(aStart, aEnd, aNameMap, 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::Grid::PlaceDefinite(nsIFrame* aChild, + const LineNameMap& aColLineNameMap, + const LineNameMap& aRowLineNameMap, + const nsStylePosition* aStyle) +{ + const nsStylePosition* itemStyle = aChild->StylePosition(); + return GridArea( + ResolveLineRange(itemStyle->mGridColumnStart, itemStyle->mGridColumnEnd, + aColLineNameMap, + &GridNamedArea::mColumnStart, &GridNamedArea::mColumnEnd, + mExplicitGridColEnd, aStyle), + ResolveLineRange(itemStyle->mGridRowStart, itemStyle->mGridRowEnd, + aRowLineNameMap, + &GridNamedArea::mRowStart, &GridNamedArea::mRowEnd, + mExplicitGridRowEnd, aStyle)); +} + +nsGridContainerFrame::LineRange +nsGridContainerFrame::Grid::ResolveAbsPosLineRange( + const nsStyleGridLine& aStart, + const nsStyleGridLine& aEnd, + const LineNameMap& aNameMap, + 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); + } + uint32_t from = aEnd.mInteger < 0 ? aExplicitGridEnd + 1: 0; + int32_t end = + ResolveLine(aEnd, aEnd.mInteger, from, aNameMap, 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()) { + uint32_t from = aStart.mInteger < 0 ? aExplicitGridEnd + 1: 0; + int32_t start = + ResolveLine(aStart, aStart.mInteger, from, aNameMap, 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, aNameMap, 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)); +} + +nsGridContainerFrame::GridArea +nsGridContainerFrame::Grid::PlaceAbsPos(nsIFrame* aChild, + const LineNameMap& aColLineNameMap, + const LineNameMap& aRowLineNameMap, + 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, + aColLineNameMap, + &GridNamedArea::mColumnStart, + &GridNamedArea::mColumnEnd, + mExplicitGridColEnd, gridColStart, mGridColEnd, + aStyle), + ResolveAbsPosLineRange(itemStyle->mGridRowStart, + itemStyle->mGridRowEnd, + aRowLineNameMap, + &GridNamedArea::mRowStart, + &GridNamedArea::mRowEnd, + mExplicitGridRowEnd, gridRowStart, mGridRowEnd, + aStyle)); +} + +uint32_t +nsGridContainerFrame::Grid::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; +} + +void +nsGridContainerFrame::Grid::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::Grid::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::Grid::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::Grid::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::Grid::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::Grid::PlaceGridItems(GridReflowInput& aState, + const LogicalSize& aComputedMinSize, + const LogicalSize& aComputedSize, + const LogicalSize& aComputedMaxSize) +{ + mAreas = aState.mFrame->GetImplicitNamedAreas(); + const nsStylePosition* const gridStyle = aState.mGridStyle; + MOZ_ASSERT(mCellMap.mCells.IsEmpty(), "unexpected entries in cell map"); + + // http://dev.w3.org/csswg/css-grid/#grid-definition + // Initialize the end lines of the Explicit Grid (mExplicitGridCol[Row]End). + // This is determined by the larger of the number of rows/columns defined + // by 'grid-template-areas' and the 'grid-template-rows'/'-columns', plus one. + // Also initialize the Implicit Grid (mGridCol[Row]End) to the same values. + // 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. + auto areas = gridStyle->mGridTemplateAreas.get(); + uint32_t numRepeatCols = aState.mColFunctions.InitRepeatTracks( + gridStyle->mGridColumnGap, + aComputedMinSize.ISize(aState.mWM), + aComputedSize.ISize(aState.mWM), + aComputedMaxSize.ISize(aState.mWM)); + mGridColEnd = mExplicitGridColEnd = + aState.mColFunctions.ComputeExplicitGridEnd(areas ? areas->mNColumns + 1 : 1); + LineNameMap colLineNameMap(gridStyle->mGridTemplateColumns, numRepeatCols); + + uint32_t numRepeatRows = aState.mRowFunctions.InitRepeatTracks( + gridStyle->mGridRowGap, + aComputedMinSize.BSize(aState.mWM), + aComputedSize.BSize(aState.mWM), + aComputedMaxSize.BSize(aState.mWM)); + mGridRowEnd = mExplicitGridRowEnd = + aState.mRowFunctions.ComputeExplicitGridEnd(areas ? areas->NRows() + 1 : 1); + LineNameMap rowLineNameMap(gridStyle->mGridTemplateRows, numRepeatRows); + + // 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; + aState.mGridItems.ClearAndRetainStorage(); + aState.mIter.Reset(); + for (; !aState.mIter.AtEnd(); aState.mIter.Next()) { + nsIFrame* child = *aState.mIter; + GridItemInfo* info = + aState.mGridItems.AppendElement(GridItemInfo(child, + PlaceDefinite(child, + colLineNameMap, + rowLineNameMap, + gridStyle))); + MOZ_ASSERT(aState.mIter.GridItemIndex() == aState.mGridItems.Length() - 1, + "GridItemIndex() is broken"); + 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 = aState.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 ? &Grid::PlaceAutoCol + : &Grid::PlaceAutoRow; + aState.mIter.Reset(); + for (; !aState.mIter.AtEnd(); aState.mIter.Next()) { + GridArea& area = aState.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 ? &Grid::PlaceAutoRow + : &Grid::PlaceAutoCol; + aState.mIter.Reset(); + for (; !aState.mIter.AtEnd(); aState.mIter.Next()) { + GridArea& area = aState.mGridItems[aState.mIter.GridItemIndex()].mArea; + MOZ_ASSERT(*aState.mIter == aState.mGridItems[aState.mIter.GridItemIndex()].mFrame, + "iterator out of sync with aState.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 (aState.mFrame->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(aState.mFrame->GetChildList( + aState.mFrame->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; + aState.mAbsPosItems.ClearAndRetainStorage(); + size_t i = 0; + for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next(), ++i) { + nsIFrame* child = e.get(); + GridItemInfo* info = + aState.mAbsPosItems.AppendElement(GridItemInfo(child, + PlaceAbsPos(child, + colLineNameMap, + rowLineNameMap, + gridStyle))); + 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; + } + } + } + + // Count empty 'auto-fit' tracks in the repeat() range. + // |colAdjust| will have a count for each line in the grid of how many + // tracks were empty between the start of the grid and that line. + Maybe<nsTArray<uint32_t>> colAdjust; + uint32_t numEmptyCols = 0; + if (aState.mColFunctions.mHasRepeatAuto && + !gridStyle->mGridTemplateColumns.mIsAutoFill && + aState.mColFunctions.NumRepeatTracks() > 0) { + for (uint32_t col = aState.mColFunctions.mRepeatAutoStart, + endRepeat = aState.mColFunctions.mRepeatAutoEnd, + numColLines = mGridColEnd + 1; + col < numColLines; ++col) { + if (numEmptyCols) { + (*colAdjust)[col] = numEmptyCols; + } + if (col < endRepeat && mCellMap.IsEmptyCol(col)) { + ++numEmptyCols; + if (colAdjust.isNothing()) { + colAdjust.emplace(numColLines); + colAdjust->SetLength(numColLines); + PodZero(colAdjust->Elements(), colAdjust->Length()); + } + + uint32_t repeatIndex = col - aState.mColFunctions.mRepeatAutoStart; + MOZ_ASSERT(aState.mColFunctions.mRemovedRepeatTracks.Length() > + repeatIndex); + aState.mColFunctions.mRemovedRepeatTracks[repeatIndex] = true; + } + } + } + Maybe<nsTArray<uint32_t>> rowAdjust; + uint32_t numEmptyRows = 0; + if (aState.mRowFunctions.mHasRepeatAuto && + !gridStyle->mGridTemplateRows.mIsAutoFill && + aState.mRowFunctions.NumRepeatTracks() > 0) { + for (uint32_t row = aState.mRowFunctions.mRepeatAutoStart, + endRepeat = aState.mRowFunctions.mRepeatAutoEnd, + numRowLines = mGridRowEnd + 1; + row < numRowLines; ++row) { + if (numEmptyRows) { + (*rowAdjust)[row] = numEmptyRows; + } + if (row < endRepeat && mCellMap.IsEmptyRow(row)) { + ++numEmptyRows; + if (rowAdjust.isNothing()) { + rowAdjust.emplace(numRowLines); + rowAdjust->SetLength(numRowLines); + PodZero(rowAdjust->Elements(), rowAdjust->Length()); + } + + uint32_t repeatIndex = row - aState.mRowFunctions.mRepeatAutoStart; + MOZ_ASSERT(aState.mRowFunctions.mRemovedRepeatTracks.Length() > + repeatIndex); + aState.mRowFunctions.mRemovedRepeatTracks[repeatIndex] = true; + } + } + } + // Remove the empty 'auto-fit' tracks we found above, if any. + if (numEmptyCols || numEmptyRows) { + // Adjust the line numbers in the grid areas. + for (auto& item : aState.mGridItems) { + GridArea& area = item.mArea; + if (numEmptyCols) { + area.mCols.AdjustForRemovedTracks(*colAdjust); + } + if (numEmptyRows) { + area.mRows.AdjustForRemovedTracks(*rowAdjust); + } + } + for (auto& item : aState.mAbsPosItems) { + GridArea& area = item.mArea; + if (numEmptyCols) { + area.mCols.AdjustAbsPosForRemovedTracks(*colAdjust); + } + if (numEmptyRows) { + area.mRows.AdjustAbsPosForRemovedTracks(*rowAdjust); + } + } + // Adjust the grid size. + mGridColEnd -= numEmptyCols; + mExplicitGridColEnd -= numEmptyCols; + mGridRowEnd -= numEmptyRows; + mExplicitGridRowEnd -= numEmptyRows; + // Adjust the track mapping to unmap the removed tracks. + auto colRepeatCount = aState.mColFunctions.NumRepeatTracks(); + aState.mColFunctions.SetNumRepeatTracks(colRepeatCount - numEmptyCols); + auto rowRepeatCount = aState.mRowFunctions.NumRepeatTracks(); + aState.mRowFunctions.SetNumRepeatTracks(rowRepeatCount - numEmptyRows); + } + + // Update the line boundaries of the implicit grid areas, if needed. + if (mAreas && + aState.mFrame->HasAnyStateBits(NS_STATE_GRID_GENERATE_COMPUTED_VALUES)) { + for (auto iter = mAreas->Iter(); !iter.Done(); iter.Next()) { + auto& areaInfo = iter.Data(); + + // Resolve the lines for the area. We use the name of the area as the + // name of the lines, knowing that the line placement algorithm will + // add the -start and -end suffixes as appropriate for layout. + nsStyleGridLine lineStartAndEnd; + lineStartAndEnd.mLineName = areaInfo.mName; + + LineRange columnLines = ResolveLineRange( + lineStartAndEnd, lineStartAndEnd, + colLineNameMap, + &GridNamedArea::mColumnStart, &GridNamedArea::mColumnEnd, + mExplicitGridColEnd, gridStyle); + + LineRange rowLines = ResolveLineRange( + lineStartAndEnd, lineStartAndEnd, + rowLineNameMap, + &GridNamedArea::mRowStart, &GridNamedArea::mRowEnd, + mExplicitGridRowEnd, gridStyle); + + // Put the resolved line indices back into the area structure. + areaInfo.mColumnStart = columnLines.mStart + mExplicitGridOffsetCol; + areaInfo.mColumnEnd = columnLines.mEnd + mExplicitGridOffsetCol; + areaInfo.mRowStart = rowLines.mStart + mExplicitGridOffsetRow; + areaInfo.mRowEnd = rowLines.mEnd + mExplicitGridOffsetRow; + } + } +} + +void +nsGridContainerFrame::Tracks::Initialize( + const TrackSizingFunctions& aFunctions, + const nsStyleCoord& aGridGap, + uint32_t aNumTracks, + nscoord aContentBoxSize) +{ + MOZ_ASSERT(aNumTracks >= aFunctions.mExplicitGridOffset + + aFunctions.NumExplicitTracks()); + mSizes.SetLength(aNumTracks); + PodZero(mSizes.Elements(), mSizes.Length()); + for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) { + mStateUnion |= mSizes[i].Initialize(aContentBoxSize, + aFunctions.MinSizingFor(i), + aFunctions.MaxSizingFor(i)); + } + mGridGap = ::ResolveToDefiniteSize(aGridGap, aContentBoxSize); + mContentBoxSize = aContentBoxSize; +} + +/** + * Reflow aChild in the given aAvailableSize. + */ +static nscoord +MeasuringReflow(nsIFrame* aChild, + const ReflowInput* aReflowInput, + nsRenderingContext* aRC, + const LogicalSize& aAvailableSize, + const LogicalSize& aCBSize, + nscoord aIMinSizeClamp = NS_MAXSIZE, + nscoord aBMinSizeClamp = NS_MAXSIZE) +{ + nsContainerFrame* parent = aChild->GetParent(); + nsPresContext* pc = aChild->PresContext(); + Maybe<ReflowInput> dummyParentState; + const ReflowInput* rs = aReflowInput; + if (!aReflowInput) { + MOZ_ASSERT(!parent->HasAnyStateBits(NS_FRAME_IN_REFLOW)); + dummyParentState.emplace(pc, parent, aRC, + LogicalSize(parent->GetWritingMode(), 0, + NS_UNCONSTRAINEDSIZE), + ReflowInput::DUMMY_PARENT_REFLOW_STATE); + rs = dummyParentState.ptr(); + } +#ifdef DEBUG + // This will suppress various CRAZY_SIZE warnings for this reflow. + parent->Properties().Set( + nsContainerFrame::DebugReflowingWithInfiniteISize(), true); +#endif + uint32_t riFlags = ReflowInput::COMPUTE_SIZE_SHRINK_WRAP | + ReflowInput::COMPUTE_SIZE_USE_AUTO_BSIZE; + if (aIMinSizeClamp != NS_MAXSIZE) { + riFlags |= ReflowInput::I_CLAMP_MARGIN_BOX_MIN_SIZE; + } + if (aBMinSizeClamp != NS_MAXSIZE) { + riFlags |= ReflowInput::B_CLAMP_MARGIN_BOX_MIN_SIZE; + aChild->Properties().Set(nsIFrame::BClampMarginBoxMinSizeProperty(), + aBMinSizeClamp); + } else { + aChild->Properties().Delete(nsIFrame::BClampMarginBoxMinSizeProperty()); + } + ReflowInput childRI(pc, *rs, aChild, aAvailableSize, &aCBSize, riFlags); + ReflowOutput childSize(childRI); + nsReflowStatus childStatus; + const uint32_t flags = NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_SIZE_VIEW; + WritingMode wm = childRI.GetWritingMode(); + parent->ReflowChild(aChild, pc, childSize, childRI, wm, + LogicalPoint(wm), nsSize(), flags, childStatus); + parent->FinishReflowChild(aChild, pc, childSize, &childRI, wm, + LogicalPoint(wm), nsSize(), flags); +#ifdef DEBUG + parent->Properties().Delete(nsContainerFrame::DebugReflowingWithInfiniteISize()); +#endif + return childSize.BSize(wm); +} + +/** + * Return the [min|max]-content contribution of aChild to its parent (i.e. + * the child's margin-box) in aAxis. + */ +static nscoord +ContentContribution(const GridItemInfo& aGridItem, + const GridReflowInput& aState, + nsRenderingContext* aRC, + WritingMode aCBWM, + LogicalAxis aAxis, + IntrinsicISizeType aConstraint, + nscoord aMinSizeClamp = NS_MAXSIZE, + uint32_t aFlags = 0) +{ + nsIFrame* child = aGridItem.mFrame; + PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis)); + nscoord size = nsLayoutUtils::IntrinsicForAxis(axis, aRC, child, aConstraint, + aFlags | nsLayoutUtils::BAIL_IF_REFLOW_NEEDED | + nsLayoutUtils::ADD_PERCENTS, + aMinSizeClamp); + if (size == NS_INTRINSIC_WIDTH_UNKNOWN) { + // We need to reflow the child to find its BSize contribution. + // XXX this will give mostly correct results for now (until bug 1174569). + nscoord availISize = INFINITE_ISIZE_COORD; + nscoord availBSize = NS_UNCONSTRAINEDSIZE; + auto childWM = child->GetWritingMode(); + const bool isOrthogonal = childWM.IsOrthogonalTo(aCBWM); + // The next two variables are MinSizeClamp values in the child's axes. + nscoord iMinSizeClamp = NS_MAXSIZE; + nscoord bMinSizeClamp = NS_MAXSIZE; + LogicalSize cbSize(childWM, 0, 0); + if (aState.mCols.mCanResolveLineRangeSize) { + nscoord sz = aState.mCols.ResolveSize(aGridItem.mArea.mCols); + if (isOrthogonal) { + availBSize = sz; + cbSize.BSize(childWM) = sz; + if (aGridItem.mState[aAxis] & ItemState::eClampMarginBoxMinSize) { + bMinSizeClamp = sz; + } + } else { + availISize = sz; + cbSize.ISize(childWM) = sz; + if (aGridItem.mState[aAxis] & ItemState::eClampMarginBoxMinSize) { + iMinSizeClamp = sz; + } + } + } + if (isOrthogonal == (aAxis == eLogicalAxisInline)) { + bMinSizeClamp = aMinSizeClamp; + } else { + iMinSizeClamp = aMinSizeClamp; + } + LogicalSize availableSize(childWM, availISize, availBSize); + size = ::MeasuringReflow(child, aState.mReflowInput, aRC, availableSize, + cbSize, iMinSizeClamp, bMinSizeClamp); + nsIFrame::IntrinsicISizeOffsetData offsets = child->IntrinsicBSizeOffsets(); + size += offsets.hMargin; + auto percent = offsets.hPctMargin; + if (availBSize == NS_UNCONSTRAINEDSIZE) { + // We always want to add in percent padding too, unless we already did so + // using a resolved column size above. + percent += offsets.hPctPadding; + } + size = nsLayoutUtils::AddPercents(size, percent); + nscoord overflow = size - aMinSizeClamp; + if (MOZ_UNLIKELY(overflow > 0)) { + nscoord contentSize = child->ContentBSize(childWM); + nscoord newContentSize = std::max(nscoord(0), contentSize - overflow); + // XXXmats deal with percentages better, see bug 1300369 comment 27. + size -= contentSize - newContentSize; + } + } + MOZ_ASSERT(aGridItem.mBaselineOffset[aAxis] >= 0, + "baseline offset should be non-negative at this point"); + MOZ_ASSERT((aGridItem.mState[aAxis] & ItemState::eIsBaselineAligned) || + aGridItem.mBaselineOffset[aAxis] == nscoord(0), + "baseline offset should be zero when not baseline-aligned"); + size += aGridItem.mBaselineOffset[aAxis]; + return std::max(size, 0); +} + +struct CachedIntrinsicSizes +{ + Maybe<nscoord> mMinSize; + Maybe<nscoord> mMinContentContribution; + Maybe<nscoord> mMaxContentContribution; + // "if the grid item spans only grid tracks that have a fixed max track + // sizing function, its automatic minimum size in that dimension is + // further clamped to less than or equal to the size necessary to fit its + // margin box within the resulting grid area (flooring at zero)" + // https://drafts.csswg.org/css-grid/#min-size-auto + // This is the clamp value to use for that: + nscoord mMinSizeClamp = NS_MAXSIZE; +}; + +static nscoord +MinContentContribution(const GridItemInfo& aGridItem, + const GridReflowInput& aState, + nsRenderingContext* aRC, + WritingMode aCBWM, + LogicalAxis aAxis, + CachedIntrinsicSizes* aCache) +{ + if (aCache->mMinContentContribution.isSome()) { + return aCache->mMinContentContribution.value(); + } + nscoord s = ContentContribution(aGridItem, aState, aRC, aCBWM, aAxis, + nsLayoutUtils::MIN_ISIZE, + aCache->mMinSizeClamp); + aCache->mMinContentContribution.emplace(s); + return s; +} + +static nscoord +MaxContentContribution(const GridItemInfo& aGridItem, + const GridReflowInput& aState, + nsRenderingContext* aRC, + WritingMode aCBWM, + LogicalAxis aAxis, + CachedIntrinsicSizes* aCache) +{ + if (aCache->mMaxContentContribution.isSome()) { + return aCache->mMaxContentContribution.value(); + } + nscoord s = ContentContribution(aGridItem, aState, aRC, aCBWM, aAxis, + nsLayoutUtils::PREF_ISIZE, + aCache->mMinSizeClamp); + aCache->mMaxContentContribution.emplace(s); + return s; +} + +// Computes the min-size contribution for a grid item, as defined at +// https://drafts.csswg.org/css-grid/#min-size-contributions +static nscoord +MinSize(const GridItemInfo& aGridItem, + const GridReflowInput& aState, + nsRenderingContext* aRC, + WritingMode aCBWM, + LogicalAxis aAxis, + CachedIntrinsicSizes* aCache) +{ + if (aCache->mMinSize.isSome()) { + return aCache->mMinSize.value(); + } + nsIFrame* child = aGridItem.mFrame; + PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis)); + const nsStylePosition* stylePos = child->StylePosition(); + const nsStyleCoord& sizeStyle = + axis == eAxisHorizontal ? stylePos->mWidth : stylePos->mHeight; + if (sizeStyle.GetUnit() != eStyleUnit_Auto) { + nscoord s = + MinContentContribution(aGridItem, aState, aRC, aCBWM, aAxis, aCache); + aCache->mMinSize.emplace(s); + return s; + } + + // 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). + MOZ_ASSERT(aGridItem.mBaselineOffset[aAxis] >= 0, + "baseline offset should be non-negative at this point"); + MOZ_ASSERT((aGridItem.mState[aAxis] & ItemState::eIsBaselineAligned) || + aGridItem.mBaselineOffset[aAxis] == nscoord(0), + "baseline offset should be zero when not baseline-aligned"); + nscoord sz = aGridItem.mBaselineOffset[aAxis] + + nsLayoutUtils::MinSizeContributionForAxis(axis, aRC, child, + nsLayoutUtils::MIN_ISIZE); + const nsStyleCoord& style = axis == eAxisHorizontal ? stylePos->mMinWidth + : stylePos->mMinHeight; + auto unit = style.GetUnit(); + if (unit == eStyleUnit_Enumerated || + (unit == eStyleUnit_Auto && + child->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(aGridItem, aState, aRC, aCBWM, aAxis, + nsLayoutUtils::MIN_ISIZE, + aCache->mMinSizeClamp, + nsLayoutUtils::MIN_INTRINSIC_ISIZE)); + } + aCache->mMinSize.emplace(sz); + return sz; +} + +void +nsGridContainerFrame::Tracks::CalculateSizes( + GridReflowInput& aState, + nsTArray<GridItemInfo>& aGridItems, + const TrackSizingFunctions& aFunctions, + nscoord aContentBoxSize, + LineRange GridArea::* aRange, + SizingConstraint aConstraint) +{ + nscoord percentageBasis = aContentBoxSize; + if (percentageBasis == NS_UNCONSTRAINEDSIZE) { + percentageBasis = 0; + } + InitializeItemBaselines(aState, aGridItems); + ResolveIntrinsicSize(aState, aGridItems, aFunctions, aRange, percentageBasis, + aConstraint); + if (aConstraint != SizingConstraint::eMinContent) { + nscoord freeSpace = aContentBoxSize; + if (freeSpace != NS_UNCONSTRAINEDSIZE) { + freeSpace -= SumOfGridGaps(); + } + DistributeFreeSpace(freeSpace); + StretchFlexibleTracks(aState, aGridItems, aFunctions, freeSpace); + } +} + +bool +nsGridContainerFrame::Tracks::HasIntrinsicButNoFlexSizingInRange( + const LineRange& aRange, + 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; + 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( + GridReflowInput& aState, + const TrackSizingFunctions& aFunctions, + nscoord aPercentageBasis, + SizingConstraint aConstraint, + const LineRange& aRange, + const GridItemInfo& aGridItem) +{ + CachedIntrinsicSizes cache; + TrackSize& sz = mSizes[aRange.mStart]; + WritingMode wm = aState.mWM; + // Calculate data for "Automatic Minimum Size" clamping, if needed. + bool needed = ((sz.mState & TrackSize::eIntrinsicMinSizing) || + aConstraint == SizingConstraint::eNoConstraint); + if (needed && TrackSize::IsDefiniteMaxSizing(sz.mState) && + aGridItem.ShouldClampMinSize(wm, mAxis, aPercentageBasis)) { + if (sz.mState & TrackSize::eIntrinsicMinSizing) { + auto maxCoord = aFunctions.MaxSizingFor(aRange.mStart); + cache.mMinSizeClamp = + nsRuleNode::ComputeCoordPercentCalc(maxCoord, aPercentageBasis); + } + aGridItem.mState[mAxis] |= ItemState::eClampMarginBoxMinSize; + } + // min sizing + nsRenderingContext* rc = &aState.mRenderingContext; + if (sz.mState & TrackSize::eAutoMinSizing) { + nscoord s; + if (aConstraint == SizingConstraint::eMinContent) { + s = MinContentContribution(aGridItem, aState, rc, wm, mAxis, &cache); + } else if (aConstraint == SizingConstraint::eMaxContent) { + s = MaxContentContribution(aGridItem, aState, rc, wm, mAxis, &cache); + } else { + MOZ_ASSERT(aConstraint == SizingConstraint::eNoConstraint); + s = MinSize(aGridItem, aState, rc, wm, mAxis, &cache); + } + sz.mBase = std::max(sz.mBase, s); + } else if (sz.mState & TrackSize::eMinContentMinSizing) { + auto s = MinContentContribution(aGridItem, aState, rc, wm, mAxis, &cache); + sz.mBase = std::max(sz.mBase, s); + } else if (sz.mState & TrackSize::eMaxContentMinSizing) { + auto s = MaxContentContribution(aGridItem, aState, rc, wm, mAxis, &cache); + sz.mBase = std::max(sz.mBase, s); + } + // max sizing + if (sz.mState & TrackSize::eMinContentMaxSizing) { + auto s = MinContentContribution(aGridItem, aState, rc, wm, mAxis, &cache); + if (sz.mLimit == NS_UNCONSTRAINEDSIZE) { + sz.mLimit = s; + } else { + sz.mLimit = std::max(sz.mLimit, s); + } + } else if (sz.mState & (TrackSize::eAutoMaxSizing | + TrackSize::eMaxContentMaxSizing)) { + auto s = MaxContentContribution(aGridItem, aState, rc, wm, mAxis, &cache); + if (sz.mLimit == NS_UNCONSTRAINEDSIZE) { + sz.mLimit = s; + } else { + sz.mLimit = std::max(sz.mLimit, s); + } + if (MOZ_UNLIKELY(sz.mState & TrackSize::eFitContent)) { + // Clamp mLimit to the fit-content() size, for §12.5.1. + auto maxCoord = aFunctions.MaxSizingFor(aRange.mStart); + nscoord fitContentClamp = + nsRuleNode::ComputeCoordPercentCalc(maxCoord, aPercentageBasis); + sz.mLimit = std::min(sz.mLimit, fitContentClamp); + } + } + if (sz.mLimit < sz.mBase) { + sz.mLimit = sz.mBase; + } + return sz.mState & TrackSize::eFlexMaxSizing; +} + +void +nsGridContainerFrame::Tracks::CalculateItemBaselines( + nsTArray<ItemBaselineData>& aBaselineItems, + BaselineSharingGroup aBaselineGroup) +{ + if (aBaselineItems.IsEmpty()) { + return; + } + + // Sort the collected items on their baseline track. + std::sort(aBaselineItems.begin(), aBaselineItems.end(), + ItemBaselineData::IsBaselineTrackLessThan); + + MOZ_ASSERT(mSizes.Length() > 0, "having an item implies at least one track"); + const uint32_t lastTrack = mSizes.Length() - 1; + nscoord maxBaseline = 0; + nscoord maxDescent = 0; + uint32_t currentTrack = kAutoLine; // guaranteed to not match any item + uint32_t trackStartIndex = 0; + for (uint32_t i = 0, len = aBaselineItems.Length(); true ; ++i) { + // Find the maximum baseline and descent in the current track. + if (i != len) { + const ItemBaselineData& item = aBaselineItems[i]; + if (currentTrack == item.mBaselineTrack) { + maxBaseline = std::max(maxBaseline, item.mBaseline); + maxDescent = std::max(maxDescent, item.mSize - item.mBaseline); + continue; + } + } + // Iterate the current track again and update the baseline offsets making + // all items baseline-aligned within this group in this track. + for (uint32_t j = trackStartIndex; j < i; ++j) { + const ItemBaselineData& item = aBaselineItems[j]; + item.mGridItem->mBaselineOffset[mAxis] = maxBaseline - item.mBaseline; + MOZ_ASSERT(item.mGridItem->mBaselineOffset[mAxis] >= 0); + } + if (i != 0) { + // Store the size of this baseline-aligned subtree. + mSizes[currentTrack].mBaselineSubtreeSize[aBaselineGroup] = + maxBaseline + maxDescent; + // Record the first(last) baseline for the first(last) track. + if (currentTrack == 0 && aBaselineGroup == BaselineSharingGroup::eFirst) { + mBaseline[aBaselineGroup] = maxBaseline; + } + if (currentTrack == lastTrack && + aBaselineGroup == BaselineSharingGroup::eLast) { + mBaseline[aBaselineGroup] = maxBaseline; + } + } + if (i == len) { + break; + } + // Initialize data for the next track with baseline-aligned items. + const ItemBaselineData& item = aBaselineItems[i]; + currentTrack = item.mBaselineTrack; + trackStartIndex = i; + maxBaseline = item.mBaseline; + maxDescent = item.mSize - item.mBaseline; + } +} + +void +nsGridContainerFrame::Tracks::InitializeItemBaselines( + GridReflowInput& aState, + nsTArray<GridItemInfo>& aGridItems) +{ + + nsTArray<ItemBaselineData> firstBaselineItems; + nsTArray<ItemBaselineData> lastBaselineItems; + WritingMode wm = aState.mWM; + nsStyleContext* containerSC = aState.mFrame->StyleContext(); + GridItemCSSOrderIterator& iter = aState.mIter; + iter.Reset(); + for (; !iter.AtEnd(); iter.Next()) { + nsIFrame* child = *iter; + GridItemInfo& gridItem = aGridItems[iter.GridItemIndex()]; + uint32_t baselineTrack = kAutoLine; + auto state = ItemState(0); + auto childWM = child->GetWritingMode(); + const bool isOrthogonal = wm.IsOrthogonalTo(childWM); + const bool isInlineAxis = mAxis == eLogicalAxisInline; // i.e. columns + // XXX update the line below to include orthogonal grid/table boxes + // XXX since they have baselines in both dimensions. And flexbox with + // XXX reversed main/cross axis? + const bool itemHasBaselineParallelToTrack = isInlineAxis == isOrthogonal; + if (itemHasBaselineParallelToTrack) { + // [align|justify]-self:[last ]baseline. + auto selfAlignment = isOrthogonal ? + child->StylePosition()->UsedJustifySelf(containerSC) : + child->StylePosition()->UsedAlignSelf(containerSC); + selfAlignment &= ~NS_STYLE_ALIGN_FLAG_BITS; + if (selfAlignment == NS_STYLE_ALIGN_BASELINE) { + state |= ItemState::eFirstBaseline | ItemState::eSelfBaseline; + const GridArea& area = gridItem.mArea; + baselineTrack = isInlineAxis ? area.mCols.mStart : area.mRows.mStart; + } else if (selfAlignment == NS_STYLE_ALIGN_LAST_BASELINE) { + state |= ItemState::eLastBaseline | ItemState::eSelfBaseline; + const GridArea& area = gridItem.mArea; + baselineTrack = (isInlineAxis ? area.mCols.mEnd : area.mRows.mEnd) - 1; + } + + // [align|justify]-content:[last ]baseline. + // https://drafts.csswg.org/css-align-3/#baseline-align-content + // "[...] and its computed 'align-self' or 'justify-self' (whichever + // affects its block axis) is 'stretch' or 'self-start' ('self-end'). + // For this purpose, the 'start', 'end', 'flex-start', and 'flex-end' + // values of 'align-self' are treated as either 'self-start' or + // 'self-end', whichever they end up equivalent to. + auto alignContent = child->StylePosition()->mAlignContent; + alignContent &= ~NS_STYLE_ALIGN_FLAG_BITS; + if (alignContent == NS_STYLE_ALIGN_BASELINE || + alignContent == NS_STYLE_ALIGN_LAST_BASELINE) { + const auto selfAlignEdge = alignContent == NS_STYLE_ALIGN_BASELINE ? + NS_STYLE_ALIGN_SELF_START : NS_STYLE_ALIGN_SELF_END; + bool validCombo = selfAlignment == NS_STYLE_ALIGN_NORMAL || + selfAlignment == NS_STYLE_ALIGN_STRETCH || + selfAlignment == selfAlignEdge; + if (!validCombo) { + // We're doing alignment in the axis that's orthogonal to mAxis here. + LogicalAxis alignAxis = GetOrthogonalAxis(mAxis); + // |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 = wm.ParallelAxisStartsOnSameSide(alignAxis, childWM); + switch (selfAlignment) { + case NS_STYLE_ALIGN_LEFT: + selfAlignment = !isInlineAxis || wm.IsBidiLTR() ? NS_STYLE_ALIGN_START + : NS_STYLE_ALIGN_END; + break; + case NS_STYLE_ALIGN_RIGHT: + selfAlignment = isInlineAxis && wm.IsBidiLTR() ? NS_STYLE_ALIGN_END + : NS_STYLE_ALIGN_START; + break; + } + switch (selfAlignment) { + case NS_STYLE_ALIGN_START: + case NS_STYLE_ALIGN_FLEX_START: + validCombo = sameSide == + (alignContent == NS_STYLE_ALIGN_BASELINE); + break; + case NS_STYLE_ALIGN_END: + case NS_STYLE_ALIGN_FLEX_END: + validCombo = sameSide == + (alignContent == NS_STYLE_ALIGN_LAST_BASELINE); + break; + } + } + if (validCombo) { + const GridArea& area = gridItem.mArea; + if (alignContent == NS_STYLE_ALIGN_BASELINE) { + state |= ItemState::eFirstBaseline | ItemState::eContentBaseline; + baselineTrack = isInlineAxis ? area.mCols.mStart : area.mRows.mStart; + } else if (alignContent == NS_STYLE_ALIGN_LAST_BASELINE) { + state |= ItemState::eLastBaseline | ItemState::eContentBaseline; + baselineTrack = (isInlineAxis ? area.mCols.mEnd : area.mRows.mEnd) - 1; + } + } + } + } + + if (state & ItemState::eIsBaselineAligned) { + // XXX available size issue + LogicalSize avail(childWM, INFINITE_ISIZE_COORD, NS_UNCONSTRAINEDSIZE); + auto* rc = &aState.mRenderingContext; + // XXX figure out if we can avoid/merge this reflow with the main reflow. + // XXX (after bug 1174569 is sorted out) + // + // XXX How should we handle percentage padding here? (bug 1330866) + // XXX (see ::ContentContribution and how it deals with percentages) + // XXX What if the true baseline after line-breaking differs from this + // XXX hypothetical baseline based on an infinite inline size? + // XXX Maybe we should just call ::ContentContribution here instead? + // XXX For now we just pass a zero-sized CB: + LogicalSize cbSize(childWM, 0, 0); + ::MeasuringReflow(child, aState.mReflowInput, rc, avail, cbSize); + nscoord baseline; + nsGridContainerFrame* grid = do_QueryFrame(child); + if (state & ItemState::eFirstBaseline) { + if (grid) { + if (isOrthogonal == isInlineAxis) { + grid->GetBBaseline(BaselineSharingGroup::eFirst, &baseline); + } else { + grid->GetIBaseline(BaselineSharingGroup::eFirst, &baseline); + } + } + if (grid || + nsLayoutUtils::GetFirstLineBaseline(wm, child, &baseline)) { + NS_ASSERTION(baseline != NS_INTRINSIC_WIDTH_UNKNOWN, + "about to use an unknown baseline"); + auto frameSize = isInlineAxis ? child->ISize(wm) : child->BSize(wm); + auto m = child->GetLogicalUsedMargin(wm); + baseline += isInlineAxis ? m.IStart(wm) : m.BStart(wm); + auto alignSize = frameSize + (isInlineAxis ? m.IStartEnd(wm) + : m.BStartEnd(wm)); + firstBaselineItems.AppendElement(ItemBaselineData( + { baselineTrack, baseline, alignSize, &gridItem })); + } else { + state &= ~ItemState::eAllBaselineBits; + } + } else { + if (grid) { + if (isOrthogonal == isInlineAxis) { + grid->GetBBaseline(BaselineSharingGroup::eLast, &baseline); + } else { + grid->GetIBaseline(BaselineSharingGroup::eLast, &baseline); + } + } + if (grid || + nsLayoutUtils::GetLastLineBaseline(wm, child, &baseline)) { + NS_ASSERTION(baseline != NS_INTRINSIC_WIDTH_UNKNOWN, + "about to use an unknown baseline"); + auto frameSize = isInlineAxis ? child->ISize(wm) : child->BSize(wm); + auto m = child->GetLogicalUsedMargin(wm); + if (!grid) { + // Convert to distance from border-box end. + baseline = frameSize - baseline; + } + auto descent = baseline + (isInlineAxis ? m.IEnd(wm) : m.BEnd(wm)); + auto alignSize = frameSize + (isInlineAxis ? m.IStartEnd(wm) + : m.BStartEnd(wm)); + lastBaselineItems.AppendElement(ItemBaselineData( + { baselineTrack, descent, alignSize, &gridItem })); + } else { + state &= ~ItemState::eAllBaselineBits; + } + } + } + MOZ_ASSERT((state & + (ItemState::eFirstBaseline | ItemState::eLastBaseline)) != + (ItemState::eFirstBaseline | ItemState::eLastBaseline), + "first/last baseline bits are mutually exclusive"); + MOZ_ASSERT((state & + (ItemState::eSelfBaseline | ItemState::eContentBaseline)) != + (ItemState::eSelfBaseline | ItemState::eContentBaseline), + "*-self and *-content baseline bits are mutually exclusive"); + MOZ_ASSERT(!(state & + (ItemState::eFirstBaseline | ItemState::eLastBaseline)) == + !(state & + (ItemState::eSelfBaseline | ItemState::eContentBaseline)), + "first/last bit requires self/content bit and vice versa"); + gridItem.mState[mAxis] = state; + gridItem.mBaselineOffset[mAxis] = nscoord(0); + } + + if (firstBaselineItems.IsEmpty() && lastBaselineItems.IsEmpty()) { + return; + } + + // TODO: CSS Align spec issue - how to align a baseline subtree in a track? + // https://lists.w3.org/Archives/Public/www-style/2016May/0141.html + mBaselineSubtreeAlign[BaselineSharingGroup::eFirst] = NS_STYLE_ALIGN_START; + mBaselineSubtreeAlign[BaselineSharingGroup::eLast] = NS_STYLE_ALIGN_END; + + CalculateItemBaselines(firstBaselineItems, BaselineSharingGroup::eFirst); + CalculateItemBaselines(lastBaselineItems, BaselineSharingGroup::eLast); +} + +void +nsGridContainerFrame::Tracks::AlignBaselineSubtree( + const GridItemInfo& aGridItem) const +{ + auto state = aGridItem.mState[mAxis]; + if (!(state & ItemState::eIsBaselineAligned)) { + return; + } + const GridArea& area = aGridItem.mArea; + int32_t baselineTrack; + const bool isFirstBaseline = state & ItemState::eFirstBaseline; + if (isFirstBaseline) { + baselineTrack = mAxis == eLogicalAxisBlock ? area.mRows.mStart + : area.mCols.mStart; + } else { + baselineTrack = (mAxis == eLogicalAxisBlock ? area.mRows.mEnd + : area.mCols.mEnd) - 1; + } + const TrackSize& sz = mSizes[baselineTrack]; + auto baselineGroup = isFirstBaseline ? BaselineSharingGroup::eFirst + : BaselineSharingGroup::eLast; + nscoord delta = sz.mBase - sz.mBaselineSubtreeSize[baselineGroup]; + const auto subtreeAlign = mBaselineSubtreeAlign[baselineGroup]; + switch (subtreeAlign) { + case NS_STYLE_ALIGN_START: + if (state & ItemState::eLastBaseline) { + aGridItem.mBaselineOffset[mAxis] += delta; + } + break; + case NS_STYLE_ALIGN_END: + if (isFirstBaseline) { + aGridItem.mBaselineOffset[mAxis] += delta; + } + break; + case NS_STYLE_ALIGN_CENTER: + aGridItem.mBaselineOffset[mAxis] += delta / 2; + break; + default: + MOZ_ASSERT_UNREACHABLE("unexpected baseline subtree alignment"); + } +} + +void +nsGridContainerFrame::Tracks::ResolveIntrinsicSize( + GridReflowInput& aState, + nsTArray<GridItemInfo>& aGridItems, + const TrackSizingFunctions& aFunctions, + LineRange GridArea::* aRange, + nscoord aPercentageBasis, + SizingConstraint 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 eIsFlexing on the item state here to speed up + // FindUsedFlexFraction later. + AutoTArray<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 + // Setup track selector for step 2.2: + const auto contentBasedMinSelector = + aConstraint == SizingConstraint::eMinContent ? + TrackSize::eIntrinsicMinSizing : TrackSize::eMinOrMaxContentMinSizing; + // Setup track selector for step 2.3: + const auto maxContentMinSelector = + aConstraint == SizingConstraint::eMaxContent ? + (TrackSize::eMaxContentMinSizing | TrackSize::eAutoMinSizing) : + TrackSize::eMaxContentMinSizing; + iter.Reset(); + for (; !iter.AtEnd(); iter.Next()) { + auto& gridItem = aGridItems[iter.GridItemIndex()]; + const GridArea& area = gridItem.mArea; + const LineRange& lineRange = area.*aRange; + uint32_t span = lineRange.Extent(); + if (span == 1) { + // Step 1. Size tracks to fit non-spanning items. + if (ResolveIntrinsicSizeStep1(aState, aFunctions, aPercentageBasis, + aConstraint, lineRange, gridItem)) { + gridItem.mState[mAxis] |= ItemState::eIsFlexing; + } + } else { + TrackSize::StateBits state = TrackSize::StateBits(0); + if (HasIntrinsicButNoFlexSizingInRange(lineRange, &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; + CachedIntrinsicSizes cache; + // Calculate data for "Automatic Minimum Size" clamping, if needed. + bool needed = ((state & TrackSize::eIntrinsicMinSizing) || + aConstraint == SizingConstraint::eNoConstraint); + if (needed && TrackSize::IsDefiniteMaxSizing(state) && + gridItem.ShouldClampMinSize(wm, mAxis, aPercentageBasis)) { + nscoord minSizeClamp = 0; + for (auto i = lineRange.mStart, end = lineRange.mEnd; i < end; ++i) { + auto maxCoord = aFunctions.MaxSizingFor(i); + minSizeClamp += + nsRuleNode::ComputeCoordPercentCalc(maxCoord, aPercentageBasis); + } + minSizeClamp += mGridGap * (span - 1); + cache.mMinSizeClamp = minSizeClamp; + gridItem.mState[mAxis] |= ItemState::eClampMarginBoxMinSize; + } + // Collect the various grid item size contributions we need. + nscoord minSize = 0; + if (state & (TrackSize::eIntrinsicMinSizing | // for 2.1 + TrackSize::eIntrinsicMaxSizing)) { // for 2.5 + minSize = MinSize(gridItem, aState, rc, wm, mAxis, &cache); + } + nscoord minContent = 0; + if (state & contentBasedMinSelector) { // for 2.2 + minContent = MinContentContribution(gridItem, aState, + rc, wm, mAxis, &cache); + } + nscoord maxContent = 0; + if (state & (maxContentMinSelector | // for 2.3 + TrackSize::eAutoOrMaxContentMaxSizing)) { // for 2.6 + maxContent = MaxContentContribution(gridItem, aState, + rc, wm, mAxis, &cache); + } + step2Items.AppendElement( + Step2ItemData({span, state, lineRange, minSize, + minContent, maxContent, *iter})); + } else { + if (state & TrackSize::eFlexMaxSizing) { + gridItem.mState[mAxis] |= ItemState::eIsFlexing; + } else if (aConstraint == SizingConstraint::eNoConstraint && + TrackSize::IsDefiniteMaxSizing(state) && + gridItem.ShouldClampMinSize(wm, mAxis, aPercentageBasis)) { + gridItem.mState[mAxis] |= ItemState::eClampMarginBoxMinSize; + } + } + } + } + + // 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(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 = contentBasedMinSelector; + if (stateBitsPerSpan[span] & selector) { + // Step 2.2 MinContentContribution to min-/max-content (and 'auto' when + // sizing under a min-content constraint) 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; + } + } + } + + selector = maxContentMinSelector; + if (stateBitsPerSpan[span] & selector) { + // Step 2.3 MaxContentContribution to max-content (and 'auto' when + // sizing under a max-content constraint) min-sizing. + for (i = spanGroupStartIndex; i < spanGroupEndIndex; ++i) { + Step2ItemData& item = step2Items[i]; + if (!(item.mState & selector)) { + continue; + } + nscoord space = item.mMaxContentContribution; + 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 (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 MinSize to intrinsic max-sizing. + for (i = spanGroupStartIndex; i < spanGroupEndIndex; ++i) { + Step2ItemData& item = step2Items[i]; + if (!(item.mState & TrackSize::eIntrinsicMaxSizing)) { + continue; + } + nscoord space = item.mMinSize; + if (space <= 0) { + continue; + } + tracks.ClearAndRetainStorage(); + space = CollectGrowable(space, plan, item.mLineRange, + TrackSize::eIntrinsicMaxSizing, + tracks); + if (space > 0) { + DistributeToTrackLimits(space, plan, tracks, aFunctions, + aPercentageBasis); + } + } + 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, aFunctions, + aPercentageBasis); + } + } + } + } + } + } + + // 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( + GridReflowInput& 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 (which is + // floored at 1). + float fr = 0.0f; + for (uint32_t track : aFlexTracks) { + float flexFactor = aFunctions.MaxSizingFor(track).GetFlexFractionValue(); + float possiblyDividedBaseSize = (flexFactor > 1.0f) + ? mSizes[track].mBase / flexFactor + : mSizes[track].mBase; + fr = std::max(fr, possiblyDividedBaseSize); + } + WritingMode wm = aState.mWM; + nsRenderingContext* rc = &aState.mRenderingContext; + 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.mState[mAxis] & ItemState::eIsFlexing) { + // XXX optimize: bug 1194446 + nscoord spaceToFill = ContentContribution(item, aState, rc, wm, mAxis, + nsLayoutUtils::PREF_ISIZE); + 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( + GridReflowInput& 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; + } + nscoord minSize = 0; + nscoord maxSize = NS_UNCONSTRAINEDSIZE; + if (aState.mReflowInput) { + auto* ri = aState.mReflowInput; + minSize = mAxis == eLogicalAxisBlock ? ri->ComputedMinBSize() + : ri->ComputedMinISize(); + maxSize = mAxis == eLogicalAxisBlock ? ri->ComputedMaxBSize() + : ri->ComputedMaxISize(); + } + Maybe<nsTArray<TrackSize>> origSizes; + // We iterate twice at most. The 2nd time if the grid size changed after + // applying a min/max-size (can only occur if aAvailableSize is indefinite). + while (true) { + float fr = FindUsedFlexFraction(aState, aGridItems, flexTracks, + aFunctions, aAvailableSize); + if (fr != 0.0f) { + bool applyMinMax = (minSize != 0 || maxSize != NS_UNCONSTRAINEDSIZE) && + aAvailableSize == NS_UNCONSTRAINEDSIZE; + for (uint32_t i : flexTracks) { + float flexFactor = aFunctions.MaxSizingFor(i).GetFlexFractionValue(); + nscoord flexLength = NSToCoordRound(flexFactor * fr); + nscoord& base = mSizes[i].mBase; + if (flexLength > base) { + if (applyMinMax && origSizes.isNothing()) { + origSizes.emplace(mSizes); + } + base = flexLength; + } + } + if (applyMinMax && origSizes.isSome()) { + // https://drafts.csswg.org/css-grid/#algo-flex-tracks + // "If using this flex fraction would cause the grid to be smaller than + // the grid container’s min-width/height (or larger than the grid + // container’s max-width/height), then redo this step, treating the free + // space as definite [...]" + nscoord newSize = 0; + for (auto& sz : mSizes) { + newSize += sz.mBase; + } + const auto sumOfGridGaps = SumOfGridGaps(); + newSize += sumOfGridGaps; + if (newSize > maxSize) { + aAvailableSize = maxSize; + } else if (newSize < minSize) { + aAvailableSize = minSize; + } + if (aAvailableSize != NS_UNCONSTRAINEDSIZE) { + // Reset min/max-size to ensure 'applyMinMax' becomes false next time. + minSize = 0; + maxSize = NS_UNCONSTRAINEDSIZE; + aAvailableSize = std::max(0, aAvailableSize - sumOfGridGaps); + // Restart with the original track sizes and definite aAvailableSize. + mSizes = Move(*origSizes); + origSizes.reset(); + if (aAvailableSize == 0) { + break; // zero available size wouldn't change any sizes though... + } + continue; + } + } + } + break; + } +} + +void +nsGridContainerFrame::Tracks::AlignJustifyContent( + const nsStylePosition* aStyle, + WritingMode aWM, + const LogicalSize& aContainerSize) +{ + if (mSizes.IsEmpty()) { + return; + } + + const bool isAlign = mAxis == eLogicalAxisBlock; + auto valueAndFallback = isAlign ? aStyle->mAlignContent : + aStyle->mJustifyContent; + bool overflowSafe; + auto alignment = ::GetAlignJustifyValue(valueAndFallback, aWM, isAlign, + &overflowSafe); + if (alignment == NS_STYLE_ALIGN_NORMAL) { + MOZ_ASSERT(valueAndFallback == NS_STYLE_ALIGN_NORMAL, + "*-content:normal cannot be specified with explicit fallback"); + alignment = NS_STYLE_ALIGN_STRETCH; + valueAndFallback = alignment; // we may need a fallback for 'stretch' below + } + + // 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(aWM) + : aContainerSize.ISize(aWM); + 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, aWM, + 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: 'first/last baseline' (bug 1151204)"); // XXX + MOZ_FALLTHROUGH; + 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?"); +} + +nscoord +nsGridContainerFrame::Tracks::BackComputedIntrinsicSize( + const TrackSizingFunctions& aFunctions, + const nsStyleCoord& aGridGap) const +{ + // Sum up the current sizes (where percentage tracks were treated as 'auto') + // in 'size'. + nscoord size = 0; + for (size_t i = 0, len = mSizes.Length(); i < len; ++i) { + size += mSizes[i].mBase; + } + + // Add grid-gap contributions to 'size' and calculate a 'percent' sum. + float percent = 0.0f; + size_t numTracks = mSizes.Length(); + if (numTracks > 1) { + const size_t gridGapCount = numTracks - 1; + nscoord gridGapLength; + float gridGapPercent; + if (::GetPercentSizeParts(aGridGap, &gridGapLength, &gridGapPercent)) { + percent = gridGapCount * gridGapPercent; + } else { + gridGapLength = aGridGap.ToLength(); + } + size += gridGapCount * gridGapLength; + } + + return std::max(0, nsLayoutUtils::AddPercents(size, percent)); +} + +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 Tracks& aTracks, 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; + auto side = mStart == aTracks.mSizes.Length() ? GridLineSide::eBeforeGridGap + : GridLineSide::eAfterGridGap; + nscoord startPos = aTracks.GridLineEdge(mStart, side); + *aPos = aGridOrigin + startPos; + *aLength = std::max(endPos - *aPos, 0); + } + } else { + if (mStart == kAutoLine) { + auto side = mEnd == 0 ? GridLineSide::eAfterGridGap + : GridLineSide::eBeforeGridGap; + nscoord endPos = aTracks.GridLineEdge(mEnd, side); + *aLength = std::max(aGridOrigin + endPos, 0); + } else { + nscoord pos; + ToPositionAndLength(aTracks.mSizes, &pos, aLength); + *aPos = aGridOrigin + pos; + } + } +} + +LogicalRect +nsGridContainerFrame::GridReflowInput::ContainingBlockFor(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(mCols.mSizes, &i, &iSize); + aArea.mRows.ToPositionAndLength(mRows.mSizes, &b, &bSize); + return LogicalRect(mWM, i, b, iSize, bSize); +} + +LogicalRect +nsGridContainerFrame::GridReflowInput::ContainingBlockForAbsPos( + const GridArea& aArea, + const LogicalPoint& aGridOrigin, + const LogicalRect& aGridCB) const +{ + nscoord i = aGridCB.IStart(mWM); + nscoord b = aGridCB.BStart(mWM); + nscoord iSize = aGridCB.ISize(mWM); + nscoord bSize = aGridCB.BSize(mWM); + aArea.mCols.ToPositionAndLengthForAbsPos(mCols, aGridOrigin.I(mWM), + &i, &iSize); + aArea.mRows.ToPositionAndLengthForAbsPos(mRows, aGridOrigin.B(mWM), + &b, &bSize); + return LogicalRect(mWM, i, b, iSize, bSize); +} + +/** + * Return a Fragmentainer object if we have a fragmentainer frame in our + * ancestor chain of containing block (CB) reflow states. We'll only + * continue traversing the ancestor chain as long as the CBs have + * the same writing-mode and have overflow:visible. + */ +Maybe<nsGridContainerFrame::Fragmentainer> +nsGridContainerFrame::GetNearestFragmentainer(const GridReflowInput& aState) const +{ + Maybe<nsGridContainerFrame::Fragmentainer> data; + const ReflowInput* gridRI = aState.mReflowInput; + if (gridRI->AvailableBSize() == NS_UNCONSTRAINEDSIZE) { + return data; + } + WritingMode wm = aState.mWM; + const ReflowInput* cbRI = gridRI->mCBReflowInput; + for ( ; cbRI; cbRI = cbRI->mCBReflowInput) { + nsIScrollableFrame* sf = do_QueryFrame(cbRI->mFrame); + if (sf) { + break; + } + if (wm.IsOrthogonalTo(cbRI->GetWritingMode())) { + break; + } + nsIAtom* frameType = cbRI->mFrame->GetType(); + if ((frameType == nsGkAtoms::canvasFrame && + PresContext()->IsPaginated()) || + frameType == nsGkAtoms::columnSetFrame) { + data.emplace(); + data->mIsTopOfPage = gridRI->mFlags.mIsTopOfPage; + data->mToFragmentainerEnd = aState.mFragBStart + + gridRI->AvailableBSize() - aState.mBorderPadding.BStart(wm); + const auto numRows = aState.mRows.mSizes.Length(); + data->mCanBreakAtStart = + numRows > 0 && aState.mRows.mSizes[0].mPosition > 0; + nscoord bSize = gridRI->ComputedBSize(); + data->mIsAutoBSize = bSize == NS_AUTOHEIGHT; + if (data->mIsAutoBSize) { + bSize = gridRI->ComputedMinBSize(); + } else { + bSize = NS_CSS_MINMAX(bSize, + gridRI->ComputedMinBSize(), + gridRI->ComputedMaxBSize()); + } + nscoord gridEnd = + aState.mRows.GridLineEdge(numRows, GridLineSide::eBeforeGridGap); + data->mCanBreakAtEnd = bSize > gridEnd && + bSize > aState.mFragBStart; + break; + } + } + return data; +} + +void +nsGridContainerFrame::ReflowInFlowChild(nsIFrame* aChild, + const GridItemInfo* aGridItemInfo, + nsSize aContainerSize, + Maybe<nscoord> aStretchBSize, + const Fragmentainer* aFragmentainer, + const GridReflowInput& aState, + const LogicalRect& aContentArea, + ReflowOutput& aDesiredSize, + nsReflowStatus& aStatus) +{ + nsPresContext* pc = PresContext(); + nsStyleContext* containerSC = StyleContext(); + WritingMode wm = aState.mReflowInput->GetWritingMode(); + LogicalMargin pad(aState.mReflowInput->ComputedLogicalPadding()); + const LogicalPoint padStart(wm, pad.IStart(wm), pad.BStart(wm)); + const bool isGridItem = !!aGridItemInfo; + auto childType = aChild->GetType(); + MOZ_ASSERT(isGridItem == (childType != nsGkAtoms::placeholderFrame)); + LogicalRect cb(wm); + WritingMode childWM = aChild->GetWritingMode(); + bool isConstrainedBSize = false; + nscoord toFragmentainerEnd; + // The part of the child's grid area that's in previous container fragments. + nscoord consumedGridAreaBSize = 0; + const bool isOrthogonal = wm.IsOrthogonalTo(childWM); + if (MOZ_LIKELY(isGridItem)) { + MOZ_ASSERT(aGridItemInfo->mFrame == aChild); + const GridArea& area = aGridItemInfo->mArea; + MOZ_ASSERT(area.IsDefinite()); + cb = aState.ContainingBlockFor(area); + isConstrainedBSize = aFragmentainer && !wm.IsOrthogonalTo(childWM); + if (isConstrainedBSize) { + // |gridAreaBOffset| is the offset of the child's grid area in this + // container fragment (if negative, that distance is the child CB size + // consumed in previous container fragments). Note that cb.BStart + // (initially) and aState.mFragBStart are in "global" grid coordinates + // (like all track positions). + nscoord gridAreaBOffset = cb.BStart(wm) - aState.mFragBStart; + consumedGridAreaBSize = std::max(0, -gridAreaBOffset); + cb.BStart(wm) = std::max(0, gridAreaBOffset); + toFragmentainerEnd = aFragmentainer->mToFragmentainerEnd - + aState.mFragBStart - cb.BStart(wm); + toFragmentainerEnd = std::max(toFragmentainerEnd, 0); + } + cb += aContentArea.Origin(wm); + aState.mRows.AlignBaselineSubtree(*aGridItemInfo); + aState.mCols.AlignBaselineSubtree(*aGridItemInfo); + // Setup [align|justify]-content:[last ]baseline related frame properties. + // These are added to the padding in SizeComputationInput::InitOffsets. + // (a negative value signals the value is for 'last baseline' and should be + // added to the (logical) end padding) + typedef const FramePropertyDescriptor<SmallValueHolder<nscoord>>* Prop; + auto SetProp = [aGridItemInfo, aChild] (LogicalAxis aGridAxis, + Prop aProp) { + auto state = aGridItemInfo->mState[aGridAxis]; + auto baselineAdjust = (state & ItemState::eContentBaseline) ? + aGridItemInfo->mBaselineOffset[aGridAxis] : nscoord(0); + if (baselineAdjust < nscoord(0)) { + // This happens when the subtree overflows its track. + // XXX spec issue? it's unclear how to handle this. + baselineAdjust = nscoord(0); + } else if (baselineAdjust > nscoord(0) && + (state & ItemState::eLastBaseline)) { + baselineAdjust = -baselineAdjust; + } + if (baselineAdjust != nscoord(0)) { + aChild->Properties().Set(aProp, baselineAdjust); + } else { + aChild->Properties().Delete(aProp); + } + }; + SetProp(eLogicalAxisBlock, isOrthogonal ? IBaselinePadProperty() : + BBaselinePadProperty()); + SetProp(eLogicalAxisInline, isOrthogonal ? BBaselinePadProperty() : + IBaselinePadProperty()); + } else { + // By convention, for frames that perform CSS Box Alignment, we position + // placeholder children at the start corner of their alignment container, + // and in this case that's usually the grid's padding box. + // ("Usually" - the exception is when the grid *also* forms the + // abs.pos. containing block. In that case, the alignment container isn't + // the padding box -- it's some grid area instead. But that case doesn't + // require any special handling here, because we handle it later using a + // special flag (STATIC_POS_IS_CB_ORIGIN) which will make us ignore the + // placeholder's position entirely.) + cb = aContentArea - padStart; + aChild->AddStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN); + } + + LogicalSize reflowSize(cb.Size(wm)); + if (isConstrainedBSize) { + reflowSize.BSize(wm) = toFragmentainerEnd; + } + LogicalSize childCBSize = reflowSize.ConvertTo(childWM, wm); + + // Setup the ClampMarginBoxMinSize reflow flags and property, if needed. + uint32_t flags = 0; + if (aGridItemInfo) { + auto childIAxis = isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline; + if (aGridItemInfo->mState[childIAxis] & ItemState::eClampMarginBoxMinSize) { + flags |= ReflowInput::I_CLAMP_MARGIN_BOX_MIN_SIZE; + } + auto childBAxis = GetOrthogonalAxis(childIAxis); + if (aGridItemInfo->mState[childBAxis] & ItemState::eClampMarginBoxMinSize) { + flags |= ReflowInput::B_CLAMP_MARGIN_BOX_MIN_SIZE; + aChild->Properties().Set(BClampMarginBoxMinSizeProperty(), + childCBSize.BSize(childWM)); + } else { + aChild->Properties().Delete(BClampMarginBoxMinSizeProperty()); + } + } + + if (!isConstrainedBSize) { + childCBSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE; + } + LogicalSize percentBasis(cb.Size(wm).ConvertTo(childWM, wm)); + ReflowInput childRI(pc, *aState.mReflowInput, aChild, childCBSize, + &percentBasis, flags); + childRI.mFlags.mIsTopOfPage = aFragmentainer ? aFragmentainer->mIsTopOfPage : false; + + // A table-wrapper needs to propagate the CB size we give it to its + // inner table frame later. @see nsTableWrapperFrame::InitChildReflowInput. + if (childType == nsGkAtoms::tableWrapperFrame) { + const auto& props = aChild->Properties(); + LogicalSize* cb = props.Get(nsTableWrapperFrame::GridItemCBSizeProperty()); + if (!cb) { + cb = new LogicalSize(childWM); + props.Set(nsTableWrapperFrame::GridItemCBSizeProperty(), cb); + } + *cb = percentBasis; + } + + // If the child is stretching in its block axis, and we might be fragmenting + // it in that axis, then setup a frame property to tell + // nsBlockFrame::ComputeFinalSize the size. + if (isConstrainedBSize && !wm.IsOrthogonalTo(childWM)) { + bool stretch = false; + if (!childRI.mStyleMargin->HasBlockAxisAuto(childWM) && + childRI.mStylePosition->BSize(childWM).GetUnit() == eStyleUnit_Auto) { + auto blockAxisAlignment = + childRI.mStylePosition->UsedAlignSelf(StyleContext()); + if (blockAxisAlignment == NS_STYLE_ALIGN_NORMAL || + blockAxisAlignment == NS_STYLE_ALIGN_STRETCH) { + stretch = true; + } + } + if (stretch) { + aChild->Properties().Set(FragStretchBSizeProperty(), *aStretchBSize); + } else { + aChild->Properties().Delete(FragStretchBSizeProperty()); + } + } + + // 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 + // aContainerSize, and then pass the correct position to FinishReflowChild. + ReflowOutput childSize(childRI); + const nsSize dummyContainerSize; + ReflowChild(aChild, pc, childSize, childRI, childWM, LogicalPoint(childWM), + dummyContainerSize, 0, aStatus); + LogicalPoint childPos = + cb.Origin(wm).ConvertTo(childWM, wm, + aContainerSize - childSize.PhysicalSize()); + // Apply align/justify-self and reflow again if that affects the size. + if (MOZ_LIKELY(isGridItem)) { + LogicalSize size = childSize.Size(childWM); // from the ReflowChild() + if (NS_FRAME_IS_COMPLETE(aStatus)) { + auto align = childRI.mStylePosition->UsedAlignSelf(containerSC); + auto state = aGridItemInfo->mState[eLogicalAxisBlock]; + if (state & ItemState::eContentBaseline) { + align = (state & ItemState::eFirstBaseline) ? NS_STYLE_ALIGN_SELF_START + : NS_STYLE_ALIGN_SELF_END; + } + nscoord cbsz = cb.BSize(wm) - consumedGridAreaBSize; + AlignSelf(*aGridItemInfo, align, cbsz, wm, childRI, size, &childPos); + } + auto justify = childRI.mStylePosition->UsedJustifySelf(containerSC); + auto state = aGridItemInfo->mState[eLogicalAxisInline]; + if (state & ItemState::eContentBaseline) { + justify = (state & ItemState::eFirstBaseline) ? NS_STYLE_JUSTIFY_SELF_START + : NS_STYLE_JUSTIFY_SELF_END; + } + nscoord cbsz = cb.ISize(wm); + JustifySelf(*aGridItemInfo, justify, cbsz, wm, childRI, size, &childPos); + } // else, nsAbsoluteContainingBlock.cpp will handle align/justify-self. + + childRI.ApplyRelativePositioning(&childPos, aContainerSize); + FinishReflowChild(aChild, pc, childSize, &childRI, childWM, childPos, + aContainerSize, 0); + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, aChild); +} + +nscoord +nsGridContainerFrame::ReflowInFragmentainer(GridReflowInput& aState, + const LogicalRect& aContentArea, + ReflowOutput& aDesiredSize, + nsReflowStatus& aStatus, + Fragmentainer& aFragmentainer, + const nsSize& aContainerSize) +{ + MOZ_ASSERT(aStatus == NS_FRAME_COMPLETE); + MOZ_ASSERT(aState.mReflowInput); + + // Collect our grid items and sort them in row order. Collect placeholders + // and put them in a separate array. + nsTArray<const GridItemInfo*> sortedItems(aState.mGridItems.Length()); + nsTArray<nsIFrame*> placeholders(aState.mAbsPosItems.Length()); + aState.mIter.Reset(GridItemCSSOrderIterator::eIncludeAll); + for (; !aState.mIter.AtEnd(); aState.mIter.Next()) { + nsIFrame* child = *aState.mIter; + if (child->GetType() != nsGkAtoms::placeholderFrame) { + const GridItemInfo* info = &aState.mGridItems[aState.mIter.GridItemIndex()]; + sortedItems.AppendElement(info); + } else { + placeholders.AppendElement(child); + } + } + // NOTE: no need to use stable_sort here, there are no dependencies on + // having content order between items on the same row in the code below. + std::sort(sortedItems.begin(), sortedItems.end(), + GridItemInfo::IsStartRowLessThan); + + // Reflow our placeholder children; they must all be complete. + for (auto child : placeholders) { + nsReflowStatus childStatus; + ReflowInFlowChild(child, nullptr, aContainerSize, Nothing(), &aFragmentainer, + aState, aContentArea, aDesiredSize, childStatus); + MOZ_ASSERT(NS_FRAME_IS_COMPLETE(childStatus), + "nsPlaceholderFrame should never need to be fragmented"); + } + + // The available size for children - we'll set this to the edge of the last + // row in most cases below, but for now use the full size. + nscoord childAvailableSize = aFragmentainer.mToFragmentainerEnd; + const uint32_t startRow = aState.mStartRow; + const uint32_t numRows = aState.mRows.mSizes.Length(); + bool isBDBClone = aState.mReflowInput->mStyleBorder->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone; + nscoord bpBEnd = aState.mBorderPadding.BEnd(aState.mWM); + + // Set |endRow| to the first row that doesn't fit. + uint32_t endRow = numRows; + for (uint32_t row = startRow; row < numRows; ++row) { + auto& sz = aState.mRows.mSizes[row]; + const nscoord bEnd = sz.mPosition + sz.mBase; + nscoord remainingAvailableSize = childAvailableSize - bEnd; + if (remainingAvailableSize < 0 || + (isBDBClone && remainingAvailableSize < bpBEnd)) { + endRow = row; + break; + } + } + + // Check for forced breaks on the items. + const bool isTopOfPage = aFragmentainer.mIsTopOfPage; + bool isForcedBreak = false; + const bool avoidBreakInside = ShouldAvoidBreakInside(*aState.mReflowInput); + for (const GridItemInfo* info : sortedItems) { + uint32_t itemStartRow = info->mArea.mRows.mStart; + if (itemStartRow == endRow) { + break; + } + auto disp = info->mFrame->StyleDisplay(); + if (disp->mBreakBefore) { + // Propagate break-before on the first row to the container unless we're + // already at top-of-page. + if ((itemStartRow == 0 && !isTopOfPage) || avoidBreakInside) { + aStatus = NS_INLINE_LINE_BREAK_BEFORE(); + return aState.mFragBStart; + } + if ((itemStartRow > startRow || + (itemStartRow == startRow && !isTopOfPage)) && + itemStartRow < endRow) { + endRow = itemStartRow; + isForcedBreak = true; + // reset any BREAK_AFTER we found on an earlier item + aStatus = NS_FRAME_COMPLETE; + break; // we're done since the items are sorted in row order + } + } + uint32_t itemEndRow = info->mArea.mRows.mEnd; + if (disp->mBreakAfter) { + if (itemEndRow != numRows) { + if (itemEndRow > startRow && itemEndRow < endRow) { + endRow = itemEndRow; + isForcedBreak = true; + // No "break;" here since later items with break-after may have + // a shorter span. + } + } else { + // Propagate break-after on the last row to the container, we may still + // find a break-before on this row though (and reset aStatus). + aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus); // tentative + } + } + } + + // Consume at least one row in each fragment until we have consumed them all. + // Except for the first row if there's a break opportunity before it. + if (startRow == endRow && startRow != numRows && + (startRow != 0 || !aFragmentainer.mCanBreakAtStart)) { + ++endRow; + } + + // Honor break-inside:avoid if we can't fit all rows. + if (avoidBreakInside && endRow < numRows) { + aStatus = NS_INLINE_LINE_BREAK_BEFORE(); + return aState.mFragBStart; + } + + // Calculate the block-size including this fragment. + nscoord bEndRow = + aState.mRows.GridLineEdge(endRow, GridLineSide::eBeforeGridGap); + nscoord bSize; + if (aFragmentainer.mIsAutoBSize) { + // We only apply min-bsize once all rows are complete (when bsize is auto). + if (endRow < numRows) { + bSize = bEndRow; + auto clampedBSize = ClampToCSSMaxBSize(bSize, aState.mReflowInput); + if (MOZ_UNLIKELY(clampedBSize != bSize)) { + // We apply max-bsize in all fragments though. + bSize = clampedBSize; + } else if (!isBDBClone) { + // The max-bsize won't make this fragment COMPLETE, so the block-end + // border will be in a later fragment. + bpBEnd = 0; + } + } else { + bSize = NS_CSS_MINMAX(bEndRow, + aState.mReflowInput->ComputedMinBSize(), + aState.mReflowInput->ComputedMaxBSize()); + } + } else { + bSize = NS_CSS_MINMAX(aState.mReflowInput->ComputedBSize(), + aState.mReflowInput->ComputedMinBSize(), + aState.mReflowInput->ComputedMaxBSize()); + } + + // Check for overflow and set aStatus INCOMPLETE if so. + bool overflow = bSize + bpBEnd > childAvailableSize; + if (overflow) { + if (avoidBreakInside) { + aStatus = NS_INLINE_LINE_BREAK_BEFORE(); + return aState.mFragBStart; + } + bool breakAfterLastRow = endRow == numRows && aFragmentainer.mCanBreakAtEnd; + if (breakAfterLastRow) { + MOZ_ASSERT(bEndRow < bSize, "bogus aFragmentainer.mCanBreakAtEnd"); + nscoord availableSize = childAvailableSize; + if (isBDBClone) { + availableSize -= bpBEnd; + } + // Pretend we have at least 1px available size, otherwise we'll never make + // progress in consuming our bSize. + availableSize = std::max(availableSize, + aState.mFragBStart + AppUnitsPerCSSPixel()); + // Fill the fragmentainer, but not more than our desired block-size and + // at least to the size of the last row (even if that overflows). + nscoord newBSize = std::min(bSize, availableSize); + newBSize = std::max(newBSize, bEndRow); + // If it's just the border+padding that is overflowing and we have + // box-decoration-break:clone then we are technically COMPLETE. There's + // no point in creating another zero-bsize fragment in this case. + if (newBSize < bSize || !isBDBClone) { + NS_FRAME_SET_INCOMPLETE(aStatus); + } + bSize = newBSize; + } else if (bSize <= bEndRow && startRow + 1 < endRow) { + if (endRow == numRows) { + // We have more than one row in this fragment, so we can break before + // the last row instead. + --endRow; + bEndRow = aState.mRows.GridLineEdge(endRow, GridLineSide::eBeforeGridGap); + bSize = bEndRow; + if (aFragmentainer.mIsAutoBSize) { + bSize = ClampToCSSMaxBSize(bSize, aState.mReflowInput); + } + } + NS_FRAME_SET_INCOMPLETE(aStatus); + } else if (endRow < numRows) { + bSize = ClampToCSSMaxBSize(bEndRow, aState.mReflowInput, &aStatus); + } // else - no break opportunities. + } else { + // Even though our block-size fits we need to honor forced breaks, or if + // a row doesn't fit in an auto-sized container (unless it's constrained + // by a max-bsize which make us overflow-incomplete). + if (endRow < numRows && (isForcedBreak || + (aFragmentainer.mIsAutoBSize && bEndRow == bSize))) { + bSize = ClampToCSSMaxBSize(bEndRow, aState.mReflowInput, &aStatus); + } + } + + // If we can't fit all rows then we're at least overflow-incomplete. + if (endRow < numRows) { + childAvailableSize = bEndRow; + if (NS_FRAME_IS_COMPLETE(aStatus)) { + NS_FRAME_SET_OVERFLOW_INCOMPLETE(aStatus); + aStatus |= NS_FRAME_REFLOW_NEXTINFLOW; + } + } else { + // Children always have the full size of the rows in this fragment. + childAvailableSize = std::max(childAvailableSize, bEndRow); + } + + return ReflowRowsInFragmentainer(aState, aContentArea, aDesiredSize, aStatus, + aFragmentainer, aContainerSize, sortedItems, + startRow, endRow, bSize, childAvailableSize); +} + +nscoord +nsGridContainerFrame::ReflowRowsInFragmentainer( + GridReflowInput& aState, + const LogicalRect& aContentArea, + ReflowOutput& aDesiredSize, + nsReflowStatus& aStatus, + Fragmentainer& aFragmentainer, + const nsSize& aContainerSize, + const nsTArray<const GridItemInfo*>& aSortedItems, + uint32_t aStartRow, + uint32_t aEndRow, + nscoord aBSize, + nscoord aAvailableSize) +{ + FrameHashtable pushedItems; + FrameHashtable incompleteItems; + FrameHashtable overflowIncompleteItems; + bool isBDBClone = aState.mReflowInput->mStyleBorder->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone; + bool didGrowRow = false; + // As we walk across rows, we track whether the current row is at the top + // of its grid-fragment, to help decide whether we can break before it. When + // this function starts, our row is at the top of the current fragment if: + // - we're starting with a nonzero row (i.e. we're a continuation) + // OR: + // - we're starting with the first row, & we're not allowed to break before + // it (which makes it effectively at the top of its grid-fragment). + bool isRowTopOfPage = aStartRow != 0 || !aFragmentainer.mCanBreakAtStart; + const bool isStartRowTopOfPage = isRowTopOfPage; + // Save our full available size for later. + const nscoord gridAvailableSize = aFragmentainer.mToFragmentainerEnd; + // Propagate the constrained size to our children. + aFragmentainer.mToFragmentainerEnd = aAvailableSize; + // Reflow the items in row order up to |aEndRow| and push items after that. + uint32_t row = 0; + // |i| is intentionally signed, so we can set it to -1 to restart the loop. + for (int32_t i = 0, len = aSortedItems.Length(); i < len; ++i) { + const GridItemInfo* const info = aSortedItems[i]; + nsIFrame* child = info->mFrame; + row = info->mArea.mRows.mStart; + MOZ_ASSERT(child->GetPrevInFlow() ? row < aStartRow : row >= aStartRow, + "unexpected child start row"); + if (row >= aEndRow) { + pushedItems.PutEntry(child); + continue; + } + + bool rowCanGrow = false; + nscoord maxRowSize = 0; + if (row >= aStartRow) { + if (row > aStartRow) { + isRowTopOfPage = false; + } + // Can we grow this row? Only consider span=1 items per spec... + rowCanGrow = !didGrowRow && info->mArea.mRows.Extent() == 1; + if (rowCanGrow) { + auto& sz = aState.mRows.mSizes[row]; + // and only min-/max-content rows or flex rows in an auto-sized container + rowCanGrow = (sz.mState & TrackSize::eMinOrMaxContentMinSizing) || + ((sz.mState & TrackSize::eFlexMaxSizing) && + aFragmentainer.mIsAutoBSize); + if (rowCanGrow) { + if (isBDBClone) { + maxRowSize = gridAvailableSize - + aState.mBorderPadding.BEnd(aState.mWM); + } else { + maxRowSize = gridAvailableSize; + } + maxRowSize -= sz.mPosition; + // ...and only if there is space for it to grow. + rowCanGrow = maxRowSize > sz.mBase; + } + } + } + + // aFragmentainer.mIsTopOfPage is propagated to the child reflow state. + // When it's false the child can request BREAK_BEFORE. We intentionally + // set it to false when the row is growable (as determined in CSS Grid + // Fragmentation) and there is a non-zero space between it and the + // fragmentainer end (that can be used to grow it). If the child reports + // a forced break in this case, we grow this row to fill the fragment and + // restart the loop. We also restart the loop with |aEndRow = row| + // (but without growing any row) for a BREAK_BEFORE child if it spans + // beyond the last row in this fragment. This is to avoid fragmenting it. + // We only restart the loop once. + aFragmentainer.mIsTopOfPage = isRowTopOfPage && !rowCanGrow; + nsReflowStatus childStatus; + // Pass along how much to stretch this fragment, in case it's needed. + nscoord bSize = + aState.mRows.GridLineEdge(std::min(aEndRow, info->mArea.mRows.mEnd), + GridLineSide::eBeforeGridGap) - + aState.mRows.GridLineEdge(std::max(aStartRow, row), + GridLineSide::eAfterGridGap); + ReflowInFlowChild(child, info, aContainerSize, Some(bSize), &aFragmentainer, + aState, aContentArea, aDesiredSize, childStatus); + MOZ_ASSERT(NS_INLINE_IS_BREAK_BEFORE(childStatus) || + !NS_FRAME_IS_FULLY_COMPLETE(childStatus) || + !child->GetNextInFlow(), + "fully-complete reflow should destroy any NIFs"); + + if (NS_INLINE_IS_BREAK_BEFORE(childStatus)) { + MOZ_ASSERT(!child->GetPrevInFlow(), + "continuations should never report BREAK_BEFORE status"); + MOZ_ASSERT(!aFragmentainer.mIsTopOfPage, + "got NS_INLINE_IS_BREAK_BEFORE at top of page"); + if (!didGrowRow) { + if (rowCanGrow) { + // Grow this row and restart with the next row as |aEndRow|. + aState.mRows.ResizeRow(row, maxRowSize); + if (aState.mSharedGridData) { + aState.mSharedGridData->mRows.ResizeRow(row, maxRowSize); + } + didGrowRow = true; + aEndRow = row + 1; // growing this row makes the next one not fit + i = -1; // i == 0 after the next loop increment + isRowTopOfPage = isStartRowTopOfPage; + overflowIncompleteItems.Clear(); + incompleteItems.Clear(); + nscoord bEndRow = + aState.mRows.GridLineEdge(aEndRow, GridLineSide::eBeforeGridGap); + aFragmentainer.mToFragmentainerEnd = bEndRow; + if (aFragmentainer.mIsAutoBSize) { + aBSize = ClampToCSSMaxBSize(bEndRow, aState.mReflowInput, &aStatus); + } else if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) { + aBSize = NS_CSS_MINMAX(aState.mReflowInput->ComputedBSize(), + aState.mReflowInput->ComputedMinBSize(), + aState.mReflowInput->ComputedMaxBSize()); + aBSize = std::min(bEndRow, aBSize); + } + continue; + } + + if (!isRowTopOfPage) { + // We can break before this row - restart with it as the new end row. + aEndRow = row; + aBSize = aState.mRows.GridLineEdge(aEndRow, GridLineSide::eBeforeGridGap); + i = -1; // i == 0 after the next loop increment + isRowTopOfPage = isStartRowTopOfPage; + overflowIncompleteItems.Clear(); + incompleteItems.Clear(); + NS_FRAME_SET_INCOMPLETE(aStatus); + continue; + } + NS_ERROR("got BREAK_BEFORE at top-of-page"); + childStatus = NS_FRAME_COMPLETE; + } else { + NS_ERROR("got BREAK_BEFORE again after growing the row?"); + NS_FRAME_SET_INCOMPLETE(childStatus); + } + } else if (NS_INLINE_IS_BREAK_AFTER(childStatus)) { + MOZ_ASSERT_UNREACHABLE("unexpected child reflow status"); + } + + if (NS_FRAME_IS_NOT_COMPLETE(childStatus)) { + incompleteItems.PutEntry(child); + } else if (!NS_FRAME_IS_FULLY_COMPLETE(childStatus)) { + overflowIncompleteItems.PutEntry(child); + } + } + + // Record a break before |aEndRow|. + aState.mNextFragmentStartRow = aEndRow; + if (aEndRow < aState.mRows.mSizes.Length()) { + aState.mRows.BreakBeforeRow(aEndRow); + if (aState.mSharedGridData) { + aState.mSharedGridData->mRows.BreakBeforeRow(aEndRow); + } + } + + if (!pushedItems.IsEmpty() || + !incompleteItems.IsEmpty() || + !overflowIncompleteItems.IsEmpty()) { + if (NS_FRAME_IS_COMPLETE(aStatus)) { + NS_FRAME_SET_OVERFLOW_INCOMPLETE(aStatus); + aStatus |= NS_FRAME_REFLOW_NEXTINFLOW; + } + // Iterate the children in normal document order and append them (or a NIF) + // to one of the following frame lists according to their status. + nsFrameList pushedList; + nsFrameList incompleteList; + nsFrameList overflowIncompleteList; + auto* pc = PresContext(); + auto* fc = pc->PresShell()->FrameConstructor(); + for (nsIFrame* child = GetChildList(kPrincipalList).FirstChild(); child; ) { + MOZ_ASSERT((pushedItems.Contains(child) ? 1 : 0) + + (incompleteItems.Contains(child) ? 1 : 0) + + (overflowIncompleteItems.Contains(child) ? 1 : 0) <= 1, + "child should only be in one of these sets"); + // Save the next-sibling so we can continue the loop if |child| is moved. + nsIFrame* next = child->GetNextSibling(); + if (pushedItems.Contains(child)) { + MOZ_ASSERT(child->GetParent() == this); + StealFrame(child); + pushedList.AppendFrame(nullptr, child); + } else if (incompleteItems.Contains(child)) { + nsIFrame* childNIF = child->GetNextInFlow(); + if (!childNIF) { + childNIF = fc->CreateContinuingFrame(pc, child, this); + incompleteList.AppendFrame(nullptr, childNIF); + } else { + auto parent = static_cast<nsGridContainerFrame*>(childNIF->GetParent()); + MOZ_ASSERT(parent != this || !mFrames.ContainsFrame(childNIF), + "child's NIF shouldn't be in the same principal list"); + // If child's existing NIF is an overflow container, convert it to an + // actual NIF, since now |child| has non-overflow stuff to give it. + // Or, if it's further away then our next-in-flow, then pull it up. + if ((childNIF->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) || + (parent != this && parent != GetNextInFlow())) { + parent->StealFrame(childNIF); + childNIF->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); + if (parent == this) { + incompleteList.AppendFrame(nullptr, childNIF); + } else { + // If childNIF already lives on the next grid fragment, then we + // don't need to reparent it, since we know it's destined to end + // up there anyway. Just move it to its parent's overflow list. + if (parent == GetNextInFlow()) { + nsFrameList toMove(childNIF, childNIF); + parent->MergeSortedOverflow(toMove); + } else { + ReparentFrame(childNIF, parent, this); + incompleteList.AppendFrame(nullptr, childNIF); + } + } + } + } + } else if (overflowIncompleteItems.Contains(child)) { + nsIFrame* childNIF = child->GetNextInFlow(); + if (!childNIF) { + childNIF = fc->CreateContinuingFrame(pc, child, this); + childNIF->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); + overflowIncompleteList.AppendFrame(nullptr, childNIF); + } else { + DebugOnly<nsGridContainerFrame*> lastParent = this; + auto nif = static_cast<nsGridContainerFrame*>(GetNextInFlow()); + // If child has any non-overflow-container NIFs, convert them to + // overflow containers, since that's all |child| needs now. + while (childNIF && + !childNIF->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { + auto parent = static_cast<nsGridContainerFrame*>(childNIF->GetParent()); + parent->StealFrame(childNIF); + childNIF->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); + if (parent == this) { + overflowIncompleteList.AppendFrame(nullptr, childNIF); + } else { + if (!nif || parent == nif) { + nsFrameList toMove(childNIF, childNIF); + parent->MergeSortedExcessOverflowContainers(toMove); + } else { + ReparentFrame(childNIF, parent, nif); + nsFrameList toMove(childNIF, childNIF); + nif->MergeSortedExcessOverflowContainers(toMove); + } + // We only need to reparent the first childNIF (or not at all if + // its parent is our NIF). + nif = nullptr; + } + lastParent = parent; + childNIF = childNIF->GetNextInFlow(); + } + } + } + child = next; + } + + // Merge the results into our respective overflow child lists. + if (!pushedList.IsEmpty()) { + MergeSortedOverflow(pushedList); + AddStateBits(NS_STATE_GRID_DID_PUSH_ITEMS); + // NOTE since we messed with our child list here, we intentionally + // make aState.mIter invalid to avoid any use of it after this point. + aState.mIter.Invalidate(); + } + if (!incompleteList.IsEmpty()) { + MergeSortedOverflow(incompleteList); + // NOTE since we messed with our child list here, we intentionally + // make aState.mIter invalid to avoid any use of it after this point. + aState.mIter.Invalidate(); + } + if (!overflowIncompleteList.IsEmpty()) { + MergeSortedExcessOverflowContainers(overflowIncompleteList); + } + } + return aBSize; +} + +nscoord +nsGridContainerFrame::ReflowChildren(GridReflowInput& aState, + const LogicalRect& aContentArea, + ReflowOutput& aDesiredSize, + nsReflowStatus& aStatus) +{ + MOZ_ASSERT(aState.mReflowInput); + + aStatus = NS_FRAME_COMPLETE; + nsOverflowAreas ocBounds; + nsReflowStatus ocStatus = NS_FRAME_COMPLETE; + if (GetPrevInFlow()) { + ReflowOverflowContainerChildren(PresContext(), *aState.mReflowInput, + ocBounds, 0, ocStatus, + MergeSortedFrameListsFor); + } + + WritingMode wm = aState.mReflowInput->GetWritingMode(); + const nsSize containerSize = + (aContentArea.Size(wm) + aState.mBorderPadding.Size(wm)).GetPhysicalSize(wm); + + nscoord bSize = aContentArea.BSize(wm); + Maybe<Fragmentainer> fragmentainer = GetNearestFragmentainer(aState); + if (MOZ_UNLIKELY(fragmentainer.isSome())) { + aState.mInFragmentainer = true; + bSize = ReflowInFragmentainer(aState, aContentArea, aDesiredSize, aStatus, + *fragmentainer, containerSize); + } else { + aState.mIter.Reset(GridItemCSSOrderIterator::eIncludeAll); + for (; !aState.mIter.AtEnd(); aState.mIter.Next()) { + nsIFrame* child = *aState.mIter; + const GridItemInfo* info = nullptr; + if (child->GetType() != nsGkAtoms::placeholderFrame) { + info = &aState.mGridItems[aState.mIter.GridItemIndex()]; + } + ReflowInFlowChild(*aState.mIter, info, containerSize, Nothing(), nullptr, + aState, aContentArea, aDesiredSize, aStatus); + MOZ_ASSERT(NS_FRAME_IS_COMPLETE(aStatus), "child should be complete " + "in unconstrained reflow"); + } + } + + // Merge overflow container bounds and status. + aDesiredSize.mOverflowAreas.UnionWith(ocBounds); + NS_MergeReflowStatusInto(&aStatus, ocStatus); + + if (IsAbsoluteContainer()) { + nsFrameList children(GetChildList(GetAbsoluteListID())); + if (!children.IsEmpty()) { + // 'gridOrigin' is the origin of the grid (the start of the first track), + // with respect to the grid container's padding-box (CB). + LogicalMargin pad(aState.mReflowInput->ComputedLogicalPadding()); + const LogicalPoint gridOrigin(wm, pad.IStart(wm), pad.BStart(wm)); + const LogicalRect gridCB(wm, 0, 0, + aContentArea.ISize(wm) + pad.IStartEnd(wm), + bSize + 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 < aState.mAbsPosItems.Length()); + MOZ_ASSERT(aState.mAbsPosItems[i].mFrame == child); + GridArea& area = aState.mAbsPosItems[i].mArea; + LogicalRect itemCB = + aState.ContainingBlockForAbsPos(area, gridOrigin, gridCB); + // nsAbsoluteContainingBlock::Reflow uses physical coordinates. + nsRect* cb = child->Properties().Get(GridItemContainingBlockRect()); + if (!cb) { + cb = new nsRect; + child->Properties().Set(GridItemContainingBlockRect(), cb); + } + *cb = itemCB.GetPhysicalRect(wm, gridCBPhysicalSize); + } + // We pass a dummy rect as CB because each child has its own CB rect. + // The eIsGridContainerCB flag tells nsAbsoluteContainingBlock::Reflow to + // use those instead. + nsRect dummyRect; + AbsPosReflowFlags flags = + AbsPosReflowFlags::eCBWidthAndHeightChanged; // XXX could be optimized + flags |= AbsPosReflowFlags::eConstrainHeight; + flags |= AbsPosReflowFlags::eIsGridContainerCB; + GetAbsoluteContainingBlock()->Reflow(this, PresContext(), + *aState.mReflowInput, + aStatus, dummyRect, flags, + &aDesiredSize.mOverflowAreas); + } + } + return bSize; +} + +void +nsGridContainerFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("nsGridContainerFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); + + if (IsFrameTreeTooDeep(aReflowInput, aDesiredSize, aStatus)) { + return; + } + + // First we gather child frames we should include in our reflow, + // i.e. overflowed children from our prev-in-flow, and pushed first-in-flow + // children (that might now fit). It's important to note that these children + // can be in arbitrary order vis-a-vis the current children in our lists. + // E.g. grid items in the document order: A, B, C may be placed in the rows + // 3, 2, 1. Assume each row goes in a separate grid container fragment, + // and we reflow the second fragment. Now if C (in fragment 1) overflows, + // we can't just prepend it to our mFrames like we usually do because that + // would violate the document order invariant that other code depends on. + // Similarly if we pull up child A (from fragment 3) we can't just append + // that for the same reason. Instead, we must sort these children into + // our child lists. (The sorting is trivial given that both lists are + // already fully sorted individually - it's just a merge.) + // + // The invariants that we maintain are that each grid container child list + // is sorted in the normal document order at all times, but that children + // in different grid container continuations may be in arbitrary order. + + auto prevInFlow = static_cast<nsGridContainerFrame*>(GetPrevInFlow()); + // Merge overflow frames from our prev-in-flow into our principal child list. + if (prevInFlow) { + AutoFrameListPtr overflow(aPresContext, + prevInFlow->StealOverflowFrames()); + if (overflow) { + ReparentFrames(*overflow, prevInFlow, this); + ::MergeSortedFrameLists(mFrames, *overflow, GetContent()); + + // Move trailing next-in-flows into our overflow list. + nsFrameList continuations; + for (nsIFrame* f = mFrames.FirstChild(); f; ) { + nsIFrame* next = f->GetNextSibling(); + nsIFrame* pif = f->GetPrevInFlow(); + if (pif && pif->GetParent() == this) { + mFrames.RemoveFrame(f); + continuations.AppendFrame(nullptr, f); + } + f = next; + } + MergeSortedOverflow(continuations); + + // Move trailing OC next-in-flows into our excess overflow containers list. + nsFrameList* overflowContainers = + GetPropTableFrames(OverflowContainersProperty()); + if (overflowContainers) { + nsFrameList moveToEOC; + for (nsIFrame* f = overflowContainers->FirstChild(); f; ) { + nsIFrame* next = f->GetNextSibling(); + nsIFrame* pif = f->GetPrevInFlow(); + if (pif && pif->GetParent() == this) { + overflowContainers->RemoveFrame(f); + moveToEOC.AppendFrame(nullptr, f); + } + f = next; + } + if (overflowContainers->IsEmpty()) { + Properties().Delete(OverflowContainersProperty()); + } + MergeSortedExcessOverflowContainers(moveToEOC); + } + } + } + + // Merge our own overflow frames into our principal child list, + // except those that are a next-in-flow for one of our items. + DebugOnly<bool> foundOwnPushedChild = false; + { + nsFrameList* ourOverflow = GetOverflowFrames(); + if (ourOverflow) { + nsFrameList items; + for (nsIFrame* f = ourOverflow->FirstChild(); f; ) { + nsIFrame* next = f->GetNextSibling(); + nsIFrame* pif = f->GetPrevInFlow(); + if (!pif || pif->GetParent() != this) { + MOZ_ASSERT(f->GetParent() == this); + ourOverflow->RemoveFrame(f); + items.AppendFrame(nullptr, f); + if (!pif) { + foundOwnPushedChild = true; + } + } + f = next; + } + ::MergeSortedFrameLists(mFrames, items, GetContent()); + if (ourOverflow->IsEmpty()) { + DestroyOverflowList(); + } + } + } + + // Pull up any first-in-flow children we might have pushed. + if (HasAnyStateBits(NS_STATE_GRID_DID_PUSH_ITEMS)) { + RemoveStateBits(NS_STATE_GRID_DID_PUSH_ITEMS); + nsFrameList items; + auto nif = static_cast<nsGridContainerFrame*>(GetNextInFlow()); + auto firstNIF = nif; + DebugOnly<bool> nifNeedPushedItem = false; + while (nif) { + nsFrameList nifItems; + for (nsIFrame* nifChild = nif->GetChildList(kPrincipalList).FirstChild(); + nifChild; ) { + nsIFrame* next = nifChild->GetNextSibling(); + if (!nifChild->GetPrevInFlow()) { + nif->StealFrame(nifChild); + ReparentFrame(nifChild, nif, this); + nifItems.AppendFrame(nullptr, nifChild); + nifNeedPushedItem = false; + } + nifChild = next; + } + ::MergeSortedFrameLists(items, nifItems, GetContent()); + + if (!nif->HasAnyStateBits(NS_STATE_GRID_DID_PUSH_ITEMS)) { + MOZ_ASSERT(!nifNeedPushedItem || mDidPushItemsBitMayLie, + "NS_STATE_GRID_DID_PUSH_ITEMS lied"); + break; + } + nifNeedPushedItem = true; + + for (nsIFrame* nifChild = nif->GetChildList(kOverflowList).FirstChild(); + nifChild; ) { + nsIFrame* next = nifChild->GetNextSibling(); + if (!nifChild->GetPrevInFlow()) { + nif->StealFrame(nifChild); + ReparentFrame(nifChild, nif, this); + nifItems.AppendFrame(nullptr, nifChild); + nifNeedPushedItem = false; + } + nifChild = next; + } + ::MergeSortedFrameLists(items, nifItems, GetContent()); + + nif->RemoveStateBits(NS_STATE_GRID_DID_PUSH_ITEMS); + nif = static_cast<nsGridContainerFrame*>(nif->GetNextInFlow()); + MOZ_ASSERT(nif || !nifNeedPushedItem || mDidPushItemsBitMayLie, + "NS_STATE_GRID_DID_PUSH_ITEMS lied"); + } + + if (!items.IsEmpty()) { + // Pull up the first next-in-flow of the pulled up items too, unless its + // parent is our nif (to avoid leaving a hole there). + nsFrameList childNIFs; + nsFrameList childOCNIFs; + for (auto child : items) { + auto childNIF = child->GetNextInFlow(); + if (childNIF && childNIF->GetParent() != firstNIF) { + auto parent = childNIF->GetParent(); + parent->StealFrame(childNIF); + ReparentFrame(childNIF, parent, firstNIF); + if ((childNIF->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)) { + childOCNIFs.AppendFrame(nullptr, childNIF); + } else { + childNIFs.AppendFrame(nullptr, childNIF); + } + } + } + // Merge items' NIFs into our NIF's respective overflow child lists. + firstNIF->MergeSortedOverflow(childNIFs); + firstNIF->MergeSortedExcessOverflowContainers(childOCNIFs); + } + + MOZ_ASSERT(foundOwnPushedChild || !items.IsEmpty() || mDidPushItemsBitMayLie, + "NS_STATE_GRID_DID_PUSH_ITEMS lied"); + ::MergeSortedFrameLists(mFrames, items, GetContent()); + } + + RenumberList(); + +#ifdef DEBUG + mDidPushItemsBitMayLie = false; + SanityCheckGridItemsBeforeReflow(); +#endif // DEBUG + + mBaseline[0][0] = NS_INTRINSIC_WIDTH_UNKNOWN; + mBaseline[0][1] = NS_INTRINSIC_WIDTH_UNKNOWN; + mBaseline[1][0] = NS_INTRINSIC_WIDTH_UNKNOWN; + mBaseline[1][1] = NS_INTRINSIC_WIDTH_UNKNOWN; + + const nsStylePosition* stylePos = aReflowInput.mStylePosition; + if (!prevInFlow) { + InitImplicitNamedAreas(stylePos); + } + GridReflowInput gridReflowInput(this, aReflowInput); + if (gridReflowInput.mIter.ItemsAreAlreadyInOrder()) { + AddStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER); + } else { + RemoveStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER); + } + if (gridReflowInput.mIter.AtEnd()) { + // We have no grid items, our parent should synthesize a baseline if needed. + AddStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE); + } else { + RemoveStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE); + } + const nscoord computedBSize = aReflowInput.ComputedBSize(); + const nscoord computedISize = aReflowInput.ComputedISize(); + const WritingMode& wm = gridReflowInput.mWM; + LogicalSize computedSize(wm, computedISize, computedBSize); + + nscoord consumedBSize = 0; + nscoord bSize; + if (!prevInFlow) { + Grid grid; + grid.PlaceGridItems(gridReflowInput, aReflowInput.ComputedMinSize(), + computedSize, aReflowInput.ComputedMaxSize()); + + gridReflowInput.CalculateTrackSizes(grid, computedSize, + SizingConstraint::eNoConstraint); + bSize = computedSize.BSize(wm); + } else { + consumedBSize = GetConsumedBSize(); + gridReflowInput.InitializeForContinuation(this, consumedBSize); + const uint32_t numRows = gridReflowInput.mRows.mSizes.Length(); + bSize = gridReflowInput.mRows.GridLineEdge(numRows, + GridLineSide::eAfterGridGap); + } + if (computedBSize == NS_AUTOHEIGHT) { + bSize = NS_CSS_MINMAX(bSize, + aReflowInput.ComputedMinBSize(), + aReflowInput.ComputedMaxBSize()); + } else { + bSize = computedBSize; + } + bSize = std::max(bSize - consumedBSize, 0); + auto& bp = gridReflowInput.mBorderPadding; + LogicalRect contentArea(wm, bp.IStart(wm), bp.BStart(wm), + computedISize, bSize); + + if (!prevInFlow) { + // Apply 'align/justify-content' to the grid. + // CalculateTrackSizes did the columns. + gridReflowInput.mRows.AlignJustifyContent(stylePos, wm, contentArea.Size(wm)); + } + + bSize = ReflowChildren(gridReflowInput, contentArea, aDesiredSize, aStatus); + bSize = std::max(bSize - consumedBSize, 0); + + // Skip our block-end border if we're INCOMPLETE. + if (!NS_FRAME_IS_COMPLETE(aStatus) && + !gridReflowInput.mSkipSides.BEnd() && + StyleBorder()->mBoxDecorationBreak != + StyleBoxDecorationBreak::Clone) { + bp.BEnd(wm) = nscoord(0); + } + + LogicalSize desiredSize(wm, computedISize + bp.IStartEnd(wm), + bSize + bp.BStartEnd(wm)); + aDesiredSize.SetSize(wm, desiredSize); + nsRect frameRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height()); + aDesiredSize.mOverflowAreas.UnionAllWith(frameRect); + + // Convert INCOMPLETE -> OVERFLOW_INCOMPLETE and zero bsize if we're an OC. + if (HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { + if (!NS_FRAME_IS_COMPLETE(aStatus)) { + NS_FRAME_SET_OVERFLOW_INCOMPLETE(aStatus); + aStatus |= NS_FRAME_REFLOW_NEXTINFLOW; + } + bSize = 0; + desiredSize.BSize(wm) = bSize + bp.BStartEnd(wm); + aDesiredSize.SetSize(wm, desiredSize); + } + + if (!gridReflowInput.mInFragmentainer) { + MOZ_ASSERT(gridReflowInput.mIter.IsValid()); + auto sz = frameRect.Size(); + CalculateBaselines(BaselineSet::eBoth, &gridReflowInput.mIter, + &gridReflowInput.mGridItems, gridReflowInput.mCols, + 0, gridReflowInput.mCols.mSizes.Length(), + wm, sz, bp.IStart(wm), + bp.IEnd(wm), desiredSize.ISize(wm)); + CalculateBaselines(BaselineSet::eBoth, &gridReflowInput.mIter, + &gridReflowInput.mGridItems, gridReflowInput.mRows, + 0, gridReflowInput.mRows.mSizes.Length(), + wm, sz, bp.BStart(wm), + bp.BEnd(wm), desiredSize.BSize(wm)); + } else { + // Only compute 'first baseline' if this fragment contains the first track. + // XXXmats maybe remove this condition? bug 1306499 + BaselineSet baselines = BaselineSet::eNone; + if (gridReflowInput.mStartRow == 0 && + gridReflowInput.mStartRow != gridReflowInput.mNextFragmentStartRow) { + baselines = BaselineSet::eFirst; + } + // Only compute 'last baseline' if this fragment contains the last track. + // XXXmats maybe remove this condition? bug 1306499 + uint32_t len = gridReflowInput.mRows.mSizes.Length(); + if (gridReflowInput.mStartRow != len && + gridReflowInput.mNextFragmentStartRow == len) { + baselines = BaselineSet(baselines | BaselineSet::eLast); + } + Maybe<GridItemCSSOrderIterator> iter; + Maybe<nsTArray<GridItemInfo>> gridItems; + if (baselines != BaselineSet::eNone) { + // We need to create a new iterator and GridItemInfo array because we + // might have pushed some children at this point. + // Even if the gridReflowInput iterator is invalid we can reuse its + // state about order to optimize initialization of the new iterator. + // An ordered child list can't become unordered by pushing frames. + // An unordered list can become ordered in a number of cases, but we + // ignore that here and guess that the child list is still unordered. + // XXX this is O(n^2) in the number of items in this fragment: bug 1306705 + using Filter = GridItemCSSOrderIterator::ChildFilter; + using Order = GridItemCSSOrderIterator::OrderState; + bool ordered = gridReflowInput.mIter.ItemsAreAlreadyInOrder(); + auto orderState = ordered ? Order::eKnownOrdered : Order::eKnownUnordered; + iter.emplace(this, kPrincipalList, Filter::eSkipPlaceholders, orderState); + gridItems.emplace(); + for (; !iter->AtEnd(); iter->Next()) { + auto child = **iter; + for (const auto& info : gridReflowInput.mGridItems) { + if (info.mFrame == child) { + gridItems->AppendElement(info); + } + } + } + } + auto sz = frameRect.Size(); + CalculateBaselines(baselines, iter.ptrOr(nullptr), gridItems.ptrOr(nullptr), + gridReflowInput.mCols, 0, + gridReflowInput.mCols.mSizes.Length(), wm, sz, + bp.IStart(wm), bp.IEnd(wm), desiredSize.ISize(wm)); + CalculateBaselines(baselines, iter.ptrOr(nullptr), gridItems.ptrOr(nullptr), + gridReflowInput.mRows, gridReflowInput.mStartRow, + gridReflowInput.mNextFragmentStartRow, wm, sz, + bp.BStart(wm), bp.BEnd(wm), desiredSize.BSize(wm)); + } + + if (HasAnyStateBits(NS_STATE_GRID_GENERATE_COMPUTED_VALUES)) { + // This state bit will never be cleared, since reflow can be called + // multiple times in fragmented grids, and it's challenging to scope + // the bit to only that sequence of calls. This is relatively harmless + // since this bit is only set by accessing a ChromeOnly property, and + // therefore can't unduly slow down normal web browsing. + + // Now that we know column and row sizes and positions, set + // the ComputedGridTrackInfo and related properties + + uint32_t colTrackCount = gridReflowInput.mCols.mSizes.Length(); + nsTArray<nscoord> colTrackPositions(colTrackCount); + nsTArray<nscoord> colTrackSizes(colTrackCount); + nsTArray<uint32_t> colTrackStates(colTrackCount); + nsTArray<bool> colRemovedRepeatTracks( + gridReflowInput.mColFunctions.mRemovedRepeatTracks); + uint32_t col = 0; + for (const TrackSize& sz : gridReflowInput.mCols.mSizes) { + colTrackPositions.AppendElement(sz.mPosition); + colTrackSizes.AppendElement(sz.mBase); + bool isRepeat = ((col >= gridReflowInput.mColFunctions.mRepeatAutoStart) && + (col < gridReflowInput.mColFunctions.mRepeatAutoEnd)); + colTrackStates.AppendElement( + isRepeat ? + (uint32_t)mozilla::dom::GridTrackState::Repeat : + (uint32_t)mozilla::dom::GridTrackState::Static + ); + + col++; + } + ComputedGridTrackInfo* colInfo = new ComputedGridTrackInfo( + gridReflowInput.mColFunctions.mExplicitGridOffset, + gridReflowInput.mColFunctions.NumExplicitTracks(), + 0, + col, + Move(colTrackPositions), + Move(colTrackSizes), + Move(colTrackStates), + Move(colRemovedRepeatTracks), + gridReflowInput.mColFunctions.mRepeatAutoStart); + Properties().Set(GridColTrackInfo(), colInfo); + + uint32_t rowTrackCount = gridReflowInput.mRows.mSizes.Length(); + nsTArray<nscoord> rowTrackPositions(rowTrackCount); + nsTArray<nscoord> rowTrackSizes(rowTrackCount); + nsTArray<uint32_t> rowTrackStates(rowTrackCount); + nsTArray<bool> rowRemovedRepeatTracks( + gridReflowInput.mRowFunctions.mRemovedRepeatTracks); + uint32_t row = 0; + for (const TrackSize& sz : gridReflowInput.mRows.mSizes) { + rowTrackPositions.AppendElement(sz.mPosition); + rowTrackSizes.AppendElement(sz.mBase); + bool isRepeat = ((row >= gridReflowInput.mRowFunctions.mRepeatAutoStart) && + (row < gridReflowInput.mRowFunctions.mRepeatAutoEnd)); + rowTrackStates.AppendElement( + isRepeat ? + (uint32_t)mozilla::dom::GridTrackState::Repeat : + (uint32_t)mozilla::dom::GridTrackState::Static + ); + + row++; + } + // Row info has to accomodate fragmentation of the grid, which may happen in + // later calls to Reflow. For now, presume that no more fragmentation will + // occur. + ComputedGridTrackInfo* rowInfo = new ComputedGridTrackInfo( + gridReflowInput.mRowFunctions.mExplicitGridOffset, + gridReflowInput.mRowFunctions.NumExplicitTracks(), + gridReflowInput.mStartRow, + row, + Move(rowTrackPositions), + Move(rowTrackSizes), + Move(rowTrackStates), + Move(rowRemovedRepeatTracks), + gridReflowInput.mRowFunctions.mRepeatAutoStart); + Properties().Set(GridRowTrackInfo(), rowInfo); + + if (prevInFlow) { + // This frame is fragmenting rows from a previous frame, so patch up + // the prior GridRowTrackInfo with a new end row. + + // FIXME: This can be streamlined and/or removed when bug 1151204 lands. + + ComputedGridTrackInfo* priorRowInfo = + prevInFlow->Properties().Get(GridRowTrackInfo()); + + // Adjust track positions based on the first track in this fragment. + if (priorRowInfo->mPositions.Length() > + priorRowInfo->mStartFragmentTrack) { + nscoord delta = + priorRowInfo->mPositions[priorRowInfo->mStartFragmentTrack]; + for (nscoord& pos : priorRowInfo->mPositions) { + pos -= delta; + } + } + + ComputedGridTrackInfo* revisedPriorRowInfo = new ComputedGridTrackInfo( + priorRowInfo->mNumLeadingImplicitTracks, + priorRowInfo->mNumExplicitTracks, + priorRowInfo->mStartFragmentTrack, + gridReflowInput.mStartRow, + Move(priorRowInfo->mPositions), + Move(priorRowInfo->mSizes), + Move(priorRowInfo->mStates), + Move(priorRowInfo->mRemovedRepeatTracks), + priorRowInfo->mRepeatFirstTrack); + prevInFlow->Properties().Set(GridRowTrackInfo(), revisedPriorRowInfo); + } + + // Generate the line info properties. We need to provide the number of + // repeat tracks produced in the reflow. Only explicit names are assigned + // to lines here; the mozilla::dom::GridLines class will later extract + // implicit names from grid areas and assign them to the appropriate lines. + + // Generate column lines first. + uint32_t capacity = gridReflowInput.mCols.mSizes.Length(); + const nsStyleGridTemplate& gridColTemplate = + gridReflowInput.mGridStyle->mGridTemplateColumns; + nsTArray<nsTArray<nsString>> columnLineNames(capacity); + for (col = 0; col <= gridReflowInput.mCols.mSizes.Length(); col++) { + // Offset col by the explicit grid offset, to get the original names. + nsTArray<nsString> explicitNames = + gridReflowInput.mCols.GetExplicitLineNamesAtIndex( + gridColTemplate, + gridReflowInput.mColFunctions, + col - gridReflowInput.mColFunctions.mExplicitGridOffset); + + columnLineNames.AppendElement(explicitNames); + } + ComputedGridLineInfo* columnLineInfo = new ComputedGridLineInfo( + Move(columnLineNames), + gridColTemplate.mRepeatAutoLineNameListBefore, + gridColTemplate.mRepeatAutoLineNameListAfter); + Properties().Set(GridColumnLineInfo(), columnLineInfo); + + // Generate row lines next. + capacity = gridReflowInput.mRows.mSizes.Length(); + const nsStyleGridTemplate& gridRowTemplate = + gridReflowInput.mGridStyle->mGridTemplateRows; + nsTArray<nsTArray<nsString>> rowLineNames(capacity); + for (row = 0; row <= gridReflowInput.mRows.mSizes.Length(); row++) { + // Offset row by the explicit grid offset, to get the original names. + nsTArray<nsString> explicitNames = + gridReflowInput.mRows.GetExplicitLineNamesAtIndex( + gridRowTemplate, + gridReflowInput.mRowFunctions, + row - gridReflowInput.mRowFunctions.mExplicitGridOffset); + + rowLineNames.AppendElement(explicitNames); + } + ComputedGridLineInfo* rowLineInfo = new ComputedGridLineInfo( + Move(rowLineNames), + gridRowTemplate.mRepeatAutoLineNameListBefore, + gridRowTemplate.mRepeatAutoLineNameListAfter); + Properties().Set(GridRowLineInfo(), rowLineInfo); + + // Generate area info for explicit areas. Implicit areas are handled + // elsewhere. + if (gridReflowInput.mGridStyle->mGridTemplateAreas) { + nsTArray<css::GridNamedArea>* areas = new nsTArray<css::GridNamedArea>( + gridReflowInput.mGridStyle->mGridTemplateAreas->mNamedAreas); + Properties().Set(ExplicitNamedAreasProperty(), areas); + } else { + Properties().Delete(ExplicitNamedAreasProperty()); + } + } + + if (!prevInFlow) { + SharedGridData* sharedGridData = Properties().Get(SharedGridData::Prop()); + if (!NS_FRAME_IS_FULLY_COMPLETE(aStatus)) { + if (!sharedGridData) { + sharedGridData = new SharedGridData; + Properties().Set(SharedGridData::Prop(), sharedGridData); + } + sharedGridData->mCols.mSizes.Clear(); + sharedGridData->mCols.mSizes.SwapElements(gridReflowInput.mCols.mSizes); + sharedGridData->mCols.mContentBoxSize = gridReflowInput.mCols.mContentBoxSize; + sharedGridData->mCols.mBaselineSubtreeAlign[0] = + gridReflowInput.mCols.mBaselineSubtreeAlign[0]; + sharedGridData->mCols.mBaselineSubtreeAlign[1] = + gridReflowInput.mCols.mBaselineSubtreeAlign[1]; + sharedGridData->mRows.mSizes.Clear(); + sharedGridData->mRows.mSizes.SwapElements(gridReflowInput.mRows.mSizes); + // Save the original row grid sizes and gaps so we can restore them later + // in GridReflowInput::Initialize for the continuations. + auto& origRowData = sharedGridData->mOriginalRowData; + origRowData.ClearAndRetainStorage(); + origRowData.SetCapacity(sharedGridData->mRows.mSizes.Length()); + nscoord prevTrackEnd = 0; + for (auto& sz : sharedGridData->mRows.mSizes) { + SharedGridData::RowData data = {sz.mBase, sz.mPosition - prevTrackEnd}; + origRowData.AppendElement(data); + prevTrackEnd = sz.mPosition + sz.mBase; + } + sharedGridData->mRows.mContentBoxSize = gridReflowInput.mRows.mContentBoxSize; + sharedGridData->mRows.mBaselineSubtreeAlign[0] = + gridReflowInput.mRows.mBaselineSubtreeAlign[0]; + sharedGridData->mRows.mBaselineSubtreeAlign[1] = + gridReflowInput.mRows.mBaselineSubtreeAlign[1]; + sharedGridData->mGridItems.Clear(); + sharedGridData->mGridItems.SwapElements(gridReflowInput.mGridItems); + sharedGridData->mAbsPosItems.Clear(); + sharedGridData->mAbsPosItems.SwapElements(gridReflowInput.mAbsPosItems); + + sharedGridData->mGenerateComputedGridInfo = + HasAnyStateBits(NS_STATE_GRID_GENERATE_COMPUTED_VALUES); + } else if (sharedGridData && !GetNextInFlow()) { + Properties().Delete(SharedGridData::Prop()); + } + } + + FinishAndStoreOverflow(&aDesiredSize); + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); +} + +nscoord +nsGridContainerFrame::IntrinsicISize(nsRenderingContext* aRenderingContext, + IntrinsicISizeType aType) +{ + RenumberList(); + + // Calculate the sum of column sizes under intrinsic sizing. + // http://dev.w3.org/csswg/css-grid/#intrinsic-sizes + GridReflowInput state(this, *aRenderingContext); + InitImplicitNamedAreas(state.mGridStyle); // XXX optimize + + auto GetDefiniteSizes = [] (const nsStyleCoord& aMinCoord, + const nsStyleCoord& aSizeCoord, + const nsStyleCoord& aMaxCoord, + nscoord* aMin, + nscoord* aSize, + nscoord* aMax) { + if (aMinCoord.ConvertsToLength()) { + *aMin = aMinCoord.ToLength(); + } + if (aMaxCoord.ConvertsToLength()) { + *aMax = std::max(*aMin, aMaxCoord.ToLength()); + } + if (aSizeCoord.ConvertsToLength()) { + *aSize = Clamp(aSizeCoord.ToLength(), *aMin, *aMax); + } + }; + // The min/sz/max sizes are the input to the "repeat-to-fill" algorithm: + // https://drafts.csswg.org/css-grid/#auto-repeat + // They're only used for auto-repeat so we skip computing them otherwise. + LogicalSize min(state.mWM, 0, 0); + LogicalSize sz(state.mWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + LogicalSize max(state.mWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + if (state.mColFunctions.mHasRepeatAuto) { + GetDefiniteSizes(state.mGridStyle->MinISize(state.mWM), + state.mGridStyle->ISize(state.mWM), + state.mGridStyle->MaxISize(state.mWM), + &min.ISize(state.mWM), + &sz.ISize(state.mWM), + &max.ISize(state.mWM)); + } + if (state.mRowFunctions.mHasRepeatAuto && + !(state.mGridStyle->mGridAutoFlow & NS_STYLE_GRID_AUTO_FLOW_ROW)) { + // Only 'grid-auto-flow:column' can create new implicit columns, so that's + // the only case where our block-size can affect the number of columns. + GetDefiniteSizes(state.mGridStyle->MinBSize(state.mWM), + state.mGridStyle->BSize(state.mWM), + state.mGridStyle->MaxBSize(state.mWM), + &min.BSize(state.mWM), + &sz.BSize(state.mWM), + &max.BSize(state.mWM)); + } + + Grid grid; + grid.PlaceGridItems(state, min, sz, max); // XXX optimize + if (grid.mGridColEnd == 0) { + return 0; + } + state.mCols.Initialize(state.mColFunctions, state.mGridStyle->mGridColumnGap, + grid.mGridColEnd, NS_UNCONSTRAINEDSIZE); + auto constraint = aType == nsLayoutUtils::MIN_ISIZE ? + SizingConstraint::eMinContent : SizingConstraint::eMaxContent; + state.mCols.CalculateSizes(state, state.mGridItems, state.mColFunctions, + NS_UNCONSTRAINEDSIZE, &GridArea::mCols, + constraint); + return state.mCols.BackComputedIntrinsicSize(state.mColFunctions, + state.mGridStyle->mGridColumnGap); +} + +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; + mBaseline[0][0] = NS_INTRINSIC_WIDTH_UNKNOWN; + mBaseline[0][1] = NS_INTRINSIC_WIDTH_UNKNOWN; + mBaseline[1][0] = NS_INTRINSIC_WIDTH_UNKNOWN; + mBaseline[1][1] = 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); + if (GetPrevInFlow()) { + DisplayOverflowContainers(aBuilder, aDirtyRect, 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. + typedef GridItemCSSOrderIterator::OrderState OrderState; + OrderState order = HasAnyStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER) + ? OrderState::eKnownOrdered + : OrderState::eKnownUnordered; + GridItemCSSOrderIterator iter(this, kPrincipalList, + GridItemCSSOrderIterator::eIncludeAll, order); + for (; !iter.AtEnd(); iter.Next()) { + nsIFrame* child = *iter; + BuildDisplayListForChild(aBuilder, child, aDirtyRect, aLists, + ::GetDisplayFlagsForGridItem(child)); + } +} + +bool +nsGridContainerFrame::DrainSelfOverflowList() +{ + // Unlike nsContainerFrame::DrainSelfOverflowList we need to merge these lists + // so that the resulting mFrames is in document content order. + // NOTE: nsContainerFrame::AppendFrames/InsertFrames calls this method. + AutoFrameListPtr overflowFrames(PresContext(), StealOverflowFrames()); + if (overflowFrames) { + ::MergeSortedFrameLists(mFrames, *overflowFrames, GetContent()); + return true; + } + return false; +} + +void +nsGridContainerFrame::AppendFrames(ChildListID aListID, nsFrameList& aFrameList) +{ + NoteNewChildren(aListID, aFrameList); + nsContainerFrame::AppendFrames(aListID, aFrameList); +} + +void +nsGridContainerFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + NoteNewChildren(aListID, aFrameList); + nsContainerFrame::InsertFrames(aListID, aPrevFrame, aFrameList); +} + +void +nsGridContainerFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) +{ +#ifdef DEBUG + ChildListIDs supportedLists = + kAbsoluteList | kFixedList | kPrincipalList | kNoReflowPrincipalList; + MOZ_ASSERT(supportedLists.Contains(aListID), "unexpected child list"); + + // Note that kPrincipalList doesn't mean aOldFrame must be on that list. + // It can also be on kOverflowList, in which case it might be a pushed + // item, and if it's the only pushed item our DID_PUSH_ITEMS bit will lie. + if (aListID == kPrincipalList && !aOldFrame->GetPrevInFlow()) { + // Since the bit may lie, set the mDidPushItemsBitMayLie value to true for + // ourself and for all our contiguous previous-in-flow nsGridContainerFrames. + nsGridContainerFrame* frameThatMayLie = this; + do { + frameThatMayLie->mDidPushItemsBitMayLie = true; + frameThatMayLie = static_cast<nsGridContainerFrame*>( + frameThatMayLie->GetPrevInFlow()); + } while (frameThatMayLie); + } +#endif + + nsContainerFrame::RemoveFrame(aListID, aOldFrame); +} + +uint16_t +nsGridContainerFrame::CSSAlignmentForAbsPosChild(const ReflowInput& aChildRI, + LogicalAxis aLogicalAxis) const +{ + MOZ_ASSERT(aChildRI.mFrame->IsAbsolutelyPositioned(), + "This method should only be called for abspos children"); + + uint16_t alignment = (aLogicalAxis == eLogicalAxisInline) ? + aChildRI.mStylePosition->UsedJustifySelf(StyleContext()) : + aChildRI.mStylePosition->UsedAlignSelf(StyleContext()); + + // XXX strip off <overflow-position> bits until we implement it + // (bug 1311892) + alignment &= ~NS_STYLE_ALIGN_FLAG_BITS; + + if (alignment == NS_STYLE_ALIGN_NORMAL) { + // "the 'normal' keyword behaves as 'start' on replaced + // absolutely-positioned boxes, and behaves as 'stretch' on all other + // absolutely-positioned boxes." + // https://drafts.csswg.org/css-align/#align-abspos + // https://drafts.csswg.org/css-align/#justify-abspos + alignment = aChildRI.mFrame->IsFrameOfType(nsIFrame::eReplaced) ? + NS_STYLE_ALIGN_START : NS_STYLE_ALIGN_STRETCH; + } else if (alignment == NS_STYLE_ALIGN_FLEX_START) { + alignment = NS_STYLE_ALIGN_START; + } else if (alignment == NS_STYLE_ALIGN_FLEX_END) { + alignment = NS_STYLE_ALIGN_END; + } else if (alignment == NS_STYLE_ALIGN_LEFT || + alignment == NS_STYLE_ALIGN_RIGHT) { + if (aLogicalAxis == eLogicalAxisInline) { + const bool isLeft = (alignment == NS_STYLE_ALIGN_LEFT); + WritingMode wm = GetWritingMode(); + alignment = (isLeft == wm.IsBidiLTR()) ? NS_STYLE_ALIGN_START + : NS_STYLE_ALIGN_END; + } else { + alignment = NS_STYLE_ALIGN_START; + } + } else if (alignment == NS_STYLE_ALIGN_BASELINE) { + alignment = NS_STYLE_ALIGN_START; + } else if (alignment == NS_STYLE_ALIGN_LAST_BASELINE) { + alignment = NS_STYLE_ALIGN_END; + } + + return alignment; +} + +nscoord +nsGridContainerFrame::SynthesizeBaseline( + const FindItemInGridOrderResult& aGridOrderItem, + LogicalAxis aAxis, + BaselineSharingGroup aGroup, + const nsSize& aCBPhysicalSize, + nscoord aCBSize, + WritingMode aCBWM) +{ + if (MOZ_UNLIKELY(!aGridOrderItem.mItem)) { + // No item in this fragment - synthesize a baseline from our border-box. + return ::SynthesizeBaselineFromBorderBox(aGroup, aCBWM, aCBSize); + } + auto GetBBaseline = [] (BaselineSharingGroup aGroup, WritingMode aWM, + const nsIFrame* aFrame, nscoord* aBaseline) { + return aGroup == BaselineSharingGroup::eFirst ? + nsLayoutUtils::GetFirstLineBaseline(aWM, aFrame, aBaseline) : + nsLayoutUtils::GetLastLineBaseline(aWM, aFrame, aBaseline); + }; + nsIFrame* child = aGridOrderItem.mItem->mFrame; + nsGridContainerFrame* grid = do_QueryFrame(child); + auto childWM = child->GetWritingMode(); + bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM); + nscoord baseline; + nscoord start; + nscoord size; + if (aAxis == eLogicalAxisBlock) { + start = child->GetLogicalNormalPosition(aCBWM, aCBPhysicalSize).B(aCBWM); + size = child->BSize(aCBWM); + if (grid && aGridOrderItem.mIsInEdgeTrack) { + isOrthogonal ? grid->GetIBaseline(aGroup, &baseline) : + grid->GetBBaseline(aGroup, &baseline); + } else if (!isOrthogonal && aGridOrderItem.mIsInEdgeTrack) { + baseline = child->BaselineBOffset(childWM, aGroup, AlignmentContext::eGrid); + } else { + baseline = ::SynthesizeBaselineFromBorderBox(aGroup, childWM, size); + } + } else { + start = child->GetLogicalNormalPosition(aCBWM, aCBPhysicalSize).I(aCBWM); + size = child->ISize(aCBWM); + if (grid && aGridOrderItem.mIsInEdgeTrack) { + isOrthogonal ? grid->GetBBaseline(aGroup, &baseline) : + grid->GetIBaseline(aGroup, &baseline); + } else if (isOrthogonal && aGridOrderItem.mIsInEdgeTrack && + GetBBaseline(aGroup, childWM, child, &baseline)) { + if (aGroup == BaselineSharingGroup::eLast) { + baseline = size - baseline; // convert to distance from border-box end + } + } else { + baseline = ::SynthesizeBaselineFromBorderBox(aGroup, childWM, size); + } + } + return aGroup == BaselineSharingGroup::eFirst ? start + baseline : + aCBSize - start - size + baseline; +} + +void +nsGridContainerFrame::CalculateBaselines( + BaselineSet aBaselineSet, + GridItemCSSOrderIterator* aIter, + const nsTArray<GridItemInfo>* aGridItems, + const Tracks& aTracks, + uint32_t aFragmentStartTrack, + uint32_t aFirstExcludedTrack, + WritingMode aWM, + const nsSize& aCBPhysicalSize, + nscoord aCBBorderPaddingStart, + nscoord aCBBorderPaddingEnd, + nscoord aCBSize) +{ + const auto axis = aTracks.mAxis; + auto firstBaseline = aTracks.mBaseline[BaselineSharingGroup::eFirst]; + if (!(aBaselineSet & BaselineSet::eFirst)) { + mBaseline[axis][BaselineSharingGroup::eFirst] = + ::SynthesizeBaselineFromBorderBox(BaselineSharingGroup::eFirst, aWM, + aCBSize); + } else if (firstBaseline == NS_INTRINSIC_WIDTH_UNKNOWN) { + FindItemInGridOrderResult gridOrderFirstItem = + FindFirstItemInGridOrder(*aIter, *aGridItems, + axis == eLogicalAxisBlock ? &GridArea::mRows : &GridArea::mCols, + axis == eLogicalAxisBlock ? &GridArea::mCols : &GridArea::mRows, + aFragmentStartTrack); + mBaseline[axis][BaselineSharingGroup::eFirst] = + SynthesizeBaseline(gridOrderFirstItem, + axis, + BaselineSharingGroup::eFirst, + aCBPhysicalSize, + aCBSize, + aWM); + } else { + // We have a 'first baseline' group in the start track in this fragment. + // Convert it from track to grid container border-box coordinates. + MOZ_ASSERT(!aGridItems->IsEmpty()); + nscoord gapBeforeStartTrack = aFragmentStartTrack == 0 ? + aTracks.GridLineEdge(aFragmentStartTrack, GridLineSide::eAfterGridGap) : + nscoord(0); // no content gap at start of fragment + mBaseline[axis][BaselineSharingGroup::eFirst] = + aCBBorderPaddingStart + gapBeforeStartTrack + firstBaseline; + } + + auto lastBaseline = aTracks.mBaseline[BaselineSharingGroup::eLast]; + if (!(aBaselineSet & BaselineSet::eLast)) { + mBaseline[axis][BaselineSharingGroup::eLast] = + ::SynthesizeBaselineFromBorderBox(BaselineSharingGroup::eLast, aWM, + aCBSize); + } else if (lastBaseline == NS_INTRINSIC_WIDTH_UNKNOWN) { + // For finding items for the 'last baseline' we need to create a reverse + // iterator ('aIter' is the forward iterator from the GridReflowInput). + using Iter = ReverseGridItemCSSOrderIterator; + auto orderState = aIter->ItemsAreAlreadyInOrder() ? + Iter::OrderState::eKnownOrdered : Iter::OrderState::eKnownUnordered; + Iter iter(this, kPrincipalList, Iter::ChildFilter::eSkipPlaceholders, + orderState); + iter.SetGridItemCount(aGridItems->Length()); + FindItemInGridOrderResult gridOrderLastItem = + FindLastItemInGridOrder(iter, *aGridItems, + axis == eLogicalAxisBlock ? &GridArea::mRows : &GridArea::mCols, + axis == eLogicalAxisBlock ? &GridArea::mCols : &GridArea::mRows, + aFragmentStartTrack, aFirstExcludedTrack); + mBaseline[axis][BaselineSharingGroup::eLast] = + SynthesizeBaseline(gridOrderLastItem, + axis, + BaselineSharingGroup::eLast, + aCBPhysicalSize, + aCBSize, + aWM); + } else { + // We have a 'last baseline' group in the end track in this fragment. + // Convert it from track to grid container border-box coordinates. + MOZ_ASSERT(!aGridItems->IsEmpty()); + auto borderBoxStartToEndOfEndTrack = aCBBorderPaddingStart + + aTracks.GridLineEdge(aFirstExcludedTrack, GridLineSide::eBeforeGridGap) - + aTracks.GridLineEdge(aFragmentStartTrack, GridLineSide::eBeforeGridGap); + mBaseline[axis][BaselineSharingGroup::eLast] = + (aCBSize - borderBoxStartToEndOfEndTrack) + lastBaseline; + } +} + +#ifdef DEBUG_FRAME_DUMP +nsresult +nsGridContainerFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("GridContainer"), aResult); +} +#endif + +void +nsGridContainerFrame::NoteNewChildren(ChildListID aListID, + const nsFrameList& aFrameList) +{ +#ifdef DEBUG + ChildListIDs supportedLists = + kAbsoluteList | kFixedList | kPrincipalList | kNoReflowPrincipalList; + MOZ_ASSERT(supportedLists.Contains(aListID), "unexpected child list"); +#endif + + nsIPresShell* shell = PresContext()->PresShell(); + for (auto pif = GetPrevInFlow(); pif; pif = pif->GetPrevInFlow()) { + if (aListID == kPrincipalList) { + pif->AddStateBits(NS_STATE_GRID_DID_PUSH_ITEMS); + } + shell->FrameNeedsReflow(pif, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY); + } +} + +void +nsGridContainerFrame::MergeSortedOverflow(nsFrameList& aList) +{ + if (aList.IsEmpty()) { + return; + } + MOZ_ASSERT(!aList.FirstChild()->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER), + "this is the wrong list to put this child frame"); + MOZ_ASSERT(aList.FirstChild()->GetParent() == this); + nsFrameList* overflow = GetOverflowFrames(); + if (overflow) { + ::MergeSortedFrameLists(*overflow, aList, GetContent()); + } else { + SetOverflowFrames(aList); + } +} + +void +nsGridContainerFrame::MergeSortedExcessOverflowContainers(nsFrameList& aList) +{ + if (aList.IsEmpty()) { + return; + } + MOZ_ASSERT(aList.FirstChild()->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER), + "this is the wrong list to put this child frame"); + MOZ_ASSERT(aList.FirstChild()->GetParent() == this); + nsFrameList* eoc = GetPropTableFrames(ExcessOverflowContainersProperty()); + if (eoc) { + ::MergeSortedFrameLists(*eoc, aList, GetContent()); + } else { + SetPropTableFrames(new (PresContext()->PresShell()) nsFrameList(aList), + ExcessOverflowContainersProperty()); + } +} + +/* static */ nsGridContainerFrame::FindItemInGridOrderResult +nsGridContainerFrame::FindFirstItemInGridOrder( + GridItemCSSOrderIterator& aIter, + const nsTArray<GridItemInfo>& aGridItems, + LineRange GridArea::* aMajor, + LineRange GridArea::* aMinor, + uint32_t aFragmentStartTrack) +{ + FindItemInGridOrderResult result = { nullptr, false }; + uint32_t minMajor = kTranslatedMaxLine + 1; + uint32_t minMinor = kTranslatedMaxLine + 1; + aIter.Reset(); + for (; !aIter.AtEnd(); aIter.Next()) { + const GridItemInfo& item = aGridItems[aIter.GridItemIndex()]; + if ((item.mArea.*aMajor).mEnd <= aFragmentStartTrack) { + continue; // item doesn't span any track in this fragment + } + uint32_t major = (item.mArea.*aMajor).mStart; + uint32_t minor = (item.mArea.*aMinor).mStart; + if (major < minMajor || (major == minMajor && minor < minMinor)) { + minMajor = major; + minMinor = minor; + result.mItem = &item; + result.mIsInEdgeTrack = major == 0U; + } + } + return result; +} + +/* static */ nsGridContainerFrame::FindItemInGridOrderResult +nsGridContainerFrame::FindLastItemInGridOrder( + ReverseGridItemCSSOrderIterator& aIter, + const nsTArray<GridItemInfo>& aGridItems, + LineRange GridArea::* aMajor, + LineRange GridArea::* aMinor, + uint32_t aFragmentStartTrack, + uint32_t aFirstExcludedTrack) +{ + FindItemInGridOrderResult result = { nullptr, false }; + int32_t maxMajor = -1; + int32_t maxMinor = -1; + aIter.Reset(); + int32_t lastMajorTrack = int32_t(aFirstExcludedTrack) - 1; + for (; !aIter.AtEnd(); aIter.Next()) { + const GridItemInfo& item = aGridItems[aIter.GridItemIndex()]; + // Subtract 1 from the end line to get the item's last track index. + int32_t major = (item.mArea.*aMajor).mEnd - 1; + // Currently, this method is only called with aFirstExcludedTrack == + // the first track in the next fragment, so we take the opportunity + // to assert this item really belongs to this fragment. + MOZ_ASSERT((item.mArea.*aMajor).mStart < aFirstExcludedTrack, + "found an item that belongs to some later fragment"); + if (major < int32_t(aFragmentStartTrack)) { + continue; // item doesn't span any track in this fragment + } + int32_t minor = (item.mArea.*aMinor).mEnd - 1; + MOZ_ASSERT(minor >= 0 && major >= 0, "grid item must have span >= 1"); + if (major > maxMajor || (major == maxMajor && minor > maxMinor)) { + maxMajor = major; + maxMinor = minor; + result.mItem = &item; + result.mIsInEdgeTrack = major == lastMajorTrack; + } + } + return result; +} + +#ifdef DEBUG +void +nsGridContainerFrame::SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) +{ + ChildListIDs supportedLists = kAbsoluteList | kFixedList | kPrincipalList; + MOZ_ASSERT(supportedLists.Contains(aListID), "unexpected child list"); + + return nsContainerFrame::SetInitialChildList(aListID, aChildList); +} + +void +nsGridContainerFrame::SanityCheckGridItemsBeforeReflow() const +{ + ChildListIDs absLists = kAbsoluteList | kFixedList | + kOverflowContainersList | kExcessOverflowContainersList; + ChildListIDs itemLists = kPrincipalList | kOverflowList; + for (const nsIFrame* f = this; f; f = f->GetNextInFlow()) { + MOZ_ASSERT(!f->HasAnyStateBits(NS_STATE_GRID_DID_PUSH_ITEMS), + "At start of reflow, we should've pulled items back from all " + "NIFs and cleared NS_STATE_GRID_DID_PUSH_ITEMS in the process"); + for (nsIFrame::ChildListIterator childLists(f); + !childLists.IsDone(); childLists.Next()) { + if (!itemLists.Contains(childLists.CurrentID())) { + MOZ_ASSERT(absLists.Contains(childLists.CurrentID()), + "unexpected non-empty child list"); + continue; + } + for (auto child : childLists.CurrentList()) { + MOZ_ASSERT(f == this || child->GetPrevInFlow(), + "all pushed items must be pulled up before reflow"); + } + } + } + // If we have a prev-in-flow, each of its children's next-in-flow + // should be one of our children or be null. + const auto pif = static_cast<nsGridContainerFrame*>(GetPrevInFlow()); + if (pif) { + const nsFrameList* oc = + GetPropTableFrames(OverflowContainersProperty()); + const nsFrameList* eoc = + GetPropTableFrames(ExcessOverflowContainersProperty()); + const nsFrameList* pifEOC = + pif->GetPropTableFrames(ExcessOverflowContainersProperty()); + for (const nsIFrame* child : pif->GetChildList(kPrincipalList)) { + const nsIFrame* childNIF = child->GetNextInFlow(); + MOZ_ASSERT(!childNIF || mFrames.ContainsFrame(childNIF) || + (pifEOC && pifEOC->ContainsFrame(childNIF)) || + (oc && oc->ContainsFrame(childNIF)) || + (eoc && eoc->ContainsFrame(childNIF))); + } + } +} + +void +nsGridContainerFrame::TrackSize::Dump() const +{ + printf("mPosition=%d mBase=%d mLimit=%d", mPosition, mBase, mLimit); + + printf(" min:"); + if (mState & eAutoMinSizing) { + printf("auto "); + } else if (mState & eMinContentMinSizing) { + printf("min-content "); + } else if (mState & eMaxContentMinSizing) { + printf("max-content "); + } + + 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 "); + } + if (mState & eBreakBefore) { + printf("break-before "); + } +} + +#endif // DEBUG + +nsGridContainerFrame* +nsGridContainerFrame::GetGridFrameWithComputedInfo(nsIFrame* aFrame) +{ + // Prepare a lambda function that we may need to call multiple times. + auto GetGridContainerFrame = [](nsIFrame *aFrame) { + // Return the aFrame's content insertion frame, iff it is + // a grid container. + nsGridContainerFrame* gridFrame = nullptr; + + if (aFrame) { + nsIFrame* contentFrame = aFrame->GetContentInsertionFrame(); + if (contentFrame && + (contentFrame->GetType() == nsGkAtoms::gridContainerFrame)) { + gridFrame = static_cast<nsGridContainerFrame*>(contentFrame); + } + } + return gridFrame; + }; + + nsGridContainerFrame* gridFrame = GetGridContainerFrame(aFrame); + if (gridFrame) { + // if any of our properties are missing, generate them + bool reflowNeeded = (!gridFrame->Properties().Has(GridColTrackInfo()) || + !gridFrame->Properties().Has(GridRowTrackInfo()) || + !gridFrame->Properties().Has(GridColumnLineInfo()) || + !gridFrame->Properties().Has(GridRowLineInfo())); + + if (reflowNeeded) { + // Trigger a reflow that generates additional grid property data. + nsIPresShell* shell = gridFrame->PresContext()->PresShell(); + gridFrame->AddStateBits(NS_STATE_GRID_GENERATE_COMPUTED_VALUES); + shell->FrameNeedsReflow(gridFrame, + nsIPresShell::eResize, + NS_FRAME_IS_DIRTY); + shell->FlushPendingNotifications(Flush_Layout); + + // Since the reflow may have side effects, get the grid frame again. + gridFrame = GetGridContainerFrame(aFrame); + + // Assert the grid properties are present + MOZ_ASSERT(!gridFrame || + gridFrame->Properties().Has(GridColTrackInfo())); + MOZ_ASSERT(!gridFrame || + gridFrame->Properties().Has(GridRowTrackInfo())); + MOZ_ASSERT(!gridFrame || + gridFrame->Properties().Has(GridColumnLineInfo())); + MOZ_ASSERT(!gridFrame || + gridFrame->Properties().Has(GridRowLineInfo())); + } + } + + return gridFrame; +} |