diff options
Diffstat (limited to 'layout/generic/nsGridContainerFrame.cpp')
-rw-r--r-- | layout/generic/nsGridContainerFrame.cpp | 9947 |
1 files changed, 9947 insertions, 0 deletions
diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp new file mode 100644 index 0000000000..4ea6542f13 --- /dev/null +++ b/layout/generic/nsGridContainerFrame.cpp @@ -0,0 +1,9947 @@ +/* -*- 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 <functional> +#include <limits> +#include <stdlib.h> // for div() +#include <type_traits> +#include "gfxContext.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/Baseline.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/CSSAlignUtils.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/dom/Grid.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 "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<LengthPercentage, StyleInteger>; +using TrackRepeat = StyleGenericTrackRepeat<LengthPercentage, StyleInteger>; +using NameList = StyleOwnedSlice<StyleCustomIdent>; +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<const StyleOwnedSlice<StyleCustomIdent>> +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()) { + // <flex> behaves like minmax(auto, <flex>) + 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 <typename Size> +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<nscoord> mBaselineSubtreeSize; + StateBits mState; +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(TrackSize::StateBits) + +static_assert( + std::is_trivially_copyable<nsGridContainerFrame::TrackSize>::value, + "Must be trivially copyable"); +static_assert( + std::is_trivially_destructible<nsGridContainerFrame::TrackSize>::value, + "Must be trivially destructible"); + +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, + // <percentage> 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(), "<flex> 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<uint32_t>(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<uint32_t>& aNumRemovedTracks) { + MOZ_ASSERT(mStart != kAutoLine, "invalid resolved line for a grid item"); + MOZ_ASSERT(mEnd != kAutoLine, "invalid resolved line for a grid item"); + uint32_t numRemovedTracks = aNumRemovedTracks[mStart]; + MOZ_ASSERT(numRemovedTracks == aNumRemovedTracks[mEnd], + "tracks that a grid item spans can't be removed"); + mStart -= numRemovedTracks; + mEnd -= numRemovedTracks; + } + /** + * Translate the lines to account for (empty) removed tracks. This method + * is only for abs.pos. children and should only be called after placement. + * Same as for in-flow items, but we don't touch 'auto' lines here and we + * also need to adjust areas that span into the removed tracks. + */ + void AdjustAbsPosForRemovedTracks( + const nsTArray<uint32_t>& aNumRemovedTracks) { + if (mStart != 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<TrackSize>& aTrackSizes, + nscoord* aPos, nscoord* aLength) const; + /** + * Given an array of track sizes, return the length of the tracks in this + * line range. + */ + nscoord ToLength(const nsTArray<TrackSize>& aTrackSizes) const; + /** + * Given an array of track sizes and a grid origin coordinate, adjust the + * abs.pos. containing block along an axis given by aPos and aLength. + * aPos and aLength should already be initialized to the grid container + * containing block for this axis before calling this method. + */ + void ToPositionAndLengthForAbsPos(const Tracks& aTracks, nscoord aGridOrigin, + nscoord* aPos, nscoord* aLength) const; + + 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<uint32_t>& 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<GridItemInfo> mGridItems; + // The subgrid's abs.pos. items. + nsTArray<GridItemInfo> 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<uint32_t>& 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<nsTArray<TrackSize>> mSizes; + // True if mSizes can be used to resolve line range sizes in an axis. + PerLogicalAxis<bool> 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<size_t>& 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 <auto-repeat> 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<const TrackListValue> 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<std::pair<size_t, size_t>> 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<bool> 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<int32_t>(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<const NameList> 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<uint32_t>( + 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(<integer>, ...) 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<uint32_t>& 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<uint32_t>& 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<nsTArray<StyleCustomIdent>> + GetResolvedLineNamesForComputedGridTrackInfo() const { + nsTArray<nsTArray<StyleCustomIdent>> result; + for (auto& expandedLine : mExpandedLineNames) { + nsTArray<StyleCustomIdent> line; + for (auto* chunk : expandedLine) { + for (auto& name : chunk->AsSpan()) { + line.AppendElement(name); + } + } + result.AppendElement(std::move(line)); + } + return result; + } + + nsTArray<RefPtr<nsAtom>> GetExplicitLineNamesAtIndex(uint32_t aIndex) const { + nsTArray<RefPtr<nsAtom>> 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<SmallPointerArray<const NameList>>& ExpandedLineNames() const { + return mExpandedLineNames; + } + const Span<const StyleOwnedSlice<StyleCustomIdent>>& + 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<uint32_t>& 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<uint32_t>& 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<const StyleCustomIdent> 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<const NameList> GetLineNamesAt( + const uint32_t aIndex) const { + SmallPointerArray<const NameList> 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<SmallPointerArray<const NameList>> mExpandedLineNames; + // The repeat(auto-fill/fit) track value, if any. (always empty for subgrid) + Span<const StyleOwnedSlice<StyleCustomIdent>> 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<ItemBaselineData>& aBaselineItems, + BaselineSharingGroup aBaselineGroup); + + /** + * Initialize grid item baseline state and offsets. + */ + void InitializeItemBaselines(GridReflowInput& aState, + nsTArray<GridItemInfo>& 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<GridItemInfo>& aGridItems, + BaselineAlignmentSet aSet, const nsSize& aContainerSize, + nsTArray<nscoord>& aTrackSizes, + nsTArray<ItemBaselineData>& aFirstBaselineItems, + nsTArray<ItemBaselineData>& 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 <TrackSizingPhase phase> + 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<bool(uint32_t aTrack, nscoord aMinSize, nscoord* aSize)>; + + // Helper method for ResolveIntrinsicSize. + template <TrackSizingPhase phase> + bool GrowSizeForSpanningItems( + nsTArray<SpanningItemData>::iterator aIter, + nsTArray<SpanningItemData>::iterator aIterEnd, + nsTArray<uint32_t>& aTracks, nsTArray<TrackSize>& aPlan, + nsTArray<TrackSize>& 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<GridItemInfo>& aGridItems, + const TrackSizingFunctions& aFunctions, + LineRange GridArea::*aRange, + nscoord aPercentageBasis, + SizingConstraint aConstraint); + + /** + * Helper for ResolveIntrinsicSize. It implements step 1 "size tracks to fit + * non-spanning items" in the spec. Return true if the track has a <flex> + * max-sizing function, false otherwise. + */ + bool 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 <TrackSizingPhase phase> + 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 <TrackSizingPhase phase> + nscoord CollectGrowable(nscoord aAvailableSpace, const LineRange& aRange, + TrackSize::StateBits aSelector, + nsTArray<uint32_t>& aGrowableTracks) const { + MOZ_ASSERT(aAvailableSpace > 0, "why call me?"); + nscoord space = aAvailableSpace - mGridGap * (aRange.Extent() - 1); + for (auto i : aRange.Range()) { + const TrackSize& sz = mSizes[i]; + space -= StartSizeInDistribution<phase>(sz); + if (space <= 0) { + return 0; + } + if (sz.mState & aSelector) { + aGrowableTracks.AppendElement(i); + } + } + return aGrowableTracks.IsEmpty() ? 0 : space; + } + + template <TrackSizingPhase phase> + void InitializeItemPlan(nsTArray<TrackSize>& aItemPlan, + const nsTArray<uint32_t>& aTracks) const { + for (uint32_t track : aTracks) { + auto& plan = aItemPlan[track]; + const TrackSize& sz = mSizes[track]; + plan.mBase = StartSizeInDistribution<phase>(sz); + bool unlimited = sz.mState & TrackSize::eInfinitelyGrowable; + plan.mLimit = unlimited ? NS_UNCONSTRAINEDSIZE : sz.mLimit; + plan.mState = sz.mState; + } + } + + template <TrackSizingPhase phase> + void InitializePlan(nsTArray<TrackSize>& 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<phase>(sz); + MOZ_ASSERT(phase == TrackSizingPhase::MaxContentMaximums || + !(sz.mState & TrackSize::eInfinitelyGrowable), + "forgot to reset the eInfinitelyGrowable bit?"); + plan.mState = sz.mState; + } + } + + template <TrackSizingPhase phase> + void CopyPlanToSize(const nsTArray<TrackSize>& 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<TrackSize>& aPlan, + const nsTArray<uint32_t>& 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<nscoord>(space / numGrowable, 1); + for (uint32_t track : aGrowableTracks) { + TrackSize& sz = aPlan[track]; + if (sz.IsFrozen()) { + continue; + } + nscoord newBase = sz.mBase + spacePerTrack; + nscoord limit = sz.mLimit; + if (MOZ_UNLIKELY((sz.mState & TrackSize::eFitContent) && + aFitContentClamper)) { + // Clamp the limit to the fit-content() size, for §12.5.2 step 5/6. + aFitContentClamper(track, sz.mBase, &limit); + } + if (newBase > limit) { + nscoord consumed = limit - sz.mBase; + if (consumed > 0) { + space -= consumed; + sz.mBase = limit; + } + sz.mState |= TrackSize::eFrozen; + if (--numGrowable == 0) { + return space; + } + } else { + sz.mBase = newBase; + space -= spacePerTrack; + } + MOZ_ASSERT(space >= 0); + if (space == 0) { + return 0; + } + } + } + MOZ_ASSERT_UNREACHABLE("we don't exit the loop above except by return"); + return 0; + } + + /** + * Helper for GrowSelectedTracksUnlimited. For the set of tracks (S) that + * match aMinSizingSelector: if a track in S doesn't match aMaxSizingSelector + * then mark it with aSkipFlag. If all tracks in S were marked then unmark + * them. Return aNumGrowable minus the number of tracks marked. It is + * assumed that aPlan have no aSkipFlag set for tracks in aGrowableTracks + * on entry to this method. + */ + static uint32_t MarkExcludedTracks(nsTArray<TrackSize>& aPlan, + uint32_t aNumGrowable, + const nsTArray<uint32_t>& 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 <TrackSizingPhase phase> + static uint32_t MarkExcludedTracks(nsTArray<TrackSize>& aPlan, + const nsTArray<uint32_t>& 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<TrackSize>& aPlan, + const nsTArray<uint32_t>& aGrowableTracks, uint32_t aNumGrowable, + const FitContentClamper& aFitContentClamper) const { + MOZ_ASSERT(aAvailableSpace > 0 && aGrowableTracks.Length() > 0 && + aNumGrowable <= aGrowableTracks.Length()); + nscoord space = aAvailableSpace; + DebugOnly<bool> didClamp = false; + while (aNumGrowable) { + nscoord spacePerTrack = std::max<nscoord>(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 <TrackSizingPhase phase> + void DistributeToTrackSizes(nscoord aAvailableSpace, + nsTArray<TrackSize>& aPlan, + nsTArray<TrackSize>& aItemPlan, + nsTArray<uint32_t>& aGrowableTracks, + TrackSize::StateBits aSelector, + const FitContentClamper& aFitContentClamper) { + InitializeItemPlan<phase>(aItemPlan, aGrowableTracks); + nscoord space = GrowTracksToLimit(aAvailableSpace, aItemPlan, + aGrowableTracks, aFitContentClamper); + if (space > 0) { + uint32_t numGrowable = + MarkExcludedTracks<phase>(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<nscoord>(space / numGrowable, 1); + for (uint32_t i = 0; i < numTracks && space > 0; ++i) { + TrackSize& sz = mSizes[i]; + if (sz.mBase == sz.mLimit) { + continue; + } + nscoord newBase = sz.mBase + spacePerTrack; + if (newBase >= sz.mLimit) { + space -= sz.mLimit - sz.mBase; + sz.mBase = sz.mLimit; + --numGrowable; + } else { + space -= spacePerTrack; + sz.mBase = newBase; + } + } + } + } + } + + /** + * Implements "12.7.1. Find the Size of an 'fr'". + * http://dev.w3.org/csswg/css-grid/#algo-find-fr-size + * (The returned value is a 'nscoord' divided by a factor - a floating type + * is used to avoid intermediary rounding errors.) + */ + float FindFrUnitSize(const LineRange& aRange, + const nsTArray<uint32_t>& aFlexTracks, + const TrackSizingFunctions& aFunctions, + nscoord aSpaceToFill) const; + + /** + * Implements the "find the used flex fraction" part of StretchFlexibleTracks. + * (The returned value is a 'nscoord' divided by a factor - a floating type + * is used to avoid intermediary rounding errors.) + */ + float FindUsedFlexFraction(GridReflowInput& aState, + nsTArray<GridItemInfo>& aGridItems, + const nsTArray<uint32_t>& aFlexTracks, + const TrackSizingFunctions& aFunctions, + nscoord aAvailableSize) const; + + /** + * Implements "12.7. Stretch Flexible Tracks" + * http://dev.w3.org/csswg/css-grid/#algo-flex-tracks + */ + void StretchFlexibleTracks(GridReflowInput& aState, + nsTArray<GridItemInfo>& aGridItems, + const TrackSizingFunctions& aFunctions, + nscoord aAvailableSize); + + /** + * Implements "12.3. Track Sizing Algorithm" + * http://dev.w3.org/csswg/css-grid/#algo-track-sizing + */ + void CalculateSizes(GridReflowInput& aState, + nsTArray<GridItemInfo>& aGridItems, + const TrackSizingFunctions& aFunctions, + nscoord 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<TrackSize, 32> mSizes; + nscoord mContentBoxSize; + nscoord mGridGap; + // The first(last)-baseline for the first(last) track in this axis. + PerBaseline<nscoord> 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<StyleAlignFlags> 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<RowData> mOriginalRowData; + nsTArray<GridItemInfo> mGridItems; + nsTArray<GridItemInfo> mAbsPosItems; + bool mGenerateComputedGridInfo; + + /** + * Only set on the first-in-flow. Continuations will Initialize() their + * GridReflowInput from it. + */ + NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, SharedGridData) +}; + +struct MOZ_STACK_CLASS nsGridContainerFrame::GridReflowInput { + GridReflowInput(nsGridContainerFrame* aFrame, const ReflowInput& aRI) + : GridReflowInput(aFrame, *aRI.mRenderingContext, &aRI, + aRI.mStylePosition, aRI.GetWritingMode()) {} + GridReflowInput(nsGridContainerFrame* aFrame, 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<size_t> len = mGridItems.Length(); + for (auto& itemInfo : mSharedGridData->mGridItems) { + if (itemInfo.mFrame == childFirstInFlow) { + auto item = + mGridItems.AppendElement(GridItemInfo(child, itemInfo.mArea)); + // Copy the item's baseline data so that the item's last fragment can + // do 'last baseline' alignment if necessary. + item->mState[0] |= itemInfo.mState[0] & ItemState::eAllBaselineBits; + item->mState[1] |= itemInfo.mState[1] & ItemState::eAllBaselineBits; + item->mBaselineOffset[0] = itemInfo.mBaselineOffset[0]; + item->mBaselineOffset[1] = itemInfo.mBaselineOffset[1]; + 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<size_t> len = mAbsPosItems.Length(); + for (auto& itemInfo : mSharedGridData->mAbsPosItems) { + if (itemInfo.mFrame == childFirstInFlow) { + mAbsPosItems.AppendElement(GridItemInfo(f, itemInfo.mArea)); + break; + } + } + MOZ_ASSERT(mAbsPosItems.Length() == len + 1, "can't find GridItemInfo"); + } + + // Copy in the computed grid info state bit + if (mSharedGridData->mGenerateComputedGridInfo) { + aGridContainerFrame->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<GridItemInfo>& aItems, + nsTArray<GridItemInfo>& 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<GridItemInfo>& 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<GridItemInfo> mGridItems; + /** + * Info about each grid-aligned abs.pos. child. + */ + nsTArray<GridItemInfo> mAbsPosItems; + + /** + * @note mReflowInput may be null when using the 2nd ctor above. In this case + * we'll construct a dummy parent reflow 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 <typename IsEmptyFuncT> + static Maybe<nsTArray<uint32_t>> 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<int32_t, int32_t> 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<Cell>& cellsInRow = mCells[i]; + cellsInRow.EnsureLengthAtLeast(numCols); + for (auto j = aGridArea.mCols.mStart; j < numCols; ++j) { + cellsInRow[j].mIsOccupied = true; + } + } + } + + uint32_t IsEmptyCol(uint32_t aCol) const { + for (auto& row : mCells) { + if (aCol < row.Length() && row[aCol].mIsOccupied) { + return false; + } + } + return true; + } + uint32_t IsEmptyRow(uint32_t aRow) const { + if (aRow >= mCells.Length()) { + return true; + } + for (const Cell& cell : mCells[aRow]) { + if (cell.mIsOccupied) { + return false; + } + } + return true; + } +#ifdef DEBUG + void Dump() const { + const size_t numRows = mCells.Length(); + for (size_t i = 0; i < numRows; ++i) { + const nsTArray<Cell>& cellsInRow = mCells[i]; + const size_t numCols = cellsInRow.Length(); + printf("%lu:\t", (unsigned long)i + 1); + for (size_t j = 0; j < numCols; ++j) { + printf(cellsInRow[j].mIsOccupied ? "X " : ". "); + } + printf("\n"); + } + } +#endif + + nsTArray<nsTArray<Cell>> mCells; + }; + + /** + * State for each cell in the grid. + */ + CellMap mCellMap; + /** + * @see HasImplicitNamedArea. + */ + ImplicitNamedAreas* mAreas; + /** + * The last column grid line (1-based) in the explicit grid. + * (i.e. the number of explicit columns + 1) + */ + uint32_t mExplicitGridColEnd; + /** + * The last row grid line (1-based) in the explicit grid. + * (i.e. the number of explicit rows + 1) + */ + uint32_t mExplicitGridRowEnd; + // Same for the implicit grid, except these become zero-based after + // resolving definite lines. + uint32_t mGridColEnd; + uint32_t mGridRowEnd; + + /** + * Offsets from the start of the implicit grid to the start of the translated + * explicit grid. They are zero if there are no implicit lines before 1,1. + * e.g. "grid-column: span 3 / 1" makes mExplicitGridOffsetCol = 3 and the + * corresponding GridArea::mCols will be 0 / 3 in the zero-based translated + * grid. + */ + uint32_t mExplicitGridOffsetCol; + uint32_t mExplicitGridOffsetRow; + + /** + * 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<nsFieldSetFrame*>(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<TrackSize>& 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<nsIFrame*>(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<TrackSizingFunctions> 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<StyleAlignFlags> 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<LineNameList> 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<nsString> 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<nsAtom> 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: <integer> must not be zero."); + line = int32_t(aFromIndex) + aNth; + } else { + if (aNth == 0) { + // <integer> was omitted; treat it as 1. + aNth = 1; + } + bool isNameOnly = !aLine.is_span && aLine.line_num == 0; + if (isNameOnly) { + AutoTArray<uint32_t, 16> 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 + // <name>-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<nsAtom> 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<uint32_t, 16> 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<nsAtom> 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 <custom-ident> N' + edgeLine = IsStart(aSide) ? 1 : aExplicitGridEnd; + } else { + // http://dev.w3.org/csswg/css-grid/#grid-placement-int + // '<custom-ident> N' + edgeLine = aNth < 0 ? 1 : aExplicitGridEnd; + } + // "If not enough lines with that name exist, all lines in the implicit + // grid are assumed to have that name..." + line = edgeLine + aNth; + } + } + return clamped(line, 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 <integer> / span * + // span <integer> / auto + return LinePair(kAutoLine, aStart.line_num); + } + // span <custom-ident> / span * + // span <custom-ident> / 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 <custom-ident> 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 <integer> + MOZ_ASSERT(aEnd.line_num != 0); + return LinePair(start, aEnd.line_num); + } + // http://dev.w3.org/csswg/css-grid/#grid-placement-errors + // auto / span <custom-ident> + return LinePair(start, 1); // XXX subgrid explicit size instead of 1? + } + } else { + uint32_t from = aStart.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 <custom-ident> 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<CellMap::Cell>& cellsInRow = mCellMap.mCells[i]; + const uint32_t len = cellsInRow.Length(); + const uint32_t lastCandidate = candidate; + // Find the first gap in the current row that's at least 'extent' wide. + // ('gap' tracks how wide the current column gap is.) + for (uint32_t j = candidate, gap = 0; j < len && gap < extent; ++j) { + if (!cellsInRow[j].mIsOccupied) { + ++gap; + continue; + } + candidate = j + 1; + gap = 0; + } + if (lastCandidate < candidate && i != iStart) { + // Couldn't fit 'extent' tracks at 'lastCandidate' here so we must + // restart from the beginning with the new 'candidate'. + i = iStart; + } else { + ++i; + } + } + return candidate; +} + +void nsGridContainerFrame::Grid::PlaceAutoCol(uint32_t aStartCol, + GridArea* aArea, + 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<CellMap::Cell>& cellsInRow = mCellMap.mCells[i]; + const uint32_t clampedJEnd = std::min<uint32_t>(jEnd, cellsInRow.Length()); + // Check if the current row is unoccupied from jStart to jEnd. + for (uint32_t j = jStart; j < clampedJEnd; ++j) { + if (cellsInRow[j].mIsOccupied) { + // Couldn't fit 'extent' rows at 'candidate' here; we hit something + // at row 'i'. So, try the row after 'i' as our next candidate. + candidate = i + 1; + gap = 0; + break; + } + } + } + return candidate; +} + +void nsGridContainerFrame::Grid::PlaceAutoRow(uint32_t aStartRow, + GridArea* aArea, + 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 <typename IsEmptyFuncT> +Maybe<nsTArray<uint32_t>> +nsGridContainerFrame::Grid::CalculateAdjustForAutoFitElements( + uint32_t* const aOutNumEmptyLines, TrackSizingFunctions& aSizingFunctions, + uint32_t aNumGridLines, IsEmptyFuncT aIsEmptyFunc) { + Maybe<nsTArray<uint32_t>> 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<nscoord> 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<nsTHashMap<nsUint32HashKey, uint32_t>> 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<uint32_t> zeroOffsetGridColEnd(mGridColEnd); + AutoRestore<uint32_t> 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<nsTArray<uint32_t>> 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<nsTArray<uint32_t>> 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<ReflowInput> dummyParentState; + const ReflowInput* rs = aReflowInput; + if (!aReflowInput) { + MOZ_ASSERT(!parent->HasAnyStateBits(NS_FRAME_IN_REFLOW)); + dummyParentState.emplace( + pc, parent, aRC, + LogicalSize(parent->GetWritingMode(), 0, NS_UNCONSTRAINEDSIZE), + ReflowInput::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<nsGridContainerFrame*>(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<LogicalSize>& 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<nsGridContainerFrame*>(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); + 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<nscoord> mMinSize; + Maybe<nscoord> mMinContentContribution; + Maybe<nscoord> mMaxContentContribution; + + // The item's percentage basis for intrinsic sizing purposes. + Maybe<LogicalSize> 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<GridItemInfo>& aGridItems, + const TrackSizingFunctions& aFunctions, nscoord aContentBoxSize, + LineRange GridArea::*aRange, SizingConstraint aConstraint) { + nscoord percentageBasis = aContentBoxSize; + if (percentageBasis == NS_UNCONSTRAINEDSIZE) { + percentageBasis = 0; + } + InitializeItemBaselines(aState, aGridItems); + ResolveIntrinsicSize(aState, aGridItems, aFunctions, aRange, percentageBasis, + aConstraint); + if (aConstraint != SizingConstraint::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<ItemBaselineData>& aBaselineItems, + BaselineSharingGroup aBaselineGroup) { + if (aBaselineItems.IsEmpty()) { + return; + } + + // Sort the collected items on their baseline track. + std::sort(aBaselineItems.begin(), aBaselineItems.end(), + ItemBaselineData::IsBaselineTrackLessThan); + + MOZ_ASSERT(mSizes.Length() > 0, "having an item implies at least one track"); + const uint32_t lastTrack = mSizes.Length() - 1; + nscoord maxBaseline = 0; + nscoord maxDescent = 0; + uint32_t currentTrack = kAutoLine; // guaranteed to not match any item + uint32_t trackStartIndex = 0; + for (uint32_t i = 0, len = aBaselineItems.Length(); true; ++i) { + // Find the maximum baseline and descent in the current track. + if (i != len) { + const ItemBaselineData& item = aBaselineItems[i]; + if (currentTrack == item.mBaselineTrack) { + maxBaseline = std::max(maxBaseline, item.mBaseline); + maxDescent = std::max(maxDescent, item.mSize - item.mBaseline); + continue; + } + } + // Iterate the current track again and update the baseline offsets making + // all items baseline-aligned within this group in this track. + for (uint32_t j = trackStartIndex; j < i; ++j) { + const ItemBaselineData& item = aBaselineItems[j]; + item.mGridItem->mBaselineOffset[mAxis] = maxBaseline - item.mBaseline; + MOZ_ASSERT(item.mGridItem->mBaselineOffset[mAxis] >= 0); + } + if (i != 0) { + // Store the size of this baseline-aligned subtree. + mSizes[currentTrack].mBaselineSubtreeSize[aBaselineGroup] = + maxBaseline + maxDescent; + // Record the first(last) baseline for the first(last) track. + if (currentTrack == 0 && aBaselineGroup == BaselineSharingGroup::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<GridItemInfo>& aGridItems) { + MOZ_ASSERT(!mIsMasonry); + if (aState.mFrame->IsSubgrid(mAxis)) { + // A grid container's subgridded axis doesn't have a baseline. + return; + } + nsTArray<ItemBaselineData> firstBaselineItems; + nsTArray<ItemBaselineData> 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) { + baseline = grid->GetBBaseline(BaselineSharingGroup::First); + } else { + baseline = grid->GetIBaseline(BaselineSharingGroup::First); + } + } + 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) { + baseline = grid->GetBBaseline(BaselineSharingGroup::Last); + } else { + baseline = grid->GetIBaseline(BaselineSharingGroup::Last); + } + } + 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<GridItemInfo>& aGridItems, + BaselineAlignmentSet aSet, const nsSize& aContainerSize, + nsTArray<nscoord>& aTrackSizes, + nsTArray<ItemBaselineData>& aFirstBaselineItems, + nsTArray<ItemBaselineData>& 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) { + baseline = grid->GetBBaseline(BaselineSharingGroup::First); + } else { + baseline = grid->GetIBaseline(BaselineSharingGroup::First); + } + } + 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) { + baseline = grid->GetBBaseline(BaselineSharingGroup::Last); + } else { + baseline = grid->GetIBaseline(BaselineSharingGroup::Last); + } + } + 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 <nsGridContainerFrame::Tracks::TrackSizingPhase phase> +bool nsGridContainerFrame::Tracks::GrowSizeForSpanningItems( + nsTArray<SpanningItemData>::iterator aIter, + nsTArray<SpanningItemData>::iterator aIterEnd, nsTArray<uint32_t>& aTracks, + nsTArray<TrackSize>& aPlan, nsTArray<TrackSize>& aItemPlan, + TrackSize::StateBits aSelector, const FitContentClamper& aFitContentClamper, + bool aNeedInfinitelyGrowableFlag) { + constexpr bool isMaxSizingPhase = + phase == TrackSizingPhase::IntrinsicMaximums || + phase == TrackSizingPhase::MaxContentMaximums; + bool needToUpdateSizes = false; + InitializePlan<phase>(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<phase>(); + if (space <= 0) { + continue; + } + aTracks.ClearAndRetainStorage(); + space = CollectGrowable<phase>(space, item.mLineRange, aSelector, aTracks); + if (space > 0) { + DistributeToTrackSizes<phase>(space, aPlan, aItemPlan, aTracks, aSelector, + aFitContentClamper); + needToUpdateSizes = true; + } + } + if (isMaxSizingPhase) { + needToUpdateSizes = true; + } + if (needToUpdateSizes) { + CopyPlanToSize<phase>(aPlan, aNeedInfinitelyGrowableFlag); + } + return needToUpdateSizes; +} + +void nsGridContainerFrame::Tracks::ResolveIntrinsicSize( + GridReflowInput& aState, nsTArray<GridItemInfo>& 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, 16> perSpanData; + + nsTArray<SpanningItemData> 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<uint32_t> tracks(maxSpan); + nsTArray<TrackSize> plan(mSizes.Length()); + plan.SetLength(mSizes.Length()); + nsTArray<TrackSize> 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<TrackSizingPhase::IntrinsicMinimums>( + 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<TrackSizingPhase::ContentBasedMinimums>( + 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<TrackSizingPhase::MaxContentMinimums>( + 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<TrackSizingPhase::IntrinsicMaximums>( + 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<TrackSizingPhase::MaxContentMaximums>( + 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<uint32_t>& 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<uint32_t> 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<GridItemInfo>& aGridItems, + const nsTArray<uint32_t>& aFlexTracks, + const TrackSizingFunctions& aFunctions, nscoord aAvailableSize) const { + if (aAvailableSize != NS_UNCONSTRAINEDSIZE) { + // Use all of the grid tracks and a 'space to fill' of the available space. + const TranslatedLineRange range(0, mSizes.Length()); + return FindFrUnitSize(range, aFlexTracks, aFunctions, aAvailableSize); + } + + // The used flex fraction is the maximum of: + // ... each flexible track's base size divided by its flex factor (which is + // floored at 1). + float fr = 0.0f; + for (uint32_t track : aFlexTracks) { + float flexFactor = aFunctions.MaxSizingFor(track).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<uint32_t> 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<GridItemInfo>& aGridItems, + const TrackSizingFunctions& aFunctions, nscoord aAvailableSize) { + if (aAvailableSize <= 0) { + return; + } + nsTArray<uint32_t> flexTracks(mSizes.Length()); + for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) { + if (mSizes[i].mState & TrackSize::eFlexMaxSizing) { + flexTracks.AppendElement(i); + } + } + if (flexTracks.IsEmpty()) { + return; + } + nscoord minSize = 0; + nscoord maxSize = NS_UNCONSTRAINEDSIZE; + if (aState.mReflowInput) { + auto* ri = aState.mReflowInput; + minSize = mAxis == eLogicalAxisBlock ? ri->ComputedMinBSize() + : ri->ComputedMinISize(); + maxSize = mAxis == eLogicalAxisBlock ? ri->ComputedMaxBSize() + : ri->ComputedMaxISize(); + } + Maybe<CopyableAutoTArray<TrackSize, 32>> 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<nscoord> 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<TrackSize>& aTrackSizes, nscoord* aPos, + nscoord* aLength) const { + MOZ_ASSERT(mStart != kAutoLine && mEnd != kAutoLine, + "expected a definite LineRange"); + MOZ_ASSERT(mStart < mEnd); + nscoord startPos = aTrackSizes[mStart].mPosition; + const TrackSize& sz = aTrackSizes[mEnd - 1]; + *aPos = startPos; + *aLength = (sz.mPosition + sz.mBase) - startPos; +} + +nscoord nsGridContainerFrame::LineRange::ToLength( + const nsTArray<TrackSize>& aTrackSizes) const { + MOZ_ASSERT(mStart != kAutoLine && mEnd != kAutoLine, + "expected a definite LineRange"); + MOZ_ASSERT(mStart < mEnd); + nscoord startPos = aTrackSizes[mStart].mPosition; + const TrackSize& sz = aTrackSizes[mEnd - 1]; + return (sz.mPosition + sz.mBase) - startPos; +} + +void nsGridContainerFrame::LineRange::ToPositionAndLengthForAbsPos( + const Tracks& aTracks, nscoord aGridOrigin, nscoord* aPos, + nscoord* aLength) const { + // kAutoLine for abspos children contributes the corresponding edge + // of the grid container's padding-box. + if (mEnd == kAutoLine) { + if (mStart == kAutoLine) { + // done + } else { + const nscoord endPos = *aPos + *aLength; + auto side = mStart == aTracks.mSizes.Length() + ? GridLineSide::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<const nsGridContainerFrame*>(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<TrackSize, 32> 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::Fragmentainer> +nsGridContainerFrame::GetNearestFragmentainer( + const GridReflowInput& aState) const { + Maybe<nsGridContainerFrame::Fragmentainer> data; + const ReflowInput* gridRI = aState.mReflowInput; + if (!gridRI->IsInFragmentedContext()) { + 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<nscoord>& 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<SmallValueHolder<nscoord>>* Prop; + auto SetProp = [aGridItemInfo, aChild](LogicalAxis aGridAxis, Prop aProp) { + auto state = aGridItemInfo->mState[aGridAxis]; + auto baselineAdjust = (state & ItemState::eContentBaseline) + ? aGridItemInfo->mBaselineOffset[aGridAxis] + : nscoord(0); + if (baselineAdjust < nscoord(0)) { + // This happens when the subtree overflows its track. + // XXX spec issue? it's unclear how to handle this. + baselineAdjust = nscoord(0); + } else if (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<const GridItemInfo*> sortedItems(aState.mGridItems.Length()); + nsTArray<nsIFrame*> 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<const GridItemInfo*>& aSortedItems, uint32_t aStartRow, + uint32_t aEndRow, nscoord aBSize, nscoord aAvailableSize) { + FrameHashtable pushedItems; + FrameHashtable incompleteItems; + FrameHashtable overflowIncompleteItems; + Maybe<nsTArray<nscoord>> 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<GridItemInfo*> 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<nsPlaceholderFrame*>(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<nscoord> currentPos(gridAxisTrackCount); + currentPos.SetLength(gridAxisTrackCount); + for (auto& sz : currentPos) { + sz = fragStartPos; + } + nsTArray<nscoord> lastPos(currentPos.Clone()); + nsTArray<GridItemInfo*> 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<Tracks::ItemBaselineData> firstBaselineItems; + nsTArray<Tracks::ItemBaselineData> lastBaselineItems; + auto applyBaselineAlignment = [&](BaselineAlignmentSet aSet) { + firstBaselineItems.ClearAndRetainStorage(); + lastBaselineItems.ClearAndRetainStorage(); + masonryTracks.InitializeItemBaselinesInMasonryAxis( + aState, aState.mGridItems, aSet, aContainerSize, currentPos, + firstBaselineItems, lastBaselineItems); + + bool didBaselineAdjustment = false; + nsTArray<Tracks::ItemBaselineData>* 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<nsGridContainerFrame*>(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> 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<nsGridContainerFrame*>(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<nscoord> 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<nscoord> 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<int32_t>(gridReflowInput.mCols.mSizes.Length()); + const auto numRows = + static_cast<int32_t>(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<CSSOrderAwareFrameIterator> iter; + Maybe<nsTArray<GridItemInfo>> gridItems; + if (baselines != BaselineSet::eNone) { + // We need to create a new iterator and GridItemInfo array because we + // might have pushed some children at this point. + // Even if the gridReflowInput iterator is invalid we can reuse its + // state about order to optimize initialization of the new iterator. + // An ordered child list can't become unordered by pushing frames. + // An unordered list can become ordered in a number of cases, but we + // ignore that here and guess that the child list is still unordered. + // XXX this is O(n^2) in the number of items in this fragment: bug 1306705 + using Filter = 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. + + // Clear our GridFragmentInfo property, which might be holding a stale + // dom::Grid object built from previously-computed info. This will + // ensure that the next call to GetGridFragments will create a new one. + if (mozilla::dom::Grid* grid = TakeProperty(GridFragmentInfo())) { + grid->ForgetFrame(); + } + + // 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<nscoord> colTrackPositions(colTrackCount); + nsTArray<nscoord> colTrackSizes(colTrackCount); + nsTArray<uint32_t> colTrackStates(colTrackCount); + nsTArray<bool> 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<nscoord> rowTrackPositions(rowTrackCount); + nsTArray<nscoord> rowTrackSizes(rowTrackCount); + nsTArray<uint32_t> rowTrackStates(rowTrackCount); + nsTArray<bool> 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<nsTArray<RefPtr<nsAtom>>> columnLineNames(capacity); + for (col = 0; col <= gridReflowInput.mCols.mSizes.Length(); col++) { + // Offset col by the explicit grid offset, to get the original names. + nsTArray<RefPtr<nsAtom>> explicitNames = + colLineNameMap.GetExplicitLineNamesAtIndex( + col - colFunctions.mExplicitGridOffset); + + columnLineNames.EmplaceBack(std::move(explicitNames)); + } + // Get the explicit names that follow a repeat auto declaration. + nsTArray<RefPtr<nsAtom>> colNamesFollowingRepeat; + nsTArray<RefPtr<nsAtom>> colBeforeRepeatAuto; + nsTArray<RefPtr<nsAtom>> 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<nsTArray<RefPtr<nsAtom>>> rowLineNames(capacity); + for (row = 0; row <= gridReflowInput.mRows.mSizes.Length(); row++) { + // Offset row by the explicit grid offset, to get the original names. + nsTArray<RefPtr<nsAtom>> explicitNames = + rowLineNameMap.GetExplicitLineNamesAtIndex( + row - rowFunctions.mExplicitGridOffset); + rowLineNames.EmplaceBack(std::move(explicitNames)); + } + // Get the explicit names that follow a repeat auto declaration. + nsTArray<RefPtr<nsAtom>> rowNamesFollowingRepeat; + nsTArray<RefPtr<nsAtom>> rowBeforeRepeatAuto; + nsTArray<RefPtr<nsAtom>> 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<NamedArea>( + 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<nsGridContainerFrame*>(FirstContinuation()); + if (f != this) { + return f->GetMinISize(aRC); + } + + DISPLAY_MIN_INLINE_SIZE(this, mCachedMinISize); + if (mCachedMinISize == NS_INTRINSIC_ISIZE_UNKNOWN) { + Maybe<nscoord> containISize = ContainIntrinsicISize(); + mCachedMinISize = containISize + ? *containISize + : IntrinsicISize(aRC, IntrinsicISizeType::MinISize); + } + return mCachedMinISize; +} + +nscoord nsGridContainerFrame::GetPrefISize(gfxContext* aRC) { + auto* f = static_cast<nsGridContainerFrame*>(FirstContinuation()); + if (f != this) { + return f->GetPrefISize(aRC); + } + + DISPLAY_PREF_INLINE_SIZE(this, mCachedPrefISize); + if (mCachedPrefISize == NS_INTRINSIC_ISIZE_UNKNOWN) { + Maybe<nscoord> 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(DestroyContext& aContext, + ChildListID aListID, + nsIFrame* aOldFrame) { + MOZ_ASSERT(aListID == FrameChildListID::Principal, "unexpected child list"); + +#ifdef DEBUG + SetDidPushItemsBitIfNeeded(aListID, aOldFrame); +#endif + + nsContainerFrame::RemoveFrame(aContext, aListID, aOldFrame); +} + +StyleAlignFlags 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) { + baseline = isOrthogonal ? grid->GetIBaseline(aGroup) + : grid->GetBBaseline(aGroup); + } else if (!isOrthogonal && aGridOrderItem.mIsInEdgeTrack) { + baseline = child + ->GetNaturalBaselineBOffset(childWM, aGroup, + BaselineExportContext::Other) + .valueOrFrom([aGroup, child, childWM]() { + return Baseline::SynthesizeBOffsetFromBorderBox( + child, childWM, aGroup); + }); + } else { + baseline = ::SynthesizeBaselineFromBorderBox(aGroup, childWM, size); + } + } else { + start = child->GetLogicalNormalPosition(aCBWM, aCBPhysicalSize).I(aCBWM); + size = child->ISize(aCBWM); + if (grid && aGridOrderItem.mIsInEdgeTrack) { + baseline = isOrthogonal ? grid->GetBBaseline(aGroup) + : grid->GetIBaseline(aGroup); + } 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<GridItemInfo>* aGridItems, const Tracks& aTracks, + uint32_t aFragmentStartTrack, uint32_t aFirstExcludedTrack, WritingMode aWM, + const nsSize& aCBPhysicalSize, nscoord aCBBorderPaddingStart, + nscoord aCBBorderPaddingEnd, nscoord aCBSize) { + const auto axis = aTracks.mAxis; + auto firstBaseline = aTracks.mBaseline[BaselineSharingGroup::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<GridItemInfo>& aGridItems, + LineRange GridArea::*aMajor, LineRange GridArea::*aMinor, + uint32_t aFragmentStartTrack) { + FindItemInGridOrderResult result = {nullptr, false}; + uint32_t minMajor = kTranslatedMaxLine + 1; + uint32_t minMinor = kTranslatedMaxLine + 1; + aIter.Reset(); + for (; !aIter.AtEnd(); aIter.Next()) { + const GridItemInfo& item = aGridItems[aIter.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<GridItemInfo>& aGridItems, LineRange GridArea::*aMajor, + LineRange GridArea::*aMinor, uint32_t aFragmentStartTrack, + uint32_t aFirstExcludedTrack) { + FindItemInGridOrderResult result = {nullptr, false}; + int32_t maxMajor = -1; + int32_t maxMinor = -1; + aIter.Reset(); + int32_t lastMajorTrack = int32_t(aFirstExcludedTrack) - 1; + for (; !aIter.AtEnd(); aIter.Next()) { + const GridItemInfo& item = aGridItems[aIter.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<TrackSize>& 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<nsFieldSetFrame*>(aFrame)->GetInner(); + } + // Since "Get" methods like GetInner and GetContentInsertionFrame can + // return null, we check the return values before dereferencing. Our + // calling pattern makes this unlikely, but we're being careful. + nsIFrame* insertionFrame = + inner ? inner->GetContentInsertionFrame() : nullptr; + nsIFrame* possibleGridFrame = insertionFrame ? insertionFrame : aFrame; + gridFrame = possibleGridFrame->IsGridContainerFrame() + ? static_cast<nsGridContainerFrame*>(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<mozilla::PresShell> 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<nsILineIterator::LineInfo, nsresult> 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; +} |