/* -*- 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 // for std::stable_sort #include #include #include "mozilla/CSSAlignUtils.h" #include "mozilla/dom/GridBinding.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 > 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) { aStatus->SetIncomplete(); } else { aSize = maxSize; } } else { aStatus->SetIncomplete(); } 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.GetEnumValue() == StyleGridTrackBreadth::MinContent; } 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 : 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, // 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, " 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 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::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 mIter; Maybe mIterEnd; // Used if child list is *not* in ascending 'order'. // This array is pre-sorted in reverse order for a reverse iterator. Maybe> 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 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& 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& 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& 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& 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>& mLineNameLists; const nsTArray& mRepeatAutoLineNameListBefore; const nsTArray& 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& mMinSizingFunctions; const nsTArray& 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 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 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& aBaselineItems, BaselineSharingGroup aBaselineGroup); /** * Initialize grid item baseline state and offsets. */ void InitializeItemBaselines(GridReflowInput& aState, nsTArray& 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& 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 * 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& aPlan, const LineRange& aRange, TrackSize::StateBits aSelector, nsTArray& 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& aPlan, const nsTArray& aTracks) const { for (uint32_t track : aTracks) { aPlan[track] = mSizes[track]; } } void CopyPlanToBase(const nsTArray& aPlan, const nsTArray& aTracks) { for (uint32_t track : aTracks) { MOZ_ASSERT(mSizes[track].mBase <= aPlan[track].mBase); mSizes[track].mBase = aPlan[track].mBase; } } void CopyPlanToLimit(const nsTArray& aPlan, const nsTArray& 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 = std::function; /** * 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& aPlan, const nsTArray& 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(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& aPlan, uint32_t aNumGrowable, const nsTArray& 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& aPlan, const nsTArray& 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 didClamp = false; while (numGrowable) { nscoord spacePerTrack = std::max(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& aPlan, nsTArray& 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& aPlan, nsTArray& 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(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& 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& aGridItems, const nsTArray& 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& 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& 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 GetExplicitLineNamesAtIndex( const nsStyleGridTemplate& aGridTemplate, const TrackSizingFunctions& aFunctions, uint32_t aIndex) { nsTArray lineNames; bool hasRepeatAuto = aGridTemplate.HasRepeatAuto(); const nsTArray>& 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 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 mOriginalRowData; nsTArray mGridItems; nsTArray 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 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 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 mGridItems; /** * Info about each grid-aligned abs.pos. child. */ nsTArray 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 ConsumedBSize(). */ 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 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& 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& 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& 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> 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>& 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 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: must not be zero."); line = int32_t(aFromIndex) + aNth; } else { if (aNth == 0) { // 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 // -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 N' edgeLine = aSide == eLineRangeSideStart ? 1 : aExplicitGridEnd; } else { // http://dev.w3.org/csswg/css-grid/#grid-placement-int // ' 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 / span * // span / auto return LinePair(kAutoLine, aStart.mInteger); } // span / span * // span / 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 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 MOZ_ASSERT(aEnd.mInteger != 0); return LinePair(start, aEnd.mInteger); } // http://dev.w3.org/csswg/css-grid/#grid-placement-errors // auto / span 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 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& 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& cellsInRow = mCellMap.mCells[i]; const uint32_t clampedJEnd = std::min(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> 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 save1(mGridColEnd); AutoRestore 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> 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> 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 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 mMinSize; Maybe mMinContentContribution; Maybe 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& 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& 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& aGridItems) { nsTArray firstBaselineItems; nsTArray 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& 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 stateBitsPerSpan; nsTArray 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 tracks(maxSpan); nsTArray 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& 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 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& aGridItems, const nsTArray& 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 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& aGridItems, const TrackSizingFunctions& aFunctions, nscoord aAvailableSize) { if (aAvailableSize <= 0) { return; } nsTArray 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> 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& 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& 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::GetNearestFragmentainer(const GridReflowInput& aState) const { Maybe 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, const Maybe& 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>* 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 (aStatus.IsComplete()) { 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 sortedItems(aState.mGridItems.Length()); nsTArray 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(childStatus.IsComplete(), "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.SetInlineLineBreakBeforeAndReset(); 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.SetInlineLineBreakAfter(); // 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.SetInlineLineBreakBeforeAndReset(); 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.SetInlineLineBreakBeforeAndReset(); 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) { aStatus.SetIncomplete(); } 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); } } aStatus.SetIncomplete(); } 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 (aStatus.IsComplete()) { aStatus.SetOverflowIncomplete(); aStatus.SetNextInFlowNeedsReflow(); } } 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& 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(childStatus.IsInlineBreakBefore() || !childStatus.IsFullyComplete() || !child->GetNextInFlow(), "fully-complete reflow should destroy any NIFs"); if (childStatus.IsInlineBreakBefore()) { MOZ_ASSERT(!child->GetPrevInFlow(), "continuations should never report BREAK_BEFORE status"); MOZ_ASSERT(!aFragmentainer.mIsTopOfPage, "got IsInlineBreakBefore() 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 (aStatus.IsIncomplete()) { 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(); aStatus.SetIncomplete(); 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?"); childStatus.SetIncomplete(); } } else if (childStatus.IsInlineBreakAfter()) { MOZ_ASSERT_UNREACHABLE("unexpected child reflow status"); } if (childStatus.IsIncomplete()) { incompleteItems.PutEntry(child); } else if (!childStatus.IsFullyComplete()) { 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 (aStatus.IsComplete()) { aStatus.SetOverflowIncomplete(); aStatus.SetNextInFlowNeedsReflow(); } // 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(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 lastParent = this; auto nif = static_cast(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(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 = 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(aStatus.IsComplete(), "child should be complete " "in unconstrained reflow"); } } // Merge overflow container bounds and status. aDesiredSize.mOverflowAreas.UnionWith(ocBounds); aStatus.MergeCompletionStatusFrom(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(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 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(GetNextInFlow()); auto firstNIF = nif; DebugOnly 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(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 = ConsumedBSize(wm); 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 (!aStatus.IsComplete() && !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 (!aStatus.IsComplete()) { aStatus.SetOverflowIncomplete(); aStatus.SetNextInFlowNeedsReflow(); } 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 iter; Maybe> 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 colTrackPositions(colTrackCount); nsTArray colTrackSizes(colTrackCount); nsTArray colTrackStates(colTrackCount); nsTArray 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 rowTrackPositions(rowTrackCount); nsTArray rowTrackSizes(rowTrackCount); nsTArray rowTrackStates(rowTrackCount); nsTArray 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> columnLineNames(capacity); for (col = 0; col <= gridReflowInput.mCols.mSizes.Length(); col++) { // Offset col by the explicit grid offset, to get the original names. nsTArray 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> rowLineNames(capacity); for (row = 0; row <= gridReflowInput.mRows.mSizes.Length(); row++) { // Offset row by the explicit grid offset, to get the original names. nsTArray 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* areas = new nsTArray( gridReflowInput.mGridStyle->mGridTemplateAreas->mNamedAreas); Properties().Set(ExplicitNamedAreasProperty(), areas); } else { Properties().Delete(ExplicitNamedAreasProperty()); } } if (!prevInFlow) { SharedGridData* sharedGridData = Properties().Get(SharedGridData::Prop()); if (!aStatus.IsFullyComplete()) { 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( 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(nullptr) : aChildRI.mStylePosition->UsedAlignSelf(nullptr); // XXX strip off bits until we implement it // (bug 1311892) alignment &= ~NS_STYLE_ALIGN_FLAG_BITS; // We group 'auto' with 'normal', because the spec says: // "The 'auto' keyword is interpreted as 'normal' // if the box is absolutely positioned [...]" // https://drafts.csswg.org/css-align-3/#valdef-align-self-auto // https://drafts.csswg.org/css-align-3/#valdef-justify-self-auto if (alignment == NS_STYLE_ALIGN_AUTO || 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* 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& 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& 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(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(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(FlushType::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; }