/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* rendering object for CSS "display: grid | inline-grid" */ #include "nsGridContainerFrame.h" #include #include #include // for div() #include #include "gfxContext.h" #include "mozilla/AutoRestore.h" #include "mozilla/ComputedStyle.h" #include "mozilla/CSSAlignUtils.h" #include "mozilla/StaticPrefs_layout.h" #include "mozilla/dom/GridBinding.h" #include "mozilla/IntegerRange.h" #include "mozilla/Maybe.h" #include "mozilla/PodOperations.h" // for PodZero #include "mozilla/Poison.h" #include "mozilla/PresShell.h" #include "nsAbsoluteContainingBlock.h" #include "nsAlgorithm.h" // for clamped() #include "nsBoxLayoutState.h" #include "nsCSSAnonBoxes.h" #include "nsCSSFrameConstructor.h" #include "nsTHashMap.h" #include "nsDisplayList.h" #include "nsHashKeys.h" #include "nsFieldSetFrame.h" #include "nsIFrameInlines.h" #include "nsPlaceholderFrame.h" #include "nsPresContext.h" #include "nsReadableUtils.h" #include "nsTableWrapperFrame.h" using namespace mozilla; typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags; typedef nsGridContainerFrame::TrackSize TrackSize; typedef mozilla::CSSAlignUtils::AlignJustifyFlags AlignJustifyFlags; using GridTemplate = StyleGridTemplateComponent; using TrackListValue = StyleGenericTrackListValue; using TrackRepeat = StyleGenericTrackRepeat; using NameList = StyleOwnedSlice; using SizingConstraint = nsGridContainerFrame::SizingConstraint; using GridItemCachedBAxisMeasurement = nsGridContainerFrame::CachedBAxisMeasurement; static mozilla::LazyLogModule gGridContainerLog("GridContainer"); #define GRID_LOG(...) \ MOZ_LOG(gGridContainerLog, LogLevel::Debug, (__VA_ARGS__)); static const int32_t kMaxLine = StyleMAX_GRID_LINE; static const int32_t kMinLine = StyleMIN_GRID_LINE; // The maximum line number, in the zero-based translated grid. static const uint32_t kTranslatedMaxLine = uint32_t(kMaxLine - kMinLine); static const uint32_t kAutoLine = kTranslatedMaxLine + 3457U; static const nsFrameState kIsSubgridBits = (NS_STATE_GRID_IS_COL_SUBGRID | NS_STATE_GRID_IS_ROW_SUBGRID); namespace mozilla { template <> inline Span> GridTemplate::LineNameLists(bool aIsSubgrid) const { if (IsTrackList()) { return AsTrackList()->line_names.AsSpan(); } if (IsSubgrid() && aIsSubgrid) { return AsSubgrid()->names.AsSpan(); } MOZ_ASSERT(IsNone() || IsMasonry() || (IsSubgrid() && !aIsSubgrid)); return {}; } template <> inline const StyleTrackBreadth& StyleTrackSize::GetMax() const { if (IsBreadth()) { return AsBreadth(); } if (IsMinmax()) { return AsMinmax()._1; } MOZ_ASSERT(IsFitContent()); return AsFitContent(); } template <> inline const StyleTrackBreadth& StyleTrackSize::GetMin() const { static const StyleTrackBreadth kAuto = StyleTrackBreadth::Auto(); if (IsBreadth()) { // behaves like minmax(auto, ) return AsBreadth().IsFr() ? kAuto : AsBreadth(); } if (IsMinmax()) { return AsMinmax()._0; } MOZ_ASSERT(IsFitContent()); return kAuto; } } // namespace mozilla 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; } template static bool IsPercentOfIndefiniteSize(const Size& aCoord, nscoord aPercentBasis) { return aPercentBasis == NS_UNCONSTRAINEDSIZE && aCoord.HasPercent(); } static nscoord ResolveToDefiniteSize(const StyleTrackBreadth& aBreadth, nscoord aPercentBasis) { MOZ_ASSERT(aBreadth.IsBreadth()); if (::IsPercentOfIndefiniteSize(aBreadth.AsBreadth(), aPercentBasis)) { return nscoord(0); } return std::max(nscoord(0), aBreadth.AsBreadth().Resolve(aPercentBasis)); } // 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::First) { return aWM.IsAlphabeticalBaseline() ? aBorderBoxSize : aBorderBoxSize / 2; } MOZ_ASSERT(aGroup == BaselineSharingGroup::Last); // Round up for central baseline offset, to be consistent with eFirst. return aWM.IsAlphabeticalBaseline() ? 0 : (aBorderBoxSize / 2) + (aBorderBoxSize % 2); } // The input sizes for calculating the number of repeat(auto-fill/fit) tracks. // https://drafts.csswg.org/css-grid/#auto-repeat struct RepeatTrackSizingInput { explicit RepeatTrackSizingInput(WritingMode aWM) : mMin(aWM, 0, 0), mSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE), mMax(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) {} RepeatTrackSizingInput(const LogicalSize& aMin, const LogicalSize& aSize, const LogicalSize& aMax) : mMin(aMin), mSize(aSize), mMax(aMax) {} // This should be used in intrinsic sizing (i.e. when we can't initialize // the sizes directly from ReflowInput values). void InitFromStyle(LogicalAxis aAxis, WritingMode aWM, const ComputedStyle* aStyle) { const auto& pos = aStyle->StylePosition(); const bool borderBoxSizing = pos->mBoxSizing == StyleBoxSizing::Border; nscoord bp = NS_UNCONSTRAINEDSIZE; // a sentinel to calculate it only once auto adjustForBoxSizing = [borderBoxSizing, aWM, aAxis, aStyle, &bp](nscoord aSize) { if (!borderBoxSizing) { return aSize; } if (bp == NS_UNCONSTRAINEDSIZE) { const auto& padding = aStyle->StylePadding()->mPadding; LogicalMargin border(aWM, aStyle->StyleBorder()->GetComputedBorder()); // We can use zero percentage basis since this is only called from // intrinsic sizing code. const nscoord percentageBasis = 0; if (aAxis == eLogicalAxisInline) { bp = std::max(padding.GetIStart(aWM).Resolve(percentageBasis), 0) + std::max(padding.GetIEnd(aWM).Resolve(percentageBasis), 0) + border.IStartEnd(aWM); } else { bp = std::max(padding.GetBStart(aWM).Resolve(percentageBasis), 0) + std::max(padding.GetBEnd(aWM).Resolve(percentageBasis), 0) + border.BStartEnd(aWM); } } return std::max(aSize - bp, 0); }; nscoord& min = mMin.Size(aAxis, aWM); nscoord& size = mSize.Size(aAxis, aWM); nscoord& max = mMax.Size(aAxis, aWM); const auto& minCoord = aAxis == eLogicalAxisInline ? pos->MinISize(aWM) : pos->MinBSize(aWM); if (minCoord.ConvertsToLength()) { min = adjustForBoxSizing(minCoord.ToLength()); } const auto& maxCoord = aAxis == eLogicalAxisInline ? pos->MaxISize(aWM) : pos->MaxBSize(aWM); if (maxCoord.ConvertsToLength()) { max = std::max(min, adjustForBoxSizing(maxCoord.ToLength())); } const auto& sizeCoord = aAxis == eLogicalAxisInline ? pos->ISize(aWM) : pos->BSize(aWM); if (sizeCoord.ConvertsToLength()) { size = Clamp(adjustForBoxSizing(sizeCoord.ToLength()), min, max); } } LogicalSize mMin; LogicalSize mSize; LogicalSize mMax; }; enum class GridLineSide { BeforeGridGap, AfterGridGap, }; struct nsGridContainerFrame::TrackSize { enum StateBits : uint16_t { // clang-format off eAutoMinSizing = 0x1, eMinContentMinSizing = 0x2, eMaxContentMinSizing = 0x4, eMinOrMaxContentMinSizing = eMinContentMinSizing | eMaxContentMinSizing, eIntrinsicMinSizing = eMinOrMaxContentMinSizing | eAutoMinSizing, eModified = 0x8, 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, eInfinitelyGrowable = 0x2000, // These are only used in the masonry axis. They share the same value // as *MinSizing above, but that's OK because we don't use those in // the masonry axis. // // This track corresponds to an item margin-box size that is stretching. eItemStretchSize = 0x1, // This bit says that we should clamp that size to mLimit. eClampToLimit = 0x2, // This bit says that the corresponding item has `auto` margin(s). eItemHasAutoMargin = 0x4, // clang-format on }; StateBits Initialize(nscoord aPercentageBasis, const StyleTrackSize&); bool IsFrozen() const { return mState & eFrozen; } #ifdef DEBUG static void DumpStateBits(StateBits aState); void Dump() const; #endif 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). PerBaseline mBaselineSubtreeSize; StateBits mState; }; MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(TrackSize::StateBits) namespace mozilla { template <> struct IsPod : std::true_type {}; } // namespace mozilla TrackSize::StateBits nsGridContainerFrame::TrackSize::Initialize( nscoord aPercentageBasis, const StyleTrackSize& aSize) { using Tag = StyleTrackBreadth::Tag; MOZ_ASSERT(mBase == 0 && mLimit == 0 && mState == 0, "track size data is expected to be initialized to zero"); mBaselineSubtreeSize[BaselineSharingGroup::First] = nscoord(0); mBaselineSubtreeSize[BaselineSharingGroup::Last] = nscoord(0); auto& min = aSize.GetMin(); auto& max = aSize.GetMax(); Tag minSizeTag = min.tag; Tag maxSizeTag = max.tag; if (aSize.IsFitContent()) { // In layout, fit-content(size) behaves as minmax(auto, max-content), with // 'size' as an additional upper-bound. mState = eFitContent; minSizeTag = Tag::Auto; maxSizeTag = Tag::MaxContent; } if (::IsPercentOfIndefiniteSize(min, 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'." minSizeTag = Tag::Auto; } if (::IsPercentOfIndefiniteSize(max, aPercentageBasis)) { maxSizeTag = Tag::Auto; } // http://dev.w3.org/csswg/css-grid/#algo-init switch (minSizeTag) { case Tag::Auto: mState |= eAutoMinSizing; break; case Tag::MinContent: mState |= eMinContentMinSizing; break; case Tag::MaxContent: mState |= eMaxContentMinSizing; break; default: MOZ_ASSERT(!min.IsFr(), " min-sizing is invalid as a track size"); mBase = ::ResolveToDefiniteSize(min, aPercentageBasis); } switch (maxSizeTag) { case Tag::Auto: mState |= eAutoMaxSizing; mLimit = NS_UNCONSTRAINEDSIZE; break; case Tag::MinContent: case Tag::MaxContent: mState |= maxSizeTag == Tag::MinContent ? eMinContentMaxSizing : eMaxContentMaxSizing; mLimit = NS_UNCONSTRAINEDSIZE; break; case Tag::Fr: mState |= eFlexMaxSizing; mLimit = mBase; break; default: mLimit = ::ResolveToDefiniteSize(max, aPercentageBasis); if (mLimit < mBase) { mLimit = mBase; } } return mState; } /** * 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 >= kMinLine && aEnd <= kMaxLine, "invalid span"); } else { MOZ_ASSERT(aStart >= kMinLine && aStart <= kMaxLine, "invalid start line"); MOZ_ASSERT(aEnd == int32_t(kAutoLine) || (aEnd >= kMinLine && aEnd <= 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(kMaxLine), "invalid span"); return mEnd; } return mEnd - mStart; } /** * Return an object suitable for iterating this range. */ auto Range() const { return IntegerRange(mStart, mEnd); } /** * Resolve this auto range to start at aStart, making it definite. * @param aClampMaxLine the maximum allowed line number (zero-based) * Precondition: this range IsAuto() */ void ResolveAutoPosition(uint32_t aStart, uint32_t aClampMaxLine) { MOZ_ASSERT(IsAuto(), "Why call me?"); mStart = aStart; mEnd += aStart; // Clamp to aClampMaxLine, which is where kMaxLine is in the explicit // grid in a non-subgrid axis; this implements clamping per // http://dev.w3.org/csswg/css-grid/#overlarge-grids // In a subgrid axis it's the end of the grid in that axis. if (MOZ_UNLIKELY(mStart >= aClampMaxLine)) { mEnd = aClampMaxLine; mStart = mEnd - 1; } else if (MOZ_UNLIKELY(mEnd > aClampMaxLine)) { mEnd = aClampMaxLine; } } /** * 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 != kAutoLine) { mStart -= aNumRemovedTracks[mStart]; } if (mEnd != kAutoLine) { MOZ_ASSERT(mStart == kAutoLine || mEnd > mStart, "invalid line range"); mEnd -= aNumRemovedTracks[mEnd]; } } /** * 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; void Translate(int32_t aOffset) { MOZ_ASSERT(IsDefinite()); mStart += aOffset; mEnd += aOffset; } /** Swap the start/end sides of this range. */ void ReverseDirection(uint32_t aGridEnd) { MOZ_ASSERT(IsDefinite()); MOZ_ASSERT(aGridEnd >= mEnd); uint32_t newStart = aGridEnd - mEnd; mEnd = aGridEnd - mStart; mStart = newStart; } /** * @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() : mStart(0), mEnd(0) {} }; /** * 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& LineRangeForAxis(LogicalAxis aAxis) { return aAxis == eLogicalAxisInline ? mCols : mRows; } const LineRange& LineRangeForAxis(LogicalAxis aAxis) const { return aAxis == eLogicalAxisInline ? mCols : mRows; } LineRange mCols; LineRange mRows; }; struct nsGridContainerFrame::GridItemInfo { /** * Item state per axis. */ enum StateBits : uint16_t { // clang-format off 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, // The baseline affects the margin or padding on the item's end side when // this bit is set. In a grid-axis it's always set for eLastBaseline and // always unset for eFirstBaseline. In a masonry-axis, it's set for // baseline groups in the EndStretch set and unset for the StartStretch set. eEndSideBaseline = 0x20, eAllBaselineBits = eIsBaselineAligned | eSelfBaseline | eContentBaseline | eEndSideBaseline, // Should apply Automatic Minimum Size per: // https://drafts.csswg.org/css-grid/#min-size-auto eApplyAutoMinSize = 0x40, // Clamp per https://drafts.csswg.org/css-grid/#min-size-auto eClampMarginBoxMinSize = 0x80, eIsSubgrid = 0x100, // set on subgrids and items in subgrids if they are adjacent to the grid // start/end edge (excluding grid-aligned abs.pos. frames) eStartEdge = 0x200, eEndEdge = 0x400, eEdgeBits = eStartEdge | eEndEdge, // Set if this item was auto-placed in this axis. eAutoPlacement = 0x800, // Set if this item is the last item in its track (masonry layout only) eIsLastItemInMasonryTrack = 0x1000, // clang-format on }; GridItemInfo(nsIFrame* aFrame, const GridArea& aArea); static bool BaselineAlignmentAffectsEndSide(StateBits state) { return state & StateBits::eEndSideBaseline; } /** * Inhibit subgrid layout unless the item is placed in the first "track" in * a parent masonry-axis, or has definite placement or spans all tracks in * the parent grid-axis. * TODO: this is stricter than what the Masonry proposal currently states * (bug 1627581) */ void MaybeInhibitSubgridInMasonry(nsGridContainerFrame* aParent, uint32_t aGridAxisTrackCount); /** * Inhibit subgridding in aAxis for this item. */ void InhibitSubgrid(nsGridContainerFrame* aParent, LogicalAxis aAxis); /** * Return a copy of this item with its row/column data swapped. */ GridItemInfo Transpose() const { GridItemInfo info(mFrame, GridArea(mArea.mRows, mArea.mCols)); info.mState[0] = mState[1]; info.mState[1] = mState[0]; info.mBaselineOffset[0] = mBaselineOffset[1]; info.mBaselineOffset[1] = mBaselineOffset[0]; return info; } /** Swap the start/end sides in aAxis. */ inline void ReverseDirection(LogicalAxis aAxis, uint32_t aGridEnd); // Is this item a subgrid in the given container axis? bool IsSubgrid(LogicalAxis aAxis) const { return mState[aAxis] & StateBits::eIsSubgrid; } // Is this item a subgrid in either axis? bool IsSubgrid() const { return IsSubgrid(eLogicalAxisInline) || IsSubgrid(eLogicalAxisBlock); } // Return the (inner) grid container frame associated with this subgrid item. nsGridContainerFrame* SubgridFrame() const { MOZ_ASSERT(IsSubgrid()); nsGridContainerFrame* gridFrame = GetGridContainerFrame(mFrame); MOZ_ASSERT(gridFrame && gridFrame->IsSubgrid()); return gridFrame; } /** * Adjust our grid areas to account for removed auto-fit tracks in aAxis. */ void AdjustForRemovedTracks(LogicalAxis aAxis, const nsTArray& aNumRemovedTracks); /** * 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. */ StyleAlignFlags GetSelfBaseline(StyleAlignFlags aAlign, LogicalAxis aAxis, nscoord* aBaselineOffset) const { MOZ_ASSERT(aAlign == StyleAlignFlags::BASELINE || aAlign == StyleAlignFlags::LAST_BASELINE); if (!(mState[aAxis] & eSelfBaseline)) { return aAlign == StyleAlignFlags::BASELINE ? StyleAlignFlags::SELF_START : StyleAlignFlags::SELF_END; } *aBaselineOffset = mBaselineOffset[aAxis]; return aAlign; } // Return true if we should apply Automatic Minimum Size to this item. // https://drafts.csswg.org/css-grid/#min-size-auto // @note the caller should also check that the item spans at least one track // that has a min track sizing function that is 'auto' before applying it. bool ShouldApplyAutoMinSize(WritingMode aContainerWM, LogicalAxis aContainerAxis, nscoord aPercentageBasis) const { const bool isInlineAxis = aContainerAxis == eLogicalAxisInline; const auto* pos = mFrame->IsTableWrapperFrame() ? mFrame->PrincipalChildList().FirstChild()->StylePosition() : mFrame->StylePosition(); const auto& size = isInlineAxis ? pos->ISize(aContainerWM) : pos->BSize(aContainerWM); // max-content and min-content should behave as initial value in block axis. // FIXME: Bug 567039: moz-fit-content and -moz-available are not supported // for block size dimension on sizing properties (e.g. height), so we // treat it as `auto`. bool isAuto = size.IsAuto() || (isInlineAxis == aContainerWM.IsOrthogonalTo(mFrame->GetWritingMode()) && size.BehavesLikeInitialValueOnBlockAxis()); // NOTE: if we have a definite size then our automatic minimum size // can't affect our size. Excluding these simplifies applying // the clamping in the right cases later. if (!isAuto && !::IsPercentOfIndefiniteSize(size, aPercentageBasis)) { return false; } const auto& minSize = isInlineAxis ? pos->MinISize(aContainerWM) : pos->MinBSize(aContainerWM); // max-content and min-content should behave as initial value in block axis. // FIXME: Bug 567039: moz-fit-content and -moz-available are not supported // for block size dimension on sizing properties (e.g. height), so we // treat it as `auto`. isAuto = minSize.IsAuto() || (isInlineAxis == aContainerWM.IsOrthogonalTo(mFrame->GetWritingMode()) && minSize.BehavesLikeInitialValueOnBlockAxis()); return isAuto && mFrame->StyleDisplay()->mOverflowX == StyleOverflow::Visible; } #ifdef DEBUG void Dump() const; #endif static bool IsStartRowLessThan(const GridItemInfo* a, const GridItemInfo* b) { return a->mArea.mRows.mStart < b->mArea.mRows.mStart; } // Sorting functions for 'masonry-auto-flow:next'. We sort the items that // were placed into the first track by the Grid placement algorithm first // (to honor that placement). All other items will be placed by the Masonry // layout algorithm (their Grid placement in the masonry axis is irrelevant). static bool RowMasonryOrdered(const GridItemInfo* a, const GridItemInfo* b) { return a->mArea.mRows.mStart == 0 && b->mArea.mRows.mStart != 0 && !a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW); } static bool ColMasonryOrdered(const GridItemInfo* a, const GridItemInfo* b) { return a->mArea.mCols.mStart == 0 && b->mArea.mCols.mStart != 0 && !a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW); } // Sorting functions for 'masonry-auto-flow:definite-first'. Similar to // the above, but here we also sort items with a definite item placement in // the grid axis in track order before 'auto'-placed items. We also sort all // continuations first since they use the same placement as their // first-in-flow (we treat them as "definite" regardless of eAutoPlacement). static bool RowMasonryDefiniteFirst(const GridItemInfo* a, const GridItemInfo* b) { bool isContinuationA = a->mFrame->GetPrevInFlow(); bool isContinuationB = b->mFrame->GetPrevInFlow(); if (isContinuationA != isContinuationB) { return isContinuationA; } auto masonryA = a->mArea.mRows.mStart; auto gridA = a->mState[eLogicalAxisInline] & StateBits::eAutoPlacement; auto masonryB = b->mArea.mRows.mStart; auto gridB = b->mState[eLogicalAxisInline] & StateBits::eAutoPlacement; return (masonryA == 0 ? masonryB != 0 : (masonryB != 0 && gridA < gridB)) && !a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW); } static bool ColMasonryDefiniteFirst(const GridItemInfo* a, const GridItemInfo* b) { MOZ_ASSERT(!a->mFrame->GetPrevInFlow() && !b->mFrame->GetPrevInFlow(), "fragmentation not supported in inline axis"); auto masonryA = a->mArea.mCols.mStart; auto gridA = a->mState[eLogicalAxisBlock] & StateBits::eAutoPlacement; auto masonryB = b->mArea.mCols.mStart; auto gridB = b->mState[eLogicalAxisBlock] & StateBits::eAutoPlacement; return (masonryA == 0 ? masonryB != 0 : (masonryB != 0 && gridA < gridB)) && !a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW); } 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) GridItemInfo::GridItemInfo(nsIFrame* aFrame, const GridArea& aArea) : mFrame(aFrame), mArea(aArea) { mState[eLogicalAxisBlock] = StateBits(mArea.mRows.mStart == kAutoLine ? eAutoPlacement : 0); mState[eLogicalAxisInline] = StateBits(mArea.mCols.mStart == kAutoLine ? eAutoPlacement : 0); if (auto* gridFrame = GetGridContainerFrame(mFrame)) { auto parentWM = aFrame->GetParent()->GetWritingMode(); bool isOrthogonal = parentWM.IsOrthogonalTo(gridFrame->GetWritingMode()); if (gridFrame->IsColSubgrid()) { mState[isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline] |= StateBits::eIsSubgrid; } if (gridFrame->IsRowSubgrid()) { mState[isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock] |= StateBits::eIsSubgrid; } } mBaselineOffset[eLogicalAxisBlock] = nscoord(0); mBaselineOffset[eLogicalAxisInline] = nscoord(0); } void GridItemInfo::ReverseDirection(LogicalAxis aAxis, uint32_t aGridEnd) { mArea.LineRangeForAxis(aAxis).ReverseDirection(aGridEnd); ItemState& state = mState[aAxis]; ItemState newState = state & ~ItemState::eEdgeBits; if (state & ItemState::eStartEdge) { newState |= ItemState::eEndEdge; } if (state & ItemState::eEndEdge) { newState |= ItemState::eStartEdge; } state = newState; } void GridItemInfo::InhibitSubgrid(nsGridContainerFrame* aParent, LogicalAxis aAxis) { MOZ_ASSERT(IsSubgrid(aAxis)); auto bit = NS_STATE_GRID_IS_COL_SUBGRID; if (aParent->GetWritingMode().IsOrthogonalTo(mFrame->GetWritingMode()) != (aAxis == eLogicalAxisBlock)) { bit = NS_STATE_GRID_IS_ROW_SUBGRID; } MOZ_ASSERT(SubgridFrame()->HasAnyStateBits(bit)); SubgridFrame()->RemoveStateBits(bit); mState[aAxis] &= StateBits(~StateBits::eIsSubgrid); } void GridItemInfo::MaybeInhibitSubgridInMasonry(nsGridContainerFrame* aParent, uint32_t aGridAxisTrackCount) { if (IsSubgrid(eLogicalAxisInline) && aParent->IsMasonry(eLogicalAxisBlock) && mArea.mRows.mStart != 0 && mArea.mCols.Extent() != aGridAxisTrackCount && (mState[eLogicalAxisInline] & eAutoPlacement)) { InhibitSubgrid(aParent, eLogicalAxisInline); return; } if (IsSubgrid(eLogicalAxisBlock) && aParent->IsMasonry(eLogicalAxisInline) && mArea.mCols.mStart != 0 && mArea.mRows.Extent() != aGridAxisTrackCount && (mState[eLogicalAxisBlock] & eAutoPlacement)) { InhibitSubgrid(aParent, eLogicalAxisBlock); } } // Each subgrid stores this data about its items etc on a frame property. struct nsGridContainerFrame::Subgrid { Subgrid(const GridArea& aArea, bool aIsOrthogonal, WritingMode aCBWM) : mArea(aArea), mGridColEnd(0), mGridRowEnd(0), mMarginBorderPadding(aCBWM), mIsOrthogonal(aIsOrthogonal) {} // Return the relevant line range for the subgrid column axis. const LineRange& SubgridCols() const { return mIsOrthogonal ? mArea.mRows : mArea.mCols; } // Return the relevant line range for the subgrid row axis. const LineRange& SubgridRows() const { return mIsOrthogonal ? mArea.mCols : mArea.mRows; } // The subgrid's items. nsTArray mGridItems; // The subgrid's abs.pos. items. nsTArray mAbsPosItems; // The subgrid's area as a grid item, i.e. in its parent's grid space. GridArea mArea; // The (inner) grid size for the subgrid, zero-based. uint32_t mGridColEnd; uint32_t mGridRowEnd; // The margin+border+padding for the subgrid box in its parent grid's WM. // (This also includes the size of any scrollbars.) LogicalMargin mMarginBorderPadding; // Does the subgrid frame have orthogonal writing-mode to its parent grid // container? bool mIsOrthogonal; NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, Subgrid) }; using Subgrid = nsGridContainerFrame::Subgrid; void GridItemInfo::AdjustForRemovedTracks( LogicalAxis aAxis, const nsTArray& aNumRemovedTracks) { const bool abspos = mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW); auto& lines = mArea.LineRangeForAxis(aAxis); if (abspos) { lines.AdjustAbsPosForRemovedTracks(aNumRemovedTracks); } else { lines.AdjustForRemovedTracks(aNumRemovedTracks); } if (IsSubgrid()) { auto* subgrid = SubgridFrame()->GetProperty(Subgrid::Prop()); if (subgrid) { auto& lines = subgrid->mArea.LineRangeForAxis(aAxis); if (abspos) { lines.AdjustAbsPosForRemovedTracks(aNumRemovedTracks); } else { lines.AdjustForRemovedTracks(aNumRemovedTracks); } } } } /** * Track size data for use by subgrids (which don't do sizing of their own * in a subgridded axis). A non-subgrid container stores its resolved sizes, * but only if it has any subgrid children. A subgrid always stores one. * In a subgridded axis, we copy the parent's sizes (see CopyUsedTrackSizes). * * This struct us stored on a frame property, which may be null before the track * sizing step for the given container. A null property is semantically * equivalent to mCanResolveLineRangeSize being false in both axes. * @note the axis used to access this data is in the grid container's own * writing-mode, same as in other track-sizing functions. */ struct nsGridContainerFrame::UsedTrackSizes { UsedTrackSizes() : mCanResolveLineRangeSize{false, false} {} /** * Setup mSizes by copying track sizes from aFrame's grid container * parent when aAxis is subgridded (and recurse if the parent is a subgrid * that doesn't have sizes yet), or by running the Track Sizing Algo when * the axis is not subgridded (for a subgrid). * Set mCanResolveLineRangeSize[aAxis] to true once we have obtained * sizes for an axis (if it's already true then this method is a NOP). */ void ResolveTrackSizesForAxis(nsGridContainerFrame* aFrame, LogicalAxis aAxis, gfxContext& aRC); /** Helper function for the above method */ void ResolveSubgridTrackSizesForAxis(nsGridContainerFrame* aFrame, LogicalAxis aAxis, Subgrid* aSubgrid, gfxContext& aRC, nscoord aContentBoxSize); // This only has valid sizes when mCanResolveLineRangeSize is true in // the same axis. It may have zero tracks (a grid with only abs.pos. // subgrids/items may have zero tracks). PerLogicalAxis> mSizes; // True if mSizes can be used to resolve line range sizes in an axis. PerLogicalAxis mCanResolveLineRangeSize; NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, UsedTrackSizes) }; using UsedTrackSizes = nsGridContainerFrame::UsedTrackSizes; #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::eEdgeBits) { printf("subgrid-adjacent-edges("); if (state & ItemState::eStartEdge) { printf("start "); } if (state & ItemState::eEndEdge) { printf("end"); } printf(") "); } if (state & ItemState::eAutoPlacement) { printf("masonry-auto "); } if (state & ItemState::eIsSubgrid) { printf("subgrid "); } if (state & ItemState::eIsFlexing) { printf("flexing "); } if (state & ItemState::eApplyAutoMinSize) { printf("auto-min-size "); } if (state & ItemState::eClampMarginBoxMinSize) { printf("clamp "); } if (state & ItemState::eIsLastItemInMasonryTrack) { printf("last-in-track "); } 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 /** * Encapsulates CSS track-sizing functions. */ struct nsGridContainerFrame::TrackSizingFunctions { private: TrackSizingFunctions(const GridTemplate& aTemplate, const StyleImplicitGridTracks& aAutoSizing, const Maybe& aRepeatAutoIndex, bool aIsSubgrid) : mTemplate(aTemplate), mTrackListValues(aTemplate.TrackListValues()), mAutoSizing(aAutoSizing), mExplicitGridOffset(0), mRepeatAutoStart(aRepeatAutoIndex.valueOr(0)), mRepeatAutoEnd(mRepeatAutoStart), mHasRepeatAuto(aRepeatAutoIndex.isSome()) { MOZ_ASSERT(!mHasRepeatAuto || !aIsSubgrid, "a track-list for a subgrid can't have an track"); if (!aIsSubgrid) { ExpandNonRepeatAutoTracks(); } #ifdef DEBUG if (mHasRepeatAuto) { MOZ_ASSERT(mExpandedTracks.Length() >= 1); const unsigned maxTrack = kMaxLine - 1; // If the exanded tracks are out of range of the maximum track, we // can't compare the repeat-auto start. It will be removed later during // grid item placement in that situation. if (mExpandedTracks.Length() < maxTrack) { MOZ_ASSERT(mRepeatAutoStart < mExpandedTracks.Length()); } } #endif } public: TrackSizingFunctions(const GridTemplate& aGridTemplate, const StyleImplicitGridTracks& aAutoSizing, bool aIsSubgrid) : TrackSizingFunctions(aGridTemplate, aAutoSizing, aGridTemplate.RepeatAutoIndex(), aIsSubgrid) {} private: enum { ForSubgridFallbackTag }; TrackSizingFunctions(const GridTemplate& aGridTemplate, const StyleImplicitGridTracks& aAutoSizing, decltype(ForSubgridFallbackTag)) : TrackSizingFunctions(aGridTemplate, aAutoSizing, Nothing(), /* aIsSubgrid */ true) {} public: /** * This is used in a subgridded axis to resolve sizes before its parent's * sizes are known for intrinsic sizing purposes. It copies the slice of * the nearest non-subgridded axis' track sizing functions spanned by * the subgrid. * * FIXME: this was written before there was a spec... the spec now says: * "If calculating the layout of a grid item in this step depends on * the available space in the block axis, assume the available space * that it would have if any row with a definite max track sizing * function had that size and all other rows were infinite." * https://drafts.csswg.org/css-grid-2/#subgrid-sizing */ static TrackSizingFunctions ForSubgridFallback( nsGridContainerFrame* aSubgridFrame, const Subgrid* aSubgrid, nsGridContainerFrame* aParentGridContainer, LogicalAxis aParentAxis) { MOZ_ASSERT(aSubgrid); MOZ_ASSERT(aSubgridFrame->IsSubgrid(aSubgrid->mIsOrthogonal ? GetOrthogonalAxis(aParentAxis) : aParentAxis)); nsGridContainerFrame* parent = aParentGridContainer; auto parentAxis = aParentAxis; LineRange range = aSubgrid->mArea.LineRangeForAxis(parentAxis); // Find our nearest non-subgridded axis and use its track sizing functions. while (parent->IsSubgrid(parentAxis)) { const auto* parentSubgrid = parent->GetProperty(Subgrid::Prop()); auto* grandParent = parent->ParentGridContainerForSubgrid(); auto grandParentWM = grandParent->GetWritingMode(); bool isSameDirInAxis = parent->GetWritingMode().ParallelAxisStartsOnSameSide(parentAxis, grandParentWM); if (MOZ_UNLIKELY(!isSameDirInAxis)) { auto end = parentAxis == eLogicalAxisBlock ? parentSubgrid->mGridRowEnd : parentSubgrid->mGridColEnd; range.ReverseDirection(end); // range is now in the same direction as the grand-parent's axis } auto grandParentAxis = parentSubgrid->mIsOrthogonal ? GetOrthogonalAxis(parentAxis) : parentAxis; const auto& parentRange = parentSubgrid->mArea.LineRangeForAxis(grandParentAxis); range.Translate(parentRange.mStart); // range is now in the grand-parent's coordinates parentAxis = grandParentAxis; parent = grandParent; } const auto* pos = parent->StylePosition(); const auto isInlineAxis = parentAxis == eLogicalAxisInline; const auto& szf = isInlineAxis ? pos->mGridTemplateRows : pos->mGridTemplateColumns; const auto& autoSizing = isInlineAxis ? pos->mGridAutoColumns : pos->mGridAutoRows; return TrackSizingFunctions(szf, autoSizing, ForSubgridFallbackTag); } /** * Initialize the number of auto-fill/fit tracks to use. * This can be zero if no auto-fill/fit track was specified, or if the repeat * begins after the maximum allowed track. */ void InitRepeatTracks(const NonNegativeLengthPercentageOrNormal& aGridGap, nscoord aMinSize, nscoord aSize, nscoord aMaxSize) { const uint32_t maxTrack = kMaxLine - 1; // Check for a repeat after the maximum allowed track. if (MOZ_UNLIKELY(mRepeatAutoStart >= maxTrack)) { mHasRepeatAuto = false; mRepeatAutoStart = 0; mRepeatAutoEnd = 0; return; } uint32_t repeatTracks = CalculateRepeatFillCount(aGridGap, aMinSize, aSize, aMaxSize) * NumRepeatTracks(); // Clamp the number of repeat tracks to the maximum possible track. repeatTracks = std::min(repeatTracks, maxTrack - mRepeatAutoStart); SetNumRepeatTracks(repeatTracks); // Blank out the removed flags for each of these tracks. mRemovedRepeatTracks.SetLength(repeatTracks); for (auto& track : mRemovedRepeatTracks) { track = false; } } uint32_t CalculateRepeatFillCount( const NonNegativeLengthPercentageOrNormal& aGridGap, nscoord aMinSize, nscoord aSize, nscoord aMaxSize) const { if (!mHasRepeatAuto) { return 0; } // At this point no tracks will have been collapsed, so the RepeatEndDelta // should not be negative. MOZ_ASSERT(RepeatEndDelta() >= 0); // Note that this uses NumRepeatTracks and mRepeatAutoStart/End, although // the result of this method is used to change those values to a fully // expanded value. Spec quotes are from // https://drafts.csswg.org/css-grid/#repeat-notation const uint32_t numTracks = mExpandedTracks.Length() + RepeatEndDelta(); MOZ_ASSERT(numTracks >= 1, "expected at least the repeat() track"); if (MOZ_UNLIKELY(numTracks >= kMaxLine)) { // The fixed tracks plus an entire repetition is either larger or as // large as the maximum track, so we do not need to measure how many // repetitions will fit. This also avoids needing to check for if // kMaxLine - numTracks would underflow at the end where we clamp the // result. return 1; } nscoord maxFill = aSize != NS_UNCONSTRAINEDSIZE ? aSize : aMaxSize; if (maxFill == NS_UNCONSTRAINEDSIZE && aMinSize == 0) { // "Otherwise, the specified track list repeats only once." return 1; } nscoord repeatTrackSum = 0; // Note that one 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& sizingFunction = SizingFor(i); const auto& maxCoord = sizingFunction.GetMax(); const auto* coord = &maxCoord; if (!coord->IsBreadth()) { coord = &sizingFunction.GetMin(); if (!coord->IsBreadth()) { return 1; } } nscoord trackSize = ::ResolveToDefiniteSize(*coord, percentBasis); if (i >= mRepeatAutoStart && i < mRepeatAutoEnd) { // Use a minimum 1px for the repeat() track-size. if (trackSize < AppUnitsPerCSSPixel()) { trackSize = AppUnitsPerCSSPixel(); } repeatTrackSum += trackSize; } sum += trackSize; } nscoord gridGap = nsLayoutUtils::ResolveGapToLength(aGridGap, aSize); if (numTracks > 1) { // Add grid-gaps for all the tracks including the repeat() track. sum += gridGap * (numTracks - 1); } // Calculate the max number of tracks that fits without overflow. nscoord available = maxFill != NS_UNCONSTRAINEDSIZE ? maxFill : aMinSize; nscoord spaceToFill = available - sum; if (spaceToFill <= 0) { // "if any number of repetitions would overflow, then 1 repetition" return 1; } // Calculate the max number of tracks that fits without overflow. // Since we already have one repetition in sum, we can simply add one grid // gap for each element in the repeat. div_t q = div(spaceToFill, repeatTrackSum + gridGap * NumRepeatTracks()); // The +1 here is for the one repeat track we already accounted for above. uint32_t numRepeatTracks = q.quot + 1; if (q.rem != 0 && 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) MOZ_ASSERT(numTracks >= NumRepeatTracks()); const uint32_t maxRepeatTrackCount = kMaxLine - numTracks; const uint32_t maxRepetitions = maxRepeatTrackCount / NumRepeatTracks(); return std::min(numRepeatTracks, maxRepetitions); } /** * 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(kMaxLine)); return end; } const StyleTrackSize& SizingFor(uint32_t aTrackIndex) const { static const StyleTrackSize kAutoTrackSize = StyleTrackSize::Breadth(StyleTrackBreadth::Auto()); // |aIndex| is the relative index to mAutoSizing. A negative value means it // is the last Nth element. auto getImplicitSize = [this](int32_t aIndex) -> const StyleTrackSize& { MOZ_ASSERT(!(mAutoSizing.Length() == 1 && mAutoSizing.AsSpan()[0] == kAutoTrackSize), "It's impossible to have one track with auto value because we " "filter out this case during parsing"); if (mAutoSizing.IsEmpty()) { return kAutoTrackSize; } // If multiple track sizes are given, the pattern is repeated as necessary // to find the size of the implicit tracks. int32_t i = aIndex % int32_t(mAutoSizing.Length()); if (i < 0) { i += mAutoSizing.Length(); } return mAutoSizing.AsSpan()[i]; }; if (MOZ_UNLIKELY(aTrackIndex < mExplicitGridOffset)) { // The last implicit grid track before the explicit grid receives the // last specified size, and so on backwards. Therefore we pass the // negative relative index to imply that we should get the implicit size // from the last Nth specified grid auto size. return getImplicitSize(int32_t(aTrackIndex) - int32_t(mExplicitGridOffset)); } uint32_t index = aTrackIndex - mExplicitGridOffset; MOZ_ASSERT(mRepeatAutoStart <= mRepeatAutoEnd); if (index >= mRepeatAutoStart) { if (index < mRepeatAutoEnd) { // Expand the repeat tracks. const auto& indices = mExpandedTracks[mRepeatAutoStart]; const TrackListValue& value = mTrackListValues[indices.first]; // We expect the default to be used for all track repeats. MOZ_ASSERT(indices.second == 0); const auto& repeatTracks = value.AsTrackRepeat().track_sizes.AsSpan(); // Find the repeat track to use, skipping over any collapsed tracks. const uint32_t finalRepeatIndex = (index - mRepeatAutoStart); uint32_t repeatWithCollapsed = 0; // NOTE: We need SizingFor before the final collapsed tracks are known. // We know that it's invalid to have empty mRemovedRepeatTracks when // there are any repeat tracks, so we can detect that situation here. if (mRemovedRepeatTracks.IsEmpty()) { repeatWithCollapsed = finalRepeatIndex; } else { // Count up through the repeat tracks, until we have seen // finalRepeatIndex number of non-collapsed tracks. for (uint32_t repeatNoCollapsed = 0; repeatNoCollapsed < finalRepeatIndex; repeatWithCollapsed++) { if (!mRemovedRepeatTracks[repeatWithCollapsed]) { repeatNoCollapsed++; } } // If we stopped iterating on a collapsed track, continue to the next // non-collapsed track. while (mRemovedRepeatTracks[repeatWithCollapsed]) { repeatWithCollapsed++; } } return repeatTracks[repeatWithCollapsed % repeatTracks.Length()]; } else { // The index is after the repeat auto range, adjust it to skip over the // repeat value. This will have no effect if there is no auto repeat, // since then RepeatEndDelta will return zero. index -= RepeatEndDelta(); } } if (index >= mExpandedTracks.Length()) { return getImplicitSize(index - mExpandedTracks.Length()); } auto& indices = mExpandedTracks[index]; const TrackListValue& value = mTrackListValues[indices.first]; if (value.IsTrackSize()) { MOZ_ASSERT(indices.second == 0); return value.AsTrackSize(); } return value.AsTrackRepeat().track_sizes.AsSpan()[indices.second]; } const StyleTrackBreadth& MaxSizingFor(uint32_t aTrackIndex) const { return SizingFor(aTrackIndex).GetMax(); } const StyleTrackBreadth& MinSizingFor(uint32_t aTrackIndex) const { return SizingFor(aTrackIndex).GetMin(); } uint32_t NumExplicitTracks() const { return mExpandedTracks.Length() + RepeatEndDelta(); } uint32_t NumRepeatTracks() const { return mRepeatAutoEnd - mRepeatAutoStart; } // The difference between mExplicitGridEnd and mSizingFunctions.Length(). int32_t RepeatEndDelta() const { return mHasRepeatAuto ? int32_t(NumRepeatTracks()) - 1 : 0; } void SetNumRepeatTracks(uint32_t aNumRepeatTracks) { MOZ_ASSERT(mHasRepeatAuto || aNumRepeatTracks == 0); mRepeatAutoEnd = mRepeatAutoStart + aNumRepeatTracks; } // Store mTrackListValues into mExpandedTracks with `repeat(INTEGER, ...)` // tracks expanded. void ExpandNonRepeatAutoTracks() { for (size_t i = 0; i < mTrackListValues.Length(); ++i) { auto& value = mTrackListValues[i]; if (value.IsTrackSize()) { mExpandedTracks.EmplaceBack(i, 0); continue; } auto& repeat = value.AsTrackRepeat(); if (!repeat.count.IsNumber()) { MOZ_ASSERT(i == mRepeatAutoStart); mRepeatAutoStart = mExpandedTracks.Length(); mRepeatAutoEnd = mRepeatAutoStart + repeat.track_sizes.Length(); mExpandedTracks.EmplaceBack(i, 0); continue; } for (auto j : IntegerRange(repeat.count.AsNumber())) { Unused << j; size_t trackSizesCount = repeat.track_sizes.Length(); for (auto k : IntegerRange(trackSizesCount)) { mExpandedTracks.EmplaceBack(i, k); } } } if (MOZ_UNLIKELY(mExpandedTracks.Length() > kMaxLine - 1)) { mExpandedTracks.TruncateLength(kMaxLine - 1); if (mHasRepeatAuto && mRepeatAutoStart > kMaxLine - 1) { // The `repeat(auto-fill/fit)` track is outside the clamped grid. mHasRepeatAuto = false; } } } // Some style data references, for easy access. const GridTemplate& mTemplate; const Span mTrackListValues; const StyleImplicitGridTracks& mAutoSizing; // An array from expanded track sizes (without expanding auto-repeat, which is // included just once at `mRepeatAutoStart`). // // Each entry contains two indices, the first into mTrackListValues, and a // second one inside mTrackListValues' repeat value, if any, or zero // otherwise. nsTArray> mExpandedTracks; // 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. // Relative to mExplicitGridOffset (repeat tracks are explicit by definition). uint32_t mRepeatAutoStart; // The (hypothetical) index of the last such repeat() track. uint32_t mRepeatAutoEnd; // True if there is a specified repeat(auto-fill/fit) track. bool mHasRepeatAuto; // True if this track (relative to mRepeatAutoStart) is a removed auto-fit. // Indexed relative to mExplicitGridOffset + mRepeatAutoStart. nsTArray mRemovedRepeatTracks; }; /** * 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 aStylePosition the style for the grid container * @param aImplicitNamedAreas the implicit areas for the grid container * @param aGridTemplate is the grid-template-rows/columns data for this axis * @param aParentLineNameMap the parent grid's map parallel to this map, or * null if this map isn't for a subgrid * @param aRange the subgrid's range in the parent grid, or null * @param aIsSameDirection true if our axis progresses in the same direction * in the subgrid and parent */ LineNameMap(const nsStylePosition* aStylePosition, const ImplicitNamedAreas* aImplicitNamedAreas, const TrackSizingFunctions& aTracks, const LineNameMap* aParentLineNameMap, const LineRange* aRange, bool aIsSameDirection) : mStylePosition(aStylePosition), mAreas(aImplicitNamedAreas), mRepeatAutoStart(aTracks.mRepeatAutoStart), mRepeatAutoEnd(aTracks.mRepeatAutoEnd), mRepeatEndDelta(aTracks.RepeatEndDelta()), mParentLineNameMap(aParentLineNameMap), mRange(aRange), mIsSameDirection(aIsSameDirection), mHasRepeatAuto(aTracks.mHasRepeatAuto) { if (MOZ_UNLIKELY(aRange)) { // subgrid case mClampMinLine = 1; mClampMaxLine = 1 + aRange->Extent(); mRepeatAutoEnd = mRepeatAutoStart; const auto& styleSubgrid = aTracks.mTemplate.AsSubgrid(); const auto fillLen = styleSubgrid->fill_len; mHasRepeatAuto = fillLen != 0; if (mHasRepeatAuto) { const auto& lineNameLists = styleSubgrid->names; const int32_t extraAutoFillLineCount = mClampMaxLine - lineNameLists.Length(); // Maximum possible number of repeat name lists. This must be reduced // to a whole number of repetitions of the fill length. const uint32_t possibleRepeatLength = std::max(0, extraAutoFillLineCount + fillLen); const uint32_t repeatRemainder = possibleRepeatLength % fillLen; mRepeatAutoStart = styleSubgrid->fill_start; mRepeatAutoEnd = mRepeatAutoStart + possibleRepeatLength - repeatRemainder; } } else { mClampMinLine = kMinLine; mClampMaxLine = kMaxLine; if (mHasRepeatAuto) { mTrackAutoRepeatLineNames = aTracks.mTemplate.GetRepeatAutoValue()->line_names.AsSpan(); } } ExpandRepeatLineNames(!!aRange, aTracks); if (mHasRepeatAuto) { // We need mTemplateLinesEnd to be after all line names. // mExpandedLineNames has one repetition of the repeat(auto-fit/fill) // track name lists already, so we must subtract the number of repeat // track name lists to get to the number of non-repeat tracks, minus 2 // because the first and last line name lists are shared with the // preceding and following non-repeat line name lists. We then add // mRepeatEndDelta to include the interior line name lists from repeat // tracks. mTemplateLinesEnd = mExpandedLineNames.Length() - (mTrackAutoRepeatLineNames.Length() - 2) + mRepeatEndDelta; } else { mTemplateLinesEnd = mExpandedLineNames.Length(); } MOZ_ASSERT(mHasRepeatAuto || mRepeatEndDelta <= 0); MOZ_ASSERT(!mHasRepeatAuto || aRange || (mExpandedLineNames.Length() >= 2 && mRepeatAutoStart <= mExpandedLineNames.Length())); } // Store line names into mExpandedLineNames with `repeat(INTEGER, ...)` // expanded (for non-subgrid), and all `repeat(...)` expanded (for subgrid). void ExpandRepeatLineNames(bool aIsSubgrid, const TrackSizingFunctions& aTracks) { auto lineNameLists = aTracks.mTemplate.LineNameLists(aIsSubgrid); const auto& trackListValues = aTracks.mTrackListValues; const NameList* nameListToMerge = nullptr; // NOTE(emilio): We rely on std::move clearing out the array. SmallPointerArray names; // This adjusts for outputting the repeat auto names in subgrid. In that // case, all of the repeat values are handled in a single iteration. const uint32_t subgridRepeatDelta = (aIsSubgrid && mHasRepeatAuto) ? (aTracks.mTemplate.AsSubgrid()->fill_len - 1) : 0; const uint32_t end = std::min( lineNameLists.Length() - subgridRepeatDelta, mClampMaxLine + 1); for (uint32_t i = 0; i < end; ++i) { if (aIsSubgrid) { if (MOZ_UNLIKELY(mHasRepeatAuto && i == mRepeatAutoStart)) { // XXX expand 'auto-fill' names for subgrid for now since HasNameAt() // only deals with auto-repeat **tracks** currently. const auto& styleSubgrid = aTracks.mTemplate.AsSubgrid(); MOZ_ASSERT(styleSubgrid->fill_len > 0); for (auto j = i; j < mRepeatAutoEnd; ++j) { const auto repeatIndex = (j - i) % styleSubgrid->fill_len; names.AppendElement( &lineNameLists[styleSubgrid->fill_start + repeatIndex]); mExpandedLineNames.AppendElement(std::move(names)); } } else if (mHasRepeatAuto && i > mRepeatAutoStart) { const auto& styleSubgrid = aTracks.mTemplate.AsSubgrid(); names.AppendElement(&lineNameLists[i + styleSubgrid->fill_len - 1]); mExpandedLineNames.AppendElement(std::move(names)); } else { names.AppendElement(&lineNameLists[i]); mExpandedLineNames.AppendElement(std::move(names)); } // XXX expand repeat(, ...) line names here (bug 1583429) continue; } if (nameListToMerge) { names.AppendElement(nameListToMerge); nameListToMerge = nullptr; } names.AppendElement(&lineNameLists[i]); if (i >= trackListValues.Length()) { mExpandedLineNames.AppendElement(std::move(names)); continue; } const auto& value = trackListValues[i]; if (value.IsTrackSize()) { mExpandedLineNames.AppendElement(std::move(names)); continue; } const auto& repeat = value.AsTrackRepeat(); if (!repeat.count.IsNumber()) { const auto repeatNames = repeat.line_names.AsSpan(); // If the repeat was truncated due to more than kMaxLine tracks, then // the repeat will no longer be set on mRepeatAutoStart). MOZ_ASSERT(!mHasRepeatAuto || mRepeatAutoStart == mExpandedLineNames.Length()); MOZ_ASSERT(repeatNames.Length() >= 2); for (const auto j : IntegerRange(repeatNames.Length() - 1)) { names.AppendElement(&repeatNames[j]); mExpandedLineNames.AppendElement(std::move(names)); } nameListToMerge = &repeatNames[repeatNames.Length() - 1]; continue; } for (auto j : IntegerRange(repeat.count.AsNumber())) { Unused << j; if (nameListToMerge) { names.AppendElement(nameListToMerge); nameListToMerge = nullptr; } size_t trackSizesCount = repeat.track_sizes.Length(); auto repeatLineNames = repeat.line_names.AsSpan(); MOZ_ASSERT(repeatLineNames.Length() == trackSizesCount || repeatLineNames.Length() == trackSizesCount + 1); for (auto k : IntegerRange(trackSizesCount)) { names.AppendElement(&repeatLineNames[k]); mExpandedLineNames.AppendElement(std::move(names)); } if (repeatLineNames.Length() == trackSizesCount + 1) { nameListToMerge = &repeatLineNames[trackSizesCount]; } } } if (MOZ_UNLIKELY(mExpandedLineNames.Length() > uint32_t(mClampMaxLine))) { mExpandedLineNames.TruncateLength(mClampMaxLine); } if (MOZ_UNLIKELY(mHasRepeatAuto && aIsSubgrid)) { mHasRepeatAuto = false; // we've expanded all subgrid auto-fill lines } } /** * 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 the lines in * aImplicitLines. * 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(nsAtom* aName, int32_t* aNth, uint32_t aFromIndex, const nsTArray& aImplicitLines) const { MOZ_ASSERT(aName); MOZ_ASSERT(!aName->IsEmpty()); MOZ_ASSERT(aNth && *aNth != 0); if (*aNth > 0) { return FindLine(aName, aNth, aFromIndex, aImplicitLines); } int32_t nth = -*aNth; int32_t line = RFindLine(aName, &nth, aFromIndex, aImplicitLines); *aNth = -nth; return line; } /** * Return a set of lines in aImplicitLines which matches the area name aName * on aSide. For example, for aName "a" and aSide being an end side, it * returns the line numbers which would match "a-end" in the relevant axis. * For subgrids it includes searching the relevant axis in all ancestor * grids too (within this subgrid's spanned area). If an ancestor has * opposite direction, we switch aSide to the opposite logical side so we * match on the same physical side as the original subgrid we're resolving * the name for. */ void FindNamedAreas(nsAtom* aName, LogicalSide aSide, nsTArray& aImplicitLines) const { // True if we're currently in a map that has the same direction as 'this'. bool sameDirectionAsThis = true; uint32_t min = !mParentLineNameMap ? 1 : mClampMinLine; uint32_t max = mClampMaxLine; for (auto* map = this; true;) { uint32_t line = map->FindNamedArea(aName, aSide, min, max); if (line > 0) { if (MOZ_LIKELY(sameDirectionAsThis)) { line -= min - 1; } else { line = max - line + 1; } aImplicitLines.AppendElement(line); } auto* parent = map->mParentLineNameMap; if (!parent) { if (MOZ_UNLIKELY(aImplicitLines.Length() > 1)) { // Remove duplicates and sort in ascending order. aImplicitLines.Sort(); for (size_t i = 0; i < aImplicitLines.Length(); ++i) { uint32_t prev = aImplicitLines[i]; auto j = i + 1; const auto start = j; while (j < aImplicitLines.Length() && aImplicitLines[j] == prev) { ++j; } if (j != start) { aImplicitLines.RemoveElementsAt(start, j - start); } } } return; } if (MOZ_UNLIKELY(!map->mIsSameDirection)) { aSide = GetOppositeSide(aSide); sameDirectionAsThis = !sameDirectionAsThis; } min = map->TranslateToParentMap(min); max = map->TranslateToParentMap(max); if (min > max) { MOZ_ASSERT(!map->mIsSameDirection); std::swap(min, max); } map = parent; } } /** * Return true if any implicit named areas match aName, in this map or * in any of our ancestor maps. */ bool HasImplicitNamedArea(nsAtom* aName) const { const auto* map = this; do { if (map->mAreas && map->mAreas->has(aName)) { return true; } map = map->mParentLineNameMap; } while (map); return false; } // For generating line name data for devtools. nsTArray> GetResolvedLineNamesForComputedGridTrackInfo() const { nsTArray> result; for (auto& expandedLine : mExpandedLineNames) { nsTArray line; for (auto* chunk : expandedLine) { for (auto& name : chunk->AsSpan()) { line.AppendElement(name); } } result.AppendElement(std::move(line)); } return result; } nsTArray> GetExplicitLineNamesAtIndex(uint32_t aIndex) const { nsTArray> lineNames; if (aIndex < mTemplateLinesEnd) { const auto nameLists = GetLineNamesAt(aIndex); for (const NameList* nameList : nameLists) { for (const auto& name : nameList->AsSpan()) { lineNames.AppendElement(name.AsAtom()); } } } return lineNames; } const nsTArray>& ExpandedLineNames() const { return mExpandedLineNames; } const Span>& TrackAutoRepeatLineNames() const { return mTrackAutoRepeatLineNames; } bool HasRepeatAuto() const { return mHasRepeatAuto; } uint32_t NumRepeatTracks() const { return mRepeatAutoEnd - mRepeatAutoStart; } uint32_t RepeatAutoStart() const { return mRepeatAutoStart; } // The min/max line number (1-based) for clamping. int32_t mClampMinLine; int32_t mClampMaxLine; private: // Return true if this map represents a subgridded axis. bool IsSubgridded() const { return mParentLineNameMap != nullptr; } /** * @see FindNamedLine, this function searches forward. */ uint32_t FindLine(nsAtom* aName, int32_t* aNth, uint32_t aFromIndex, const nsTArray& aImplicitLines) const { MOZ_ASSERT(aNth && *aNth > 0); int32_t nth = *aNth; // For a subgrid we need to search to the end of the grid rather than // the end of the local name list, since ancestors might match. const uint32_t end = IsSubgridded() ? mClampMaxLine : mTemplateLinesEnd; uint32_t line; uint32_t i = aFromIndex; for (; i < end; i = line) { line = i + 1; if (Contains(i, aName) || aImplicitLines.Contains(line)) { if (--nth == 0) { return line; } } } for (auto implicitLine : aImplicitLines) { if (implicitLine > i) { // implicitLine 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 implicitLine; } } } 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(nsAtom* aName, int32_t* aNth, uint32_t aFromIndex, const nsTArray& aImplicitLines) 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; // Implicit lines may be beyond the explicit grid so we match those // first if it's within the mTemplateLinesEnd..aFromIndex range. // aImplicitLines is presumed sorted. // For a subgrid we need to search to the end of the grid rather than // the end of the local name list, since ancestors might match. const uint32_t end = IsSubgridded() ? mClampMaxLine : mTemplateLinesEnd; for (auto implicitLine : Reversed(aImplicitLines)) { if (implicitLine <= end) { break; } if (implicitLine < aFromIndex) { if (--nth == 0) { return implicitLine; } } } for (uint32_t i = std::min(aFromIndex, end); i; --i) { if (Contains(i - 1, aName) || aImplicitLines.Contains(i)) { 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 in this map or any parent map. bool Contains(uint32_t aIndex, nsAtom* aName) const { const auto* map = this; while (true) { if (aIndex < map->mTemplateLinesEnd && map->HasNameAt(aIndex, aName)) { return true; } auto* parent = map->mParentLineNameMap; if (!parent) { return false; } uint32_t line = map->TranslateToParentMap(aIndex + 1); MOZ_ASSERT(line >= 1, "expected a 1-based line number"); aIndex = line - 1; map = parent; } MOZ_ASSERT_UNREACHABLE("we always return from inside the loop above"); } static bool Contains(Span aNames, nsAtom* aName) { for (auto& name : aNames) { if (name.AsAtom() == aName) { return true; } } return false; } // Return true if aName exists at aIndex in this map. bool HasNameAt(const uint32_t aIndex, nsAtom* const aName) const { const auto nameLists = GetLineNamesAt(aIndex); for (const NameList* nameList : nameLists) { if (Contains(nameList->AsSpan(), aName)) { return true; } } return false; } // Get the line names at an index. // This accounts for auto repeat. The results may be spread over multiple name // lists returned in the array, which is done to avoid unneccessarily copying // the arrays to concatenate them. SmallPointerArray GetLineNamesAt( const uint32_t aIndex) const { SmallPointerArray names; // The index into mExpandedLineNames to use, if aIndex doesn't point to a // name inside of a auto repeat. uint32_t repeatAdjustedIndex = aIndex; if (mHasRepeatAuto) { // If the index is inside of the auto repeat, use the repeat line // names. Otherwise, if the index is past the end of the repeat it must // be adjusted to acount for the repeat tracks. // mExpandedLineNames has the first and last line name lists from the // repeat in it already, so we can just ignore aIndex == mRepeatAutoStart // and treat when aIndex == mRepeatAutoEnd the same as any line after the // the repeat. const uint32_t maxRepeatLine = mTrackAutoRepeatLineNames.Length() - 1; if (aIndex > mRepeatAutoStart && aIndex < mRepeatAutoEnd) { // The index is inside the auto repeat. Calculate the lines to use, // including the previous repetitions final names when we roll over // from one repetition to the next. const uint32_t repeatIndex = (aIndex - mRepeatAutoStart) % maxRepeatLine; if (repeatIndex == 0) { // The index is at the start of a new repetition. The start of the // first repetition is intentionally ignored above, so this will // consider both the end of the previous repetition and the start // the one that contains aIndex. names.AppendElement(&mTrackAutoRepeatLineNames[maxRepeatLine]); } names.AppendElement(&mTrackAutoRepeatLineNames[repeatIndex]); return names; } if (aIndex != mRepeatAutoStart && aIndex >= mRepeatAutoEnd) { // Adjust the index to account for the line names of the repeat. repeatAdjustedIndex -= mRepeatEndDelta; repeatAdjustedIndex += mTrackAutoRepeatLineNames.Length() - 2; } } MOZ_ASSERT(names.IsEmpty()); // The index is not inside the repeat tracks, or no repeat tracks exist. const auto& nameLists = mExpandedLineNames[repeatAdjustedIndex]; for (const NameList* nameList : nameLists) { names.AppendElement(nameList); } return names; } // Translate a subgrid line (1-based) to a parent line (1-based). uint32_t TranslateToParentMap(uint32_t aLine) const { if (MOZ_LIKELY(mIsSameDirection)) { return aLine + mRange->mStart; } MOZ_ASSERT(mRange->mEnd + 1 >= aLine); return mRange->mEnd - (aLine - 1) + 1; } /** * Return the 1-based line that match aName in 'grid-template-areas' * on the side aSide. Clamp the result to aMin..aMax but require * that some part of the area is inside for it to match. * Return zero if there is no match. */ uint32_t FindNamedArea(nsAtom* aName, LogicalSide aSide, int32_t aMin, int32_t aMax) const { if (const NamedArea* area = FindNamedArea(aName)) { int32_t start = IsBlock(aSide) ? area->rows.start : area->columns.start; int32_t end = IsBlock(aSide) ? area->rows.end : area->columns.end; if (IsStart(aSide)) { if (start >= aMin) { if (start <= aMax) { return start; } } else if (end >= aMin) { return aMin; } } else { if (end <= aMax) { if (end >= aMin) { return end; } } else if (start <= aMax) { return aMax; } } } return 0; // no match } /** * A convenience method to lookup a name in 'grid-template-areas'. * @return null if not found */ const NamedArea* FindNamedArea(nsAtom* aName) const { if (mStylePosition->mGridTemplateAreas.IsNone()) { return nullptr; } const auto areas = mStylePosition->mGridTemplateAreas.AsAreas(); for (const NamedArea& area : areas->areas.AsSpan()) { if (area.name.AsAtom() == aName) { return &area; } } return nullptr; } // Some style data references, for easy access. const nsStylePosition* mStylePosition; const ImplicitNamedAreas* mAreas; // The expanded list of line-names. Each entry is usually a single NameList, // but can be multiple in the case where repeat() expands to something that // has a line name list at the end. nsTArray> mExpandedLineNames; // The repeat(auto-fill/fit) track value, if any. (always empty for subgrid) Span> mTrackAutoRepeatLineNames; // The index of the repeat(auto-fill/fit) track, or zero if there is none. uint32_t mRepeatAutoStart; // The index one past the end of the repeat(auto-fill/fit) tracks. Equal to // mRepeatAutoStart if there are no repeat(auto-fill/fit) tracks. uint32_t mRepeatAutoEnd; // The total number of repeat tracks minus 1. int32_t mRepeatEndDelta; // The end of the line name lists with repeat(auto-fill/fit) tracks accounted // for. uint32_t mTemplateLinesEnd; // The parent line map, or null if this map isn't for a subgrid. const LineNameMap* mParentLineNameMap; // The subgrid's range, or null if this map isn't for a subgrid. const LineRange* mRange; // True if the subgrid/parent axes progresses in the same direction. const bool mIsSameDirection; // True if there is a specified repeat(auto-fill/fit) track. bool mHasRepeatAuto; }; /** * State for the tracks in one dimension. */ struct nsGridContainerFrame::Tracks { explicit Tracks(LogicalAxis aAxis) : mContentBoxSize(NS_UNCONSTRAINEDSIZE), mGridGap(NS_UNCONSTRAINEDSIZE), mStateUnion(TrackSize::StateBits{0}), mAxis(aAxis), mCanResolveLineRangeSize(false), mIsMasonry(false) { mBaselineSubtreeAlign[BaselineSharingGroup::First] = StyleAlignFlags::AUTO; mBaselineSubtreeAlign[BaselineSharingGroup::Last] = StyleAlignFlags::AUTO; mBaseline[BaselineSharingGroup::First] = NS_INTRINSIC_ISIZE_UNKNOWN; mBaseline[BaselineSharingGroup::Last] = NS_INTRINSIC_ISIZE_UNKNOWN; } void Initialize(const TrackSizingFunctions& aFunctions, const NonNegativeLengthPercentageOrNormal& aGridGap, uint32_t aNumTracks, nscoord aContentBoxSize); /** * Return the union of the state bits for the tracks in aRange. */ TrackSize::StateBits StateBitsForRange(const LineRange& aRange) 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); /** * A masonry axis has four baseline alignment sets and each set can have * a first- and last-baseline alignment group, for a total of eight possible * baseline alignment groups, as follows: * set 1: the first item in each `start` or `stretch` grid track * set 2: the last item in each `start` grid track * set 3: the last item in each `end` or `stretch` grid track * set 4: the first item in each `end` grid track * (`start`/`end`/`stretch` refers to the relevant `align/justify-tracks` * value of the (grid-axis) start track for the item) Baseline-alignment for * set 1 and 2 always adjusts the item's padding or margin on the start side, * and set 3 and 4 on the end side, for both first- and last-baseline groups * in the set. (This is similar to regular grid which always adjusts * first-baseline groups on the start side and last-baseline groups on the * end-side. The crux is that those groups are always aligned to the track's * start/end side respectively.) */ struct BaselineAlignmentSet { bool MatchTrackAlignment(StyleAlignFlags aTrackAlignment) const { if (mTrackAlignmentSet == BaselineAlignmentSet::StartStretch) { return aTrackAlignment == StyleAlignFlags::START || (aTrackAlignment == StyleAlignFlags::STRETCH && mItemSet == BaselineAlignmentSet::FirstItems); } return aTrackAlignment == StyleAlignFlags::END || (aTrackAlignment == StyleAlignFlags::STRETCH && mItemSet == BaselineAlignmentSet::LastItems); } enum ItemSet { FirstItems, LastItems }; ItemSet mItemSet = FirstItems; enum TrackAlignmentSet { StartStretch, EndStretch }; TrackAlignmentSet mTrackAlignmentSet = StartStretch; }; void InitializeItemBaselinesInMasonryAxis( GridReflowInput& aState, nsTArray& aGridItems, BaselineAlignmentSet aSet, const nsSize& aContainerSize, nsTArray& aTrackSizes, nsTArray& aFirstBaselineItems, nsTArray& aLastBaselineItems); /** * 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; enum class TrackSizingPhase { IntrinsicMinimums, ContentBasedMinimums, MaxContentMinimums, IntrinsicMaximums, MaxContentMaximums, }; // Some data we collect on each item that spans more than one track for step 3 // and 4 of the Track Sizing Algorithm in ResolveIntrinsicSize below. // https://w3c.github.io/csswg-drafts/css-grid-1/#algo-spanning-items struct SpanningItemData final { uint32_t mSpan; TrackSize::StateBits mState; LineRange mLineRange; nscoord mMinSize; nscoord mMinContentContribution; nscoord mMaxContentContribution; nsIFrame* mFrame; static bool IsSpanLessThan(const SpanningItemData& a, const SpanningItemData& b) { return a.mSpan < b.mSpan; } template nscoord SizeContributionForPhase() const { switch (phase) { case TrackSizingPhase::IntrinsicMinimums: return mMinSize; case TrackSizingPhase::ContentBasedMinimums: case TrackSizingPhase::IntrinsicMaximums: return mMinContentContribution; case TrackSizingPhase::MaxContentMinimums: case TrackSizingPhase::MaxContentMaximums: return mMaxContentContribution; } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected phase"); } #ifdef DEBUG void Dump() const { printf( "SpanningItemData { mSpan: %d, mState: %d, mLineRange: (%d, %d), " "mMinSize: %d, mMinContentContribution: %d, mMaxContentContribution: " "%d, mFrame: %p\n", mSpan, mState, mLineRange.mStart, mLineRange.mEnd, mMinSize, mMinContentContribution, mMaxContentContribution, mFrame); } #endif }; using FitContentClamper = std::function; // Helper method for ResolveIntrinsicSize. template bool GrowSizeForSpanningItems( nsTArray::iterator aIter, nsTArray::iterator aIterEnd, nsTArray& aTracks, nsTArray& aPlan, nsTArray& aItemPlan, TrackSize::StateBits aSelector, const FitContentClamper& aFitContentClamper = nullptr, bool aNeedInfinitelyGrowableFlag = false); /** * 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 ResolveIntrinsicSizeForNonSpanningItems( GridReflowInput& aState, const TrackSizingFunctions& aFunctions, nscoord aPercentageBasis, SizingConstraint aConstraint, const LineRange& aRange, const GridItemInfo& aGridItem); // Helper method that returns the track size to use in §11.5.1.2 // https://drafts.csswg.org/css-grid/#extra-space template static nscoord StartSizeInDistribution(const TrackSize& aSize) { switch (phase) { case TrackSizingPhase::IntrinsicMinimums: case TrackSizingPhase::ContentBasedMinimums: case TrackSizingPhase::MaxContentMinimums: return aSize.mBase; case TrackSizingPhase::IntrinsicMaximums: case TrackSizingPhase::MaxContentMaximums: if (aSize.mLimit == NS_UNCONSTRAINEDSIZE) { return aSize.mBase; } return aSize.mLimit; } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected phase"); } /** * Collect the tracks which are growable (matching aSelector) into * aGrowableTracks, and return the amount of space that can be used * to grow those tracks. This method implements CSS Grid §11.5.1.2. * https://drafts.csswg.org/css-grid/#extra-space */ template nscoord CollectGrowable(nscoord aAvailableSpace, const LineRange& aRange, TrackSize::StateBits aSelector, nsTArray& aGrowableTracks) const { MOZ_ASSERT(aAvailableSpace > 0, "why call me?"); nscoord space = aAvailableSpace - mGridGap * (aRange.Extent() - 1); for (auto i : aRange.Range()) { const TrackSize& sz = mSizes[i]; space -= StartSizeInDistribution(sz); if (space <= 0) { return 0; } if (sz.mState & aSelector) { aGrowableTracks.AppendElement(i); } } return aGrowableTracks.IsEmpty() ? 0 : space; } template void InitializeItemPlan(nsTArray& aItemPlan, const nsTArray& aTracks) const { for (uint32_t track : aTracks) { auto& plan = aItemPlan[track]; const TrackSize& sz = mSizes[track]; plan.mBase = StartSizeInDistribution(sz); bool unlimited = sz.mState & TrackSize::eInfinitelyGrowable; plan.mLimit = unlimited ? NS_UNCONSTRAINEDSIZE : sz.mLimit; plan.mState = sz.mState; } } template void InitializePlan(nsTArray& aPlan) const { for (size_t i = 0, len = aPlan.Length(); i < len; ++i) { auto& plan = aPlan[i]; const auto& sz = mSizes[i]; plan.mBase = StartSizeInDistribution(sz); MOZ_ASSERT(phase == TrackSizingPhase::MaxContentMaximums || !(sz.mState & TrackSize::eInfinitelyGrowable), "forgot to reset the eInfinitelyGrowable bit?"); plan.mState = sz.mState; } } template void CopyPlanToSize(const nsTArray& aPlan, bool aNeedInfinitelyGrowableFlag = false) { for (size_t i = 0, len = mSizes.Length(); i < len; ++i) { const auto& plan = aPlan[i]; MOZ_ASSERT(plan.mBase >= 0); auto& sz = mSizes[i]; switch (phase) { case TrackSizingPhase::IntrinsicMinimums: case TrackSizingPhase::ContentBasedMinimums: case TrackSizingPhase::MaxContentMinimums: sz.mBase = plan.mBase; break; case TrackSizingPhase::IntrinsicMaximums: if (plan.mState & TrackSize::eModified) { if (sz.mLimit == NS_UNCONSTRAINEDSIZE && aNeedInfinitelyGrowableFlag) { sz.mState |= TrackSize::eInfinitelyGrowable; } sz.mLimit = plan.mBase; } break; case TrackSizingPhase::MaxContentMaximums: if (plan.mState & TrackSize::eModified) { sz.mLimit = plan.mBase; } sz.mState &= ~TrackSize::eInfinitelyGrowable; break; } } } /** * 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, const 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. */ static uint32_t MarkExcludedTracks(nsTArray& aPlan, uint32_t aNumGrowable, const nsTArray& aGrowableTracks, TrackSize::StateBits aMinSizingSelector, TrackSize::StateBits aMaxSizingSelector, TrackSize::StateBits aSkipFlag) { 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; } /** * Mark all tracks in aGrowableTracks with an eSkipGrowUnlimited bit if * they *shouldn't* grow unlimited in §11.5.1.2.3 "Distribute space beyond * growth limits" https://drafts.csswg.org/css-grid/#extra-space * Return the number of tracks that are still growable. */ template static uint32_t MarkExcludedTracks(nsTArray& aPlan, const nsTArray& aGrowableTracks, TrackSize::StateBits aSelector) { uint32_t numGrowable = aGrowableTracks.Length(); if (phase == TrackSizingPhase::IntrinsicMaximums || phase == TrackSizingPhase::MaxContentMaximums) { // "when handling any intrinsic growth limit: all affected tracks" return numGrowable; } 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); } return numGrowable; } /** * Increase the planned size for tracks in aGrowableTracks that aren't * marked with a eSkipGrowUnlimited flag 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, uint32_t aNumGrowable, const FitContentClamper& aFitContentClamper) const { MOZ_ASSERT(aAvailableSpace > 0 && aGrowableTracks.Length() > 0 && aNumGrowable <= aGrowableTracks.Length()); nscoord space = aAvailableSpace; DebugOnly didClamp = false; while (aNumGrowable) { nscoord spacePerTrack = std::max(space / aNumGrowable, 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; --aNumGrowable; } } 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. */ template void DistributeToTrackSizes(nscoord aAvailableSpace, nsTArray& aPlan, nsTArray& aItemPlan, nsTArray& aGrowableTracks, TrackSize::StateBits aSelector, const FitContentClamper& aFitContentClamper) { InitializeItemPlan(aItemPlan, aGrowableTracks); nscoord space = GrowTracksToLimit(aAvailableSpace, aItemPlan, aGrowableTracks, aFitContentClamper); if (space > 0) { uint32_t numGrowable = MarkExcludedTracks(aItemPlan, aGrowableTracks, aSelector); GrowSelectedTracksUnlimited(space, aItemPlan, aGrowableTracks, numGrowable, aFitContentClamper); } for (uint32_t track : aGrowableTracks) { nscoord& plannedSize = aPlan[track].mBase; nscoord itemIncurredSize = aItemPlan[track].mBase; if (plannedSize < itemIncurredSize) { plannedSize = itemIncurredSize; } } } /** * 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 aContentBoxSize, 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, StyleContentDistribution aAligmentStyleValue, WritingMode aWM, nscoord aContentBoxSize, bool aIsSubgridded); 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::BeforeGridGap) { 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 SumOfGridTracksAndGaps() { return SumOfGridTracks() + SumOfGridGaps(); } nscoord SumOfGridTracks() const { nscoord result = 0; for (const TrackSize& size : mSizes) { result += size.mBase; } return result; } 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; } #ifdef DEBUG void Dump() const; #endif CopyableAutoTArray mSizes; nscoord mContentBoxSize; nscoord mGridGap; // The first(last)-baseline for the first(last) track in this axis. PerBaseline mBaseline; // 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 StyleAlignFlags::{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. PerBaseline mBaselineSubtreeAlign; // True if track positions and sizes are final in this axis. bool mCanResolveLineRangeSize; // True if this axis has masonry layout. bool mIsMasonry; }; #ifdef DEBUG void nsGridContainerFrame::Tracks::Dump() const { printf("%zu %s %s ", mSizes.Length(), mIsMasonry ? "masonry" : "grid", mAxis == eLogicalAxisBlock ? "rows" : "columns"); TrackSize::DumpStateBits(mStateUnion); printf("\n"); for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) { printf(" %d: ", i); mSizes[i].Dump(); printf("\n"); } double px = AppUnitsPerCSSPixel(); printf("Baselines: %.2fpx %2fpx\n", mBaseline[BaselineSharingGroup::First] / px, mBaseline[BaselineSharingGroup::Last] / px); printf("Gap: %.2fpx\n", mGridGap / px); printf("ContentBoxSize: %.2fpx\n", mContentBoxSize / px); } #endif /** * 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 input 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, gfxContext& 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->GetProperty(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 || aGridContainerFrame->IsMasonry(eLogicalAxisBlock)) { // All of the grid's rows fit inside of previous grid-container fragments, // or it's a masonry axis. 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; if (firstInFlow->GetProperty(UsedTrackSizes::Prop())) { auto* prop = aGridContainerFrame->GetProperty(UsedTrackSizes::Prop()); if (!prop) { prop = new UsedTrackSizes(); aGridContainerFrame->SetProperty(UsedTrackSizes::Prop(), prop); } prop->mCanResolveLineRangeSize = {true, true}; prop->mSizes[eLogicalAxisInline].Assign(mCols.mSizes); prop->mSizes[eLogicalAxisBlock].Assign(mRows.mSizes); } // 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]; item->mState[0] |= itemInfo.mState[0] & ItemState::eAutoPlacement; item->mState[1] |= itemInfo.mState[1] & ItemState::eAutoPlacement; 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) const 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->SetShouldGenerateComputedInfo(true); } } /** * Calculate our track sizes in the given axis. */ void CalculateTrackSizesForAxis(LogicalAxis aAxis, const Grid& aGrid, nscoord aCBSize, SizingConstraint aConstraint); /** * Calculate our track sizes. */ void CalculateTrackSizes(const Grid& aGrid, const LogicalSize& aContentBox, SizingConstraint aConstraint); /** * Return the percentage basis for a grid item in its writing-mode. * If aAxis is eLogicalAxisInline then we return NS_UNCONSTRAINEDSIZE in * both axes since we know all track sizes are indefinite at this point * (we calculate column sizes before row sizes). Otherwise, assert that * column sizes are known and calculate the size for aGridItem.mArea.mCols * and use NS_UNCONSTRAINEDSIZE in the other axis. * @param aAxis the axis we're currently calculating track sizes for */ LogicalSize PercentageBasisFor(LogicalAxis aAxis, const GridItemInfo& aGridItem) const; /** * 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; /** * Apply `align/justify-content` alignment in our masonry axis. * This aligns the "masonry box" within our content box size. */ void AlignJustifyContentInMasonryAxis(nscoord aMasonryBoxSize, nscoord aContentBoxSize); /** * Apply `align/justify-tracks` alignment in our masonry axis. */ void AlignJustifyTracksInMasonryAxis(const LogicalSize& aContentSize, const nsSize& aContainerSize); // Helper for CollectSubgridItemsForAxis. static void CollectSubgridForAxis(LogicalAxis aAxis, WritingMode aContainerWM, const LineRange& aRangeInAxis, const LineRange& aRangeInOppositeAxis, const GridItemInfo& aItem, const nsTArray& aItems, nsTArray& aResult) { const auto oppositeAxis = GetOrthogonalAxis(aAxis); bool itemIsSubgridInOppositeAxis = aItem.IsSubgrid(oppositeAxis); auto subgridWM = aItem.mFrame->GetWritingMode(); bool isOrthogonal = subgridWM.IsOrthogonalTo(aContainerWM); bool isSameDirInAxis = subgridWM.ParallelAxisStartsOnSameSide(aAxis, aContainerWM); bool isSameDirInOppositeAxis = subgridWM.ParallelAxisStartsOnSameSide(oppositeAxis, aContainerWM); if (isOrthogonal) { // We'll Transpose the area below so these needs to be transposed as well. std::swap(isSameDirInAxis, isSameDirInOppositeAxis); } uint32_t offsetInAxis = aRangeInAxis.mStart; uint32_t gridEndInAxis = aRangeInAxis.Extent(); uint32_t offsetInOppositeAxis = aRangeInOppositeAxis.mStart; uint32_t gridEndInOppositeAxis = aRangeInOppositeAxis.Extent(); for (const auto& subgridItem : aItems) { auto newItem = aResult.AppendElement( isOrthogonal ? subgridItem.Transpose() : subgridItem); if (MOZ_UNLIKELY(!isSameDirInAxis)) { newItem->ReverseDirection(aAxis, gridEndInAxis); } newItem->mArea.LineRangeForAxis(aAxis).Translate(offsetInAxis); if (itemIsSubgridInOppositeAxis) { if (MOZ_UNLIKELY(!isSameDirInOppositeAxis)) { newItem->ReverseDirection(oppositeAxis, gridEndInOppositeAxis); } LineRange& range = newItem->mArea.LineRangeForAxis(oppositeAxis); range.Translate(offsetInOppositeAxis); } if (newItem->IsSubgrid(aAxis)) { auto* subgrid = subgridItem.SubgridFrame()->GetProperty(Subgrid::Prop()); CollectSubgridForAxis(aAxis, aContainerWM, newItem->mArea.LineRangeForAxis(aAxis), newItem->mArea.LineRangeForAxis(oppositeAxis), *newItem, subgrid->mGridItems, aResult); } } } // Copy all descendant items from all our subgrid children that are subgridded // in aAxis recursively into aResult. All item grid area's and state are // translated to our coordinates. void CollectSubgridItemsForAxis(LogicalAxis aAxis, nsTArray& aResult) const { for (const auto& item : mGridItems) { if (item.IsSubgrid(aAxis)) { const auto oppositeAxis = GetOrthogonalAxis(aAxis); auto* subgrid = item.SubgridFrame()->GetProperty(Subgrid::Prop()); CollectSubgridForAxis(aAxis, mWM, item.mArea.LineRangeForAxis(aAxis), item.mArea.LineRangeForAxis(oppositeAxis), item, subgrid->mGridItems, aResult); } } } Tracks& TracksFor(LogicalAxis aAxis) { return aAxis == eLogicalAxisBlock ? mRows : mCols; } const Tracks& TracksFor(LogicalAxis aAxis) const { return aAxis == eLogicalAxisBlock ? mRows : mCols; } CSSOrderAwareFrameIterator 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 input if we need it to calculate * min/max-content contributions when sizing tracks. */ const ReflowInput* const mReflowInput; gfxContext& 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, gfxContext& aRenderingContext, const ReflowInput* aReflowInput, const nsStylePosition* aGridStyle, const WritingMode& aWM) : mIter(aFrame, FrameChildListID::Principal), mGridStyle(aGridStyle), mCols(eLogicalAxisInline), mRows(eLogicalAxisBlock), mColFunctions(mGridStyle->mGridTemplateColumns, mGridStyle->mGridAutoColumns, aFrame->IsSubgrid(eLogicalAxisInline)), mRowFunctions(mGridStyle->mGridTemplateRows, mGridStyle->mGridAutoRows, aFrame->IsSubgrid(eLogicalAxisBlock)), mReflowInput(aReflowInput), mRenderingContext(aRenderingContext), mFrame(aFrame), mSharedGridData(nullptr), mBorderPadding(aWM), mFragBStart(0), mStartRow(0), mNextFragmentStartRow(0), mSkipSides(aFrame->GetWritingMode()), mWM(aWM), mInFragmentainer(false) { MOZ_ASSERT(!aReflowInput || aReflowInput->mFrame == mFrame); if (aReflowInput) { mBorderPadding = aReflowInput->ComputedLogicalBorderPadding(mWM); mSkipSides = aFrame->PreReflowBlockLevelLogicalSkipSides(); mBorderPadding.ApplySkipSides(mSkipSides); } mCols.mIsMasonry = aFrame->IsMasonry(eLogicalAxisInline); mRows.mIsMasonry = aFrame->IsMasonry(eLogicalAxisBlock); MOZ_ASSERT(!(mCols.mIsMasonry && mRows.mIsMasonry), "can't have masonry layout in both axes"); } }; 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 { explicit Grid(const Grid* aParentGrid = nullptr) : mParentGrid(aParentGrid) {} /** * 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 aRepeatSizing the container's [min-|max-]*size - used to determine * the number of repeat(auto-fill/fit) tracks. */ void PlaceGridItems(GridReflowInput& aState, const RepeatTrackSizingInput& aRepeatSizing); void SubgridPlaceGridItems(GridReflowInput& aParentState, Grid* aParentGrid, const GridItemInfo& aGridItem); /** * 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 StyleGridLine& aStart, const StyleGridLine& aEnd, const LineNameMap& aNameMap, LogicalAxis aAxis, 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. * @param aClampMaxColLine the maximum allowed column line number (zero-based) * Pre-condition: aArea->mRows.IsDefinite() is true. * Post-condition: aArea->IsDefinite() is true. */ void PlaceAutoCol(uint32_t aStartCol, GridArea* aArea, uint32_t aClampMaxColLine) 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. * @param aClampMaxRowLine the maximum allowed row line number (zero-based) * Pre-condition: aArea->mCols.IsDefinite() is true. * Post-condition: aArea->IsDefinite() is true. */ void PlaceAutoRow(uint32_t aStartRow, GridArea* aArea, uint32_t aClampMaxRowLine) 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. * @param aClampMaxColLine the maximum allowed column line number (zero-based) * @param aClampMaxRowLine the maximum allowed row line number (zero-based) * 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, uint32_t aClampMaxColLine, uint32_t aClampMaxRowLine) 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. * @param aClampMaxColLine the maximum allowed column line number (zero-based) * @param aClampMaxRowLine the maximum allowed row line number (zero-based) * 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, uint32_t aClampMaxColLine, uint32_t aClampMaxRowLine) 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); } /** * Calculates the empty tracks in a repeat(auto-fit). * @param aOutNumEmptyLines Outputs the number of tracks which are empty. * @param aSizingFunctions Sizing functions for the relevant axis. * @param aNumGridLines Number of grid lines for the relevant axis. * @param aIsEmptyFunc Functor to check if a cell is empty. This should be * mCellMap.IsColEmpty or mCellMap.IsRowEmpty, depending on the axis. */ template static Maybe> CalculateAdjustForAutoFitElements( uint32_t* aOutNumEmptyTracks, TrackSizingFunctions& aSizingFunctions, uint32_t aNumGridLines, IsEmptyFuncT aIsEmptyFunc); /** * 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 aSide the axis+edge we're resolving names for (e.g. if we're resolving a grid-row-start line, pass eLogicalSideBStart) * @param aExplicitGridEnd the last line in the explicit grid * @param aStyle the StylePosition() for the grid container * @return a definite line (1-based), clamped to * the mClampMinLine..mClampMaxLine range */ int32_t ResolveLine(const StyleGridLine& aLine, int32_t aNth, uint32_t aFromIndex, const LineNameMap& aNameMap, LogicalSide aSide, uint32_t aExplicitGridEnd, const nsStylePosition* aStyle); /** * Helper method for ResolveLineRange. * @see ResolveLineRange * @return a pair (start,end) of lines */ typedef std::pair LinePair; LinePair ResolveLineRangeHelper(const StyleGridLine& aStart, const StyleGridLine& aEnd, const LineNameMap& aNameMap, LogicalAxis aAxis, 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 aAxis the axis we're resolving names in * @param aExplicitGridEnd the last line in the explicit grid * @param aStyle the StylePosition() for the grid container */ LineRange ResolveLineRange(const StyleGridLine& aStart, const StyleGridLine& aEnd, const LineNameMap& aNameMap, LogicalAxis aAxis, 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(nsAtom* aName) const { return mAreas && mAreas->has(aName); } // 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(nsAtom* aString, const nsString& aSuffix, uint32_t* aIndex) { if (StringEndsWith(nsDependentAtomString(aString), aSuffix)) { *aIndex = aString->GetLength() - aSuffix.Length(); return *aIndex != 0; } return false; } static bool IsNameWithEndSuffix(nsAtom* aString, uint32_t* aIndex) { return IsNameWithSuffix(aString, u"-end"_ns, aIndex); } static bool IsNameWithStartSuffix(nsAtom* aString, uint32_t* aIndex) { return IsNameWithSuffix(aString, u"-start"_ns, aIndex); } // Return the relevant parent LineNameMap for the given subgrid axis aAxis. const LineNameMap* ParentLineMapForAxis(bool aIsOrthogonal, LogicalAxis aAxis) const { if (!mParentGrid) { return nullptr; } bool isRows = aIsOrthogonal == (aAxis == eLogicalAxisInline); return isRows ? mParentGrid->mRowNameMap : mParentGrid->mColNameMap; } void SetLineMaps(const LineNameMap* aColNameMap, const LineNameMap* aRowNameMap) { mColNameMap = aColNameMap; mRowNameMap = aRowNameMap; } /** * 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 { constexpr 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; /** * Our parent grid if any. */ const Grid* mParentGrid; /** * Our LineNameMaps. */ const LineNameMap* mColNameMap; const LineNameMap* mRowNameMap; }; /** * Compute margin+border+padding for aGridItem.mFrame (a subgrid) and store it * on its Subgrid property (and return that property). * aPercentageBasis is in the grid item's writing-mode. */ static Subgrid* SubgridComputeMarginBorderPadding( const GridItemInfo& aGridItem, const LogicalSize& aPercentageBasis) { auto* subgridFrame = aGridItem.SubgridFrame(); auto cbWM = aGridItem.mFrame->GetParent()->GetWritingMode(); auto* subgrid = subgridFrame->GetProperty(Subgrid::Prop()); auto wm = subgridFrame->GetWritingMode(); auto pmPercentageBasis = cbWM.IsOrthogonalTo(wm) ? aPercentageBasis.BSize(wm) : aPercentageBasis.ISize(wm); SizeComputationInput sz(subgridFrame, nullptr, cbWM, pmPercentageBasis); subgrid->mMarginBorderPadding = sz.ComputedLogicalMargin(cbWM) + sz.ComputedLogicalBorderPadding(cbWM); if (aGridItem.mFrame != subgridFrame) { nsIScrollableFrame* scrollFrame = aGridItem.mFrame->GetScrollTargetFrame(); if (scrollFrame) { MOZ_ASSERT( sz.ComputedLogicalMargin(cbWM) == LogicalMargin(cbWM) && sz.ComputedLogicalBorder(cbWM) == LogicalMargin(cbWM), "A scrolled inner frame should not have any margin or border!"); // Add the margin and border from the (outer) scroll frame. SizeComputationInput szScrollFrame(aGridItem.mFrame, nullptr, cbWM, pmPercentageBasis); subgrid->mMarginBorderPadding += szScrollFrame.ComputedLogicalMargin(cbWM) + szScrollFrame.ComputedLogicalBorder(cbWM); nsMargin ssz = scrollFrame->GetActualScrollbarSizes(); subgrid->mMarginBorderPadding += LogicalMargin(cbWM, ssz); } if (aGridItem.mFrame->IsFieldSetFrame()) { const auto* f = static_cast(aGridItem.mFrame); const auto* inner = f->GetInner(); auto wm = inner->GetWritingMode(); LogicalPoint pos = inner->GetLogicalPosition(aGridItem.mFrame->GetSize()); // The legend is always on the BStart side and it inflates the fieldset's // "border area" size. The inner frame's b-start pos equals that size. LogicalMargin offsets(wm, pos.B(wm), 0, 0, 0); subgrid->mMarginBorderPadding += offsets.ConvertTo(cbWM, wm); } } return subgrid; } static void CopyUsedTrackSizes(nsTArray& aResult, const nsGridContainerFrame* aUsedTrackSizesFrame, const UsedTrackSizes* aUsedTrackSizes, const nsGridContainerFrame* aSubgridFrame, const Subgrid* aSubgrid, LogicalAxis aSubgridAxis) { MOZ_ASSERT(aSubgridFrame->ParentGridContainerForSubgrid() == aUsedTrackSizesFrame); aResult.SetLength(aSubgridAxis == eLogicalAxisInline ? aSubgrid->mGridColEnd : aSubgrid->mGridRowEnd); auto parentAxis = aSubgrid->mIsOrthogonal ? GetOrthogonalAxis(aSubgridAxis) : aSubgridAxis; const auto& parentSizes = aUsedTrackSizes->mSizes[parentAxis]; MOZ_ASSERT(aUsedTrackSizes->mCanResolveLineRangeSize[parentAxis]); if (parentSizes.IsEmpty()) { return; } const auto& range = aSubgrid->mArea.LineRangeForAxis(parentAxis); const auto cbwm = aUsedTrackSizesFrame->GetWritingMode(); const auto wm = aSubgridFrame->GetWritingMode(); // Recompute the MBP to resolve percentages against the resolved track sizes. if (parentAxis == eLogicalAxisInline) { // Find the subgrid's grid item frame in its parent grid container. This // is usually the same as aSubgridFrame but it may also have a ScrollFrame, // FieldSetFrame etc. We just loop until we see the first ancestor // GridContainerFrame and pick the last frame we saw before that. // Note that all subgrids are inside a parent (sub)grid container. const nsIFrame* outerGridItemFrame = aSubgridFrame; for (nsIFrame* parent = aSubgridFrame->GetParent(); parent != aUsedTrackSizesFrame; parent = parent->GetParent()) { MOZ_ASSERT(!parent->IsGridContainerFrame()); outerGridItemFrame = parent; } auto sizeInAxis = range.ToLength(aUsedTrackSizes->mSizes[parentAxis]); LogicalSize pmPercentageBasis = aSubgrid->mIsOrthogonal ? LogicalSize(wm, nscoord(0), sizeInAxis) : LogicalSize(wm, sizeInAxis, nscoord(0)); GridItemInfo info(const_cast(outerGridItemFrame), aSubgrid->mArea); SubgridComputeMarginBorderPadding(info, pmPercentageBasis); } const LogicalMargin& mbp = aSubgrid->mMarginBorderPadding; nscoord startMBP; nscoord endMBP; if (MOZ_LIKELY(cbwm.ParallelAxisStartsOnSameSide(parentAxis, wm))) { startMBP = mbp.Start(parentAxis, cbwm); endMBP = mbp.End(parentAxis, cbwm); uint32_t i = range.mStart; nscoord startPos = parentSizes[i].mPosition + startMBP; for (auto& sz : aResult) { sz = parentSizes[i++]; sz.mPosition -= startPos; } } else { startMBP = mbp.End(parentAxis, cbwm); endMBP = mbp.Start(parentAxis, cbwm); uint32_t i = range.mEnd - 1; const auto& parentEnd = parentSizes[i]; nscoord parentEndPos = parentEnd.mPosition + parentEnd.mBase - startMBP; for (auto& sz : aResult) { sz = parentSizes[i--]; sz.mPosition = parentEndPos - (sz.mPosition + sz.mBase); } } auto& startTrack = aResult[0]; startTrack.mPosition = 0; startTrack.mBase -= startMBP; if (MOZ_UNLIKELY(startTrack.mBase < nscoord(0))) { // Our MBP doesn't fit in the start track. Adjust the track position // to maintain track alignment with our parent. startTrack.mPosition = startTrack.mBase; startTrack.mBase = nscoord(0); } auto& endTrack = aResult.LastElement(); endTrack.mBase -= endMBP; if (MOZ_UNLIKELY(endTrack.mBase < nscoord(0))) { endTrack.mBase = nscoord(0); } } void nsGridContainerFrame::UsedTrackSizes::ResolveTrackSizesForAxis( nsGridContainerFrame* aFrame, LogicalAxis aAxis, gfxContext& aRC) { if (mCanResolveLineRangeSize[aAxis]) { return; } if (!aFrame->IsSubgrid()) { // We can't resolve sizes in this axis at this point. aFrame is the top grid // container, which will store its final track sizes later once they're // resolved in this axis (in GridReflowInput::CalculateTrackSizesForAxis). // The single caller of this method only needs track sizes for // calculating a CB size and it will treat it as indefinite when // this happens. return; } auto* parent = aFrame->ParentGridContainerForSubgrid(); auto* parentSizes = parent->GetUsedTrackSizes(); if (!parentSizes) { parentSizes = new UsedTrackSizes(); parent->SetProperty(UsedTrackSizes::Prop(), parentSizes); } auto* subgrid = aFrame->GetProperty(Subgrid::Prop()); const auto parentAxis = subgrid->mIsOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis; parentSizes->ResolveTrackSizesForAxis(parent, parentAxis, aRC); if (!parentSizes->mCanResolveLineRangeSize[parentAxis]) { if (aFrame->IsSubgrid(aAxis)) { ResolveSubgridTrackSizesForAxis(aFrame, aAxis, subgrid, aRC, NS_UNCONSTRAINEDSIZE); } return; } if (aFrame->IsSubgrid(aAxis)) { CopyUsedTrackSizes(mSizes[aAxis], parent, parentSizes, aFrame, subgrid, aAxis); mCanResolveLineRangeSize[aAxis] = true; } else { const auto& range = subgrid->mArea.LineRangeForAxis(parentAxis); nscoord contentBoxSize = range.ToLength(parentSizes->mSizes[parentAxis]); auto parentWM = aFrame->GetParent()->GetWritingMode(); contentBoxSize -= subgrid->mMarginBorderPadding.StartEnd(parentAxis, parentWM); contentBoxSize = std::max(nscoord(0), contentBoxSize); ResolveSubgridTrackSizesForAxis(aFrame, aAxis, subgrid, aRC, contentBoxSize); } } void nsGridContainerFrame::UsedTrackSizes::ResolveSubgridTrackSizesForAxis( nsGridContainerFrame* aFrame, LogicalAxis aAxis, Subgrid* aSubgrid, gfxContext& aRC, nscoord aContentBoxSize) { GridReflowInput state(aFrame, aRC); state.mGridItems = aSubgrid->mGridItems.Clone(); Grid grid; grid.mGridColEnd = aSubgrid->mGridColEnd; grid.mGridRowEnd = aSubgrid->mGridRowEnd; state.CalculateTrackSizesForAxis(aAxis, grid, aContentBoxSize, SizingConstraint::NoConstraint); const auto& tracks = aAxis == eLogicalAxisInline ? state.mCols : state.mRows; mSizes[aAxis].Assign(tracks.mSizes); mCanResolveLineRangeSize[aAxis] = tracks.mCanResolveLineRangeSize; MOZ_ASSERT(mCanResolveLineRangeSize[aAxis]); } void nsGridContainerFrame::GridReflowInput::CalculateTrackSizesForAxis( LogicalAxis aAxis, const Grid& aGrid, nscoord aContentBoxSize, SizingConstraint aConstraint) { auto& tracks = aAxis == eLogicalAxisInline ? mCols : mRows; const auto& sizingFunctions = aAxis == eLogicalAxisInline ? mColFunctions : mRowFunctions; const auto& gapStyle = aAxis == eLogicalAxisInline ? mGridStyle->mColumnGap : mGridStyle->mRowGap; if (tracks.mIsMasonry) { // See comment on nsGridContainerFrame::MasonryLayout(). tracks.Initialize(sizingFunctions, gapStyle, 2, aContentBoxSize); tracks.mCanResolveLineRangeSize = true; return; } uint32_t gridEnd = aAxis == eLogicalAxisInline ? aGrid.mGridColEnd : aGrid.mGridRowEnd; Maybe fallbackTrackSizing; bool useParentGaps = false; const bool isSubgriddedAxis = mFrame->IsSubgrid(aAxis); if (MOZ_LIKELY(!isSubgriddedAxis)) { tracks.Initialize(sizingFunctions, gapStyle, gridEnd, aContentBoxSize); } else { tracks.mGridGap = nsLayoutUtils::ResolveGapToLength(gapStyle, aContentBoxSize); tracks.mContentBoxSize = aContentBoxSize; const auto* subgrid = mFrame->GetProperty(Subgrid::Prop()); tracks.mSizes.SetLength(gridEnd); auto* parent = mFrame->ParentGridContainerForSubgrid(); auto parentAxis = subgrid->mIsOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis; const auto* parentSizes = parent->GetUsedTrackSizes(); if (parentSizes && parentSizes->mCanResolveLineRangeSize[parentAxis]) { CopyUsedTrackSizes(tracks.mSizes, parent, parentSizes, mFrame, subgrid, aAxis); useParentGaps = gapStyle.IsNormal(); } else { fallbackTrackSizing.emplace(TrackSizingFunctions::ForSubgridFallback( mFrame, subgrid, parent, parentAxis)); tracks.Initialize(*fallbackTrackSizing, gapStyle, gridEnd, aContentBoxSize); } } // We run the Track Sizing Algorithm in non-subgridded axes, and in some // cases in a subgridded axis when our parent track sizes aren't resolved yet. if (MOZ_LIKELY(!isSubgriddedAxis) || fallbackTrackSizing.isSome()) { const size_t origGridItemCount = mGridItems.Length(); if (mFrame->HasSubgridItems(aAxis)) { CollectSubgridItemsForAxis(aAxis, mGridItems); } tracks.CalculateSizes( *this, mGridItems, fallbackTrackSizing ? *fallbackTrackSizing : sizingFunctions, aContentBoxSize, aAxis == eLogicalAxisInline ? &GridArea::mCols : &GridArea::mRows, aConstraint); // XXXmats we're losing the baseline state of subgrid descendants that // CollectSubgridItemsForAxis added here. We need to propagate that // state into the subgrid's Reflow somehow... mGridItems.TruncateLength(origGridItemCount); } if (aContentBoxSize != NS_UNCONSTRAINEDSIZE) { auto alignment = mGridStyle->UsedContentAlignment(tracks.mAxis); tracks.AlignJustifyContent(mGridStyle, alignment, mWM, aContentBoxSize, isSubgriddedAxis); } else if (!useParentGaps) { const nscoord gridGap = tracks.mGridGap; nscoord pos = 0; for (TrackSize& sz : tracks.mSizes) { sz.mPosition = pos; pos += sz.mBase + gridGap; } } if (aConstraint == SizingConstraint::NoConstraint && (mFrame->HasSubgridItems() || mFrame->IsSubgrid())) { mFrame->StoreUsedTrackSizes(aAxis, tracks.mSizes); } // positions and sizes are now final tracks.mCanResolveLineRangeSize = true; } void nsGridContainerFrame::GridReflowInput::CalculateTrackSizes( const Grid& aGrid, const LogicalSize& aContentBox, SizingConstraint aConstraint) { CalculateTrackSizesForAxis(eLogicalAxisInline, aGrid, aContentBox.ISize(mWM), aConstraint); CalculateTrackSizesForAxis(eLogicalAxisBlock, aGrid, aContentBox.BSize(mWM), aConstraint); } // Align an item's margin box in its aAxis inside aCBSize. static void AlignJustifySelf(StyleAlignFlags aAlignment, LogicalAxis aAxis, AlignJustifyFlags aFlags, nscoord aBaselineAdjust, nscoord aCBSize, const ReflowInput& aRI, const LogicalSize& aChildSize, LogicalPoint* aPos) { MOZ_ASSERT(aAlignment != StyleAlignFlags::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::SameSide) ? offset : -offset; } } static void AlignSelf(const nsGridContainerFrame::GridItemInfo& aGridItem, StyleAlignFlags aAlignSelf, nscoord aCBSize, const WritingMode aCBWM, const ReflowInput& aRI, const LogicalSize& aSize, AlignJustifyFlags aFlags, LogicalPoint* aPos) { AlignJustifyFlags flags = aFlags; if (aAlignSelf & StyleAlignFlags::SAFE) { flags |= AlignJustifyFlags::OverflowSafe; } aAlignSelf &= ~StyleAlignFlags::FLAG_BITS; WritingMode childWM = aRI.GetWritingMode(); if (aCBWM.ParallelAxisStartsOnSameSide(eLogicalAxisBlock, childWM)) { flags |= AlignJustifyFlags::SameSide; } // Grid's 'align-self' axis is never parallel to the container's inline axis. if (aAlignSelf == StyleAlignFlags::LEFT || aAlignSelf == StyleAlignFlags::RIGHT) { aAlignSelf = StyleAlignFlags::START; } if (MOZ_LIKELY(aAlignSelf == StyleAlignFlags::NORMAL)) { aAlignSelf = StyleAlignFlags::STRETCH; } nscoord baselineAdjust = 0; if (aAlignSelf == StyleAlignFlags::BASELINE || aAlignSelf == StyleAlignFlags::LAST_BASELINE) { aAlignSelf = aGridItem.GetSelfBaseline(aAlignSelf, eLogicalAxisBlock, &baselineAdjust); // Adjust the baseline alignment value if the baseline affects the opposite // side of what AlignJustifySelf expects. auto state = aGridItem.mState[eLogicalAxisBlock]; if (aAlignSelf == StyleAlignFlags::LAST_BASELINE && !GridItemInfo::BaselineAlignmentAffectsEndSide(state)) { aAlignSelf = StyleAlignFlags::BASELINE; } else if (aAlignSelf == StyleAlignFlags::BASELINE && GridItemInfo::BaselineAlignmentAffectsEndSide(state)) { aAlignSelf = StyleAlignFlags::LAST_BASELINE; } } bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM); LogicalAxis axis = isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock; AlignJustifySelf(aAlignSelf, axis, flags, baselineAdjust, aCBSize, aRI, aSize, aPos); } static void JustifySelf(const nsGridContainerFrame::GridItemInfo& aGridItem, StyleAlignFlags aJustifySelf, nscoord aCBSize, const WritingMode aCBWM, const ReflowInput& aRI, const LogicalSize& aSize, AlignJustifyFlags aFlags, LogicalPoint* aPos) { AlignJustifyFlags flags = aFlags; if (aJustifySelf & StyleAlignFlags::SAFE) { flags |= AlignJustifyFlags::OverflowSafe; } aJustifySelf &= ~StyleAlignFlags::FLAG_BITS; WritingMode childWM = aRI.GetWritingMode(); if (aCBWM.ParallelAxisStartsOnSameSide(eLogicalAxisInline, childWM)) { flags |= AlignJustifyFlags::SameSide; } if (MOZ_LIKELY(aJustifySelf == StyleAlignFlags::NORMAL)) { aJustifySelf = StyleAlignFlags::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. if (aJustifySelf == StyleAlignFlags::LEFT) { aJustifySelf = aCBWM.IsBidiLTR() ? StyleAlignFlags::START : StyleAlignFlags::END; } else if (aJustifySelf == StyleAlignFlags::RIGHT) { aJustifySelf = aCBWM.IsBidiLTR() ? StyleAlignFlags::END : StyleAlignFlags::START; } else if (aJustifySelf == StyleAlignFlags::BASELINE || aJustifySelf == StyleAlignFlags::LAST_BASELINE) { aJustifySelf = aGridItem.GetSelfBaseline(aJustifySelf, eLogicalAxisInline, &baselineAdjust); // Adjust the baseline alignment value if the baseline affects the opposite // side of what AlignJustifySelf expects. auto state = aGridItem.mState[eLogicalAxisInline]; if (aJustifySelf == StyleAlignFlags::LAST_BASELINE && !GridItemInfo::BaselineAlignmentAffectsEndSide(state)) { aJustifySelf = StyleAlignFlags::BASELINE; } else if (aJustifySelf == StyleAlignFlags::BASELINE && GridItemInfo::BaselineAlignmentAffectsEndSide(state)) { aJustifySelf = StyleAlignFlags::LAST_BASELINE; } } bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM); LogicalAxis axis = isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline; AlignJustifySelf(aJustifySelf, axis, flags, baselineAdjust, aCBSize, aRI, aSize, aPos); } static StyleAlignFlags GetAlignJustifyValue(StyleAlignFlags aAlignment, const WritingMode aWM, const bool aIsAlign, bool* aOverflowSafe) { *aOverflowSafe = bool(aAlignment & StyleAlignFlags::SAFE); aAlignment &= ~StyleAlignFlags::FLAG_BITS; // Map some alignment values to 'start' / 'end'. if (aAlignment == StyleAlignFlags::LEFT || aAlignment == StyleAlignFlags::RIGHT) { if (aIsAlign) { // Grid's 'align-content' axis is never parallel to the inline axis. return StyleAlignFlags::START; } bool isStart = aWM.IsBidiLTR() == (aAlignment == StyleAlignFlags::LEFT); return isStart ? StyleAlignFlags::START : StyleAlignFlags::END; } if (aAlignment == StyleAlignFlags::FLEX_START) { return StyleAlignFlags::START; // same as 'start' for Grid } if (aAlignment == StyleAlignFlags::FLEX_END) { return StyleAlignFlags::END; // same as 'end' for Grid } return aAlignment; } static Maybe GetAlignJustifyFallbackIfAny( const StyleContentDistribution& aDistribution, const WritingMode aWM, const bool aIsAlign, bool* aOverflowSafe) { // TODO: Eventually this should look at aDistribution's fallback alignment, // see https://github.com/w3c/csswg-drafts/issues/1002. if (aDistribution.primary == StyleAlignFlags::STRETCH || aDistribution.primary == StyleAlignFlags::SPACE_BETWEEN) { return Some(StyleAlignFlags::START); } if (aDistribution.primary == StyleAlignFlags::SPACE_AROUND || aDistribution.primary == StyleAlignFlags::SPACE_EVENLY) { return Some(StyleAlignFlags::CENTER); } return Nothing(); } //---------------------------------------------------------------------- // Frame class boilerplate // ======================= NS_QUERYFRAME_HEAD(nsGridContainerFrame) NS_QUERYFRAME_ENTRY(nsGridContainerFrame) NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) NS_IMPL_FRAMEARENA_HELPERS(nsGridContainerFrame) nsContainerFrame* NS_NewGridContainerFrame(PresShell* aPresShell, ComputedStyle* aStyle) { return new (aPresShell) nsGridContainerFrame(aStyle, aPresShell->GetPresContext()); } //---------------------------------------------------------------------- // nsGridContainerFrame Method Implementations // =========================================== /*static*/ const nsRect& nsGridContainerFrame::GridItemCB(nsIFrame* aChild) { MOZ_ASSERT(aChild->IsAbsolutelyPositioned()); nsRect* cb = aChild->GetProperty(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( Span 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(kMaxLine)); nsTHashSet currentStarts; ImplicitNamedAreas* areas = GetImplicitNamedAreas(); for (uint32_t i = 0; i < len; ++i) { for (const auto& nameIdent : aLineNameLists[i].AsSpan()) { nsAtom* name = nameIdent.AsAtom(); uint32_t indexOfSuffix; if (Grid::IsNameWithStartSuffix(name, &indexOfSuffix) || Grid::IsNameWithEndSuffix(name, &indexOfSuffix)) { // Extract the name that was found earlier. nsDependentSubstring areaName(nsDependentAtomString(name), 0, indexOfSuffix); // Lazily create the ImplicitNamedAreas. if (!areas) { areas = new ImplicitNamedAreas; SetProperty(ImplicitNamedAreasProperty(), areas); } RefPtr name = NS_Atomize(areaName); auto addPtr = areas->lookupForAdd(name); if (!addPtr) { if (!areas->add( addPtr, name, NamedArea{StyleAtom(do_AddRef(name)), {0, 0}, {0, 0}})) { MOZ_CRASH("OOM while adding grid name lists"); } } } } } } 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(); } auto Add = [&](const GridTemplate& aTemplate, bool aIsSubgrid) { AddImplicitNamedAreas(aTemplate.LineNameLists(aIsSubgrid)); for (auto& value : aTemplate.TrackListValues()) { if (value.IsTrackRepeat()) { AddImplicitNamedAreas(value.AsTrackRepeat().line_names.AsSpan()); } } }; Add(aStyle->mGridTemplateColumns, IsSubgrid(eLogicalAxisInline)); Add(aStyle->mGridTemplateRows, IsSubgrid(eLogicalAxisBlock)); if (areas && areas->count() == 0) { RemoveProperty(ImplicitNamedAreasProperty()); } } int32_t nsGridContainerFrame::Grid::ResolveLine( const StyleGridLine& aLine, int32_t aNth, uint32_t aFromIndex, const LineNameMap& aNameMap, LogicalSide aSide, uint32_t aExplicitGridEnd, const nsStylePosition* aStyle) { MOZ_ASSERT(!aLine.IsAuto()); int32_t line = 0; if (aLine.LineName()->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.is_span && aLine.line_num == 0; if (isNameOnly) { AutoTArray implicitLines; aNameMap.FindNamedAreas(aLine.ident.AsAtom(), aSide, implicitLines); if (!implicitLines.IsEmpty() || aNameMap.HasImplicitNamedArea(aLine.LineName())) { // aName 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 nsAutoString lineName(nsDependentAtomString(aLine.LineName())); if (IsStart(aSide)) { lineName.AppendLiteral("-start"); } else { lineName.AppendLiteral("-end"); } RefPtr name = NS_Atomize(lineName); line = aNameMap.FindNamedLine(name, &aNth, aFromIndex, implicitLines); } } if (line == 0) { // If LineName() ends in -start/-end, try the prefix as a named area. AutoTArray implicitLines; uint32_t index; bool useStart = IsNameWithStartSuffix(aLine.LineName(), &index); if (useStart || IsNameWithEndSuffix(aLine.LineName(), &index)) { auto side = MakeLogicalSide( GetAxis(aSide), useStart ? eLogicalEdgeStart : eLogicalEdgeEnd); RefPtr name = NS_Atomize(nsDependentSubstring( nsDependentAtomString(aLine.LineName()), 0, index)); aNameMap.FindNamedAreas(name, side, implicitLines); } line = aNameMap.FindNamedLine(aLine.LineName(), &aNth, aFromIndex, implicitLines); } if (line == 0) { MOZ_ASSERT(aNth != 0, "we found all N named lines but 'line' is zero!"); int32_t edgeLine; if (aLine.is_span) { // http://dev.w3.org/csswg/css-grid/#grid-placement-span-int // 'span N' edgeLine = IsStart(aSide) ? 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, aNameMap.mClampMinLine, aNameMap.mClampMaxLine); } nsGridContainerFrame::Grid::LinePair nsGridContainerFrame::Grid::ResolveLineRangeHelper( const StyleGridLine& aStart, const StyleGridLine& aEnd, const LineNameMap& aNameMap, LogicalAxis aAxis, uint32_t aExplicitGridEnd, const nsStylePosition* aStyle) { MOZ_ASSERT(int32_t(kAutoLine) > kMaxLine); if (aStart.is_span) { if (aEnd.is_span || aEnd.IsAuto()) { // http://dev.w3.org/csswg/css-grid/#grid-placement-errors if (aStart.LineName()->IsEmpty()) { // span / span * // span / auto return LinePair(kAutoLine, aStart.line_num); } // span / span * // span / auto return LinePair(kAutoLine, 1); // XXX subgrid explicit size instead of 1? } uint32_t from = aEnd.line_num < 0 ? aExplicitGridEnd + 1 : 0; auto end = ResolveLine(aEnd, aEnd.line_num, from, aNameMap, MakeLogicalSide(aAxis, eLogicalEdgeEnd), aExplicitGridEnd, aStyle); int32_t span = aStart.line_num == 0 ? 1 : aStart.line_num; 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, aNameMap.mClampMinLine); return LinePair(start, end); } auto start = ResolveLine(aStart, -span, end, aNameMap, MakeLogicalSide(aAxis, eLogicalEdgeStart), aExplicitGridEnd, 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.is_span) { if (aEnd.LineName()->IsEmpty()) { // auto / span MOZ_ASSERT(aEnd.line_num != 0); return LinePair(start, aEnd.line_num); } // 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.line_num < 0 ? aExplicitGridEnd + 1 : 0; start = ResolveLine(aStart, aStart.line_num, from, aNameMap, MakeLogicalSide(aAxis, eLogicalEdgeStart), aExplicitGridEnd, 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.line_num == 0 ? 1 : aEnd.line_num; if (aEnd.is_span) { if (MOZ_UNLIKELY(start < 0)) { if (aEnd.LineName()->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, aNameMap.mClampMaxLine)); } from = start; } } else { from = aEnd.line_num < 0 ? aExplicitGridEnd + 1 : 0; } auto end = ResolveLine(aEnd, nth, from, aNameMap, MakeLogicalSide(aAxis, eLogicalEdgeEnd), aExplicitGridEnd, aStyle); if (start == int32_t(kAutoLine)) { // auto / definite line start = std::max(aNameMap.mClampMinLine, end - 1); } return LinePair(start, end); } nsGridContainerFrame::LineRange nsGridContainerFrame::Grid::ResolveLineRange( const StyleGridLine& aStart, const StyleGridLine& aEnd, const LineNameMap& aNameMap, LogicalAxis aAxis, uint32_t aExplicitGridEnd, const nsStylePosition* aStyle) { LinePair r = ResolveLineRangeHelper(aStart, aEnd, aNameMap, aAxis, aExplicitGridEnd, aStyle); MOZ_ASSERT(r.second != int32_t(kAutoLine)); if (r.first == int32_t(kAutoLine)) { // r.second is a span, clamp it to aNameMap.mClampMaxLine - 1 so that // the returned range has a HypotheticalEnd <= aNameMap.mClampMaxLine. // http://dev.w3.org/csswg/css-grid/#overlarge-grids r.second = std::min(r.second, aNameMap.mClampMaxLine - 1); } else { // http://dev.w3.org/csswg/css-grid/#grid-placement-errors if (r.first > r.second) { std::swap(r.first, r.second); } else if (r.first == r.second) { if (MOZ_UNLIKELY(r.first == aNameMap.mClampMaxLine)) { r.first = aNameMap.mClampMaxLine - 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, eLogicalAxisInline, mExplicitGridColEnd, aStyle), ResolveLineRange(itemStyle->mGridRowStart, itemStyle->mGridRowEnd, aRowLineNameMap, eLogicalAxisBlock, mExplicitGridRowEnd, aStyle)); } nsGridContainerFrame::LineRange nsGridContainerFrame::Grid::ResolveAbsPosLineRange( const StyleGridLine& aStart, const StyleGridLine& aEnd, const LineNameMap& aNameMap, LogicalAxis aAxis, 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.line_num < 0 ? aExplicitGridEnd + 1 : 0; int32_t end = ResolveLine(aEnd, aEnd.line_num, from, aNameMap, MakeLogicalSide(aAxis, eLogicalEdgeEnd), aExplicitGridEnd, aStyle); if (aEnd.is_span) { ++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.line_num < 0 ? aExplicitGridEnd + 1 : 0; int32_t start = ResolveLine(aStart, aStart.line_num, from, aNameMap, MakeLogicalSide(aAxis, eLogicalEdgeStart), aExplicitGridEnd, aStyle); if (aStart.is_span) { start = std::max(aGridEnd - start, aGridStart); } start = AutoIfOutside(start, aGridStart, aGridEnd); return LineRange(start, kAutoLine); } LineRange r = ResolveLineRange(aStart, aEnd, aNameMap, aAxis, aExplicitGridEnd, aStyle); if (r.IsAuto()) { MOZ_ASSERT(aStart.is_span && aEnd.is_span, "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, eLogicalAxisInline, mExplicitGridColEnd, gridColStart, mGridColEnd, aStyle), ResolveAbsPosLineRange( itemStyle->mGridRowStart, itemStyle->mGridRowEnd, aRowLineNameMap, eLogicalAxisBlock, 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, uint32_t aClampMaxColLine) const { MOZ_ASSERT(aArea->mRows.IsDefinite() && aArea->mCols.IsAuto()); uint32_t col = FindAutoCol(aStartCol, aArea->mRows.mStart, aArea); aArea->mCols.ResolveAutoPosition(col, aClampMaxColLine); 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, uint32_t aClampMaxRowLine) const { MOZ_ASSERT(aArea->mCols.IsDefinite() && aArea->mRows.IsAuto()); uint32_t row = FindAutoRow(aArea->mCols.mStart, aStartRow, aArea); aArea->mRows.ResolveAutoPosition(row, aClampMaxRowLine); MOZ_ASSERT(aArea->IsDefinite()); } void nsGridContainerFrame::Grid::PlaceAutoAutoInRowOrder( uint32_t aStartCol, uint32_t aStartRow, GridArea* aArea, uint32_t aClampMaxColLine, uint32_t aClampMaxRowLine) 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, aClampMaxColLine); aArea->mRows.ResolveAutoPosition(row, aClampMaxRowLine); MOZ_ASSERT(aArea->IsDefinite()); } void nsGridContainerFrame::Grid::PlaceAutoAutoInColOrder( uint32_t aStartCol, uint32_t aStartRow, GridArea* aArea, uint32_t aClampMaxColLine, uint32_t aClampMaxRowLine) 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, aClampMaxColLine); aArea->mRows.ResolveAutoPosition(row, aClampMaxRowLine); MOZ_ASSERT(aArea->IsDefinite()); } template Maybe> nsGridContainerFrame::Grid::CalculateAdjustForAutoFitElements( uint32_t* const aOutNumEmptyLines, TrackSizingFunctions& aSizingFunctions, uint32_t aNumGridLines, IsEmptyFuncT aIsEmptyFunc) { Maybe> trackAdjust; uint32_t& numEmptyLines = *aOutNumEmptyLines; numEmptyLines = 0; if (aSizingFunctions.NumRepeatTracks() > 0) { MOZ_ASSERT(aSizingFunctions.mHasRepeatAuto); // Since this loop is concerned with just the repeat tracks, we // iterate from 0..NumRepeatTracks() which is the natural range of // mRemoveRepeatTracks. This means we have to add // (mExplicitGridOffset + mRepeatAutoStart) to get a zero-based // index for arrays like mCellMap/aIsEmptyFunc and trackAdjust. We'll then // fill out the trackAdjust array for all the remaining lines. const uint32_t repeatStart = (aSizingFunctions.mExplicitGridOffset + aSizingFunctions.mRepeatAutoStart); const uint32_t numRepeats = aSizingFunctions.NumRepeatTracks(); for (uint32_t i = 0; i < numRepeats; ++i) { if (numEmptyLines) { MOZ_ASSERT(trackAdjust.isSome()); (*trackAdjust)[repeatStart + i] = numEmptyLines; } if (aIsEmptyFunc(repeatStart + i)) { ++numEmptyLines; if (trackAdjust.isNothing()) { trackAdjust.emplace(aNumGridLines); trackAdjust->SetLength(aNumGridLines); PodZero(trackAdjust->Elements(), trackAdjust->Length()); } aSizingFunctions.mRemovedRepeatTracks[i] = true; } } // Fill out the trackAdjust array for all the tracks after the repeats. if (numEmptyLines) { for (uint32_t line = repeatStart + numRepeats; line < aNumGridLines; ++line) { (*trackAdjust)[line] = numEmptyLines; } } } return trackAdjust; } void nsGridContainerFrame::Grid::SubgridPlaceGridItems( GridReflowInput& aParentState, Grid* aParentGrid, const GridItemInfo& aGridItem) { MOZ_ASSERT(aGridItem.mArea.IsDefinite() || aGridItem.mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW), "the subgrid's lines should be resolved by now"); if (aGridItem.IsSubgrid(eLogicalAxisInline)) { aParentState.mFrame->AddStateBits(NS_STATE_GRID_HAS_COL_SUBGRID_ITEM); } if (aGridItem.IsSubgrid(eLogicalAxisBlock)) { aParentState.mFrame->AddStateBits(NS_STATE_GRID_HAS_ROW_SUBGRID_ITEM); } auto* childGrid = aGridItem.SubgridFrame(); const auto* pos = childGrid->StylePosition(); childGrid->NormalizeChildLists(); GridReflowInput state(childGrid, aParentState.mRenderingContext); childGrid->InitImplicitNamedAreas(pos); const bool isOrthogonal = aParentState.mWM.IsOrthogonalTo(state.mWM); // Record the subgrid's GridArea in a frame property. auto* subgrid = childGrid->GetProperty(Subgrid::Prop()); if (!subgrid) { subgrid = new Subgrid(aGridItem.mArea, isOrthogonal, aParentState.mWM); childGrid->SetProperty(Subgrid::Prop(), subgrid); } else { subgrid->mArea = aGridItem.mArea; subgrid->mIsOrthogonal = isOrthogonal; subgrid->mGridItems.Clear(); subgrid->mAbsPosItems.Clear(); } // Abs.pos. subgrids may have kAutoLine in their area. Map those to the edge // line in the parent's grid (zero-based line numbers). if (MOZ_UNLIKELY(subgrid->mArea.mCols.mStart == kAutoLine)) { subgrid->mArea.mCols.mStart = 0; } if (MOZ_UNLIKELY(subgrid->mArea.mCols.mEnd == kAutoLine)) { subgrid->mArea.mCols.mEnd = aParentGrid->mGridColEnd - 1; } if (MOZ_UNLIKELY(subgrid->mArea.mRows.mStart == kAutoLine)) { subgrid->mArea.mRows.mStart = 0; } if (MOZ_UNLIKELY(subgrid->mArea.mRows.mEnd == kAutoLine)) { subgrid->mArea.mRows.mEnd = aParentGrid->mGridRowEnd - 1; } MOZ_ASSERT((subgrid->mArea.mCols.Extent() > 0 && subgrid->mArea.mRows.Extent() > 0) || state.mGridItems.IsEmpty(), "subgrid needs at least one track for its items"); // 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 in a non-subgridded axis so we skip // computing them otherwise. RepeatTrackSizingInput repeatSizing(state.mWM); if (!childGrid->IsColSubgrid() && state.mColFunctions.mHasRepeatAuto) { repeatSizing.InitFromStyle(eLogicalAxisInline, state.mWM, state.mFrame->Style()); } if (!childGrid->IsRowSubgrid() && state.mRowFunctions.mHasRepeatAuto) { repeatSizing.InitFromStyle(eLogicalAxisBlock, state.mWM, state.mFrame->Style()); } PlaceGridItems(state, repeatSizing); subgrid->mGridItems = std::move(state.mGridItems); subgrid->mAbsPosItems = std::move(state.mAbsPosItems); subgrid->mGridColEnd = mGridColEnd; subgrid->mGridRowEnd = mGridRowEnd; } void nsGridContainerFrame::Grid::PlaceGridItems( GridReflowInput& aState, const RepeatTrackSizingInput& aSizes) { MOZ_ASSERT(mCellMap.mCells.IsEmpty(), "unexpected entries in cell map"); mAreas = aState.mFrame->GetImplicitNamedAreas(); if (aState.mFrame->HasSubgridItems() || aState.mFrame->IsSubgrid()) { if (auto* uts = aState.mFrame->GetUsedTrackSizes()) { uts->mCanResolveLineRangeSize = {false, false}; uts->mSizes[eLogicalAxisInline].ClearAndRetainStorage(); uts->mSizes[eLogicalAxisBlock].ClearAndRetainStorage(); } } // SubgridPlaceGridItems will set these if we find any subgrid items. aState.mFrame->RemoveStateBits(NS_STATE_GRID_HAS_COL_SUBGRID_ITEM | NS_STATE_GRID_HAS_ROW_SUBGRID_ITEM); // 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. const nsStylePosition* const gridStyle = aState.mGridStyle; const auto* areas = gridStyle->mGridTemplateAreas.IsNone() ? nullptr : &*gridStyle->mGridTemplateAreas.AsAreas(); const LineNameMap* parentLineNameMap = nullptr; const LineRange* subgridRange = nullptr; bool subgridAxisIsSameDirection = true; if (!aState.mFrame->IsColSubgrid()) { aState.mColFunctions.InitRepeatTracks( gridStyle->mColumnGap, aSizes.mMin.ISize(aState.mWM), aSizes.mSize.ISize(aState.mWM), aSizes.mMax.ISize(aState.mWM)); uint32_t areaCols = areas ? areas->width + 1 : 1; mExplicitGridColEnd = aState.mColFunctions.ComputeExplicitGridEnd(areaCols); } else { const auto* subgrid = aState.mFrame->GetProperty(Subgrid::Prop()); subgridRange = &subgrid->SubgridCols(); uint32_t extent = subgridRange->Extent(); mExplicitGridColEnd = extent + 1; // the grid is 1-based at this point parentLineNameMap = ParentLineMapForAxis(subgrid->mIsOrthogonal, eLogicalAxisInline); auto parentWM = aState.mFrame->ParentGridContainerForSubgrid()->GetWritingMode(); subgridAxisIsSameDirection = aState.mWM.ParallelAxisStartsOnSameSide(eLogicalAxisInline, parentWM); } mGridColEnd = mExplicitGridColEnd; LineNameMap colLineNameMap(gridStyle, mAreas, aState.mColFunctions, parentLineNameMap, subgridRange, subgridAxisIsSameDirection); if (!aState.mFrame->IsRowSubgrid()) { const Maybe containBSize = aState.mFrame->ContainIntrinsicBSize(); const nscoord repeatTrackSizingBSize = [&] { // This clamping only applies to auto sizes. if (containBSize && aSizes.mSize.BSize(aState.mWM) == NS_UNCONSTRAINEDSIZE) { return NS_CSS_MINMAX(*containBSize, aSizes.mMin.BSize(aState.mWM), aSizes.mMax.BSize(aState.mWM)); } return aSizes.mSize.BSize(aState.mWM); }(); aState.mRowFunctions.InitRepeatTracks( gridStyle->mRowGap, aSizes.mMin.BSize(aState.mWM), repeatTrackSizingBSize, aSizes.mMax.BSize(aState.mWM)); uint32_t areaRows = areas ? areas->strings.Length() + 1 : 1; mExplicitGridRowEnd = aState.mRowFunctions.ComputeExplicitGridEnd(areaRows); parentLineNameMap = nullptr; subgridRange = nullptr; } else { const auto* subgrid = aState.mFrame->GetProperty(Subgrid::Prop()); subgridRange = &subgrid->SubgridRows(); uint32_t extent = subgridRange->Extent(); mExplicitGridRowEnd = extent + 1; // the grid is 1-based at this point parentLineNameMap = ParentLineMapForAxis(subgrid->mIsOrthogonal, eLogicalAxisBlock); auto parentWM = aState.mFrame->ParentGridContainerForSubgrid()->GetWritingMode(); subgridAxisIsSameDirection = aState.mWM.ParallelAxisStartsOnSameSide(eLogicalAxisBlock, parentWM); } mGridRowEnd = mExplicitGridRowEnd; LineNameMap rowLineNameMap(gridStyle, mAreas, aState.mRowFunctions, parentLineNameMap, subgridRange, subgridAxisIsSameDirection); const bool isSubgridOrItemInSubgrid = aState.mFrame->IsSubgrid() || !!mParentGrid; auto SetSubgridChildEdgeBits = [this, isSubgridOrItemInSubgrid](GridItemInfo& aItem) -> void { if (isSubgridOrItemInSubgrid) { const auto& area = aItem.mArea; if (area.mCols.mStart == 0) { aItem.mState[eLogicalAxisInline] |= ItemState::eStartEdge; } if (area.mCols.mEnd == mGridColEnd) { aItem.mState[eLogicalAxisInline] |= ItemState::eEndEdge; } if (area.mRows.mStart == 0) { aItem.mState[eLogicalAxisBlock] |= ItemState::eStartEdge; } if (area.mRows.mEnd == mGridRowEnd) { aItem.mState[eLogicalAxisBlock] |= ItemState::eEndEdge; } } }; SetLineMaps(&colLineNameMap, &rowLineNameMap); // 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.ItemIndex() == aState.mGridItems.Length() - 1, "ItemIndex() 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; const bool isRowMasonry = aState.mFrame->IsMasonry(eLogicalAxisBlock); const bool isColMasonry = aState.mFrame->IsMasonry(eLogicalAxisInline); const bool isMasonry = isColMasonry || isRowMasonry; mGridColEnd += offsetToColZero; mGridRowEnd += offsetToRowZero; const uint32_t gridAxisTrackCount = isRowMasonry ? mGridColEnd : mGridRowEnd; aState.mIter.Reset(); for (; !aState.mIter.AtEnd(); aState.mIter.Next()) { auto& item = aState.mGridItems[aState.mIter.ItemIndex()]; GridArea& area = item.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()) { if (isMasonry) { item.MaybeInhibitSubgridInMasonry(aState.mFrame, gridAxisTrackCount); } if (item.IsSubgrid()) { Grid grid(this); grid.SubgridPlaceGridItems(aState, this, item); } mCellMap.Fill(area); InflateGridFor(area); SetSubgridChildEdgeBits(item); } } // 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 = isMasonry ? isRowMasonry : !!(flowStyle & StyleGridAutoFlow::ROW); const bool isSparse = !(flowStyle & StyleGridAutoFlow::DENSE); uint32_t clampMaxColLine = colLineNameMap.mClampMaxLine + offsetToColZero; uint32_t clampMaxRowLine = rowLineNameMap.mClampMaxLine + offsetToRowZero; // 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; uint32_t clampMaxLine = isRowOrder ? clampMaxColLine : clampMaxRowLine; aState.mIter.Reset(); for (; !aState.mIter.AtEnd(); aState.mIter.Next()) { auto& item = aState.mGridItems[aState.mIter.ItemIndex()]; GridArea& area = item.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. const uint32_t cursor = isSparse ? cursors->Get(major.mStart) : 0; (this->*placeAutoMinorFunc)(cursor, &area, clampMaxLine); if (isMasonry) { item.MaybeInhibitSubgridInMasonry(aState.mFrame, gridAxisTrackCount); } if (item.IsSubgrid()) { Grid grid(this); grid.SubgridPlaceGridItems(aState, this, item); } mCellMap.Fill(area); SetSubgridChildEdgeBits(item); if (isSparse) { cursors->InsertOrUpdate(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; uint32_t clampMaxMajorLine = isRowOrder ? clampMaxRowLine : clampMaxColLine; aState.mIter.Reset(); for (; !aState.mIter.AtEnd(); aState.mIter.Next()) { auto& item = aState.mGridItems[aState.mIter.ItemIndex()]; GridArea& area = item.mArea; MOZ_ASSERT(*aState.mIter == item.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, clampMaxMajorLine); if (isSparse) { cursorMajor = major.mStart; } } else { // Items with 'auto' in both dimensions. if (isRowOrder) { PlaceAutoAutoInRowOrder(cursorMinor, cursorMajor, &area, clampMaxColLine, clampMaxRowLine); } else { PlaceAutoAutoInColOrder(cursorMajor, cursorMinor, &area, clampMaxColLine, clampMaxRowLine); } 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 } } if (isMasonry) { item.MaybeInhibitSubgridInMasonry(aState.mFrame, gridAxisTrackCount); } if (item.IsSubgrid()) { Grid grid(this); grid.SubgridPlaceGridItems(aState, this, item); } mCellMap.Fill(area); InflateGridFor(area); SetSubgridChildEdgeBits(item); // XXXmats it might be possible to optimize this a bit for masonry layout // if this item was placed in the 2nd row && !isSparse, or the 1st row // is full. Still gotta inflate the grid for all items though to make // the grid large enough... } } // Force all items into the 1st/2nd track and have span 1 in the masonry axis. // (See comment on nsGridContainerFrame::MasonryLayout().) if (isMasonry) { auto masonryAxis = isRowMasonry ? eLogicalAxisBlock : eLogicalAxisInline; aState.mIter.Reset(); for (; !aState.mIter.AtEnd(); aState.mIter.Next()) { auto& item = aState.mGridItems[aState.mIter.ItemIndex()]; auto& masonryRange = item.mArea.LineRangeForAxis(masonryAxis); masonryRange.mStart = std::min(masonryRange.mStart, 1U); masonryRange.mEnd = masonryRange.mStart + 1U; } } 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. const 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 zeroOffsetGridColEnd(mGridColEnd); AutoRestore zeroOffsetGridRowEnd(mGridRowEnd); mGridColEnd -= offsetToColZero; mGridRowEnd -= offsetToRowZero; aState.mAbsPosItems.ClearAndRetainStorage(); for (nsIFrame* child : children) { 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 (isColMasonry) { // XXXmats clamp any non-auto line to 0 or 1. This is intended to // allow authors to address the start/end of the masonry box. // This is experimental at this point though and needs author feedback // and spec work to sort out what is desired and how it should work. // See https://github.com/w3c/csswg-drafts/issues/4650 area.mCols.mStart = std::min(area.mCols.mStart, 1U); } } if (area.mCols.mUntranslatedEnd != int32_t(kAutoLine)) { area.mCols.mEnd = area.mCols.mUntranslatedEnd + offsetToColZero; if (isColMasonry) { // ditto area.mCols.mEnd = std::min(area.mCols.mEnd, 1U); } } if (area.mRows.mUntranslatedStart != int32_t(kAutoLine)) { area.mRows.mStart = area.mRows.mUntranslatedStart + offsetToRowZero; if (isRowMasonry) { // ditto area.mRows.mStart = std::min(area.mRows.mStart, 1U); } } if (area.mRows.mUntranslatedEnd != int32_t(kAutoLine)) { area.mRows.mEnd = area.mRows.mUntranslatedEnd + offsetToRowZero; if (isRowMasonry) { // ditto area.mRows.mEnd = std::min(area.mRows.mEnd, 1U); } } if (isMasonry) { info->MaybeInhibitSubgridInMasonry(aState.mFrame, gridAxisTrackCount); } // An abs.pos. subgrid with placement auto/1 or -1/auto technically // doesn't span any parent tracks. Inhibit subgridding in this case. if (info->IsSubgrid(eLogicalAxisInline)) { if (info->mArea.mCols.mStart == zeroOffsetGridColEnd.SavedValue() || info->mArea.mCols.mEnd == 0) { info->InhibitSubgrid(aState.mFrame, eLogicalAxisInline); } } if (info->IsSubgrid(eLogicalAxisBlock)) { if (info->mArea.mRows.mStart == zeroOffsetGridRowEnd.SavedValue() || info->mArea.mRows.mEnd == 0) { info->InhibitSubgrid(aState.mFrame, eLogicalAxisBlock); } } if (info->IsSubgrid()) { Grid grid(this); grid.SubgridPlaceGridItems(aState, this, *info); } } } // 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.GetRepeatAutoValue()->count.IsAutoFit()) { const auto& cellMap = mCellMap; colAdjust = CalculateAdjustForAutoFitElements( &numEmptyCols, aState.mColFunctions, mGridColEnd + 1, [&cellMap](uint32_t i) -> bool { return cellMap.IsEmptyCol(i); }); } // Do similar work for the row tracks, with the same logic. Maybe> rowAdjust; uint32_t numEmptyRows = 0; if (aState.mRowFunctions.mHasRepeatAuto && gridStyle->mGridTemplateRows.GetRepeatAutoValue()->count.IsAutoFit()) { const auto& cellMap = mCellMap; rowAdjust = CalculateAdjustForAutoFitElements( &numEmptyRows, aState.mRowFunctions, mGridRowEnd + 1, [&cellMap](uint32_t i) -> bool { return cellMap.IsEmptyRow(i); }); } MOZ_ASSERT((numEmptyCols > 0) == colAdjust.isSome()); MOZ_ASSERT((numEmptyRows > 0) == rowAdjust.isSome()); // 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) { if (numEmptyCols) { item.AdjustForRemovedTracks(eLogicalAxisInline, *colAdjust); } if (numEmptyRows) { item.AdjustForRemovedTracks(eLogicalAxisBlock, *rowAdjust); } } for (auto& item : aState.mAbsPosItems) { if (numEmptyCols) { item.AdjustForRemovedTracks(eLogicalAxisInline, *colAdjust); } if (numEmptyRows) { item.AdjustForRemovedTracks(eLogicalAxisBlock, *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->ShouldGenerateComputedInfo()) { for (auto iter = mAreas->iter(); !iter.done(); iter.next()) { auto& areaInfo = iter.get().value(); // 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. StyleGridLine lineStartAndEnd; lineStartAndEnd.ident = areaInfo.name; LineRange columnLines = ResolveLineRange(lineStartAndEnd, lineStartAndEnd, colLineNameMap, eLogicalAxisInline, mExplicitGridColEnd, gridStyle); LineRange rowLines = ResolveLineRange(lineStartAndEnd, lineStartAndEnd, rowLineNameMap, eLogicalAxisBlock, mExplicitGridRowEnd, gridStyle); // Put the resolved line indices back into the area structure. areaInfo.columns.start = columnLines.mStart + mExplicitGridOffsetCol; areaInfo.columns.end = columnLines.mEnd + mExplicitGridOffsetCol; areaInfo.rows.start = rowLines.mStart + mExplicitGridOffsetRow; areaInfo.rows.end = rowLines.mEnd + mExplicitGridOffsetRow; } } } void nsGridContainerFrame::Tracks::Initialize( const TrackSizingFunctions& aFunctions, const NonNegativeLengthPercentageOrNormal& aGridGap, uint32_t aNumTracks, nscoord aContentBoxSize) { mSizes.SetLength(aNumTracks); PodZero(mSizes.Elements(), mSizes.Length()); for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) { auto& sz = mSizes[i]; mStateUnion |= sz.Initialize(aContentBoxSize, aFunctions.SizingFor(i)); if (mIsMasonry) { sz.mBase = aContentBoxSize; sz.mLimit = aContentBoxSize; } } mGridGap = nsLayoutUtils::ResolveGapToLength(aGridGap, aContentBoxSize); mContentBoxSize = aContentBoxSize; } /** * Reflow aChild in the given aAvailableSize. */ static nscoord MeasuringReflow(nsIFrame* aChild, const ReflowInput* aReflowInput, gfxContext* 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::InitFlag::DummyParentReflowInput); rs = dummyParentState.ptr(); } #ifdef DEBUG // This will suppress various ABSURD_SIZE warnings for this reflow. parent->SetProperty(nsContainerFrame::DebugReflowingWithInfiniteISize(), true); #endif auto wm = aChild->GetWritingMode(); ComputeSizeFlags csFlags = ComputeSizeFlag::IsGridMeasuringReflow; if (aAvailableSize.ISize(wm) == INFINITE_ISIZE_COORD) { csFlags += ComputeSizeFlag::ShrinkWrap; } if (aIMinSizeClamp != NS_MAXSIZE) { csFlags += ComputeSizeFlag::IClampMarginBoxMinSize; } if (aBMinSizeClamp != NS_MAXSIZE) { csFlags += ComputeSizeFlag::BClampMarginBoxMinSize; aChild->SetProperty(nsIFrame::BClampMarginBoxMinSizeProperty(), aBMinSizeClamp); } else { aChild->RemoveProperty(nsIFrame::BClampMarginBoxMinSizeProperty()); } ReflowInput childRI(pc, *rs, aChild, aAvailableSize, Some(aCBSize), {}, {}, csFlags); // FIXME (perf): It would be faster to do this only if the previous reflow of // the child was not a measuring reflow, and only if the child does some of // the things that are affected by ComputeSizeFlag::IsGridMeasuringReflow. childRI.SetBResize(true); // Not 100% sure this is needed, but be conservative for now: childRI.mFlags.mIsBResizeForPercentages = true; ReflowOutput childSize(childRI); nsReflowStatus childStatus; const nsIFrame::ReflowChildFlags flags = nsIFrame::ReflowChildFlags::NoMoveFrame | nsIFrame::ReflowChildFlags::NoSizeView | nsIFrame::ReflowChildFlags::NoDeleteNextInFlowChild; if (StaticPrefs::layout_css_grid_item_baxis_measurement_enabled()) { bool found; GridItemCachedBAxisMeasurement cachedMeasurement = aChild->GetProperty(GridItemCachedBAxisMeasurement::Prop(), &found); if (found && cachedMeasurement.IsValidFor(aChild, aCBSize)) { childSize.BSize(wm) = cachedMeasurement.BSize(); childSize.ISize(wm) = aChild->ISize(wm); nsContainerFrame::FinishReflowChild(aChild, pc, childSize, &childRI, wm, LogicalPoint(wm), nsSize(), flags); GRID_LOG( "[perf] MeasuringReflow accepted cached value=%d, child=%p, " "aCBSize.ISize=%d", cachedMeasurement.BSize(), aChild, aCBSize.ISize(aChild->GetWritingMode())); return cachedMeasurement.BSize(); } } parent->ReflowChild(aChild, pc, childSize, childRI, wm, LogicalPoint(wm), nsSize(), flags, childStatus); nsContainerFrame::FinishReflowChild(aChild, pc, childSize, &childRI, wm, LogicalPoint(wm), nsSize(), flags); #ifdef DEBUG parent->RemoveProperty(nsContainerFrame::DebugReflowingWithInfiniteISize()); #endif if (StaticPrefs::layout_css_grid_item_baxis_measurement_enabled()) { bool found; GridItemCachedBAxisMeasurement cachedMeasurement = aChild->GetProperty(GridItemCachedBAxisMeasurement::Prop(), &found); if (!found && GridItemCachedBAxisMeasurement::CanCacheMeasurement(aChild, aCBSize)) { GridItemCachedBAxisMeasurement cachedMeasurement(aChild, aCBSize, childSize.BSize(wm)); aChild->SetProperty(GridItemCachedBAxisMeasurement::Prop(), cachedMeasurement); GRID_LOG( "[perf] MeasuringReflow created new cached value=%d, child=%p, " "aCBSize.ISize=%d", cachedMeasurement.BSize(), aChild, aCBSize.ISize(aChild->GetWritingMode())); } else if (found) { if (GridItemCachedBAxisMeasurement::CanCacheMeasurement(aChild, aCBSize)) { cachedMeasurement.Update(aChild, aCBSize, childSize.BSize(wm)); GRID_LOG( "[perf] MeasuringReflow rejected but updated cached value=%d, " "child=%p, aCBSize.ISize=%d", cachedMeasurement.BSize(), aChild, aCBSize.ISize(aChild->GetWritingMode())); aChild->SetProperty(GridItemCachedBAxisMeasurement::Prop(), cachedMeasurement); } else { aChild->RemoveProperty(GridItemCachedBAxisMeasurement::Prop()); GRID_LOG( "[perf] MeasuringReflow rejected and removed cached value, " "child=%p", aChild); } } } return childSize.BSize(wm); } /** * Reflow aChild in the given aAvailableSize, using aNewContentBoxSize as its * computed size in aChildAxis. */ static void PostReflowStretchChild( nsIFrame* aChild, const ReflowInput& aReflowInput, const LogicalSize& aAvailableSize, const LogicalSize& aCBSize, LogicalAxis aChildAxis, const nscoord aNewContentBoxSize, nscoord aIMinSizeClamp = NS_MAXSIZE, nscoord aBMinSizeClamp = NS_MAXSIZE) { nsPresContext* pc = aChild->PresContext(); ComputeSizeFlags csFlags; if (aIMinSizeClamp != NS_MAXSIZE) { csFlags += ComputeSizeFlag::IClampMarginBoxMinSize; } if (aBMinSizeClamp != NS_MAXSIZE) { csFlags += ComputeSizeFlag::BClampMarginBoxMinSize; aChild->SetProperty(nsIFrame::BClampMarginBoxMinSizeProperty(), aBMinSizeClamp); } else { aChild->RemoveProperty(nsIFrame::BClampMarginBoxMinSizeProperty()); } ReflowInput ri(pc, aReflowInput, aChild, aAvailableSize, Some(aCBSize), {}, {}, csFlags); if (aChildAxis == eLogicalAxisBlock) { ri.SetComputedBSize(ri.ApplyMinMaxBSize(aNewContentBoxSize)); } else { ri.SetComputedISize(ri.ApplyMinMaxISize(aNewContentBoxSize)); } ReflowOutput childSize(ri); nsReflowStatus childStatus; const nsIFrame::ReflowChildFlags flags = nsIFrame::ReflowChildFlags::NoMoveFrame | nsIFrame::ReflowChildFlags::NoDeleteNextInFlowChild; auto wm = aChild->GetWritingMode(); nsContainerFrame* parent = aChild->GetParent(); parent->ReflowChild(aChild, pc, childSize, ri, wm, LogicalPoint(wm), nsSize(), flags, childStatus); nsContainerFrame::FinishReflowChild(aChild, pc, childSize, &ri, wm, LogicalPoint(wm), nsSize(), flags); } /** * Return the accumulated margin+border+padding in aAxis for aFrame (a subgrid) * and its ancestor subgrids. */ static LogicalMargin SubgridAccumulatedMarginBorderPadding( nsIFrame* aFrame, const Subgrid* aSubgrid, WritingMode aResultWM, LogicalAxis aAxis) { MOZ_ASSERT(aFrame->IsGridContainerFrame()); auto* subgridFrame = static_cast(aFrame); LogicalMargin result(aSubgrid->mMarginBorderPadding); auto* parent = subgridFrame->ParentGridContainerForSubgrid(); auto subgridCBWM = parent->GetWritingMode(); auto childRange = aSubgrid->mArea.LineRangeForAxis(aAxis); bool skipStartSide = false; bool skipEndSide = false; auto axis = aSubgrid->mIsOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis; // If aFrame's parent is also a subgrid, then add its MBP on the edges that // are adjacent (i.e. start or end in the same track), recursively. // ("parent" refers to the grid-frame we're currently adding MBP for, // and "grandParent" its parent, as we walk up the chain.) while (parent->IsSubgrid(axis)) { auto* parentSubgrid = parent->GetProperty(Subgrid::Prop()); auto* grandParent = parent->ParentGridContainerForSubgrid(); auto parentCBWM = grandParent->GetWritingMode(); if (parentCBWM.IsOrthogonalTo(subgridCBWM)) { axis = GetOrthogonalAxis(axis); } const auto& parentRange = parentSubgrid->mArea.LineRangeForAxis(axis); bool sameDir = parentCBWM.ParallelAxisStartsOnSameSide(axis, subgridCBWM); if (sameDir) { skipStartSide |= childRange.mStart != 0; skipEndSide |= childRange.mEnd != parentRange.Extent(); } else { skipEndSide |= childRange.mStart != 0; skipStartSide |= childRange.mEnd != parentRange.Extent(); } if (skipStartSide && skipEndSide) { break; } auto mbp = parentSubgrid->mMarginBorderPadding.ConvertTo(subgridCBWM, parentCBWM); if (skipStartSide) { mbp.Start(aAxis, subgridCBWM) = nscoord(0); } if (skipEndSide) { mbp.End(aAxis, subgridCBWM) = nscoord(0); } result += mbp; parent = grandParent; childRange = parentRange; } return result.ConvertTo(aResultWM, subgridCBWM); } /** * 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, gfxContext* aRC, WritingMode aCBWM, LogicalAxis aAxis, const Maybe& aPercentageBasis, IntrinsicISizeType aConstraint, nscoord aMinSizeClamp = NS_MAXSIZE, uint32_t aFlags = 0) { nsIFrame* child = aGridItem.mFrame; nscoord extraMargin = 0; nsGridContainerFrame::Subgrid* subgrid = nullptr; if (child->GetParent() != aState.mFrame) { // |child| is a subgrid descendant, so it contributes its subgrids' // margin+border+padding for any edge tracks that it spans. auto* subgridFrame = child->GetParent(); subgrid = subgridFrame->GetProperty(Subgrid::Prop()); const auto itemEdgeBits = aGridItem.mState[aAxis] & ItemState::eEdgeBits; if (itemEdgeBits) { LogicalMargin mbp = SubgridAccumulatedMarginBorderPadding( subgridFrame, subgrid, aCBWM, aAxis); if (itemEdgeBits & ItemState::eStartEdge) { extraMargin += mbp.Start(aAxis, aCBWM); } if (itemEdgeBits & ItemState::eEndEdge) { extraMargin += mbp.End(aAxis, aCBWM); } } // It also contributes (half of) the subgrid's gap on its edges (if any) // subtracted by the non-subgrid ancestor grid container's gap. // Note that this can also be negative since it's considered a margin. if (itemEdgeBits != ItemState::eEdgeBits) { auto subgridAxis = aCBWM.IsOrthogonalTo(subgridFrame->GetWritingMode()) ? GetOrthogonalAxis(aAxis) : aAxis; auto& gapStyle = subgridAxis == eLogicalAxisBlock ? subgridFrame->StylePosition()->mRowGap : subgridFrame->StylePosition()->mColumnGap; if (!gapStyle.IsNormal()) { auto subgridExtent = subgridAxis == eLogicalAxisBlock ? subgrid->mGridRowEnd : subgrid->mGridColEnd; if (subgridExtent > 1) { nscoord subgridGap = nsLayoutUtils::ResolveGapToLength(gapStyle, NS_UNCONSTRAINEDSIZE); auto& tracks = aAxis == eLogicalAxisBlock ? aState.mRows : aState.mCols; auto gapDelta = subgridGap - tracks.mGridGap; if (!itemEdgeBits) { extraMargin += gapDelta; } else { extraMargin += gapDelta / 2; } } } } } PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis)); nscoord size = nsLayoutUtils::IntrinsicForAxis( axis, aRC, child, aConstraint, aPercentageBasis, aFlags | nsLayoutUtils::BAIL_IF_REFLOW_NEEDED, aMinSizeClamp); auto childWM = child->GetWritingMode(); const bool isOrthogonal = childWM.IsOrthogonalTo(aCBWM); auto childAxis = isOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis; if (size == NS_INTRINSIC_ISIZE_UNKNOWN && childAxis == eLogicalAxisBlock) { // 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; // The next two variables are MinSizeClamp values in the child's axes. nscoord iMinSizeClamp = NS_MAXSIZE; nscoord bMinSizeClamp = NS_MAXSIZE; LogicalSize cbSize(childWM, 0, NS_UNCONSTRAINEDSIZE); // Below, we try to resolve the child's grid-area size in its inline-axis // to use as the CB/Available size in the MeasuringReflow that follows. if (child->GetParent() != aState.mFrame) { // This item is a child of a subgrid descendant. auto* subgridFrame = static_cast(child->GetParent()); MOZ_ASSERT(subgridFrame->IsGridContainerFrame()); auto* uts = subgridFrame->GetProperty(UsedTrackSizes::Prop()); if (!uts) { uts = new UsedTrackSizes(); subgridFrame->SetProperty(UsedTrackSizes::Prop(), uts); } // The grid-item's inline-axis as expressed in the subgrid's WM. auto subgridAxis = childWM.IsOrthogonalTo(subgridFrame->GetWritingMode()) ? eLogicalAxisBlock : eLogicalAxisInline; uts->ResolveTrackSizesForAxis(subgridFrame, subgridAxis, *aRC); if (uts->mCanResolveLineRangeSize[subgridAxis]) { auto* subgrid = subgridFrame->GetProperty(nsGridContainerFrame::Subgrid::Prop()); const GridItemInfo* originalItem = nullptr; for (const auto& item : subgrid->mGridItems) { if (item.mFrame == child) { originalItem = &item; break; } } MOZ_ASSERT(originalItem, "huh?"); const auto& range = originalItem->mArea.LineRangeForAxis(subgridAxis); nscoord pos, sz; range.ToPositionAndLength(uts->mSizes[subgridAxis], &pos, &sz); if (childWM.IsOrthogonalTo(subgridFrame->GetWritingMode())) { 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; } } } } else 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); if (MOZ_UNLIKELY(child->IsXULBoxFrame())) { auto* pc = child->PresContext(); // For XUL-in-CSS-Grid (e.g. in our frontend code), we defer to XUL's // GetPrefSize() function (which reports an answer in both axes), instead // of actually reflowing. It's important to avoid the "measuring + final" // two-pass reflow for XUL, because some XUL layout code may incorrectly // optimize away the second reflow in cases where it's really needed. // XXXdholbert We'll remove this special case in bug 1600542. ReflowInput childRI(pc, *aState.mReflowInput, child, availableSize, Some(cbSize)); nsBoxLayoutState state(pc, &aState.mRenderingContext, &childRI, childRI.mReflowDepth); nsSize physicalPrefSize = child->GetXULPrefSize(state); auto prefSize = LogicalSize(childWM, physicalPrefSize); size = prefSize.BSize(childWM); // XXXdholbert This won't have percentage margins resolved. // Hopefully we can just avoid those for XUL-content-in-css-grid? size += childRI.ComputedLogicalMargin(childWM).BStartEnd(childWM); } else { size = ::MeasuringReflow(child, aState.mReflowInput, aRC, availableSize, cbSize, iMinSizeClamp, bMinSizeClamp); size += child->GetLogicalUsedMargin(childWM).BStartEnd(childWM); } nscoord overflow = size - aMinSizeClamp; if (MOZ_UNLIKELY(overflow > 0)) { nscoord contentSize = child->ContentSize(childWM).BSize(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]; size += extraMargin; return std::max(size, 0); } struct CachedIntrinsicSizes { Maybe mMinSize; Maybe mMinContentContribution; Maybe mMaxContentContribution; // The item's percentage basis for intrinsic sizing purposes. Maybe mPercentageBasis; // "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, gfxContext* aRC, WritingMode aCBWM, LogicalAxis aAxis, CachedIntrinsicSizes* aCache) { if (aCache->mMinContentContribution.isSome()) { return aCache->mMinContentContribution.value(); } if (aCache->mPercentageBasis.isNothing()) { aCache->mPercentageBasis.emplace( aState.PercentageBasisFor(aAxis, aGridItem)); } nscoord s = ContentContribution( aGridItem, aState, aRC, aCBWM, aAxis, aCache->mPercentageBasis, IntrinsicISizeType::MinISize, aCache->mMinSizeClamp); aCache->mMinContentContribution.emplace(s); return s; } static nscoord MaxContentContribution(const GridItemInfo& aGridItem, const GridReflowInput& aState, gfxContext* aRC, WritingMode aCBWM, LogicalAxis aAxis, CachedIntrinsicSizes* aCache) { if (aCache->mMaxContentContribution.isSome()) { return aCache->mMaxContentContribution.value(); } if (aCache->mPercentageBasis.isNothing()) { aCache->mPercentageBasis.emplace( aState.PercentageBasisFor(aAxis, aGridItem)); } nscoord s = ContentContribution( aGridItem, aState, aRC, aCBWM, aAxis, aCache->mPercentageBasis, IntrinsicISizeType::PrefISize, 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-contribution static nscoord MinSize(const GridItemInfo& aGridItem, const GridReflowInput& aState, gfxContext* 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(); StyleSize sizeStyle = axis == eAxisHorizontal ? stylePos->mWidth : stylePos->mHeight; auto ourInlineAxis = child->GetWritingMode().PhysicalAxis(eLogicalAxisInline); // max-content and min-content should behave as initial value in block axis. // FIXME: Bug 567039: moz-fit-content and -moz-available are not supported // for block size dimension on sizing properties (e.g. height), so we // treat it as `auto`. if (axis != ourInlineAxis && sizeStyle.BehavesLikeInitialValueOnBlockAxis()) { sizeStyle = StyleSize::Auto(); } if (!sizeStyle.IsAuto() && !sizeStyle.HasPercent()) { nscoord s = MinContentContribution(aGridItem, aState, aRC, aCBWM, aAxis, aCache); aCache->mMinSize.emplace(s); return s; } if (aCache->mPercentageBasis.isNothing()) { aCache->mPercentageBasis.emplace( aState.PercentageBasisFor(aAxis, aGridItem)); } // 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, IntrinsicISizeType::MinISize, *aCache->mPercentageBasis); const StyleSize& style = axis == eAxisHorizontal ? stylePos->mMinWidth : stylePos->mMinHeight; // max-content and min-content should behave as initial value in block axis. // FIXME: Bug 567039: moz-fit-content and -moz-available are not supported // for block size dimension on sizing properties (e.g. height), so we // treat it as `auto`. const bool inInlineAxis = axis == ourInlineAxis; const bool isAuto = style.IsAuto() || (!inInlineAxis && style.BehavesLikeInitialValueOnBlockAxis()); if ((inInlineAxis && nsIFrame::ToExtremumLength(style)) || (isAuto && child->StyleDisplay()->mOverflowX == StyleOverflow::Visible)) { // Now calculate the "content size" part and return whichever is smaller. MOZ_ASSERT(isAuto || sz == NS_UNCONSTRAINEDSIZE); sz = std::min(sz, ContentContribution(aGridItem, aState, aRC, aCBWM, aAxis, aCache->mPercentageBasis, IntrinsicISizeType::MinISize, 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::MinContent) { nscoord freeSpace = aContentBoxSize; if (freeSpace != NS_UNCONSTRAINEDSIZE) { freeSpace -= SumOfGridGaps(); } DistributeFreeSpace(freeSpace); StretchFlexibleTracks(aState, aGridItems, aFunctions, freeSpace); } } TrackSize::StateBits nsGridContainerFrame::Tracks::StateBitsForRange( const LineRange& aRange) const { MOZ_ASSERT(!aRange.IsAuto(), "must have a definite range"); TrackSize::StateBits state = TrackSize::StateBits{0}; for (auto i : aRange.Range()) { state |= mSizes[i].mState; } return state; } static void AddSubgridContribution(TrackSize& aSize, nscoord aMarginBorderPadding) { if (aSize.mState & TrackSize::eIntrinsicMinSizing) { aSize.mBase = std::max(aSize.mBase, aMarginBorderPadding); aSize.mLimit = std::max(aSize.mLimit, aSize.mBase); } // XXX maybe eFlexMaxSizing too? // (once we implement https://github.com/w3c/csswg-drafts/issues/2177) if (aSize.mState & (TrackSize::eIntrinsicMaxSizing | TrackSize::eFitContent)) { aSize.mLimit = std::max(aSize.mLimit, aMarginBorderPadding); } } bool nsGridContainerFrame::Tracks::ResolveIntrinsicSizeForNonSpanningItems( GridReflowInput& aState, const TrackSizingFunctions& aFunctions, nscoord aPercentageBasis, SizingConstraint aConstraint, const LineRange& aRange, const GridItemInfo& aGridItem) { gfxContext* rc = &aState.mRenderingContext; WritingMode wm = aState.mWM; CachedIntrinsicSizes cache; TrackSize& sz = mSizes[aRange.mStart]; // min sizing if (sz.mState & TrackSize::eAutoMinSizing) { nscoord s; // Check if we need to apply "Automatic Minimum Size" and cache it. if (aGridItem.ShouldApplyAutoMinSize(wm, mAxis, aPercentageBasis)) { aGridItem.mState[mAxis] |= ItemState::eApplyAutoMinSize; // Clamp it if it's spanning a definite track max-sizing function. if (TrackSize::IsDefiniteMaxSizing(sz.mState)) { cache.mMinSizeClamp = aFunctions.MaxSizingFor(aRange.mStart) .AsBreadth() .Resolve(aPercentageBasis); aGridItem.mState[mAxis] |= ItemState::eClampMarginBoxMinSize; } if (aConstraint != SizingConstraint::MaxContent) { s = MinContentContribution(aGridItem, aState, rc, wm, mAxis, &cache); } else { s = MaxContentContribution(aGridItem, aState, rc, wm, mAxis, &cache); } } else { 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. nscoord fitContentClamp = aFunctions.SizingFor(aRange.mStart) .AsFitContent() .AsBreadth() .Resolve(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::First) { mBaseline[aBaselineGroup] = maxBaseline; } if (currentTrack == lastTrack && aBaselineGroup == BaselineSharingGroup::Last) { 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) { MOZ_ASSERT(!mIsMasonry); if (aState.mFrame->IsSubgrid(mAxis)) { // A grid container's subgridded axis doesn't have a baseline. return; } nsTArray firstBaselineItems; nsTArray lastBaselineItems; WritingMode wm = aState.mWM; ComputedStyle* containerSC = aState.mFrame->Style(); for (GridItemInfo& gridItem : aGridItems) { if (gridItem.IsSubgrid(mAxis)) { // A subgrid itself is never baseline-aligned. continue; } nsIFrame* child = gridItem.mFrame; 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)._0 : child->StylePosition()->UsedAlignSelf(containerSC)._0; selfAlignment &= ~StyleAlignFlags::FLAG_BITS; if (selfAlignment == StyleAlignFlags::BASELINE) { state |= ItemState::eFirstBaseline | ItemState::eSelfBaseline; const GridArea& area = gridItem.mArea; baselineTrack = isInlineAxis ? area.mCols.mStart : area.mRows.mStart; } else if (selfAlignment == StyleAlignFlags::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.primary; alignContent &= ~StyleAlignFlags::FLAG_BITS; if (alignContent == StyleAlignFlags::BASELINE || alignContent == StyleAlignFlags::LAST_BASELINE) { const auto selfAlignEdge = alignContent == StyleAlignFlags::BASELINE ? StyleAlignFlags::SELF_START : StyleAlignFlags::SELF_END; bool validCombo = selfAlignment == StyleAlignFlags::NORMAL || selfAlignment == StyleAlignFlags::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); if (selfAlignment == StyleAlignFlags::LEFT) { selfAlignment = !isInlineAxis || wm.IsBidiLTR() ? StyleAlignFlags::START : StyleAlignFlags::END; } else if (selfAlignment == StyleAlignFlags::RIGHT) { selfAlignment = isInlineAxis && wm.IsBidiLTR() ? StyleAlignFlags::END : StyleAlignFlags::START; } if (selfAlignment == StyleAlignFlags::START || selfAlignment == StyleAlignFlags::FLEX_START) { validCombo = sameSide == (alignContent == StyleAlignFlags::BASELINE); } else if (selfAlignment == StyleAlignFlags::END || selfAlignment == StyleAlignFlags::FLEX_END) { validCombo = sameSide == (alignContent == StyleAlignFlags::LAST_BASELINE); } } if (validCombo) { const GridArea& area = gridItem.mArea; if (alignContent == StyleAlignFlags::BASELINE) { state |= ItemState::eFirstBaseline | ItemState::eContentBaseline; baselineTrack = isInlineAxis ? area.mCols.mStart : area.mRows.mStart; } else if (alignContent == StyleAlignFlags::LAST_BASELINE) { state |= ItemState::eLastBaseline | ItemState::eContentBaseline; baselineTrack = (isInlineAxis ? area.mCols.mEnd : area.mRows.mEnd) - 1; } } } } if (state & ItemState::eIsBaselineAligned) { // XXXmats if |child| is a descendant of a subgrid then the metrics // below needs to account for the accumulated MPB somehow... // 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 an unconstrined-bsize CB: LogicalSize cbSize(childWM, 0, NS_UNCONSTRAINEDSIZE); ::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::First, &baseline); } else { grid->GetIBaseline(BaselineSharingGroup::First, &baseline); } } if (grid || nsLayoutUtils::GetFirstLineBaseline(wm, child, &baseline)) { NS_ASSERTION(baseline != NS_INTRINSIC_ISIZE_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::Last, &baseline); } else { grid->GetIBaseline(BaselineSharingGroup::Last, &baseline); } } if (grid || nsLayoutUtils::GetLastLineBaseline(wm, child, &baseline)) { NS_ASSERTION(baseline != NS_INTRINSIC_ISIZE_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})); state |= ItemState::eEndSideBaseline; } 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::First] = StyleAlignFlags::START; mBaselineSubtreeAlign[BaselineSharingGroup::Last] = StyleAlignFlags::END; CalculateItemBaselines(firstBaselineItems, BaselineSharingGroup::First); CalculateItemBaselines(lastBaselineItems, BaselineSharingGroup::Last); } // TODO: we store the wrong baseline group offset in some cases (bug 1632200) void nsGridContainerFrame::Tracks::InitializeItemBaselinesInMasonryAxis( GridReflowInput& aState, nsTArray& aGridItems, BaselineAlignmentSet aSet, const nsSize& aContainerSize, nsTArray& aTrackSizes, nsTArray& aFirstBaselineItems, nsTArray& aLastBaselineItems) { MOZ_ASSERT(mIsMasonry); WritingMode wm = aState.mWM; ComputedStyle* containerSC = aState.mFrame->Style(); for (GridItemInfo& gridItem : aGridItems) { if (gridItem.IsSubgrid(mAxis)) { // A subgrid itself is never baseline-aligned. continue; } const auto& area = gridItem.mArea; if (aSet.mItemSet == BaselineAlignmentSet::LastItems) { // NOTE: eIsLastItemInMasonryTrack is set also if the item is the ONLY // item in its track; the eIsBaselineAligned check excludes it though // since it participates in the start baseline groups in that case. // // XXX what if it's the only item in THAT baseline group? // XXX should it participate in the last-item group instead then // if there are more baseline-aligned items there? if (!(gridItem.mState[mAxis] & ItemState::eIsLastItemInMasonryTrack) || (gridItem.mState[mAxis] & ItemState::eIsBaselineAligned)) { continue; } } else { if (area.LineRangeForAxis(mAxis).mStart > 0 || (gridItem.mState[mAxis] & ItemState::eIsBaselineAligned)) { continue; } } auto trackAlign = aState.mGridStyle ->UsedTracksAlignment( mAxis, area.LineRangeForAxis(GetOrthogonalAxis(mAxis)).mStart) .primary; if (!aSet.MatchTrackAlignment(trackAlign)) { continue; } nsIFrame* child = gridItem.mFrame; 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) { const auto* pos = child->StylePosition(); // [align|justify]-self:[last ]baseline. auto selfAlignment = pos->UsedSelfAlignment(mAxis, containerSC); selfAlignment &= ~StyleAlignFlags::FLAG_BITS; if (selfAlignment == StyleAlignFlags::BASELINE) { state |= ItemState::eFirstBaseline | ItemState::eSelfBaseline; baselineTrack = isInlineAxis ? area.mCols.mStart : area.mRows.mStart; } else if (selfAlignment == StyleAlignFlags::LAST_BASELINE) { state |= ItemState::eLastBaseline | ItemState::eSelfBaseline; baselineTrack = (isInlineAxis ? area.mCols.mEnd : area.mRows.mEnd) - 1; } else { // [align|justify]-content:[last ]baseline. auto childAxis = isOrthogonal ? GetOrthogonalAxis(mAxis) : mAxis; auto alignContent = pos->UsedContentAlignment(childAxis).primary; alignContent &= ~StyleAlignFlags::FLAG_BITS; if (alignContent == StyleAlignFlags::BASELINE) { state |= ItemState::eFirstBaseline | ItemState::eContentBaseline; baselineTrack = isInlineAxis ? area.mCols.mStart : area.mRows.mStart; } else if (alignContent == StyleAlignFlags::LAST_BASELINE) { state |= ItemState::eLastBaseline | ItemState::eContentBaseline; baselineTrack = (isInlineAxis ? area.mCols.mEnd : area.mRows.mEnd) - 1; } } } if (state & ItemState::eIsBaselineAligned) { // XXXmats if |child| is a descendant of a subgrid then the metrics // below needs to account for the accumulated MPB somehow... nscoord baseline; nsGridContainerFrame* grid = do_QueryFrame(child); if (state & ItemState::eFirstBaseline) { if (grid) { if (isOrthogonal == isInlineAxis) { grid->GetBBaseline(BaselineSharingGroup::First, &baseline); } else { grid->GetIBaseline(BaselineSharingGroup::First, &baseline); } } if (grid || nsLayoutUtils::GetFirstLineBaseline(wm, child, &baseline)) { NS_ASSERTION(baseline != NS_INTRINSIC_ISIZE_UNKNOWN, "about to use an unknown baseline"); auto frameSize = isInlineAxis ? child->ISize(wm) : child->BSize(wm); nscoord alignSize; LogicalPoint pos = child->GetLogicalNormalPosition(wm, aContainerSize); baseline += pos.Pos(mAxis, wm); if (aSet.mTrackAlignmentSet == BaselineAlignmentSet::EndStretch) { state |= ItemState::eEndSideBaseline; // Convert to distance from the track end. baseline = aTrackSizes[gridItem.mArea .LineRangeForAxis(GetOrthogonalAxis(mAxis)) .mStart] - baseline; } alignSize = frameSize; aFirstBaselineItems.AppendElement(ItemBaselineData( {baselineTrack, baseline, alignSize, &gridItem})); } else { state &= ~ItemState::eAllBaselineBits; } } else { if (grid) { if (isOrthogonal == isInlineAxis) { grid->GetBBaseline(BaselineSharingGroup::Last, &baseline); } else { grid->GetIBaseline(BaselineSharingGroup::Last, &baseline); } } if (grid || nsLayoutUtils::GetLastLineBaseline(wm, child, &baseline)) { NS_ASSERTION(baseline != NS_INTRINSIC_ISIZE_UNKNOWN, "about to use an unknown baseline"); auto frameSize = isInlineAxis ? child->ISize(wm) : child->BSize(wm); auto m = child->GetLogicalUsedMargin(wm); if (!grid && aSet.mTrackAlignmentSet == BaselineAlignmentSet::EndStretch) { // Convert to distance from border-box end. state |= ItemState::eEndSideBaseline; LogicalPoint pos = child->GetLogicalNormalPosition(wm, aContainerSize); baseline += pos.Pos(mAxis, wm); baseline = aTrackSizes[gridItem.mArea .LineRangeForAxis(GetOrthogonalAxis(mAxis)) .mStart] - baseline; } else if (grid && aSet.mTrackAlignmentSet == BaselineAlignmentSet::StartStretch) { // Convert to distance from border-box start. baseline = frameSize - baseline; } if (aSet.mItemSet == BaselineAlignmentSet::LastItems && aSet.mTrackAlignmentSet == BaselineAlignmentSet::StartStretch) { LogicalPoint pos = child->GetLogicalNormalPosition(wm, aContainerSize); baseline += pos.B(wm); } if (aSet.mTrackAlignmentSet == BaselineAlignmentSet::EndStretch) { state |= ItemState::eEndSideBaseline; } auto descent = baseline + ((state & ItemState::eEndSideBaseline) ? (isInlineAxis ? m.IEnd(wm) : m.BEnd(wm)) : (isInlineAxis ? m.IStart(wm) : m.BStart(wm))); auto alignSize = frameSize + (isInlineAxis ? m.IStartEnd(wm) : m.BStartEnd(wm)); aLastBaselineItems.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); } CalculateItemBaselines(aFirstBaselineItems, BaselineSharingGroup::First); CalculateItemBaselines(aLastBaselineItems, BaselineSharingGroup::Last); // TODO: make sure the mBaselines (i.e. the baselines we export from // the grid container) are offset from the correct container edge. // Also, which of the baselines do we pick to export exactly? MOZ_ASSERT(aFirstBaselineItems.Length() != 1 || aFirstBaselineItems[0].mGridItem->mBaselineOffset[mAxis] == 0, "a baseline group that contains only one item should not " "produce a non-zero item baseline offset"); MOZ_ASSERT(aLastBaselineItems.Length() != 1 || aLastBaselineItems[0].mGridItem->mBaselineOffset[mAxis] == 0, "a baseline group that contains only one item should not " "produce a non-zero item baseline offset"); } void nsGridContainerFrame::Tracks::AlignBaselineSubtree( const GridItemInfo& aGridItem) const { if (mIsMasonry) { return; } 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::First : BaselineSharingGroup::Last; nscoord delta = sz.mBase - sz.mBaselineSubtreeSize[baselineGroup]; const auto subtreeAlign = mBaselineSubtreeAlign[baselineGroup]; if (subtreeAlign == StyleAlignFlags::START) { if (state & ItemState::eLastBaseline) { aGridItem.mBaselineOffset[mAxis] += delta; } } else if (subtreeAlign == StyleAlignFlags::END) { if (isFirstBaseline) { aGridItem.mBaselineOffset[mAxis] += delta; } } else if (subtreeAlign == StyleAlignFlags::CENTER) { aGridItem.mBaselineOffset[mAxis] += delta / 2; } else { MOZ_ASSERT_UNREACHABLE("unexpected baseline subtree alignment"); } } template bool nsGridContainerFrame::Tracks::GrowSizeForSpanningItems( nsTArray::iterator aIter, nsTArray::iterator aIterEnd, nsTArray& aTracks, nsTArray& aPlan, nsTArray& aItemPlan, TrackSize::StateBits aSelector, const FitContentClamper& aFitContentClamper, bool aNeedInfinitelyGrowableFlag) { constexpr bool isMaxSizingPhase = phase == TrackSizingPhase::IntrinsicMaximums || phase == TrackSizingPhase::MaxContentMaximums; bool needToUpdateSizes = false; InitializePlan(aPlan); for (; aIter != aIterEnd; ++aIter) { const SpanningItemData& item = *aIter; if (!(item.mState & aSelector)) { continue; } if (isMaxSizingPhase) { for (auto i : item.mLineRange.Range()) { aPlan[i].mState |= TrackSize::eModified; } } nscoord space = item.SizeContributionForPhase(); if (space <= 0) { continue; } aTracks.ClearAndRetainStorage(); space = CollectGrowable(space, item.mLineRange, aSelector, aTracks); if (space > 0) { DistributeToTrackSizes(space, aPlan, aItemPlan, aTracks, aSelector, aFitContentClamper); needToUpdateSizes = true; } } if (isMaxSizingPhase) { needToUpdateSizes = true; } if (needToUpdateSizes) { CopyPlanToSize(aPlan, aNeedInfinitelyGrowableFlag); } return needToUpdateSizes; } void nsGridContainerFrame::Tracks::ResolveIntrinsicSize( GridReflowInput& aState, nsTArray& aGridItems, const TrackSizingFunctions& aFunctions, LineRange GridArea::*aRange, nscoord aPercentageBasis, SizingConstraint aConstraint) { // Resolve Intrinsic Track Sizes // https://w3c.github.io/csswg-drafts/css-grid-1/#algo-content // We're also setting eIsFlexing on the item state here to speed up // FindUsedFlexFraction later. gfxContext* rc = &aState.mRenderingContext; WritingMode wm = aState.mWM; // Data we accumulate when grouping similar sized spans together. struct PerSpanData { uint32_t mItemCountWithSameSpan = 0; TrackSize::StateBits mStateBits = TrackSize::StateBits{0}; }; AutoTArray perSpanData; nsTArray spanningItems; uint32_t maxSpan = 0; // max span of items in `spanningItems`. // Setup track selector for step 3.2: const auto contentBasedMinSelector = aConstraint == SizingConstraint::MinContent ? TrackSize::eIntrinsicMinSizing : TrackSize::eMinOrMaxContentMinSizing; // Setup track selector for step 3.3: const auto maxContentMinSelector = aConstraint == SizingConstraint::MaxContent ? (TrackSize::eMaxContentMinSizing | TrackSize::eAutoMinSizing) : TrackSize::eMaxContentMinSizing; const auto orthogonalAxis = GetOrthogonalAxis(mAxis); const bool isMasonryInOtherAxis = aState.mFrame->IsMasonry(orthogonalAxis); for (auto& gridItem : aGridItems) { MOZ_ASSERT(!(gridItem.mState[mAxis] & (ItemState::eApplyAutoMinSize | ItemState::eIsFlexing | ItemState::eClampMarginBoxMinSize)), "Why are any of these bits set already?"); const GridArea& area = gridItem.mArea; const LineRange& lineRange = area.*aRange; // If we have masonry layout in the other axis then skip this item unless // it's in the first masonry track, or has definite placement in this axis, // or spans all tracks in this axis (since that implies it will be placed // at line 1 regardless of layout results of other items). if (isMasonryInOtherAxis && gridItem.mArea.LineRangeForAxis(orthogonalAxis).mStart != 0 && (gridItem.mState[mAxis] & ItemState::eAutoPlacement) && gridItem.mArea.LineRangeForAxis(mAxis).Extent() != mSizes.Length()) { continue; } uint32_t span = lineRange.Extent(); if (MOZ_UNLIKELY(gridItem.mState[mAxis] & ItemState::eIsSubgrid)) { auto itemWM = gridItem.mFrame->GetWritingMode(); auto percentageBasis = aState.PercentageBasisFor(mAxis, gridItem); if (percentageBasis.ISize(itemWM) == NS_UNCONSTRAINEDSIZE) { percentageBasis.ISize(itemWM) = nscoord(0); } if (percentageBasis.BSize(itemWM) == NS_UNCONSTRAINEDSIZE) { percentageBasis.BSize(itemWM) = nscoord(0); } auto* subgrid = SubgridComputeMarginBorderPadding(gridItem, percentageBasis); LogicalMargin mbp = SubgridAccumulatedMarginBorderPadding( gridItem.SubgridFrame(), subgrid, wm, mAxis); if (span == 1) { AddSubgridContribution(mSizes[lineRange.mStart], mbp.StartEnd(mAxis, wm)); } else { AddSubgridContribution(mSizes[lineRange.mStart], mbp.Start(mAxis, wm)); AddSubgridContribution(mSizes[lineRange.mEnd - 1], mbp.End(mAxis, wm)); } continue; } if (span == 1) { // Step 2. Size tracks to fit non-spanning items. if (ResolveIntrinsicSizeForNonSpanningItems(aState, aFunctions, aPercentageBasis, aConstraint, lineRange, gridItem)) { gridItem.mState[mAxis] |= ItemState::eIsFlexing; } } else { TrackSize::StateBits state = StateBitsForRange(lineRange); // Check if we need to apply "Automatic Minimum Size" and cache it. if ((state & TrackSize::eAutoMinSizing) && !(state & TrackSize::eFlexMaxSizing) && gridItem.ShouldApplyAutoMinSize(wm, mAxis, aPercentageBasis)) { gridItem.mState[mAxis] |= ItemState::eApplyAutoMinSize; } if (state & TrackSize::eFlexMaxSizing) { gridItem.mState[mAxis] |= ItemState::eIsFlexing; } else if (state & (TrackSize::eIntrinsicMinSizing | TrackSize::eIntrinsicMaxSizing)) { // Collect data for Step 3. maxSpan = std::max(maxSpan, span); if (span >= perSpanData.Length()) { perSpanData.SetLength(2 * span); } perSpanData[span].mItemCountWithSameSpan++; perSpanData[span].mStateBits |= state; CachedIntrinsicSizes cache; // Calculate data for "Automatic Minimum Size" clamping, if needed. if (TrackSize::IsDefiniteMaxSizing(state) && (gridItem.mState[mAxis] & ItemState::eApplyAutoMinSize)) { nscoord minSizeClamp = 0; for (auto i : lineRange.Range()) { minSizeClamp += aFunctions.MaxSizingFor(i).AsBreadth().Resolve( 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 3.1 minSize = MinSize(gridItem, aState, rc, wm, mAxis, &cache); } nscoord minContent = 0; if (state & (contentBasedMinSelector | // for 3.2 TrackSize::eIntrinsicMaxSizing)) { // for 3.5 minContent = MinContentContribution(gridItem, aState, rc, wm, mAxis, &cache); } nscoord maxContent = 0; if (state & (maxContentMinSelector | // for 3.3 TrackSize::eAutoOrMaxContentMaxSizing)) { // for 3.6 maxContent = MaxContentContribution(gridItem, aState, rc, wm, mAxis, &cache); } spanningItems.AppendElement( SpanningItemData({span, state, lineRange, minSize, minContent, maxContent, gridItem.mFrame})); } } MOZ_ASSERT(!(gridItem.mState[mAxis] & ItemState::eClampMarginBoxMinSize) || (gridItem.mState[mAxis] & ItemState::eApplyAutoMinSize), "clamping only applies to Automatic Minimum Size"); } // Step 3 - Increase sizes to accommodate spanning items crossing // content-sized tracks. if (maxSpan) { 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; }; // Sort the collected items on span length, shortest first. There's no need // for a stable sort here since the sizing isn't order dependent within // a group of items with the same span length. std::sort(spanningItems.begin(), spanningItems.end(), SpanningItemData::IsSpanLessThan); nsTArray tracks(maxSpan); nsTArray plan(mSizes.Length()); plan.SetLength(mSizes.Length()); nsTArray itemPlan(mSizes.Length()); itemPlan.SetLength(mSizes.Length()); // Start / end iterator for items of the same span length: auto spanGroupStart = spanningItems.begin(); auto spanGroupEnd = spanGroupStart; const auto end = spanningItems.end(); for (; spanGroupStart != end; spanGroupStart = spanGroupEnd) { const uint32_t span = spanGroupStart->mSpan; spanGroupEnd = spanGroupStart + perSpanData[span].mItemCountWithSameSpan; TrackSize::StateBits stateBitsForSpan = perSpanData[span].mStateBits; bool updatedBase = false; // Did we update any mBase in step 3.1..3.3? TrackSize::StateBits selector(TrackSize::eIntrinsicMinSizing); if (stateBitsForSpan & selector) { // Step 3.1 MinSize to intrinsic min-sizing. updatedBase = GrowSizeForSpanningItems( spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector); } selector = contentBasedMinSelector; if (stateBitsForSpan & selector) { // Step 3.2 MinContentContribution to min-/max-content (and 'auto' when // sizing under a min-content constraint) min-sizing. updatedBase |= GrowSizeForSpanningItems( spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector); } selector = maxContentMinSelector; if (stateBitsForSpan & selector) { // Step 3.3 MaxContentContribution to max-content (and 'auto' when // sizing under a max-content constraint) min-sizing. updatedBase |= GrowSizeForSpanningItems( spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector); } if (updatedBase) { // Step 3.4 for (TrackSize& sz : mSizes) { if (sz.mBase > sz.mLimit) { sz.mLimit = sz.mBase; } } } selector = TrackSize::eIntrinsicMaxSizing; if (stateBitsForSpan & selector) { const bool willRunStep3_6 = stateBitsForSpan & TrackSize::eAutoOrMaxContentMaxSizing; // Step 3.5 MinContentContribution to intrinsic max-sizing. GrowSizeForSpanningItems( spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector, fitContentClamper, willRunStep3_6); if (willRunStep3_6) { // Step 2.6 MaxContentContribution to max-content max-sizing. selector = TrackSize::eAutoOrMaxContentMaxSizing; GrowSizeForSpanningItems( spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector, fitContentClamper); } } } } // Step 5 - If any track still has an infinite growth limit, set its growth // limit to its base size. 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 (auto i : aRange.Range()) { const TrackSize& sz = mSizes[i]; if (sz.mState & TrackSize::eFlexMaxSizing) { flexFactorSum += aFunctions.MaxSizingFor(i).AsFr(); } else { leftOverSpace -= sz.mBase; if (leftOverSpace <= 0) { return 0.0f; } } } bool restart; float hypotheticalFrSize; nsTArray flexTracks(aFlexTracks.Clone()); 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).AsFr(); 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).AsFr(); float possiblyDividedBaseSize = (flexFactor > 1.0f) ? mSizes[track].mBase / flexFactor : mSizes[track].mBase; fr = std::max(fr, possiblyDividedBaseSize); } WritingMode wm = aState.mWM; gfxContext* rc = &aState.mRenderingContext; // ... 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 (const GridItemInfo& item : aGridItems) { if (item.mState[mAxis] & ItemState::eIsFlexing) { // XXX optimize: bug 1194446 auto pb = Some(aState.PercentageBasisFor(mAxis, item)); nscoord spaceToFill = ContentContribution(item, aState, rc, wm, mAxis, pb, IntrinsicISizeType::PrefISize); const LineRange& range = mAxis == eLogicalAxisInline ? item.mArea.mCols : item.mArea.mRows; MOZ_ASSERT(range.Extent() >= 1); const auto spannedGaps = range.Extent() - 1; if (spannedGaps > 0) { spaceToFill -= mGridGap * spannedGaps; } if (spaceToFill <= 0) { continue; } // ... and all its spanned tracks as input. nsTArray itemFlexTracks; for (auto i : range.Range()) { 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; bool applyMinMax = (minSize != 0 || maxSize != NS_UNCONSTRAINEDSIZE) && aAvailableSize == NS_UNCONSTRAINEDSIZE; // 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) { for (uint32_t i : flexTracks) { float flexFactor = aFunctions.MaxSizingFor(i).AsFr(); nscoord flexLength = NSToCoordRound(flexFactor * fr); nscoord& base = mSizes[i].mBase; if (flexLength > base) { if (applyMinMax && origSizes.isNothing()) { origSizes.emplace(mSizes); } base = flexLength; } } } if (applyMinMax) { applyMinMax = false; // 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 [...]" const auto sumOfGridGaps = SumOfGridGaps(); nscoord newSize = SumOfGridTracks() + sumOfGridGaps; if (newSize > maxSize) { aAvailableSize = maxSize; } else if (newSize < minSize) { aAvailableSize = minSize; } if (aAvailableSize != NS_UNCONSTRAINEDSIZE) { aAvailableSize = std::max(0, aAvailableSize - sumOfGridGaps); // Restart with the original track sizes and definite aAvailableSize. if (origSizes.isSome()) { mSizes = std::move(*origSizes); origSizes.reset(); } // else, no mSizes[].mBase were changed above so it's still correct if (aAvailableSize == 0) { break; // zero available size wouldn't change any sizes though... } continue; } } break; } } void nsGridContainerFrame::Tracks::AlignJustifyContent( const nsStylePosition* aStyle, StyleContentDistribution aAligmentStyleValue, WritingMode aWM, nscoord aContentBoxSize, bool aIsSubgriddedAxis) { const bool isAlign = mAxis == eLogicalAxisBlock; // Align-/justify-content doesn't apply in a subgridded axis. // Gap properties do apply though so we need to stretch/position the tracks // to center-align the gaps with the parent's gaps. if (MOZ_UNLIKELY(aIsSubgriddedAxis)) { auto& gap = isAlign ? aStyle->mRowGap : aStyle->mColumnGap; if (gap.IsNormal()) { return; } auto len = mSizes.Length(); if (len <= 1) { return; } // This stores the gap deltas between the subgrid gap and the gaps in // the used track sizes (as encoded in its tracks' mPosition): nsTArray gapDeltas; const size_t numGaps = len - 1; gapDeltas.SetLength(numGaps); for (size_t i = 0; i < numGaps; ++i) { TrackSize& sz1 = mSizes[i]; TrackSize& sz2 = mSizes[i + 1]; nscoord currentGap = sz2.mPosition - (sz1.mPosition + sz1.mBase); gapDeltas[i] = mGridGap - currentGap; } // Recompute the tracks' size/position so that they end up with // a subgrid-gap centered on the original track gap. nscoord currentPos = mSizes[0].mPosition; nscoord lastHalfDelta(0); for (size_t i = 0; i < numGaps; ++i) { TrackSize& sz = mSizes[i]; nscoord delta = gapDeltas[i]; nscoord halfDelta; nscoord roundingError = NSCoordDivRem(delta, 2, &halfDelta); auto newSize = sz.mBase - (halfDelta + roundingError) - lastHalfDelta; lastHalfDelta = halfDelta; if (newSize >= 0) { sz.mBase = newSize; sz.mPosition = currentPos; currentPos += newSize + mGridGap; } else { sz.mBase = nscoord(0); sz.mPosition = currentPos + newSize; currentPos = sz.mPosition + mGridGap; } } auto& lastTrack = mSizes.LastElement(); auto newSize = lastTrack.mBase - lastHalfDelta; if (newSize >= 0) { lastTrack.mBase = newSize; lastTrack.mPosition = currentPos; } else { lastTrack.mBase = nscoord(0); lastTrack.mPosition = currentPos + newSize; } return; } if (mSizes.IsEmpty()) { return; } bool overflowSafe; auto alignment = ::GetAlignJustifyValue(aAligmentStyleValue.primary, aWM, isAlign, &overflowSafe); if (alignment == StyleAlignFlags::NORMAL) { alignment = StyleAlignFlags::STRETCH; // we may need a fallback for 'stretch' below aAligmentStyleValue = {alignment}; } // Compute the free space and count auto-sized tracks. size_t numAutoTracks = 0; nscoord space; if (alignment != StyleAlignFlags::START) { nscoord trackSizeSum = 0; if (aIsSubgriddedAxis) { numAutoTracks = mSizes.Length(); } else { for (const TrackSize& sz : mSizes) { trackSizeSum += sz.mBase; if (sz.mState & TrackSize::eAutoMaxSizing) { ++numAutoTracks; } } } space = aContentBoxSize - trackSizeSum - SumOfGridGaps(); // Use the fallback value instead when applicable. if (space < 0 || (alignment == StyleAlignFlags::SPACE_BETWEEN && mSizes.Length() == 1)) { auto fallback = ::GetAlignJustifyFallbackIfAny(aAligmentStyleValue, 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 = StyleAlignFlags::START; } } // Optimize the cases where we just need to set each track's position. nscoord pos = 0; bool distribute = true; if (alignment == StyleAlignFlags::BASELINE || alignment == StyleAlignFlags::LAST_BASELINE) { NS_WARNING("NYI: 'first/last baseline' (bug 1151204)"); // XXX alignment = StyleAlignFlags::START; } if (alignment == StyleAlignFlags::START) { distribute = false; } else if (alignment == StyleAlignFlags::END) { pos = space; distribute = false; } else if (alignment == StyleAlignFlags::CENTER) { pos = space / 2; distribute = false; } else if (alignment == StyleAlignFlags::STRETCH) { distribute = numAutoTracks != 0; } 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; if (alignment == StyleAlignFlags::STRETCH) { MOZ_ASSERT(numAutoTracks > 0, "we handled numAutoTracks == 0 above"); // The outer loop typically only runs once - it repeats only in a masonry // axis when some stretchable items reach their `max-size`. // It's O(n^2) worst case; if all items are stretchable with a `max-size` // and exactly one item reaches its `max-size` each round. while (space) { pos = 0; nscoord spacePerTrack; roundingError = NSCoordDivRem(space, numAutoTracks, &spacePerTrack); space = 0; 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; if (mIsMasonry && (sz.mState & TrackSize::eClampToLimit)) { auto clampedSize = std::min(newBase, sz.mLimit); auto sizeOverLimit = newBase - clampedSize; if (sizeOverLimit > 0) { newBase = clampedSize; sz.mState &= ~(sz.mState & TrackSize::eAutoMaxSizing); // This repeats the outer loop to distribute the superfluous space: space += sizeOverLimit; if (--numAutoTracks == 0) { // ... except if we don't have any stretchable items left. space = 0; } } } sz.mBase = newBase; pos += newBase + mGridGap; } } MOZ_ASSERT(!roundingError, "we didn't distribute all rounding error?"); return; } if (alignment == StyleAlignFlags::SPACE_BETWEEN) { MOZ_ASSERT(mSizes.Length() > 1, "should've used a fallback above"); roundingError = NSCoordDivRem(space, mSizes.Length() - 1, &between); } else if (alignment == StyleAlignFlags::SPACE_AROUND) { roundingError = NSCoordDivRem(space, mSizes.Length(), &between); pos = between / 2; } else if (alignment == StyleAlignFlags::SPACE_EVENLY) { roundingError = NSCoordDivRem(space, mSizes.Length() + 1, &between); pos = between; } else { MOZ_ASSERT_UNREACHABLE("unknown align-/justify-content value"); between = 0; // just to avoid a compiler warning roundingError = 0; // just to avoid a compiler warning } between += mGridGap; for (TrackSize& sz : mSizes) { sz.mPosition = pos; nscoord spacing = between; if (roundingError) { roundingError -= 1; spacing += 1; } pos += sz.mBase + spacing; } MOZ_ASSERT(!roundingError, "we didn't distribute all rounding error?"); } void nsGridContainerFrame::LineRange::ToPositionAndLength( const nsTArray& 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::BeforeGridGap : GridLineSide::AfterGridGap; nscoord startPos = aTracks.GridLineEdge(mStart, side); *aPos = aGridOrigin + startPos; *aLength = std::max(endPos - *aPos, 0); } } else { if (mStart == kAutoLine) { auto side = mEnd == 0 ? GridLineSide::AfterGridGap : GridLineSide::BeforeGridGap; nscoord endPos = aTracks.GridLineEdge(mEnd, side); *aLength = std::max(aGridOrigin + endPos, 0); } else if (MOZ_LIKELY(mStart != mEnd)) { nscoord pos; ToPositionAndLength(aTracks.mSizes, &pos, aLength); *aPos = aGridOrigin + pos; } else { // The grid area only covers removed 'auto-fit' tracks. nscoord pos = aTracks.GridLineEdge(mStart, GridLineSide::BeforeGridGap); *aPos = aGridOrigin + pos; *aLength = nscoord(0); } } } LogicalSize nsGridContainerFrame::GridReflowInput::PercentageBasisFor( LogicalAxis aAxis, const GridItemInfo& aGridItem) const { auto wm = aGridItem.mFrame->GetWritingMode(); const auto* itemParent = aGridItem.mFrame->GetParent(); if (MOZ_UNLIKELY(itemParent != mFrame)) { // The item comes from a descendant subgrid. Use the subgrid's // used track sizes to resolve the grid area size, if present. MOZ_ASSERT(itemParent->IsGridContainerFrame()); auto* subgridFrame = static_cast(itemParent); MOZ_ASSERT(subgridFrame->IsSubgrid()); if (auto* uts = subgridFrame->GetUsedTrackSizes()) { auto subgridWM = subgridFrame->GetWritingMode(); LogicalSize cbSize(subgridWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); if (!subgridFrame->IsSubgrid(eLogicalAxisInline) && uts->mCanResolveLineRangeSize[eLogicalAxisInline]) { // NOTE: At this point aGridItem.mArea is in this->mFrame coordinates // and thus may have been transposed. The range values in a non- // subgridded axis still has its original values in subgridFrame's // coordinates though. auto rangeAxis = subgridWM.IsOrthogonalTo(mWM) ? eLogicalAxisBlock : eLogicalAxisInline; const auto& range = aGridItem.mArea.LineRangeForAxis(rangeAxis); cbSize.ISize(subgridWM) = range.ToLength(uts->mSizes[eLogicalAxisInline]); } if (!subgridFrame->IsSubgrid(eLogicalAxisBlock) && uts->mCanResolveLineRangeSize[eLogicalAxisBlock]) { auto rangeAxis = subgridWM.IsOrthogonalTo(mWM) ? eLogicalAxisInline : eLogicalAxisBlock; const auto& range = aGridItem.mArea.LineRangeForAxis(rangeAxis); cbSize.BSize(subgridWM) = range.ToLength(uts->mSizes[eLogicalAxisBlock]); } return cbSize.ConvertTo(wm, subgridWM); } return LogicalSize(wm, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); } if (aAxis == eLogicalAxisInline || !mCols.mCanResolveLineRangeSize) { return LogicalSize(wm, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); } // Note: for now, we only resolve transferred percentages to row sizing. // We may need to adjust these assertions once we implement bug 1300366. MOZ_ASSERT(!mRows.mCanResolveLineRangeSize); nscoord colSize = aGridItem.mArea.mCols.ToLength(mCols.mSizes); nscoord rowSize = NS_UNCONSTRAINEDSIZE; return !wm.IsOrthogonalTo(mWM) ? LogicalSize(wm, colSize, rowSize) : LogicalSize(wm, rowSize, colSize); } 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); } void nsGridContainerFrame::GridReflowInput::AlignJustifyContentInMasonryAxis( nscoord aMasonryBoxSize, nscoord aContentBoxSize) { if (aContentBoxSize == NS_UNCONSTRAINEDSIZE) { aContentBoxSize = aMasonryBoxSize; } auto& masonryAxisTracks = mRows.mIsMasonry ? mRows : mCols; MOZ_ASSERT(masonryAxisTracks.mSizes.Length() == 2, "unexpected masonry axis tracks"); const auto masonryAxis = masonryAxisTracks.mAxis; const auto contentAlignment = mGridStyle->UsedContentAlignment(masonryAxis); if (contentAlignment.primary == StyleAlignFlags::NORMAL || contentAlignment.primary == StyleAlignFlags::STRETCH) { // Stretch the "masonry box" to the full content box if it's smaller. nscoord cbSize = std::max(aMasonryBoxSize, aContentBoxSize); for (auto& sz : masonryAxisTracks.mSizes) { sz.mBase = cbSize; } return; } // Save our current track sizes; replace them with one track sized to // the masonry box and align that within our content box. auto savedTrackSizes(std::move(masonryAxisTracks.mSizes)); masonryAxisTracks.mSizes.AppendElement(savedTrackSizes[0]); masonryAxisTracks.mSizes[0].mBase = aMasonryBoxSize; masonryAxisTracks.AlignJustifyContent(mGridStyle, contentAlignment, mWM, aContentBoxSize, false); nscoord masonryBoxOffset = masonryAxisTracks.mSizes[0].mPosition; // Restore the original track sizes... masonryAxisTracks.mSizes = std::move(savedTrackSizes); // ...then reposition and resize all of them to the aligned result. for (auto& sz : masonryAxisTracks.mSizes) { sz.mPosition = masonryBoxOffset; sz.mBase = aMasonryBoxSize; } } // Note: this is called after all items have been positioned/reflowed. // The masonry-axis tracks have the size of the "masonry box" at this point // and are positioned according to 'align/justify-content'. void nsGridContainerFrame::GridReflowInput::AlignJustifyTracksInMasonryAxis( const LogicalSize& aContentSize, const nsSize& aContainerSize) { auto& masonryAxisTracks = mRows.mIsMasonry ? mRows : mCols; MOZ_ASSERT(masonryAxisTracks.mSizes.Length() == 2, "unexpected masonry axis tracks"); const auto masonryAxis = masonryAxisTracks.mAxis; auto gridAxis = GetOrthogonalAxis(masonryAxis); auto& gridAxisTracks = TracksFor(gridAxis); AutoTArray savedSizes; savedSizes.AppendElements(masonryAxisTracks.mSizes); auto wm = mWM; nscoord contentAreaStart = mBorderPadding.Start(masonryAxis, wm); // The offset to the "masonry box" from our content-box start edge. nscoord masonryBoxOffset = masonryAxisTracks.mSizes[0].mPosition; nscoord alignmentContainerSize = masonryAxisTracks.mSizes[0].mBase; for (auto i : IntegerRange(gridAxisTracks.mSizes.Length())) { auto tracksAlignment = mGridStyle->UsedTracksAlignment(masonryAxis, i); if (tracksAlignment.primary != StyleAlignFlags::START) { masonryAxisTracks.mSizes.ClearAndRetainStorage(); for (const auto& item : mGridItems) { if (item.mArea.LineRangeForAxis(gridAxis).mStart == i) { const auto* child = item.mFrame; LogicalRect rect = child->GetLogicalRect(wm, aContainerSize); TrackSize sz = {0, 0, 0, {0, 0}, TrackSize::StateBits{0}}; const auto& margin = child->GetLogicalUsedMargin(wm); sz.mPosition = rect.Start(masonryAxis, wm) - margin.Start(masonryAxis, wm) - contentAreaStart; sz.mBase = rect.Size(masonryAxis, wm) + margin.StartEnd(masonryAxis, wm); // Account for a align-self baseline offset on the end side. // XXXmats hmm, it seems it would be a lot simpler to just store // these baseline adjustments into the UsedMarginProperty instead auto state = item.mState[masonryAxis]; if ((state & ItemState::eSelfBaseline) && (state & ItemState::eEndSideBaseline)) { sz.mBase += item.mBaselineOffset[masonryAxis]; } if (tracksAlignment.primary == StyleAlignFlags::STRETCH) { const auto* pos = child->StylePosition(); auto itemAlignment = pos->UsedSelfAlignment(masonryAxis, mFrame->Style()); if (child->StyleMargin()->HasAuto(masonryAxis, wm)) { sz.mState |= TrackSize::eAutoMaxSizing; sz.mState |= TrackSize::eItemHasAutoMargin; } else if (pos->Size(masonryAxis, wm).IsAuto() && (itemAlignment == StyleAlignFlags::NORMAL || itemAlignment == StyleAlignFlags::STRETCH)) { sz.mState |= TrackSize::eAutoMaxSizing; sz.mState |= TrackSize::eItemStretchSize; const auto& max = pos->MaxSize(masonryAxis, wm); if (max.ConvertsToLength()) { // XXX deal with percentages // XXX add in baselineOffset ? use actual frame size - content // size? nscoord boxSizingAdjust = child->GetLogicalUsedBorderAndPadding(wm).StartEnd( masonryAxis, wm); if (pos->mBoxSizing == StyleBoxSizing::Border) { boxSizingAdjust = 0; } sz.mLimit = nsLayoutUtils::ComputeBSizeValue( aContentSize.Size(masonryAxis, wm), boxSizingAdjust, max.AsLengthPercentage()); sz.mLimit += margin.StartEnd(masonryAxis, wm); sz.mState |= TrackSize::eClampToLimit; } } } masonryAxisTracks.mSizes.AppendElement(std::move(sz)); } } masonryAxisTracks.AlignJustifyContent(mGridStyle, tracksAlignment, wm, alignmentContainerSize, false); auto iter = mGridItems.begin(); auto end = mGridItems.end(); // We limit the loop to the number of items we found in the current // grid-axis axis track (in the outer loop) as an optimization. for (auto r : IntegerRange(masonryAxisTracks.mSizes.Length())) { GridItemInfo* item = nullptr; auto& sz = masonryAxisTracks.mSizes[r]; // Find the next item in the current grid-axis axis track. for (; iter != end; ++iter) { if (iter->mArea.LineRangeForAxis(gridAxis).mStart == i) { item = &*iter; ++iter; break; } } nsIFrame* child = item->mFrame; const auto childWM = child->GetWritingMode(); auto masonryChildAxis = childWM.IsOrthogonalTo(wm) ? gridAxis : masonryAxis; LogicalMargin margin = child->GetLogicalUsedMargin(childWM); bool forceReposition = false; if (sz.mState & TrackSize::eItemStretchSize) { auto size = child->GetLogicalSize().Size(masonryChildAxis, childWM); auto newSize = sz.mBase - margin.StartEnd(masonryChildAxis, childWM); if (size != newSize) { // XXX need to pass aIMinSizeClamp aBMinSizeClamp ? LogicalSize cb = ContainingBlockFor(item->mArea).Size(wm).ConvertTo(childWM, wm); LogicalSize availableSize = cb; cb.Size(masonryChildAxis, childWM) = alignmentContainerSize; availableSize.Size(eLogicalAxisBlock, childWM) = NS_UNCONSTRAINEDSIZE; const auto& bp = child->GetLogicalUsedBorderAndPadding(childWM); newSize -= bp.StartEnd(masonryChildAxis, childWM); ::PostReflowStretchChild(child, *mReflowInput, availableSize, cb, masonryChildAxis, newSize); if (childWM.IsPhysicalRTL()) { // The NormalPosition of this child is frame-size dependent so we // need to reset its stored position below. forceReposition = true; } } } else if (sz.mState & TrackSize::eItemHasAutoMargin) { // Re-compute the auto-margin(s) in the masonry axis. auto size = child->GetLogicalSize().Size(masonryChildAxis, childWM); auto spaceToFill = sz.mBase - size; if (spaceToFill > nscoord(0)) { const auto& marginStyle = child->StyleMargin(); if (marginStyle->mMargin.Start(masonryChildAxis, childWM) .IsAuto()) { if (marginStyle->mMargin.End(masonryChildAxis, childWM) .IsAuto()) { nscoord half; nscoord roundingError = NSCoordDivRem(spaceToFill, 2, &half); margin.Start(masonryChildAxis, childWM) = half; margin.End(masonryChildAxis, childWM) = half + roundingError; } else { margin.Start(masonryChildAxis, childWM) = spaceToFill; } } else { MOZ_ASSERT( marginStyle->mMargin.End(masonryChildAxis, childWM).IsAuto()); margin.End(masonryChildAxis, childWM) = spaceToFill; } nsMargin* propValue = child->GetProperty(nsIFrame::UsedMarginProperty()); if (propValue) { *propValue = margin.GetPhysicalMargin(childWM); } else { child->AddProperty( nsIFrame::UsedMarginProperty(), new nsMargin(margin.GetPhysicalMargin(childWM))); } } } nscoord newPos = contentAreaStart + masonryBoxOffset + sz.mPosition + margin.Start(masonryChildAxis, childWM); LogicalPoint pos = child->GetLogicalNormalPosition(wm, aContainerSize); auto delta = newPos - pos.Pos(masonryAxis, wm); if (delta != 0 || forceReposition) { LogicalPoint logicalDelta(wm); logicalDelta.Pos(masonryAxis, wm) = delta; child->MovePositionBy(wm, logicalDelta); } } } else if (masonryBoxOffset != nscoord(0)) { // TODO move placeholders too auto delta = masonryBoxOffset; LogicalPoint logicalDelta(wm); logicalDelta.Pos(masonryAxis, wm) = delta; for (const auto& item : mGridItems) { if (item.mArea.LineRangeForAxis(gridAxis).mStart != i) { continue; } item.mFrame->MovePositionBy(wm, logicalDelta); } } } masonryAxisTracks.mSizes = std::move(savedSizes); } /** * Return a Fragmentainer object if we have a fragmentainer frame in our * ancestor chain of containing block (CB) reflow inputs. 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 && !GetPrevInFlow()) { 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; } LayoutFrameType frameType = cbRI->mFrame->Type(); if ((frameType == LayoutFrameType::Canvas && PresContext()->IsPaginated()) || frameType == LayoutFrameType::ColumnSet) { data.emplace(); data->mIsTopOfPage = gridRI->mFlags.mIsTopOfPage; if (gridRI->AvailableBSize() != NS_UNCONSTRAINEDSIZE) { data->mToFragmentainerEnd = aState.mFragBStart + gridRI->AvailableBSize() - aState.mBorderPadding.BStart(wm); } else { // This occurs when nsColumnSetFrame reflows its last column in // unconstrained available block-size. data->mToFragmentainerEnd = NS_UNCONSTRAINEDSIZE; } 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_UNCONSTRAINEDSIZE; if (data->mIsAutoBSize) { bSize = gridRI->ComputedMinBSize(); } else { bSize = NS_CSS_MINMAX(bSize, gridRI->ComputedMinBSize(), gridRI->ComputedMaxBSize()); } nscoord gridEnd = aState.mRows.GridLineEdge(numRows, GridLineSide::BeforeGridGap); 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(); ComputedStyle* containerSC = Style(); WritingMode wm = aState.mReflowInput->GetWritingMode(); const bool isGridItem = !!aGridItemInfo; MOZ_ASSERT(isGridItem == !aChild->IsPlaceholderFrame()); 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); if (aFragmentainer && !wm.IsOrthogonalTo(childWM)) { // |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); if (aFragmentainer->mToFragmentainerEnd != NS_UNCONSTRAINEDSIZE) { toFragmentainerEnd = aFragmentainer->mToFragmentainerEnd - aState.mFragBStart - cb.BStart(wm); toFragmentainerEnd = std::max(toFragmentainerEnd, 0); isConstrainedBSize = true; } } 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 (GridItemInfo::BaselineAlignmentAffectsEndSide(state)) { baselineAdjust = -baselineAdjust; } if (baselineAdjust != nscoord(0)) { aChild->SetProperty(aProp, baselineAdjust); } else { aChild->RemoveProperty(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 content-box. // ("Usually" - the exception is when the grid *also* forms the // abs.pos. containing block. In that case, the alignment container isn't // the content-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 (ReflowInput::InitFlag::StaticPosIsCBOrigin) which will make // us ignore the placeholder's position entirely.) cb = aContentArea; 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. ComputeSizeFlags csFlags; if (aGridItemInfo) { // AlignJustifyTracksInMasonryAxis stretches items in a masonry-axis so we // don't do that here. auto* pos = aChild->StylePosition(); auto j = IsMasonry(eLogicalAxisInline) ? StyleAlignFlags::START : pos->UsedJustifySelf(Style())._0; auto a = IsMasonry(eLogicalAxisBlock) ? StyleAlignFlags::START : pos->UsedAlignSelf(Style())._0; bool stretch[2]; stretch[eLogicalAxisInline] = j == StyleAlignFlags::NORMAL || j == StyleAlignFlags::STRETCH; stretch[eLogicalAxisBlock] = a == StyleAlignFlags::NORMAL || a == StyleAlignFlags::STRETCH; auto childIAxis = isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline; // Clamp during reflow if we're stretching in that axis. if (stretch[childIAxis]) { if (aGridItemInfo->mState[childIAxis] & ItemState::eClampMarginBoxMinSize) { csFlags += ComputeSizeFlag::IClampMarginBoxMinSize; } } else { csFlags += ComputeSizeFlag::ShrinkWrap; } auto childBAxis = GetOrthogonalAxis(childIAxis); if (stretch[childBAxis] && aGridItemInfo->mState[childBAxis] & ItemState::eClampMarginBoxMinSize) { csFlags += ComputeSizeFlag::BClampMarginBoxMinSize; aChild->SetProperty(BClampMarginBoxMinSizeProperty(), childCBSize.BSize(childWM)); } else { aChild->RemoveProperty(BClampMarginBoxMinSizeProperty()); } if ((aGridItemInfo->mState[childIAxis] & ItemState::eApplyAutoMinSize)) { csFlags += ComputeSizeFlag::IApplyAutoMinSize; } } if (!isConstrainedBSize) { childCBSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE; } LogicalSize percentBasis(cb.Size(wm).ConvertTo(childWM, wm)); ReflowInput childRI(pc, *aState.mReflowInput, aChild, childCBSize, Some(percentBasis), {}, {}, csFlags); childRI.mFlags.mIsTopOfPage = aFragmentainer ? aFragmentainer->mIsTopOfPage : false; // FIXME (perf): It would be faster to do this only if the previous reflow of // the child was a measuring reflow, and only if the child does some of the // things that are affected by ComputeSizeFlag::IsGridMeasuringReflow. childRI.SetBResize(true); childRI.mFlags.mIsBResizeForPercentages = true; // 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).IsAuto()) { auto blockAxisAlignment = childRI.mStylePosition->UsedAlignSelf(Style()); if (!IsMasonry(eLogicalAxisBlock) && (blockAxisAlignment._0 == StyleAlignFlags::NORMAL || blockAxisAlignment._0 == StyleAlignFlags::STRETCH)) { stretch = true; } } if (stretch) { aChild->SetProperty(FragStretchBSizeProperty(), *aStretchBSize); } else { aChild->RemoveProperty(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; // XXXdholbert The childPos that we use for ReflowChild shouldn't matter, // since we finalize it in FinishReflowChild. However, it does matter if the // child happens to be XUL (which sizes menu popup frames based on the // position within the viewport, during this ReflowChild call). So we make an // educated guess that the child will be at the origin of its containing // block, and then use align/justify to correct that as-needed further // down. (If the child has a different writing mode than its parent, though, // then we can't express the CB origin until we've reflowed the child and // determined its size. In that case, we throw up our hands and don't bother // trying to guess the position up-front after all.) // XXXdholbert We'll remove this special case in bug 1600542, and then we can // go back to just setting childPos in a single call after ReflowChild. LogicalPoint childPos(childWM); if (MOZ_LIKELY(childWM == wm)) { // Initially, assume the child will be at the containing block origin. // (This may get corrected during alignment/justification below.) childPos = cb.Origin(wm); } ReflowChild(aChild, pc, childSize, childRI, childWM, childPos, dummyContainerSize, ReflowChildFlags::Default, aStatus); if (MOZ_UNLIKELY(childWM != wm)) { // As above: assume the child will be at the containing block origin. // (which we can now compute in terms of the childWM, now that we know the // child's size). 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() auto applyItemSelfAlignment = [&](LogicalAxis aAxis, nscoord aCBSize) { auto align = childRI.mStylePosition->UsedSelfAlignment(aAxis, containerSC); auto state = aGridItemInfo->mState[aAxis]; auto flags = AlignJustifyFlags::NoFlags; if (IsMasonry(aAxis)) { // In a masonry axis, we inhibit applying 'stretch' and auto-margins // here since AlignJustifyTracksInMasonryAxis deals with that. // The only other {align,justify}-{self,content} values that have an // effect are '[last] baseline', the rest behave as 'start'. if (MOZ_LIKELY(!(state & ItemState::eSelfBaseline))) { align = {StyleAlignFlags::START}; } else { auto group = (state & ItemState::eFirstBaseline) ? BaselineSharingGroup::First : BaselineSharingGroup::Last; auto itemStart = aGridItemInfo->mArea.LineRangeForAxis(aAxis).mStart; aCBSize = aState.TracksFor(aAxis) .mSizes[itemStart] .mBaselineSubtreeSize[group]; } flags = AlignJustifyFlags::IgnoreAutoMargins; } else if (state & ItemState::eContentBaseline) { align = {(state & ItemState::eFirstBaseline) ? StyleAlignFlags::SELF_START : StyleAlignFlags::SELF_END}; } if (aAxis == eLogicalAxisBlock) { AlignSelf(*aGridItemInfo, align, aCBSize, wm, childRI, size, flags, &childPos); } else { JustifySelf(*aGridItemInfo, align, aCBSize, wm, childRI, size, flags, &childPos); } }; if (aStatus.IsComplete()) { applyItemSelfAlignment(eLogicalAxisBlock, cb.BSize(wm) - consumedGridAreaBSize); } applyItemSelfAlignment(eLogicalAxisInline, cb.ISize(wm)); } // else, nsAbsoluteContainingBlock.cpp will handle align/justify-self. FinishReflowChild(aChild, pc, childSize, &childRI, childWM, childPos, aContainerSize, ReflowChildFlags::ApplyRelativePositioning); ConsiderChildOverflow(aDesiredSize.mOverflowAreas, aChild); } nscoord nsGridContainerFrame::ReflowInFragmentainer( GridReflowInput& aState, const LogicalRect& aContentArea, ReflowOutput& aDesiredSize, nsReflowStatus& aStatus, Fragmentainer& aFragmentainer, const nsSize& aContainerSize) { MOZ_ASSERT(aStatus.IsEmpty()); 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(CSSOrderAwareFrameIterator::ChildFilter::IncludeAll); for (; !aState.mIter.AtEnd(); aState.mIter.Next()) { nsIFrame* child = *aState.mIter; if (!child->IsPlaceholderFrame()) { const GridItemInfo* info = &aState.mGridItems[aState.mIter.ItemIndex()]; sortedItems.AppendElement(info); } else { placeholders.AppendElement(child); } } // NOTE: We don't need stable_sort here, except in Masonry layout. There are // no dependencies on having content order between items on the same row in // the code below in the non-Masonry case. if (IsMasonry()) { std::stable_sort(sortedItems.begin(), sortedItems.end(), GridItemInfo::IsStartRowLessThan); } else { 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 if available block-size for children // is constrained. That is, ignore forced breaks if available block-size for // children is unconstrained since our parent expected us to be fully // complete. bool isForcedBreak = false; const bool avoidBreakInside = ShouldAvoidBreakInside(*aState.mReflowInput); if (childAvailableSize != NS_UNCONSTRAINEDSIZE) { const bool isTopOfPage = aFragmentainer.mIsTopOfPage; for (const GridItemInfo* info : sortedItems) { uint32_t itemStartRow = info->mArea.mRows.mStart; if (itemStartRow == endRow) { break; } const auto* disp = info->mFrame->StyleDisplay(); if (disp->BreakBefore()) { // 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.Reset(); break; // we're done since the items are sorted in row order } } uint32_t itemEndRow = info->mArea.mRows.mEnd; if (disp->BreakAfter()) { 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::BeforeGridGap); 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::BeforeGridGap); 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; Maybe> masonryAxisPos; const auto rowCount = aState.mRows.mSizes.Length(); nscoord masonryAxisGap; const auto wm = aState.mWM; const bool isColMasonry = IsMasonry(eLogicalAxisInline); if (isColMasonry) { for (auto& sz : aState.mCols.mSizes) { sz.mPosition = 0; } masonryAxisGap = nsLayoutUtils::ResolveGapToLength( aState.mGridStyle->mColumnGap, aContentArea.ISize(wm)); aState.mCols.mGridGap = masonryAxisGap; masonryAxisPos.emplace(rowCount); masonryAxisPos->SetLength(rowCount); PodZero(masonryAxisPos->Elements(), rowCount); } 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.Insert(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(wm); } else { maxRowSize = gridAvailableSize; } maxRowSize -= sz.mPosition; // ...and only if there is space for it to grow. rowCanGrow = maxRowSize > sz.mBase; } } } if (isColMasonry) { const auto& cols = info->mArea.mCols; MOZ_ASSERT((cols.mStart == 0 || cols.mStart == 1) && cols.Extent() == 1); aState.mCols.mSizes[cols.mStart].mPosition = masonryAxisPos.ref()[row]; } // aFragmentainer.mIsTopOfPage is propagated to the child reflow input. // When it's false the child may request InlineBreak::Before. We set it // to false when the row is growable (as determined in the CSS Grid // Fragmentation spec) 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 InlineBreak::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::BeforeGridGap) - aState.mRows.GridLineEdge(std::max(aStartRow, row), GridLineSide::AfterGridGap); 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 InlineBreak::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::BeforeGridGap); 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::BeforeGridGap); i = -1; // i == 0 after the next loop increment isRowTopOfPage = isStartRowTopOfPage; overflowIncompleteItems.Clear(); incompleteItems.Clear(); aStatus.SetIncomplete(); continue; } NS_ERROR("got InlineBreak::Before at top-of-page"); childStatus.Reset(); } else { // We got InlineBreak::Before again after growing the row - this can // happen if the child isn't splittable, e.g. some form controls. childStatus.Reset(); if (child->GetNextInFlow()) { // The child already has a fragment, so we know it's splittable. childStatus.SetIncomplete(); } // else, report that it's complete } } else if (childStatus.IsInlineBreakAfter()) { MOZ_ASSERT_UNREACHABLE("unexpected child reflow status"); } MOZ_ASSERT(!childStatus.IsInlineBreakBefore(), "should've handled InlineBreak::Before above"); if (childStatus.IsIncomplete()) { incompleteItems.Insert(child); } else if (!childStatus.IsFullyComplete()) { overflowIncompleteItems.Insert(child); } if (isColMasonry) { auto childWM = child->GetWritingMode(); auto childAxis = !childWM.IsOrthogonalTo(wm) ? eLogicalAxisInline : eLogicalAxisBlock; auto normalPos = child->GetLogicalNormalPosition(wm, aContainerSize); auto sz = childAxis == eLogicalAxisBlock ? child->BSize() : child->ISize(); auto pos = normalPos.Pos(eLogicalAxisInline, wm) + sz + child->GetLogicalUsedMargin(childWM).End(childAxis, childWM); masonryAxisPos.ref()[row] = pos + masonryAxisGap - aContentArea.Start(eLogicalAxisInline, wm); } } // Record a break before |aEndRow|. aState.mNextFragmentStartRow = aEndRow; if (aEndRow < rowCount) { aState.mRows.BreakBeforeRow(aEndRow); if (aState.mSharedGridData) { aState.mSharedGridData->mRows.BreakBeforeRow(aEndRow); } } const bool childrenMoved = PushIncompleteChildren( pushedItems, incompleteItems, overflowIncompleteItems); if (childrenMoved && aStatus.IsComplete()) { aStatus.SetOverflowIncomplete(); aStatus.SetNextInFlowNeedsReflow(); } if (!pushedItems.IsEmpty()) { 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 (!incompleteItems.IsEmpty()) { // 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 (isColMasonry) { nscoord maxSize = 0; for (auto pos : masonryAxisPos.ref()) { maxSize = std::max(maxSize, pos); } maxSize = std::max(nscoord(0), maxSize - masonryAxisGap); aState.AlignJustifyContentInMasonryAxis(maxSize, aContentArea.ISize(wm)); } return aBSize; } // Here's a brief overview of how Masonry layout is implemented: // We setup two synthetic tracks in the Masonry axis so that the Reflow code // can treat it the same as for normal grid layout. The first track is // fixed (during item placement/layout) at the content box start and contains // the start items for each grid-axis track. The second track contains // all other items and is moved to the position where we want to position // the currently laid out item (like a sliding window as we place items). // Once item layout is done, the tracks are resized to be the size of // the "masonry box", which is the offset from the content box start to // the margin-box end of the item that is furthest away (this happens in // AlignJustifyContentInMasonryAxis() called at the end of this method). // This is to prepare for AlignJustifyTracksInMasonryAxis, which is called // later by our caller. // Both tracks store their first-/last-baseline group offsets as usual. // The first-baseline of the start track, and the last-baseline of the last // track (if they exist) are exported as the grid container's baselines, or // we fall back to picking an item's baseline (all this is per normal grid // layout). There's a slight difference in which items belongs to which // group though - see InitializeItemBaselinesInMasonryAxis for details. // This method returns the "masonry box" size (in the masonry axis). nscoord nsGridContainerFrame::MasonryLayout(GridReflowInput& aState, const LogicalRect& aContentArea, SizingConstraint aConstraint, ReflowOutput& aDesiredSize, nsReflowStatus& aStatus, Fragmentainer* aFragmentainer, const nsSize& aContainerSize) { using BaselineAlignmentSet = Tracks::BaselineAlignmentSet; auto recordAutoPlacement = [this, &aState](GridItemInfo* aItem, LogicalAxis aGridAxis) { // When we're auto-placing an item in a continuation we need to record // the placement in mSharedGridData. if (MOZ_UNLIKELY(aState.mSharedGridData && GetPrevInFlow()) && (aItem->mState[aGridAxis] & ItemState::eAutoPlacement)) { auto* child = aItem->mFrame; MOZ_RELEASE_ASSERT(!child->GetPrevInFlow(), "continuations should never be auto-placed"); for (auto& sharedItem : aState.mSharedGridData->mGridItems) { if (sharedItem.mFrame == child) { sharedItem.mArea.LineRangeForAxis(aGridAxis) = aItem->mArea.LineRangeForAxis(aGridAxis); MOZ_ASSERT(sharedItem.mState[aGridAxis] & ItemState::eAutoPlacement); sharedItem.mState[aGridAxis] &= ~ItemState::eAutoPlacement; break; } } } aItem->mState[aGridAxis] &= ~ItemState::eAutoPlacement; }; // Collect our grid items and sort them in grid order. nsTArray sortedItems(aState.mGridItems.Length()); aState.mIter.Reset(CSSOrderAwareFrameIterator::ChildFilter::IncludeAll); size_t absposIndex = 0; const LogicalAxis masonryAxis = IsMasonry(eLogicalAxisBlock) ? eLogicalAxisBlock : eLogicalAxisInline; const auto wm = aState.mWM; for (; !aState.mIter.AtEnd(); aState.mIter.Next()) { nsIFrame* child = *aState.mIter; if (MOZ_LIKELY(!child->IsPlaceholderFrame())) { GridItemInfo* item = &aState.mGridItems[aState.mIter.ItemIndex()]; sortedItems.AppendElement(item); } else if (aConstraint == SizingConstraint::NoConstraint) { // (we only collect placeholders in the NoConstraint case since they // don't affect intrinsic sizing in any way) GridItemInfo* item = nullptr; auto* ph = static_cast(child); if (ph->GetOutOfFlowFrame()->GetParent() == this) { item = &aState.mAbsPosItems[absposIndex++]; MOZ_RELEASE_ASSERT(item->mFrame == ph->GetOutOfFlowFrame()); auto masonryStart = item->mArea.LineRangeForAxis(masonryAxis).mStart; // If the item was placed by the author at line 1 (masonryStart == 0) // then include it to be placed at the masonry-box start. If it's // auto-placed and has an `auto` inset value in the masonry axis then // we include it to be placed after the last grid item with the same // grid-axis start track. // XXXmats this is all a bit experimental at this point, pending a spec if (masonryStart == 0 || (masonryStart == kAutoLine && item->mFrame->StylePosition() ->mOffset.Start(masonryAxis, wm) .IsAuto())) { sortedItems.AppendElement(item); } else { item = nullptr; } } if (!item) { // It wasn't included above - just reflow it and be done with it. nsReflowStatus childStatus; ReflowInFlowChild(child, nullptr, aContainerSize, Nothing(), nullptr, aState, aContentArea, aDesiredSize, childStatus); } } } const auto masonryAutoFlow = aState.mGridStyle->mMasonryAutoFlow; const bool definiteFirst = masonryAutoFlow.order == StyleMasonryItemOrder::DefiniteFirst; if (masonryAxis == eLogicalAxisBlock) { std::stable_sort(sortedItems.begin(), sortedItems.end(), definiteFirst ? GridItemInfo::RowMasonryDefiniteFirst : GridItemInfo::RowMasonryOrdered); } else { std::stable_sort(sortedItems.begin(), sortedItems.end(), definiteFirst ? GridItemInfo::ColMasonryDefiniteFirst : GridItemInfo::ColMasonryOrdered); } FrameHashtable pushedItems; FrameHashtable incompleteItems; FrameHashtable overflowIncompleteItems; nscoord toFragmentainerEnd = nscoord_MAX; nscoord fragStartPos = aState.mFragBStart; const bool avoidBreakInside = aFragmentainer && ShouldAvoidBreakInside(*aState.mReflowInput); const bool isTopOfPageAtStart = aFragmentainer && aFragmentainer->mIsTopOfPage; if (aFragmentainer) { toFragmentainerEnd = std::max(0, aFragmentainer->mToFragmentainerEnd); } const LogicalAxis gridAxis = GetOrthogonalAxis(masonryAxis); const auto gridAxisTrackCount = aState.TracksFor(gridAxis).mSizes.Length(); auto& masonryTracks = aState.TracksFor(masonryAxis); auto& masonrySizes = masonryTracks.mSizes; MOZ_ASSERT(masonrySizes.Length() == 2); for (auto& sz : masonrySizes) { sz.mPosition = fragStartPos; } // The current running position for each grid-axis track where the next item // should be positioned. When an item is placed we'll update the tracks it // spans to the end of its margin box + 'gap'. nsTArray currentPos(gridAxisTrackCount); currentPos.SetLength(gridAxisTrackCount); for (auto& sz : currentPos) { sz = fragStartPos; } nsTArray lastPos(currentPos.Clone()); nsTArray lastItems(gridAxisTrackCount); lastItems.SetLength(gridAxisTrackCount); PodZero(lastItems.Elements(), gridAxisTrackCount); const nscoord gap = nsLayoutUtils::ResolveGapToLength( masonryAxis == eLogicalAxisBlock ? aState.mGridStyle->mRowGap : aState.mGridStyle->mColumnGap, masonryTracks.mContentBoxSize); masonryTracks.mGridGap = gap; uint32_t cursor = 0; const auto containerToMasonryBoxOffset = fragStartPos - aContentArea.Start(masonryAxis, wm); const bool isPack = masonryAutoFlow.placement == StyleMasonryPlacement::Pack; bool didAlignStartAlignedFirstItems = false; // Return true if any of the lastItems in aRange are baseline-aligned in // the masonry axis. auto lastItemHasBaselineAlignment = [&](const LineRange& aRange) { for (auto i : aRange.Range()) { if (auto* child = lastItems[i] ? lastItems[i]->mFrame : nullptr) { const auto& pos = child->StylePosition(); auto selfAlignment = pos->UsedSelfAlignment(masonryAxis, this->Style()); if (selfAlignment == StyleAlignFlags::BASELINE || selfAlignment == StyleAlignFlags::LAST_BASELINE) { return true; } auto childAxis = masonryAxis; if (child->GetWritingMode().IsOrthogonalTo(wm)) { childAxis = gridAxis; } auto contentAlignment = pos->UsedContentAlignment(childAxis).primary; if (contentAlignment == StyleAlignFlags::BASELINE || contentAlignment == StyleAlignFlags::LAST_BASELINE) { return true; } } } return false; }; // Resolve aItem's placement, unless it's definite already. Return its // masonry axis position with that placement. auto placeItem = [&](GridItemInfo* aItem) -> nscoord { auto& masonryAxisRange = aItem->mArea.LineRangeForAxis(masonryAxis); MOZ_ASSERT(masonryAxisRange.mStart != 0, "item placement is already final"); auto& gridAxisRange = aItem->mArea.LineRangeForAxis(gridAxis); bool isAutoPlaced = aItem->mState[gridAxis] & ItemState::eAutoPlacement; uint32_t start = isAutoPlaced ? 0 : gridAxisRange.mStart; if (isAutoPlaced && !isPack) { start = cursor; isAutoPlaced = false; } const uint32_t extent = gridAxisRange.Extent(); if (start + extent > gridAxisTrackCount) { // Note that this will only happen to auto-placed items since the grid is // always wide enough to fit other items. start = 0; } // This keeps track of the smallest `maxPosForRange` value that // we discover in the loop below: nscoord minPos = nscoord_MAX; MOZ_ASSERT(extent <= gridAxisTrackCount); const uint32_t iEnd = gridAxisTrackCount + 1 - extent; for (uint32_t i = start; i < iEnd; ++i) { // Find the max `currentPos` value for the tracks that we would span // if we were to use `i` as our start track: nscoord maxPosForRange = 0; for (auto j = i, jEnd = j + extent; j < jEnd; ++j) { maxPosForRange = std::max(currentPos[j], maxPosForRange); } if (maxPosForRange < minPos) { minPos = maxPosForRange; start = i; } if (!isAutoPlaced) { break; } } gridAxisRange.mStart = start; gridAxisRange.mEnd = start + extent; bool isFirstItem = true; for (uint32_t i : gridAxisRange.Range()) { if (lastItems[i]) { isFirstItem = false; break; } } // If this is the first item in its spanned grid tracks, then place it in // the first masonry track. Otherwise, place it in the second masonry track. masonryAxisRange.mStart = isFirstItem ? 0 : 1; masonryAxisRange.mEnd = masonryAxisRange.mStart + 1; return minPos; }; // Handle the resulting reflow status after reflowing aItem. // This may set aStatus to BreakBefore which the caller is expected // to handle by returning from MasonryLayout. // @return true if this item should consume all remaining space auto handleChildStatus = [&](GridItemInfo* aItem, const nsReflowStatus& aChildStatus) { bool result = false; if (MOZ_UNLIKELY(aFragmentainer)) { auto* child = aItem->mFrame; if (!aChildStatus.IsComplete() || aChildStatus.IsInlineBreakBefore() || aChildStatus.IsInlineBreakAfter() || child->StyleDisplay()->BreakAfter()) { if (!isTopOfPageAtStart && avoidBreakInside) { aStatus.SetInlineLineBreakBeforeAndReset(); return result; } result = true; } if (aChildStatus.IsInlineBreakBefore()) { aStatus.SetIncomplete(); pushedItems.Insert(child); } else if (aChildStatus.IsIncomplete()) { recordAutoPlacement(aItem, gridAxis); aStatus.SetIncomplete(); incompleteItems.Insert(child); } else if (!aChildStatus.IsFullyComplete()) { recordAutoPlacement(aItem, gridAxis); overflowIncompleteItems.Insert(child); } } return result; }; // @return the distance from the masonry-box start to the end of the margin- // box of aChild auto offsetToMarginBoxEnd = [&](nsIFrame* aChild) { auto childWM = aChild->GetWritingMode(); auto childAxis = !childWM.IsOrthogonalTo(wm) ? masonryAxis : gridAxis; auto normalPos = aChild->GetLogicalNormalPosition(wm, aContainerSize); auto sz = childAxis == eLogicalAxisBlock ? aChild->BSize() : aChild->ISize(); return containerToMasonryBoxOffset + normalPos.Pos(masonryAxis, wm) + sz + aChild->GetLogicalUsedMargin(childWM).End(childAxis, childWM); }; // Apply baseline alignment to items belonging to the given set. nsTArray firstBaselineItems; nsTArray lastBaselineItems; auto applyBaselineAlignment = [&](BaselineAlignmentSet aSet) { firstBaselineItems.ClearAndRetainStorage(); lastBaselineItems.ClearAndRetainStorage(); masonryTracks.InitializeItemBaselinesInMasonryAxis( aState, aState.mGridItems, aSet, aContainerSize, currentPos, firstBaselineItems, lastBaselineItems); bool didBaselineAdjustment = false; nsTArray* baselineItems[] = {&firstBaselineItems, &lastBaselineItems}; for (const auto* items : baselineItems) { for (const auto& data : *items) { GridItemInfo* item = data.mGridItem; MOZ_ASSERT((item->mState[masonryAxis] & ItemState::eIsBaselineAligned)); nscoord baselineOffset = item->mBaselineOffset[masonryAxis]; if (baselineOffset == nscoord(0)) { continue; // no adjustment needed for this item } didBaselineAdjustment = true; auto* child = item->mFrame; auto masonryAxisStart = item->mArea.LineRangeForAxis(masonryAxis).mStart; auto gridAxisRange = item->mArea.LineRangeForAxis(gridAxis); masonrySizes[masonryAxisStart].mPosition = aSet.mItemSet == BaselineAlignmentSet::LastItems ? lastPos[gridAxisRange.mStart] : fragStartPos; bool consumeAllSpace = false; const auto state = item->mState[masonryAxis]; if ((state & ItemState::eContentBaseline) || MOZ_UNLIKELY(aFragmentainer)) { if (MOZ_UNLIKELY(aFragmentainer)) { aFragmentainer->mIsTopOfPage = isTopOfPageAtStart && masonrySizes[masonryAxisStart].mPosition == fragStartPos; } nsReflowStatus childStatus; ReflowInFlowChild(child, item, aContainerSize, Nothing(), aFragmentainer, aState, aContentArea, aDesiredSize, childStatus); consumeAllSpace = handleChildStatus(item, childStatus); if (aStatus.IsInlineBreakBefore()) { return false; } } else if (!(state & ItemState::eEndSideBaseline)) { // `align/justify-self` baselines on the start side can be handled by // just moving the frame (except in a fragmentainer in which case we // reflow it above instead since it might make it INCOMPLETE). LogicalPoint logicalDelta(wm); logicalDelta.Pos(masonryAxis, wm) = baselineOffset; child->MovePositionBy(wm, logicalDelta); } if ((state & ItemState::eEndSideBaseline) && !consumeAllSpace) { // Account for an end-side baseline adjustment. for (uint32_t i : gridAxisRange.Range()) { currentPos[i] += baselineOffset; } } else { nscoord pos = consumeAllSpace ? toFragmentainerEnd : offsetToMarginBoxEnd(child); pos += gap; for (uint32_t i : gridAxisRange.Range()) { currentPos[i] = pos; } } } } return didBaselineAdjustment; }; // Place and reflow items. We'll use two fake tracks in the masonry axis. // The first contains items that were placed there by the regular grid // placement algo (PlaceGridItems) and we may add some items here if there // are still empty slots. The second track contains all other items. // Both tracks always have the size of the content box in the masonry axis. // The position of the first track is always at the start. The position // of the second track is updated as we go to a position where we want // the current item to be positioned. for (GridItemInfo* item : sortedItems) { auto* child = item->mFrame; auto& masonryRange = item->mArea.LineRangeForAxis(masonryAxis); auto& gridRange = item->mArea.LineRangeForAxis(gridAxis); nsReflowStatus childStatus; if (MOZ_UNLIKELY(child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW))) { auto contentArea = aContentArea; nscoord pos = nscoord_MAX; // XXXmats take mEnd into consideration... if (gridRange.mStart == kAutoLine) { for (auto p : currentPos) { pos = std::min(p, pos); } } else if (gridRange.mStart < currentPos.Length()) { pos = currentPos[gridRange.mStart]; } else if (currentPos.Length() > 0) { pos = currentPos.LastElement(); } if (pos == nscoord_MAX) { pos = nscoord(0); } contentArea.Start(masonryAxis, wm) = pos; child = child->GetPlaceholderFrame(); ReflowInFlowChild(child, nullptr, aContainerSize, Nothing(), nullptr, aState, contentArea, aDesiredSize, childStatus); } else { MOZ_ASSERT(gridRange.Extent() > 0 && gridRange.Extent() <= gridAxisTrackCount); MOZ_ASSERT((masonryRange.mStart == 0 || masonryRange.mStart == 1) && masonryRange.Extent() == 1); if (masonryRange.mStart != 0) { masonrySizes[1].mPosition = placeItem(item); } // If this is the first item NOT in the first track and if any of // the grid-axis tracks we span has a baseline-aligned item then we // need to do that baseline alignment now since it may affect // the placement of this and later items. if (!didAlignStartAlignedFirstItems && aConstraint == SizingConstraint::NoConstraint && masonryRange.mStart != 0 && lastItemHasBaselineAlignment(gridRange)) { didAlignStartAlignedFirstItems = true; if (applyBaselineAlignment({BaselineAlignmentSet::FirstItems, BaselineAlignmentSet::StartStretch})) { // Baseline alignment resized some items - redo our placement. masonrySizes[1].mPosition = placeItem(item); } if (aStatus.IsInlineBreakBefore()) { return fragStartPos; } } for (uint32_t i : gridRange.Range()) { lastItems[i] = item; } cursor = gridRange.mEnd; if (cursor >= gridAxisTrackCount) { cursor = 0; } nscoord pos; if (aConstraint == SizingConstraint::NoConstraint) { const auto* disp = child->StyleDisplay(); if (MOZ_UNLIKELY(aFragmentainer)) { aFragmentainer->mIsTopOfPage = isTopOfPageAtStart && masonrySizes[masonryRange.mStart].mPosition == fragStartPos; if (!aFragmentainer->mIsTopOfPage && (disp->BreakBefore() || masonrySizes[masonryRange.mStart].mPosition >= toFragmentainerEnd)) { childStatus.SetInlineLineBreakBeforeAndReset(); } } if (!childStatus.IsInlineBreakBefore()) { ReflowInFlowChild(child, item, aContainerSize, Nothing(), aFragmentainer, aState, aContentArea, aDesiredSize, childStatus); } bool consumeAllSpace = handleChildStatus(item, childStatus); if (aStatus.IsInlineBreakBefore()) { return fragStartPos; } pos = consumeAllSpace ? toFragmentainerEnd : offsetToMarginBoxEnd(child); } else { LogicalSize percentBasis( aState.PercentageBasisFor(eLogicalAxisInline, *item)); IntrinsicISizeType type = aConstraint == SizingConstraint::MaxContent ? IntrinsicISizeType::PrefISize : IntrinsicISizeType::MinISize; auto sz = ::ContentContribution(*item, aState, &aState.mRenderingContext, wm, masonryAxis, Some(percentBasis), type); pos = sz + masonrySizes[masonryRange.mStart].mPosition; } pos += gap; for (uint32_t i : gridRange.Range()) { lastPos[i] = currentPos[i]; currentPos[i] = pos; } } } // Do the remaining baseline alignment sets. if (aConstraint == SizingConstraint::NoConstraint) { for (auto*& item : lastItems) { if (item) { item->mState[masonryAxis] |= ItemState::eIsLastItemInMasonryTrack; } } BaselineAlignmentSet baselineSets[] = { {BaselineAlignmentSet::FirstItems, BaselineAlignmentSet::StartStretch}, {BaselineAlignmentSet::FirstItems, BaselineAlignmentSet::EndStretch}, {BaselineAlignmentSet::LastItems, BaselineAlignmentSet::StartStretch}, {BaselineAlignmentSet::LastItems, BaselineAlignmentSet::EndStretch}, }; for (uint32_t i = 0; i < ArrayLength(baselineSets); ++i) { if (i == 0 && didAlignStartAlignedFirstItems) { continue; } applyBaselineAlignment(baselineSets[i]); } } const bool childrenMoved = PushIncompleteChildren( pushedItems, incompleteItems, overflowIncompleteItems); if (childrenMoved && aStatus.IsComplete()) { aStatus.SetOverflowIncomplete(); aStatus.SetNextInFlowNeedsReflow(); } if (!pushedItems.IsEmpty()) { 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 (!incompleteItems.IsEmpty()) { // 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(); } nscoord masonryBoxSize = 0; for (auto pos : currentPos) { masonryBoxSize = std::max(masonryBoxSize, pos); } masonryBoxSize = std::max(nscoord(0), masonryBoxSize - gap); if (aConstraint == SizingConstraint::NoConstraint) { aState.AlignJustifyContentInMasonryAxis(masonryBoxSize, masonryTracks.mContentBoxSize); } return masonryBoxSize; } nsGridContainerFrame* nsGridContainerFrame::ParentGridContainerForSubgrid() const { MOZ_ASSERT(IsSubgrid()); nsIFrame* p = GetParent(); while (p->GetContent() == GetContent()) { p = p->GetParent(); } MOZ_ASSERT(p->IsGridContainerFrame()); auto* parent = static_cast(p); MOZ_ASSERT(parent->HasSubgridItems()); return parent; } nscoord nsGridContainerFrame::ReflowChildren(GridReflowInput& aState, const LogicalRect& aContentArea, const nsSize& aContainerSize, ReflowOutput& aDesiredSize, nsReflowStatus& aStatus) { WritingMode wm = aState.mReflowInput->GetWritingMode(); nscoord bSize = aContentArea.BSize(wm); MOZ_ASSERT(aState.mReflowInput); MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); if (HidesContentForLayout()) { return bSize; } OverflowAreas ocBounds; nsReflowStatus ocStatus; if (GetPrevInFlow()) { ReflowOverflowContainerChildren(PresContext(), *aState.mReflowInput, ocBounds, ReflowChildFlags::Default, ocStatus, MergeSortedFrameListsFor); } Maybe fragmentainer = GetNearestFragmentainer(aState); // MasonryLayout() can only handle fragmentation in the masonry-axis, // so we let ReflowInFragmentainer() deal with grid-axis fragmentation // in the else-clause below. if (IsMasonry() && !(IsMasonry(eLogicalAxisInline) && fragmentainer.isSome())) { aState.mInFragmentainer = fragmentainer.isSome(); nscoord sz = MasonryLayout( aState, aContentArea, SizingConstraint::NoConstraint, aDesiredSize, aStatus, fragmentainer.ptrOr(nullptr), aContainerSize); if (IsMasonry(eLogicalAxisBlock)) { bSize = aState.mReflowInput->ComputedBSize(); if (bSize == NS_UNCONSTRAINEDSIZE) { bSize = NS_CSS_MINMAX(sz, aState.mReflowInput->ComputedMinBSize(), aState.mReflowInput->ComputedMaxBSize()); } } } else if (MOZ_UNLIKELY(fragmentainer.isSome())) { if (IsMasonry(eLogicalAxisInline) && !GetPrevInFlow()) { // First we do an unconstrained reflow to resolve the item placement // which is then kept as-is in the constrained reflow below. MasonryLayout(aState, aContentArea, SizingConstraint::NoConstraint, aDesiredSize, aStatus, nullptr, aContainerSize); } aState.mInFragmentainer = true; bSize = ReflowInFragmentainer(aState, aContentArea, aDesiredSize, aStatus, *fragmentainer, aContainerSize); } else { aState.mIter.Reset(CSSOrderAwareFrameIterator::ChildFilter::IncludeAll); for (; !aState.mIter.AtEnd(); aState.mIter.Next()) { nsIFrame* child = *aState.mIter; const GridItemInfo* info = nullptr; if (!child->IsPlaceholderFrame()) { info = &aState.mGridItems[aState.mIter.ItemIndex()]; } ReflowInFlowChild(child, info, aContainerSize, 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()) { const 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(wm)); 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 (nsIFrame* child : children) { 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->GetProperty(GridItemContainingBlockRect()); if (!cb) { cb = new nsRect; child->SetProperty(GridItemContainingBlockRect(), cb); } *cb = itemCB.GetPhysicalRect(wm, gridCBPhysicalSize); ++i; } // 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::CBWidthAndHeightChanged; // XXX could be optimized flags |= AbsPosReflowFlags::ConstrainHeight; flags |= AbsPosReflowFlags::IsGridContainerCB; 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) { if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) { return; } MarkInReflow(); DO_GLOBAL_REFLOW_COUNT("nsGridContainerFrame"); DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); if (IsFrameTreeTooDeep(aReflowInput, aDesiredSize, aStatus)) { return; } NormalizeChildLists(); #ifdef DEBUG mDidPushItemsBitMayLie = false; SanityCheckChildListsBeforeReflow(); #endif // DEBUG for (auto& perAxisBaseline : mBaseline) { for (auto& baseline : perAxisBaseline) { baseline = NS_INTRINSIC_ISIZE_UNKNOWN; } } const nsStylePosition* stylePos = aReflowInput.mStylePosition; auto prevInFlow = static_cast(GetPrevInFlow()); if (MOZ_LIKELY(!prevInFlow)) { InitImplicitNamedAreas(stylePos); } else { MOZ_ASSERT(prevInFlow->HasAnyStateBits(kIsSubgridBits) == HasAnyStateBits(kIsSubgridBits), "continuations should have same kIsSubgridBits"); } 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() || aReflowInput.mStyleDisplay->IsContainLayout()) { // We have no grid items, or we're layout-contained. So, we have no // baseline, and 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; const LogicalSize computedSize(wm, computedISize, computedBSize); nscoord consumedBSize = 0; nscoord bSize = 0; if (MOZ_LIKELY(!prevInFlow)) { Grid grid; if (MOZ_LIKELY(!IsSubgrid())) { RepeatTrackSizingInput repeatSizing(aReflowInput.ComputedMinSize(), computedSize, aReflowInput.ComputedMaxSize()); grid.PlaceGridItems(gridReflowInput, repeatSizing); } else { auto* subgrid = GetProperty(Subgrid::Prop()); MOZ_ASSERT(subgrid, "an ancestor forgot to call PlaceGridItems?"); gridReflowInput.mGridItems = subgrid->mGridItems.Clone(); gridReflowInput.mAbsPosItems = subgrid->mAbsPosItems.Clone(); grid.mGridColEnd = subgrid->mGridColEnd; grid.mGridRowEnd = subgrid->mGridRowEnd; } // XXX Technically incorrect: 'contain-intrinsic-block-size: none' is // treated as 0, ignoring our row sizes, when really we should use them but // *they* should be computed as if we had no children. To be fixed in bug // 1488878. const Maybe containBSize = aReflowInput.mFrame->ContainIntrinsicBSize(); const nscoord trackSizingBSize = [&] { // This clamping only applies to auto sizes. if (containBSize && computedBSize == NS_UNCONSTRAINEDSIZE) { return NS_CSS_MINMAX(*containBSize, aReflowInput.ComputedMinBSize(), aReflowInput.ComputedMaxBSize()); } return computedBSize; }(); const LogicalSize containLogicalSize(wm, computedISize, trackSizingBSize); gridReflowInput.CalculateTrackSizes(grid, containLogicalSize, SizingConstraint::NoConstraint); if (containBSize) { bSize = *containBSize; } else { if (IsMasonry(eLogicalAxisBlock)) { bSize = computedBSize; } else { const auto& rowSizes = gridReflowInput.mRows.mSizes; if (MOZ_LIKELY(!IsSubgrid(eLogicalAxisBlock))) { // Note: we can't use GridLineEdge here since we haven't calculated // the rows' mPosition yet (happens in AlignJustifyContent below). for (const auto& sz : rowSizes) { bSize += sz.mBase; } bSize += gridReflowInput.mRows.SumOfGridGaps(); } else if (computedBSize == NS_UNCONSTRAINEDSIZE) { bSize = gridReflowInput.mRows.GridLineEdge( rowSizes.Length(), GridLineSide::BeforeGridGap); } } } } else { consumedBSize = CalcAndCacheConsumedBSize(); gridReflowInput.InitializeForContinuation(this, consumedBSize); // XXX Technically incorrect: 'contain-intrinsic-block-size: none' is // treated as 0, ignoring our row sizes, when really we should use them but // *they* should be computed as if we had no children. To be fixed in bug // 1488878. if (Maybe containBSize = aReflowInput.mFrame->ContainIntrinsicBSize()) { bSize = *containBSize; } else { const uint32_t numRows = gridReflowInput.mRows.mSizes.Length(); bSize = gridReflowInput.mRows.GridLineEdge(numRows, GridLineSide::AfterGridGap); } } if (computedBSize == NS_UNCONSTRAINEDSIZE) { bSize = NS_CSS_MINMAX(bSize, aReflowInput.ComputedMinBSize(), aReflowInput.ComputedMaxBSize()); } else if (aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis()) { nscoord contentBSize = NS_CSS_MINMAX(bSize, aReflowInput.ComputedMinBSize(), aReflowInput.ComputedMaxBSize()); bSize = std::max(contentBSize, computedBSize); } else { bSize = computedBSize; } if (bSize != NS_UNCONSTRAINEDSIZE) { bSize = std::max(bSize - consumedBSize, 0); } auto& bp = gridReflowInput.mBorderPadding; LogicalRect contentArea(wm, bp.IStart(wm), bp.BStart(wm), computedISize, bSize); if (!prevInFlow) { const auto& rowSizes = gridReflowInput.mRows.mSizes; if (!IsRowSubgrid()) { // Apply 'align-content' to the grid. if (computedBSize == NS_UNCONSTRAINEDSIZE && stylePos->mRowGap.IsLengthPercentage() && stylePos->mRowGap.AsLengthPercentage().HasPercent()) { // Re-resolve the row-gap now that we know our intrinsic block-size. gridReflowInput.mRows.mGridGap = nsLayoutUtils::ResolveGapToLength(stylePos->mRowGap, bSize); } if (!gridReflowInput.mRows.mIsMasonry) { auto alignment = stylePos->mAlignContent; gridReflowInput.mRows.AlignJustifyContent(stylePos, alignment, wm, bSize, false); } } else { if (computedBSize == NS_UNCONSTRAINEDSIZE) { bSize = gridReflowInput.mRows.GridLineEdge(rowSizes.Length(), GridLineSide::BeforeGridGap); contentArea.BSize(wm) = std::max(bSize, nscoord(0)); } } // Save the final row sizes for use by subgrids, if needed. if (HasSubgridItems() || IsSubgrid()) { StoreUsedTrackSizes(eLogicalAxisBlock, rowSizes); } } nsSize containerSize = contentArea.Size(wm).GetPhysicalSize(wm); bool repositionChildren = false; if (containerSize.width == NS_UNCONSTRAINEDSIZE && wm.IsVerticalRL()) { // Note that writing-mode:vertical-rl is the only case where the block // logical direction progresses in a negative physical direction, and // therefore block-dir coordinate conversion depends on knowing the width // of the coordinate space in order to translate between the logical and // physical origins. // // A masonry axis size may be unconstrained, otherwise in a regular grid // our intrinsic size is always known by now. We'll re-position // the children below once our size is known. repositionChildren = true; containerSize.width = 0; } containerSize.width += bp.LeftRight(wm); containerSize.height += bp.TopBottom(wm); bSize = ReflowChildren(gridReflowInput, contentArea, containerSize, 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); if (repositionChildren) { nsPoint physicalDelta(aDesiredSize.Width() - bp.LeftRight(wm), 0); for (const auto& item : gridReflowInput.mGridItems) { auto* child = item.mFrame; child->MovePositionBy(physicalDelta); ConsiderChildOverflow(aDesiredSize.mOverflowAreas, child); } } if (Style()->GetPseudoType() == PseudoStyleType::scrolledContent) { // Per spec, the grid area is included in a grid container's scrollable // overflow region [1], as well as the padding on the end-edge sides that // would satisfy the requirements of 'place-content: end' alignment [2]. // // Note that we include the padding from all sides of the grid area, not // just the end sides; this is fine because the grid area is relative to our // content-box origin. The inflated bounds won't go beyond our padding-box // edges on the start sides. // // The margin areas of grid item boxes are also included in the scrollable // overflow region [2]. // // [1] https://drafts.csswg.org/css-grid-1/#overflow // [2] https://drafts.csswg.org/css-overflow-3/#scrollable // Synthesize a grid area covering all columns and rows, and compute its // rect relative to our border-box. // // Note: the grid columns and rows exist only if there is an explicit grid; // or when an implicit grid is needed to place any grid items. See // nsGridContainerFrame::Grid::PlaceGridItems(). const auto numCols = static_cast(gridReflowInput.mCols.mSizes.Length()); const auto numRows = static_cast(gridReflowInput.mRows.mSizes.Length()); if (numCols > 0 && numRows > 0) { const GridArea gridArea(LineRange(0, numCols), LineRange(0, numRows)); const LogicalRect gridAreaRect = gridReflowInput.ContainingBlockFor(gridArea) + LogicalPoint(wm, bp.IStart(wm), bp.BStart(wm)); MOZ_ASSERT(bp == aReflowInput.ComputedLogicalPadding(wm), "A scrolled inner frame shouldn't have any border!"); const LogicalMargin& padding = bp; nsRect physicalGridAreaRectWithPadding = gridAreaRect.GetPhysicalRect(wm, containerSize); physicalGridAreaRectWithPadding.Inflate(padding.GetPhysicalMargin(wm)); aDesiredSize.mOverflowAreas.UnionAllWith(physicalGridAreaRectWithPadding); } nsRect gridItemMarginBoxBounds; for (const auto& item : gridReflowInput.mGridItems) { gridItemMarginBoxBounds = gridItemMarginBoxBounds.Union(item.mFrame->GetMarginRect()); } aDesiredSize.mOverflowAreas.UnionAllWith(gridItemMarginBoxBounds); } // TODO: fix align-tracks alignment in fragments if ((IsMasonry(eLogicalAxisBlock) && !prevInFlow) || IsMasonry(eLogicalAxisInline)) { gridReflowInput.AlignJustifyTracksInMasonryAxis( contentArea.Size(wm), aDesiredSize.PhysicalSize()); } // 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 = CSSOrderAwareFrameIterator::ChildFilter; using Order = CSSOrderAwareFrameIterator::OrderState; bool ordered = gridReflowInput.mIter.ItemsAreAlreadyInOrder(); auto orderState = ordered ? Order::Ordered : Order::Unordered; iter.emplace(this, FrameChildListID::Principal, Filter::SkipPlaceholders, 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 (ShouldGenerateComputedInfo()) { // 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 const auto* subgrid = GetProperty(Subgrid::Prop()); const auto* subgridColRange = subgrid && IsSubgrid(eLogicalAxisInline) ? &subgrid->SubgridCols() : nullptr; LineNameMap colLineNameMap( gridReflowInput.mGridStyle, GetImplicitNamedAreas(), gridReflowInput.mColFunctions, nullptr, subgridColRange, true); uint32_t colTrackCount = gridReflowInput.mCols.mSizes.Length(); nsTArray colTrackPositions(colTrackCount); nsTArray colTrackSizes(colTrackCount); nsTArray colTrackStates(colTrackCount); nsTArray colRemovedRepeatTracks( gridReflowInput.mColFunctions.mRemovedRepeatTracks.Clone()); 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++; } // Get the number of explicit tracks first. The order of argument evaluation // is implementation-defined. We should be OK here because colTrackSizes is // taken by rvalue, but computing the size first prevents any changes in the // argument types of the constructor from breaking this. const uint32_t numColExplicitTracks = IsSubgrid(eLogicalAxisInline) ? colTrackSizes.Length() : gridReflowInput.mColFunctions.NumExplicitTracks(); ComputedGridTrackInfo* colInfo = new ComputedGridTrackInfo( gridReflowInput.mColFunctions.mExplicitGridOffset, numColExplicitTracks, 0, col, std::move(colTrackPositions), std::move(colTrackSizes), std::move(colTrackStates), std::move(colRemovedRepeatTracks), gridReflowInput.mColFunctions.mRepeatAutoStart, colLineNameMap.GetResolvedLineNamesForComputedGridTrackInfo(), IsSubgrid(eLogicalAxisInline), IsMasonry(eLogicalAxisInline)); SetProperty(GridColTrackInfo(), colInfo); const auto* subgridRowRange = subgrid && IsSubgrid(eLogicalAxisBlock) ? &subgrid->SubgridRows() : nullptr; LineNameMap rowLineNameMap( gridReflowInput.mGridStyle, GetImplicitNamedAreas(), gridReflowInput.mRowFunctions, nullptr, subgridRowRange, true); uint32_t rowTrackCount = gridReflowInput.mRows.mSizes.Length(); nsTArray rowTrackPositions(rowTrackCount); nsTArray rowTrackSizes(rowTrackCount); nsTArray rowTrackStates(rowTrackCount); nsTArray rowRemovedRepeatTracks( gridReflowInput.mRowFunctions.mRemovedRepeatTracks.Clone()); 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++; } // Get the number of explicit tracks first. The order of argument evaluation // is implementation-defined. We should be OK here because colTrackSizes is // taken by rvalue, but computing the size first prevents any changes in the // argument types of the constructor from breaking this. const uint32_t numRowExplicitTracks = IsSubgrid(eLogicalAxisBlock) ? rowTrackSizes.Length() : gridReflowInput.mRowFunctions.NumExplicitTracks(); // Row info has to accommodate 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, numRowExplicitTracks, gridReflowInput.mStartRow, row, std::move(rowTrackPositions), std::move(rowTrackSizes), std::move(rowTrackStates), std::move(rowRemovedRepeatTracks), gridReflowInput.mRowFunctions.mRepeatAutoStart, rowLineNameMap.GetResolvedLineNamesForComputedGridTrackInfo(), IsSubgrid(eLogicalAxisBlock), IsMasonry(eLogicalAxisBlock)); SetProperty(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->GetProperty(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, std::move(priorRowInfo->mPositions), std::move(priorRowInfo->mSizes), std::move(priorRowInfo->mStates), std::move(priorRowInfo->mRemovedRepeatTracks), priorRowInfo->mRepeatFirstTrack, std::move(priorRowInfo->mResolvedLineNames), priorRowInfo->mIsSubgrid, priorRowInfo->mIsMasonry); prevInFlow->SetProperty(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. auto& colFunctions = gridReflowInput.mColFunctions; // Generate column lines first. uint32_t capacity = gridReflowInput.mCols.mSizes.Length(); 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 = colLineNameMap.GetExplicitLineNamesAtIndex( col - colFunctions.mExplicitGridOffset); columnLineNames.EmplaceBack(std::move(explicitNames)); } // Get the explicit names that follow a repeat auto declaration. nsTArray> colNamesFollowingRepeat; nsTArray> colBeforeRepeatAuto; nsTArray> colAfterRepeatAuto; // Note: the following is only used for a non-subgridded axis. if (colLineNameMap.HasRepeatAuto()) { MOZ_ASSERT(!colFunctions.mTemplate.IsSubgrid()); // The line name list after the repeatAutoIndex holds the line names // for the first explicit line after the repeat auto declaration. uint32_t repeatAutoEnd = colLineNameMap.RepeatAutoStart() + 1; for (auto* list : colLineNameMap.ExpandedLineNames()[repeatAutoEnd]) { for (auto& name : list->AsSpan()) { colNamesFollowingRepeat.AppendElement(name.AsAtom()); } } auto names = colLineNameMap.TrackAutoRepeatLineNames(); for (auto& name : names[0].AsSpan()) { colBeforeRepeatAuto.AppendElement(name.AsAtom()); } for (auto& name : names[1].AsSpan()) { colAfterRepeatAuto.AppendElement(name.AsAtom()); } } ComputedGridLineInfo* columnLineInfo = new ComputedGridLineInfo( std::move(columnLineNames), std::move(colBeforeRepeatAuto), std::move(colAfterRepeatAuto), std::move(colNamesFollowingRepeat)); SetProperty(GridColumnLineInfo(), columnLineInfo); // Generate row lines next. auto& rowFunctions = gridReflowInput.mRowFunctions; capacity = gridReflowInput.mRows.mSizes.Length(); 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 = rowLineNameMap.GetExplicitLineNamesAtIndex( row - rowFunctions.mExplicitGridOffset); rowLineNames.EmplaceBack(std::move(explicitNames)); } // Get the explicit names that follow a repeat auto declaration. nsTArray> rowNamesFollowingRepeat; nsTArray> rowBeforeRepeatAuto; nsTArray> rowAfterRepeatAuto; // Note: the following is only used for a non-subgridded axis. if (rowLineNameMap.HasRepeatAuto()) { MOZ_ASSERT(!rowFunctions.mTemplate.IsSubgrid()); // The line name list after the repeatAutoIndex holds the line names // for the first explicit line after the repeat auto declaration. uint32_t repeatAutoEnd = rowLineNameMap.RepeatAutoStart() + 1; for (auto* list : rowLineNameMap.ExpandedLineNames()[repeatAutoEnd]) { for (auto& name : list->AsSpan()) { rowNamesFollowingRepeat.AppendElement(name.AsAtom()); } } auto names = rowLineNameMap.TrackAutoRepeatLineNames(); for (auto& name : names[0].AsSpan()) { rowBeforeRepeatAuto.AppendElement(name.AsAtom()); } for (auto& name : names[1].AsSpan()) { rowAfterRepeatAuto.AppendElement(name.AsAtom()); } } ComputedGridLineInfo* rowLineInfo = new ComputedGridLineInfo( std::move(rowLineNames), std::move(rowBeforeRepeatAuto), std::move(rowAfterRepeatAuto), std::move(rowNamesFollowingRepeat)); SetProperty(GridRowLineInfo(), rowLineInfo); // Generate area info for explicit areas. Implicit areas are handled // elsewhere. if (!gridReflowInput.mGridStyle->mGridTemplateAreas.IsNone()) { auto* areas = new StyleOwnedSlice( gridReflowInput.mGridStyle->mGridTemplateAreas.AsAreas()->areas); SetProperty(ExplicitNamedAreasProperty(), areas); } else { RemoveProperty(ExplicitNamedAreasProperty()); } } if (!prevInFlow) { SharedGridData* sharedGridData = GetProperty(SharedGridData::Prop()); if (!aStatus.IsFullyComplete()) { if (!sharedGridData) { sharedGridData = new SharedGridData; SetProperty(SharedGridData::Prop(), sharedGridData); } sharedGridData->mCols.mSizes = std::move(gridReflowInput.mCols.mSizes); sharedGridData->mCols.mContentBoxSize = gridReflowInput.mCols.mContentBoxSize; sharedGridData->mCols.mBaselineSubtreeAlign = gridReflowInput.mCols.mBaselineSubtreeAlign; sharedGridData->mCols.mIsMasonry = gridReflowInput.mCols.mIsMasonry; sharedGridData->mRows.mSizes = std::move(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 = gridReflowInput.mRows.mBaselineSubtreeAlign; sharedGridData->mRows.mIsMasonry = gridReflowInput.mRows.mIsMasonry; sharedGridData->mGridItems = std::move(gridReflowInput.mGridItems); sharedGridData->mAbsPosItems = std::move(gridReflowInput.mAbsPosItems); sharedGridData->mGenerateComputedGridInfo = ShouldGenerateComputedInfo(); } else if (sharedGridData && !GetNextInFlow()) { RemoveProperty(SharedGridData::Prop()); } } FinishAndStoreOverflow(&aDesiredSize); } void nsGridContainerFrame::UpdateSubgridFrameState() { nsFrameState oldBits = GetStateBits() & kIsSubgridBits; nsFrameState newBits = ComputeSelfSubgridMasonryBits() & kIsSubgridBits; if (newBits != oldBits) { RemoveStateBits(kIsSubgridBits); if (!newBits) { RemoveProperty(Subgrid::Prop()); } else { AddStateBits(newBits); } } } nsFrameState nsGridContainerFrame::ComputeSelfSubgridMasonryBits() const { // 'contain:layout/paint' makes us an "independent formatting context", // which prevents us from being a subgrid in this case (but not always). // We will also need to check our containing scroll frame for this property. // https://drafts.csswg.org/css-display-3/#establish-an-independent-formatting-context const auto* display = StyleDisplay(); const bool inhibitSubgrid = display->IsContainLayout() || display->IsContainPaint(); nsFrameState bits = nsFrameState(0); const auto* pos = StylePosition(); // We can only have masonry layout in one axis. if (pos->mGridTemplateRows.IsMasonry()) { bits |= NS_STATE_GRID_IS_ROW_MASONRY; } else if (pos->mGridTemplateColumns.IsMasonry()) { bits |= NS_STATE_GRID_IS_COL_MASONRY; } // Skip our scroll frame and such if we have it. // This will store the outermost frame that shares our content node: const nsIFrame* outerFrame = this; // ...and this will store that frame's parent: auto* parent = GetParent(); while (parent && parent->GetContent() == GetContent()) { // If we find our containing frame has 'contain:layout/paint' we can't be // subgrid, for the same reasons as above. This can happen when this frame // is itself a grid item. const auto* parentDisplay = parent->StyleDisplay(); if (parentDisplay->IsContainLayout() || parentDisplay->IsContainPaint()) { return nsFrameState(0); } outerFrame = parent; parent = parent->GetParent(); } const nsGridContainerFrame* gridParent = do_QueryFrame(parent); if (gridParent) { bool isOrthogonal = GetWritingMode().IsOrthogonalTo(parent->GetWritingMode()); // NOTE: our NS_FRAME_OUT_OF_FLOW isn't set yet so we check our style. bool isOutOfFlow = outerFrame->StyleDisplay()->IsAbsolutelyPositionedStyle(); bool isColSubgrid = pos->mGridTemplateColumns.IsSubgrid() && !inhibitSubgrid; // Subgridding a parent masonry axis makes us use masonry layout too, // unless our other axis is a masonry axis. if (isColSubgrid && parent->HasAnyStateBits(isOrthogonal ? NS_STATE_GRID_IS_ROW_MASONRY : NS_STATE_GRID_IS_COL_MASONRY)) { isColSubgrid = false; if (!HasAnyStateBits(NS_STATE_GRID_IS_ROW_MASONRY)) { bits |= NS_STATE_GRID_IS_COL_MASONRY; } } // OOF subgrids don't create tracks in the parent, so we need to check that // it has one anyway. Otherwise we refuse to subgrid that axis since we // can't place grid items inside a subgrid without at least one track. if (isColSubgrid && isOutOfFlow) { auto parentAxis = isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline; if (!gridParent->WillHaveAtLeastOneTrackInAxis(parentAxis)) { isColSubgrid = false; } } if (isColSubgrid) { bits |= NS_STATE_GRID_IS_COL_SUBGRID; } bool isRowSubgrid = pos->mGridTemplateRows.IsSubgrid() && !inhibitSubgrid; if (isRowSubgrid && parent->HasAnyStateBits(isOrthogonal ? NS_STATE_GRID_IS_COL_MASONRY : NS_STATE_GRID_IS_ROW_MASONRY)) { isRowSubgrid = false; if (!HasAnyStateBits(NS_STATE_GRID_IS_COL_MASONRY)) { bits |= NS_STATE_GRID_IS_ROW_MASONRY; } } if (isRowSubgrid && isOutOfFlow) { auto parentAxis = isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock; if (!gridParent->WillHaveAtLeastOneTrackInAxis(parentAxis)) { isRowSubgrid = false; } } if (isRowSubgrid) { bits |= NS_STATE_GRID_IS_ROW_SUBGRID; } } return bits; } bool nsGridContainerFrame::WillHaveAtLeastOneTrackInAxis( LogicalAxis aAxis) const { if (IsSubgrid(aAxis)) { // This is enforced by refusing to be a subgrid unless our parent has // at least one track in aAxis by ComputeSelfSubgridMasonryBits above. return true; } if (IsMasonry(aAxis)) { return false; } const auto* pos = StylePosition(); const auto& gridTemplate = aAxis == eLogicalAxisBlock ? pos->mGridTemplateRows : pos->mGridTemplateColumns; if (gridTemplate.IsTrackList()) { return true; } for (nsIFrame* child : PrincipalChildList()) { if (!child->IsPlaceholderFrame()) { // A grid item triggers at least one implicit track in each axis. return true; } } if (!pos->mGridTemplateAreas.IsNone()) { return true; } return false; } void nsGridContainerFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, nsIFrame* aPrevInFlow) { nsContainerFrame::Init(aContent, aParent, aPrevInFlow); if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER)) { AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT); } nsFrameState bits = nsFrameState(0); if (MOZ_LIKELY(!aPrevInFlow)) { bits = ComputeSelfSubgridMasonryBits(); } else { bits = aPrevInFlow->GetStateBits() & (NS_STATE_GRID_IS_ROW_MASONRY | NS_STATE_GRID_IS_COL_MASONRY | kIsSubgridBits | NS_STATE_GRID_HAS_COL_SUBGRID_ITEM | NS_STATE_GRID_HAS_ROW_SUBGRID_ITEM); } AddStateBits(bits); } void nsGridContainerFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) { nsContainerFrame::DidSetComputedStyle(aOldStyle); if (!aOldStyle) { return; // Init() already initialized the bits. } UpdateSubgridFrameState(); } nscoord nsGridContainerFrame::IntrinsicISize(gfxContext* aRenderingContext, IntrinsicISizeType aType) { // Calculate the sum of column sizes under intrinsic sizing. // http://dev.w3.org/csswg/css-grid/#intrinsic-sizes NormalizeChildLists(); GridReflowInput state(this, *aRenderingContext); InitImplicitNamedAreas(state.mGridStyle); // XXX optimize // 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. RepeatTrackSizingInput repeatSizing(state.mWM); if (!IsColSubgrid() && state.mColFunctions.mHasRepeatAuto) { repeatSizing.InitFromStyle(eLogicalAxisInline, state.mWM, state.mFrame->Style()); } if ((!IsRowSubgrid() && state.mRowFunctions.mHasRepeatAuto && !(state.mGridStyle->mGridAutoFlow & StyleGridAutoFlow::ROW)) || IsMasonry(eLogicalAxisInline)) { // 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. // Masonry layout always depends on how many rows we have though. repeatSizing.InitFromStyle(eLogicalAxisBlock, state.mWM, state.mFrame->Style()); } Grid grid; if (MOZ_LIKELY(!IsSubgrid())) { grid.PlaceGridItems(state, repeatSizing); // XXX optimize } else { auto* subgrid = GetProperty(Subgrid::Prop()); state.mGridItems = subgrid->mGridItems.Clone(); state.mAbsPosItems = subgrid->mAbsPosItems.Clone(); grid.mGridColEnd = subgrid->mGridColEnd; grid.mGridRowEnd = subgrid->mGridRowEnd; } auto constraint = aType == IntrinsicISizeType::MinISize ? SizingConstraint::MinContent : SizingConstraint::MaxContent; if (IsMasonry(eLogicalAxisInline)) { ReflowOutput desiredSize(state.mWM); nsSize containerSize; LogicalRect contentArea(state.mWM); nsReflowStatus status; state.mRows.mSizes.SetLength(grid.mGridRowEnd); state.CalculateTrackSizesForAxis(eLogicalAxisInline, grid, NS_UNCONSTRAINEDSIZE, constraint); return MasonryLayout(state, contentArea, constraint, desiredSize, status, nullptr, containerSize); } if (grid.mGridColEnd == 0) { return nscoord(0); } state.CalculateTrackSizesForAxis(eLogicalAxisInline, grid, NS_UNCONSTRAINEDSIZE, constraint); if (MOZ_LIKELY(!IsSubgrid())) { return state.mCols.SumOfGridTracksAndGaps(); } const auto& last = state.mCols.mSizes.LastElement(); return last.mPosition + last.mBase; } nscoord nsGridContainerFrame::GetMinISize(gfxContext* aRC) { auto* f = static_cast(FirstContinuation()); if (f != this) { return f->GetMinISize(aRC); } DISPLAY_MIN_INLINE_SIZE(this, mCachedMinISize); if (mCachedMinISize == NS_INTRINSIC_ISIZE_UNKNOWN) { Maybe containISize = ContainIntrinsicISize(); mCachedMinISize = containISize ? *containISize : IntrinsicISize(aRC, IntrinsicISizeType::MinISize); } return mCachedMinISize; } nscoord nsGridContainerFrame::GetPrefISize(gfxContext* aRC) { auto* f = static_cast(FirstContinuation()); if (f != this) { return f->GetPrefISize(aRC); } DISPLAY_PREF_INLINE_SIZE(this, mCachedPrefISize); if (mCachedPrefISize == NS_INTRINSIC_ISIZE_UNKNOWN) { Maybe containISize = ContainIntrinsicISize(); mCachedPrefISize = containISize ? *containISize : IntrinsicISize(aRC, IntrinsicISizeType::PrefISize); } return mCachedPrefISize; } void nsGridContainerFrame::MarkIntrinsicISizesDirty() { mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN; mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN; for (auto& perAxisBaseline : mBaseline) { for (auto& baseline : perAxisBaseline) { baseline = NS_INTRINSIC_ISIZE_UNKNOWN; } } nsContainerFrame::MarkIntrinsicISizesDirty(); } void nsGridContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { DisplayBorderBackgroundOutline(aBuilder, aLists); if (GetPrevInFlow()) { DisplayOverflowContainers(aBuilder, aLists); } // Our children are all grid-level boxes, which behave the same as // inline-blocks in painting, so their borders/backgrounds all go on // the BlockBorderBackgrounds list. typedef CSSOrderAwareFrameIterator::OrderState OrderState; OrderState order = HasAnyStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER) ? OrderState::Ordered : OrderState::Unordered; CSSOrderAwareFrameIterator iter( this, FrameChildListID::Principal, CSSOrderAwareFrameIterator::ChildFilter::IncludeAll, order); const auto flags = DisplayFlagsForFlexOrGridItem(); for (; !iter.AtEnd(); iter.Next()) { nsIFrame* child = *iter; BuildDisplayListForChild(aBuilder, child, aLists, flags); } } bool nsGridContainerFrame::DrainSelfOverflowList() { return DrainAndMergeSelfOverflowList(); } void nsGridContainerFrame::AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) { NoteNewChildren(aListID, aFrameList); nsContainerFrame::AppendFrames(aListID, std::move(aFrameList)); } void nsGridContainerFrame::InsertFrames( ChildListID aListID, nsIFrame* aPrevFrame, const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) { NoteNewChildren(aListID, aFrameList); nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine, std::move(aFrameList)); } void nsGridContainerFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) { MOZ_ASSERT(aListID == FrameChildListID::Principal, "unexpected child list"); #ifdef DEBUG SetDidPushItemsBitIfNeeded(aListID, aOldFrame); #endif nsContainerFrame::RemoveFrame(aListID, aOldFrame); } StyleAlignFlags nsGridContainerFrame::CSSAlignmentForAbsPosChild( const ReflowInput& aChildRI, LogicalAxis aLogicalAxis) const { MOZ_ASSERT(aChildRI.mFrame->IsAbsolutelyPositioned(), "This method should only be called for abspos children"); StyleAlignFlags alignment = (aLogicalAxis == eLogicalAxisInline) ? aChildRI.mStylePosition->UsedJustifySelf(Style())._0 : aChildRI.mStylePosition->UsedAlignSelf(Style())._0; // Extract and strip the flag bits StyleAlignFlags alignmentFlags = alignment & StyleAlignFlags::FLAG_BITS; alignment &= ~StyleAlignFlags::FLAG_BITS; if (alignment == StyleAlignFlags::NORMAL) { // "the 'normal' keyword behaves as 'start' on replaced // absolutely-positioned boxes, and behaves as 'stretch' on all other // absolutely-positioned boxes." // https://drafts.csswg.org/css-align/#align-abspos // https://drafts.csswg.org/css-align/#justify-abspos alignment = aChildRI.mFrame->IsFrameOfType(nsIFrame::eReplaced) ? StyleAlignFlags::START : StyleAlignFlags::STRETCH; } else if (alignment == StyleAlignFlags::FLEX_START) { alignment = StyleAlignFlags::START; } else if (alignment == StyleAlignFlags::FLEX_END) { alignment = StyleAlignFlags::END; } else if (alignment == StyleAlignFlags::LEFT || alignment == StyleAlignFlags::RIGHT) { if (aLogicalAxis == eLogicalAxisInline) { const bool isLeft = (alignment == StyleAlignFlags::LEFT); WritingMode wm = GetWritingMode(); alignment = (isLeft == wm.IsBidiLTR()) ? StyleAlignFlags::START : StyleAlignFlags::END; } else { alignment = StyleAlignFlags::START; } } else if (alignment == StyleAlignFlags::BASELINE) { alignment = StyleAlignFlags::START; } else if (alignment == StyleAlignFlags::LAST_BASELINE) { alignment = StyleAlignFlags::END; } return (alignment | alignmentFlags); } 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::First ? 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::Grid); } 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::Last) { baseline = size - baseline; // convert to distance from border-box end } } else { baseline = ::SynthesizeBaselineFromBorderBox(aGroup, childWM, size); } } return aGroup == BaselineSharingGroup::First ? start + baseline : aCBSize - start - size + baseline; } void nsGridContainerFrame::CalculateBaselines( BaselineSet aBaselineSet, CSSOrderAwareFrameIterator* 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::First]; if (!(aBaselineSet & BaselineSet::eFirst)) { mBaseline[axis][BaselineSharingGroup::First] = ::SynthesizeBaselineFromBorderBox(BaselineSharingGroup::First, aWM, aCBSize); } else if (firstBaseline == NS_INTRINSIC_ISIZE_UNKNOWN) { FindItemInGridOrderResult gridOrderFirstItem = FindFirstItemInGridOrder( *aIter, *aGridItems, axis == eLogicalAxisBlock ? &GridArea::mRows : &GridArea::mCols, axis == eLogicalAxisBlock ? &GridArea::mCols : &GridArea::mRows, aFragmentStartTrack); mBaseline[axis][BaselineSharingGroup::First] = SynthesizeBaseline( gridOrderFirstItem, axis, BaselineSharingGroup::First, 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::AfterGridGap) : nscoord(0); // no content gap at start of fragment mBaseline[axis][BaselineSharingGroup::First] = aCBBorderPaddingStart + gapBeforeStartTrack + firstBaseline; } auto lastBaseline = aTracks.mBaseline[BaselineSharingGroup::Last]; if (!(aBaselineSet & BaselineSet::eLast)) { mBaseline[axis][BaselineSharingGroup::Last] = ::SynthesizeBaselineFromBorderBox(BaselineSharingGroup::Last, aWM, aCBSize); } else if (lastBaseline == NS_INTRINSIC_ISIZE_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 = ReverseCSSOrderAwareFrameIterator; auto orderState = aIter->ItemsAreAlreadyInOrder() ? Iter::OrderState::Ordered : Iter::OrderState::Unordered; Iter iter(this, FrameChildListID::Principal, Iter::ChildFilter::SkipPlaceholders, orderState); iter.SetItemCount(aGridItems->Length()); FindItemInGridOrderResult gridOrderLastItem = FindLastItemInGridOrder( iter, *aGridItems, axis == eLogicalAxisBlock ? &GridArea::mRows : &GridArea::mCols, axis == eLogicalAxisBlock ? &GridArea::mCols : &GridArea::mRows, aFragmentStartTrack, aFirstExcludedTrack); mBaseline[axis][BaselineSharingGroup::Last] = SynthesizeBaseline(gridOrderLastItem, axis, BaselineSharingGroup::Last, 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::BeforeGridGap) - aTracks.GridLineEdge(aFragmentStartTrack, GridLineSide::BeforeGridGap); mBaseline[axis][BaselineSharingGroup::Last] = (aCBSize - borderBoxStartToEndOfEndTrack) + lastBaseline; } } #ifdef DEBUG_FRAME_DUMP nsresult nsGridContainerFrame::GetFrameName(nsAString& aResult) const { return MakeFrameName(u"GridContainer"_ns, aResult); } void nsGridContainerFrame::ExtraContainerFrameInfo(nsACString& aTo) const { if (const void* const subgrid = GetProperty(Subgrid::Prop())) { aTo += nsPrintfCString(" [subgrid=%p]", subgrid); } } #endif /* static */ nsGridContainerFrame::FindItemInGridOrderResult nsGridContainerFrame::FindFirstItemInGridOrder( CSSOrderAwareFrameIterator& 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.ItemIndex()]; 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( ReverseCSSOrderAwareFrameIterator& 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.ItemIndex()]; // 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; } nsGridContainerFrame::UsedTrackSizes* nsGridContainerFrame::GetUsedTrackSizes() const { return GetProperty(UsedTrackSizes::Prop()); } void nsGridContainerFrame::StoreUsedTrackSizes( LogicalAxis aAxis, const nsTArray& aSizes) { auto* uts = GetUsedTrackSizes(); if (!uts) { uts = new UsedTrackSizes(); SetProperty(UsedTrackSizes::Prop(), uts); } uts->mSizes[aAxis] = aSizes.Clone(); uts->mCanResolveLineRangeSize[aAxis] = true; // XXX is resetting these bits necessary? for (auto& sz : uts->mSizes[aAxis]) { sz.mState &= ~(TrackSize::eFrozen | TrackSize::eSkipGrowUnlimited | TrackSize::eInfinitelyGrowable); } } #ifdef DEBUG void nsGridContainerFrame::SetInitialChildList(ChildListID aListID, nsFrameList&& aChildList) { ChildListIDs supportedLists = {FrameChildListID::Principal}; // We don't handle the FrameChildListID::Backdrop frames in any way, but it // only contains a placeholder for ::backdrop which is OK to not reflow (for // now anyway). supportedLists += FrameChildListID::Backdrop; MOZ_ASSERT(supportedLists.contains(aListID), "unexpected child list"); return nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList)); } void nsGridContainerFrame::TrackSize::DumpStateBits(StateBits aState) { printf("min:"); if (aState & eAutoMinSizing) { printf("auto-min "); } else if (aState & eMinContentMinSizing) { printf("min-content "); } else if (aState & eMaxContentMinSizing) { printf("max-content "); } printf(" max:"); if (aState & eAutoMaxSizing) { printf("auto "); } else if (aState & eMinContentMaxSizing) { printf("min-content "); } else if (aState & eMaxContentMaxSizing) { printf("max-content "); } else if (aState & eFlexMaxSizing) { printf("flex "); } if (aState & eFrozen) { printf("frozen "); } if (aState & eModified) { printf("modified "); } if (aState & eBreakBefore) { printf("break-before "); } } void nsGridContainerFrame::TrackSize::Dump() const { printf("mPosition=%d mBase=%d mLimit=%d ", mPosition, mBase, mLimit); DumpStateBits(mState); } #endif // DEBUG nsGridContainerFrame* nsGridContainerFrame::GetGridContainerFrame( nsIFrame* aFrame) { nsGridContainerFrame* gridFrame = nullptr; if (aFrame) { nsIFrame* inner = aFrame; if (MOZ_UNLIKELY(aFrame->IsFieldSetFrame())) { inner = static_cast(aFrame)->GetInner(); } // Since "Get" methods like GetInner and GetContentInsertionFrame can // return null, we check the return values before dereferencing. Our // calling pattern makes this unlikely, but we're being careful. nsIFrame* insertionFrame = inner ? inner->GetContentInsertionFrame() : nullptr; nsIFrame* possibleGridFrame = insertionFrame ? insertionFrame : aFrame; gridFrame = possibleGridFrame->IsGridContainerFrame() ? static_cast(possibleGridFrame) : nullptr; } return gridFrame; } nsGridContainerFrame* nsGridContainerFrame::GetGridFrameWithComputedInfo( nsIFrame* aFrame) { nsGridContainerFrame* gridFrame = GetGridContainerFrame(aFrame); if (!gridFrame) { return nullptr; } auto HasComputedInfo = [](const nsGridContainerFrame& aFrame) -> bool { return aFrame.HasProperty(GridColTrackInfo()) && aFrame.HasProperty(GridRowTrackInfo()) && aFrame.HasProperty(GridColumnLineInfo()) && aFrame.HasProperty(GridRowLineInfo()); }; if (HasComputedInfo(*gridFrame)) { return gridFrame; } // Trigger a reflow that generates additional grid property data. // Hold onto aFrame while we do this, in case reflow destroys it. AutoWeakFrame weakFrameRef(gridFrame); RefPtr presShell = gridFrame->PresShell(); gridFrame->SetShouldGenerateComputedInfo(true); presShell->FrameNeedsReflow(gridFrame, IntrinsicDirty::None, NS_FRAME_IS_DIRTY); presShell->FlushPendingNotifications(FlushType::Layout); // If the weakFrameRef is no longer valid, then we must bail out. if (!weakFrameRef.IsAlive()) { return nullptr; } // This can happen if for some reason we ended up not reflowing, like in print // preview under some circumstances. if (MOZ_UNLIKELY(!HasComputedInfo(*gridFrame))) { return nullptr; } return gridFrame; } // TODO: This is a rather dumb implementation of nsILineIterator, but it's // better than our pre-existing behavior. Ideally, we should probably use the // grid information to return a meaningful number of lines etc. bool nsGridContainerFrame::IsLineIteratorFlowRTL() { return false; } int32_t nsGridContainerFrame::GetNumLines() const { return mFrames.GetLength(); } Result nsGridContainerFrame::GetLine( int32_t aLineNumber) { if (aLineNumber < 0 || aLineNumber >= GetNumLines()) { return Err(NS_ERROR_FAILURE); } LineInfo rv; nsIFrame* f = mFrames.FrameAt(aLineNumber); rv.mLineBounds = f->GetRect(); rv.mFirstFrameOnLine = f; rv.mNumFramesOnLine = 1; return rv; } int32_t nsGridContainerFrame::FindLineContaining(nsIFrame* aFrame, int32_t aStartLine) { const int32_t index = mFrames.IndexOf(aFrame); if (index < 0) { return -1; } if (index < aStartLine) { return -1; } return index; } NS_IMETHODIMP nsGridContainerFrame::CheckLineOrder(int32_t aLine, bool* aIsReordered, nsIFrame** aFirstVisual, nsIFrame** aLastVisual) { *aIsReordered = false; *aFirstVisual = nullptr; *aLastVisual = nullptr; return NS_OK; } NS_IMETHODIMP nsGridContainerFrame::FindFrameAt(int32_t aLineNumber, nsPoint aPos, nsIFrame** aFrameFound, bool* aPosIsBeforeFirstFrame, bool* aPosIsAfterLastFrame) { const auto wm = GetWritingMode(); const LogicalPoint pos(wm, aPos, GetSize()); *aFrameFound = nullptr; *aPosIsBeforeFirstFrame = true; *aPosIsAfterLastFrame = false; nsIFrame* f = mFrames.FrameAt(aLineNumber); if (!f) { return NS_OK; } auto rect = f->GetLogicalRect(wm, GetSize()); *aFrameFound = f; *aPosIsBeforeFirstFrame = pos.I(wm) < rect.IStart(wm); *aPosIsAfterLastFrame = pos.I(wm) > rect.IEnd(wm); return NS_OK; }