Bug 1839315: part 4) Link from `SheetLoadData::mWasAlternate` to spec. r=emilio DONTBUILD
[gecko.git] / layout / generic / nsGridContainerFrame.cpp
blob578dfe68182274a0be5c47f65765bbd2845bc9a3
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* rendering object for CSS "display: grid | inline-grid" */
9 #include "nsGridContainerFrame.h"
11 #include <functional>
12 #include <limits>
13 #include <stdlib.h> // for div()
14 #include <type_traits>
15 #include "gfxContext.h"
16 #include "mozilla/AutoRestore.h"
17 #include "mozilla/Baseline.h"
18 #include "mozilla/ComputedStyle.h"
19 #include "mozilla/CSSAlignUtils.h"
20 #include "mozilla/StaticPrefs_layout.h"
21 #include "mozilla/dom/Grid.h"
22 #include "mozilla/dom/GridBinding.h"
23 #include "mozilla/IntegerRange.h"
24 #include "mozilla/Maybe.h"
25 #include "mozilla/PodOperations.h" // for PodZero
26 #include "mozilla/Poison.h"
27 #include "mozilla/PresShell.h"
28 #include "nsAbsoluteContainingBlock.h"
29 #include "nsAlgorithm.h" // for clamped()
30 #include "nsCSSAnonBoxes.h"
31 #include "nsCSSFrameConstructor.h"
32 #include "nsTHashMap.h"
33 #include "nsDisplayList.h"
34 #include "nsHashKeys.h"
35 #include "nsFieldSetFrame.h"
36 #include "nsIFrameInlines.h"
37 #include "nsPlaceholderFrame.h"
38 #include "nsPresContext.h"
39 #include "nsReadableUtils.h"
40 #include "nsTableWrapperFrame.h"
42 using namespace mozilla;
44 typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags;
45 typedef nsGridContainerFrame::TrackSize TrackSize;
46 typedef mozilla::CSSAlignUtils::AlignJustifyFlags AlignJustifyFlags;
48 using GridTemplate = StyleGridTemplateComponent;
49 using TrackListValue =
50 StyleGenericTrackListValue<LengthPercentage, StyleInteger>;
51 using TrackRepeat = StyleGenericTrackRepeat<LengthPercentage, StyleInteger>;
52 using NameList = StyleOwnedSlice<StyleCustomIdent>;
53 using SizingConstraint = nsGridContainerFrame::SizingConstraint;
54 using GridItemCachedBAxisMeasurement =
55 nsGridContainerFrame::CachedBAxisMeasurement;
57 static mozilla::LazyLogModule gGridContainerLog("GridContainer");
58 #define GRID_LOG(...) \
59 MOZ_LOG(gGridContainerLog, LogLevel::Debug, (__VA_ARGS__));
61 static const int32_t kMaxLine = StyleMAX_GRID_LINE;
62 static const int32_t kMinLine = StyleMIN_GRID_LINE;
63 // The maximum line number, in the zero-based translated grid.
64 static const uint32_t kTranslatedMaxLine = uint32_t(kMaxLine - kMinLine);
65 static const uint32_t kAutoLine = kTranslatedMaxLine + 3457U;
67 static const nsFrameState kIsSubgridBits =
68 (NS_STATE_GRID_IS_COL_SUBGRID | NS_STATE_GRID_IS_ROW_SUBGRID);
70 namespace mozilla {
72 template <>
73 inline Span<const StyleOwnedSlice<StyleCustomIdent>>
74 GridTemplate::LineNameLists(bool aIsSubgrid) const {
75 if (IsTrackList()) {
76 return AsTrackList()->line_names.AsSpan();
78 if (IsSubgrid() && aIsSubgrid) {
79 return AsSubgrid()->names.AsSpan();
81 MOZ_ASSERT(IsNone() || IsMasonry() || (IsSubgrid() && !aIsSubgrid));
82 return {};
85 template <>
86 inline const StyleTrackBreadth& StyleTrackSize::GetMax() const {
87 if (IsBreadth()) {
88 return AsBreadth();
90 if (IsMinmax()) {
91 return AsMinmax()._1;
93 MOZ_ASSERT(IsFitContent());
94 return AsFitContent();
97 template <>
98 inline const StyleTrackBreadth& StyleTrackSize::GetMin() const {
99 static const StyleTrackBreadth kAuto = StyleTrackBreadth::Auto();
100 if (IsBreadth()) {
101 // <flex> behaves like minmax(auto, <flex>)
102 return AsBreadth().IsFr() ? kAuto : AsBreadth();
104 if (IsMinmax()) {
105 return AsMinmax()._0;
107 MOZ_ASSERT(IsFitContent());
108 return kAuto;
111 } // namespace mozilla
113 static nscoord ClampToCSSMaxBSize(nscoord aSize,
114 const ReflowInput* aReflowInput) {
115 auto maxSize = aReflowInput->ComputedMaxBSize();
116 if (MOZ_UNLIKELY(maxSize != NS_UNCONSTRAINEDSIZE)) {
117 MOZ_ASSERT(aReflowInput->ComputedMinBSize() <= maxSize);
118 aSize = std::min(aSize, maxSize);
120 return aSize;
123 // Same as above and set aStatus INCOMPLETE if aSize wasn't clamped.
124 // (If we clamp aSize it means our size is less than the break point,
125 // i.e. we're effectively breaking in our overflow, so we should leave
126 // aStatus as is (it will likely be set to OVERFLOW_INCOMPLETE later)).
127 static nscoord ClampToCSSMaxBSize(nscoord aSize,
128 const ReflowInput* aReflowInput,
129 nsReflowStatus* aStatus) {
130 auto maxSize = aReflowInput->ComputedMaxBSize();
131 if (MOZ_UNLIKELY(maxSize != NS_UNCONSTRAINEDSIZE)) {
132 MOZ_ASSERT(aReflowInput->ComputedMinBSize() <= maxSize);
133 if (aSize < maxSize) {
134 aStatus->SetIncomplete();
135 } else {
136 aSize = maxSize;
138 } else {
139 aStatus->SetIncomplete();
141 return aSize;
144 template <typename Size>
145 static bool IsPercentOfIndefiniteSize(const Size& aCoord,
146 nscoord aPercentBasis) {
147 return aPercentBasis == NS_UNCONSTRAINEDSIZE && aCoord.HasPercent();
150 static nscoord ResolveToDefiniteSize(const StyleTrackBreadth& aBreadth,
151 nscoord aPercentBasis) {
152 MOZ_ASSERT(aBreadth.IsBreadth());
153 if (::IsPercentOfIndefiniteSize(aBreadth.AsBreadth(), aPercentBasis)) {
154 return nscoord(0);
156 return std::max(nscoord(0), aBreadth.AsBreadth().Resolve(aPercentBasis));
159 // Synthesize a baseline from a border box. For an alphabetical baseline
160 // this is the end edge of the border box. For a central baseline it's
161 // the center of the border box.
162 // https://drafts.csswg.org/css-align-3/#synthesize-baselines
163 // For a 'first baseline' the measure is from the border-box start edge and
164 // for a 'last baseline' the measure is from the border-box end edge.
165 static nscoord SynthesizeBaselineFromBorderBox(BaselineSharingGroup aGroup,
166 WritingMode aWM,
167 nscoord aBorderBoxSize) {
168 if (aGroup == BaselineSharingGroup::First) {
169 return aWM.IsAlphabeticalBaseline() ? aBorderBoxSize : aBorderBoxSize / 2;
171 MOZ_ASSERT(aGroup == BaselineSharingGroup::Last);
172 // Round up for central baseline offset, to be consistent with eFirst.
173 return aWM.IsAlphabeticalBaseline()
175 : (aBorderBoxSize / 2) + (aBorderBoxSize % 2);
178 // The input sizes for calculating the number of repeat(auto-fill/fit) tracks.
179 // https://drafts.csswg.org/css-grid/#auto-repeat
180 struct RepeatTrackSizingInput {
181 explicit RepeatTrackSizingInput(WritingMode aWM)
182 : mMin(aWM, 0, 0),
183 mSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
184 mMax(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) {}
185 RepeatTrackSizingInput(const LogicalSize& aMin, const LogicalSize& aSize,
186 const LogicalSize& aMax)
187 : mMin(aMin), mSize(aSize), mMax(aMax) {}
189 // This should be used in intrinsic sizing (i.e. when we can't initialize
190 // the sizes directly from ReflowInput values).
191 void InitFromStyle(LogicalAxis aAxis, WritingMode aWM,
192 const ComputedStyle* aStyle) {
193 const auto& pos = aStyle->StylePosition();
194 const bool borderBoxSizing = pos->mBoxSizing == StyleBoxSizing::Border;
195 nscoord bp = NS_UNCONSTRAINEDSIZE; // a sentinel to calculate it only once
196 auto adjustForBoxSizing = [borderBoxSizing, aWM, aAxis, aStyle,
197 &bp](nscoord aSize) {
198 if (!borderBoxSizing) {
199 return aSize;
201 if (bp == NS_UNCONSTRAINEDSIZE) {
202 const auto& padding = aStyle->StylePadding()->mPadding;
203 LogicalMargin border(aWM, aStyle->StyleBorder()->GetComputedBorder());
204 // We can use zero percentage basis since this is only called from
205 // intrinsic sizing code.
206 const nscoord percentageBasis = 0;
207 if (aAxis == eLogicalAxisInline) {
208 bp = std::max(padding.GetIStart(aWM).Resolve(percentageBasis), 0) +
209 std::max(padding.GetIEnd(aWM).Resolve(percentageBasis), 0) +
210 border.IStartEnd(aWM);
211 } else {
212 bp = std::max(padding.GetBStart(aWM).Resolve(percentageBasis), 0) +
213 std::max(padding.GetBEnd(aWM).Resolve(percentageBasis), 0) +
214 border.BStartEnd(aWM);
217 return std::max(aSize - bp, 0);
219 nscoord& min = mMin.Size(aAxis, aWM);
220 nscoord& size = mSize.Size(aAxis, aWM);
221 nscoord& max = mMax.Size(aAxis, aWM);
222 const auto& minCoord =
223 aAxis == eLogicalAxisInline ? pos->MinISize(aWM) : pos->MinBSize(aWM);
224 if (minCoord.ConvertsToLength()) {
225 min = adjustForBoxSizing(minCoord.ToLength());
227 const auto& maxCoord =
228 aAxis == eLogicalAxisInline ? pos->MaxISize(aWM) : pos->MaxBSize(aWM);
229 if (maxCoord.ConvertsToLength()) {
230 max = std::max(min, adjustForBoxSizing(maxCoord.ToLength()));
232 const auto& sizeCoord =
233 aAxis == eLogicalAxisInline ? pos->ISize(aWM) : pos->BSize(aWM);
234 if (sizeCoord.ConvertsToLength()) {
235 size = Clamp(adjustForBoxSizing(sizeCoord.ToLength()), min, max);
239 LogicalSize mMin;
240 LogicalSize mSize;
241 LogicalSize mMax;
244 enum class GridLineSide {
245 BeforeGridGap,
246 AfterGridGap,
249 struct nsGridContainerFrame::TrackSize {
250 enum StateBits : uint16_t {
251 // clang-format off
252 eAutoMinSizing = 0x1,
253 eMinContentMinSizing = 0x2,
254 eMaxContentMinSizing = 0x4,
255 eMinOrMaxContentMinSizing = eMinContentMinSizing | eMaxContentMinSizing,
256 eIntrinsicMinSizing = eMinOrMaxContentMinSizing | eAutoMinSizing,
257 eModified = 0x8,
258 eAutoMaxSizing = 0x10,
259 eMinContentMaxSizing = 0x20,
260 eMaxContentMaxSizing = 0x40,
261 eAutoOrMaxContentMaxSizing = eAutoMaxSizing | eMaxContentMaxSizing,
262 eIntrinsicMaxSizing = eAutoOrMaxContentMaxSizing | eMinContentMaxSizing,
263 eFlexMaxSizing = 0x80,
264 eFrozen = 0x100,
265 eSkipGrowUnlimited1 = 0x200,
266 eSkipGrowUnlimited2 = 0x400,
267 eSkipGrowUnlimited = eSkipGrowUnlimited1 | eSkipGrowUnlimited2,
268 eBreakBefore = 0x800,
269 eFitContent = 0x1000,
270 eInfinitelyGrowable = 0x2000,
272 // These are only used in the masonry axis. They share the same value
273 // as *MinSizing above, but that's OK because we don't use those in
274 // the masonry axis.
276 // This track corresponds to an item margin-box size that is stretching.
277 eItemStretchSize = 0x1,
278 // This bit says that we should clamp that size to mLimit.
279 eClampToLimit = 0x2,
280 // This bit says that the corresponding item has `auto` margin(s).
281 eItemHasAutoMargin = 0x4,
282 // clang-format on
285 StateBits Initialize(nscoord aPercentageBasis, const StyleTrackSize&);
286 bool IsFrozen() const { return mState & eFrozen; }
287 #ifdef DEBUG
288 static void DumpStateBits(StateBits aState);
289 void Dump() const;
290 #endif
292 static bool IsDefiniteMaxSizing(StateBits aStateBits) {
293 return (aStateBits & (eIntrinsicMaxSizing | eFlexMaxSizing)) == 0;
296 nscoord mBase;
297 nscoord mLimit;
298 nscoord mPosition; // zero until we apply 'align/justify-content'
299 // mBaselineSubtreeSize is the size of a baseline-aligned subtree within
300 // this track. One subtree per baseline-sharing group (per track).
301 PerBaseline<nscoord> mBaselineSubtreeSize;
302 StateBits mState;
305 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(TrackSize::StateBits)
307 static_assert(
308 std::is_trivially_copyable<nsGridContainerFrame::TrackSize>::value,
309 "Must be trivially copyable");
310 static_assert(
311 std::is_trivially_destructible<nsGridContainerFrame::TrackSize>::value,
312 "Must be trivially destructible");
314 TrackSize::StateBits nsGridContainerFrame::TrackSize::Initialize(
315 nscoord aPercentageBasis, const StyleTrackSize& aSize) {
316 using Tag = StyleTrackBreadth::Tag;
318 MOZ_ASSERT(mBase == 0 && mLimit == 0 && mState == 0,
319 "track size data is expected to be initialized to zero");
320 mBaselineSubtreeSize[BaselineSharingGroup::First] = nscoord(0);
321 mBaselineSubtreeSize[BaselineSharingGroup::Last] = nscoord(0);
323 auto& min = aSize.GetMin();
324 auto& max = aSize.GetMax();
326 Tag minSizeTag = min.tag;
327 Tag maxSizeTag = max.tag;
328 if (aSize.IsFitContent()) {
329 // In layout, fit-content(size) behaves as minmax(auto, max-content), with
330 // 'size' as an additional upper-bound.
331 mState = eFitContent;
332 minSizeTag = Tag::Auto;
333 maxSizeTag = Tag::MaxContent;
335 if (::IsPercentOfIndefiniteSize(min, aPercentageBasis)) {
336 // https://drafts.csswg.org/css-grid/#valdef-grid-template-columns-percentage
337 // "If the inline or block size of the grid container is indefinite,
338 // <percentage> values relative to that size are treated as 'auto'."
339 minSizeTag = Tag::Auto;
341 if (::IsPercentOfIndefiniteSize(max, aPercentageBasis)) {
342 maxSizeTag = Tag::Auto;
345 // http://dev.w3.org/csswg/css-grid/#algo-init
346 switch (minSizeTag) {
347 case Tag::Auto:
348 mState |= eAutoMinSizing;
349 break;
350 case Tag::MinContent:
351 mState |= eMinContentMinSizing;
352 break;
353 case Tag::MaxContent:
354 mState |= eMaxContentMinSizing;
355 break;
356 default:
357 MOZ_ASSERT(!min.IsFr(), "<flex> min-sizing is invalid as a track size");
358 mBase = ::ResolveToDefiniteSize(min, aPercentageBasis);
360 switch (maxSizeTag) {
361 case Tag::Auto:
362 mState |= eAutoMaxSizing;
363 mLimit = NS_UNCONSTRAINEDSIZE;
364 break;
365 case Tag::MinContent:
366 case Tag::MaxContent:
367 mState |= maxSizeTag == Tag::MinContent ? eMinContentMaxSizing
368 : eMaxContentMaxSizing;
369 mLimit = NS_UNCONSTRAINEDSIZE;
370 break;
371 case Tag::Fr:
372 mState |= eFlexMaxSizing;
373 mLimit = mBase;
374 break;
375 default:
376 mLimit = ::ResolveToDefiniteSize(max, aPercentageBasis);
377 if (mLimit < mBase) {
378 mLimit = mBase;
381 return mState;
385 * A LineRange can be definite or auto - when it's definite it represents
386 * a consecutive set of tracks between a starting line and an ending line.
387 * Before it's definite it can also represent an auto position with a span,
388 * where mStart == kAutoLine and mEnd is the (non-zero positive) span.
389 * For normal-flow items, the invariant mStart < mEnd holds when both
390 * lines are definite.
392 * For abs.pos. grid items, mStart and mEnd may both be kAutoLine, meaning
393 * "attach this side to the grid container containing block edge".
394 * Additionally, mStart <= mEnd holds when both are definite (non-kAutoLine),
395 * i.e. the invariant is slightly relaxed compared to normal flow items.
397 struct nsGridContainerFrame::LineRange {
398 LineRange(int32_t aStart, int32_t aEnd)
399 : mUntranslatedStart(aStart), mUntranslatedEnd(aEnd) {
400 #ifdef DEBUG
401 if (!IsAutoAuto()) {
402 if (IsAuto()) {
403 MOZ_ASSERT(aEnd >= kMinLine && aEnd <= kMaxLine, "invalid span");
404 } else {
405 MOZ_ASSERT(aStart >= kMinLine && aStart <= kMaxLine,
406 "invalid start line");
407 MOZ_ASSERT(aEnd == int32_t(kAutoLine) ||
408 (aEnd >= kMinLine && aEnd <= kMaxLine),
409 "invalid end line");
412 #endif
414 bool IsAutoAuto() const { return mStart == kAutoLine && mEnd == kAutoLine; }
415 bool IsAuto() const { return mStart == kAutoLine; }
416 bool IsDefinite() const { return mStart != kAutoLine; }
417 uint32_t Extent() const {
418 MOZ_ASSERT(mEnd != kAutoLine, "Extent is undefined for abs.pos. 'auto'");
419 if (IsAuto()) {
420 MOZ_ASSERT(mEnd >= 1 && mEnd < uint32_t(kMaxLine), "invalid span");
421 return mEnd;
423 return mEnd - mStart;
427 * Return an object suitable for iterating this range.
429 auto Range() const { return IntegerRange<uint32_t>(mStart, mEnd); }
432 * Resolve this auto range to start at aStart, making it definite.
433 * @param aClampMaxLine the maximum allowed line number (zero-based)
434 * Precondition: this range IsAuto()
436 void ResolveAutoPosition(uint32_t aStart, uint32_t aClampMaxLine) {
437 MOZ_ASSERT(IsAuto(), "Why call me?");
438 mStart = aStart;
439 mEnd += aStart;
440 // Clamp to aClampMaxLine, which is where kMaxLine is in the explicit
441 // grid in a non-subgrid axis; this implements clamping per
442 // http://dev.w3.org/csswg/css-grid/#overlarge-grids
443 // In a subgrid axis it's the end of the grid in that axis.
444 if (MOZ_UNLIKELY(mStart >= aClampMaxLine)) {
445 mEnd = aClampMaxLine;
446 mStart = mEnd - 1;
447 } else if (MOZ_UNLIKELY(mEnd > aClampMaxLine)) {
448 mEnd = aClampMaxLine;
452 * Translate the lines to account for (empty) removed tracks. This method
453 * is only for grid items and should only be called after placement.
454 * aNumRemovedTracks contains a count for each line in the grid how many
455 * tracks were removed between the start of the grid and that line.
457 void AdjustForRemovedTracks(const nsTArray<uint32_t>& aNumRemovedTracks) {
458 MOZ_ASSERT(mStart != kAutoLine, "invalid resolved line for a grid item");
459 MOZ_ASSERT(mEnd != kAutoLine, "invalid resolved line for a grid item");
460 uint32_t numRemovedTracks = aNumRemovedTracks[mStart];
461 MOZ_ASSERT(numRemovedTracks == aNumRemovedTracks[mEnd],
462 "tracks that a grid item spans can't be removed");
463 mStart -= numRemovedTracks;
464 mEnd -= numRemovedTracks;
467 * Translate the lines to account for (empty) removed tracks. This method
468 * is only for abs.pos. children and should only be called after placement.
469 * Same as for in-flow items, but we don't touch 'auto' lines here and we
470 * also need to adjust areas that span into the removed tracks.
472 void AdjustAbsPosForRemovedTracks(
473 const nsTArray<uint32_t>& aNumRemovedTracks) {
474 if (mStart != kAutoLine) {
475 mStart -= aNumRemovedTracks[mStart];
477 if (mEnd != kAutoLine) {
478 MOZ_ASSERT(mStart == kAutoLine || mEnd > mStart, "invalid line range");
479 mEnd -= aNumRemovedTracks[mEnd];
483 * Return the contribution of this line range for step 2 in
484 * http://dev.w3.org/csswg/css-grid/#auto-placement-algo
486 uint32_t HypotheticalEnd() const { return mEnd; }
488 * Given an array of track sizes, return the starting position and length
489 * of the tracks in this line range.
491 void ToPositionAndLength(const nsTArray<TrackSize>& aTrackSizes,
492 nscoord* aPos, nscoord* aLength) const;
494 * Given an array of track sizes, return the length of the tracks in this
495 * line range.
497 nscoord ToLength(const nsTArray<TrackSize>& aTrackSizes) const;
499 * Given an array of track sizes and a grid origin coordinate, adjust the
500 * abs.pos. containing block along an axis given by aPos and aLength.
501 * aPos and aLength should already be initialized to the grid container
502 * containing block for this axis before calling this method.
504 void ToPositionAndLengthForAbsPos(const Tracks& aTracks, nscoord aGridOrigin,
505 nscoord* aPos, nscoord* aLength) const;
507 void Translate(int32_t aOffset) {
508 MOZ_ASSERT(IsDefinite());
509 mStart += aOffset;
510 mEnd += aOffset;
513 /** Swap the start/end sides of this range. */
514 void ReverseDirection(uint32_t aGridEnd) {
515 MOZ_ASSERT(IsDefinite());
516 MOZ_ASSERT(aGridEnd >= mEnd);
517 uint32_t newStart = aGridEnd - mEnd;
518 mEnd = aGridEnd - mStart;
519 mStart = newStart;
523 * @note We'll use the signed member while resolving definite positions
524 * to line numbers (1-based), which may become negative for implicit lines
525 * to the top/left of the explicit grid. PlaceGridItems() then translates
526 * the whole grid to a 0,0 origin and we'll use the unsigned member from
527 * there on.
529 union {
530 uint32_t mStart;
531 int32_t mUntranslatedStart;
533 union {
534 uint32_t mEnd;
535 int32_t mUntranslatedEnd;
538 protected:
539 LineRange() : mStart(0), mEnd(0) {}
543 * Helper class to construct a LineRange from translated lines.
544 * The ctor only accepts translated definite line numbers.
546 struct nsGridContainerFrame::TranslatedLineRange : public LineRange {
547 TranslatedLineRange(uint32_t aStart, uint32_t aEnd) {
548 MOZ_ASSERT(aStart < aEnd && aEnd <= kTranslatedMaxLine);
549 mStart = aStart;
550 mEnd = aEnd;
555 * A GridArea is the area in the grid for a grid item.
556 * The area is represented by two LineRanges, both of which can be auto
557 * (@see LineRange) in intermediate steps while the item is being placed.
558 * @see PlaceGridItems
560 struct nsGridContainerFrame::GridArea {
561 GridArea(const LineRange& aCols, const LineRange& aRows)
562 : mCols(aCols), mRows(aRows) {}
563 bool IsDefinite() const { return mCols.IsDefinite() && mRows.IsDefinite(); }
564 LineRange& LineRangeForAxis(LogicalAxis aAxis) {
565 return aAxis == eLogicalAxisInline ? mCols : mRows;
567 const LineRange& LineRangeForAxis(LogicalAxis aAxis) const {
568 return aAxis == eLogicalAxisInline ? mCols : mRows;
570 LineRange mCols;
571 LineRange mRows;
574 struct nsGridContainerFrame::GridItemInfo {
576 * Item state per axis.
578 enum StateBits : uint16_t {
579 // clang-format off
580 eIsFlexing = 0x1, // does the item span a flex track?
581 eFirstBaseline = 0x2, // participate in 'first baseline' alignment?
582 // ditto 'last baseline', mutually exclusive w. eFirstBaseline
583 eLastBaseline = 0x4,
584 eIsBaselineAligned = eFirstBaseline | eLastBaseline,
585 // One of e[Self|Content]Baseline is set when eIsBaselineAligned is true
586 eSelfBaseline = 0x8, // is it *-self:[last ]baseline alignment?
587 // Ditto *-content:[last ]baseline. Mutually exclusive w. eSelfBaseline.
588 eContentBaseline = 0x10,
589 // The baseline affects the margin or padding on the item's end side when
590 // this bit is set. In a grid-axis it's always set for eLastBaseline and
591 // always unset for eFirstBaseline. In a masonry-axis, it's set for
592 // baseline groups in the EndStretch set and unset for the StartStretch set.
593 eEndSideBaseline = 0x20,
594 eAllBaselineBits = eIsBaselineAligned | eSelfBaseline | eContentBaseline |
595 eEndSideBaseline,
596 // Should apply Automatic Minimum Size per:
597 // https://drafts.csswg.org/css-grid/#min-size-auto
598 eApplyAutoMinSize = 0x40,
599 // Clamp per https://drafts.csswg.org/css-grid/#min-size-auto
600 eClampMarginBoxMinSize = 0x80,
601 eIsSubgrid = 0x100,
602 // set on subgrids and items in subgrids if they are adjacent to the grid
603 // start/end edge (excluding grid-aligned abs.pos. frames)
604 eStartEdge = 0x200,
605 eEndEdge = 0x400,
606 eEdgeBits = eStartEdge | eEndEdge,
607 // Set if this item was auto-placed in this axis.
608 eAutoPlacement = 0x800,
609 // Set if this item is the last item in its track (masonry layout only)
610 eIsLastItemInMasonryTrack = 0x1000,
611 // clang-format on
614 GridItemInfo(nsIFrame* aFrame, const GridArea& aArea);
616 static bool BaselineAlignmentAffectsEndSide(StateBits state) {
617 return state & StateBits::eEndSideBaseline;
621 * Inhibit subgrid layout unless the item is placed in the first "track" in
622 * a parent masonry-axis, or has definite placement or spans all tracks in
623 * the parent grid-axis.
624 * TODO: this is stricter than what the Masonry proposal currently states
625 * (bug 1627581)
627 void MaybeInhibitSubgridInMasonry(nsGridContainerFrame* aParent,
628 uint32_t aGridAxisTrackCount);
631 * Inhibit subgridding in aAxis for this item.
633 void InhibitSubgrid(nsGridContainerFrame* aParent, LogicalAxis aAxis);
636 * Return a copy of this item with its row/column data swapped.
638 GridItemInfo Transpose() const {
639 GridItemInfo info(mFrame, GridArea(mArea.mRows, mArea.mCols));
640 info.mState[0] = mState[1];
641 info.mState[1] = mState[0];
642 info.mBaselineOffset[0] = mBaselineOffset[1];
643 info.mBaselineOffset[1] = mBaselineOffset[0];
644 return info;
647 /** Swap the start/end sides in aAxis. */
648 inline void ReverseDirection(LogicalAxis aAxis, uint32_t aGridEnd);
650 // Is this item a subgrid in the given container axis?
651 bool IsSubgrid(LogicalAxis aAxis) const {
652 return mState[aAxis] & StateBits::eIsSubgrid;
655 // Is this item a subgrid in either axis?
656 bool IsSubgrid() const {
657 return IsSubgrid(eLogicalAxisInline) || IsSubgrid(eLogicalAxisBlock);
660 // Return the (inner) grid container frame associated with this subgrid item.
661 nsGridContainerFrame* SubgridFrame() const {
662 MOZ_ASSERT(IsSubgrid());
663 nsGridContainerFrame* gridFrame = GetGridContainerFrame(mFrame);
664 MOZ_ASSERT(gridFrame && gridFrame->IsSubgrid());
665 return gridFrame;
669 * Adjust our grid areas to account for removed auto-fit tracks in aAxis.
671 void AdjustForRemovedTracks(LogicalAxis aAxis,
672 const nsTArray<uint32_t>& aNumRemovedTracks);
675 * If the item is [align|justify]-self:[last ]baseline aligned in the given
676 * axis then set aBaselineOffset to the baseline offset and return aAlign.
677 * Otherwise, return a fallback alignment.
679 StyleAlignFlags GetSelfBaseline(StyleAlignFlags aAlign, LogicalAxis aAxis,
680 nscoord* aBaselineOffset) const {
681 MOZ_ASSERT(aAlign == StyleAlignFlags::BASELINE ||
682 aAlign == StyleAlignFlags::LAST_BASELINE);
683 if (!(mState[aAxis] & eSelfBaseline)) {
684 return aAlign == StyleAlignFlags::BASELINE ? StyleAlignFlags::SELF_START
685 : StyleAlignFlags::SELF_END;
687 *aBaselineOffset = mBaselineOffset[aAxis];
688 return aAlign;
691 // Return true if we should apply Automatic Minimum Size to this item.
692 // https://drafts.csswg.org/css-grid/#min-size-auto
693 // @note the caller should also check that the item spans at least one track
694 // that has a min track sizing function that is 'auto' before applying it.
695 bool ShouldApplyAutoMinSize(WritingMode aContainerWM,
696 LogicalAxis aContainerAxis,
697 nscoord aPercentageBasis) const {
698 const bool isInlineAxis = aContainerAxis == eLogicalAxisInline;
699 const auto* pos =
700 mFrame->IsTableWrapperFrame()
701 ? mFrame->PrincipalChildList().FirstChild()->StylePosition()
702 : mFrame->StylePosition();
703 const auto& size =
704 isInlineAxis ? pos->ISize(aContainerWM) : pos->BSize(aContainerWM);
705 // max-content and min-content should behave as initial value in block axis.
706 // FIXME: Bug 567039: moz-fit-content and -moz-available are not supported
707 // for block size dimension on sizing properties (e.g. height), so we
708 // treat it as `auto`.
709 bool isAuto = size.IsAuto() ||
710 (isInlineAxis ==
711 aContainerWM.IsOrthogonalTo(mFrame->GetWritingMode()) &&
712 size.BehavesLikeInitialValueOnBlockAxis());
713 // NOTE: if we have a definite size then our automatic minimum size
714 // can't affect our size. Excluding these simplifies applying
715 // the clamping in the right cases later.
716 if (!isAuto && !::IsPercentOfIndefiniteSize(size, aPercentageBasis)) {
717 return false;
719 const auto& minSize = isInlineAxis ? pos->MinISize(aContainerWM)
720 : pos->MinBSize(aContainerWM);
721 // max-content and min-content should behave as initial value in block axis.
722 // FIXME: Bug 567039: moz-fit-content and -moz-available are not supported
723 // for block size dimension on sizing properties (e.g. height), so we
724 // treat it as `auto`.
725 isAuto = minSize.IsAuto() ||
726 (isInlineAxis ==
727 aContainerWM.IsOrthogonalTo(mFrame->GetWritingMode()) &&
728 minSize.BehavesLikeInitialValueOnBlockAxis());
729 return isAuto &&
730 mFrame->StyleDisplay()->mOverflowX == StyleOverflow::Visible;
733 #ifdef DEBUG
734 void Dump() const;
735 #endif
737 static bool IsStartRowLessThan(const GridItemInfo* a, const GridItemInfo* b) {
738 return a->mArea.mRows.mStart < b->mArea.mRows.mStart;
741 // Sorting functions for 'masonry-auto-flow:next'. We sort the items that
742 // were placed into the first track by the Grid placement algorithm first
743 // (to honor that placement). All other items will be placed by the Masonry
744 // layout algorithm (their Grid placement in the masonry axis is irrelevant).
745 static bool RowMasonryOrdered(const GridItemInfo* a, const GridItemInfo* b) {
746 return a->mArea.mRows.mStart == 0 && b->mArea.mRows.mStart != 0 &&
747 !a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
749 static bool ColMasonryOrdered(const GridItemInfo* a, const GridItemInfo* b) {
750 return a->mArea.mCols.mStart == 0 && b->mArea.mCols.mStart != 0 &&
751 !a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
754 // Sorting functions for 'masonry-auto-flow:definite-first'. Similar to
755 // the above, but here we also sort items with a definite item placement in
756 // the grid axis in track order before 'auto'-placed items. We also sort all
757 // continuations first since they use the same placement as their
758 // first-in-flow (we treat them as "definite" regardless of eAutoPlacement).
759 static bool RowMasonryDefiniteFirst(const GridItemInfo* a,
760 const GridItemInfo* b) {
761 bool isContinuationA = a->mFrame->GetPrevInFlow();
762 bool isContinuationB = b->mFrame->GetPrevInFlow();
763 if (isContinuationA != isContinuationB) {
764 return isContinuationA;
766 auto masonryA = a->mArea.mRows.mStart;
767 auto gridA = a->mState[eLogicalAxisInline] & StateBits::eAutoPlacement;
768 auto masonryB = b->mArea.mRows.mStart;
769 auto gridB = b->mState[eLogicalAxisInline] & StateBits::eAutoPlacement;
770 return (masonryA == 0 ? masonryB != 0 : (masonryB != 0 && gridA < gridB)) &&
771 !a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
773 static bool ColMasonryDefiniteFirst(const GridItemInfo* a,
774 const GridItemInfo* b) {
775 MOZ_ASSERT(!a->mFrame->GetPrevInFlow() && !b->mFrame->GetPrevInFlow(),
776 "fragmentation not supported in inline axis");
777 auto masonryA = a->mArea.mCols.mStart;
778 auto gridA = a->mState[eLogicalAxisBlock] & StateBits::eAutoPlacement;
779 auto masonryB = b->mArea.mCols.mStart;
780 auto gridB = b->mState[eLogicalAxisBlock] & StateBits::eAutoPlacement;
781 return (masonryA == 0 ? masonryB != 0 : (masonryB != 0 && gridA < gridB)) &&
782 !a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
785 nsIFrame* const mFrame;
786 GridArea mArea;
787 // Offset from the margin edge to the baseline (LogicalAxis index). It's from
788 // the start edge when eFirstBaseline is set, end edge otherwise. It's mutable
789 // since we update the value fairly late (just before reflowing the item).
790 mutable nscoord mBaselineOffset[2];
791 mutable StateBits mState[2]; // state bits per axis (LogicalAxis index)
792 static_assert(mozilla::eLogicalAxisBlock == 0, "unexpected index value");
793 static_assert(mozilla::eLogicalAxisInline == 1, "unexpected index value");
796 using GridItemInfo = nsGridContainerFrame::GridItemInfo;
797 using ItemState = GridItemInfo::StateBits;
798 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ItemState)
800 GridItemInfo::GridItemInfo(nsIFrame* aFrame, const GridArea& aArea)
801 : mFrame(aFrame), mArea(aArea) {
802 mState[eLogicalAxisBlock] =
803 StateBits(mArea.mRows.mStart == kAutoLine ? eAutoPlacement : 0);
804 mState[eLogicalAxisInline] =
805 StateBits(mArea.mCols.mStart == kAutoLine ? eAutoPlacement : 0);
806 if (auto* gridFrame = GetGridContainerFrame(mFrame)) {
807 auto parentWM = aFrame->GetParent()->GetWritingMode();
808 bool isOrthogonal = parentWM.IsOrthogonalTo(gridFrame->GetWritingMode());
809 if (gridFrame->IsColSubgrid()) {
810 mState[isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline] |=
811 StateBits::eIsSubgrid;
813 if (gridFrame->IsRowSubgrid()) {
814 mState[isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock] |=
815 StateBits::eIsSubgrid;
818 mBaselineOffset[eLogicalAxisBlock] = nscoord(0);
819 mBaselineOffset[eLogicalAxisInline] = nscoord(0);
822 void GridItemInfo::ReverseDirection(LogicalAxis aAxis, uint32_t aGridEnd) {
823 mArea.LineRangeForAxis(aAxis).ReverseDirection(aGridEnd);
824 ItemState& state = mState[aAxis];
825 ItemState newState = state & ~ItemState::eEdgeBits;
826 if (state & ItemState::eStartEdge) {
827 newState |= ItemState::eEndEdge;
829 if (state & ItemState::eEndEdge) {
830 newState |= ItemState::eStartEdge;
832 state = newState;
835 void GridItemInfo::InhibitSubgrid(nsGridContainerFrame* aParent,
836 LogicalAxis aAxis) {
837 MOZ_ASSERT(IsSubgrid(aAxis));
838 auto bit = NS_STATE_GRID_IS_COL_SUBGRID;
839 if (aParent->GetWritingMode().IsOrthogonalTo(mFrame->GetWritingMode()) !=
840 (aAxis == eLogicalAxisBlock)) {
841 bit = NS_STATE_GRID_IS_ROW_SUBGRID;
843 MOZ_ASSERT(SubgridFrame()->HasAnyStateBits(bit));
844 SubgridFrame()->RemoveStateBits(bit);
845 mState[aAxis] &= StateBits(~StateBits::eIsSubgrid);
848 void GridItemInfo::MaybeInhibitSubgridInMasonry(nsGridContainerFrame* aParent,
849 uint32_t aGridAxisTrackCount) {
850 if (IsSubgrid(eLogicalAxisInline) && aParent->IsMasonry(eLogicalAxisBlock) &&
851 mArea.mRows.mStart != 0 && mArea.mCols.Extent() != aGridAxisTrackCount &&
852 (mState[eLogicalAxisInline] & eAutoPlacement)) {
853 InhibitSubgrid(aParent, eLogicalAxisInline);
854 return;
856 if (IsSubgrid(eLogicalAxisBlock) && aParent->IsMasonry(eLogicalAxisInline) &&
857 mArea.mCols.mStart != 0 && mArea.mRows.Extent() != aGridAxisTrackCount &&
858 (mState[eLogicalAxisBlock] & eAutoPlacement)) {
859 InhibitSubgrid(aParent, eLogicalAxisBlock);
863 // Each subgrid stores this data about its items etc on a frame property.
864 struct nsGridContainerFrame::Subgrid {
865 Subgrid(const GridArea& aArea, bool aIsOrthogonal, WritingMode aCBWM)
866 : mArea(aArea),
867 mGridColEnd(0),
868 mGridRowEnd(0),
869 mMarginBorderPadding(aCBWM),
870 mIsOrthogonal(aIsOrthogonal) {}
872 // Return the relevant line range for the subgrid column axis.
873 const LineRange& SubgridCols() const {
874 return mIsOrthogonal ? mArea.mRows : mArea.mCols;
876 // Return the relevant line range for the subgrid row axis.
877 const LineRange& SubgridRows() const {
878 return mIsOrthogonal ? mArea.mCols : mArea.mRows;
881 // The subgrid's items.
882 nsTArray<GridItemInfo> mGridItems;
883 // The subgrid's abs.pos. items.
884 nsTArray<GridItemInfo> mAbsPosItems;
885 // The subgrid's area as a grid item, i.e. in its parent's grid space.
886 GridArea mArea;
887 // The (inner) grid size for the subgrid, zero-based.
888 uint32_t mGridColEnd;
889 uint32_t mGridRowEnd;
890 // The margin+border+padding for the subgrid box in its parent grid's WM.
891 // (This also includes the size of any scrollbars.)
892 LogicalMargin mMarginBorderPadding;
893 // Does the subgrid frame have orthogonal writing-mode to its parent grid
894 // container?
895 bool mIsOrthogonal;
897 NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, Subgrid)
899 using Subgrid = nsGridContainerFrame::Subgrid;
901 void GridItemInfo::AdjustForRemovedTracks(
902 LogicalAxis aAxis, const nsTArray<uint32_t>& aNumRemovedTracks) {
903 const bool abspos = mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
904 auto& lines = mArea.LineRangeForAxis(aAxis);
905 if (abspos) {
906 lines.AdjustAbsPosForRemovedTracks(aNumRemovedTracks);
907 } else {
908 lines.AdjustForRemovedTracks(aNumRemovedTracks);
910 if (IsSubgrid()) {
911 auto* subgrid = SubgridFrame()->GetProperty(Subgrid::Prop());
912 if (subgrid) {
913 auto& lines = subgrid->mArea.LineRangeForAxis(aAxis);
914 if (abspos) {
915 lines.AdjustAbsPosForRemovedTracks(aNumRemovedTracks);
916 } else {
917 lines.AdjustForRemovedTracks(aNumRemovedTracks);
924 * Track size data for use by subgrids (which don't do sizing of their own
925 * in a subgridded axis). A non-subgrid container stores its resolved sizes,
926 * but only if it has any subgrid children. A subgrid always stores one.
927 * In a subgridded axis, we copy the parent's sizes (see CopyUsedTrackSizes).
929 * This struct us stored on a frame property, which may be null before the track
930 * sizing step for the given container. A null property is semantically
931 * equivalent to mCanResolveLineRangeSize being false in both axes.
932 * @note the axis used to access this data is in the grid container's own
933 * writing-mode, same as in other track-sizing functions.
935 struct nsGridContainerFrame::UsedTrackSizes {
936 UsedTrackSizes() : mCanResolveLineRangeSize{false, false} {}
939 * Setup mSizes by copying track sizes from aFrame's grid container
940 * parent when aAxis is subgridded (and recurse if the parent is a subgrid
941 * that doesn't have sizes yet), or by running the Track Sizing Algo when
942 * the axis is not subgridded (for a subgrid).
943 * Set mCanResolveLineRangeSize[aAxis] to true once we have obtained
944 * sizes for an axis (if it's already true then this method is a NOP).
946 void ResolveTrackSizesForAxis(nsGridContainerFrame* aFrame, LogicalAxis aAxis,
947 gfxContext& aRC);
949 /** Helper function for the above method */
950 void ResolveSubgridTrackSizesForAxis(nsGridContainerFrame* aFrame,
951 LogicalAxis aAxis, Subgrid* aSubgrid,
952 gfxContext& aRC,
953 nscoord aContentBoxSize);
955 // This only has valid sizes when mCanResolveLineRangeSize is true in
956 // the same axis. It may have zero tracks (a grid with only abs.pos.
957 // subgrids/items may have zero tracks).
958 PerLogicalAxis<nsTArray<TrackSize>> mSizes;
959 // True if mSizes can be used to resolve line range sizes in an axis.
960 PerLogicalAxis<bool> mCanResolveLineRangeSize;
962 NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, UsedTrackSizes)
964 using UsedTrackSizes = nsGridContainerFrame::UsedTrackSizes;
966 #ifdef DEBUG
967 void nsGridContainerFrame::GridItemInfo::Dump() const {
968 auto Dump1 = [this](const char* aMsg, LogicalAxis aAxis) {
969 auto state = mState[aAxis];
970 if (!state) {
971 return;
973 printf("%s", aMsg);
974 if (state & ItemState::eEdgeBits) {
975 printf("subgrid-adjacent-edges(");
976 if (state & ItemState::eStartEdge) {
977 printf("start ");
979 if (state & ItemState::eEndEdge) {
980 printf("end");
982 printf(") ");
984 if (state & ItemState::eAutoPlacement) {
985 printf("masonry-auto ");
987 if (state & ItemState::eIsSubgrid) {
988 printf("subgrid ");
990 if (state & ItemState::eIsFlexing) {
991 printf("flexing ");
993 if (state & ItemState::eApplyAutoMinSize) {
994 printf("auto-min-size ");
996 if (state & ItemState::eClampMarginBoxMinSize) {
997 printf("clamp ");
999 if (state & ItemState::eIsLastItemInMasonryTrack) {
1000 printf("last-in-track ");
1002 if (state & ItemState::eFirstBaseline) {
1003 printf("first baseline %s-alignment ",
1004 (state & ItemState::eSelfBaseline) ? "self" : "content");
1006 if (state & ItemState::eLastBaseline) {
1007 printf("last baseline %s-alignment ",
1008 (state & ItemState::eSelfBaseline) ? "self" : "content");
1010 if (state & ItemState::eIsBaselineAligned) {
1011 printf("%.2fpx", NSAppUnitsToFloatPixels(mBaselineOffset[aAxis],
1012 AppUnitsPerCSSPixel()));
1014 printf("\n");
1016 printf("grid-row: %d %d\n", mArea.mRows.mStart, mArea.mRows.mEnd);
1017 Dump1(" grid block-axis: ", eLogicalAxisBlock);
1018 printf("grid-column: %d %d\n", mArea.mCols.mStart, mArea.mCols.mEnd);
1019 Dump1(" grid inline-axis: ", eLogicalAxisInline);
1021 #endif
1024 * Encapsulates CSS track-sizing functions.
1026 struct nsGridContainerFrame::TrackSizingFunctions {
1027 private:
1028 TrackSizingFunctions(const GridTemplate& aTemplate,
1029 const StyleImplicitGridTracks& aAutoSizing,
1030 const Maybe<size_t>& aRepeatAutoIndex, bool aIsSubgrid)
1031 : mTemplate(aTemplate),
1032 mTrackListValues(aTemplate.TrackListValues()),
1033 mAutoSizing(aAutoSizing),
1034 mExplicitGridOffset(0),
1035 mRepeatAutoStart(aRepeatAutoIndex.valueOr(0)),
1036 mRepeatAutoEnd(mRepeatAutoStart),
1037 mHasRepeatAuto(aRepeatAutoIndex.isSome()) {
1038 MOZ_ASSERT(!mHasRepeatAuto || !aIsSubgrid,
1039 "a track-list for a subgrid can't have an <auto-repeat> track");
1040 if (!aIsSubgrid) {
1041 ExpandNonRepeatAutoTracks();
1044 #ifdef DEBUG
1045 if (mHasRepeatAuto) {
1046 MOZ_ASSERT(mExpandedTracks.Length() >= 1);
1047 const unsigned maxTrack = kMaxLine - 1;
1048 // If the exanded tracks are out of range of the maximum track, we
1049 // can't compare the repeat-auto start. It will be removed later during
1050 // grid item placement in that situation.
1051 if (mExpandedTracks.Length() < maxTrack) {
1052 MOZ_ASSERT(mRepeatAutoStart < mExpandedTracks.Length());
1055 #endif
1058 public:
1059 TrackSizingFunctions(const GridTemplate& aGridTemplate,
1060 const StyleImplicitGridTracks& aAutoSizing,
1061 bool aIsSubgrid)
1062 : TrackSizingFunctions(aGridTemplate, aAutoSizing,
1063 aGridTemplate.RepeatAutoIndex(), aIsSubgrid) {}
1065 private:
1066 enum { ForSubgridFallbackTag };
1067 TrackSizingFunctions(const GridTemplate& aGridTemplate,
1068 const StyleImplicitGridTracks& aAutoSizing,
1069 decltype(ForSubgridFallbackTag))
1070 : TrackSizingFunctions(aGridTemplate, aAutoSizing, Nothing(),
1071 /* aIsSubgrid */ true) {}
1073 public:
1075 * This is used in a subgridded axis to resolve sizes before its parent's
1076 * sizes are known for intrinsic sizing purposes. It copies the slice of
1077 * the nearest non-subgridded axis' track sizing functions spanned by
1078 * the subgrid.
1080 * FIXME: this was written before there was a spec... the spec now says:
1081 * "If calculating the layout of a grid item in this step depends on
1082 * the available space in the block axis, assume the available space
1083 * that it would have if any row with a definite max track sizing
1084 * function had that size and all other rows were infinite."
1085 * https://drafts.csswg.org/css-grid-2/#subgrid-sizing
1087 static TrackSizingFunctions ForSubgridFallback(
1088 nsGridContainerFrame* aSubgridFrame, const Subgrid* aSubgrid,
1089 nsGridContainerFrame* aParentGridContainer, LogicalAxis aParentAxis) {
1090 MOZ_ASSERT(aSubgrid);
1091 MOZ_ASSERT(aSubgridFrame->IsSubgrid(aSubgrid->mIsOrthogonal
1092 ? GetOrthogonalAxis(aParentAxis)
1093 : aParentAxis));
1094 nsGridContainerFrame* parent = aParentGridContainer;
1095 auto parentAxis = aParentAxis;
1096 LineRange range = aSubgrid->mArea.LineRangeForAxis(parentAxis);
1097 // Find our nearest non-subgridded axis and use its track sizing functions.
1098 while (parent->IsSubgrid(parentAxis)) {
1099 const auto* parentSubgrid = parent->GetProperty(Subgrid::Prop());
1100 auto* grandParent = parent->ParentGridContainerForSubgrid();
1101 auto grandParentWM = grandParent->GetWritingMode();
1102 bool isSameDirInAxis =
1103 parent->GetWritingMode().ParallelAxisStartsOnSameSide(parentAxis,
1104 grandParentWM);
1105 if (MOZ_UNLIKELY(!isSameDirInAxis)) {
1106 auto end = parentAxis == eLogicalAxisBlock ? parentSubgrid->mGridRowEnd
1107 : parentSubgrid->mGridColEnd;
1108 range.ReverseDirection(end);
1109 // range is now in the same direction as the grand-parent's axis
1111 auto grandParentAxis = parentSubgrid->mIsOrthogonal
1112 ? GetOrthogonalAxis(parentAxis)
1113 : parentAxis;
1114 const auto& parentRange =
1115 parentSubgrid->mArea.LineRangeForAxis(grandParentAxis);
1116 range.Translate(parentRange.mStart);
1117 // range is now in the grand-parent's coordinates
1118 parentAxis = grandParentAxis;
1119 parent = grandParent;
1121 const auto* pos = parent->StylePosition();
1122 const auto isInlineAxis = parentAxis == eLogicalAxisInline;
1123 const auto& szf =
1124 isInlineAxis ? pos->mGridTemplateRows : pos->mGridTemplateColumns;
1125 const auto& autoSizing =
1126 isInlineAxis ? pos->mGridAutoColumns : pos->mGridAutoRows;
1127 return TrackSizingFunctions(szf, autoSizing, ForSubgridFallbackTag);
1131 * Initialize the number of auto-fill/fit tracks to use.
1132 * This can be zero if no auto-fill/fit track was specified, or if the repeat
1133 * begins after the maximum allowed track.
1135 void InitRepeatTracks(const NonNegativeLengthPercentageOrNormal& aGridGap,
1136 nscoord aMinSize, nscoord aSize, nscoord aMaxSize) {
1137 const uint32_t maxTrack = kMaxLine - 1;
1138 // Check for a repeat after the maximum allowed track.
1139 if (MOZ_UNLIKELY(mRepeatAutoStart >= maxTrack)) {
1140 mHasRepeatAuto = false;
1141 mRepeatAutoStart = 0;
1142 mRepeatAutoEnd = 0;
1143 return;
1145 uint32_t repeatTracks =
1146 CalculateRepeatFillCount(aGridGap, aMinSize, aSize, aMaxSize) *
1147 NumRepeatTracks();
1148 // Clamp the number of repeat tracks to the maximum possible track.
1149 repeatTracks = std::min(repeatTracks, maxTrack - mRepeatAutoStart);
1150 SetNumRepeatTracks(repeatTracks);
1151 // Blank out the removed flags for each of these tracks.
1152 mRemovedRepeatTracks.SetLength(repeatTracks);
1153 for (auto& track : mRemovedRepeatTracks) {
1154 track = false;
1158 uint32_t CalculateRepeatFillCount(
1159 const NonNegativeLengthPercentageOrNormal& aGridGap, nscoord aMinSize,
1160 nscoord aSize, nscoord aMaxSize) const {
1161 if (!mHasRepeatAuto) {
1162 return 0;
1164 // At this point no tracks will have been collapsed, so the RepeatEndDelta
1165 // should not be negative.
1166 MOZ_ASSERT(RepeatEndDelta() >= 0);
1167 // Note that this uses NumRepeatTracks and mRepeatAutoStart/End, although
1168 // the result of this method is used to change those values to a fully
1169 // expanded value. Spec quotes are from
1170 // https://drafts.csswg.org/css-grid/#repeat-notation
1171 const uint32_t numTracks = mExpandedTracks.Length() + RepeatEndDelta();
1172 MOZ_ASSERT(numTracks >= 1, "expected at least the repeat() track");
1173 if (MOZ_UNLIKELY(numTracks >= kMaxLine)) {
1174 // The fixed tracks plus an entire repetition is either larger or as
1175 // large as the maximum track, so we do not need to measure how many
1176 // repetitions will fit. This also avoids needing to check for if
1177 // kMaxLine - numTracks would underflow at the end where we clamp the
1178 // result.
1179 return 1;
1181 nscoord maxFill = aSize != NS_UNCONSTRAINEDSIZE ? aSize : aMaxSize;
1182 if (maxFill == NS_UNCONSTRAINEDSIZE && aMinSize == 0) {
1183 // "Otherwise, the specified track list repeats only once."
1184 return 1;
1186 nscoord repeatTrackSum = 0;
1187 // Note that one repeat() track size is included in |sum| in this loop.
1188 nscoord sum = 0;
1189 const nscoord percentBasis = aSize;
1190 for (uint32_t i = 0; i < numTracks; ++i) {
1191 // "treating each track as its max track sizing function if that is
1192 // definite or as its minimum track sizing function otherwise"
1193 // https://drafts.csswg.org/css-grid/#valdef-repeat-auto-fill
1194 const auto& sizingFunction = SizingFor(i);
1195 const auto& maxCoord = sizingFunction.GetMax();
1196 const auto* coord = &maxCoord;
1197 if (!coord->IsBreadth()) {
1198 coord = &sizingFunction.GetMin();
1199 if (!coord->IsBreadth()) {
1200 return 1;
1203 nscoord trackSize = ::ResolveToDefiniteSize(*coord, percentBasis);
1204 if (i >= mRepeatAutoStart && i < mRepeatAutoEnd) {
1205 // Use a minimum 1px for the repeat() track-size.
1206 if (trackSize < AppUnitsPerCSSPixel()) {
1207 trackSize = AppUnitsPerCSSPixel();
1209 repeatTrackSum += trackSize;
1211 sum += trackSize;
1213 nscoord gridGap = nsLayoutUtils::ResolveGapToLength(aGridGap, aSize);
1214 if (numTracks > 1) {
1215 // Add grid-gaps for all the tracks including the repeat() track.
1216 sum += gridGap * (numTracks - 1);
1218 // Calculate the max number of tracks that fits without overflow.
1219 nscoord available = maxFill != NS_UNCONSTRAINEDSIZE ? maxFill : aMinSize;
1220 nscoord spaceToFill = available - sum;
1221 if (spaceToFill <= 0) {
1222 // "if any number of repetitions would overflow, then 1 repetition"
1223 return 1;
1225 // Calculate the max number of tracks that fits without overflow.
1226 // Since we already have one repetition in sum, we can simply add one grid
1227 // gap for each element in the repeat.
1228 div_t q = div(spaceToFill, repeatTrackSum + gridGap * NumRepeatTracks());
1229 // The +1 here is for the one repeat track we already accounted for above.
1230 uint32_t numRepeatTracks = q.quot + 1;
1231 if (q.rem != 0 && maxFill == NS_UNCONSTRAINEDSIZE) {
1232 // "Otherwise, if the grid container has a definite min size in
1233 // the relevant axis, the number of repetitions is the largest possible
1234 // positive integer that fulfills that minimum requirement."
1235 ++numRepeatTracks; // one more to ensure the grid is at least min-size
1237 // Clamp the number of repeat tracks so that the last line <= kMaxLine.
1238 // (note that |numTracks| already includes one repeat() track)
1239 MOZ_ASSERT(numTracks >= NumRepeatTracks());
1240 const uint32_t maxRepeatTrackCount = kMaxLine - numTracks;
1241 const uint32_t maxRepetitions = maxRepeatTrackCount / NumRepeatTracks();
1242 return std::min(numRepeatTracks, maxRepetitions);
1246 * Compute the explicit grid end line number (in a zero-based grid).
1247 * @param aGridTemplateAreasEnd 'grid-template-areas' end line in this axis
1249 uint32_t ComputeExplicitGridEnd(uint32_t aGridTemplateAreasEnd) {
1250 uint32_t end = NumExplicitTracks() + 1;
1251 end = std::max(end, aGridTemplateAreasEnd);
1252 end = std::min(end, uint32_t(kMaxLine));
1253 return end;
1255 const StyleTrackSize& SizingFor(uint32_t aTrackIndex) const {
1256 static const StyleTrackSize kAutoTrackSize =
1257 StyleTrackSize::Breadth(StyleTrackBreadth::Auto());
1258 // |aIndex| is the relative index to mAutoSizing. A negative value means it
1259 // is the last Nth element.
1260 auto getImplicitSize = [this](int32_t aIndex) -> const StyleTrackSize& {
1261 MOZ_ASSERT(!(mAutoSizing.Length() == 1 &&
1262 mAutoSizing.AsSpan()[0] == kAutoTrackSize),
1263 "It's impossible to have one track with auto value because we "
1264 "filter out this case during parsing");
1266 if (mAutoSizing.IsEmpty()) {
1267 return kAutoTrackSize;
1270 // If multiple track sizes are given, the pattern is repeated as necessary
1271 // to find the size of the implicit tracks.
1272 int32_t i = aIndex % int32_t(mAutoSizing.Length());
1273 if (i < 0) {
1274 i += mAutoSizing.Length();
1276 return mAutoSizing.AsSpan()[i];
1279 if (MOZ_UNLIKELY(aTrackIndex < mExplicitGridOffset)) {
1280 // The last implicit grid track before the explicit grid receives the
1281 // last specified size, and so on backwards. Therefore we pass the
1282 // negative relative index to imply that we should get the implicit size
1283 // from the last Nth specified grid auto size.
1284 return getImplicitSize(int32_t(aTrackIndex) -
1285 int32_t(mExplicitGridOffset));
1287 uint32_t index = aTrackIndex - mExplicitGridOffset;
1288 MOZ_ASSERT(mRepeatAutoStart <= mRepeatAutoEnd);
1290 if (index >= mRepeatAutoStart) {
1291 if (index < mRepeatAutoEnd) {
1292 // Expand the repeat tracks.
1293 const auto& indices = mExpandedTracks[mRepeatAutoStart];
1294 const TrackListValue& value = mTrackListValues[indices.first];
1296 // We expect the default to be used for all track repeats.
1297 MOZ_ASSERT(indices.second == 0);
1299 const auto& repeatTracks = value.AsTrackRepeat().track_sizes.AsSpan();
1301 // Find the repeat track to use, skipping over any collapsed tracks.
1302 const uint32_t finalRepeatIndex = (index - mRepeatAutoStart);
1303 uint32_t repeatWithCollapsed = 0;
1304 // NOTE: We need SizingFor before the final collapsed tracks are known.
1305 // We know that it's invalid to have empty mRemovedRepeatTracks when
1306 // there are any repeat tracks, so we can detect that situation here.
1307 if (mRemovedRepeatTracks.IsEmpty()) {
1308 repeatWithCollapsed = finalRepeatIndex;
1309 } else {
1310 // Count up through the repeat tracks, until we have seen
1311 // finalRepeatIndex number of non-collapsed tracks.
1312 for (uint32_t repeatNoCollapsed = 0;
1313 repeatNoCollapsed < finalRepeatIndex; repeatWithCollapsed++) {
1314 if (!mRemovedRepeatTracks[repeatWithCollapsed]) {
1315 repeatNoCollapsed++;
1318 // If we stopped iterating on a collapsed track, continue to the next
1319 // non-collapsed track.
1320 while (mRemovedRepeatTracks[repeatWithCollapsed]) {
1321 repeatWithCollapsed++;
1324 return repeatTracks[repeatWithCollapsed % repeatTracks.Length()];
1325 } else {
1326 // The index is after the repeat auto range, adjust it to skip over the
1327 // repeat value. This will have no effect if there is no auto repeat,
1328 // since then RepeatEndDelta will return zero.
1329 index -= RepeatEndDelta();
1332 if (index >= mExpandedTracks.Length()) {
1333 return getImplicitSize(index - mExpandedTracks.Length());
1335 auto& indices = mExpandedTracks[index];
1336 const TrackListValue& value = mTrackListValues[indices.first];
1337 if (value.IsTrackSize()) {
1338 MOZ_ASSERT(indices.second == 0);
1339 return value.AsTrackSize();
1341 return value.AsTrackRepeat().track_sizes.AsSpan()[indices.second];
1343 const StyleTrackBreadth& MaxSizingFor(uint32_t aTrackIndex) const {
1344 return SizingFor(aTrackIndex).GetMax();
1346 const StyleTrackBreadth& MinSizingFor(uint32_t aTrackIndex) const {
1347 return SizingFor(aTrackIndex).GetMin();
1349 uint32_t NumExplicitTracks() const {
1350 return mExpandedTracks.Length() + RepeatEndDelta();
1352 uint32_t NumRepeatTracks() const { return mRepeatAutoEnd - mRepeatAutoStart; }
1353 // The difference between mExplicitGridEnd and mSizingFunctions.Length().
1354 int32_t RepeatEndDelta() const {
1355 return mHasRepeatAuto ? int32_t(NumRepeatTracks()) - 1 : 0;
1357 void SetNumRepeatTracks(uint32_t aNumRepeatTracks) {
1358 MOZ_ASSERT(mHasRepeatAuto || aNumRepeatTracks == 0);
1359 mRepeatAutoEnd = mRepeatAutoStart + aNumRepeatTracks;
1362 // Store mTrackListValues into mExpandedTracks with `repeat(INTEGER, ...)`
1363 // tracks expanded.
1364 void ExpandNonRepeatAutoTracks() {
1365 for (size_t i = 0; i < mTrackListValues.Length(); ++i) {
1366 auto& value = mTrackListValues[i];
1367 if (value.IsTrackSize()) {
1368 mExpandedTracks.EmplaceBack(i, 0);
1369 continue;
1371 auto& repeat = value.AsTrackRepeat();
1372 if (!repeat.count.IsNumber()) {
1373 MOZ_ASSERT(i == mRepeatAutoStart);
1374 mRepeatAutoStart = mExpandedTracks.Length();
1375 mRepeatAutoEnd = mRepeatAutoStart + repeat.track_sizes.Length();
1376 mExpandedTracks.EmplaceBack(i, 0);
1377 continue;
1379 for (auto j : IntegerRange(repeat.count.AsNumber())) {
1380 Unused << j;
1381 size_t trackSizesCount = repeat.track_sizes.Length();
1382 for (auto k : IntegerRange(trackSizesCount)) {
1383 mExpandedTracks.EmplaceBack(i, k);
1387 if (MOZ_UNLIKELY(mExpandedTracks.Length() > kMaxLine - 1)) {
1388 mExpandedTracks.TruncateLength(kMaxLine - 1);
1389 if (mHasRepeatAuto && mRepeatAutoStart > kMaxLine - 1) {
1390 // The `repeat(auto-fill/fit)` track is outside the clamped grid.
1391 mHasRepeatAuto = false;
1396 // Some style data references, for easy access.
1397 const GridTemplate& mTemplate;
1398 const Span<const TrackListValue> mTrackListValues;
1399 const StyleImplicitGridTracks& mAutoSizing;
1400 // An array from expanded track sizes (without expanding auto-repeat, which is
1401 // included just once at `mRepeatAutoStart`).
1403 // Each entry contains two indices, the first into mTrackListValues, and a
1404 // second one inside mTrackListValues' repeat value, if any, or zero
1405 // otherwise.
1406 nsTArray<std::pair<size_t, size_t>> mExpandedTracks;
1407 // Offset from the start of the implicit grid to the first explicit track.
1408 uint32_t mExplicitGridOffset;
1409 // The index of the repeat(auto-fill/fit) track, or zero if there is none.
1410 // Relative to mExplicitGridOffset (repeat tracks are explicit by definition).
1411 uint32_t mRepeatAutoStart;
1412 // The (hypothetical) index of the last such repeat() track.
1413 uint32_t mRepeatAutoEnd;
1414 // True if there is a specified repeat(auto-fill/fit) track.
1415 bool mHasRepeatAuto;
1416 // True if this track (relative to mRepeatAutoStart) is a removed auto-fit.
1417 // Indexed relative to mExplicitGridOffset + mRepeatAutoStart.
1418 nsTArray<bool> mRemovedRepeatTracks;
1422 * Utility class to find line names. It provides an interface to lookup line
1423 * names with a dynamic number of repeat(auto-fill/fit) tracks taken into
1424 * account.
1426 class MOZ_STACK_CLASS nsGridContainerFrame::LineNameMap {
1427 public:
1429 * Create a LineNameMap.
1430 * @param aStylePosition the style for the grid container
1431 * @param aImplicitNamedAreas the implicit areas for the grid container
1432 * @param aGridTemplate is the grid-template-rows/columns data for this axis
1433 * @param aParentLineNameMap the parent grid's map parallel to this map, or
1434 * null if this map isn't for a subgrid
1435 * @param aRange the subgrid's range in the parent grid, or null
1436 * @param aIsSameDirection true if our axis progresses in the same direction
1437 * in the subgrid and parent
1439 LineNameMap(const nsStylePosition* aStylePosition,
1440 const ImplicitNamedAreas* aImplicitNamedAreas,
1441 const TrackSizingFunctions& aTracks,
1442 const LineNameMap* aParentLineNameMap, const LineRange* aRange,
1443 bool aIsSameDirection)
1444 : mStylePosition(aStylePosition),
1445 mAreas(aImplicitNamedAreas),
1446 mRepeatAutoStart(aTracks.mRepeatAutoStart),
1447 mRepeatAutoEnd(aTracks.mRepeatAutoEnd),
1448 mRepeatEndDelta(aTracks.RepeatEndDelta()),
1449 mParentLineNameMap(aParentLineNameMap),
1450 mRange(aRange),
1451 mIsSameDirection(aIsSameDirection),
1452 mHasRepeatAuto(aTracks.mHasRepeatAuto) {
1453 if (MOZ_UNLIKELY(aRange)) { // subgrid case
1454 mClampMinLine = 1;
1455 mClampMaxLine = 1 + aRange->Extent();
1456 mRepeatAutoEnd = mRepeatAutoStart;
1457 const auto& styleSubgrid = aTracks.mTemplate.AsSubgrid();
1458 const auto fillLen = styleSubgrid->fill_len;
1459 mHasRepeatAuto = fillLen != 0;
1460 if (mHasRepeatAuto) {
1461 const auto& lineNameLists = styleSubgrid->names;
1462 const int32_t extraAutoFillLineCount =
1463 mClampMaxLine - lineNameLists.Length();
1464 // Maximum possible number of repeat name lists. This must be reduced
1465 // to a whole number of repetitions of the fill length.
1466 const uint32_t possibleRepeatLength =
1467 std::max<int32_t>(0, extraAutoFillLineCount + fillLen);
1468 const uint32_t repeatRemainder = possibleRepeatLength % fillLen;
1469 mRepeatAutoStart = styleSubgrid->fill_start;
1470 mRepeatAutoEnd =
1471 mRepeatAutoStart + possibleRepeatLength - repeatRemainder;
1473 } else {
1474 mClampMinLine = kMinLine;
1475 mClampMaxLine = kMaxLine;
1476 if (mHasRepeatAuto) {
1477 mTrackAutoRepeatLineNames =
1478 aTracks.mTemplate.GetRepeatAutoValue()->line_names.AsSpan();
1481 ExpandRepeatLineNames(!!aRange, aTracks);
1482 if (mHasRepeatAuto) {
1483 // We need mTemplateLinesEnd to be after all line names.
1484 // mExpandedLineNames has one repetition of the repeat(auto-fit/fill)
1485 // track name lists already, so we must subtract the number of repeat
1486 // track name lists to get to the number of non-repeat tracks, minus 2
1487 // because the first and last line name lists are shared with the
1488 // preceding and following non-repeat line name lists. We then add
1489 // mRepeatEndDelta to include the interior line name lists from repeat
1490 // tracks.
1491 mTemplateLinesEnd = mExpandedLineNames.Length() -
1492 (mTrackAutoRepeatLineNames.Length() - 2) +
1493 mRepeatEndDelta;
1494 } else {
1495 mTemplateLinesEnd = mExpandedLineNames.Length();
1497 MOZ_ASSERT(mHasRepeatAuto || mRepeatEndDelta <= 0);
1498 MOZ_ASSERT(!mHasRepeatAuto || aRange ||
1499 (mExpandedLineNames.Length() >= 2 &&
1500 mRepeatAutoStart <= mExpandedLineNames.Length()));
1503 // Store line names into mExpandedLineNames with `repeat(INTEGER, ...)`
1504 // expanded (for non-subgrid), and all `repeat(...)` expanded (for subgrid).
1505 void ExpandRepeatLineNames(bool aIsSubgrid,
1506 const TrackSizingFunctions& aTracks) {
1507 auto lineNameLists = aTracks.mTemplate.LineNameLists(aIsSubgrid);
1509 const auto& trackListValues = aTracks.mTrackListValues;
1510 const NameList* nameListToMerge = nullptr;
1511 // NOTE(emilio): We rely on std::move clearing out the array.
1512 SmallPointerArray<const NameList> names;
1513 // This adjusts for outputting the repeat auto names in subgrid. In that
1514 // case, all of the repeat values are handled in a single iteration.
1515 const uint32_t subgridRepeatDelta =
1516 (aIsSubgrid && mHasRepeatAuto)
1517 ? (aTracks.mTemplate.AsSubgrid()->fill_len - 1)
1518 : 0;
1519 const uint32_t end = std::min<uint32_t>(
1520 lineNameLists.Length() - subgridRepeatDelta, mClampMaxLine + 1);
1521 for (uint32_t i = 0; i < end; ++i) {
1522 if (aIsSubgrid) {
1523 if (MOZ_UNLIKELY(mHasRepeatAuto && i == mRepeatAutoStart)) {
1524 // XXX expand 'auto-fill' names for subgrid for now since HasNameAt()
1525 // only deals with auto-repeat **tracks** currently.
1526 const auto& styleSubgrid = aTracks.mTemplate.AsSubgrid();
1527 MOZ_ASSERT(styleSubgrid->fill_len > 0);
1528 for (auto j = i; j < mRepeatAutoEnd; ++j) {
1529 const auto repeatIndex = (j - i) % styleSubgrid->fill_len;
1530 names.AppendElement(
1531 &lineNameLists[styleSubgrid->fill_start + repeatIndex]);
1532 mExpandedLineNames.AppendElement(std::move(names));
1534 } else if (mHasRepeatAuto && i > mRepeatAutoStart) {
1535 const auto& styleSubgrid = aTracks.mTemplate.AsSubgrid();
1536 names.AppendElement(&lineNameLists[i + styleSubgrid->fill_len - 1]);
1537 mExpandedLineNames.AppendElement(std::move(names));
1538 } else {
1539 names.AppendElement(&lineNameLists[i]);
1540 mExpandedLineNames.AppendElement(std::move(names));
1542 // XXX expand repeat(<integer>, ...) line names here (bug 1583429)
1543 continue;
1546 if (nameListToMerge) {
1547 names.AppendElement(nameListToMerge);
1548 nameListToMerge = nullptr;
1550 names.AppendElement(&lineNameLists[i]);
1551 if (i >= trackListValues.Length()) {
1552 mExpandedLineNames.AppendElement(std::move(names));
1553 continue;
1555 const auto& value = trackListValues[i];
1556 if (value.IsTrackSize()) {
1557 mExpandedLineNames.AppendElement(std::move(names));
1558 continue;
1560 const auto& repeat = value.AsTrackRepeat();
1561 if (!repeat.count.IsNumber()) {
1562 const auto repeatNames = repeat.line_names.AsSpan();
1563 // If the repeat was truncated due to more than kMaxLine tracks, then
1564 // the repeat will no longer be set on mRepeatAutoStart).
1565 MOZ_ASSERT(!mHasRepeatAuto ||
1566 mRepeatAutoStart == mExpandedLineNames.Length());
1567 MOZ_ASSERT(repeatNames.Length() >= 2);
1568 for (const auto j : IntegerRange(repeatNames.Length() - 1)) {
1569 names.AppendElement(&repeatNames[j]);
1570 mExpandedLineNames.AppendElement(std::move(names));
1572 nameListToMerge = &repeatNames[repeatNames.Length() - 1];
1573 continue;
1575 for (auto j : IntegerRange(repeat.count.AsNumber())) {
1576 Unused << j;
1577 if (nameListToMerge) {
1578 names.AppendElement(nameListToMerge);
1579 nameListToMerge = nullptr;
1581 size_t trackSizesCount = repeat.track_sizes.Length();
1582 auto repeatLineNames = repeat.line_names.AsSpan();
1583 MOZ_ASSERT(repeatLineNames.Length() == trackSizesCount ||
1584 repeatLineNames.Length() == trackSizesCount + 1);
1585 for (auto k : IntegerRange(trackSizesCount)) {
1586 names.AppendElement(&repeatLineNames[k]);
1587 mExpandedLineNames.AppendElement(std::move(names));
1589 if (repeatLineNames.Length() == trackSizesCount + 1) {
1590 nameListToMerge = &repeatLineNames[trackSizesCount];
1595 if (MOZ_UNLIKELY(mExpandedLineNames.Length() > uint32_t(mClampMaxLine))) {
1596 mExpandedLineNames.TruncateLength(mClampMaxLine);
1598 if (MOZ_UNLIKELY(mHasRepeatAuto && aIsSubgrid)) {
1599 mHasRepeatAuto = false; // we've expanded all subgrid auto-fill lines
1604 * Find the aNth occurrence of aName, searching forward if aNth is positive,
1605 * and in reverse if aNth is negative (aNth == 0 is invalid), starting from
1606 * aFromIndex (not inclusive), and return a 1-based line number.
1607 * Also take into account there is an unconditional match at the lines in
1608 * aImplicitLines.
1609 * Return zero if aNth occurrences can't be found. In that case, aNth has
1610 * been decremented with the number of occurrences that were found (if any).
1612 * E.g. to search for "A 2" forward from the start of the grid: aName is "A"
1613 * aNth is 2 and aFromIndex is zero. To search for "A -2", aNth is -2 and
1614 * aFromIndex is ExplicitGridEnd + 1 (which is the line "before" the last
1615 * line when we're searching in reverse). For "span A 2", aNth is 2 when
1616 * used on a grid-[row|column]-end property and -2 for a *-start property,
1617 * and aFromIndex is the line (which we should skip) on the opposite property.
1619 uint32_t FindNamedLine(nsAtom* aName, int32_t* aNth, uint32_t aFromIndex,
1620 const nsTArray<uint32_t>& aImplicitLines) const {
1621 MOZ_ASSERT(aName);
1622 MOZ_ASSERT(!aName->IsEmpty());
1623 MOZ_ASSERT(aNth && *aNth != 0);
1624 if (*aNth > 0) {
1625 return FindLine(aName, aNth, aFromIndex, aImplicitLines);
1627 int32_t nth = -*aNth;
1628 int32_t line = RFindLine(aName, &nth, aFromIndex, aImplicitLines);
1629 *aNth = -nth;
1630 return line;
1634 * Return a set of lines in aImplicitLines which matches the area name aName
1635 * on aSide. For example, for aName "a" and aSide being an end side, it
1636 * returns the line numbers which would match "a-end" in the relevant axis.
1637 * For subgrids it includes searching the relevant axis in all ancestor
1638 * grids too (within this subgrid's spanned area). If an ancestor has
1639 * opposite direction, we switch aSide to the opposite logical side so we
1640 * match on the same physical side as the original subgrid we're resolving
1641 * the name for.
1643 void FindNamedAreas(nsAtom* aName, LogicalSide aSide,
1644 nsTArray<uint32_t>& aImplicitLines) const {
1645 // True if we're currently in a map that has the same direction as 'this'.
1646 bool sameDirectionAsThis = true;
1647 uint32_t min = !mParentLineNameMap ? 1 : mClampMinLine;
1648 uint32_t max = mClampMaxLine;
1649 for (auto* map = this; true;) {
1650 uint32_t line = map->FindNamedArea(aName, aSide, min, max);
1651 if (line > 0) {
1652 if (MOZ_LIKELY(sameDirectionAsThis)) {
1653 line -= min - 1;
1654 } else {
1655 line = max - line + 1;
1657 aImplicitLines.AppendElement(line);
1659 auto* parent = map->mParentLineNameMap;
1660 if (!parent) {
1661 if (MOZ_UNLIKELY(aImplicitLines.Length() > 1)) {
1662 // Remove duplicates and sort in ascending order.
1663 aImplicitLines.Sort();
1664 for (size_t i = 0; i < aImplicitLines.Length(); ++i) {
1665 uint32_t prev = aImplicitLines[i];
1666 auto j = i + 1;
1667 const auto start = j;
1668 while (j < aImplicitLines.Length() && aImplicitLines[j] == prev) {
1669 ++j;
1671 if (j != start) {
1672 aImplicitLines.RemoveElementsAt(start, j - start);
1676 return;
1678 if (MOZ_UNLIKELY(!map->mIsSameDirection)) {
1679 aSide = GetOppositeSide(aSide);
1680 sameDirectionAsThis = !sameDirectionAsThis;
1682 min = map->TranslateToParentMap(min);
1683 max = map->TranslateToParentMap(max);
1684 if (min > max) {
1685 MOZ_ASSERT(!map->mIsSameDirection);
1686 std::swap(min, max);
1688 map = parent;
1693 * Return true if any implicit named areas match aName, in this map or
1694 * in any of our ancestor maps.
1696 bool HasImplicitNamedArea(nsAtom* aName) const {
1697 const auto* map = this;
1698 do {
1699 if (map->mAreas && map->mAreas->has(aName)) {
1700 return true;
1702 map = map->mParentLineNameMap;
1703 } while (map);
1704 return false;
1707 // For generating line name data for devtools.
1708 nsTArray<nsTArray<StyleCustomIdent>>
1709 GetResolvedLineNamesForComputedGridTrackInfo() const {
1710 nsTArray<nsTArray<StyleCustomIdent>> result;
1711 for (auto& expandedLine : mExpandedLineNames) {
1712 nsTArray<StyleCustomIdent> line;
1713 for (auto* chunk : expandedLine) {
1714 for (auto& name : chunk->AsSpan()) {
1715 line.AppendElement(name);
1718 result.AppendElement(std::move(line));
1720 return result;
1723 nsTArray<RefPtr<nsAtom>> GetExplicitLineNamesAtIndex(uint32_t aIndex) const {
1724 nsTArray<RefPtr<nsAtom>> lineNames;
1725 if (aIndex < mTemplateLinesEnd) {
1726 const auto nameLists = GetLineNamesAt(aIndex);
1727 for (const NameList* nameList : nameLists) {
1728 for (const auto& name : nameList->AsSpan()) {
1729 lineNames.AppendElement(name.AsAtom());
1733 return lineNames;
1736 const nsTArray<SmallPointerArray<const NameList>>& ExpandedLineNames() const {
1737 return mExpandedLineNames;
1739 const Span<const StyleOwnedSlice<StyleCustomIdent>>&
1740 TrackAutoRepeatLineNames() const {
1741 return mTrackAutoRepeatLineNames;
1743 bool HasRepeatAuto() const { return mHasRepeatAuto; }
1744 uint32_t NumRepeatTracks() const { return mRepeatAutoEnd - mRepeatAutoStart; }
1745 uint32_t RepeatAutoStart() const { return mRepeatAutoStart; }
1747 // The min/max line number (1-based) for clamping.
1748 int32_t mClampMinLine;
1749 int32_t mClampMaxLine;
1751 private:
1752 // Return true if this map represents a subgridded axis.
1753 bool IsSubgridded() const { return mParentLineNameMap != nullptr; }
1756 * @see FindNamedLine, this function searches forward.
1758 uint32_t FindLine(nsAtom* aName, int32_t* aNth, uint32_t aFromIndex,
1759 const nsTArray<uint32_t>& aImplicitLines) const {
1760 MOZ_ASSERT(aNth && *aNth > 0);
1761 int32_t nth = *aNth;
1762 // For a subgrid we need to search to the end of the grid rather than
1763 // the end of the local name list, since ancestors might match.
1764 const uint32_t end = IsSubgridded() ? mClampMaxLine : mTemplateLinesEnd;
1765 uint32_t line;
1766 uint32_t i = aFromIndex;
1767 for (; i < end; i = line) {
1768 line = i + 1;
1769 if (Contains(i, aName) || aImplicitLines.Contains(line)) {
1770 if (--nth == 0) {
1771 return line;
1775 for (auto implicitLine : aImplicitLines) {
1776 if (implicitLine > i) {
1777 // implicitLine is after the lines we searched above so it's last.
1778 // (grid-template-areas has more tracks than
1779 // grid-template-[rows|columns])
1780 if (--nth == 0) {
1781 return implicitLine;
1785 MOZ_ASSERT(nth > 0, "should have returned a valid line above already");
1786 *aNth = nth;
1787 return 0;
1791 * @see FindNamedLine, this function searches in reverse.
1793 uint32_t RFindLine(nsAtom* aName, int32_t* aNth, uint32_t aFromIndex,
1794 const nsTArray<uint32_t>& aImplicitLines) const {
1795 MOZ_ASSERT(aNth && *aNth > 0);
1796 if (MOZ_UNLIKELY(aFromIndex == 0)) {
1797 return 0; // There are no named lines beyond the start of the explicit
1798 // grid.
1800 --aFromIndex; // (shift aFromIndex so we can treat it as inclusive)
1801 int32_t nth = *aNth;
1802 // Implicit lines may be beyond the explicit grid so we match those
1803 // first if it's within the mTemplateLinesEnd..aFromIndex range.
1804 // aImplicitLines is presumed sorted.
1805 // For a subgrid we need to search to the end of the grid rather than
1806 // the end of the local name list, since ancestors might match.
1807 const uint32_t end = IsSubgridded() ? mClampMaxLine : mTemplateLinesEnd;
1808 for (auto implicitLine : Reversed(aImplicitLines)) {
1809 if (implicitLine <= end) {
1810 break;
1812 if (implicitLine < aFromIndex) {
1813 if (--nth == 0) {
1814 return implicitLine;
1818 for (uint32_t i = std::min(aFromIndex, end); i; --i) {
1819 if (Contains(i - 1, aName) || aImplicitLines.Contains(i)) {
1820 if (--nth == 0) {
1821 return i;
1825 MOZ_ASSERT(nth > 0, "should have returned a valid line above already");
1826 *aNth = nth;
1827 return 0;
1830 // Return true if aName exists at aIndex in this map or any parent map.
1831 bool Contains(uint32_t aIndex, nsAtom* aName) const {
1832 const auto* map = this;
1833 while (true) {
1834 if (aIndex < map->mTemplateLinesEnd && map->HasNameAt(aIndex, aName)) {
1835 return true;
1837 auto* parent = map->mParentLineNameMap;
1838 if (!parent) {
1839 return false;
1841 uint32_t line = map->TranslateToParentMap(aIndex + 1);
1842 MOZ_ASSERT(line >= 1, "expected a 1-based line number");
1843 aIndex = line - 1;
1844 map = parent;
1846 MOZ_ASSERT_UNREACHABLE("we always return from inside the loop above");
1849 static bool Contains(Span<const StyleCustomIdent> aNames, nsAtom* aName) {
1850 for (auto& name : aNames) {
1851 if (name.AsAtom() == aName) {
1852 return true;
1855 return false;
1858 // Return true if aName exists at aIndex in this map.
1859 bool HasNameAt(const uint32_t aIndex, nsAtom* const aName) const {
1860 const auto nameLists = GetLineNamesAt(aIndex);
1861 for (const NameList* nameList : nameLists) {
1862 if (Contains(nameList->AsSpan(), aName)) {
1863 return true;
1866 return false;
1869 // Get the line names at an index.
1870 // This accounts for auto repeat. The results may be spread over multiple name
1871 // lists returned in the array, which is done to avoid unneccessarily copying
1872 // the arrays to concatenate them.
1873 SmallPointerArray<const NameList> GetLineNamesAt(
1874 const uint32_t aIndex) const {
1875 SmallPointerArray<const NameList> names;
1876 // The index into mExpandedLineNames to use, if aIndex doesn't point to a
1877 // name inside of a auto repeat.
1878 uint32_t repeatAdjustedIndex = aIndex;
1879 if (mHasRepeatAuto) {
1880 // If the index is inside of the auto repeat, use the repeat line
1881 // names. Otherwise, if the index is past the end of the repeat it must
1882 // be adjusted to acount for the repeat tracks.
1883 // mExpandedLineNames has the first and last line name lists from the
1884 // repeat in it already, so we can just ignore aIndex == mRepeatAutoStart
1885 // and treat when aIndex == mRepeatAutoEnd the same as any line after the
1886 // the repeat.
1887 const uint32_t maxRepeatLine = mTrackAutoRepeatLineNames.Length() - 1;
1888 if (aIndex > mRepeatAutoStart && aIndex < mRepeatAutoEnd) {
1889 // The index is inside the auto repeat. Calculate the lines to use,
1890 // including the previous repetitions final names when we roll over
1891 // from one repetition to the next.
1892 const uint32_t repeatIndex =
1893 (aIndex - mRepeatAutoStart) % maxRepeatLine;
1894 if (repeatIndex == 0) {
1895 // The index is at the start of a new repetition. The start of the
1896 // first repetition is intentionally ignored above, so this will
1897 // consider both the end of the previous repetition and the start
1898 // the one that contains aIndex.
1899 names.AppendElement(&mTrackAutoRepeatLineNames[maxRepeatLine]);
1901 names.AppendElement(&mTrackAutoRepeatLineNames[repeatIndex]);
1902 return names;
1904 if (aIndex != mRepeatAutoStart && aIndex >= mRepeatAutoEnd) {
1905 // Adjust the index to account for the line names of the repeat.
1906 repeatAdjustedIndex -= mRepeatEndDelta;
1907 repeatAdjustedIndex += mTrackAutoRepeatLineNames.Length() - 2;
1910 MOZ_ASSERT(names.IsEmpty());
1911 // The index is not inside the repeat tracks, or no repeat tracks exist.
1912 const auto& nameLists = mExpandedLineNames[repeatAdjustedIndex];
1913 for (const NameList* nameList : nameLists) {
1914 names.AppendElement(nameList);
1916 return names;
1919 // Translate a subgrid line (1-based) to a parent line (1-based).
1920 uint32_t TranslateToParentMap(uint32_t aLine) const {
1921 if (MOZ_LIKELY(mIsSameDirection)) {
1922 return aLine + mRange->mStart;
1924 MOZ_ASSERT(mRange->mEnd + 1 >= aLine);
1925 return mRange->mEnd - (aLine - 1) + 1;
1929 * Return the 1-based line that match aName in 'grid-template-areas'
1930 * on the side aSide. Clamp the result to aMin..aMax but require
1931 * that some part of the area is inside for it to match.
1932 * Return zero if there is no match.
1934 uint32_t FindNamedArea(nsAtom* aName, LogicalSide aSide, int32_t aMin,
1935 int32_t aMax) const {
1936 if (const NamedArea* area = FindNamedArea(aName)) {
1937 int32_t start = IsBlock(aSide) ? area->rows.start : area->columns.start;
1938 int32_t end = IsBlock(aSide) ? area->rows.end : area->columns.end;
1939 if (IsStart(aSide)) {
1940 if (start >= aMin) {
1941 if (start <= aMax) {
1942 return start;
1944 } else if (end >= aMin) {
1945 return aMin;
1947 } else {
1948 if (end <= aMax) {
1949 if (end >= aMin) {
1950 return end;
1952 } else if (start <= aMax) {
1953 return aMax;
1957 return 0; // no match
1961 * A convenience method to lookup a name in 'grid-template-areas'.
1962 * @return null if not found
1964 const NamedArea* FindNamedArea(nsAtom* aName) const {
1965 if (mStylePosition->mGridTemplateAreas.IsNone()) {
1966 return nullptr;
1968 const auto areas = mStylePosition->mGridTemplateAreas.AsAreas();
1969 for (const NamedArea& area : areas->areas.AsSpan()) {
1970 if (area.name.AsAtom() == aName) {
1971 return &area;
1974 return nullptr;
1977 // Some style data references, for easy access.
1978 const nsStylePosition* mStylePosition;
1979 const ImplicitNamedAreas* mAreas;
1980 // The expanded list of line-names. Each entry is usually a single NameList,
1981 // but can be multiple in the case where repeat() expands to something that
1982 // has a line name list at the end.
1983 nsTArray<SmallPointerArray<const NameList>> mExpandedLineNames;
1984 // The repeat(auto-fill/fit) track value, if any. (always empty for subgrid)
1985 Span<const StyleOwnedSlice<StyleCustomIdent>> mTrackAutoRepeatLineNames;
1986 // The index of the repeat(auto-fill/fit) track, or zero if there is none.
1987 uint32_t mRepeatAutoStart;
1988 // The index one past the end of the repeat(auto-fill/fit) tracks. Equal to
1989 // mRepeatAutoStart if there are no repeat(auto-fill/fit) tracks.
1990 uint32_t mRepeatAutoEnd;
1991 // The total number of repeat tracks minus 1.
1992 int32_t mRepeatEndDelta;
1993 // The end of the line name lists with repeat(auto-fill/fit) tracks accounted
1994 // for.
1995 uint32_t mTemplateLinesEnd;
1997 // The parent line map, or null if this map isn't for a subgrid.
1998 const LineNameMap* mParentLineNameMap;
1999 // The subgrid's range, or null if this map isn't for a subgrid.
2000 const LineRange* mRange;
2001 // True if the subgrid/parent axes progresses in the same direction.
2002 const bool mIsSameDirection;
2004 // True if there is a specified repeat(auto-fill/fit) track.
2005 bool mHasRepeatAuto;
2009 * State for the tracks in one dimension.
2011 struct nsGridContainerFrame::Tracks {
2012 explicit Tracks(LogicalAxis aAxis)
2013 : mContentBoxSize(NS_UNCONSTRAINEDSIZE),
2014 mGridGap(NS_UNCONSTRAINEDSIZE),
2015 mStateUnion(TrackSize::StateBits{0}),
2016 mAxis(aAxis),
2017 mCanResolveLineRangeSize(false),
2018 mIsMasonry(false) {
2019 mBaselineSubtreeAlign[BaselineSharingGroup::First] = StyleAlignFlags::AUTO;
2020 mBaselineSubtreeAlign[BaselineSharingGroup::Last] = StyleAlignFlags::AUTO;
2021 mBaseline[BaselineSharingGroup::First] = NS_INTRINSIC_ISIZE_UNKNOWN;
2022 mBaseline[BaselineSharingGroup::Last] = NS_INTRINSIC_ISIZE_UNKNOWN;
2025 void Initialize(const TrackSizingFunctions& aFunctions,
2026 const NonNegativeLengthPercentageOrNormal& aGridGap,
2027 uint32_t aNumTracks, nscoord aContentBoxSize);
2030 * Return the union of the state bits for the tracks in aRange.
2032 TrackSize::StateBits StateBitsForRange(const LineRange& aRange) const;
2034 // Some data we collect for aligning baseline-aligned items.
2035 struct ItemBaselineData {
2036 uint32_t mBaselineTrack;
2037 nscoord mBaseline;
2038 nscoord mSize;
2039 GridItemInfo* mGridItem;
2040 static bool IsBaselineTrackLessThan(const ItemBaselineData& a,
2041 const ItemBaselineData& b) {
2042 return a.mBaselineTrack < b.mBaselineTrack;
2047 * Calculate baseline offsets for the given set of items.
2048 * Helper for InitialzeItemBaselines.
2050 void CalculateItemBaselines(nsTArray<ItemBaselineData>& aBaselineItems,
2051 BaselineSharingGroup aBaselineGroup);
2054 * Initialize grid item baseline state and offsets.
2056 void InitializeItemBaselines(GridReflowInput& aState,
2057 nsTArray<GridItemInfo>& aGridItems);
2060 * A masonry axis has four baseline alignment sets and each set can have
2061 * a first- and last-baseline alignment group, for a total of eight possible
2062 * baseline alignment groups, as follows:
2063 * set 1: the first item in each `start` or `stretch` grid track
2064 * set 2: the last item in each `start` grid track
2065 * set 3: the last item in each `end` or `stretch` grid track
2066 * set 4: the first item in each `end` grid track
2067 * (`start`/`end`/`stretch` refers to the relevant `align/justify-tracks`
2068 * value of the (grid-axis) start track for the item) Baseline-alignment for
2069 * set 1 and 2 always adjusts the item's padding or margin on the start side,
2070 * and set 3 and 4 on the end side, for both first- and last-baseline groups
2071 * in the set. (This is similar to regular grid which always adjusts
2072 * first-baseline groups on the start side and last-baseline groups on the
2073 * end-side. The crux is that those groups are always aligned to the track's
2074 * start/end side respectively.)
2076 struct BaselineAlignmentSet {
2077 bool MatchTrackAlignment(StyleAlignFlags aTrackAlignment) const {
2078 if (mTrackAlignmentSet == BaselineAlignmentSet::StartStretch) {
2079 return aTrackAlignment == StyleAlignFlags::START ||
2080 (aTrackAlignment == StyleAlignFlags::STRETCH &&
2081 mItemSet == BaselineAlignmentSet::FirstItems);
2083 return aTrackAlignment == StyleAlignFlags::END ||
2084 (aTrackAlignment == StyleAlignFlags::STRETCH &&
2085 mItemSet == BaselineAlignmentSet::LastItems);
2088 enum ItemSet { FirstItems, LastItems };
2089 ItemSet mItemSet = FirstItems;
2090 enum TrackAlignmentSet { StartStretch, EndStretch };
2091 TrackAlignmentSet mTrackAlignmentSet = StartStretch;
2093 void InitializeItemBaselinesInMasonryAxis(
2094 GridReflowInput& aState, nsTArray<GridItemInfo>& aGridItems,
2095 BaselineAlignmentSet aSet, const nsSize& aContainerSize,
2096 nsTArray<nscoord>& aTrackSizes,
2097 nsTArray<ItemBaselineData>& aFirstBaselineItems,
2098 nsTArray<ItemBaselineData>& aLastBaselineItems);
2101 * Apply the additional alignment needed to align the baseline-aligned subtree
2102 * the item belongs to within its baseline track.
2104 void AlignBaselineSubtree(const GridItemInfo& aGridItem) const;
2106 enum class TrackSizingPhase {
2107 IntrinsicMinimums,
2108 ContentBasedMinimums,
2109 MaxContentMinimums,
2110 IntrinsicMaximums,
2111 MaxContentMaximums,
2114 // Some data we collect on each item that spans more than one track for step 3
2115 // and 4 of the Track Sizing Algorithm in ResolveIntrinsicSize below.
2116 // https://w3c.github.io/csswg-drafts/css-grid-1/#algo-spanning-items
2117 struct SpanningItemData final {
2118 uint32_t mSpan;
2119 TrackSize::StateBits mState;
2120 LineRange mLineRange;
2121 nscoord mMinSize;
2122 nscoord mMinContentContribution;
2123 nscoord mMaxContentContribution;
2124 nsIFrame* mFrame;
2126 static bool IsSpanLessThan(const SpanningItemData& a,
2127 const SpanningItemData& b) {
2128 return a.mSpan < b.mSpan;
2131 template <TrackSizingPhase phase>
2132 nscoord SizeContributionForPhase() const {
2133 switch (phase) {
2134 case TrackSizingPhase::IntrinsicMinimums:
2135 return mMinSize;
2136 case TrackSizingPhase::ContentBasedMinimums:
2137 case TrackSizingPhase::IntrinsicMaximums:
2138 return mMinContentContribution;
2139 case TrackSizingPhase::MaxContentMinimums:
2140 case TrackSizingPhase::MaxContentMaximums:
2141 return mMaxContentContribution;
2143 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected phase");
2146 #ifdef DEBUG
2147 void Dump() const {
2148 printf(
2149 "SpanningItemData { mSpan: %d, mState: %d, mLineRange: (%d, %d), "
2150 "mMinSize: %d, mMinContentContribution: %d, mMaxContentContribution: "
2151 "%d, mFrame: %p\n",
2152 mSpan, mState, mLineRange.mStart, mLineRange.mEnd, mMinSize,
2153 mMinContentContribution, mMaxContentContribution, mFrame);
2155 #endif
2158 using FitContentClamper =
2159 std::function<bool(uint32_t aTrack, nscoord aMinSize, nscoord* aSize)>;
2161 // Helper method for ResolveIntrinsicSize.
2162 template <TrackSizingPhase phase>
2163 bool GrowSizeForSpanningItems(
2164 nsTArray<SpanningItemData>::iterator aIter,
2165 nsTArray<SpanningItemData>::iterator aIterEnd,
2166 nsTArray<uint32_t>& aTracks, nsTArray<TrackSize>& aPlan,
2167 nsTArray<TrackSize>& aItemPlan, TrackSize::StateBits aSelector,
2168 const FitContentClamper& aFitContentClamper = nullptr,
2169 bool aNeedInfinitelyGrowableFlag = false);
2171 * Resolve Intrinsic Track Sizes.
2172 * http://dev.w3.org/csswg/css-grid/#algo-content
2174 void ResolveIntrinsicSize(GridReflowInput& aState,
2175 nsTArray<GridItemInfo>& aGridItems,
2176 const TrackSizingFunctions& aFunctions,
2177 LineRange GridArea::*aRange,
2178 nscoord aPercentageBasis,
2179 SizingConstraint aConstraint);
2182 * Helper for ResolveIntrinsicSize. It implements step 1 "size tracks to fit
2183 * non-spanning items" in the spec. Return true if the track has a <flex>
2184 * max-sizing function, false otherwise.
2186 bool ResolveIntrinsicSizeForNonSpanningItems(
2187 GridReflowInput& aState, const TrackSizingFunctions& aFunctions,
2188 nscoord aPercentageBasis, SizingConstraint aConstraint,
2189 const LineRange& aRange, const GridItemInfo& aGridItem);
2191 // Helper method that returns the track size to use in §11.5.1.2
2192 // https://drafts.csswg.org/css-grid/#extra-space
2193 template <TrackSizingPhase phase>
2194 static nscoord StartSizeInDistribution(const TrackSize& aSize) {
2195 switch (phase) {
2196 case TrackSizingPhase::IntrinsicMinimums:
2197 case TrackSizingPhase::ContentBasedMinimums:
2198 case TrackSizingPhase::MaxContentMinimums:
2199 return aSize.mBase;
2200 case TrackSizingPhase::IntrinsicMaximums:
2201 case TrackSizingPhase::MaxContentMaximums:
2202 if (aSize.mLimit == NS_UNCONSTRAINEDSIZE) {
2203 return aSize.mBase;
2205 return aSize.mLimit;
2207 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected phase");
2211 * Collect the tracks which are growable (matching aSelector) into
2212 * aGrowableTracks, and return the amount of space that can be used
2213 * to grow those tracks. This method implements CSS Grid §11.5.1.2.
2214 * https://drafts.csswg.org/css-grid/#extra-space
2216 template <TrackSizingPhase phase>
2217 nscoord CollectGrowable(nscoord aAvailableSpace, const LineRange& aRange,
2218 TrackSize::StateBits aSelector,
2219 nsTArray<uint32_t>& aGrowableTracks) const {
2220 MOZ_ASSERT(aAvailableSpace > 0, "why call me?");
2221 nscoord space = aAvailableSpace - mGridGap * (aRange.Extent() - 1);
2222 for (auto i : aRange.Range()) {
2223 const TrackSize& sz = mSizes[i];
2224 space -= StartSizeInDistribution<phase>(sz);
2225 if (space <= 0) {
2226 return 0;
2228 if (sz.mState & aSelector) {
2229 aGrowableTracks.AppendElement(i);
2232 return aGrowableTracks.IsEmpty() ? 0 : space;
2235 template <TrackSizingPhase phase>
2236 void InitializeItemPlan(nsTArray<TrackSize>& aItemPlan,
2237 const nsTArray<uint32_t>& aTracks) const {
2238 for (uint32_t track : aTracks) {
2239 auto& plan = aItemPlan[track];
2240 const TrackSize& sz = mSizes[track];
2241 plan.mBase = StartSizeInDistribution<phase>(sz);
2242 bool unlimited = sz.mState & TrackSize::eInfinitelyGrowable;
2243 plan.mLimit = unlimited ? NS_UNCONSTRAINEDSIZE : sz.mLimit;
2244 plan.mState = sz.mState;
2248 template <TrackSizingPhase phase>
2249 void InitializePlan(nsTArray<TrackSize>& aPlan) const {
2250 for (size_t i = 0, len = aPlan.Length(); i < len; ++i) {
2251 auto& plan = aPlan[i];
2252 const auto& sz = mSizes[i];
2253 plan.mBase = StartSizeInDistribution<phase>(sz);
2254 MOZ_ASSERT(phase == TrackSizingPhase::MaxContentMaximums ||
2255 !(sz.mState & TrackSize::eInfinitelyGrowable),
2256 "forgot to reset the eInfinitelyGrowable bit?");
2257 plan.mState = sz.mState;
2261 template <TrackSizingPhase phase>
2262 void CopyPlanToSize(const nsTArray<TrackSize>& aPlan,
2263 bool aNeedInfinitelyGrowableFlag = false) {
2264 for (size_t i = 0, len = mSizes.Length(); i < len; ++i) {
2265 const auto& plan = aPlan[i];
2266 MOZ_ASSERT(plan.mBase >= 0);
2267 auto& sz = mSizes[i];
2268 switch (phase) {
2269 case TrackSizingPhase::IntrinsicMinimums:
2270 case TrackSizingPhase::ContentBasedMinimums:
2271 case TrackSizingPhase::MaxContentMinimums:
2272 sz.mBase = plan.mBase;
2273 break;
2274 case TrackSizingPhase::IntrinsicMaximums:
2275 if (plan.mState & TrackSize::eModified) {
2276 if (sz.mLimit == NS_UNCONSTRAINEDSIZE &&
2277 aNeedInfinitelyGrowableFlag) {
2278 sz.mState |= TrackSize::eInfinitelyGrowable;
2280 sz.mLimit = plan.mBase;
2282 break;
2283 case TrackSizingPhase::MaxContentMaximums:
2284 if (plan.mState & TrackSize::eModified) {
2285 sz.mLimit = plan.mBase;
2287 sz.mState &= ~TrackSize::eInfinitelyGrowable;
2288 break;
2294 * Grow the planned size for tracks in aGrowableTracks up to their limit
2295 * and then freeze them (all aGrowableTracks must be unfrozen on entry).
2296 * Subtract the space added from aAvailableSpace and return that.
2298 nscoord GrowTracksToLimit(nscoord aAvailableSpace, nsTArray<TrackSize>& aPlan,
2299 const nsTArray<uint32_t>& aGrowableTracks,
2300 const FitContentClamper& aFitContentClamper) const {
2301 MOZ_ASSERT(aAvailableSpace > 0 && aGrowableTracks.Length() > 0);
2302 nscoord space = aAvailableSpace;
2303 uint32_t numGrowable = aGrowableTracks.Length();
2304 while (true) {
2305 nscoord spacePerTrack = std::max<nscoord>(space / numGrowable, 1);
2306 for (uint32_t track : aGrowableTracks) {
2307 TrackSize& sz = aPlan[track];
2308 if (sz.IsFrozen()) {
2309 continue;
2311 nscoord newBase = sz.mBase + spacePerTrack;
2312 nscoord limit = sz.mLimit;
2313 if (MOZ_UNLIKELY((sz.mState & TrackSize::eFitContent) &&
2314 aFitContentClamper)) {
2315 // Clamp the limit to the fit-content() size, for §12.5.2 step 5/6.
2316 aFitContentClamper(track, sz.mBase, &limit);
2318 if (newBase > limit) {
2319 nscoord consumed = limit - sz.mBase;
2320 if (consumed > 0) {
2321 space -= consumed;
2322 sz.mBase = limit;
2324 sz.mState |= TrackSize::eFrozen;
2325 if (--numGrowable == 0) {
2326 return space;
2328 } else {
2329 sz.mBase = newBase;
2330 space -= spacePerTrack;
2332 MOZ_ASSERT(space >= 0);
2333 if (space == 0) {
2334 return 0;
2338 MOZ_ASSERT_UNREACHABLE("we don't exit the loop above except by return");
2339 return 0;
2343 * Helper for GrowSelectedTracksUnlimited. For the set of tracks (S) that
2344 * match aMinSizingSelector: if a track in S doesn't match aMaxSizingSelector
2345 * then mark it with aSkipFlag. If all tracks in S were marked then unmark
2346 * them. Return aNumGrowable minus the number of tracks marked. It is
2347 * assumed that aPlan have no aSkipFlag set for tracks in aGrowableTracks
2348 * on entry to this method.
2350 static uint32_t MarkExcludedTracks(nsTArray<TrackSize>& aPlan,
2351 uint32_t aNumGrowable,
2352 const nsTArray<uint32_t>& aGrowableTracks,
2353 TrackSize::StateBits aMinSizingSelector,
2354 TrackSize::StateBits aMaxSizingSelector,
2355 TrackSize::StateBits aSkipFlag) {
2356 bool foundOneSelected = false;
2357 bool foundOneGrowable = false;
2358 uint32_t numGrowable = aNumGrowable;
2359 for (uint32_t track : aGrowableTracks) {
2360 TrackSize& sz = aPlan[track];
2361 const auto state = sz.mState;
2362 if (state & aMinSizingSelector) {
2363 foundOneSelected = true;
2364 if (state & aMaxSizingSelector) {
2365 foundOneGrowable = true;
2366 continue;
2368 sz.mState |= aSkipFlag;
2369 MOZ_ASSERT(numGrowable != 0);
2370 --numGrowable;
2373 // 12.5 "if there are no such tracks, then all affected tracks"
2374 if (foundOneSelected && !foundOneGrowable) {
2375 for (uint32_t track : aGrowableTracks) {
2376 aPlan[track].mState &= ~aSkipFlag;
2378 numGrowable = aNumGrowable;
2380 return numGrowable;
2384 * Mark all tracks in aGrowableTracks with an eSkipGrowUnlimited bit if
2385 * they *shouldn't* grow unlimited in §11.5.1.2.3 "Distribute space beyond
2386 * growth limits" https://drafts.csswg.org/css-grid/#extra-space
2387 * Return the number of tracks that are still growable.
2389 template <TrackSizingPhase phase>
2390 static uint32_t MarkExcludedTracks(nsTArray<TrackSize>& aPlan,
2391 const nsTArray<uint32_t>& aGrowableTracks,
2392 TrackSize::StateBits aSelector) {
2393 uint32_t numGrowable = aGrowableTracks.Length();
2394 if (phase == TrackSizingPhase::IntrinsicMaximums ||
2395 phase == TrackSizingPhase::MaxContentMaximums) {
2396 // "when handling any intrinsic growth limit: all affected tracks"
2397 return numGrowable;
2399 MOZ_ASSERT(aSelector == (aSelector & TrackSize::eIntrinsicMinSizing) &&
2400 (aSelector & TrackSize::eMaxContentMinSizing),
2401 "Should only get here for track sizing steps 2.1 to 2.3");
2402 // Note that eMaxContentMinSizing is always included. We do those first:
2403 numGrowable = MarkExcludedTracks(
2404 aPlan, numGrowable, aGrowableTracks, TrackSize::eMaxContentMinSizing,
2405 TrackSize::eMaxContentMaxSizing, TrackSize::eSkipGrowUnlimited1);
2406 // Now mark min-content/auto min-sizing tracks if requested.
2407 auto minOrAutoSelector = aSelector & ~TrackSize::eMaxContentMinSizing;
2408 if (minOrAutoSelector) {
2409 numGrowable = MarkExcludedTracks(
2410 aPlan, numGrowable, aGrowableTracks, minOrAutoSelector,
2411 TrackSize::eIntrinsicMaxSizing, TrackSize::eSkipGrowUnlimited2);
2413 return numGrowable;
2417 * Increase the planned size for tracks in aGrowableTracks that aren't
2418 * marked with a eSkipGrowUnlimited flag beyond their limit.
2419 * This implements the "Distribute space beyond growth limits" step in
2420 * https://drafts.csswg.org/css-grid/#distribute-extra-space
2422 void GrowSelectedTracksUnlimited(
2423 nscoord aAvailableSpace, nsTArray<TrackSize>& aPlan,
2424 const nsTArray<uint32_t>& aGrowableTracks, uint32_t aNumGrowable,
2425 const FitContentClamper& aFitContentClamper) const {
2426 MOZ_ASSERT(aAvailableSpace > 0 && aGrowableTracks.Length() > 0 &&
2427 aNumGrowable <= aGrowableTracks.Length());
2428 nscoord space = aAvailableSpace;
2429 DebugOnly<bool> didClamp = false;
2430 while (aNumGrowable) {
2431 nscoord spacePerTrack = std::max<nscoord>(space / aNumGrowable, 1);
2432 for (uint32_t track : aGrowableTracks) {
2433 TrackSize& sz = aPlan[track];
2434 if (sz.mState & TrackSize::eSkipGrowUnlimited) {
2435 continue; // an excluded track
2437 nscoord delta = spacePerTrack;
2438 nscoord newBase = sz.mBase + delta;
2439 if (MOZ_UNLIKELY((sz.mState & TrackSize::eFitContent) &&
2440 aFitContentClamper)) {
2441 // Clamp newBase to the fit-content() size, for §12.5.2 step 5/6.
2442 if (aFitContentClamper(track, sz.mBase, &newBase)) {
2443 didClamp = true;
2444 delta = newBase - sz.mBase;
2445 MOZ_ASSERT(delta >= 0, "track size shouldn't shrink");
2446 sz.mState |= TrackSize::eSkipGrowUnlimited1;
2447 --aNumGrowable;
2450 sz.mBase = newBase;
2451 space -= delta;
2452 MOZ_ASSERT(space >= 0);
2453 if (space == 0) {
2454 return;
2458 MOZ_ASSERT(didClamp,
2459 "we don't exit the loop above except by return, "
2460 "unless we clamped some track's size");
2464 * Distribute aAvailableSpace to the planned base size for aGrowableTracks
2465 * up to their limits, then distribute the remaining space beyond the limits.
2467 template <TrackSizingPhase phase>
2468 void DistributeToTrackSizes(nscoord aAvailableSpace,
2469 nsTArray<TrackSize>& aPlan,
2470 nsTArray<TrackSize>& aItemPlan,
2471 nsTArray<uint32_t>& aGrowableTracks,
2472 TrackSize::StateBits aSelector,
2473 const FitContentClamper& aFitContentClamper) {
2474 InitializeItemPlan<phase>(aItemPlan, aGrowableTracks);
2475 nscoord space = GrowTracksToLimit(aAvailableSpace, aItemPlan,
2476 aGrowableTracks, aFitContentClamper);
2477 if (space > 0) {
2478 uint32_t numGrowable =
2479 MarkExcludedTracks<phase>(aItemPlan, aGrowableTracks, aSelector);
2480 GrowSelectedTracksUnlimited(space, aItemPlan, aGrowableTracks,
2481 numGrowable, aFitContentClamper);
2483 for (uint32_t track : aGrowableTracks) {
2484 nscoord& plannedSize = aPlan[track].mBase;
2485 nscoord itemIncurredSize = aItemPlan[track].mBase;
2486 if (plannedSize < itemIncurredSize) {
2487 plannedSize = itemIncurredSize;
2493 * Distribute aAvailableSize to the tracks. This implements 12.6 at:
2494 * http://dev.w3.org/csswg/css-grid/#algo-grow-tracks
2496 void DistributeFreeSpace(nscoord aAvailableSize) {
2497 const uint32_t numTracks = mSizes.Length();
2498 if (MOZ_UNLIKELY(numTracks == 0 || aAvailableSize <= 0)) {
2499 return;
2501 if (aAvailableSize == NS_UNCONSTRAINEDSIZE) {
2502 for (TrackSize& sz : mSizes) {
2503 sz.mBase = sz.mLimit;
2505 } else {
2506 // Compute free space and count growable tracks.
2507 nscoord space = aAvailableSize;
2508 uint32_t numGrowable = numTracks;
2509 for (const TrackSize& sz : mSizes) {
2510 space -= sz.mBase;
2511 MOZ_ASSERT(sz.mBase <= sz.mLimit);
2512 if (sz.mBase == sz.mLimit) {
2513 --numGrowable;
2516 // Distribute the free space evenly to the growable tracks. If not exactly
2517 // divisable the remainder is added to the leading tracks.
2518 while (space > 0 && numGrowable) {
2519 nscoord spacePerTrack = std::max<nscoord>(space / numGrowable, 1);
2520 for (uint32_t i = 0; i < numTracks && space > 0; ++i) {
2521 TrackSize& sz = mSizes[i];
2522 if (sz.mBase == sz.mLimit) {
2523 continue;
2525 nscoord newBase = sz.mBase + spacePerTrack;
2526 if (newBase >= sz.mLimit) {
2527 space -= sz.mLimit - sz.mBase;
2528 sz.mBase = sz.mLimit;
2529 --numGrowable;
2530 } else {
2531 space -= spacePerTrack;
2532 sz.mBase = newBase;
2540 * Implements "12.7.1. Find the Size of an 'fr'".
2541 * http://dev.w3.org/csswg/css-grid/#algo-find-fr-size
2542 * (The returned value is a 'nscoord' divided by a factor - a floating type
2543 * is used to avoid intermediary rounding errors.)
2545 float FindFrUnitSize(const LineRange& aRange,
2546 const nsTArray<uint32_t>& aFlexTracks,
2547 const TrackSizingFunctions& aFunctions,
2548 nscoord aSpaceToFill) const;
2551 * Implements the "find the used flex fraction" part of StretchFlexibleTracks.
2552 * (The returned value is a 'nscoord' divided by a factor - a floating type
2553 * is used to avoid intermediary rounding errors.)
2555 float FindUsedFlexFraction(GridReflowInput& aState,
2556 nsTArray<GridItemInfo>& aGridItems,
2557 const nsTArray<uint32_t>& aFlexTracks,
2558 const TrackSizingFunctions& aFunctions,
2559 nscoord aAvailableSize) const;
2562 * Implements "12.7. Stretch Flexible Tracks"
2563 * http://dev.w3.org/csswg/css-grid/#algo-flex-tracks
2565 void StretchFlexibleTracks(GridReflowInput& aState,
2566 nsTArray<GridItemInfo>& aGridItems,
2567 const TrackSizingFunctions& aFunctions,
2568 nscoord aAvailableSize);
2571 * Implements "12.3. Track Sizing Algorithm"
2572 * http://dev.w3.org/csswg/css-grid/#algo-track-sizing
2574 void CalculateSizes(GridReflowInput& aState,
2575 nsTArray<GridItemInfo>& aGridItems,
2576 const TrackSizingFunctions& aFunctions,
2577 nscoord aContentBoxSize, LineRange GridArea::*aRange,
2578 SizingConstraint aConstraint);
2581 * Apply 'align/justify-content', whichever is relevant for this axis.
2582 * https://drafts.csswg.org/css-align-3/#propdef-align-content
2584 void AlignJustifyContent(const nsStylePosition* aStyle,
2585 StyleContentDistribution aAligmentStyleValue,
2586 WritingMode aWM, nscoord aContentBoxSize,
2587 bool aIsSubgridded);
2589 nscoord GridLineEdge(uint32_t aLine, GridLineSide aSide) const {
2590 if (MOZ_UNLIKELY(mSizes.IsEmpty())) {
2591 // https://drafts.csswg.org/css-grid/#grid-definition
2592 // "... the explicit grid still contains one grid line in each axis."
2593 MOZ_ASSERT(aLine == 0, "We should only resolve line 1 in an empty grid");
2594 return nscoord(0);
2596 MOZ_ASSERT(aLine <= mSizes.Length(), "mSizes is too small");
2597 if (aSide == GridLineSide::BeforeGridGap) {
2598 if (aLine == 0) {
2599 return nscoord(0);
2601 const TrackSize& sz = mSizes[aLine - 1];
2602 return sz.mPosition + sz.mBase;
2604 if (aLine == mSizes.Length()) {
2605 return mContentBoxSize;
2607 return mSizes[aLine].mPosition;
2610 nscoord SumOfGridTracksAndGaps() {
2611 return SumOfGridTracks() + SumOfGridGaps();
2614 nscoord SumOfGridTracks() const {
2615 nscoord result = 0;
2616 for (const TrackSize& size : mSizes) {
2617 result += size.mBase;
2619 return result;
2622 nscoord SumOfGridGaps() const {
2623 auto len = mSizes.Length();
2624 return MOZ_LIKELY(len > 1) ? (len - 1) * mGridGap : 0;
2628 * Break before aRow, i.e. set the eBreakBefore flag on aRow and set the grid
2629 * gap before aRow to zero (and shift all rows after it by the removed gap).
2631 void BreakBeforeRow(uint32_t aRow) {
2632 MOZ_ASSERT(mAxis == eLogicalAxisBlock,
2633 "Should only be fragmenting in the block axis (between rows)");
2634 nscoord prevRowEndPos = 0;
2635 if (aRow != 0) {
2636 auto& prevSz = mSizes[aRow - 1];
2637 prevRowEndPos = prevSz.mPosition + prevSz.mBase;
2639 auto& sz = mSizes[aRow];
2640 const nscoord gap = sz.mPosition - prevRowEndPos;
2641 sz.mState |= TrackSize::eBreakBefore;
2642 if (gap != 0) {
2643 for (uint32_t i = aRow, len = mSizes.Length(); i < len; ++i) {
2644 mSizes[i].mPosition -= gap;
2650 * Set the size of aRow to aSize and adjust the position of all rows after it.
2652 void ResizeRow(uint32_t aRow, nscoord aNewSize) {
2653 MOZ_ASSERT(mAxis == eLogicalAxisBlock,
2654 "Should only be fragmenting in the block axis (between rows)");
2655 MOZ_ASSERT(aNewSize >= 0);
2656 auto& sz = mSizes[aRow];
2657 nscoord delta = aNewSize - sz.mBase;
2658 NS_WARNING_ASSERTION(delta != nscoord(0), "Useless call to ResizeRow");
2659 sz.mBase = aNewSize;
2660 const uint32_t numRows = mSizes.Length();
2661 for (uint32_t r = aRow + 1; r < numRows; ++r) {
2662 mSizes[r].mPosition += delta;
2666 nscoord ResolveSize(const LineRange& aRange) const {
2667 MOZ_ASSERT(mCanResolveLineRangeSize);
2668 MOZ_ASSERT(aRange.Extent() > 0, "grid items cover at least one track");
2669 nscoord pos, size;
2670 aRange.ToPositionAndLength(mSizes, &pos, &size);
2671 return size;
2674 #ifdef DEBUG
2675 void Dump() const;
2676 #endif
2678 CopyableAutoTArray<TrackSize, 32> mSizes;
2679 nscoord mContentBoxSize;
2680 nscoord mGridGap;
2681 // The first(last)-baseline for the first(last) track in this axis.
2682 PerBaseline<nscoord> mBaseline;
2683 // The union of the track min/max-sizing state bits in this axis.
2684 TrackSize::StateBits mStateUnion;
2685 LogicalAxis mAxis;
2686 // Used for aligning a baseline-aligned subtree of items. The only possible
2687 // values are StyleAlignFlags::{START,END,CENTER,AUTO}. AUTO means there are
2688 // no baseline-aligned items in any track in that axis.
2689 // There is one alignment value for each BaselineSharingGroup.
2690 PerBaseline<StyleAlignFlags> mBaselineSubtreeAlign;
2691 // True if track positions and sizes are final in this axis.
2692 bool mCanResolveLineRangeSize;
2693 // True if this axis has masonry layout.
2694 bool mIsMasonry;
2697 #ifdef DEBUG
2698 void nsGridContainerFrame::Tracks::Dump() const {
2699 printf("%zu %s %s ", mSizes.Length(), mIsMasonry ? "masonry" : "grid",
2700 mAxis == eLogicalAxisBlock ? "rows" : "columns");
2701 TrackSize::DumpStateBits(mStateUnion);
2702 printf("\n");
2703 for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) {
2704 printf(" %d: ", i);
2705 mSizes[i].Dump();
2706 printf("\n");
2708 double px = AppUnitsPerCSSPixel();
2709 printf("Baselines: %.2fpx %2fpx\n",
2710 mBaseline[BaselineSharingGroup::First] / px,
2711 mBaseline[BaselineSharingGroup::Last] / px);
2712 printf("Gap: %.2fpx\n", mGridGap / px);
2713 printf("ContentBoxSize: %.2fpx\n", mContentBoxSize / px);
2715 #endif
2718 * Grid data shared by all continuations, owned by the first-in-flow.
2719 * The data is initialized from the first-in-flow's GridReflowInput at
2720 * the end of its reflow. Fragmentation will modify mRows.mSizes -
2721 * the mPosition to remove the row gap at the break boundary, the mState
2722 * by setting the eBreakBefore flag, and mBase is modified when we decide
2723 * to grow a row. mOriginalRowData is setup by the first-in-flow and
2724 * not modified after that. It's used for undoing the changes to mRows.
2725 * mCols, mGridItems, mAbsPosItems are used for initializing the grid
2726 * reflow input for continuations, see GridReflowInput::Initialize below.
2728 struct nsGridContainerFrame::SharedGridData {
2729 SharedGridData()
2730 : mCols(eLogicalAxisInline),
2731 mRows(eLogicalAxisBlock),
2732 mGenerateComputedGridInfo(false) {}
2733 Tracks mCols;
2734 Tracks mRows;
2735 struct RowData {
2736 nscoord mBase; // the original track size
2737 nscoord mGap; // the original gap before a track
2739 nsTArray<RowData> mOriginalRowData;
2740 nsTArray<GridItemInfo> mGridItems;
2741 nsTArray<GridItemInfo> mAbsPosItems;
2742 bool mGenerateComputedGridInfo;
2745 * Only set on the first-in-flow. Continuations will Initialize() their
2746 * GridReflowInput from it.
2748 NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, SharedGridData)
2751 struct MOZ_STACK_CLASS nsGridContainerFrame::GridReflowInput {
2752 GridReflowInput(nsGridContainerFrame* aFrame, const ReflowInput& aRI)
2753 : GridReflowInput(aFrame, *aRI.mRenderingContext, &aRI,
2754 aRI.mStylePosition, aRI.GetWritingMode()) {}
2755 GridReflowInput(nsGridContainerFrame* aFrame, gfxContext& aRC)
2756 : GridReflowInput(aFrame, aRC, nullptr, aFrame->StylePosition(),
2757 aFrame->GetWritingMode()) {}
2760 * Initialize our track sizes and grid item info using the shared
2761 * state from aGridContainerFrame first-in-flow.
2763 void InitializeForContinuation(nsGridContainerFrame* aGridContainerFrame,
2764 nscoord aConsumedBSize) {
2765 MOZ_ASSERT(aGridContainerFrame->GetPrevInFlow(),
2766 "don't call this on the first-in-flow");
2767 MOZ_ASSERT(mGridItems.IsEmpty() && mAbsPosItems.IsEmpty(),
2768 "shouldn't have any item data yet");
2770 // Get the SharedGridData from the first-in-flow. Also calculate the number
2771 // of fragments before this so that we can figure out our start row below.
2772 uint32_t fragment = 0;
2773 nsIFrame* firstInFlow = aGridContainerFrame;
2774 for (auto pif = aGridContainerFrame->GetPrevInFlow(); pif;
2775 pif = pif->GetPrevInFlow()) {
2776 ++fragment;
2777 firstInFlow = pif;
2779 mSharedGridData = firstInFlow->GetProperty(SharedGridData::Prop());
2780 MOZ_ASSERT(mSharedGridData, "first-in-flow must have SharedGridData");
2782 // Find the start row for this fragment and undo breaks after that row
2783 // since the breaks might be different from the last reflow.
2784 auto& rowSizes = mSharedGridData->mRows.mSizes;
2785 const uint32_t numRows = rowSizes.Length();
2786 mStartRow = numRows;
2787 for (uint32_t row = 0, breakCount = 0; row < numRows; ++row) {
2788 if (rowSizes[row].mState & TrackSize::eBreakBefore) {
2789 if (fragment == ++breakCount) {
2790 mStartRow = row;
2791 mFragBStart = rowSizes[row].mPosition;
2792 // Restore the original size for |row| and grid gaps / state after it.
2793 const auto& origRowData = mSharedGridData->mOriginalRowData;
2794 rowSizes[row].mBase = origRowData[row].mBase;
2795 nscoord prevEndPos = rowSizes[row].mPosition + rowSizes[row].mBase;
2796 while (++row < numRows) {
2797 auto& sz = rowSizes[row];
2798 const auto& orig = origRowData[row];
2799 sz.mPosition = prevEndPos + orig.mGap;
2800 sz.mBase = orig.mBase;
2801 sz.mState &= ~TrackSize::eBreakBefore;
2802 prevEndPos = sz.mPosition + sz.mBase;
2804 break;
2808 if (mStartRow == numRows ||
2809 aGridContainerFrame->IsMasonry(eLogicalAxisBlock)) {
2810 // All of the grid's rows fit inside of previous grid-container fragments,
2811 // or it's a masonry axis.
2812 mFragBStart = aConsumedBSize;
2815 // Copy the shared track state.
2816 // XXX consider temporarily swapping the array elements instead and swapping
2817 // XXX them back after we're done reflowing, for better performance.
2818 // XXX (bug 1252002)
2819 mCols = mSharedGridData->mCols;
2820 mRows = mSharedGridData->mRows;
2822 if (firstInFlow->GetProperty(UsedTrackSizes::Prop())) {
2823 auto* prop = aGridContainerFrame->GetProperty(UsedTrackSizes::Prop());
2824 if (!prop) {
2825 prop = new UsedTrackSizes();
2826 aGridContainerFrame->SetProperty(UsedTrackSizes::Prop(), prop);
2828 prop->mCanResolveLineRangeSize = {true, true};
2829 prop->mSizes[eLogicalAxisInline].Assign(mCols.mSizes);
2830 prop->mSizes[eLogicalAxisBlock].Assign(mRows.mSizes);
2833 // Copy item data from each child's first-in-flow data in mSharedGridData.
2834 // XXX NOTE: This is O(n^2) in the number of items. (bug 1252186)
2835 mIter.Reset();
2836 for (; !mIter.AtEnd(); mIter.Next()) {
2837 nsIFrame* child = *mIter;
2838 nsIFrame* childFirstInFlow = child->FirstInFlow();
2839 DebugOnly<size_t> len = mGridItems.Length();
2840 for (auto& itemInfo : mSharedGridData->mGridItems) {
2841 if (itemInfo.mFrame == childFirstInFlow) {
2842 auto item =
2843 mGridItems.AppendElement(GridItemInfo(child, itemInfo.mArea));
2844 // Copy the item's baseline data so that the item's last fragment can
2845 // do 'last baseline' alignment if necessary.
2846 item->mState[0] |= itemInfo.mState[0] & ItemState::eAllBaselineBits;
2847 item->mState[1] |= itemInfo.mState[1] & ItemState::eAllBaselineBits;
2848 item->mBaselineOffset[0] = itemInfo.mBaselineOffset[0];
2849 item->mBaselineOffset[1] = itemInfo.mBaselineOffset[1];
2850 item->mState[0] |= itemInfo.mState[0] & ItemState::eAutoPlacement;
2851 item->mState[1] |= itemInfo.mState[1] & ItemState::eAutoPlacement;
2852 break;
2855 MOZ_ASSERT(mGridItems.Length() == len + 1, "can't find GridItemInfo");
2858 // XXX NOTE: This is O(n^2) in the number of abs.pos. items. (bug 1252186)
2859 const nsFrameList& absPosChildren = aGridContainerFrame->GetChildList(
2860 aGridContainerFrame->GetAbsoluteListID());
2861 for (auto f : absPosChildren) {
2862 nsIFrame* childFirstInFlow = f->FirstInFlow();
2863 DebugOnly<size_t> len = mAbsPosItems.Length();
2864 for (auto& itemInfo : mSharedGridData->mAbsPosItems) {
2865 if (itemInfo.mFrame == childFirstInFlow) {
2866 mAbsPosItems.AppendElement(GridItemInfo(f, itemInfo.mArea));
2867 break;
2870 MOZ_ASSERT(mAbsPosItems.Length() == len + 1, "can't find GridItemInfo");
2873 // Copy in the computed grid info state bit
2874 if (mSharedGridData->mGenerateComputedGridInfo) {
2875 aGridContainerFrame->SetShouldGenerateComputedInfo(true);
2880 * Calculate our track sizes in the given axis.
2882 void CalculateTrackSizesForAxis(LogicalAxis aAxis, const Grid& aGrid,
2883 nscoord aCBSize,
2884 SizingConstraint aConstraint);
2887 * Calculate our track sizes.
2889 void CalculateTrackSizes(const Grid& aGrid, const LogicalSize& aContentBox,
2890 SizingConstraint aConstraint);
2893 * Return the percentage basis for a grid item in its writing-mode.
2894 * If aAxis is eLogicalAxisInline then we return NS_UNCONSTRAINEDSIZE in
2895 * both axes since we know all track sizes are indefinite at this point
2896 * (we calculate column sizes before row sizes). Otherwise, assert that
2897 * column sizes are known and calculate the size for aGridItem.mArea.mCols
2898 * and use NS_UNCONSTRAINEDSIZE in the other axis.
2899 * @param aAxis the axis we're currently calculating track sizes for
2901 LogicalSize PercentageBasisFor(LogicalAxis aAxis,
2902 const GridItemInfo& aGridItem) const;
2905 * Return the containing block for a grid item occupying aArea.
2907 LogicalRect ContainingBlockFor(const GridArea& aArea) const;
2910 * Return the containing block for an abs.pos. grid item occupying aArea.
2911 * Any 'auto' lines in the grid area will be aligned with grid container
2912 * containing block on that side.
2913 * @param aGridOrigin the origin of the grid
2914 * @param aGridCB the grid container containing block (its padding area)
2916 LogicalRect ContainingBlockForAbsPos(const GridArea& aArea,
2917 const LogicalPoint& aGridOrigin,
2918 const LogicalRect& aGridCB) const;
2921 * Apply `align/justify-content` alignment in our masonry axis.
2922 * This aligns the "masonry box" within our content box size.
2924 void AlignJustifyContentInMasonryAxis(nscoord aMasonryBoxSize,
2925 nscoord aContentBoxSize);
2927 * Apply `align/justify-tracks` alignment in our masonry axis.
2929 void AlignJustifyTracksInMasonryAxis(const LogicalSize& aContentSize,
2930 const nsSize& aContainerSize);
2932 // Helper for CollectSubgridItemsForAxis.
2933 static void CollectSubgridForAxis(LogicalAxis aAxis, WritingMode aContainerWM,
2934 const LineRange& aRangeInAxis,
2935 const LineRange& aRangeInOppositeAxis,
2936 const GridItemInfo& aItem,
2937 const nsTArray<GridItemInfo>& aItems,
2938 nsTArray<GridItemInfo>& aResult) {
2939 const auto oppositeAxis = GetOrthogonalAxis(aAxis);
2940 bool itemIsSubgridInOppositeAxis = aItem.IsSubgrid(oppositeAxis);
2941 auto subgridWM = aItem.mFrame->GetWritingMode();
2942 bool isOrthogonal = subgridWM.IsOrthogonalTo(aContainerWM);
2943 bool isSameDirInAxis =
2944 subgridWM.ParallelAxisStartsOnSameSide(aAxis, aContainerWM);
2945 bool isSameDirInOppositeAxis =
2946 subgridWM.ParallelAxisStartsOnSameSide(oppositeAxis, aContainerWM);
2947 if (isOrthogonal) {
2948 // We'll Transpose the area below so these needs to be transposed as well.
2949 std::swap(isSameDirInAxis, isSameDirInOppositeAxis);
2951 uint32_t offsetInAxis = aRangeInAxis.mStart;
2952 uint32_t gridEndInAxis = aRangeInAxis.Extent();
2953 uint32_t offsetInOppositeAxis = aRangeInOppositeAxis.mStart;
2954 uint32_t gridEndInOppositeAxis = aRangeInOppositeAxis.Extent();
2955 for (const auto& subgridItem : aItems) {
2956 auto newItem = aResult.AppendElement(
2957 isOrthogonal ? subgridItem.Transpose() : subgridItem);
2958 if (MOZ_UNLIKELY(!isSameDirInAxis)) {
2959 newItem->ReverseDirection(aAxis, gridEndInAxis);
2961 newItem->mArea.LineRangeForAxis(aAxis).Translate(offsetInAxis);
2962 if (itemIsSubgridInOppositeAxis) {
2963 if (MOZ_UNLIKELY(!isSameDirInOppositeAxis)) {
2964 newItem->ReverseDirection(oppositeAxis, gridEndInOppositeAxis);
2966 LineRange& range = newItem->mArea.LineRangeForAxis(oppositeAxis);
2967 range.Translate(offsetInOppositeAxis);
2969 if (newItem->IsSubgrid(aAxis)) {
2970 auto* subgrid =
2971 subgridItem.SubgridFrame()->GetProperty(Subgrid::Prop());
2972 CollectSubgridForAxis(aAxis, aContainerWM,
2973 newItem->mArea.LineRangeForAxis(aAxis),
2974 newItem->mArea.LineRangeForAxis(oppositeAxis),
2975 *newItem, subgrid->mGridItems, aResult);
2980 // Copy all descendant items from all our subgrid children that are subgridded
2981 // in aAxis recursively into aResult. All item grid area's and state are
2982 // translated to our coordinates.
2983 void CollectSubgridItemsForAxis(LogicalAxis aAxis,
2984 nsTArray<GridItemInfo>& aResult) const {
2985 for (const auto& item : mGridItems) {
2986 if (item.IsSubgrid(aAxis)) {
2987 const auto oppositeAxis = GetOrthogonalAxis(aAxis);
2988 auto* subgrid = item.SubgridFrame()->GetProperty(Subgrid::Prop());
2989 CollectSubgridForAxis(aAxis, mWM, item.mArea.LineRangeForAxis(aAxis),
2990 item.mArea.LineRangeForAxis(oppositeAxis), item,
2991 subgrid->mGridItems, aResult);
2996 Tracks& TracksFor(LogicalAxis aAxis) {
2997 return aAxis == eLogicalAxisBlock ? mRows : mCols;
2999 const Tracks& TracksFor(LogicalAxis aAxis) const {
3000 return aAxis == eLogicalAxisBlock ? mRows : mCols;
3003 CSSOrderAwareFrameIterator mIter;
3004 const nsStylePosition* const mGridStyle;
3005 Tracks mCols;
3006 Tracks mRows;
3007 TrackSizingFunctions mColFunctions;
3008 TrackSizingFunctions mRowFunctions;
3010 * Info about each (normal flow) grid item.
3012 nsTArray<GridItemInfo> mGridItems;
3014 * Info about each grid-aligned abs.pos. child.
3016 nsTArray<GridItemInfo> mAbsPosItems;
3019 * @note mReflowInput may be null when using the 2nd ctor above. In this case
3020 * we'll construct a dummy parent reflow input if we need it to calculate
3021 * min/max-content contributions when sizing tracks.
3023 const ReflowInput* const mReflowInput;
3024 gfxContext& mRenderingContext;
3025 nsGridContainerFrame* const mFrame;
3026 SharedGridData* mSharedGridData; // [weak] owned by mFrame's first-in-flow.
3027 /** Computed border+padding with mSkipSides applied. */
3028 LogicalMargin mBorderPadding;
3030 * BStart of this fragment in "grid space" (i.e. the concatenation of content
3031 * areas of all fragments). Equal to mRows.mSizes[mStartRow].mPosition,
3032 * or, if this fragment starts after the last row, the ConsumedBSize().
3034 nscoord mFragBStart;
3035 /** The start row for this fragment. */
3036 uint32_t mStartRow;
3038 * The start row for the next fragment, if any. If mNextFragmentStartRow ==
3039 * mStartRow then there are no rows in this fragment.
3041 uint32_t mNextFragmentStartRow;
3042 /** Our tentative ApplySkipSides bits. */
3043 LogicalSides mSkipSides;
3044 const WritingMode mWM;
3045 /** Initialized lazily, when we find the fragmentainer. */
3046 bool mInFragmentainer;
3048 private:
3049 GridReflowInput(nsGridContainerFrame* aFrame, gfxContext& aRenderingContext,
3050 const ReflowInput* aReflowInput,
3051 const nsStylePosition* aGridStyle, const WritingMode& aWM)
3052 : mIter(aFrame, FrameChildListID::Principal),
3053 mGridStyle(aGridStyle),
3054 mCols(eLogicalAxisInline),
3055 mRows(eLogicalAxisBlock),
3056 mColFunctions(mGridStyle->mGridTemplateColumns,
3057 mGridStyle->mGridAutoColumns,
3058 aFrame->IsSubgrid(eLogicalAxisInline)),
3059 mRowFunctions(mGridStyle->mGridTemplateRows, mGridStyle->mGridAutoRows,
3060 aFrame->IsSubgrid(eLogicalAxisBlock)),
3061 mReflowInput(aReflowInput),
3062 mRenderingContext(aRenderingContext),
3063 mFrame(aFrame),
3064 mSharedGridData(nullptr),
3065 mBorderPadding(aWM),
3066 mFragBStart(0),
3067 mStartRow(0),
3068 mNextFragmentStartRow(0),
3069 mSkipSides(aFrame->GetWritingMode()),
3070 mWM(aWM),
3071 mInFragmentainer(false) {
3072 MOZ_ASSERT(!aReflowInput || aReflowInput->mFrame == mFrame);
3073 if (aReflowInput) {
3074 mBorderPadding = aReflowInput->ComputedLogicalBorderPadding(mWM);
3075 mSkipSides = aFrame->PreReflowBlockLevelLogicalSkipSides();
3076 mBorderPadding.ApplySkipSides(mSkipSides);
3078 mCols.mIsMasonry = aFrame->IsMasonry(eLogicalAxisInline);
3079 mRows.mIsMasonry = aFrame->IsMasonry(eLogicalAxisBlock);
3080 MOZ_ASSERT(!(mCols.mIsMasonry && mRows.mIsMasonry),
3081 "can't have masonry layout in both axes");
3085 using GridReflowInput = nsGridContainerFrame::GridReflowInput;
3088 * The Grid implements grid item placement and the state of the grid -
3089 * the size of the explicit/implicit grid, which cells are occupied etc.
3091 struct MOZ_STACK_CLASS nsGridContainerFrame::Grid {
3092 explicit Grid(const Grid* aParentGrid = nullptr) : mParentGrid(aParentGrid) {}
3095 * Place all child frames into the grid and expand the (implicit) grid as
3096 * needed. The allocated GridAreas are stored in the GridAreaProperty
3097 * frame property on the child frame.
3098 * @param aRepeatSizing the container's [min-|max-]*size - used to determine
3099 * the number of repeat(auto-fill/fit) tracks.
3101 void PlaceGridItems(GridReflowInput& aState,
3102 const RepeatTrackSizingInput& aRepeatSizing);
3104 void SubgridPlaceGridItems(GridReflowInput& aParentState, Grid* aParentGrid,
3105 const GridItemInfo& aGridItem);
3108 * As above but for an abs.pos. child. Any 'auto' lines will be represented
3109 * by kAutoLine in the LineRange result.
3110 * @param aGridStart the first line in the final, but untranslated grid
3111 * @param aGridEnd the last line in the final, but untranslated grid
3113 LineRange ResolveAbsPosLineRange(const StyleGridLine& aStart,
3114 const StyleGridLine& aEnd,
3115 const LineNameMap& aNameMap,
3116 LogicalAxis aAxis, uint32_t aExplicitGridEnd,
3117 int32_t aGridStart, int32_t aGridEnd,
3118 const nsStylePosition* aStyle);
3121 * Return a GridArea for abs.pos. item with non-auto lines placed at
3122 * a definite line (1-based) with placement errors resolved. One or both
3123 * positions may still be 'auto'.
3124 * @param aChild the abs.pos. grid item to place
3125 * @param aStyle the StylePosition() for the grid container
3127 GridArea PlaceAbsPos(nsIFrame* aChild, const LineNameMap& aColLineNameMap,
3128 const LineNameMap& aRowLineNameMap,
3129 const nsStylePosition* aStyle);
3132 * Find the first column in row aLockedRow starting at aStartCol where aArea
3133 * could be placed without overlapping other items. The returned column may
3134 * cause aArea to overflow the current implicit grid bounds if placed there.
3136 uint32_t FindAutoCol(uint32_t aStartCol, uint32_t aLockedRow,
3137 const GridArea* aArea) const;
3140 * Place aArea in the first column (in row aArea->mRows.mStart) starting at
3141 * aStartCol without overlapping other items. The resulting aArea may
3142 * overflow the current implicit grid bounds.
3143 * @param aClampMaxColLine the maximum allowed column line number (zero-based)
3144 * Pre-condition: aArea->mRows.IsDefinite() is true.
3145 * Post-condition: aArea->IsDefinite() is true.
3147 void PlaceAutoCol(uint32_t aStartCol, GridArea* aArea,
3148 uint32_t aClampMaxColLine) const;
3151 * Find the first row in column aLockedCol starting at aStartRow where aArea
3152 * could be placed without overlapping other items. The returned row may
3153 * cause aArea to overflow the current implicit grid bounds if placed there.
3155 uint32_t FindAutoRow(uint32_t aLockedCol, uint32_t aStartRow,
3156 const GridArea* aArea) const;
3159 * Place aArea in the first row (in column aArea->mCols.mStart) starting at
3160 * aStartRow without overlapping other items. The resulting aArea may
3161 * overflow the current implicit grid bounds.
3162 * @param aClampMaxRowLine the maximum allowed row line number (zero-based)
3163 * Pre-condition: aArea->mCols.IsDefinite() is true.
3164 * Post-condition: aArea->IsDefinite() is true.
3166 void PlaceAutoRow(uint32_t aStartRow, GridArea* aArea,
3167 uint32_t aClampMaxRowLine) const;
3170 * Place aArea in the first column starting at aStartCol,aStartRow without
3171 * causing it to overlap other items or overflow mGridColEnd.
3172 * If there's no such column in aStartRow, continue in position 1,aStartRow+1.
3173 * @param aClampMaxColLine the maximum allowed column line number (zero-based)
3174 * @param aClampMaxRowLine the maximum allowed row line number (zero-based)
3175 * Pre-condition: aArea->mCols.IsAuto() && aArea->mRows.IsAuto() is true.
3176 * Post-condition: aArea->IsDefinite() is true.
3178 void PlaceAutoAutoInRowOrder(uint32_t aStartCol, uint32_t aStartRow,
3179 GridArea* aArea, uint32_t aClampMaxColLine,
3180 uint32_t aClampMaxRowLine) const;
3183 * Place aArea in the first row starting at aStartCol,aStartRow without
3184 * causing it to overlap other items or overflow mGridRowEnd.
3185 * If there's no such row in aStartCol, continue in position aStartCol+1,1.
3186 * @param aClampMaxColLine the maximum allowed column line number (zero-based)
3187 * @param aClampMaxRowLine the maximum allowed row line number (zero-based)
3188 * Pre-condition: aArea->mCols.IsAuto() && aArea->mRows.IsAuto() is true.
3189 * Post-condition: aArea->IsDefinite() is true.
3191 void PlaceAutoAutoInColOrder(uint32_t aStartCol, uint32_t aStartRow,
3192 GridArea* aArea, uint32_t aClampMaxColLine,
3193 uint32_t aClampMaxRowLine) const;
3196 * Return aLine if it's inside the aMin..aMax range (inclusive),
3197 * otherwise return kAutoLine.
3199 static int32_t AutoIfOutside(int32_t aLine, int32_t aMin, int32_t aMax) {
3200 MOZ_ASSERT(aMin <= aMax);
3201 if (aLine < aMin || aLine > aMax) {
3202 return kAutoLine;
3204 return aLine;
3208 * Inflate the implicit grid to include aArea.
3209 * @param aArea may be definite or auto
3211 void InflateGridFor(const GridArea& aArea) {
3212 mGridColEnd = std::max(mGridColEnd, aArea.mCols.HypotheticalEnd());
3213 mGridRowEnd = std::max(mGridRowEnd, aArea.mRows.HypotheticalEnd());
3214 MOZ_ASSERT(mGridColEnd <= kTranslatedMaxLine &&
3215 mGridRowEnd <= kTranslatedMaxLine);
3219 * Calculates the empty tracks in a repeat(auto-fit).
3220 * @param aOutNumEmptyLines Outputs the number of tracks which are empty.
3221 * @param aSizingFunctions Sizing functions for the relevant axis.
3222 * @param aNumGridLines Number of grid lines for the relevant axis.
3223 * @param aIsEmptyFunc Functor to check if a cell is empty. This should be
3224 * mCellMap.IsColEmpty or mCellMap.IsRowEmpty, depending on the axis.
3226 template <typename IsEmptyFuncT>
3227 static Maybe<nsTArray<uint32_t>> CalculateAdjustForAutoFitElements(
3228 uint32_t* aOutNumEmptyTracks, TrackSizingFunctions& aSizingFunctions,
3229 uint32_t aNumGridLines, IsEmptyFuncT aIsEmptyFunc);
3232 * Return a line number for (non-auto) aLine, per:
3233 * http://dev.w3.org/csswg/css-grid/#line-placement
3234 * @param aLine style data for the line (must be non-auto)
3235 * @param aNth a number of lines to find from aFromIndex, negative if the
3236 * search should be in reverse order. In the case aLine has
3237 * a specified line name, it's permitted to pass in zero which
3238 * will be treated as one.
3239 * @param aFromIndex the zero-based index to start counting from
3240 * @param aLineNameList the explicit named lines
3241 * @param aSide the axis+edge we're resolving names for (e.g. if we're
3242 resolving a grid-row-start line, pass eLogicalSideBStart)
3243 * @param aExplicitGridEnd the last line in the explicit grid
3244 * @param aStyle the StylePosition() for the grid container
3245 * @return a definite line (1-based), clamped to
3246 * the mClampMinLine..mClampMaxLine range
3248 int32_t ResolveLine(const StyleGridLine& aLine, int32_t aNth,
3249 uint32_t aFromIndex, const LineNameMap& aNameMap,
3250 LogicalSide aSide, uint32_t aExplicitGridEnd,
3251 const nsStylePosition* aStyle);
3254 * Helper method for ResolveLineRange.
3255 * @see ResolveLineRange
3256 * @return a pair (start,end) of lines
3258 typedef std::pair<int32_t, int32_t> LinePair;
3259 LinePair ResolveLineRangeHelper(const StyleGridLine& aStart,
3260 const StyleGridLine& aEnd,
3261 const LineNameMap& aNameMap,
3262 LogicalAxis aAxis, uint32_t aExplicitGridEnd,
3263 const nsStylePosition* aStyle);
3266 * Return a LineRange based on the given style data. Non-auto lines
3267 * are resolved to a definite line number (1-based) per:
3268 * http://dev.w3.org/csswg/css-grid/#line-placement
3269 * with placement errors corrected per:
3270 * http://dev.w3.org/csswg/css-grid/#grid-placement-errors
3271 * @param aStyle the StylePosition() for the grid container
3272 * @param aStart style data for the start line
3273 * @param aEnd style data for the end line
3274 * @param aLineNameList the explicit named lines
3275 * @param aAxis the axis we're resolving names in
3276 * @param aExplicitGridEnd the last line in the explicit grid
3277 * @param aStyle the StylePosition() for the grid container
3279 LineRange ResolveLineRange(const StyleGridLine& aStart,
3280 const StyleGridLine& aEnd,
3281 const LineNameMap& aNameMap, LogicalAxis aAxis,
3282 uint32_t aExplicitGridEnd,
3283 const nsStylePosition* aStyle);
3286 * Return a GridArea with non-auto lines placed at a definite line (1-based)
3287 * with placement errors resolved. One or both positions may still
3288 * be 'auto'.
3289 * @param aChild the grid item
3290 * @param aStyle the StylePosition() for the grid container
3292 GridArea PlaceDefinite(nsIFrame* aChild, const LineNameMap& aColLineNameMap,
3293 const LineNameMap& aRowLineNameMap,
3294 const nsStylePosition* aStyle);
3296 bool HasImplicitNamedArea(nsAtom* aName) const {
3297 return mAreas && mAreas->has(aName);
3300 // Return true if aString ends in aSuffix and has at least one character
3301 // before the suffix. Assign aIndex to where the suffix starts.
3302 static bool IsNameWithSuffix(nsAtom* aString, const nsString& aSuffix,
3303 uint32_t* aIndex) {
3304 if (StringEndsWith(nsDependentAtomString(aString), aSuffix)) {
3305 *aIndex = aString->GetLength() - aSuffix.Length();
3306 return *aIndex != 0;
3308 return false;
3311 static bool IsNameWithEndSuffix(nsAtom* aString, uint32_t* aIndex) {
3312 return IsNameWithSuffix(aString, u"-end"_ns, aIndex);
3315 static bool IsNameWithStartSuffix(nsAtom* aString, uint32_t* aIndex) {
3316 return IsNameWithSuffix(aString, u"-start"_ns, aIndex);
3319 // Return the relevant parent LineNameMap for the given subgrid axis aAxis.
3320 const LineNameMap* ParentLineMapForAxis(bool aIsOrthogonal,
3321 LogicalAxis aAxis) const {
3322 if (!mParentGrid) {
3323 return nullptr;
3325 bool isRows = aIsOrthogonal == (aAxis == eLogicalAxisInline);
3326 return isRows ? mParentGrid->mRowNameMap : mParentGrid->mColNameMap;
3329 void SetLineMaps(const LineNameMap* aColNameMap,
3330 const LineNameMap* aRowNameMap) {
3331 mColNameMap = aColNameMap;
3332 mRowNameMap = aRowNameMap;
3336 * A CellMap holds state for each cell in the grid.
3337 * It's row major. It's sparse in the sense that it only has enough rows to
3338 * cover the last row that has a grid item. Each row only has enough entries
3339 * to cover columns that are occupied *on that row*, i.e. it's not a full
3340 * matrix covering the entire implicit grid. An absent Cell means that it's
3341 * unoccupied by any grid item.
3343 struct CellMap {
3344 struct Cell {
3345 constexpr Cell() : mIsOccupied(false) {}
3346 bool mIsOccupied : 1;
3349 void Fill(const GridArea& aGridArea) {
3350 MOZ_ASSERT(aGridArea.IsDefinite());
3351 MOZ_ASSERT(aGridArea.mRows.mStart < aGridArea.mRows.mEnd);
3352 MOZ_ASSERT(aGridArea.mCols.mStart < aGridArea.mCols.mEnd);
3353 const auto numRows = aGridArea.mRows.mEnd;
3354 const auto numCols = aGridArea.mCols.mEnd;
3355 mCells.EnsureLengthAtLeast(numRows);
3356 for (auto i = aGridArea.mRows.mStart; i < numRows; ++i) {
3357 nsTArray<Cell>& cellsInRow = mCells[i];
3358 cellsInRow.EnsureLengthAtLeast(numCols);
3359 for (auto j = aGridArea.mCols.mStart; j < numCols; ++j) {
3360 cellsInRow[j].mIsOccupied = true;
3365 uint32_t IsEmptyCol(uint32_t aCol) const {
3366 for (auto& row : mCells) {
3367 if (aCol < row.Length() && row[aCol].mIsOccupied) {
3368 return false;
3371 return true;
3373 uint32_t IsEmptyRow(uint32_t aRow) const {
3374 if (aRow >= mCells.Length()) {
3375 return true;
3377 for (const Cell& cell : mCells[aRow]) {
3378 if (cell.mIsOccupied) {
3379 return false;
3382 return true;
3384 #ifdef DEBUG
3385 void Dump() const {
3386 const size_t numRows = mCells.Length();
3387 for (size_t i = 0; i < numRows; ++i) {
3388 const nsTArray<Cell>& cellsInRow = mCells[i];
3389 const size_t numCols = cellsInRow.Length();
3390 printf("%lu:\t", (unsigned long)i + 1);
3391 for (size_t j = 0; j < numCols; ++j) {
3392 printf(cellsInRow[j].mIsOccupied ? "X " : ". ");
3394 printf("\n");
3397 #endif
3399 nsTArray<nsTArray<Cell>> mCells;
3403 * State for each cell in the grid.
3405 CellMap mCellMap;
3407 * @see HasImplicitNamedArea.
3409 ImplicitNamedAreas* mAreas;
3411 * The last column grid line (1-based) in the explicit grid.
3412 * (i.e. the number of explicit columns + 1)
3414 uint32_t mExplicitGridColEnd;
3416 * The last row grid line (1-based) in the explicit grid.
3417 * (i.e. the number of explicit rows + 1)
3419 uint32_t mExplicitGridRowEnd;
3420 // Same for the implicit grid, except these become zero-based after
3421 // resolving definite lines.
3422 uint32_t mGridColEnd;
3423 uint32_t mGridRowEnd;
3426 * Offsets from the start of the implicit grid to the start of the translated
3427 * explicit grid. They are zero if there are no implicit lines before 1,1.
3428 * e.g. "grid-column: span 3 / 1" makes mExplicitGridOffsetCol = 3 and the
3429 * corresponding GridArea::mCols will be 0 / 3 in the zero-based translated
3430 * grid.
3432 uint32_t mExplicitGridOffsetCol;
3433 uint32_t mExplicitGridOffsetRow;
3436 * Our parent grid if any.
3438 const Grid* mParentGrid;
3441 * Our LineNameMaps.
3443 const LineNameMap* mColNameMap;
3444 const LineNameMap* mRowNameMap;
3448 * Compute margin+border+padding for aGridItem.mFrame (a subgrid) and store it
3449 * on its Subgrid property (and return that property).
3450 * aPercentageBasis is in the grid item's writing-mode.
3452 static Subgrid* SubgridComputeMarginBorderPadding(
3453 const GridItemInfo& aGridItem, const LogicalSize& aPercentageBasis) {
3454 auto* subgridFrame = aGridItem.SubgridFrame();
3455 auto cbWM = aGridItem.mFrame->GetParent()->GetWritingMode();
3456 auto* subgrid = subgridFrame->GetProperty(Subgrid::Prop());
3457 auto wm = subgridFrame->GetWritingMode();
3458 auto pmPercentageBasis = cbWM.IsOrthogonalTo(wm) ? aPercentageBasis.BSize(wm)
3459 : aPercentageBasis.ISize(wm);
3460 SizeComputationInput sz(subgridFrame, nullptr, cbWM, pmPercentageBasis);
3461 subgrid->mMarginBorderPadding =
3462 sz.ComputedLogicalMargin(cbWM) + sz.ComputedLogicalBorderPadding(cbWM);
3464 if (aGridItem.mFrame != subgridFrame) {
3465 nsIScrollableFrame* scrollFrame = aGridItem.mFrame->GetScrollTargetFrame();
3466 if (scrollFrame) {
3467 MOZ_ASSERT(
3468 sz.ComputedLogicalMargin(cbWM) == LogicalMargin(cbWM) &&
3469 sz.ComputedLogicalBorder(cbWM) == LogicalMargin(cbWM),
3470 "A scrolled inner frame should not have any margin or border!");
3472 // Add the margin and border from the (outer) scroll frame.
3473 SizeComputationInput szScrollFrame(aGridItem.mFrame, nullptr, cbWM,
3474 pmPercentageBasis);
3475 subgrid->mMarginBorderPadding +=
3476 szScrollFrame.ComputedLogicalMargin(cbWM) +
3477 szScrollFrame.ComputedLogicalBorder(cbWM);
3479 nsMargin ssz = scrollFrame->GetActualScrollbarSizes();
3480 subgrid->mMarginBorderPadding += LogicalMargin(cbWM, ssz);
3483 if (aGridItem.mFrame->IsFieldSetFrame()) {
3484 const auto* f = static_cast<nsFieldSetFrame*>(aGridItem.mFrame);
3485 const auto* inner = f->GetInner();
3486 auto wm = inner->GetWritingMode();
3487 LogicalPoint pos = inner->GetLogicalPosition(aGridItem.mFrame->GetSize());
3488 // The legend is always on the BStart side and it inflates the fieldset's
3489 // "border area" size. The inner frame's b-start pos equals that size.
3490 LogicalMargin offsets(wm, pos.B(wm), 0, 0, 0);
3491 subgrid->mMarginBorderPadding += offsets.ConvertTo(cbWM, wm);
3494 return subgrid;
3497 static void CopyUsedTrackSizes(nsTArray<TrackSize>& aResult,
3498 const nsGridContainerFrame* aUsedTrackSizesFrame,
3499 const UsedTrackSizes* aUsedTrackSizes,
3500 const nsGridContainerFrame* aSubgridFrame,
3501 const Subgrid* aSubgrid,
3502 LogicalAxis aSubgridAxis) {
3503 MOZ_ASSERT(aSubgridFrame->ParentGridContainerForSubgrid() ==
3504 aUsedTrackSizesFrame);
3505 aResult.SetLength(aSubgridAxis == eLogicalAxisInline ? aSubgrid->mGridColEnd
3506 : aSubgrid->mGridRowEnd);
3507 auto parentAxis =
3508 aSubgrid->mIsOrthogonal ? GetOrthogonalAxis(aSubgridAxis) : aSubgridAxis;
3509 const auto& parentSizes = aUsedTrackSizes->mSizes[parentAxis];
3510 MOZ_ASSERT(aUsedTrackSizes->mCanResolveLineRangeSize[parentAxis]);
3511 if (parentSizes.IsEmpty()) {
3512 return;
3514 const auto& range = aSubgrid->mArea.LineRangeForAxis(parentAxis);
3515 const auto cbwm = aUsedTrackSizesFrame->GetWritingMode();
3516 const auto wm = aSubgridFrame->GetWritingMode();
3517 // Recompute the MBP to resolve percentages against the resolved track sizes.
3518 if (parentAxis == eLogicalAxisInline) {
3519 // Find the subgrid's grid item frame in its parent grid container. This
3520 // is usually the same as aSubgridFrame but it may also have a ScrollFrame,
3521 // FieldSetFrame etc. We just loop until we see the first ancestor
3522 // GridContainerFrame and pick the last frame we saw before that.
3523 // Note that all subgrids are inside a parent (sub)grid container.
3524 const nsIFrame* outerGridItemFrame = aSubgridFrame;
3525 for (nsIFrame* parent = aSubgridFrame->GetParent();
3526 parent != aUsedTrackSizesFrame; parent = parent->GetParent()) {
3527 MOZ_ASSERT(!parent->IsGridContainerFrame());
3528 outerGridItemFrame = parent;
3530 auto sizeInAxis = range.ToLength(aUsedTrackSizes->mSizes[parentAxis]);
3531 LogicalSize pmPercentageBasis =
3532 aSubgrid->mIsOrthogonal ? LogicalSize(wm, nscoord(0), sizeInAxis)
3533 : LogicalSize(wm, sizeInAxis, nscoord(0));
3534 GridItemInfo info(const_cast<nsIFrame*>(outerGridItemFrame),
3535 aSubgrid->mArea);
3536 SubgridComputeMarginBorderPadding(info, pmPercentageBasis);
3538 const LogicalMargin& mbp = aSubgrid->mMarginBorderPadding;
3539 nscoord startMBP;
3540 nscoord endMBP;
3541 if (MOZ_LIKELY(cbwm.ParallelAxisStartsOnSameSide(parentAxis, wm))) {
3542 startMBP = mbp.Start(parentAxis, cbwm);
3543 endMBP = mbp.End(parentAxis, cbwm);
3544 uint32_t i = range.mStart;
3545 nscoord startPos = parentSizes[i].mPosition + startMBP;
3546 for (auto& sz : aResult) {
3547 sz = parentSizes[i++];
3548 sz.mPosition -= startPos;
3550 } else {
3551 startMBP = mbp.End(parentAxis, cbwm);
3552 endMBP = mbp.Start(parentAxis, cbwm);
3553 uint32_t i = range.mEnd - 1;
3554 const auto& parentEnd = parentSizes[i];
3555 nscoord parentEndPos = parentEnd.mPosition + parentEnd.mBase - startMBP;
3556 for (auto& sz : aResult) {
3557 sz = parentSizes[i--];
3558 sz.mPosition = parentEndPos - (sz.mPosition + sz.mBase);
3561 auto& startTrack = aResult[0];
3562 startTrack.mPosition = 0;
3563 startTrack.mBase -= startMBP;
3564 if (MOZ_UNLIKELY(startTrack.mBase < nscoord(0))) {
3565 // Our MBP doesn't fit in the start track. Adjust the track position
3566 // to maintain track alignment with our parent.
3567 startTrack.mPosition = startTrack.mBase;
3568 startTrack.mBase = nscoord(0);
3570 auto& endTrack = aResult.LastElement();
3571 endTrack.mBase -= endMBP;
3572 if (MOZ_UNLIKELY(endTrack.mBase < nscoord(0))) {
3573 endTrack.mBase = nscoord(0);
3577 void nsGridContainerFrame::UsedTrackSizes::ResolveTrackSizesForAxis(
3578 nsGridContainerFrame* aFrame, LogicalAxis aAxis, gfxContext& aRC) {
3579 if (mCanResolveLineRangeSize[aAxis]) {
3580 return;
3582 if (!aFrame->IsSubgrid()) {
3583 // We can't resolve sizes in this axis at this point. aFrame is the top grid
3584 // container, which will store its final track sizes later once they're
3585 // resolved in this axis (in GridReflowInput::CalculateTrackSizesForAxis).
3586 // The single caller of this method only needs track sizes for
3587 // calculating a CB size and it will treat it as indefinite when
3588 // this happens.
3589 return;
3591 auto* parent = aFrame->ParentGridContainerForSubgrid();
3592 auto* parentSizes = parent->GetUsedTrackSizes();
3593 if (!parentSizes) {
3594 parentSizes = new UsedTrackSizes();
3595 parent->SetProperty(UsedTrackSizes::Prop(), parentSizes);
3597 auto* subgrid = aFrame->GetProperty(Subgrid::Prop());
3598 const auto parentAxis =
3599 subgrid->mIsOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis;
3600 parentSizes->ResolveTrackSizesForAxis(parent, parentAxis, aRC);
3601 if (!parentSizes->mCanResolveLineRangeSize[parentAxis]) {
3602 if (aFrame->IsSubgrid(aAxis)) {
3603 ResolveSubgridTrackSizesForAxis(aFrame, aAxis, subgrid, aRC,
3604 NS_UNCONSTRAINEDSIZE);
3606 return;
3608 if (aFrame->IsSubgrid(aAxis)) {
3609 CopyUsedTrackSizes(mSizes[aAxis], parent, parentSizes, aFrame, subgrid,
3610 aAxis);
3611 mCanResolveLineRangeSize[aAxis] = true;
3612 } else {
3613 const auto& range = subgrid->mArea.LineRangeForAxis(parentAxis);
3614 nscoord contentBoxSize = range.ToLength(parentSizes->mSizes[parentAxis]);
3615 auto parentWM = aFrame->GetParent()->GetWritingMode();
3616 contentBoxSize -=
3617 subgrid->mMarginBorderPadding.StartEnd(parentAxis, parentWM);
3618 contentBoxSize = std::max(nscoord(0), contentBoxSize);
3619 ResolveSubgridTrackSizesForAxis(aFrame, aAxis, subgrid, aRC,
3620 contentBoxSize);
3624 void nsGridContainerFrame::UsedTrackSizes::ResolveSubgridTrackSizesForAxis(
3625 nsGridContainerFrame* aFrame, LogicalAxis aAxis, Subgrid* aSubgrid,
3626 gfxContext& aRC, nscoord aContentBoxSize) {
3627 GridReflowInput state(aFrame, aRC);
3628 state.mGridItems = aSubgrid->mGridItems.Clone();
3629 Grid grid;
3630 grid.mGridColEnd = aSubgrid->mGridColEnd;
3631 grid.mGridRowEnd = aSubgrid->mGridRowEnd;
3632 state.CalculateTrackSizesForAxis(aAxis, grid, aContentBoxSize,
3633 SizingConstraint::NoConstraint);
3634 const auto& tracks = aAxis == eLogicalAxisInline ? state.mCols : state.mRows;
3635 mSizes[aAxis].Assign(tracks.mSizes);
3636 mCanResolveLineRangeSize[aAxis] = tracks.mCanResolveLineRangeSize;
3637 MOZ_ASSERT(mCanResolveLineRangeSize[aAxis]);
3640 void nsGridContainerFrame::GridReflowInput::CalculateTrackSizesForAxis(
3641 LogicalAxis aAxis, const Grid& aGrid, nscoord aContentBoxSize,
3642 SizingConstraint aConstraint) {
3643 auto& tracks = aAxis == eLogicalAxisInline ? mCols : mRows;
3644 const auto& sizingFunctions =
3645 aAxis == eLogicalAxisInline ? mColFunctions : mRowFunctions;
3646 const auto& gapStyle = aAxis == eLogicalAxisInline ? mGridStyle->mColumnGap
3647 : mGridStyle->mRowGap;
3648 if (tracks.mIsMasonry) {
3649 // See comment on nsGridContainerFrame::MasonryLayout().
3650 tracks.Initialize(sizingFunctions, gapStyle, 2, aContentBoxSize);
3651 tracks.mCanResolveLineRangeSize = true;
3652 return;
3654 uint32_t gridEnd =
3655 aAxis == eLogicalAxisInline ? aGrid.mGridColEnd : aGrid.mGridRowEnd;
3656 Maybe<TrackSizingFunctions> fallbackTrackSizing;
3658 bool useParentGaps = false;
3659 const bool isSubgriddedAxis = mFrame->IsSubgrid(aAxis);
3660 if (MOZ_LIKELY(!isSubgriddedAxis)) {
3661 tracks.Initialize(sizingFunctions, gapStyle, gridEnd, aContentBoxSize);
3662 } else {
3663 tracks.mGridGap =
3664 nsLayoutUtils::ResolveGapToLength(gapStyle, aContentBoxSize);
3665 tracks.mContentBoxSize = aContentBoxSize;
3666 const auto* subgrid = mFrame->GetProperty(Subgrid::Prop());
3667 tracks.mSizes.SetLength(gridEnd);
3668 auto* parent = mFrame->ParentGridContainerForSubgrid();
3669 auto parentAxis = subgrid->mIsOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis;
3670 const auto* parentSizes = parent->GetUsedTrackSizes();
3671 if (parentSizes && parentSizes->mCanResolveLineRangeSize[parentAxis]) {
3672 CopyUsedTrackSizes(tracks.mSizes, parent, parentSizes, mFrame, subgrid,
3673 aAxis);
3674 useParentGaps = gapStyle.IsNormal();
3675 } else {
3676 fallbackTrackSizing.emplace(TrackSizingFunctions::ForSubgridFallback(
3677 mFrame, subgrid, parent, parentAxis));
3678 tracks.Initialize(*fallbackTrackSizing, gapStyle, gridEnd,
3679 aContentBoxSize);
3683 // We run the Track Sizing Algorithm in non-subgridded axes, and in some
3684 // cases in a subgridded axis when our parent track sizes aren't resolved yet.
3685 if (MOZ_LIKELY(!isSubgriddedAxis) || fallbackTrackSizing.isSome()) {
3686 const size_t origGridItemCount = mGridItems.Length();
3687 if (mFrame->HasSubgridItems(aAxis)) {
3688 CollectSubgridItemsForAxis(aAxis, mGridItems);
3690 tracks.CalculateSizes(
3691 *this, mGridItems,
3692 fallbackTrackSizing ? *fallbackTrackSizing : sizingFunctions,
3693 aContentBoxSize,
3694 aAxis == eLogicalAxisInline ? &GridArea::mCols : &GridArea::mRows,
3695 aConstraint);
3696 // XXXmats we're losing the baseline state of subgrid descendants that
3697 // CollectSubgridItemsForAxis added here. We need to propagate that
3698 // state into the subgrid's Reflow somehow...
3699 mGridItems.TruncateLength(origGridItemCount);
3702 if (aContentBoxSize != NS_UNCONSTRAINEDSIZE) {
3703 auto alignment = mGridStyle->UsedContentAlignment(tracks.mAxis);
3704 tracks.AlignJustifyContent(mGridStyle, alignment, mWM, aContentBoxSize,
3705 isSubgriddedAxis);
3706 } else if (!useParentGaps) {
3707 const nscoord gridGap = tracks.mGridGap;
3708 nscoord pos = 0;
3709 for (TrackSize& sz : tracks.mSizes) {
3710 sz.mPosition = pos;
3711 pos += sz.mBase + gridGap;
3715 if (aConstraint == SizingConstraint::NoConstraint &&
3716 (mFrame->HasSubgridItems() || mFrame->IsSubgrid())) {
3717 mFrame->StoreUsedTrackSizes(aAxis, tracks.mSizes);
3720 // positions and sizes are now final
3721 tracks.mCanResolveLineRangeSize = true;
3724 void nsGridContainerFrame::GridReflowInput::CalculateTrackSizes(
3725 const Grid& aGrid, const LogicalSize& aContentBox,
3726 SizingConstraint aConstraint) {
3727 CalculateTrackSizesForAxis(eLogicalAxisInline, aGrid, aContentBox.ISize(mWM),
3728 aConstraint);
3729 CalculateTrackSizesForAxis(eLogicalAxisBlock, aGrid, aContentBox.BSize(mWM),
3730 aConstraint);
3733 // Align an item's margin box in its aAxis inside aCBSize.
3734 static void AlignJustifySelf(StyleAlignFlags aAlignment, LogicalAxis aAxis,
3735 AlignJustifyFlags aFlags, nscoord aBaselineAdjust,
3736 nscoord aCBSize, const ReflowInput& aRI,
3737 const LogicalSize& aChildSize,
3738 LogicalPoint* aPos) {
3739 MOZ_ASSERT(aAlignment != StyleAlignFlags::AUTO,
3740 "unexpected 'auto' "
3741 "computed value for normal flow grid item");
3743 // NOTE: this is the resulting frame offset (border box).
3744 nscoord offset = CSSAlignUtils::AlignJustifySelf(
3745 aAlignment, aAxis, aFlags, aBaselineAdjust, aCBSize, aRI, aChildSize);
3747 // Set the position (aPos) for the requested alignment.
3748 if (offset != 0) {
3749 WritingMode wm = aRI.GetWritingMode();
3750 nscoord& pos = aAxis == eLogicalAxisBlock ? aPos->B(wm) : aPos->I(wm);
3751 pos += MOZ_LIKELY(aFlags & AlignJustifyFlags::SameSide) ? offset : -offset;
3755 static void AlignSelf(const nsGridContainerFrame::GridItemInfo& aGridItem,
3756 StyleAlignFlags aAlignSelf, nscoord aCBSize,
3757 const WritingMode aCBWM, const ReflowInput& aRI,
3758 const LogicalSize& aSize, AlignJustifyFlags aFlags,
3759 LogicalPoint* aPos) {
3760 AlignJustifyFlags flags = aFlags;
3761 if (aAlignSelf & StyleAlignFlags::SAFE) {
3762 flags |= AlignJustifyFlags::OverflowSafe;
3764 aAlignSelf &= ~StyleAlignFlags::FLAG_BITS;
3766 WritingMode childWM = aRI.GetWritingMode();
3767 if (aCBWM.ParallelAxisStartsOnSameSide(eLogicalAxisBlock, childWM)) {
3768 flags |= AlignJustifyFlags::SameSide;
3771 // Grid's 'align-self' axis is never parallel to the container's inline axis.
3772 if (aAlignSelf == StyleAlignFlags::LEFT ||
3773 aAlignSelf == StyleAlignFlags::RIGHT) {
3774 aAlignSelf = StyleAlignFlags::START;
3776 if (MOZ_LIKELY(aAlignSelf == StyleAlignFlags::NORMAL)) {
3777 aAlignSelf = StyleAlignFlags::STRETCH;
3780 nscoord baselineAdjust = 0;
3781 if (aAlignSelf == StyleAlignFlags::BASELINE ||
3782 aAlignSelf == StyleAlignFlags::LAST_BASELINE) {
3783 aAlignSelf = aGridItem.GetSelfBaseline(aAlignSelf, eLogicalAxisBlock,
3784 &baselineAdjust);
3785 // Adjust the baseline alignment value if the baseline affects the opposite
3786 // side of what AlignJustifySelf expects.
3787 auto state = aGridItem.mState[eLogicalAxisBlock];
3788 if (aAlignSelf == StyleAlignFlags::LAST_BASELINE &&
3789 !GridItemInfo::BaselineAlignmentAffectsEndSide(state)) {
3790 aAlignSelf = StyleAlignFlags::BASELINE;
3791 } else if (aAlignSelf == StyleAlignFlags::BASELINE &&
3792 GridItemInfo::BaselineAlignmentAffectsEndSide(state)) {
3793 aAlignSelf = StyleAlignFlags::LAST_BASELINE;
3797 bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM);
3798 LogicalAxis axis = isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock;
3799 AlignJustifySelf(aAlignSelf, axis, flags, baselineAdjust, aCBSize, aRI, aSize,
3800 aPos);
3803 static void JustifySelf(const nsGridContainerFrame::GridItemInfo& aGridItem,
3804 StyleAlignFlags aJustifySelf, nscoord aCBSize,
3805 const WritingMode aCBWM, const ReflowInput& aRI,
3806 const LogicalSize& aSize, AlignJustifyFlags aFlags,
3807 LogicalPoint* aPos) {
3808 AlignJustifyFlags flags = aFlags;
3809 if (aJustifySelf & StyleAlignFlags::SAFE) {
3810 flags |= AlignJustifyFlags::OverflowSafe;
3812 aJustifySelf &= ~StyleAlignFlags::FLAG_BITS;
3814 WritingMode childWM = aRI.GetWritingMode();
3815 if (aCBWM.ParallelAxisStartsOnSameSide(eLogicalAxisInline, childWM)) {
3816 flags |= AlignJustifyFlags::SameSide;
3819 if (MOZ_LIKELY(aJustifySelf == StyleAlignFlags::NORMAL)) {
3820 aJustifySelf = StyleAlignFlags::STRETCH;
3823 nscoord baselineAdjust = 0;
3824 // Grid's 'justify-self' axis is always parallel to the container's inline
3825 // axis, so justify-self:left|right always applies.
3826 if (aJustifySelf == StyleAlignFlags::LEFT) {
3827 aJustifySelf =
3828 aCBWM.IsBidiLTR() ? StyleAlignFlags::START : StyleAlignFlags::END;
3829 } else if (aJustifySelf == StyleAlignFlags::RIGHT) {
3830 aJustifySelf =
3831 aCBWM.IsBidiLTR() ? StyleAlignFlags::END : StyleAlignFlags::START;
3832 } else if (aJustifySelf == StyleAlignFlags::BASELINE ||
3833 aJustifySelf == StyleAlignFlags::LAST_BASELINE) {
3834 aJustifySelf = aGridItem.GetSelfBaseline(aJustifySelf, eLogicalAxisInline,
3835 &baselineAdjust);
3836 // Adjust the baseline alignment value if the baseline affects the opposite
3837 // side of what AlignJustifySelf expects.
3838 auto state = aGridItem.mState[eLogicalAxisInline];
3839 if (aJustifySelf == StyleAlignFlags::LAST_BASELINE &&
3840 !GridItemInfo::BaselineAlignmentAffectsEndSide(state)) {
3841 aJustifySelf = StyleAlignFlags::BASELINE;
3842 } else if (aJustifySelf == StyleAlignFlags::BASELINE &&
3843 GridItemInfo::BaselineAlignmentAffectsEndSide(state)) {
3844 aJustifySelf = StyleAlignFlags::LAST_BASELINE;
3848 bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM);
3849 LogicalAxis axis = isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline;
3850 AlignJustifySelf(aJustifySelf, axis, flags, baselineAdjust, aCBSize, aRI,
3851 aSize, aPos);
3854 static StyleAlignFlags GetAlignJustifyValue(StyleAlignFlags aAlignment,
3855 const WritingMode aWM,
3856 const bool aIsAlign,
3857 bool* aOverflowSafe) {
3858 *aOverflowSafe = bool(aAlignment & StyleAlignFlags::SAFE);
3859 aAlignment &= ~StyleAlignFlags::FLAG_BITS;
3861 // Map some alignment values to 'start' / 'end'.
3862 if (aAlignment == StyleAlignFlags::LEFT ||
3863 aAlignment == StyleAlignFlags::RIGHT) {
3864 if (aIsAlign) {
3865 // Grid's 'align-content' axis is never parallel to the inline axis.
3866 return StyleAlignFlags::START;
3868 bool isStart = aWM.IsBidiLTR() == (aAlignment == StyleAlignFlags::LEFT);
3869 return isStart ? StyleAlignFlags::START : StyleAlignFlags::END;
3871 if (aAlignment == StyleAlignFlags::FLEX_START) {
3872 return StyleAlignFlags::START; // same as 'start' for Grid
3874 if (aAlignment == StyleAlignFlags::FLEX_END) {
3875 return StyleAlignFlags::END; // same as 'end' for Grid
3877 return aAlignment;
3880 static Maybe<StyleAlignFlags> GetAlignJustifyFallbackIfAny(
3881 const StyleContentDistribution& aDistribution, const WritingMode aWM,
3882 const bool aIsAlign, bool* aOverflowSafe) {
3883 // TODO: Eventually this should look at aDistribution's fallback alignment,
3884 // see https://github.com/w3c/csswg-drafts/issues/1002.
3885 if (aDistribution.primary == StyleAlignFlags::STRETCH ||
3886 aDistribution.primary == StyleAlignFlags::SPACE_BETWEEN) {
3887 return Some(StyleAlignFlags::START);
3889 if (aDistribution.primary == StyleAlignFlags::SPACE_AROUND ||
3890 aDistribution.primary == StyleAlignFlags::SPACE_EVENLY) {
3891 return Some(StyleAlignFlags::CENTER);
3893 return Nothing();
3896 //----------------------------------------------------------------------
3898 // Frame class boilerplate
3899 // =======================
3901 NS_QUERYFRAME_HEAD(nsGridContainerFrame)
3902 NS_QUERYFRAME_ENTRY(nsGridContainerFrame)
3903 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
3905 NS_IMPL_FRAMEARENA_HELPERS(nsGridContainerFrame)
3907 nsContainerFrame* NS_NewGridContainerFrame(PresShell* aPresShell,
3908 ComputedStyle* aStyle) {
3909 return new (aPresShell)
3910 nsGridContainerFrame(aStyle, aPresShell->GetPresContext());
3913 //----------------------------------------------------------------------
3915 // nsGridContainerFrame Method Implementations
3916 // ===========================================
3918 /*static*/ const nsRect& nsGridContainerFrame::GridItemCB(nsIFrame* aChild) {
3919 MOZ_ASSERT(aChild->IsAbsolutelyPositioned());
3920 nsRect* cb = aChild->GetProperty(GridItemContainingBlockRect());
3921 MOZ_ASSERT(cb,
3922 "this method must only be called on grid items, and the grid "
3923 "container should've reflowed this item by now and set up cb");
3924 return *cb;
3927 void nsGridContainerFrame::AddImplicitNamedAreas(
3928 Span<LineNameList> aLineNameLists) {
3929 // http://dev.w3.org/csswg/css-grid/#implicit-named-areas
3930 // Note: recording these names for fast lookup later is just an optimization.
3931 const uint32_t len = std::min(aLineNameLists.Length(), size_t(kMaxLine));
3932 nsTHashSet<nsString> currentStarts;
3933 ImplicitNamedAreas* areas = GetImplicitNamedAreas();
3934 for (uint32_t i = 0; i < len; ++i) {
3935 for (const auto& nameIdent : aLineNameLists[i].AsSpan()) {
3936 nsAtom* name = nameIdent.AsAtom();
3937 uint32_t indexOfSuffix;
3938 if (Grid::IsNameWithStartSuffix(name, &indexOfSuffix) ||
3939 Grid::IsNameWithEndSuffix(name, &indexOfSuffix)) {
3940 // Extract the name that was found earlier.
3941 nsDependentSubstring areaName(nsDependentAtomString(name), 0,
3942 indexOfSuffix);
3944 // Lazily create the ImplicitNamedAreas.
3945 if (!areas) {
3946 areas = new ImplicitNamedAreas;
3947 SetProperty(ImplicitNamedAreasProperty(), areas);
3950 RefPtr<nsAtom> name = NS_Atomize(areaName);
3951 auto addPtr = areas->lookupForAdd(name);
3952 if (!addPtr) {
3953 if (!areas->add(
3954 addPtr, name,
3955 NamedArea{StyleAtom(do_AddRef(name)), {0, 0}, {0, 0}})) {
3956 MOZ_CRASH("OOM while adding grid name lists");
3964 void nsGridContainerFrame::InitImplicitNamedAreas(
3965 const nsStylePosition* aStyle) {
3966 ImplicitNamedAreas* areas = GetImplicitNamedAreas();
3967 if (areas) {
3968 // Clear it, but reuse the hashtable itself for now. We'll remove it
3969 // below if it isn't needed anymore.
3970 areas->clear();
3972 auto Add = [&](const GridTemplate& aTemplate, bool aIsSubgrid) {
3973 AddImplicitNamedAreas(aTemplate.LineNameLists(aIsSubgrid));
3974 for (auto& value : aTemplate.TrackListValues()) {
3975 if (value.IsTrackRepeat()) {
3976 AddImplicitNamedAreas(value.AsTrackRepeat().line_names.AsSpan());
3980 Add(aStyle->mGridTemplateColumns, IsSubgrid(eLogicalAxisInline));
3981 Add(aStyle->mGridTemplateRows, IsSubgrid(eLogicalAxisBlock));
3982 if (areas && areas->count() == 0) {
3983 RemoveProperty(ImplicitNamedAreasProperty());
3987 int32_t nsGridContainerFrame::Grid::ResolveLine(
3988 const StyleGridLine& aLine, int32_t aNth, uint32_t aFromIndex,
3989 const LineNameMap& aNameMap, LogicalSide aSide, uint32_t aExplicitGridEnd,
3990 const nsStylePosition* aStyle) {
3991 MOZ_ASSERT(!aLine.IsAuto());
3992 int32_t line = 0;
3993 if (aLine.LineName()->IsEmpty()) {
3994 MOZ_ASSERT(aNth != 0, "css-grid 9.2: <integer> must not be zero.");
3995 line = int32_t(aFromIndex) + aNth;
3996 } else {
3997 if (aNth == 0) {
3998 // <integer> was omitted; treat it as 1.
3999 aNth = 1;
4001 bool isNameOnly = !aLine.is_span && aLine.line_num == 0;
4002 if (isNameOnly) {
4003 AutoTArray<uint32_t, 16> implicitLines;
4004 aNameMap.FindNamedAreas(aLine.ident.AsAtom(), aSide, implicitLines);
4005 if (!implicitLines.IsEmpty() ||
4006 aNameMap.HasImplicitNamedArea(aLine.LineName())) {
4007 // aName is a named area - look for explicit lines named
4008 // <name>-start/-end depending on which side we're resolving.
4009 // http://dev.w3.org/csswg/css-grid/#grid-placement-slot
4010 nsAutoString lineName(nsDependentAtomString(aLine.LineName()));
4011 if (IsStart(aSide)) {
4012 lineName.AppendLiteral("-start");
4013 } else {
4014 lineName.AppendLiteral("-end");
4016 RefPtr<nsAtom> name = NS_Atomize(lineName);
4017 line = aNameMap.FindNamedLine(name, &aNth, aFromIndex, implicitLines);
4021 if (line == 0) {
4022 // If LineName() ends in -start/-end, try the prefix as a named area.
4023 AutoTArray<uint32_t, 16> implicitLines;
4024 uint32_t index;
4025 bool useStart = IsNameWithStartSuffix(aLine.LineName(), &index);
4026 if (useStart || IsNameWithEndSuffix(aLine.LineName(), &index)) {
4027 auto side = MakeLogicalSide(
4028 GetAxis(aSide), useStart ? eLogicalEdgeStart : eLogicalEdgeEnd);
4029 RefPtr<nsAtom> name = NS_Atomize(nsDependentSubstring(
4030 nsDependentAtomString(aLine.LineName()), 0, index));
4031 aNameMap.FindNamedAreas(name, side, implicitLines);
4033 line = aNameMap.FindNamedLine(aLine.LineName(), &aNth, aFromIndex,
4034 implicitLines);
4037 if (line == 0) {
4038 MOZ_ASSERT(aNth != 0, "we found all N named lines but 'line' is zero!");
4039 int32_t edgeLine;
4040 if (aLine.is_span) {
4041 // http://dev.w3.org/csswg/css-grid/#grid-placement-span-int
4042 // 'span <custom-ident> N'
4043 edgeLine = IsStart(aSide) ? 1 : aExplicitGridEnd;
4044 } else {
4045 // http://dev.w3.org/csswg/css-grid/#grid-placement-int
4046 // '<custom-ident> N'
4047 edgeLine = aNth < 0 ? 1 : aExplicitGridEnd;
4049 // "If not enough lines with that name exist, all lines in the implicit
4050 // grid are assumed to have that name..."
4051 line = edgeLine + aNth;
4054 return clamped(line, aNameMap.mClampMinLine, aNameMap.mClampMaxLine);
4057 nsGridContainerFrame::Grid::LinePair
4058 nsGridContainerFrame::Grid::ResolveLineRangeHelper(
4059 const StyleGridLine& aStart, const StyleGridLine& aEnd,
4060 const LineNameMap& aNameMap, LogicalAxis aAxis, uint32_t aExplicitGridEnd,
4061 const nsStylePosition* aStyle) {
4062 MOZ_ASSERT(int32_t(kAutoLine) > kMaxLine);
4064 if (aStart.is_span) {
4065 if (aEnd.is_span || aEnd.IsAuto()) {
4066 // http://dev.w3.org/csswg/css-grid/#grid-placement-errors
4067 if (aStart.LineName()->IsEmpty()) {
4068 // span <integer> / span *
4069 // span <integer> / auto
4070 return LinePair(kAutoLine, aStart.line_num);
4072 // span <custom-ident> / span *
4073 // span <custom-ident> / auto
4074 return LinePair(kAutoLine, 1); // XXX subgrid explicit size instead of 1?
4077 uint32_t from = aEnd.line_num < 0 ? aExplicitGridEnd + 1 : 0;
4078 auto end = ResolveLine(aEnd, aEnd.line_num, from, aNameMap,
4079 MakeLogicalSide(aAxis, eLogicalEdgeEnd),
4080 aExplicitGridEnd, aStyle);
4081 int32_t span = aStart.line_num == 0 ? 1 : aStart.line_num;
4082 if (end <= 1) {
4083 // The end is at or before the first explicit line, thus all lines before
4084 // it match <custom-ident> since they're implicit.
4085 int32_t start = std::max(end - span, aNameMap.mClampMinLine);
4086 return LinePair(start, end);
4088 auto start = ResolveLine(aStart, -span, end, aNameMap,
4089 MakeLogicalSide(aAxis, eLogicalEdgeStart),
4090 aExplicitGridEnd, aStyle);
4091 return LinePair(start, end);
4094 int32_t start = kAutoLine;
4095 if (aStart.IsAuto()) {
4096 if (aEnd.IsAuto()) {
4097 // auto / auto
4098 return LinePair(start, 1); // XXX subgrid explicit size instead of 1?
4100 if (aEnd.is_span) {
4101 if (aEnd.LineName()->IsEmpty()) {
4102 // auto / span <integer>
4103 MOZ_ASSERT(aEnd.line_num != 0);
4104 return LinePair(start, aEnd.line_num);
4106 // http://dev.w3.org/csswg/css-grid/#grid-placement-errors
4107 // auto / span <custom-ident>
4108 return LinePair(start, 1); // XXX subgrid explicit size instead of 1?
4110 } else {
4111 uint32_t from = aStart.line_num < 0 ? aExplicitGridEnd + 1 : 0;
4112 start = ResolveLine(aStart, aStart.line_num, from, aNameMap,
4113 MakeLogicalSide(aAxis, eLogicalEdgeStart),
4114 aExplicitGridEnd, aStyle);
4115 if (aEnd.IsAuto()) {
4116 // A "definite line / auto" should resolve the auto to 'span 1'.
4117 // The error handling in ResolveLineRange will make that happen and also
4118 // clamp the end line correctly if we return "start / start".
4119 return LinePair(start, start);
4123 uint32_t from;
4124 int32_t nth = aEnd.line_num == 0 ? 1 : aEnd.line_num;
4125 if (aEnd.is_span) {
4126 if (MOZ_UNLIKELY(start < 0)) {
4127 if (aEnd.LineName()->IsEmpty()) {
4128 return LinePair(start, start + nth);
4130 from = 0;
4131 } else {
4132 if (start >= int32_t(aExplicitGridEnd)) {
4133 // The start is at or after the last explicit line, thus all lines
4134 // after it match <custom-ident> since they're implicit.
4135 return LinePair(start, std::min(start + nth, aNameMap.mClampMaxLine));
4137 from = start;
4139 } else {
4140 from = aEnd.line_num < 0 ? aExplicitGridEnd + 1 : 0;
4142 auto end = ResolveLine(aEnd, nth, from, aNameMap,
4143 MakeLogicalSide(aAxis, eLogicalEdgeEnd),
4144 aExplicitGridEnd, aStyle);
4145 if (start == int32_t(kAutoLine)) {
4146 // auto / definite line
4147 start = std::max(aNameMap.mClampMinLine, end - 1);
4149 return LinePair(start, end);
4152 nsGridContainerFrame::LineRange nsGridContainerFrame::Grid::ResolveLineRange(
4153 const StyleGridLine& aStart, const StyleGridLine& aEnd,
4154 const LineNameMap& aNameMap, LogicalAxis aAxis, uint32_t aExplicitGridEnd,
4155 const nsStylePosition* aStyle) {
4156 LinePair r = ResolveLineRangeHelper(aStart, aEnd, aNameMap, aAxis,
4157 aExplicitGridEnd, aStyle);
4158 MOZ_ASSERT(r.second != int32_t(kAutoLine));
4160 if (r.first == int32_t(kAutoLine)) {
4161 // r.second is a span, clamp it to aNameMap.mClampMaxLine - 1 so that
4162 // the returned range has a HypotheticalEnd <= aNameMap.mClampMaxLine.
4163 // http://dev.w3.org/csswg/css-grid/#overlarge-grids
4164 r.second = std::min(r.second, aNameMap.mClampMaxLine - 1);
4165 } else {
4166 // http://dev.w3.org/csswg/css-grid/#grid-placement-errors
4167 if (r.first > r.second) {
4168 std::swap(r.first, r.second);
4169 } else if (r.first == r.second) {
4170 if (MOZ_UNLIKELY(r.first == aNameMap.mClampMaxLine)) {
4171 r.first = aNameMap.mClampMaxLine - 1;
4173 r.second = r.first + 1; // XXX subgrid explicit size instead of 1?
4176 return LineRange(r.first, r.second);
4179 nsGridContainerFrame::GridArea nsGridContainerFrame::Grid::PlaceDefinite(
4180 nsIFrame* aChild, const LineNameMap& aColLineNameMap,
4181 const LineNameMap& aRowLineNameMap, const nsStylePosition* aStyle) {
4182 const nsStylePosition* itemStyle = aChild->StylePosition();
4183 return GridArea(
4184 ResolveLineRange(itemStyle->mGridColumnStart, itemStyle->mGridColumnEnd,
4185 aColLineNameMap, eLogicalAxisInline, mExplicitGridColEnd,
4186 aStyle),
4187 ResolveLineRange(itemStyle->mGridRowStart, itemStyle->mGridRowEnd,
4188 aRowLineNameMap, eLogicalAxisBlock, mExplicitGridRowEnd,
4189 aStyle));
4192 nsGridContainerFrame::LineRange
4193 nsGridContainerFrame::Grid::ResolveAbsPosLineRange(
4194 const StyleGridLine& aStart, const StyleGridLine& aEnd,
4195 const LineNameMap& aNameMap, LogicalAxis aAxis, uint32_t aExplicitGridEnd,
4196 int32_t aGridStart, int32_t aGridEnd, const nsStylePosition* aStyle) {
4197 if (aStart.IsAuto()) {
4198 if (aEnd.IsAuto()) {
4199 return LineRange(kAutoLine, kAutoLine);
4201 uint32_t from = aEnd.line_num < 0 ? aExplicitGridEnd + 1 : 0;
4202 int32_t end = ResolveLine(aEnd, aEnd.line_num, from, aNameMap,
4203 MakeLogicalSide(aAxis, eLogicalEdgeEnd),
4204 aExplicitGridEnd, aStyle);
4205 if (aEnd.is_span) {
4206 ++end;
4208 // A line outside the existing grid is treated as 'auto' for abs.pos (10.1).
4209 end = AutoIfOutside(end, aGridStart, aGridEnd);
4210 return LineRange(kAutoLine, end);
4213 if (aEnd.IsAuto()) {
4214 uint32_t from = aStart.line_num < 0 ? aExplicitGridEnd + 1 : 0;
4215 int32_t start = ResolveLine(aStart, aStart.line_num, from, aNameMap,
4216 MakeLogicalSide(aAxis, eLogicalEdgeStart),
4217 aExplicitGridEnd, aStyle);
4218 if (aStart.is_span) {
4219 start = std::max(aGridEnd - start, aGridStart);
4221 start = AutoIfOutside(start, aGridStart, aGridEnd);
4222 return LineRange(start, kAutoLine);
4225 LineRange r =
4226 ResolveLineRange(aStart, aEnd, aNameMap, aAxis, aExplicitGridEnd, aStyle);
4227 if (r.IsAuto()) {
4228 MOZ_ASSERT(aStart.is_span && aEnd.is_span,
4229 "span / span is the only case "
4230 "leading to IsAuto here -- we dealt with the other cases above");
4231 // The second span was ignored per 9.2.1. For abs.pos., 10.1 says that this
4232 // case should result in "auto / auto" unlike normal flow grid items.
4233 return LineRange(kAutoLine, kAutoLine);
4236 return LineRange(AutoIfOutside(r.mUntranslatedStart, aGridStart, aGridEnd),
4237 AutoIfOutside(r.mUntranslatedEnd, aGridStart, aGridEnd));
4240 nsGridContainerFrame::GridArea nsGridContainerFrame::Grid::PlaceAbsPos(
4241 nsIFrame* aChild, const LineNameMap& aColLineNameMap,
4242 const LineNameMap& aRowLineNameMap, const nsStylePosition* aStyle) {
4243 const nsStylePosition* itemStyle = aChild->StylePosition();
4244 int32_t gridColStart = 1 - mExplicitGridOffsetCol;
4245 int32_t gridRowStart = 1 - mExplicitGridOffsetRow;
4246 return GridArea(ResolveAbsPosLineRange(
4247 itemStyle->mGridColumnStart, itemStyle->mGridColumnEnd,
4248 aColLineNameMap, eLogicalAxisInline, mExplicitGridColEnd,
4249 gridColStart, mGridColEnd, aStyle),
4250 ResolveAbsPosLineRange(
4251 itemStyle->mGridRowStart, itemStyle->mGridRowEnd,
4252 aRowLineNameMap, eLogicalAxisBlock, mExplicitGridRowEnd,
4253 gridRowStart, mGridRowEnd, aStyle));
4256 uint32_t nsGridContainerFrame::Grid::FindAutoCol(uint32_t aStartCol,
4257 uint32_t aLockedRow,
4258 const GridArea* aArea) const {
4259 const uint32_t extent = aArea->mCols.Extent();
4260 const uint32_t iStart = aLockedRow;
4261 const uint32_t iEnd = iStart + aArea->mRows.Extent();
4262 uint32_t candidate = aStartCol;
4263 for (uint32_t i = iStart; i < iEnd;) {
4264 if (i >= mCellMap.mCells.Length()) {
4265 break;
4267 const nsTArray<CellMap::Cell>& cellsInRow = mCellMap.mCells[i];
4268 const uint32_t len = cellsInRow.Length();
4269 const uint32_t lastCandidate = candidate;
4270 // Find the first gap in the current row that's at least 'extent' wide.
4271 // ('gap' tracks how wide the current column gap is.)
4272 for (uint32_t j = candidate, gap = 0; j < len && gap < extent; ++j) {
4273 if (!cellsInRow[j].mIsOccupied) {
4274 ++gap;
4275 continue;
4277 candidate = j + 1;
4278 gap = 0;
4280 if (lastCandidate < candidate && i != iStart) {
4281 // Couldn't fit 'extent' tracks at 'lastCandidate' here so we must
4282 // restart from the beginning with the new 'candidate'.
4283 i = iStart;
4284 } else {
4285 ++i;
4288 return candidate;
4291 void nsGridContainerFrame::Grid::PlaceAutoCol(uint32_t aStartCol,
4292 GridArea* aArea,
4293 uint32_t aClampMaxColLine) const {
4294 MOZ_ASSERT(aArea->mRows.IsDefinite() && aArea->mCols.IsAuto());
4295 uint32_t col = FindAutoCol(aStartCol, aArea->mRows.mStart, aArea);
4296 aArea->mCols.ResolveAutoPosition(col, aClampMaxColLine);
4297 MOZ_ASSERT(aArea->IsDefinite());
4300 uint32_t nsGridContainerFrame::Grid::FindAutoRow(uint32_t aLockedCol,
4301 uint32_t aStartRow,
4302 const GridArea* aArea) const {
4303 const uint32_t extent = aArea->mRows.Extent();
4304 const uint32_t jStart = aLockedCol;
4305 const uint32_t jEnd = jStart + aArea->mCols.Extent();
4306 const uint32_t iEnd = mCellMap.mCells.Length();
4307 uint32_t candidate = aStartRow;
4308 // Find the first gap in the rows that's at least 'extent' tall.
4309 // ('gap' tracks how tall the current row gap is.)
4310 for (uint32_t i = candidate, gap = 0; i < iEnd && gap < extent; ++i) {
4311 ++gap; // tentative, but we may reset it below if a column is occupied
4312 const nsTArray<CellMap::Cell>& cellsInRow = mCellMap.mCells[i];
4313 const uint32_t clampedJEnd = std::min<uint32_t>(jEnd, cellsInRow.Length());
4314 // Check if the current row is unoccupied from jStart to jEnd.
4315 for (uint32_t j = jStart; j < clampedJEnd; ++j) {
4316 if (cellsInRow[j].mIsOccupied) {
4317 // Couldn't fit 'extent' rows at 'candidate' here; we hit something
4318 // at row 'i'. So, try the row after 'i' as our next candidate.
4319 candidate = i + 1;
4320 gap = 0;
4321 break;
4325 return candidate;
4328 void nsGridContainerFrame::Grid::PlaceAutoRow(uint32_t aStartRow,
4329 GridArea* aArea,
4330 uint32_t aClampMaxRowLine) const {
4331 MOZ_ASSERT(aArea->mCols.IsDefinite() && aArea->mRows.IsAuto());
4332 uint32_t row = FindAutoRow(aArea->mCols.mStart, aStartRow, aArea);
4333 aArea->mRows.ResolveAutoPosition(row, aClampMaxRowLine);
4334 MOZ_ASSERT(aArea->IsDefinite());
4337 void nsGridContainerFrame::Grid::PlaceAutoAutoInRowOrder(
4338 uint32_t aStartCol, uint32_t aStartRow, GridArea* aArea,
4339 uint32_t aClampMaxColLine, uint32_t aClampMaxRowLine) const {
4340 MOZ_ASSERT(aArea->mCols.IsAuto() && aArea->mRows.IsAuto());
4341 const uint32_t colExtent = aArea->mCols.Extent();
4342 const uint32_t gridRowEnd = mGridRowEnd;
4343 const uint32_t gridColEnd = mGridColEnd;
4344 uint32_t col = aStartCol;
4345 uint32_t row = aStartRow;
4346 for (; row < gridRowEnd; ++row) {
4347 col = FindAutoCol(col, row, aArea);
4348 if (col + colExtent <= gridColEnd) {
4349 break;
4351 col = 0;
4353 MOZ_ASSERT(row < gridRowEnd || col == 0,
4354 "expected column 0 for placing in a new row");
4355 aArea->mCols.ResolveAutoPosition(col, aClampMaxColLine);
4356 aArea->mRows.ResolveAutoPosition(row, aClampMaxRowLine);
4357 MOZ_ASSERT(aArea->IsDefinite());
4360 void nsGridContainerFrame::Grid::PlaceAutoAutoInColOrder(
4361 uint32_t aStartCol, uint32_t aStartRow, GridArea* aArea,
4362 uint32_t aClampMaxColLine, uint32_t aClampMaxRowLine) const {
4363 MOZ_ASSERT(aArea->mCols.IsAuto() && aArea->mRows.IsAuto());
4364 const uint32_t rowExtent = aArea->mRows.Extent();
4365 const uint32_t gridRowEnd = mGridRowEnd;
4366 const uint32_t gridColEnd = mGridColEnd;
4367 uint32_t col = aStartCol;
4368 uint32_t row = aStartRow;
4369 for (; col < gridColEnd; ++col) {
4370 row = FindAutoRow(col, row, aArea);
4371 if (row + rowExtent <= gridRowEnd) {
4372 break;
4374 row = 0;
4376 MOZ_ASSERT(col < gridColEnd || row == 0,
4377 "expected row 0 for placing in a new column");
4378 aArea->mCols.ResolveAutoPosition(col, aClampMaxColLine);
4379 aArea->mRows.ResolveAutoPosition(row, aClampMaxRowLine);
4380 MOZ_ASSERT(aArea->IsDefinite());
4383 template <typename IsEmptyFuncT>
4384 Maybe<nsTArray<uint32_t>>
4385 nsGridContainerFrame::Grid::CalculateAdjustForAutoFitElements(
4386 uint32_t* const aOutNumEmptyLines, TrackSizingFunctions& aSizingFunctions,
4387 uint32_t aNumGridLines, IsEmptyFuncT aIsEmptyFunc) {
4388 Maybe<nsTArray<uint32_t>> trackAdjust;
4389 uint32_t& numEmptyLines = *aOutNumEmptyLines;
4390 numEmptyLines = 0;
4391 if (aSizingFunctions.NumRepeatTracks() > 0) {
4392 MOZ_ASSERT(aSizingFunctions.mHasRepeatAuto);
4393 // Since this loop is concerned with just the repeat tracks, we
4394 // iterate from 0..NumRepeatTracks() which is the natural range of
4395 // mRemoveRepeatTracks. This means we have to add
4396 // (mExplicitGridOffset + mRepeatAutoStart) to get a zero-based
4397 // index for arrays like mCellMap/aIsEmptyFunc and trackAdjust. We'll then
4398 // fill out the trackAdjust array for all the remaining lines.
4399 const uint32_t repeatStart = (aSizingFunctions.mExplicitGridOffset +
4400 aSizingFunctions.mRepeatAutoStart);
4401 const uint32_t numRepeats = aSizingFunctions.NumRepeatTracks();
4402 for (uint32_t i = 0; i < numRepeats; ++i) {
4403 if (numEmptyLines) {
4404 MOZ_ASSERT(trackAdjust.isSome());
4405 (*trackAdjust)[repeatStart + i] = numEmptyLines;
4407 if (aIsEmptyFunc(repeatStart + i)) {
4408 ++numEmptyLines;
4409 if (trackAdjust.isNothing()) {
4410 trackAdjust.emplace(aNumGridLines);
4411 trackAdjust->SetLength(aNumGridLines);
4412 PodZero(trackAdjust->Elements(), trackAdjust->Length());
4415 aSizingFunctions.mRemovedRepeatTracks[i] = true;
4418 // Fill out the trackAdjust array for all the tracks after the repeats.
4419 if (numEmptyLines) {
4420 for (uint32_t line = repeatStart + numRepeats; line < aNumGridLines;
4421 ++line) {
4422 (*trackAdjust)[line] = numEmptyLines;
4427 return trackAdjust;
4430 void nsGridContainerFrame::Grid::SubgridPlaceGridItems(
4431 GridReflowInput& aParentState, Grid* aParentGrid,
4432 const GridItemInfo& aGridItem) {
4433 MOZ_ASSERT(aGridItem.mArea.IsDefinite() ||
4434 aGridItem.mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
4435 "the subgrid's lines should be resolved by now");
4436 if (aGridItem.IsSubgrid(eLogicalAxisInline)) {
4437 aParentState.mFrame->AddStateBits(NS_STATE_GRID_HAS_COL_SUBGRID_ITEM);
4439 if (aGridItem.IsSubgrid(eLogicalAxisBlock)) {
4440 aParentState.mFrame->AddStateBits(NS_STATE_GRID_HAS_ROW_SUBGRID_ITEM);
4442 auto* childGrid = aGridItem.SubgridFrame();
4443 const auto* pos = childGrid->StylePosition();
4444 childGrid->NormalizeChildLists();
4445 GridReflowInput state(childGrid, aParentState.mRenderingContext);
4446 childGrid->InitImplicitNamedAreas(pos);
4448 const bool isOrthogonal = aParentState.mWM.IsOrthogonalTo(state.mWM);
4449 // Record the subgrid's GridArea in a frame property.
4450 auto* subgrid = childGrid->GetProperty(Subgrid::Prop());
4451 if (!subgrid) {
4452 subgrid = new Subgrid(aGridItem.mArea, isOrthogonal, aParentState.mWM);
4453 childGrid->SetProperty(Subgrid::Prop(), subgrid);
4454 } else {
4455 subgrid->mArea = aGridItem.mArea;
4456 subgrid->mIsOrthogonal = isOrthogonal;
4457 subgrid->mGridItems.Clear();
4458 subgrid->mAbsPosItems.Clear();
4461 // Abs.pos. subgrids may have kAutoLine in their area. Map those to the edge
4462 // line in the parent's grid (zero-based line numbers).
4463 if (MOZ_UNLIKELY(subgrid->mArea.mCols.mStart == kAutoLine)) {
4464 subgrid->mArea.mCols.mStart = 0;
4466 if (MOZ_UNLIKELY(subgrid->mArea.mCols.mEnd == kAutoLine)) {
4467 subgrid->mArea.mCols.mEnd = aParentGrid->mGridColEnd - 1;
4469 if (MOZ_UNLIKELY(subgrid->mArea.mRows.mStart == kAutoLine)) {
4470 subgrid->mArea.mRows.mStart = 0;
4472 if (MOZ_UNLIKELY(subgrid->mArea.mRows.mEnd == kAutoLine)) {
4473 subgrid->mArea.mRows.mEnd = aParentGrid->mGridRowEnd - 1;
4476 MOZ_ASSERT((subgrid->mArea.mCols.Extent() > 0 &&
4477 subgrid->mArea.mRows.Extent() > 0) ||
4478 state.mGridItems.IsEmpty(),
4479 "subgrid needs at least one track for its items");
4481 // The min/sz/max sizes are the input to the "repeat-to-fill" algorithm:
4482 // https://drafts.csswg.org/css-grid/#auto-repeat
4483 // They're only used for auto-repeat in a non-subgridded axis so we skip
4484 // computing them otherwise.
4485 RepeatTrackSizingInput repeatSizing(state.mWM);
4486 if (!childGrid->IsColSubgrid() && state.mColFunctions.mHasRepeatAuto) {
4487 repeatSizing.InitFromStyle(eLogicalAxisInline, state.mWM,
4488 state.mFrame->Style());
4490 if (!childGrid->IsRowSubgrid() && state.mRowFunctions.mHasRepeatAuto) {
4491 repeatSizing.InitFromStyle(eLogicalAxisBlock, state.mWM,
4492 state.mFrame->Style());
4495 PlaceGridItems(state, repeatSizing);
4497 subgrid->mGridItems = std::move(state.mGridItems);
4498 subgrid->mAbsPosItems = std::move(state.mAbsPosItems);
4499 subgrid->mGridColEnd = mGridColEnd;
4500 subgrid->mGridRowEnd = mGridRowEnd;
4503 void nsGridContainerFrame::Grid::PlaceGridItems(
4504 GridReflowInput& aState, const RepeatTrackSizingInput& aSizes) {
4505 MOZ_ASSERT(mCellMap.mCells.IsEmpty(), "unexpected entries in cell map");
4507 mAreas = aState.mFrame->GetImplicitNamedAreas();
4509 if (aState.mFrame->HasSubgridItems() || aState.mFrame->IsSubgrid()) {
4510 if (auto* uts = aState.mFrame->GetUsedTrackSizes()) {
4511 uts->mCanResolveLineRangeSize = {false, false};
4512 uts->mSizes[eLogicalAxisInline].ClearAndRetainStorage();
4513 uts->mSizes[eLogicalAxisBlock].ClearAndRetainStorage();
4517 // SubgridPlaceGridItems will set these if we find any subgrid items.
4518 aState.mFrame->RemoveStateBits(NS_STATE_GRID_HAS_COL_SUBGRID_ITEM |
4519 NS_STATE_GRID_HAS_ROW_SUBGRID_ITEM);
4521 // http://dev.w3.org/csswg/css-grid/#grid-definition
4522 // Initialize the end lines of the Explicit Grid (mExplicitGridCol[Row]End).
4523 // This is determined by the larger of the number of rows/columns defined
4524 // by 'grid-template-areas' and the 'grid-template-rows'/'-columns', plus one.
4525 // Also initialize the Implicit Grid (mGridCol[Row]End) to the same values.
4526 // Note that this is for a grid with a 1,1 origin. We'll change that
4527 // to a 0,0 based grid after placing definite lines.
4528 const nsStylePosition* const gridStyle = aState.mGridStyle;
4529 const auto* areas = gridStyle->mGridTemplateAreas.IsNone()
4530 ? nullptr
4531 : &*gridStyle->mGridTemplateAreas.AsAreas();
4532 const LineNameMap* parentLineNameMap = nullptr;
4533 const LineRange* subgridRange = nullptr;
4534 bool subgridAxisIsSameDirection = true;
4535 if (!aState.mFrame->IsColSubgrid()) {
4536 aState.mColFunctions.InitRepeatTracks(
4537 gridStyle->mColumnGap, aSizes.mMin.ISize(aState.mWM),
4538 aSizes.mSize.ISize(aState.mWM), aSizes.mMax.ISize(aState.mWM));
4539 uint32_t areaCols = areas ? areas->width + 1 : 1;
4540 mExplicitGridColEnd = aState.mColFunctions.ComputeExplicitGridEnd(areaCols);
4541 } else {
4542 const auto* subgrid = aState.mFrame->GetProperty(Subgrid::Prop());
4543 subgridRange = &subgrid->SubgridCols();
4544 uint32_t extent = subgridRange->Extent();
4545 mExplicitGridColEnd = extent + 1; // the grid is 1-based at this point
4546 parentLineNameMap =
4547 ParentLineMapForAxis(subgrid->mIsOrthogonal, eLogicalAxisInline);
4548 auto parentWM =
4549 aState.mFrame->ParentGridContainerForSubgrid()->GetWritingMode();
4550 subgridAxisIsSameDirection =
4551 aState.mWM.ParallelAxisStartsOnSameSide(eLogicalAxisInline, parentWM);
4553 mGridColEnd = mExplicitGridColEnd;
4554 LineNameMap colLineNameMap(gridStyle, mAreas, aState.mColFunctions,
4555 parentLineNameMap, subgridRange,
4556 subgridAxisIsSameDirection);
4558 if (!aState.mFrame->IsRowSubgrid()) {
4559 const Maybe<nscoord> containBSize = aState.mFrame->ContainIntrinsicBSize();
4560 const nscoord repeatTrackSizingBSize = [&] {
4561 // This clamping only applies to auto sizes.
4562 if (containBSize &&
4563 aSizes.mSize.BSize(aState.mWM) == NS_UNCONSTRAINEDSIZE) {
4564 return NS_CSS_MINMAX(*containBSize, aSizes.mMin.BSize(aState.mWM),
4565 aSizes.mMax.BSize(aState.mWM));
4567 return aSizes.mSize.BSize(aState.mWM);
4568 }();
4569 aState.mRowFunctions.InitRepeatTracks(
4570 gridStyle->mRowGap, aSizes.mMin.BSize(aState.mWM),
4571 repeatTrackSizingBSize, aSizes.mMax.BSize(aState.mWM));
4572 uint32_t areaRows = areas ? areas->strings.Length() + 1 : 1;
4573 mExplicitGridRowEnd = aState.mRowFunctions.ComputeExplicitGridEnd(areaRows);
4574 parentLineNameMap = nullptr;
4575 subgridRange = nullptr;
4576 } else {
4577 const auto* subgrid = aState.mFrame->GetProperty(Subgrid::Prop());
4578 subgridRange = &subgrid->SubgridRows();
4579 uint32_t extent = subgridRange->Extent();
4580 mExplicitGridRowEnd = extent + 1; // the grid is 1-based at this point
4581 parentLineNameMap =
4582 ParentLineMapForAxis(subgrid->mIsOrthogonal, eLogicalAxisBlock);
4583 auto parentWM =
4584 aState.mFrame->ParentGridContainerForSubgrid()->GetWritingMode();
4585 subgridAxisIsSameDirection =
4586 aState.mWM.ParallelAxisStartsOnSameSide(eLogicalAxisBlock, parentWM);
4588 mGridRowEnd = mExplicitGridRowEnd;
4589 LineNameMap rowLineNameMap(gridStyle, mAreas, aState.mRowFunctions,
4590 parentLineNameMap, subgridRange,
4591 subgridAxisIsSameDirection);
4593 const bool isSubgridOrItemInSubgrid =
4594 aState.mFrame->IsSubgrid() || !!mParentGrid;
4595 auto SetSubgridChildEdgeBits =
4596 [this, isSubgridOrItemInSubgrid](GridItemInfo& aItem) -> void {
4597 if (isSubgridOrItemInSubgrid) {
4598 const auto& area = aItem.mArea;
4599 if (area.mCols.mStart == 0) {
4600 aItem.mState[eLogicalAxisInline] |= ItemState::eStartEdge;
4602 if (area.mCols.mEnd == mGridColEnd) {
4603 aItem.mState[eLogicalAxisInline] |= ItemState::eEndEdge;
4605 if (area.mRows.mStart == 0) {
4606 aItem.mState[eLogicalAxisBlock] |= ItemState::eStartEdge;
4608 if (area.mRows.mEnd == mGridRowEnd) {
4609 aItem.mState[eLogicalAxisBlock] |= ItemState::eEndEdge;
4614 SetLineMaps(&colLineNameMap, &rowLineNameMap);
4616 // http://dev.w3.org/csswg/css-grid/#line-placement
4617 // Resolve definite positions per spec chap 9.2.
4618 int32_t minCol = 1;
4619 int32_t minRow = 1;
4620 aState.mGridItems.ClearAndRetainStorage();
4621 aState.mIter.Reset();
4622 for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
4623 nsIFrame* child = *aState.mIter;
4624 GridItemInfo* info = aState.mGridItems.AppendElement(GridItemInfo(
4625 child,
4626 PlaceDefinite(child, colLineNameMap, rowLineNameMap, gridStyle)));
4627 MOZ_ASSERT(aState.mIter.ItemIndex() == aState.mGridItems.Length() - 1,
4628 "ItemIndex() is broken");
4629 GridArea& area = info->mArea;
4630 if (area.mCols.IsDefinite()) {
4631 minCol = std::min(minCol, area.mCols.mUntranslatedStart);
4633 if (area.mRows.IsDefinite()) {
4634 minRow = std::min(minRow, area.mRows.mUntranslatedStart);
4638 // Translate the whole grid so that the top-/left-most area is at 0,0.
4639 mExplicitGridOffsetCol = 1 - minCol; // minCol/Row is always <= 1, see above
4640 mExplicitGridOffsetRow = 1 - minRow;
4641 aState.mColFunctions.mExplicitGridOffset = mExplicitGridOffsetCol;
4642 aState.mRowFunctions.mExplicitGridOffset = mExplicitGridOffsetRow;
4643 const int32_t offsetToColZero = int32_t(mExplicitGridOffsetCol) - 1;
4644 const int32_t offsetToRowZero = int32_t(mExplicitGridOffsetRow) - 1;
4645 const bool isRowMasonry = aState.mFrame->IsMasonry(eLogicalAxisBlock);
4646 const bool isColMasonry = aState.mFrame->IsMasonry(eLogicalAxisInline);
4647 const bool isMasonry = isColMasonry || isRowMasonry;
4648 mGridColEnd += offsetToColZero;
4649 mGridRowEnd += offsetToRowZero;
4650 const uint32_t gridAxisTrackCount = isRowMasonry ? mGridColEnd : mGridRowEnd;
4651 aState.mIter.Reset();
4652 for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
4653 auto& item = aState.mGridItems[aState.mIter.ItemIndex()];
4654 GridArea& area = item.mArea;
4655 if (area.mCols.IsDefinite()) {
4656 area.mCols.mStart = area.mCols.mUntranslatedStart + offsetToColZero;
4657 area.mCols.mEnd = area.mCols.mUntranslatedEnd + offsetToColZero;
4659 if (area.mRows.IsDefinite()) {
4660 area.mRows.mStart = area.mRows.mUntranslatedStart + offsetToRowZero;
4661 area.mRows.mEnd = area.mRows.mUntranslatedEnd + offsetToRowZero;
4663 if (area.IsDefinite()) {
4664 if (isMasonry) {
4665 item.MaybeInhibitSubgridInMasonry(aState.mFrame, gridAxisTrackCount);
4667 if (item.IsSubgrid()) {
4668 Grid grid(this);
4669 grid.SubgridPlaceGridItems(aState, this, item);
4671 mCellMap.Fill(area);
4672 InflateGridFor(area);
4673 SetSubgridChildEdgeBits(item);
4677 // http://dev.w3.org/csswg/css-grid/#auto-placement-algo
4678 // Step 1, place 'auto' items that have one definite position -
4679 // definite row (column) for grid-auto-flow:row (column).
4680 auto flowStyle = gridStyle->mGridAutoFlow;
4681 const bool isRowOrder =
4682 isMasonry ? isRowMasonry : !!(flowStyle & StyleGridAutoFlow::ROW);
4683 const bool isSparse = !(flowStyle & StyleGridAutoFlow::DENSE);
4684 uint32_t clampMaxColLine = colLineNameMap.mClampMaxLine + offsetToColZero;
4685 uint32_t clampMaxRowLine = rowLineNameMap.mClampMaxLine + offsetToRowZero;
4686 // We need 1 cursor per row (or column) if placement is sparse.
4688 Maybe<nsTHashMap<nsUint32HashKey, uint32_t>> cursors;
4689 if (isSparse) {
4690 cursors.emplace();
4692 auto placeAutoMinorFunc =
4693 isRowOrder ? &Grid::PlaceAutoCol : &Grid::PlaceAutoRow;
4694 uint32_t clampMaxLine = isRowOrder ? clampMaxColLine : clampMaxRowLine;
4695 aState.mIter.Reset();
4696 for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
4697 auto& item = aState.mGridItems[aState.mIter.ItemIndex()];
4698 GridArea& area = item.mArea;
4699 LineRange& major = isRowOrder ? area.mRows : area.mCols;
4700 LineRange& minor = isRowOrder ? area.mCols : area.mRows;
4701 if (major.IsDefinite() && minor.IsAuto()) {
4702 // Items with 'auto' in the minor dimension only.
4703 const uint32_t cursor = isSparse ? cursors->Get(major.mStart) : 0;
4704 (this->*placeAutoMinorFunc)(cursor, &area, clampMaxLine);
4705 if (isMasonry) {
4706 item.MaybeInhibitSubgridInMasonry(aState.mFrame, gridAxisTrackCount);
4708 if (item.IsSubgrid()) {
4709 Grid grid(this);
4710 grid.SubgridPlaceGridItems(aState, this, item);
4712 mCellMap.Fill(area);
4713 SetSubgridChildEdgeBits(item);
4714 if (isSparse) {
4715 cursors->InsertOrUpdate(major.mStart, minor.mEnd);
4718 InflateGridFor(area); // Step 2, inflating for auto items too
4722 // XXX NOTE possible spec issue.
4723 // XXX It's unclear if the remaining major-dimension auto and
4724 // XXX auto in both dimensions should use the same cursor or not,
4725 // XXX https://www.w3.org/Bugs/Public/show_bug.cgi?id=16044
4726 // XXX seems to indicate it shouldn't.
4727 // XXX http://dev.w3.org/csswg/css-grid/#auto-placement-cursor
4728 // XXX now says it should (but didn't in earlier versions)
4730 // Step 3, place the remaining grid items
4731 uint32_t cursorMajor = 0; // for 'dense' these two cursors will stay at 0,0
4732 uint32_t cursorMinor = 0;
4733 auto placeAutoMajorFunc =
4734 isRowOrder ? &Grid::PlaceAutoRow : &Grid::PlaceAutoCol;
4735 uint32_t clampMaxMajorLine = isRowOrder ? clampMaxRowLine : clampMaxColLine;
4736 aState.mIter.Reset();
4737 for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
4738 auto& item = aState.mGridItems[aState.mIter.ItemIndex()];
4739 GridArea& area = item.mArea;
4740 MOZ_ASSERT(*aState.mIter == item.mFrame,
4741 "iterator out of sync with aState.mGridItems");
4742 LineRange& major = isRowOrder ? area.mRows : area.mCols;
4743 LineRange& minor = isRowOrder ? area.mCols : area.mRows;
4744 if (major.IsAuto()) {
4745 if (minor.IsDefinite()) {
4746 // Items with 'auto' in the major dimension only.
4747 if (isSparse) {
4748 if (minor.mStart < cursorMinor) {
4749 ++cursorMajor;
4751 cursorMinor = minor.mStart;
4753 (this->*placeAutoMajorFunc)(cursorMajor, &area, clampMaxMajorLine);
4754 if (isSparse) {
4755 cursorMajor = major.mStart;
4757 } else {
4758 // Items with 'auto' in both dimensions.
4759 if (isRowOrder) {
4760 PlaceAutoAutoInRowOrder(cursorMinor, cursorMajor, &area,
4761 clampMaxColLine, clampMaxRowLine);
4762 } else {
4763 PlaceAutoAutoInColOrder(cursorMajor, cursorMinor, &area,
4764 clampMaxColLine, clampMaxRowLine);
4766 if (isSparse) {
4767 cursorMajor = major.mStart;
4768 cursorMinor = minor.mEnd;
4769 #ifdef DEBUG
4770 uint32_t gridMajorEnd = isRowOrder ? mGridRowEnd : mGridColEnd;
4771 uint32_t gridMinorEnd = isRowOrder ? mGridColEnd : mGridRowEnd;
4772 MOZ_ASSERT(cursorMajor <= gridMajorEnd,
4773 "we shouldn't need to place items further than 1 track "
4774 "past the current end of the grid, in major dimension");
4775 MOZ_ASSERT(cursorMinor <= gridMinorEnd,
4776 "we shouldn't add implicit minor tracks for auto/auto");
4777 #endif
4780 if (isMasonry) {
4781 item.MaybeInhibitSubgridInMasonry(aState.mFrame, gridAxisTrackCount);
4783 if (item.IsSubgrid()) {
4784 Grid grid(this);
4785 grid.SubgridPlaceGridItems(aState, this, item);
4787 mCellMap.Fill(area);
4788 InflateGridFor(area);
4789 SetSubgridChildEdgeBits(item);
4790 // XXXmats it might be possible to optimize this a bit for masonry layout
4791 // if this item was placed in the 2nd row && !isSparse, or the 1st row
4792 // is full. Still gotta inflate the grid for all items though to make
4793 // the grid large enough...
4797 // Force all items into the 1st/2nd track and have span 1 in the masonry axis.
4798 // (See comment on nsGridContainerFrame::MasonryLayout().)
4799 if (isMasonry) {
4800 auto masonryAxis = isRowMasonry ? eLogicalAxisBlock : eLogicalAxisInline;
4801 aState.mIter.Reset();
4802 for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
4803 auto& item = aState.mGridItems[aState.mIter.ItemIndex()];
4804 auto& masonryRange = item.mArea.LineRangeForAxis(masonryAxis);
4805 masonryRange.mStart = std::min(masonryRange.mStart, 1U);
4806 masonryRange.mEnd = masonryRange.mStart + 1U;
4810 if (aState.mFrame->IsAbsoluteContainer()) {
4811 // 9.4 Absolutely-positioned Grid Items
4812 // http://dev.w3.org/csswg/css-grid/#abspos-items
4813 // We only resolve definite lines here; we'll align auto positions to the
4814 // grid container later during reflow.
4815 const nsFrameList& children =
4816 aState.mFrame->GetChildList(aState.mFrame->GetAbsoluteListID());
4817 const int32_t offsetToColZero = int32_t(mExplicitGridOffsetCol) - 1;
4818 const int32_t offsetToRowZero = int32_t(mExplicitGridOffsetRow) - 1;
4819 // Untranslate the grid again temporarily while resolving abs.pos. lines.
4820 AutoRestore<uint32_t> zeroOffsetGridColEnd(mGridColEnd);
4821 AutoRestore<uint32_t> zeroOffsetGridRowEnd(mGridRowEnd);
4822 mGridColEnd -= offsetToColZero;
4823 mGridRowEnd -= offsetToRowZero;
4824 aState.mAbsPosItems.ClearAndRetainStorage();
4825 for (nsIFrame* child : children) {
4826 GridItemInfo* info = aState.mAbsPosItems.AppendElement(GridItemInfo(
4827 child,
4828 PlaceAbsPos(child, colLineNameMap, rowLineNameMap, gridStyle)));
4829 GridArea& area = info->mArea;
4830 if (area.mCols.mUntranslatedStart != int32_t(kAutoLine)) {
4831 area.mCols.mStart = area.mCols.mUntranslatedStart + offsetToColZero;
4832 if (isColMasonry) {
4833 // XXXmats clamp any non-auto line to 0 or 1. This is intended to
4834 // allow authors to address the start/end of the masonry box.
4835 // This is experimental at this point though and needs author feedback
4836 // and spec work to sort out what is desired and how it should work.
4837 // See https://github.com/w3c/csswg-drafts/issues/4650
4838 area.mCols.mStart = std::min(area.mCols.mStart, 1U);
4841 if (area.mCols.mUntranslatedEnd != int32_t(kAutoLine)) {
4842 area.mCols.mEnd = area.mCols.mUntranslatedEnd + offsetToColZero;
4843 if (isColMasonry) {
4844 // ditto
4845 area.mCols.mEnd = std::min(area.mCols.mEnd, 1U);
4848 if (area.mRows.mUntranslatedStart != int32_t(kAutoLine)) {
4849 area.mRows.mStart = area.mRows.mUntranslatedStart + offsetToRowZero;
4850 if (isRowMasonry) {
4851 // ditto
4852 area.mRows.mStart = std::min(area.mRows.mStart, 1U);
4855 if (area.mRows.mUntranslatedEnd != int32_t(kAutoLine)) {
4856 area.mRows.mEnd = area.mRows.mUntranslatedEnd + offsetToRowZero;
4857 if (isRowMasonry) {
4858 // ditto
4859 area.mRows.mEnd = std::min(area.mRows.mEnd, 1U);
4862 if (isMasonry) {
4863 info->MaybeInhibitSubgridInMasonry(aState.mFrame, gridAxisTrackCount);
4866 // An abs.pos. subgrid with placement auto/1 or -1/auto technically
4867 // doesn't span any parent tracks. Inhibit subgridding in this case.
4868 if (info->IsSubgrid(eLogicalAxisInline)) {
4869 if (info->mArea.mCols.mStart == zeroOffsetGridColEnd.SavedValue() ||
4870 info->mArea.mCols.mEnd == 0) {
4871 info->InhibitSubgrid(aState.mFrame, eLogicalAxisInline);
4874 if (info->IsSubgrid(eLogicalAxisBlock)) {
4875 if (info->mArea.mRows.mStart == zeroOffsetGridRowEnd.SavedValue() ||
4876 info->mArea.mRows.mEnd == 0) {
4877 info->InhibitSubgrid(aState.mFrame, eLogicalAxisBlock);
4881 if (info->IsSubgrid()) {
4882 Grid grid(this);
4883 grid.SubgridPlaceGridItems(aState, this, *info);
4888 // Count empty 'auto-fit' tracks in the repeat() range.
4889 // |colAdjust| will have a count for each line in the grid of how many
4890 // tracks were empty between the start of the grid and that line.
4892 Maybe<nsTArray<uint32_t>> colAdjust;
4893 uint32_t numEmptyCols = 0;
4894 if (aState.mColFunctions.mHasRepeatAuto &&
4895 gridStyle->mGridTemplateColumns.GetRepeatAutoValue()->count.IsAutoFit()) {
4896 const auto& cellMap = mCellMap;
4897 colAdjust = CalculateAdjustForAutoFitElements(
4898 &numEmptyCols, aState.mColFunctions, mGridColEnd + 1,
4899 [&cellMap](uint32_t i) -> bool { return cellMap.IsEmptyCol(i); });
4902 // Do similar work for the row tracks, with the same logic.
4903 Maybe<nsTArray<uint32_t>> rowAdjust;
4904 uint32_t numEmptyRows = 0;
4905 if (aState.mRowFunctions.mHasRepeatAuto &&
4906 gridStyle->mGridTemplateRows.GetRepeatAutoValue()->count.IsAutoFit()) {
4907 const auto& cellMap = mCellMap;
4908 rowAdjust = CalculateAdjustForAutoFitElements(
4909 &numEmptyRows, aState.mRowFunctions, mGridRowEnd + 1,
4910 [&cellMap](uint32_t i) -> bool { return cellMap.IsEmptyRow(i); });
4912 MOZ_ASSERT((numEmptyCols > 0) == colAdjust.isSome());
4913 MOZ_ASSERT((numEmptyRows > 0) == rowAdjust.isSome());
4914 // Remove the empty 'auto-fit' tracks we found above, if any.
4915 if (numEmptyCols || numEmptyRows) {
4916 // Adjust the line numbers in the grid areas.
4917 for (auto& item : aState.mGridItems) {
4918 if (numEmptyCols) {
4919 item.AdjustForRemovedTracks(eLogicalAxisInline, *colAdjust);
4921 if (numEmptyRows) {
4922 item.AdjustForRemovedTracks(eLogicalAxisBlock, *rowAdjust);
4925 for (auto& item : aState.mAbsPosItems) {
4926 if (numEmptyCols) {
4927 item.AdjustForRemovedTracks(eLogicalAxisInline, *colAdjust);
4929 if (numEmptyRows) {
4930 item.AdjustForRemovedTracks(eLogicalAxisBlock, *rowAdjust);
4933 // Adjust the grid size.
4934 mGridColEnd -= numEmptyCols;
4935 mExplicitGridColEnd -= numEmptyCols;
4936 mGridRowEnd -= numEmptyRows;
4937 mExplicitGridRowEnd -= numEmptyRows;
4938 // Adjust the track mapping to unmap the removed tracks.
4939 auto colRepeatCount = aState.mColFunctions.NumRepeatTracks();
4940 aState.mColFunctions.SetNumRepeatTracks(colRepeatCount - numEmptyCols);
4941 auto rowRepeatCount = aState.mRowFunctions.NumRepeatTracks();
4942 aState.mRowFunctions.SetNumRepeatTracks(rowRepeatCount - numEmptyRows);
4945 // Update the line boundaries of the implicit grid areas, if needed.
4946 if (mAreas && aState.mFrame->ShouldGenerateComputedInfo()) {
4947 for (auto iter = mAreas->iter(); !iter.done(); iter.next()) {
4948 auto& areaInfo = iter.get().value();
4950 // Resolve the lines for the area. We use the name of the area as the
4951 // name of the lines, knowing that the line placement algorithm will
4952 // add the -start and -end suffixes as appropriate for layout.
4953 StyleGridLine lineStartAndEnd;
4954 lineStartAndEnd.ident = areaInfo.name;
4956 LineRange columnLines =
4957 ResolveLineRange(lineStartAndEnd, lineStartAndEnd, colLineNameMap,
4958 eLogicalAxisInline, mExplicitGridColEnd, gridStyle);
4960 LineRange rowLines =
4961 ResolveLineRange(lineStartAndEnd, lineStartAndEnd, rowLineNameMap,
4962 eLogicalAxisBlock, mExplicitGridRowEnd, gridStyle);
4964 // Put the resolved line indices back into the area structure.
4965 areaInfo.columns.start = columnLines.mStart + mExplicitGridOffsetCol;
4966 areaInfo.columns.end = columnLines.mEnd + mExplicitGridOffsetCol;
4967 areaInfo.rows.start = rowLines.mStart + mExplicitGridOffsetRow;
4968 areaInfo.rows.end = rowLines.mEnd + mExplicitGridOffsetRow;
4973 void nsGridContainerFrame::Tracks::Initialize(
4974 const TrackSizingFunctions& aFunctions,
4975 const NonNegativeLengthPercentageOrNormal& aGridGap, uint32_t aNumTracks,
4976 nscoord aContentBoxSize) {
4977 mSizes.SetLength(aNumTracks);
4978 PodZero(mSizes.Elements(), mSizes.Length());
4979 for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) {
4980 auto& sz = mSizes[i];
4981 mStateUnion |= sz.Initialize(aContentBoxSize, aFunctions.SizingFor(i));
4982 if (mIsMasonry) {
4983 sz.mBase = aContentBoxSize;
4984 sz.mLimit = aContentBoxSize;
4987 mGridGap = nsLayoutUtils::ResolveGapToLength(aGridGap, aContentBoxSize);
4988 mContentBoxSize = aContentBoxSize;
4992 * Reflow aChild in the given aAvailableSize.
4994 static nscoord MeasuringReflow(nsIFrame* aChild,
4995 const ReflowInput* aReflowInput, gfxContext* aRC,
4996 const LogicalSize& aAvailableSize,
4997 const LogicalSize& aCBSize,
4998 nscoord aIMinSizeClamp = NS_MAXSIZE,
4999 nscoord aBMinSizeClamp = NS_MAXSIZE) {
5000 nsContainerFrame* parent = aChild->GetParent();
5001 nsPresContext* pc = aChild->PresContext();
5002 Maybe<ReflowInput> dummyParentState;
5003 const ReflowInput* rs = aReflowInput;
5004 if (!aReflowInput) {
5005 MOZ_ASSERT(!parent->HasAnyStateBits(NS_FRAME_IN_REFLOW));
5006 dummyParentState.emplace(
5007 pc, parent, aRC,
5008 LogicalSize(parent->GetWritingMode(), 0, NS_UNCONSTRAINEDSIZE),
5009 ReflowInput::InitFlag::DummyParentReflowInput);
5010 rs = dummyParentState.ptr();
5012 #ifdef DEBUG
5013 // This will suppress various ABSURD_SIZE warnings for this reflow.
5014 parent->SetProperty(nsContainerFrame::DebugReflowingWithInfiniteISize(),
5015 true);
5016 #endif
5017 auto wm = aChild->GetWritingMode();
5018 ComputeSizeFlags csFlags = ComputeSizeFlag::IsGridMeasuringReflow;
5019 if (aAvailableSize.ISize(wm) == INFINITE_ISIZE_COORD) {
5020 csFlags += ComputeSizeFlag::ShrinkWrap;
5022 if (aIMinSizeClamp != NS_MAXSIZE) {
5023 csFlags += ComputeSizeFlag::IClampMarginBoxMinSize;
5025 if (aBMinSizeClamp != NS_MAXSIZE) {
5026 csFlags += ComputeSizeFlag::BClampMarginBoxMinSize;
5027 aChild->SetProperty(nsIFrame::BClampMarginBoxMinSizeProperty(),
5028 aBMinSizeClamp);
5029 } else {
5030 aChild->RemoveProperty(nsIFrame::BClampMarginBoxMinSizeProperty());
5032 ReflowInput childRI(pc, *rs, aChild, aAvailableSize, Some(aCBSize), {}, {},
5033 csFlags);
5035 // FIXME (perf): It would be faster to do this only if the previous reflow of
5036 // the child was not a measuring reflow, and only if the child does some of
5037 // the things that are affected by ComputeSizeFlag::IsGridMeasuringReflow.
5038 childRI.SetBResize(true);
5039 // Not 100% sure this is needed, but be conservative for now:
5040 childRI.mFlags.mIsBResizeForPercentages = true;
5042 ReflowOutput childSize(childRI);
5043 nsReflowStatus childStatus;
5044 const nsIFrame::ReflowChildFlags flags =
5045 nsIFrame::ReflowChildFlags::NoMoveFrame |
5046 nsIFrame::ReflowChildFlags::NoSizeView |
5047 nsIFrame::ReflowChildFlags::NoDeleteNextInFlowChild;
5049 bool found;
5050 GridItemCachedBAxisMeasurement cachedMeasurement =
5051 aChild->GetProperty(GridItemCachedBAxisMeasurement::Prop(), &found);
5052 if (found && cachedMeasurement.IsValidFor(aChild, aCBSize)) {
5053 childSize.BSize(wm) = cachedMeasurement.BSize();
5054 childSize.ISize(wm) = aChild->ISize(wm);
5055 nsContainerFrame::FinishReflowChild(aChild, pc, childSize, &childRI, wm,
5056 LogicalPoint(wm), nsSize(), flags);
5057 GRID_LOG(
5058 "[perf] MeasuringReflow accepted cached value=%d, child=%p, "
5059 "aCBSize.ISize=%d",
5060 cachedMeasurement.BSize(), aChild,
5061 aCBSize.ISize(aChild->GetWritingMode()));
5062 return cachedMeasurement.BSize();
5065 parent->ReflowChild(aChild, pc, childSize, childRI, wm, LogicalPoint(wm),
5066 nsSize(), flags, childStatus);
5067 nsContainerFrame::FinishReflowChild(aChild, pc, childSize, &childRI, wm,
5068 LogicalPoint(wm), nsSize(), flags);
5069 #ifdef DEBUG
5070 parent->RemoveProperty(nsContainerFrame::DebugReflowingWithInfiniteISize());
5071 #endif
5072 if (!found &&
5073 GridItemCachedBAxisMeasurement::CanCacheMeasurement(aChild, aCBSize)) {
5074 GridItemCachedBAxisMeasurement cachedMeasurement(aChild, aCBSize,
5075 childSize.BSize(wm));
5076 aChild->SetProperty(GridItemCachedBAxisMeasurement::Prop(),
5077 cachedMeasurement);
5078 GRID_LOG(
5079 "[perf] MeasuringReflow created new cached value=%d, child=%p, "
5080 "aCBSize.ISize=%d",
5081 cachedMeasurement.BSize(), aChild,
5082 aCBSize.ISize(aChild->GetWritingMode()));
5083 } else if (found) {
5084 if (GridItemCachedBAxisMeasurement::CanCacheMeasurement(aChild, aCBSize)) {
5085 cachedMeasurement.Update(aChild, aCBSize, childSize.BSize(wm));
5086 GRID_LOG(
5087 "[perf] MeasuringReflow rejected but updated cached value=%d, "
5088 "child=%p, aCBSize.ISize=%d",
5089 cachedMeasurement.BSize(), aChild,
5090 aCBSize.ISize(aChild->GetWritingMode()));
5091 aChild->SetProperty(GridItemCachedBAxisMeasurement::Prop(),
5092 cachedMeasurement);
5093 } else {
5094 aChild->RemoveProperty(GridItemCachedBAxisMeasurement::Prop());
5095 GRID_LOG(
5096 "[perf] MeasuringReflow rejected and removed cached value, "
5097 "child=%p",
5098 aChild);
5102 return childSize.BSize(wm);
5106 * Reflow aChild in the given aAvailableSize, using aNewContentBoxSize as its
5107 * computed size in aChildAxis.
5109 static void PostReflowStretchChild(
5110 nsIFrame* aChild, const ReflowInput& aReflowInput,
5111 const LogicalSize& aAvailableSize, const LogicalSize& aCBSize,
5112 LogicalAxis aChildAxis, const nscoord aNewContentBoxSize,
5113 nscoord aIMinSizeClamp = NS_MAXSIZE, nscoord aBMinSizeClamp = NS_MAXSIZE) {
5114 nsPresContext* pc = aChild->PresContext();
5115 ComputeSizeFlags csFlags;
5116 if (aIMinSizeClamp != NS_MAXSIZE) {
5117 csFlags += ComputeSizeFlag::IClampMarginBoxMinSize;
5119 if (aBMinSizeClamp != NS_MAXSIZE) {
5120 csFlags += ComputeSizeFlag::BClampMarginBoxMinSize;
5121 aChild->SetProperty(nsIFrame::BClampMarginBoxMinSizeProperty(),
5122 aBMinSizeClamp);
5123 } else {
5124 aChild->RemoveProperty(nsIFrame::BClampMarginBoxMinSizeProperty());
5126 ReflowInput ri(pc, aReflowInput, aChild, aAvailableSize, Some(aCBSize), {},
5127 {}, csFlags);
5128 if (aChildAxis == eLogicalAxisBlock) {
5129 ri.SetComputedBSize(ri.ApplyMinMaxBSize(aNewContentBoxSize));
5130 } else {
5131 ri.SetComputedISize(ri.ApplyMinMaxISize(aNewContentBoxSize));
5133 ReflowOutput childSize(ri);
5134 nsReflowStatus childStatus;
5135 const nsIFrame::ReflowChildFlags flags =
5136 nsIFrame::ReflowChildFlags::NoMoveFrame |
5137 nsIFrame::ReflowChildFlags::NoDeleteNextInFlowChild;
5138 auto wm = aChild->GetWritingMode();
5139 nsContainerFrame* parent = aChild->GetParent();
5140 parent->ReflowChild(aChild, pc, childSize, ri, wm, LogicalPoint(wm), nsSize(),
5141 flags, childStatus);
5142 nsContainerFrame::FinishReflowChild(aChild, pc, childSize, &ri, wm,
5143 LogicalPoint(wm), nsSize(), flags);
5147 * Return the accumulated margin+border+padding in aAxis for aFrame (a subgrid)
5148 * and its ancestor subgrids.
5150 static LogicalMargin SubgridAccumulatedMarginBorderPadding(
5151 nsIFrame* aFrame, const Subgrid* aSubgrid, WritingMode aResultWM,
5152 LogicalAxis aAxis) {
5153 MOZ_ASSERT(aFrame->IsGridContainerFrame());
5154 auto* subgridFrame = static_cast<nsGridContainerFrame*>(aFrame);
5155 LogicalMargin result(aSubgrid->mMarginBorderPadding);
5156 auto* parent = subgridFrame->ParentGridContainerForSubgrid();
5157 auto subgridCBWM = parent->GetWritingMode();
5158 auto childRange = aSubgrid->mArea.LineRangeForAxis(aAxis);
5159 bool skipStartSide = false;
5160 bool skipEndSide = false;
5161 auto axis = aSubgrid->mIsOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis;
5162 // If aFrame's parent is also a subgrid, then add its MBP on the edges that
5163 // are adjacent (i.e. start or end in the same track), recursively.
5164 // ("parent" refers to the grid-frame we're currently adding MBP for,
5165 // and "grandParent" its parent, as we walk up the chain.)
5166 while (parent->IsSubgrid(axis)) {
5167 auto* parentSubgrid = parent->GetProperty(Subgrid::Prop());
5168 auto* grandParent = parent->ParentGridContainerForSubgrid();
5169 auto parentCBWM = grandParent->GetWritingMode();
5170 if (parentCBWM.IsOrthogonalTo(subgridCBWM)) {
5171 axis = GetOrthogonalAxis(axis);
5173 const auto& parentRange = parentSubgrid->mArea.LineRangeForAxis(axis);
5174 bool sameDir = parentCBWM.ParallelAxisStartsOnSameSide(axis, subgridCBWM);
5175 if (sameDir) {
5176 skipStartSide |= childRange.mStart != 0;
5177 skipEndSide |= childRange.mEnd != parentRange.Extent();
5178 } else {
5179 skipEndSide |= childRange.mStart != 0;
5180 skipStartSide |= childRange.mEnd != parentRange.Extent();
5182 if (skipStartSide && skipEndSide) {
5183 break;
5185 auto mbp =
5186 parentSubgrid->mMarginBorderPadding.ConvertTo(subgridCBWM, parentCBWM);
5187 if (skipStartSide) {
5188 mbp.Start(aAxis, subgridCBWM) = nscoord(0);
5190 if (skipEndSide) {
5191 mbp.End(aAxis, subgridCBWM) = nscoord(0);
5193 result += mbp;
5194 parent = grandParent;
5195 childRange = parentRange;
5197 return result.ConvertTo(aResultWM, subgridCBWM);
5201 * Return the [min|max]-content contribution of aChild to its parent (i.e.
5202 * the child's margin-box) in aAxis.
5204 static nscoord ContentContribution(
5205 const GridItemInfo& aGridItem, const GridReflowInput& aState,
5206 gfxContext* aRC, WritingMode aCBWM, LogicalAxis aAxis,
5207 const Maybe<LogicalSize>& aPercentageBasis, IntrinsicISizeType aConstraint,
5208 nscoord aMinSizeClamp = NS_MAXSIZE, uint32_t aFlags = 0) {
5209 nsIFrame* child = aGridItem.mFrame;
5211 nscoord extraMargin = 0;
5212 nsGridContainerFrame::Subgrid* subgrid = nullptr;
5213 if (child->GetParent() != aState.mFrame) {
5214 // |child| is a subgrid descendant, so it contributes its subgrids'
5215 // margin+border+padding for any edge tracks that it spans.
5216 auto* subgridFrame = child->GetParent();
5217 subgrid = subgridFrame->GetProperty(Subgrid::Prop());
5218 const auto itemEdgeBits = aGridItem.mState[aAxis] & ItemState::eEdgeBits;
5219 if (itemEdgeBits) {
5220 LogicalMargin mbp = SubgridAccumulatedMarginBorderPadding(
5221 subgridFrame, subgrid, aCBWM, aAxis);
5222 if (itemEdgeBits & ItemState::eStartEdge) {
5223 extraMargin += mbp.Start(aAxis, aCBWM);
5225 if (itemEdgeBits & ItemState::eEndEdge) {
5226 extraMargin += mbp.End(aAxis, aCBWM);
5229 // It also contributes (half of) the subgrid's gap on its edges (if any)
5230 // subtracted by the non-subgrid ancestor grid container's gap.
5231 // Note that this can also be negative since it's considered a margin.
5232 if (itemEdgeBits != ItemState::eEdgeBits) {
5233 auto subgridAxis = aCBWM.IsOrthogonalTo(subgridFrame->GetWritingMode())
5234 ? GetOrthogonalAxis(aAxis)
5235 : aAxis;
5236 auto& gapStyle = subgridAxis == eLogicalAxisBlock
5237 ? subgridFrame->StylePosition()->mRowGap
5238 : subgridFrame->StylePosition()->mColumnGap;
5239 if (!gapStyle.IsNormal()) {
5240 auto subgridExtent = subgridAxis == eLogicalAxisBlock
5241 ? subgrid->mGridRowEnd
5242 : subgrid->mGridColEnd;
5243 if (subgridExtent > 1) {
5244 nscoord subgridGap =
5245 nsLayoutUtils::ResolveGapToLength(gapStyle, NS_UNCONSTRAINEDSIZE);
5246 auto& tracks =
5247 aAxis == eLogicalAxisBlock ? aState.mRows : aState.mCols;
5248 auto gapDelta = subgridGap - tracks.mGridGap;
5249 if (!itemEdgeBits) {
5250 extraMargin += gapDelta;
5251 } else {
5252 extraMargin += gapDelta / 2;
5259 PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis));
5260 nscoord size = nsLayoutUtils::IntrinsicForAxis(
5261 axis, aRC, child, aConstraint, aPercentageBasis,
5262 aFlags | nsLayoutUtils::BAIL_IF_REFLOW_NEEDED, aMinSizeClamp);
5263 auto childWM = child->GetWritingMode();
5264 const bool isOrthogonal = childWM.IsOrthogonalTo(aCBWM);
5265 auto childAxis = isOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis;
5266 if (size == NS_INTRINSIC_ISIZE_UNKNOWN && childAxis == eLogicalAxisBlock) {
5267 // We need to reflow the child to find its BSize contribution.
5268 // XXX this will give mostly correct results for now (until bug 1174569).
5269 nscoord availISize = INFINITE_ISIZE_COORD;
5270 nscoord availBSize = NS_UNCONSTRAINEDSIZE;
5271 // The next two variables are MinSizeClamp values in the child's axes.
5272 nscoord iMinSizeClamp = NS_MAXSIZE;
5273 nscoord bMinSizeClamp = NS_MAXSIZE;
5274 LogicalSize cbSize(childWM, 0, NS_UNCONSTRAINEDSIZE);
5275 // Below, we try to resolve the child's grid-area size in its inline-axis
5276 // to use as the CB/Available size in the MeasuringReflow that follows.
5277 if (child->GetParent() != aState.mFrame) {
5278 // This item is a child of a subgrid descendant.
5279 auto* subgridFrame =
5280 static_cast<nsGridContainerFrame*>(child->GetParent());
5281 MOZ_ASSERT(subgridFrame->IsGridContainerFrame());
5282 auto* uts = subgridFrame->GetProperty(UsedTrackSizes::Prop());
5283 if (!uts) {
5284 uts = new UsedTrackSizes();
5285 subgridFrame->SetProperty(UsedTrackSizes::Prop(), uts);
5287 // The grid-item's inline-axis as expressed in the subgrid's WM.
5288 auto subgridAxis = childWM.IsOrthogonalTo(subgridFrame->GetWritingMode())
5289 ? eLogicalAxisBlock
5290 : eLogicalAxisInline;
5291 uts->ResolveTrackSizesForAxis(subgridFrame, subgridAxis, *aRC);
5292 if (uts->mCanResolveLineRangeSize[subgridAxis]) {
5293 auto* subgrid =
5294 subgridFrame->GetProperty(nsGridContainerFrame::Subgrid::Prop());
5295 const GridItemInfo* originalItem = nullptr;
5296 for (const auto& item : subgrid->mGridItems) {
5297 if (item.mFrame == child) {
5298 originalItem = &item;
5299 break;
5302 MOZ_ASSERT(originalItem, "huh?");
5303 const auto& range = originalItem->mArea.LineRangeForAxis(subgridAxis);
5304 nscoord pos, sz;
5305 range.ToPositionAndLength(uts->mSizes[subgridAxis], &pos, &sz);
5306 if (childWM.IsOrthogonalTo(subgridFrame->GetWritingMode())) {
5307 availBSize = sz;
5308 cbSize.BSize(childWM) = sz;
5309 if (aGridItem.mState[aAxis] & ItemState::eClampMarginBoxMinSize) {
5310 bMinSizeClamp = sz;
5312 } else {
5313 availISize = sz;
5314 cbSize.ISize(childWM) = sz;
5315 if (aGridItem.mState[aAxis] & ItemState::eClampMarginBoxMinSize) {
5316 iMinSizeClamp = sz;
5320 } else if (aState.mCols.mCanResolveLineRangeSize) {
5321 nscoord sz = aState.mCols.ResolveSize(aGridItem.mArea.mCols);
5322 if (isOrthogonal) {
5323 availBSize = sz;
5324 cbSize.BSize(childWM) = sz;
5325 if (aGridItem.mState[aAxis] & ItemState::eClampMarginBoxMinSize) {
5326 bMinSizeClamp = sz;
5328 } else {
5329 availISize = sz;
5330 cbSize.ISize(childWM) = sz;
5331 if (aGridItem.mState[aAxis] & ItemState::eClampMarginBoxMinSize) {
5332 iMinSizeClamp = sz;
5336 if (isOrthogonal == (aAxis == eLogicalAxisInline)) {
5337 bMinSizeClamp = aMinSizeClamp;
5338 } else {
5339 iMinSizeClamp = aMinSizeClamp;
5341 LogicalSize availableSize(childWM, availISize, availBSize);
5342 size = ::MeasuringReflow(child, aState.mReflowInput, aRC, availableSize,
5343 cbSize, iMinSizeClamp, bMinSizeClamp);
5344 size += child->GetLogicalUsedMargin(childWM).BStartEnd(childWM);
5345 nscoord overflow = size - aMinSizeClamp;
5346 if (MOZ_UNLIKELY(overflow > 0)) {
5347 nscoord contentSize = child->ContentBSize(childWM);
5348 nscoord newContentSize = std::max(nscoord(0), contentSize - overflow);
5349 // XXXmats deal with percentages better, see bug 1300369 comment 27.
5350 size -= contentSize - newContentSize;
5353 MOZ_ASSERT(aGridItem.mBaselineOffset[aAxis] >= 0,
5354 "baseline offset should be non-negative at this point");
5355 MOZ_ASSERT((aGridItem.mState[aAxis] & ItemState::eIsBaselineAligned) ||
5356 aGridItem.mBaselineOffset[aAxis] == nscoord(0),
5357 "baseline offset should be zero when not baseline-aligned");
5358 size += aGridItem.mBaselineOffset[aAxis];
5359 size += extraMargin;
5360 return std::max(size, 0);
5363 struct CachedIntrinsicSizes {
5364 Maybe<nscoord> mMinSize;
5365 Maybe<nscoord> mMinContentContribution;
5366 Maybe<nscoord> mMaxContentContribution;
5368 // The item's percentage basis for intrinsic sizing purposes.
5369 Maybe<LogicalSize> mPercentageBasis;
5371 // "if the grid item spans only grid tracks that have a fixed max track
5372 // sizing function, its automatic minimum size in that dimension is
5373 // further clamped to less than or equal to the size necessary to fit its
5374 // margin box within the resulting grid area (flooring at zero)"
5375 // https://drafts.csswg.org/css-grid/#min-size-auto
5376 // This is the clamp value to use for that:
5377 nscoord mMinSizeClamp = NS_MAXSIZE;
5380 static nscoord MinContentContribution(const GridItemInfo& aGridItem,
5381 const GridReflowInput& aState,
5382 gfxContext* aRC, WritingMode aCBWM,
5383 LogicalAxis aAxis,
5384 CachedIntrinsicSizes* aCache) {
5385 if (aCache->mMinContentContribution.isSome()) {
5386 return aCache->mMinContentContribution.value();
5388 if (aCache->mPercentageBasis.isNothing()) {
5389 aCache->mPercentageBasis.emplace(
5390 aState.PercentageBasisFor(aAxis, aGridItem));
5392 nscoord s = ContentContribution(
5393 aGridItem, aState, aRC, aCBWM, aAxis, aCache->mPercentageBasis,
5394 IntrinsicISizeType::MinISize, aCache->mMinSizeClamp);
5395 aCache->mMinContentContribution.emplace(s);
5396 return s;
5399 static nscoord MaxContentContribution(const GridItemInfo& aGridItem,
5400 const GridReflowInput& aState,
5401 gfxContext* aRC, WritingMode aCBWM,
5402 LogicalAxis aAxis,
5403 CachedIntrinsicSizes* aCache) {
5404 if (aCache->mMaxContentContribution.isSome()) {
5405 return aCache->mMaxContentContribution.value();
5407 if (aCache->mPercentageBasis.isNothing()) {
5408 aCache->mPercentageBasis.emplace(
5409 aState.PercentageBasisFor(aAxis, aGridItem));
5411 nscoord s = ContentContribution(
5412 aGridItem, aState, aRC, aCBWM, aAxis, aCache->mPercentageBasis,
5413 IntrinsicISizeType::PrefISize, aCache->mMinSizeClamp);
5414 aCache->mMaxContentContribution.emplace(s);
5415 return s;
5418 // Computes the min-size contribution for a grid item, as defined at
5419 // https://drafts.csswg.org/css-grid/#min-size-contribution
5420 static nscoord MinSize(const GridItemInfo& aGridItem,
5421 const GridReflowInput& aState, gfxContext* aRC,
5422 WritingMode aCBWM, LogicalAxis aAxis,
5423 CachedIntrinsicSizes* aCache) {
5424 if (aCache->mMinSize.isSome()) {
5425 return aCache->mMinSize.value();
5427 nsIFrame* child = aGridItem.mFrame;
5428 PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis));
5429 const nsStylePosition* stylePos = child->StylePosition();
5430 StyleSize sizeStyle =
5431 axis == eAxisHorizontal ? stylePos->mWidth : stylePos->mHeight;
5433 auto ourInlineAxis = child->GetWritingMode().PhysicalAxis(eLogicalAxisInline);
5434 // max-content and min-content should behave as initial value in block axis.
5435 // FIXME: Bug 567039: moz-fit-content and -moz-available are not supported
5436 // for block size dimension on sizing properties (e.g. height), so we
5437 // treat it as `auto`.
5438 if (axis != ourInlineAxis && sizeStyle.BehavesLikeInitialValueOnBlockAxis()) {
5439 sizeStyle = StyleSize::Auto();
5442 if (!sizeStyle.IsAuto() && !sizeStyle.HasPercent()) {
5443 nscoord s =
5444 MinContentContribution(aGridItem, aState, aRC, aCBWM, aAxis, aCache);
5445 aCache->mMinSize.emplace(s);
5446 return s;
5449 if (aCache->mPercentageBasis.isNothing()) {
5450 aCache->mPercentageBasis.emplace(
5451 aState.PercentageBasisFor(aAxis, aGridItem));
5454 // https://drafts.csswg.org/css-grid/#min-size-auto
5455 // This calculates the min-content contribution from either a definite
5456 // min-width (or min-height depending on aAxis), or the "specified /
5457 // transferred size" for min-width:auto if overflow == visible (as min-width:0
5458 // otherwise), or NS_UNCONSTRAINEDSIZE for other min-width intrinsic values
5459 // (which results in always taking the "content size" part below).
5460 MOZ_ASSERT(aGridItem.mBaselineOffset[aAxis] >= 0,
5461 "baseline offset should be non-negative at this point");
5462 MOZ_ASSERT((aGridItem.mState[aAxis] & ItemState::eIsBaselineAligned) ||
5463 aGridItem.mBaselineOffset[aAxis] == nscoord(0),
5464 "baseline offset should be zero when not baseline-aligned");
5465 nscoord sz = aGridItem.mBaselineOffset[aAxis] +
5466 nsLayoutUtils::MinSizeContributionForAxis(
5467 axis, aRC, child, IntrinsicISizeType::MinISize,
5468 *aCache->mPercentageBasis);
5469 const StyleSize& style =
5470 axis == eAxisHorizontal ? stylePos->mMinWidth : stylePos->mMinHeight;
5471 // max-content and min-content should behave as initial value in block axis.
5472 // FIXME: Bug 567039: moz-fit-content and -moz-available are not supported
5473 // for block size dimension on sizing properties (e.g. height), so we
5474 // treat it as `auto`.
5475 const bool inInlineAxis = axis == ourInlineAxis;
5476 const bool isAuto =
5477 style.IsAuto() ||
5478 (!inInlineAxis && style.BehavesLikeInitialValueOnBlockAxis());
5479 if ((inInlineAxis && nsIFrame::ToExtremumLength(style)) ||
5480 (isAuto && child->StyleDisplay()->mOverflowX == StyleOverflow::Visible)) {
5481 // Now calculate the "content size" part and return whichever is smaller.
5482 MOZ_ASSERT(isAuto || sz == NS_UNCONSTRAINEDSIZE);
5483 sz = std::min(sz, ContentContribution(aGridItem, aState, aRC, aCBWM, aAxis,
5484 aCache->mPercentageBasis,
5485 IntrinsicISizeType::MinISize,
5486 aCache->mMinSizeClamp,
5487 nsLayoutUtils::MIN_INTRINSIC_ISIZE));
5489 aCache->mMinSize.emplace(sz);
5490 return sz;
5493 void nsGridContainerFrame::Tracks::CalculateSizes(
5494 GridReflowInput& aState, nsTArray<GridItemInfo>& aGridItems,
5495 const TrackSizingFunctions& aFunctions, nscoord aContentBoxSize,
5496 LineRange GridArea::*aRange, SizingConstraint aConstraint) {
5497 nscoord percentageBasis = aContentBoxSize;
5498 if (percentageBasis == NS_UNCONSTRAINEDSIZE) {
5499 percentageBasis = 0;
5501 InitializeItemBaselines(aState, aGridItems);
5502 ResolveIntrinsicSize(aState, aGridItems, aFunctions, aRange, percentageBasis,
5503 aConstraint);
5504 if (aConstraint != SizingConstraint::MinContent) {
5505 nscoord freeSpace = aContentBoxSize;
5506 if (freeSpace != NS_UNCONSTRAINEDSIZE) {
5507 freeSpace -= SumOfGridGaps();
5509 DistributeFreeSpace(freeSpace);
5510 StretchFlexibleTracks(aState, aGridItems, aFunctions, freeSpace);
5514 TrackSize::StateBits nsGridContainerFrame::Tracks::StateBitsForRange(
5515 const LineRange& aRange) const {
5516 MOZ_ASSERT(!aRange.IsAuto(), "must have a definite range");
5517 TrackSize::StateBits state = TrackSize::StateBits{0};
5518 for (auto i : aRange.Range()) {
5519 state |= mSizes[i].mState;
5521 return state;
5524 static void AddSubgridContribution(TrackSize& aSize,
5525 nscoord aMarginBorderPadding) {
5526 if (aSize.mState & TrackSize::eIntrinsicMinSizing) {
5527 aSize.mBase = std::max(aSize.mBase, aMarginBorderPadding);
5528 aSize.mLimit = std::max(aSize.mLimit, aSize.mBase);
5530 // XXX maybe eFlexMaxSizing too?
5531 // (once we implement https://github.com/w3c/csswg-drafts/issues/2177)
5532 if (aSize.mState &
5533 (TrackSize::eIntrinsicMaxSizing | TrackSize::eFitContent)) {
5534 aSize.mLimit = std::max(aSize.mLimit, aMarginBorderPadding);
5538 bool nsGridContainerFrame::Tracks::ResolveIntrinsicSizeForNonSpanningItems(
5539 GridReflowInput& aState, const TrackSizingFunctions& aFunctions,
5540 nscoord aPercentageBasis, SizingConstraint aConstraint,
5541 const LineRange& aRange, const GridItemInfo& aGridItem) {
5542 gfxContext* rc = &aState.mRenderingContext;
5543 WritingMode wm = aState.mWM;
5544 CachedIntrinsicSizes cache;
5545 TrackSize& sz = mSizes[aRange.mStart];
5547 // min sizing
5548 if (sz.mState & TrackSize::eAutoMinSizing) {
5549 nscoord s;
5550 // Check if we need to apply "Automatic Minimum Size" and cache it.
5551 if (aGridItem.ShouldApplyAutoMinSize(wm, mAxis, aPercentageBasis)) {
5552 aGridItem.mState[mAxis] |= ItemState::eApplyAutoMinSize;
5553 // Clamp it if it's spanning a definite track max-sizing function.
5554 if (TrackSize::IsDefiniteMaxSizing(sz.mState)) {
5555 cache.mMinSizeClamp = aFunctions.MaxSizingFor(aRange.mStart)
5556 .AsBreadth()
5557 .Resolve(aPercentageBasis);
5558 aGridItem.mState[mAxis] |= ItemState::eClampMarginBoxMinSize;
5560 if (aConstraint != SizingConstraint::MaxContent) {
5561 s = MinContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
5562 } else {
5563 s = MaxContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
5565 } else {
5566 s = MinSize(aGridItem, aState, rc, wm, mAxis, &cache);
5568 sz.mBase = std::max(sz.mBase, s);
5569 } else if (sz.mState & TrackSize::eMinContentMinSizing) {
5570 auto s = MinContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
5571 sz.mBase = std::max(sz.mBase, s);
5572 } else if (sz.mState & TrackSize::eMaxContentMinSizing) {
5573 auto s = MaxContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
5574 sz.mBase = std::max(sz.mBase, s);
5577 // max sizing
5578 if (sz.mState & TrackSize::eMinContentMaxSizing) {
5579 auto s = MinContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
5580 if (sz.mLimit == NS_UNCONSTRAINEDSIZE) {
5581 sz.mLimit = s;
5582 } else {
5583 sz.mLimit = std::max(sz.mLimit, s);
5585 } else if (sz.mState &
5586 (TrackSize::eAutoMaxSizing | TrackSize::eMaxContentMaxSizing)) {
5587 auto s = MaxContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
5588 if (sz.mLimit == NS_UNCONSTRAINEDSIZE) {
5589 sz.mLimit = s;
5590 } else {
5591 sz.mLimit = std::max(sz.mLimit, s);
5593 if (MOZ_UNLIKELY(sz.mState & TrackSize::eFitContent)) {
5594 // Clamp mLimit to the fit-content() size, for §12.5.1.
5595 nscoord fitContentClamp = aFunctions.SizingFor(aRange.mStart)
5596 .AsFitContent()
5597 .AsBreadth()
5598 .Resolve(aPercentageBasis);
5599 sz.mLimit = std::min(sz.mLimit, fitContentClamp);
5603 if (sz.mLimit < sz.mBase) {
5604 sz.mLimit = sz.mBase;
5607 return sz.mState & TrackSize::eFlexMaxSizing;
5610 void nsGridContainerFrame::Tracks::CalculateItemBaselines(
5611 nsTArray<ItemBaselineData>& aBaselineItems,
5612 BaselineSharingGroup aBaselineGroup) {
5613 if (aBaselineItems.IsEmpty()) {
5614 return;
5617 // Sort the collected items on their baseline track.
5618 std::sort(aBaselineItems.begin(), aBaselineItems.end(),
5619 ItemBaselineData::IsBaselineTrackLessThan);
5621 MOZ_ASSERT(mSizes.Length() > 0, "having an item implies at least one track");
5622 const uint32_t lastTrack = mSizes.Length() - 1;
5623 nscoord maxBaseline = 0;
5624 nscoord maxDescent = 0;
5625 uint32_t currentTrack = kAutoLine; // guaranteed to not match any item
5626 uint32_t trackStartIndex = 0;
5627 for (uint32_t i = 0, len = aBaselineItems.Length(); true; ++i) {
5628 // Find the maximum baseline and descent in the current track.
5629 if (i != len) {
5630 const ItemBaselineData& item = aBaselineItems[i];
5631 if (currentTrack == item.mBaselineTrack) {
5632 maxBaseline = std::max(maxBaseline, item.mBaseline);
5633 maxDescent = std::max(maxDescent, item.mSize - item.mBaseline);
5634 continue;
5637 // Iterate the current track again and update the baseline offsets making
5638 // all items baseline-aligned within this group in this track.
5639 for (uint32_t j = trackStartIndex; j < i; ++j) {
5640 const ItemBaselineData& item = aBaselineItems[j];
5641 item.mGridItem->mBaselineOffset[mAxis] = maxBaseline - item.mBaseline;
5642 MOZ_ASSERT(item.mGridItem->mBaselineOffset[mAxis] >= 0);
5644 if (i != 0) {
5645 // Store the size of this baseline-aligned subtree.
5646 mSizes[currentTrack].mBaselineSubtreeSize[aBaselineGroup] =
5647 maxBaseline + maxDescent;
5648 // Record the first(last) baseline for the first(last) track.
5649 if (currentTrack == 0 && aBaselineGroup == BaselineSharingGroup::First) {
5650 mBaseline[aBaselineGroup] = maxBaseline;
5652 if (currentTrack == lastTrack &&
5653 aBaselineGroup == BaselineSharingGroup::Last) {
5654 mBaseline[aBaselineGroup] = maxBaseline;
5657 if (i == len) {
5658 break;
5660 // Initialize data for the next track with baseline-aligned items.
5661 const ItemBaselineData& item = aBaselineItems[i];
5662 currentTrack = item.mBaselineTrack;
5663 trackStartIndex = i;
5664 maxBaseline = item.mBaseline;
5665 maxDescent = item.mSize - item.mBaseline;
5669 void nsGridContainerFrame::Tracks::InitializeItemBaselines(
5670 GridReflowInput& aState, nsTArray<GridItemInfo>& aGridItems) {
5671 MOZ_ASSERT(!mIsMasonry);
5672 if (aState.mFrame->IsSubgrid(mAxis)) {
5673 // A grid container's subgridded axis doesn't have a baseline.
5674 return;
5676 nsTArray<ItemBaselineData> firstBaselineItems;
5677 nsTArray<ItemBaselineData> lastBaselineItems;
5678 WritingMode wm = aState.mWM;
5679 ComputedStyle* containerSC = aState.mFrame->Style();
5680 for (GridItemInfo& gridItem : aGridItems) {
5681 if (gridItem.IsSubgrid(mAxis)) {
5682 // A subgrid itself is never baseline-aligned.
5683 continue;
5685 nsIFrame* child = gridItem.mFrame;
5686 uint32_t baselineTrack = kAutoLine;
5687 auto state = ItemState(0);
5688 auto childWM = child->GetWritingMode();
5689 const bool isOrthogonal = wm.IsOrthogonalTo(childWM);
5690 const bool isInlineAxis = mAxis == eLogicalAxisInline; // i.e. columns
5691 // XXX update the line below to include orthogonal grid/table boxes
5692 // XXX since they have baselines in both dimensions. And flexbox with
5693 // XXX reversed main/cross axis?
5694 const bool itemHasBaselineParallelToTrack = isInlineAxis == isOrthogonal;
5695 if (itemHasBaselineParallelToTrack) {
5696 // [align|justify]-self:[last ]baseline.
5697 auto selfAlignment =
5698 isOrthogonal ? child->StylePosition()->UsedJustifySelf(containerSC)._0
5699 : child->StylePosition()->UsedAlignSelf(containerSC)._0;
5700 selfAlignment &= ~StyleAlignFlags::FLAG_BITS;
5701 if (selfAlignment == StyleAlignFlags::BASELINE) {
5702 state |= ItemState::eFirstBaseline | ItemState::eSelfBaseline;
5703 const GridArea& area = gridItem.mArea;
5704 baselineTrack = isInlineAxis ? area.mCols.mStart : area.mRows.mStart;
5705 } else if (selfAlignment == StyleAlignFlags::LAST_BASELINE) {
5706 state |= ItemState::eLastBaseline | ItemState::eSelfBaseline;
5707 const GridArea& area = gridItem.mArea;
5708 baselineTrack = (isInlineAxis ? area.mCols.mEnd : area.mRows.mEnd) - 1;
5711 // [align|justify]-content:[last ]baseline.
5712 // https://drafts.csswg.org/css-align-3/#baseline-align-content
5713 // "[...] and its computed 'align-self' or 'justify-self' (whichever
5714 // affects its block axis) is 'stretch' or 'self-start' ('self-end').
5715 // For this purpose, the 'start', 'end', 'flex-start', and 'flex-end'
5716 // values of 'align-self' are treated as either 'self-start' or
5717 // 'self-end', whichever they end up equivalent to.
5718 auto alignContent = child->StylePosition()->mAlignContent.primary;
5719 alignContent &= ~StyleAlignFlags::FLAG_BITS;
5720 if (alignContent == StyleAlignFlags::BASELINE ||
5721 alignContent == StyleAlignFlags::LAST_BASELINE) {
5722 const auto selfAlignEdge = alignContent == StyleAlignFlags::BASELINE
5723 ? StyleAlignFlags::SELF_START
5724 : StyleAlignFlags::SELF_END;
5725 bool validCombo = selfAlignment == StyleAlignFlags::NORMAL ||
5726 selfAlignment == StyleAlignFlags::STRETCH ||
5727 selfAlignment == selfAlignEdge;
5728 if (!validCombo) {
5729 // We're doing alignment in the axis that's orthogonal to mAxis here.
5730 LogicalAxis alignAxis = GetOrthogonalAxis(mAxis);
5731 // |sameSide| is true if the container's start side in this axis is
5732 // the same as the child's start side, in the child's parallel axis.
5733 bool sameSide = wm.ParallelAxisStartsOnSameSide(alignAxis, childWM);
5734 if (selfAlignment == StyleAlignFlags::LEFT) {
5735 selfAlignment = !isInlineAxis || wm.IsBidiLTR()
5736 ? StyleAlignFlags::START
5737 : StyleAlignFlags::END;
5738 } else if (selfAlignment == StyleAlignFlags::RIGHT) {
5739 selfAlignment = isInlineAxis && wm.IsBidiLTR()
5740 ? StyleAlignFlags::END
5741 : StyleAlignFlags::START;
5744 if (selfAlignment == StyleAlignFlags::START ||
5745 selfAlignment == StyleAlignFlags::FLEX_START) {
5746 validCombo =
5747 sameSide == (alignContent == StyleAlignFlags::BASELINE);
5748 } else if (selfAlignment == StyleAlignFlags::END ||
5749 selfAlignment == StyleAlignFlags::FLEX_END) {
5750 validCombo =
5751 sameSide == (alignContent == StyleAlignFlags::LAST_BASELINE);
5754 if (validCombo) {
5755 const GridArea& area = gridItem.mArea;
5756 if (alignContent == StyleAlignFlags::BASELINE) {
5757 state |= ItemState::eFirstBaseline | ItemState::eContentBaseline;
5758 baselineTrack =
5759 isInlineAxis ? area.mCols.mStart : area.mRows.mStart;
5760 } else if (alignContent == StyleAlignFlags::LAST_BASELINE) {
5761 state |= ItemState::eLastBaseline | ItemState::eContentBaseline;
5762 baselineTrack =
5763 (isInlineAxis ? area.mCols.mEnd : area.mRows.mEnd) - 1;
5769 if (state & ItemState::eIsBaselineAligned) {
5770 // XXXmats if |child| is a descendant of a subgrid then the metrics
5771 // below needs to account for the accumulated MPB somehow...
5773 // XXX available size issue
5774 LogicalSize avail(childWM, INFINITE_ISIZE_COORD, NS_UNCONSTRAINEDSIZE);
5775 auto* rc = &aState.mRenderingContext;
5776 // XXX figure out if we can avoid/merge this reflow with the main reflow.
5777 // XXX (after bug 1174569 is sorted out)
5779 // XXX How should we handle percentage padding here? (bug 1330866)
5780 // XXX (see ::ContentContribution and how it deals with percentages)
5781 // XXX What if the true baseline after line-breaking differs from this
5782 // XXX hypothetical baseline based on an infinite inline size?
5783 // XXX Maybe we should just call ::ContentContribution here instead?
5784 // XXX For now we just pass an unconstrined-bsize CB:
5785 LogicalSize cbSize(childWM, 0, NS_UNCONSTRAINEDSIZE);
5786 ::MeasuringReflow(child, aState.mReflowInput, rc, avail, cbSize);
5787 nscoord baseline;
5788 nsGridContainerFrame* grid = do_QueryFrame(child);
5789 if (state & ItemState::eFirstBaseline) {
5790 if (grid) {
5791 if (isOrthogonal == isInlineAxis) {
5792 baseline = grid->GetBBaseline(BaselineSharingGroup::First);
5793 } else {
5794 baseline = grid->GetIBaseline(BaselineSharingGroup::First);
5797 if (grid || nsLayoutUtils::GetFirstLineBaseline(wm, child, &baseline)) {
5798 NS_ASSERTION(baseline != NS_INTRINSIC_ISIZE_UNKNOWN,
5799 "about to use an unknown baseline");
5800 auto frameSize = isInlineAxis ? child->ISize(wm) : child->BSize(wm);
5801 auto m = child->GetLogicalUsedMargin(wm);
5802 baseline += isInlineAxis ? m.IStart(wm) : m.BStart(wm);
5803 auto alignSize =
5804 frameSize + (isInlineAxis ? m.IStartEnd(wm) : m.BStartEnd(wm));
5805 firstBaselineItems.AppendElement(ItemBaselineData(
5806 {baselineTrack, baseline, alignSize, &gridItem}));
5807 } else {
5808 state &= ~ItemState::eAllBaselineBits;
5810 } else {
5811 if (grid) {
5812 if (isOrthogonal == isInlineAxis) {
5813 baseline = grid->GetBBaseline(BaselineSharingGroup::Last);
5814 } else {
5815 baseline = grid->GetIBaseline(BaselineSharingGroup::Last);
5818 if (grid || nsLayoutUtils::GetLastLineBaseline(wm, child, &baseline)) {
5819 NS_ASSERTION(baseline != NS_INTRINSIC_ISIZE_UNKNOWN,
5820 "about to use an unknown baseline");
5821 auto frameSize = isInlineAxis ? child->ISize(wm) : child->BSize(wm);
5822 auto m = child->GetLogicalUsedMargin(wm);
5823 if (!grid) {
5824 // Convert to distance from border-box end.
5825 baseline = frameSize - baseline;
5827 auto descent = baseline + (isInlineAxis ? m.IEnd(wm) : m.BEnd(wm));
5828 auto alignSize =
5829 frameSize + (isInlineAxis ? m.IStartEnd(wm) : m.BStartEnd(wm));
5830 lastBaselineItems.AppendElement(
5831 ItemBaselineData({baselineTrack, descent, alignSize, &gridItem}));
5832 state |= ItemState::eEndSideBaseline;
5833 } else {
5834 state &= ~ItemState::eAllBaselineBits;
5838 MOZ_ASSERT(
5839 (state & (ItemState::eFirstBaseline | ItemState::eLastBaseline)) !=
5840 (ItemState::eFirstBaseline | ItemState::eLastBaseline),
5841 "first/last baseline bits are mutually exclusive");
5842 MOZ_ASSERT(
5843 (state & (ItemState::eSelfBaseline | ItemState::eContentBaseline)) !=
5844 (ItemState::eSelfBaseline | ItemState::eContentBaseline),
5845 "*-self and *-content baseline bits are mutually exclusive");
5846 MOZ_ASSERT(
5847 !(state & (ItemState::eFirstBaseline | ItemState::eLastBaseline)) ==
5848 !(state & (ItemState::eSelfBaseline | ItemState::eContentBaseline)),
5849 "first/last bit requires self/content bit and vice versa");
5850 gridItem.mState[mAxis] |= state;
5851 gridItem.mBaselineOffset[mAxis] = nscoord(0);
5854 if (firstBaselineItems.IsEmpty() && lastBaselineItems.IsEmpty()) {
5855 return;
5858 // TODO: CSS Align spec issue - how to align a baseline subtree in a track?
5859 // https://lists.w3.org/Archives/Public/www-style/2016May/0141.html
5860 mBaselineSubtreeAlign[BaselineSharingGroup::First] = StyleAlignFlags::START;
5861 mBaselineSubtreeAlign[BaselineSharingGroup::Last] = StyleAlignFlags::END;
5863 CalculateItemBaselines(firstBaselineItems, BaselineSharingGroup::First);
5864 CalculateItemBaselines(lastBaselineItems, BaselineSharingGroup::Last);
5867 // TODO: we store the wrong baseline group offset in some cases (bug 1632200)
5868 void nsGridContainerFrame::Tracks::InitializeItemBaselinesInMasonryAxis(
5869 GridReflowInput& aState, nsTArray<GridItemInfo>& aGridItems,
5870 BaselineAlignmentSet aSet, const nsSize& aContainerSize,
5871 nsTArray<nscoord>& aTrackSizes,
5872 nsTArray<ItemBaselineData>& aFirstBaselineItems,
5873 nsTArray<ItemBaselineData>& aLastBaselineItems) {
5874 MOZ_ASSERT(mIsMasonry);
5875 WritingMode wm = aState.mWM;
5876 ComputedStyle* containerSC = aState.mFrame->Style();
5877 for (GridItemInfo& gridItem : aGridItems) {
5878 if (gridItem.IsSubgrid(mAxis)) {
5879 // A subgrid itself is never baseline-aligned.
5880 continue;
5882 const auto& area = gridItem.mArea;
5883 if (aSet.mItemSet == BaselineAlignmentSet::LastItems) {
5884 // NOTE: eIsLastItemInMasonryTrack is set also if the item is the ONLY
5885 // item in its track; the eIsBaselineAligned check excludes it though
5886 // since it participates in the start baseline groups in that case.
5888 // XXX what if it's the only item in THAT baseline group?
5889 // XXX should it participate in the last-item group instead then
5890 // if there are more baseline-aligned items there?
5891 if (!(gridItem.mState[mAxis] & ItemState::eIsLastItemInMasonryTrack) ||
5892 (gridItem.mState[mAxis] & ItemState::eIsBaselineAligned)) {
5893 continue;
5895 } else {
5896 if (area.LineRangeForAxis(mAxis).mStart > 0 ||
5897 (gridItem.mState[mAxis] & ItemState::eIsBaselineAligned)) {
5898 continue;
5901 auto trackAlign =
5902 aState.mGridStyle
5903 ->UsedTracksAlignment(
5904 mAxis, area.LineRangeForAxis(GetOrthogonalAxis(mAxis)).mStart)
5905 .primary;
5906 if (!aSet.MatchTrackAlignment(trackAlign)) {
5907 continue;
5910 nsIFrame* child = gridItem.mFrame;
5911 uint32_t baselineTrack = kAutoLine;
5912 auto state = ItemState(0);
5913 auto childWM = child->GetWritingMode();
5914 const bool isOrthogonal = wm.IsOrthogonalTo(childWM);
5915 const bool isInlineAxis = mAxis == eLogicalAxisInline; // i.e. columns
5916 // XXX update the line below to include orthogonal grid/table boxes
5917 // XXX since they have baselines in both dimensions. And flexbox with
5918 // XXX reversed main/cross axis?
5919 const bool itemHasBaselineParallelToTrack = isInlineAxis == isOrthogonal;
5920 if (itemHasBaselineParallelToTrack) {
5921 const auto* pos = child->StylePosition();
5922 // [align|justify]-self:[last ]baseline.
5923 auto selfAlignment = pos->UsedSelfAlignment(mAxis, containerSC);
5924 selfAlignment &= ~StyleAlignFlags::FLAG_BITS;
5925 if (selfAlignment == StyleAlignFlags::BASELINE) {
5926 state |= ItemState::eFirstBaseline | ItemState::eSelfBaseline;
5927 baselineTrack = isInlineAxis ? area.mCols.mStart : area.mRows.mStart;
5928 } else if (selfAlignment == StyleAlignFlags::LAST_BASELINE) {
5929 state |= ItemState::eLastBaseline | ItemState::eSelfBaseline;
5930 baselineTrack = (isInlineAxis ? area.mCols.mEnd : area.mRows.mEnd) - 1;
5931 } else {
5932 // [align|justify]-content:[last ]baseline.
5933 auto childAxis = isOrthogonal ? GetOrthogonalAxis(mAxis) : mAxis;
5934 auto alignContent = pos->UsedContentAlignment(childAxis).primary;
5935 alignContent &= ~StyleAlignFlags::FLAG_BITS;
5936 if (alignContent == StyleAlignFlags::BASELINE) {
5937 state |= ItemState::eFirstBaseline | ItemState::eContentBaseline;
5938 baselineTrack = isInlineAxis ? area.mCols.mStart : area.mRows.mStart;
5939 } else if (alignContent == StyleAlignFlags::LAST_BASELINE) {
5940 state |= ItemState::eLastBaseline | ItemState::eContentBaseline;
5941 baselineTrack =
5942 (isInlineAxis ? area.mCols.mEnd : area.mRows.mEnd) - 1;
5947 if (state & ItemState::eIsBaselineAligned) {
5948 // XXXmats if |child| is a descendant of a subgrid then the metrics
5949 // below needs to account for the accumulated MPB somehow...
5951 nscoord baseline;
5952 nsGridContainerFrame* grid = do_QueryFrame(child);
5953 if (state & ItemState::eFirstBaseline) {
5954 if (grid) {
5955 if (isOrthogonal == isInlineAxis) {
5956 baseline = grid->GetBBaseline(BaselineSharingGroup::First);
5957 } else {
5958 baseline = grid->GetIBaseline(BaselineSharingGroup::First);
5961 if (grid || nsLayoutUtils::GetFirstLineBaseline(wm, child, &baseline)) {
5962 NS_ASSERTION(baseline != NS_INTRINSIC_ISIZE_UNKNOWN,
5963 "about to use an unknown baseline");
5964 auto frameSize = isInlineAxis ? child->ISize(wm) : child->BSize(wm);
5965 nscoord alignSize;
5966 LogicalPoint pos =
5967 child->GetLogicalNormalPosition(wm, aContainerSize);
5968 baseline += pos.Pos(mAxis, wm);
5969 if (aSet.mTrackAlignmentSet == BaselineAlignmentSet::EndStretch) {
5970 state |= ItemState::eEndSideBaseline;
5971 // Convert to distance from the track end.
5972 baseline =
5973 aTrackSizes[gridItem.mArea
5974 .LineRangeForAxis(GetOrthogonalAxis(mAxis))
5975 .mStart] -
5976 baseline;
5978 alignSize = frameSize;
5979 aFirstBaselineItems.AppendElement(ItemBaselineData(
5980 {baselineTrack, baseline, alignSize, &gridItem}));
5981 } else {
5982 state &= ~ItemState::eAllBaselineBits;
5984 } else {
5985 if (grid) {
5986 if (isOrthogonal == isInlineAxis) {
5987 baseline = grid->GetBBaseline(BaselineSharingGroup::Last);
5988 } else {
5989 baseline = grid->GetIBaseline(BaselineSharingGroup::Last);
5992 if (grid || nsLayoutUtils::GetLastLineBaseline(wm, child, &baseline)) {
5993 NS_ASSERTION(baseline != NS_INTRINSIC_ISIZE_UNKNOWN,
5994 "about to use an unknown baseline");
5995 auto frameSize = isInlineAxis ? child->ISize(wm) : child->BSize(wm);
5996 auto m = child->GetLogicalUsedMargin(wm);
5997 if (!grid &&
5998 aSet.mTrackAlignmentSet == BaselineAlignmentSet::EndStretch) {
5999 // Convert to distance from border-box end.
6000 state |= ItemState::eEndSideBaseline;
6001 LogicalPoint pos =
6002 child->GetLogicalNormalPosition(wm, aContainerSize);
6003 baseline += pos.Pos(mAxis, wm);
6004 baseline =
6005 aTrackSizes[gridItem.mArea
6006 .LineRangeForAxis(GetOrthogonalAxis(mAxis))
6007 .mStart] -
6008 baseline;
6009 } else if (grid && aSet.mTrackAlignmentSet ==
6010 BaselineAlignmentSet::StartStretch) {
6011 // Convert to distance from border-box start.
6012 baseline = frameSize - baseline;
6014 if (aSet.mItemSet == BaselineAlignmentSet::LastItems &&
6015 aSet.mTrackAlignmentSet == BaselineAlignmentSet::StartStretch) {
6016 LogicalPoint pos =
6017 child->GetLogicalNormalPosition(wm, aContainerSize);
6018 baseline += pos.B(wm);
6020 if (aSet.mTrackAlignmentSet == BaselineAlignmentSet::EndStretch) {
6021 state |= ItemState::eEndSideBaseline;
6023 auto descent =
6024 baseline + ((state & ItemState::eEndSideBaseline)
6025 ? (isInlineAxis ? m.IEnd(wm) : m.BEnd(wm))
6026 : (isInlineAxis ? m.IStart(wm) : m.BStart(wm)));
6027 auto alignSize =
6028 frameSize + (isInlineAxis ? m.IStartEnd(wm) : m.BStartEnd(wm));
6029 aLastBaselineItems.AppendElement(
6030 ItemBaselineData({baselineTrack, descent, alignSize, &gridItem}));
6031 } else {
6032 state &= ~ItemState::eAllBaselineBits;
6036 MOZ_ASSERT(
6037 (state & (ItemState::eFirstBaseline | ItemState::eLastBaseline)) !=
6038 (ItemState::eFirstBaseline | ItemState::eLastBaseline),
6039 "first/last baseline bits are mutually exclusive");
6040 MOZ_ASSERT(
6041 (state & (ItemState::eSelfBaseline | ItemState::eContentBaseline)) !=
6042 (ItemState::eSelfBaseline | ItemState::eContentBaseline),
6043 "*-self and *-content baseline bits are mutually exclusive");
6044 MOZ_ASSERT(
6045 !(state & (ItemState::eFirstBaseline | ItemState::eLastBaseline)) ==
6046 !(state & (ItemState::eSelfBaseline | ItemState::eContentBaseline)),
6047 "first/last bit requires self/content bit and vice versa");
6048 gridItem.mState[mAxis] |= state;
6049 gridItem.mBaselineOffset[mAxis] = nscoord(0);
6052 CalculateItemBaselines(aFirstBaselineItems, BaselineSharingGroup::First);
6053 CalculateItemBaselines(aLastBaselineItems, BaselineSharingGroup::Last);
6055 // TODO: make sure the mBaselines (i.e. the baselines we export from
6056 // the grid container) are offset from the correct container edge.
6057 // Also, which of the baselines do we pick to export exactly?
6059 MOZ_ASSERT(aFirstBaselineItems.Length() != 1 ||
6060 aFirstBaselineItems[0].mGridItem->mBaselineOffset[mAxis] == 0,
6061 "a baseline group that contains only one item should not "
6062 "produce a non-zero item baseline offset");
6063 MOZ_ASSERT(aLastBaselineItems.Length() != 1 ||
6064 aLastBaselineItems[0].mGridItem->mBaselineOffset[mAxis] == 0,
6065 "a baseline group that contains only one item should not "
6066 "produce a non-zero item baseline offset");
6069 void nsGridContainerFrame::Tracks::AlignBaselineSubtree(
6070 const GridItemInfo& aGridItem) const {
6071 if (mIsMasonry) {
6072 return;
6074 auto state = aGridItem.mState[mAxis];
6075 if (!(state & ItemState::eIsBaselineAligned)) {
6076 return;
6078 const GridArea& area = aGridItem.mArea;
6079 int32_t baselineTrack;
6080 const bool isFirstBaseline = state & ItemState::eFirstBaseline;
6081 if (isFirstBaseline) {
6082 baselineTrack =
6083 mAxis == eLogicalAxisBlock ? area.mRows.mStart : area.mCols.mStart;
6084 } else {
6085 baselineTrack =
6086 (mAxis == eLogicalAxisBlock ? area.mRows.mEnd : area.mCols.mEnd) - 1;
6088 const TrackSize& sz = mSizes[baselineTrack];
6089 auto baselineGroup = isFirstBaseline ? BaselineSharingGroup::First
6090 : BaselineSharingGroup::Last;
6091 nscoord delta = sz.mBase - sz.mBaselineSubtreeSize[baselineGroup];
6092 const auto subtreeAlign = mBaselineSubtreeAlign[baselineGroup];
6093 if (subtreeAlign == StyleAlignFlags::START) {
6094 if (state & ItemState::eLastBaseline) {
6095 aGridItem.mBaselineOffset[mAxis] += delta;
6097 } else if (subtreeAlign == StyleAlignFlags::END) {
6098 if (isFirstBaseline) {
6099 aGridItem.mBaselineOffset[mAxis] += delta;
6101 } else if (subtreeAlign == StyleAlignFlags::CENTER) {
6102 aGridItem.mBaselineOffset[mAxis] += delta / 2;
6103 } else {
6104 MOZ_ASSERT_UNREACHABLE("unexpected baseline subtree alignment");
6108 template <nsGridContainerFrame::Tracks::TrackSizingPhase phase>
6109 bool nsGridContainerFrame::Tracks::GrowSizeForSpanningItems(
6110 nsTArray<SpanningItemData>::iterator aIter,
6111 nsTArray<SpanningItemData>::iterator aIterEnd, nsTArray<uint32_t>& aTracks,
6112 nsTArray<TrackSize>& aPlan, nsTArray<TrackSize>& aItemPlan,
6113 TrackSize::StateBits aSelector, const FitContentClamper& aFitContentClamper,
6114 bool aNeedInfinitelyGrowableFlag) {
6115 constexpr bool isMaxSizingPhase =
6116 phase == TrackSizingPhase::IntrinsicMaximums ||
6117 phase == TrackSizingPhase::MaxContentMaximums;
6118 bool needToUpdateSizes = false;
6119 InitializePlan<phase>(aPlan);
6120 for (; aIter != aIterEnd; ++aIter) {
6121 const SpanningItemData& item = *aIter;
6122 if (!(item.mState & aSelector)) {
6123 continue;
6125 if (isMaxSizingPhase) {
6126 for (auto i : item.mLineRange.Range()) {
6127 aPlan[i].mState |= TrackSize::eModified;
6130 nscoord space = item.SizeContributionForPhase<phase>();
6131 if (space <= 0) {
6132 continue;
6134 aTracks.ClearAndRetainStorage();
6135 space = CollectGrowable<phase>(space, item.mLineRange, aSelector, aTracks);
6136 if (space > 0) {
6137 DistributeToTrackSizes<phase>(space, aPlan, aItemPlan, aTracks, aSelector,
6138 aFitContentClamper);
6139 needToUpdateSizes = true;
6142 if (isMaxSizingPhase) {
6143 needToUpdateSizes = true;
6145 if (needToUpdateSizes) {
6146 CopyPlanToSize<phase>(aPlan, aNeedInfinitelyGrowableFlag);
6148 return needToUpdateSizes;
6151 void nsGridContainerFrame::Tracks::ResolveIntrinsicSize(
6152 GridReflowInput& aState, nsTArray<GridItemInfo>& aGridItems,
6153 const TrackSizingFunctions& aFunctions, LineRange GridArea::*aRange,
6154 nscoord aPercentageBasis, SizingConstraint aConstraint) {
6155 // Resolve Intrinsic Track Sizes
6156 // https://w3c.github.io/csswg-drafts/css-grid-1/#algo-content
6157 // We're also setting eIsFlexing on the item state here to speed up
6158 // FindUsedFlexFraction later.
6160 gfxContext* rc = &aState.mRenderingContext;
6161 WritingMode wm = aState.mWM;
6163 // Data we accumulate when grouping similar sized spans together.
6164 struct PerSpanData {
6165 uint32_t mItemCountWithSameSpan = 0;
6166 TrackSize::StateBits mStateBits = TrackSize::StateBits{0};
6168 AutoTArray<PerSpanData, 16> perSpanData;
6170 nsTArray<SpanningItemData> spanningItems;
6171 uint32_t maxSpan = 0; // max span of items in `spanningItems`.
6173 // Setup track selector for step 3.2:
6174 const auto contentBasedMinSelector =
6175 aConstraint == SizingConstraint::MinContent
6176 ? TrackSize::eIntrinsicMinSizing
6177 : TrackSize::eMinOrMaxContentMinSizing;
6179 // Setup track selector for step 3.3:
6180 const auto maxContentMinSelector =
6181 aConstraint == SizingConstraint::MaxContent
6182 ? (TrackSize::eMaxContentMinSizing | TrackSize::eAutoMinSizing)
6183 : TrackSize::eMaxContentMinSizing;
6185 const auto orthogonalAxis = GetOrthogonalAxis(mAxis);
6186 const bool isMasonryInOtherAxis = aState.mFrame->IsMasonry(orthogonalAxis);
6188 for (auto& gridItem : aGridItems) {
6189 MOZ_ASSERT(!(gridItem.mState[mAxis] &
6190 (ItemState::eApplyAutoMinSize | ItemState::eIsFlexing |
6191 ItemState::eClampMarginBoxMinSize)),
6192 "Why are any of these bits set already?");
6194 const GridArea& area = gridItem.mArea;
6195 const LineRange& lineRange = area.*aRange;
6197 // If we have masonry layout in the other axis then skip this item unless
6198 // it's in the first masonry track, or has definite placement in this axis,
6199 // or spans all tracks in this axis (since that implies it will be placed
6200 // at line 1 regardless of layout results of other items).
6201 if (isMasonryInOtherAxis &&
6202 gridItem.mArea.LineRangeForAxis(orthogonalAxis).mStart != 0 &&
6203 (gridItem.mState[mAxis] & ItemState::eAutoPlacement) &&
6204 gridItem.mArea.LineRangeForAxis(mAxis).Extent() != mSizes.Length()) {
6205 continue;
6208 uint32_t span = lineRange.Extent();
6209 if (MOZ_UNLIKELY(gridItem.mState[mAxis] & ItemState::eIsSubgrid)) {
6210 auto itemWM = gridItem.mFrame->GetWritingMode();
6211 auto percentageBasis = aState.PercentageBasisFor(mAxis, gridItem);
6213 if (percentageBasis.ISize(itemWM) == NS_UNCONSTRAINEDSIZE) {
6214 percentageBasis.ISize(itemWM) = nscoord(0);
6217 if (percentageBasis.BSize(itemWM) == NS_UNCONSTRAINEDSIZE) {
6218 percentageBasis.BSize(itemWM) = nscoord(0);
6221 auto* subgrid =
6222 SubgridComputeMarginBorderPadding(gridItem, percentageBasis);
6223 LogicalMargin mbp = SubgridAccumulatedMarginBorderPadding(
6224 gridItem.SubgridFrame(), subgrid, wm, mAxis);
6226 if (span == 1) {
6227 AddSubgridContribution(mSizes[lineRange.mStart],
6228 mbp.StartEnd(mAxis, wm));
6229 } else {
6230 AddSubgridContribution(mSizes[lineRange.mStart], mbp.Start(mAxis, wm));
6231 AddSubgridContribution(mSizes[lineRange.mEnd - 1], mbp.End(mAxis, wm));
6233 continue;
6236 if (span == 1) {
6237 // Step 2. Size tracks to fit non-spanning items.
6238 if (ResolveIntrinsicSizeForNonSpanningItems(aState, aFunctions,
6239 aPercentageBasis, aConstraint,
6240 lineRange, gridItem)) {
6241 gridItem.mState[mAxis] |= ItemState::eIsFlexing;
6243 } else {
6244 TrackSize::StateBits state = StateBitsForRange(lineRange);
6246 // Check if we need to apply "Automatic Minimum Size" and cache it.
6247 if ((state & TrackSize::eAutoMinSizing) &&
6248 !(state & TrackSize::eFlexMaxSizing) &&
6249 gridItem.ShouldApplyAutoMinSize(wm, mAxis, aPercentageBasis)) {
6250 gridItem.mState[mAxis] |= ItemState::eApplyAutoMinSize;
6253 if (state & TrackSize::eFlexMaxSizing) {
6254 gridItem.mState[mAxis] |= ItemState::eIsFlexing;
6255 } else if (state & (TrackSize::eIntrinsicMinSizing |
6256 TrackSize::eIntrinsicMaxSizing)) {
6257 // Collect data for Step 3.
6258 maxSpan = std::max(maxSpan, span);
6259 if (span >= perSpanData.Length()) {
6260 perSpanData.SetLength(2 * span);
6263 perSpanData[span].mItemCountWithSameSpan++;
6264 perSpanData[span].mStateBits |= state;
6266 CachedIntrinsicSizes cache;
6268 // Calculate data for "Automatic Minimum Size" clamping, if needed.
6269 if (TrackSize::IsDefiniteMaxSizing(state) &&
6270 (gridItem.mState[mAxis] & ItemState::eApplyAutoMinSize)) {
6271 nscoord minSizeClamp = 0;
6272 for (auto i : lineRange.Range()) {
6273 minSizeClamp += aFunctions.MaxSizingFor(i).AsBreadth().Resolve(
6274 aPercentageBasis);
6276 minSizeClamp += mGridGap * (span - 1);
6277 cache.mMinSizeClamp = minSizeClamp;
6278 gridItem.mState[mAxis] |= ItemState::eClampMarginBoxMinSize;
6281 // Collect the various grid item size contributions we need.
6282 nscoord minSize = 0;
6283 if (state & TrackSize::eIntrinsicMinSizing) { // for 3.1
6284 minSize = MinSize(gridItem, aState, rc, wm, mAxis, &cache);
6286 nscoord minContent = 0;
6287 if (state & (contentBasedMinSelector | // for 3.2
6288 TrackSize::eIntrinsicMaxSizing)) { // for 3.5
6289 minContent =
6290 MinContentContribution(gridItem, aState, rc, wm, mAxis, &cache);
6292 nscoord maxContent = 0;
6293 if (state & (maxContentMinSelector | // for 3.3
6294 TrackSize::eAutoOrMaxContentMaxSizing)) { // for 3.6
6295 maxContent =
6296 MaxContentContribution(gridItem, aState, rc, wm, mAxis, &cache);
6299 spanningItems.AppendElement(
6300 SpanningItemData({span, state, lineRange, minSize, minContent,
6301 maxContent, gridItem.mFrame}));
6305 MOZ_ASSERT(!(gridItem.mState[mAxis] & ItemState::eClampMarginBoxMinSize) ||
6306 (gridItem.mState[mAxis] & ItemState::eApplyAutoMinSize),
6307 "clamping only applies to Automatic Minimum Size");
6310 // Step 3 - Increase sizes to accommodate spanning items crossing
6311 // content-sized tracks.
6312 if (maxSpan) {
6313 auto fitContentClamper = [&aFunctions, aPercentageBasis](uint32_t aTrack,
6314 nscoord aMinSize,
6315 nscoord* aSize) {
6316 nscoord fitContentLimit = ::ResolveToDefiniteSize(
6317 aFunctions.MaxSizingFor(aTrack), aPercentageBasis);
6318 if (*aSize > fitContentLimit) {
6319 *aSize = std::max(aMinSize, fitContentLimit);
6320 return true;
6322 return false;
6325 // Sort the collected items on span length, shortest first. There's no need
6326 // for a stable sort here since the sizing isn't order dependent within
6327 // a group of items with the same span length.
6328 std::sort(spanningItems.begin(), spanningItems.end(),
6329 SpanningItemData::IsSpanLessThan);
6331 nsTArray<uint32_t> tracks(maxSpan);
6332 nsTArray<TrackSize> plan(mSizes.Length());
6333 plan.SetLength(mSizes.Length());
6334 nsTArray<TrackSize> itemPlan(mSizes.Length());
6335 itemPlan.SetLength(mSizes.Length());
6336 // Start / end iterator for items of the same span length:
6337 auto spanGroupStart = spanningItems.begin();
6338 auto spanGroupEnd = spanGroupStart;
6339 const auto end = spanningItems.end();
6340 for (; spanGroupStart != end; spanGroupStart = spanGroupEnd) {
6341 const uint32_t span = spanGroupStart->mSpan;
6342 spanGroupEnd = spanGroupStart + perSpanData[span].mItemCountWithSameSpan;
6343 TrackSize::StateBits stateBitsForSpan = perSpanData[span].mStateBits;
6344 bool updatedBase = false; // Did we update any mBase in step 3.1..3.3?
6345 TrackSize::StateBits selector(TrackSize::eIntrinsicMinSizing);
6346 if (stateBitsForSpan & selector) {
6347 // Step 3.1 MinSize to intrinsic min-sizing.
6348 updatedBase =
6349 GrowSizeForSpanningItems<TrackSizingPhase::IntrinsicMinimums>(
6350 spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector);
6353 selector = contentBasedMinSelector;
6354 if (stateBitsForSpan & selector) {
6355 // Step 3.2 MinContentContribution to min-/max-content (and 'auto' when
6356 // sizing under a min-content constraint) min-sizing.
6357 updatedBase |=
6358 GrowSizeForSpanningItems<TrackSizingPhase::ContentBasedMinimums>(
6359 spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector);
6362 selector = maxContentMinSelector;
6363 if (stateBitsForSpan & selector) {
6364 // Step 3.3 MaxContentContribution to max-content (and 'auto' when
6365 // sizing under a max-content constraint) min-sizing.
6366 updatedBase |=
6367 GrowSizeForSpanningItems<TrackSizingPhase::MaxContentMinimums>(
6368 spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector);
6371 if (updatedBase) {
6372 // Step 3.4
6373 for (TrackSize& sz : mSizes) {
6374 if (sz.mBase > sz.mLimit) {
6375 sz.mLimit = sz.mBase;
6380 selector = TrackSize::eIntrinsicMaxSizing;
6381 if (stateBitsForSpan & selector) {
6382 const bool willRunStep3_6 =
6383 stateBitsForSpan & TrackSize::eAutoOrMaxContentMaxSizing;
6384 // Step 3.5 MinContentContribution to intrinsic max-sizing.
6385 GrowSizeForSpanningItems<TrackSizingPhase::IntrinsicMaximums>(
6386 spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector,
6387 fitContentClamper, willRunStep3_6);
6389 if (willRunStep3_6) {
6390 // Step 2.6 MaxContentContribution to max-content max-sizing.
6391 selector = TrackSize::eAutoOrMaxContentMaxSizing;
6392 GrowSizeForSpanningItems<TrackSizingPhase::MaxContentMaximums>(
6393 spanGroupStart, spanGroupEnd, tracks, plan, itemPlan, selector,
6394 fitContentClamper);
6400 // Step 5 - If any track still has an infinite growth limit, set its growth
6401 // limit to its base size.
6402 for (TrackSize& sz : mSizes) {
6403 if (sz.mLimit == NS_UNCONSTRAINEDSIZE) {
6404 sz.mLimit = sz.mBase;
6409 float nsGridContainerFrame::Tracks::FindFrUnitSize(
6410 const LineRange& aRange, const nsTArray<uint32_t>& aFlexTracks,
6411 const TrackSizingFunctions& aFunctions, nscoord aSpaceToFill) const {
6412 MOZ_ASSERT(aSpaceToFill > 0 && !aFlexTracks.IsEmpty());
6413 float flexFactorSum = 0.0f;
6414 nscoord leftOverSpace = aSpaceToFill;
6415 for (auto i : aRange.Range()) {
6416 const TrackSize& sz = mSizes[i];
6417 if (sz.mState & TrackSize::eFlexMaxSizing) {
6418 flexFactorSum += aFunctions.MaxSizingFor(i).AsFr();
6419 } else {
6420 leftOverSpace -= sz.mBase;
6421 if (leftOverSpace <= 0) {
6422 return 0.0f;
6426 bool restart;
6427 float hypotheticalFrSize;
6428 nsTArray<uint32_t> flexTracks(aFlexTracks.Clone());
6429 uint32_t numFlexTracks = flexTracks.Length();
6430 do {
6431 restart = false;
6432 hypotheticalFrSize = leftOverSpace / std::max(flexFactorSum, 1.0f);
6433 for (uint32_t i = 0, len = flexTracks.Length(); i < len; ++i) {
6434 uint32_t track = flexTracks[i];
6435 if (track == kAutoLine) {
6436 continue; // Track marked as inflexible in a prev. iter of this loop.
6438 float flexFactor = aFunctions.MaxSizingFor(track).AsFr();
6439 const nscoord base = mSizes[track].mBase;
6440 if (flexFactor * hypotheticalFrSize < base) {
6441 // 12.7.1.4: Treat this track as inflexible.
6442 flexTracks[i] = kAutoLine;
6443 flexFactorSum -= flexFactor;
6444 leftOverSpace -= base;
6445 --numFlexTracks;
6446 if (numFlexTracks == 0 || leftOverSpace <= 0) {
6447 return 0.0f;
6449 restart = true;
6450 // break; XXX (bug 1176621 comment 16) measure which is more common
6453 } while (restart);
6454 return hypotheticalFrSize;
6457 float nsGridContainerFrame::Tracks::FindUsedFlexFraction(
6458 GridReflowInput& aState, nsTArray<GridItemInfo>& aGridItems,
6459 const nsTArray<uint32_t>& aFlexTracks,
6460 const TrackSizingFunctions& aFunctions, nscoord aAvailableSize) const {
6461 if (aAvailableSize != NS_UNCONSTRAINEDSIZE) {
6462 // Use all of the grid tracks and a 'space to fill' of the available space.
6463 const TranslatedLineRange range(0, mSizes.Length());
6464 return FindFrUnitSize(range, aFlexTracks, aFunctions, aAvailableSize);
6467 // The used flex fraction is the maximum of:
6468 // ... each flexible track's base size divided by its flex factor (which is
6469 // floored at 1).
6470 float fr = 0.0f;
6471 for (uint32_t track : aFlexTracks) {
6472 float flexFactor = aFunctions.MaxSizingFor(track).AsFr();
6473 float possiblyDividedBaseSize = (flexFactor > 1.0f)
6474 ? mSizes[track].mBase / flexFactor
6475 : mSizes[track].mBase;
6476 fr = std::max(fr, possiblyDividedBaseSize);
6478 WritingMode wm = aState.mWM;
6479 gfxContext* rc = &aState.mRenderingContext;
6480 // ... the result of 'finding the size of an fr' for each item that spans
6481 // a flex track with its max-content contribution as 'space to fill'
6482 for (const GridItemInfo& item : aGridItems) {
6483 if (item.mState[mAxis] & ItemState::eIsFlexing) {
6484 // XXX optimize: bug 1194446
6485 auto pb = Some(aState.PercentageBasisFor(mAxis, item));
6486 nscoord spaceToFill = ContentContribution(item, aState, rc, wm, mAxis, pb,
6487 IntrinsicISizeType::PrefISize);
6488 const LineRange& range =
6489 mAxis == eLogicalAxisInline ? item.mArea.mCols : item.mArea.mRows;
6490 MOZ_ASSERT(range.Extent() >= 1);
6491 const auto spannedGaps = range.Extent() - 1;
6492 if (spannedGaps > 0) {
6493 spaceToFill -= mGridGap * spannedGaps;
6495 if (spaceToFill <= 0) {
6496 continue;
6498 // ... and all its spanned tracks as input.
6499 nsTArray<uint32_t> itemFlexTracks;
6500 for (auto i : range.Range()) {
6501 if (mSizes[i].mState & TrackSize::eFlexMaxSizing) {
6502 itemFlexTracks.AppendElement(i);
6505 float itemFr =
6506 FindFrUnitSize(range, itemFlexTracks, aFunctions, spaceToFill);
6507 fr = std::max(fr, itemFr);
6510 return fr;
6513 void nsGridContainerFrame::Tracks::StretchFlexibleTracks(
6514 GridReflowInput& aState, nsTArray<GridItemInfo>& aGridItems,
6515 const TrackSizingFunctions& aFunctions, nscoord aAvailableSize) {
6516 if (aAvailableSize <= 0) {
6517 return;
6519 nsTArray<uint32_t> flexTracks(mSizes.Length());
6520 for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) {
6521 if (mSizes[i].mState & TrackSize::eFlexMaxSizing) {
6522 flexTracks.AppendElement(i);
6525 if (flexTracks.IsEmpty()) {
6526 return;
6528 nscoord minSize = 0;
6529 nscoord maxSize = NS_UNCONSTRAINEDSIZE;
6530 if (aState.mReflowInput) {
6531 auto* ri = aState.mReflowInput;
6532 minSize = mAxis == eLogicalAxisBlock ? ri->ComputedMinBSize()
6533 : ri->ComputedMinISize();
6534 maxSize = mAxis == eLogicalAxisBlock ? ri->ComputedMaxBSize()
6535 : ri->ComputedMaxISize();
6537 Maybe<CopyableAutoTArray<TrackSize, 32>> origSizes;
6538 bool applyMinMax = (minSize != 0 || maxSize != NS_UNCONSTRAINEDSIZE) &&
6539 aAvailableSize == NS_UNCONSTRAINEDSIZE;
6540 // We iterate twice at most. The 2nd time if the grid size changed after
6541 // applying a min/max-size (can only occur if aAvailableSize is indefinite).
6542 while (true) {
6543 float fr = FindUsedFlexFraction(aState, aGridItems, flexTracks, aFunctions,
6544 aAvailableSize);
6545 if (fr != 0.0f) {
6546 for (uint32_t i : flexTracks) {
6547 float flexFactor = aFunctions.MaxSizingFor(i).AsFr();
6548 nscoord flexLength = NSToCoordRound(flexFactor * fr);
6549 nscoord& base = mSizes[i].mBase;
6550 if (flexLength > base) {
6551 if (applyMinMax && origSizes.isNothing()) {
6552 origSizes.emplace(mSizes);
6554 base = flexLength;
6558 if (applyMinMax) {
6559 applyMinMax = false;
6560 // https://drafts.csswg.org/css-grid/#algo-flex-tracks
6561 // "If using this flex fraction would cause the grid to be smaller than
6562 // the grid container’s min-width/height (or larger than the grid
6563 // container’s max-width/height), then redo this step, treating the free
6564 // space as definite [...]"
6565 const auto sumOfGridGaps = SumOfGridGaps();
6566 nscoord newSize = SumOfGridTracks() + sumOfGridGaps;
6567 if (newSize > maxSize) {
6568 aAvailableSize = maxSize;
6569 } else if (newSize < minSize) {
6570 aAvailableSize = minSize;
6572 if (aAvailableSize != NS_UNCONSTRAINEDSIZE) {
6573 aAvailableSize = std::max(0, aAvailableSize - sumOfGridGaps);
6574 // Restart with the original track sizes and definite aAvailableSize.
6575 if (origSizes.isSome()) {
6576 mSizes = std::move(*origSizes);
6577 origSizes.reset();
6578 } // else, no mSizes[].mBase were changed above so it's still correct
6579 if (aAvailableSize == 0) {
6580 break; // zero available size wouldn't change any sizes though...
6582 continue;
6585 break;
6589 void nsGridContainerFrame::Tracks::AlignJustifyContent(
6590 const nsStylePosition* aStyle, StyleContentDistribution aAligmentStyleValue,
6591 WritingMode aWM, nscoord aContentBoxSize, bool aIsSubgriddedAxis) {
6592 const bool isAlign = mAxis == eLogicalAxisBlock;
6593 // Align-/justify-content doesn't apply in a subgridded axis.
6594 // Gap properties do apply though so we need to stretch/position the tracks
6595 // to center-align the gaps with the parent's gaps.
6596 if (MOZ_UNLIKELY(aIsSubgriddedAxis)) {
6597 auto& gap = isAlign ? aStyle->mRowGap : aStyle->mColumnGap;
6598 if (gap.IsNormal()) {
6599 return;
6601 auto len = mSizes.Length();
6602 if (len <= 1) {
6603 return;
6605 // This stores the gap deltas between the subgrid gap and the gaps in
6606 // the used track sizes (as encoded in its tracks' mPosition):
6607 nsTArray<nscoord> gapDeltas;
6608 const size_t numGaps = len - 1;
6609 gapDeltas.SetLength(numGaps);
6610 for (size_t i = 0; i < numGaps; ++i) {
6611 TrackSize& sz1 = mSizes[i];
6612 TrackSize& sz2 = mSizes[i + 1];
6613 nscoord currentGap = sz2.mPosition - (sz1.mPosition + sz1.mBase);
6614 gapDeltas[i] = mGridGap - currentGap;
6616 // Recompute the tracks' size/position so that they end up with
6617 // a subgrid-gap centered on the original track gap.
6618 nscoord currentPos = mSizes[0].mPosition;
6619 nscoord lastHalfDelta(0);
6620 for (size_t i = 0; i < numGaps; ++i) {
6621 TrackSize& sz = mSizes[i];
6622 nscoord delta = gapDeltas[i];
6623 nscoord halfDelta;
6624 nscoord roundingError = NSCoordDivRem(delta, 2, &halfDelta);
6625 auto newSize = sz.mBase - (halfDelta + roundingError) - lastHalfDelta;
6626 lastHalfDelta = halfDelta;
6627 if (newSize >= 0) {
6628 sz.mBase = newSize;
6629 sz.mPosition = currentPos;
6630 currentPos += newSize + mGridGap;
6631 } else {
6632 sz.mBase = nscoord(0);
6633 sz.mPosition = currentPos + newSize;
6634 currentPos = sz.mPosition + mGridGap;
6637 auto& lastTrack = mSizes.LastElement();
6638 auto newSize = lastTrack.mBase - lastHalfDelta;
6639 if (newSize >= 0) {
6640 lastTrack.mBase = newSize;
6641 lastTrack.mPosition = currentPos;
6642 } else {
6643 lastTrack.mBase = nscoord(0);
6644 lastTrack.mPosition = currentPos + newSize;
6646 return;
6649 if (mSizes.IsEmpty()) {
6650 return;
6653 bool overflowSafe;
6654 auto alignment = ::GetAlignJustifyValue(aAligmentStyleValue.primary, aWM,
6655 isAlign, &overflowSafe);
6656 if (alignment == StyleAlignFlags::NORMAL) {
6657 alignment = StyleAlignFlags::STRETCH;
6658 // we may need a fallback for 'stretch' below
6659 aAligmentStyleValue = {alignment};
6662 // Compute the free space and count auto-sized tracks.
6663 size_t numAutoTracks = 0;
6664 nscoord space;
6665 if (alignment != StyleAlignFlags::START) {
6666 nscoord trackSizeSum = 0;
6667 if (aIsSubgriddedAxis) {
6668 numAutoTracks = mSizes.Length();
6669 } else {
6670 for (const TrackSize& sz : mSizes) {
6671 trackSizeSum += sz.mBase;
6672 if (sz.mState & TrackSize::eAutoMaxSizing) {
6673 ++numAutoTracks;
6677 space = aContentBoxSize - trackSizeSum - SumOfGridGaps();
6678 // Use the fallback value instead when applicable.
6679 if (space < 0 ||
6680 (alignment == StyleAlignFlags::SPACE_BETWEEN && mSizes.Length() == 1)) {
6681 auto fallback = ::GetAlignJustifyFallbackIfAny(aAligmentStyleValue, aWM,
6682 isAlign, &overflowSafe);
6683 if (fallback) {
6684 alignment = *fallback;
6687 if (space == 0 || (space < 0 && overflowSafe)) {
6688 // XXX check that this makes sense also for [last ]baseline (bug 1151204).
6689 alignment = StyleAlignFlags::START;
6693 // Optimize the cases where we just need to set each track's position.
6694 nscoord pos = 0;
6695 bool distribute = true;
6696 if (alignment == StyleAlignFlags::BASELINE ||
6697 alignment == StyleAlignFlags::LAST_BASELINE) {
6698 NS_WARNING("NYI: 'first/last baseline' (bug 1151204)"); // XXX
6699 alignment = StyleAlignFlags::START;
6701 if (alignment == StyleAlignFlags::START) {
6702 distribute = false;
6703 } else if (alignment == StyleAlignFlags::END) {
6704 pos = space;
6705 distribute = false;
6706 } else if (alignment == StyleAlignFlags::CENTER) {
6707 pos = space / 2;
6708 distribute = false;
6709 } else if (alignment == StyleAlignFlags::STRETCH) {
6710 distribute = numAutoTracks != 0;
6712 if (!distribute) {
6713 for (TrackSize& sz : mSizes) {
6714 sz.mPosition = pos;
6715 pos += sz.mBase + mGridGap;
6717 return;
6720 // Distribute free space to/between tracks and set their position.
6721 MOZ_ASSERT(space > 0, "should've handled that on the fallback path above");
6722 nscoord between, roundingError;
6723 if (alignment == StyleAlignFlags::STRETCH) {
6724 MOZ_ASSERT(numAutoTracks > 0, "we handled numAutoTracks == 0 above");
6725 // The outer loop typically only runs once - it repeats only in a masonry
6726 // axis when some stretchable items reach their `max-size`.
6727 // It's O(n^2) worst case; if all items are stretchable with a `max-size`
6728 // and exactly one item reaches its `max-size` each round.
6729 while (space) {
6730 pos = 0;
6731 nscoord spacePerTrack;
6732 roundingError = NSCoordDivRem(space, numAutoTracks, &spacePerTrack);
6733 space = 0;
6734 for (TrackSize& sz : mSizes) {
6735 sz.mPosition = pos;
6736 if (!(sz.mState & TrackSize::eAutoMaxSizing)) {
6737 pos += sz.mBase + mGridGap;
6738 continue;
6740 nscoord stretch = spacePerTrack;
6741 if (roundingError) {
6742 roundingError -= 1;
6743 stretch += 1;
6745 nscoord newBase = sz.mBase + stretch;
6746 if (mIsMasonry && (sz.mState & TrackSize::eClampToLimit)) {
6747 auto clampedSize = std::min(newBase, sz.mLimit);
6748 auto sizeOverLimit = newBase - clampedSize;
6749 if (sizeOverLimit > 0) {
6750 newBase = clampedSize;
6751 sz.mState &= ~(sz.mState & TrackSize::eAutoMaxSizing);
6752 // This repeats the outer loop to distribute the superfluous space:
6753 space += sizeOverLimit;
6754 if (--numAutoTracks == 0) {
6755 // ... except if we don't have any stretchable items left.
6756 space = 0;
6760 sz.mBase = newBase;
6761 pos += newBase + mGridGap;
6764 MOZ_ASSERT(!roundingError, "we didn't distribute all rounding error?");
6765 return;
6767 if (alignment == StyleAlignFlags::SPACE_BETWEEN) {
6768 MOZ_ASSERT(mSizes.Length() > 1, "should've used a fallback above");
6769 roundingError = NSCoordDivRem(space, mSizes.Length() - 1, &between);
6770 } else if (alignment == StyleAlignFlags::SPACE_AROUND) {
6771 roundingError = NSCoordDivRem(space, mSizes.Length(), &between);
6772 pos = between / 2;
6773 } else if (alignment == StyleAlignFlags::SPACE_EVENLY) {
6774 roundingError = NSCoordDivRem(space, mSizes.Length() + 1, &between);
6775 pos = between;
6776 } else {
6777 MOZ_ASSERT_UNREACHABLE("unknown align-/justify-content value");
6778 between = 0; // just to avoid a compiler warning
6779 roundingError = 0; // just to avoid a compiler warning
6781 between += mGridGap;
6782 for (TrackSize& sz : mSizes) {
6783 sz.mPosition = pos;
6784 nscoord spacing = between;
6785 if (roundingError) {
6786 roundingError -= 1;
6787 spacing += 1;
6789 pos += sz.mBase + spacing;
6791 MOZ_ASSERT(!roundingError, "we didn't distribute all rounding error?");
6794 void nsGridContainerFrame::LineRange::ToPositionAndLength(
6795 const nsTArray<TrackSize>& aTrackSizes, nscoord* aPos,
6796 nscoord* aLength) const {
6797 MOZ_ASSERT(mStart != kAutoLine && mEnd != kAutoLine,
6798 "expected a definite LineRange");
6799 MOZ_ASSERT(mStart < mEnd);
6800 nscoord startPos = aTrackSizes[mStart].mPosition;
6801 const TrackSize& sz = aTrackSizes[mEnd - 1];
6802 *aPos = startPos;
6803 *aLength = (sz.mPosition + sz.mBase) - startPos;
6806 nscoord nsGridContainerFrame::LineRange::ToLength(
6807 const nsTArray<TrackSize>& aTrackSizes) const {
6808 MOZ_ASSERT(mStart != kAutoLine && mEnd != kAutoLine,
6809 "expected a definite LineRange");
6810 MOZ_ASSERT(mStart < mEnd);
6811 nscoord startPos = aTrackSizes[mStart].mPosition;
6812 const TrackSize& sz = aTrackSizes[mEnd - 1];
6813 return (sz.mPosition + sz.mBase) - startPos;
6816 void nsGridContainerFrame::LineRange::ToPositionAndLengthForAbsPos(
6817 const Tracks& aTracks, nscoord aGridOrigin, nscoord* aPos,
6818 nscoord* aLength) const {
6819 // kAutoLine for abspos children contributes the corresponding edge
6820 // of the grid container's padding-box.
6821 if (mEnd == kAutoLine) {
6822 if (mStart == kAutoLine) {
6823 // done
6824 } else {
6825 const nscoord endPos = *aPos + *aLength;
6826 auto side = mStart == aTracks.mSizes.Length()
6827 ? GridLineSide::BeforeGridGap
6828 : GridLineSide::AfterGridGap;
6829 nscoord startPos = aTracks.GridLineEdge(mStart, side);
6830 *aPos = aGridOrigin + startPos;
6831 *aLength = std::max(endPos - *aPos, 0);
6833 } else {
6834 if (mStart == kAutoLine) {
6835 auto side =
6836 mEnd == 0 ? GridLineSide::AfterGridGap : GridLineSide::BeforeGridGap;
6837 nscoord endPos = aTracks.GridLineEdge(mEnd, side);
6838 *aLength = std::max(aGridOrigin + endPos, 0);
6839 } else if (MOZ_LIKELY(mStart != mEnd)) {
6840 nscoord pos;
6841 ToPositionAndLength(aTracks.mSizes, &pos, aLength);
6842 *aPos = aGridOrigin + pos;
6843 } else {
6844 // The grid area only covers removed 'auto-fit' tracks.
6845 nscoord pos = aTracks.GridLineEdge(mStart, GridLineSide::BeforeGridGap);
6846 *aPos = aGridOrigin + pos;
6847 *aLength = nscoord(0);
6852 LogicalSize nsGridContainerFrame::GridReflowInput::PercentageBasisFor(
6853 LogicalAxis aAxis, const GridItemInfo& aGridItem) const {
6854 auto wm = aGridItem.mFrame->GetWritingMode();
6855 const auto* itemParent = aGridItem.mFrame->GetParent();
6856 if (MOZ_UNLIKELY(itemParent != mFrame)) {
6857 // The item comes from a descendant subgrid. Use the subgrid's
6858 // used track sizes to resolve the grid area size, if present.
6859 MOZ_ASSERT(itemParent->IsGridContainerFrame());
6860 auto* subgridFrame = static_cast<const nsGridContainerFrame*>(itemParent);
6861 MOZ_ASSERT(subgridFrame->IsSubgrid());
6862 if (auto* uts = subgridFrame->GetUsedTrackSizes()) {
6863 auto subgridWM = subgridFrame->GetWritingMode();
6864 LogicalSize cbSize(subgridWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
6865 if (!subgridFrame->IsSubgrid(eLogicalAxisInline) &&
6866 uts->mCanResolveLineRangeSize[eLogicalAxisInline]) {
6867 // NOTE: At this point aGridItem.mArea is in this->mFrame coordinates
6868 // and thus may have been transposed. The range values in a non-
6869 // subgridded axis still has its original values in subgridFrame's
6870 // coordinates though.
6871 auto rangeAxis = subgridWM.IsOrthogonalTo(mWM) ? eLogicalAxisBlock
6872 : eLogicalAxisInline;
6873 const auto& range = aGridItem.mArea.LineRangeForAxis(rangeAxis);
6874 cbSize.ISize(subgridWM) =
6875 range.ToLength(uts->mSizes[eLogicalAxisInline]);
6877 if (!subgridFrame->IsSubgrid(eLogicalAxisBlock) &&
6878 uts->mCanResolveLineRangeSize[eLogicalAxisBlock]) {
6879 auto rangeAxis = subgridWM.IsOrthogonalTo(mWM) ? eLogicalAxisInline
6880 : eLogicalAxisBlock;
6881 const auto& range = aGridItem.mArea.LineRangeForAxis(rangeAxis);
6882 cbSize.BSize(subgridWM) =
6883 range.ToLength(uts->mSizes[eLogicalAxisBlock]);
6885 return cbSize.ConvertTo(wm, subgridWM);
6888 return LogicalSize(wm, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
6891 if (aAxis == eLogicalAxisInline || !mCols.mCanResolveLineRangeSize) {
6892 return LogicalSize(wm, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
6894 // Note: for now, we only resolve transferred percentages to row sizing.
6895 // We may need to adjust these assertions once we implement bug 1300366.
6896 MOZ_ASSERT(!mRows.mCanResolveLineRangeSize);
6897 nscoord colSize = aGridItem.mArea.mCols.ToLength(mCols.mSizes);
6898 nscoord rowSize = NS_UNCONSTRAINEDSIZE;
6899 return !wm.IsOrthogonalTo(mWM) ? LogicalSize(wm, colSize, rowSize)
6900 : LogicalSize(wm, rowSize, colSize);
6903 LogicalRect nsGridContainerFrame::GridReflowInput::ContainingBlockFor(
6904 const GridArea& aArea) const {
6905 nscoord i, b, iSize, bSize;
6906 MOZ_ASSERT(aArea.mCols.Extent() > 0, "grid items cover at least one track");
6907 MOZ_ASSERT(aArea.mRows.Extent() > 0, "grid items cover at least one track");
6908 aArea.mCols.ToPositionAndLength(mCols.mSizes, &i, &iSize);
6909 aArea.mRows.ToPositionAndLength(mRows.mSizes, &b, &bSize);
6910 return LogicalRect(mWM, i, b, iSize, bSize);
6913 LogicalRect nsGridContainerFrame::GridReflowInput::ContainingBlockForAbsPos(
6914 const GridArea& aArea, const LogicalPoint& aGridOrigin,
6915 const LogicalRect& aGridCB) const {
6916 nscoord i = aGridCB.IStart(mWM);
6917 nscoord b = aGridCB.BStart(mWM);
6918 nscoord iSize = aGridCB.ISize(mWM);
6919 nscoord bSize = aGridCB.BSize(mWM);
6920 aArea.mCols.ToPositionAndLengthForAbsPos(mCols, aGridOrigin.I(mWM), &i,
6921 &iSize);
6922 aArea.mRows.ToPositionAndLengthForAbsPos(mRows, aGridOrigin.B(mWM), &b,
6923 &bSize);
6924 return LogicalRect(mWM, i, b, iSize, bSize);
6927 void nsGridContainerFrame::GridReflowInput::AlignJustifyContentInMasonryAxis(
6928 nscoord aMasonryBoxSize, nscoord aContentBoxSize) {
6929 if (aContentBoxSize == NS_UNCONSTRAINEDSIZE) {
6930 aContentBoxSize = aMasonryBoxSize;
6932 auto& masonryAxisTracks = mRows.mIsMasonry ? mRows : mCols;
6933 MOZ_ASSERT(masonryAxisTracks.mSizes.Length() == 2,
6934 "unexpected masonry axis tracks");
6935 const auto masonryAxis = masonryAxisTracks.mAxis;
6936 const auto contentAlignment = mGridStyle->UsedContentAlignment(masonryAxis);
6937 if (contentAlignment.primary == StyleAlignFlags::NORMAL ||
6938 contentAlignment.primary == StyleAlignFlags::STRETCH) {
6939 // Stretch the "masonry box" to the full content box if it's smaller.
6940 nscoord cbSize = std::max(aMasonryBoxSize, aContentBoxSize);
6941 for (auto& sz : masonryAxisTracks.mSizes) {
6942 sz.mBase = cbSize;
6944 return;
6947 // Save our current track sizes; replace them with one track sized to
6948 // the masonry box and align that within our content box.
6949 auto savedTrackSizes(std::move(masonryAxisTracks.mSizes));
6950 masonryAxisTracks.mSizes.AppendElement(savedTrackSizes[0]);
6951 masonryAxisTracks.mSizes[0].mBase = aMasonryBoxSize;
6952 masonryAxisTracks.AlignJustifyContent(mGridStyle, contentAlignment, mWM,
6953 aContentBoxSize, false);
6954 nscoord masonryBoxOffset = masonryAxisTracks.mSizes[0].mPosition;
6955 // Restore the original track sizes...
6956 masonryAxisTracks.mSizes = std::move(savedTrackSizes);
6957 // ...then reposition and resize all of them to the aligned result.
6958 for (auto& sz : masonryAxisTracks.mSizes) {
6959 sz.mPosition = masonryBoxOffset;
6960 sz.mBase = aMasonryBoxSize;
6964 // Note: this is called after all items have been positioned/reflowed.
6965 // The masonry-axis tracks have the size of the "masonry box" at this point
6966 // and are positioned according to 'align/justify-content'.
6967 void nsGridContainerFrame::GridReflowInput::AlignJustifyTracksInMasonryAxis(
6968 const LogicalSize& aContentSize, const nsSize& aContainerSize) {
6969 auto& masonryAxisTracks = mRows.mIsMasonry ? mRows : mCols;
6970 MOZ_ASSERT(masonryAxisTracks.mSizes.Length() == 2,
6971 "unexpected masonry axis tracks");
6972 const auto masonryAxis = masonryAxisTracks.mAxis;
6973 auto gridAxis = GetOrthogonalAxis(masonryAxis);
6974 auto& gridAxisTracks = TracksFor(gridAxis);
6975 AutoTArray<TrackSize, 32> savedSizes;
6976 savedSizes.AppendElements(masonryAxisTracks.mSizes);
6977 auto wm = mWM;
6978 nscoord contentAreaStart = mBorderPadding.Start(masonryAxis, wm);
6979 // The offset to the "masonry box" from our content-box start edge.
6980 nscoord masonryBoxOffset = masonryAxisTracks.mSizes[0].mPosition;
6981 nscoord alignmentContainerSize = masonryAxisTracks.mSizes[0].mBase;
6983 for (auto i : IntegerRange(gridAxisTracks.mSizes.Length())) {
6984 auto tracksAlignment = mGridStyle->UsedTracksAlignment(masonryAxis, i);
6985 if (tracksAlignment.primary != StyleAlignFlags::START) {
6986 masonryAxisTracks.mSizes.ClearAndRetainStorage();
6987 for (const auto& item : mGridItems) {
6988 if (item.mArea.LineRangeForAxis(gridAxis).mStart == i) {
6989 const auto* child = item.mFrame;
6990 LogicalRect rect = child->GetLogicalRect(wm, aContainerSize);
6991 TrackSize sz = {0, 0, 0, {0, 0}, TrackSize::StateBits{0}};
6992 const auto& margin = child->GetLogicalUsedMargin(wm);
6993 sz.mPosition = rect.Start(masonryAxis, wm) -
6994 margin.Start(masonryAxis, wm) - contentAreaStart;
6995 sz.mBase =
6996 rect.Size(masonryAxis, wm) + margin.StartEnd(masonryAxis, wm);
6997 // Account for a align-self baseline offset on the end side.
6998 // XXXmats hmm, it seems it would be a lot simpler to just store
6999 // these baseline adjustments into the UsedMarginProperty instead
7000 auto state = item.mState[masonryAxis];
7001 if ((state & ItemState::eSelfBaseline) &&
7002 (state & ItemState::eEndSideBaseline)) {
7003 sz.mBase += item.mBaselineOffset[masonryAxis];
7005 if (tracksAlignment.primary == StyleAlignFlags::STRETCH) {
7006 const auto* pos = child->StylePosition();
7007 auto itemAlignment =
7008 pos->UsedSelfAlignment(masonryAxis, mFrame->Style());
7009 if (child->StyleMargin()->HasAuto(masonryAxis, wm)) {
7010 sz.mState |= TrackSize::eAutoMaxSizing;
7011 sz.mState |= TrackSize::eItemHasAutoMargin;
7012 } else if (pos->Size(masonryAxis, wm).IsAuto() &&
7013 (itemAlignment == StyleAlignFlags::NORMAL ||
7014 itemAlignment == StyleAlignFlags::STRETCH)) {
7015 sz.mState |= TrackSize::eAutoMaxSizing;
7016 sz.mState |= TrackSize::eItemStretchSize;
7017 const auto& max = pos->MaxSize(masonryAxis, wm);
7018 if (max.ConvertsToLength()) { // XXX deal with percentages
7019 // XXX add in baselineOffset ? use actual frame size - content
7020 // size?
7021 nscoord boxSizingAdjust =
7022 child->GetLogicalUsedBorderAndPadding(wm).StartEnd(
7023 masonryAxis, wm);
7024 if (pos->mBoxSizing == StyleBoxSizing::Border) {
7025 boxSizingAdjust = 0;
7027 sz.mLimit = nsLayoutUtils::ComputeBSizeValue(
7028 aContentSize.Size(masonryAxis, wm), boxSizingAdjust,
7029 max.AsLengthPercentage());
7030 sz.mLimit += margin.StartEnd(masonryAxis, wm);
7031 sz.mState |= TrackSize::eClampToLimit;
7035 masonryAxisTracks.mSizes.AppendElement(std::move(sz));
7038 masonryAxisTracks.AlignJustifyContent(mGridStyle, tracksAlignment, wm,
7039 alignmentContainerSize, false);
7040 auto iter = mGridItems.begin();
7041 auto end = mGridItems.end();
7042 // We limit the loop to the number of items we found in the current
7043 // grid-axis axis track (in the outer loop) as an optimization.
7044 for (auto r : IntegerRange(masonryAxisTracks.mSizes.Length())) {
7045 GridItemInfo* item = nullptr;
7046 auto& sz = masonryAxisTracks.mSizes[r];
7047 // Find the next item in the current grid-axis axis track.
7048 for (; iter != end; ++iter) {
7049 if (iter->mArea.LineRangeForAxis(gridAxis).mStart == i) {
7050 item = &*iter;
7051 ++iter;
7052 break;
7055 nsIFrame* child = item->mFrame;
7056 const auto childWM = child->GetWritingMode();
7057 auto masonryChildAxis =
7058 childWM.IsOrthogonalTo(wm) ? gridAxis : masonryAxis;
7059 LogicalMargin margin = child->GetLogicalUsedMargin(childWM);
7060 bool forceReposition = false;
7061 if (sz.mState & TrackSize::eItemStretchSize) {
7062 auto size = child->GetLogicalSize().Size(masonryChildAxis, childWM);
7063 auto newSize = sz.mBase - margin.StartEnd(masonryChildAxis, childWM);
7064 if (size != newSize) {
7065 // XXX need to pass aIMinSizeClamp aBMinSizeClamp ?
7066 LogicalSize cb =
7067 ContainingBlockFor(item->mArea).Size(wm).ConvertTo(childWM, wm);
7068 LogicalSize availableSize = cb;
7069 cb.Size(masonryChildAxis, childWM) = alignmentContainerSize;
7070 availableSize.Size(eLogicalAxisBlock, childWM) =
7071 NS_UNCONSTRAINEDSIZE;
7072 const auto& bp = child->GetLogicalUsedBorderAndPadding(childWM);
7073 newSize -= bp.StartEnd(masonryChildAxis, childWM);
7074 ::PostReflowStretchChild(child, *mReflowInput, availableSize, cb,
7075 masonryChildAxis, newSize);
7076 if (childWM.IsPhysicalRTL()) {
7077 // The NormalPosition of this child is frame-size dependent so we
7078 // need to reset its stored position below.
7079 forceReposition = true;
7082 } else if (sz.mState & TrackSize::eItemHasAutoMargin) {
7083 // Re-compute the auto-margin(s) in the masonry axis.
7084 auto size = child->GetLogicalSize().Size(masonryChildAxis, childWM);
7085 auto spaceToFill = sz.mBase - size;
7086 if (spaceToFill > nscoord(0)) {
7087 const auto& marginStyle = child->StyleMargin();
7088 if (marginStyle->mMargin.Start(masonryChildAxis, childWM)
7089 .IsAuto()) {
7090 if (marginStyle->mMargin.End(masonryChildAxis, childWM)
7091 .IsAuto()) {
7092 nscoord half;
7093 nscoord roundingError = NSCoordDivRem(spaceToFill, 2, &half);
7094 margin.Start(masonryChildAxis, childWM) = half;
7095 margin.End(masonryChildAxis, childWM) = half + roundingError;
7096 } else {
7097 margin.Start(masonryChildAxis, childWM) = spaceToFill;
7099 } else {
7100 MOZ_ASSERT(
7101 marginStyle->mMargin.End(masonryChildAxis, childWM).IsAuto());
7102 margin.End(masonryChildAxis, childWM) = spaceToFill;
7104 nsMargin* propValue =
7105 child->GetProperty(nsIFrame::UsedMarginProperty());
7106 if (propValue) {
7107 *propValue = margin.GetPhysicalMargin(childWM);
7108 } else {
7109 child->AddProperty(
7110 nsIFrame::UsedMarginProperty(),
7111 new nsMargin(margin.GetPhysicalMargin(childWM)));
7115 nscoord newPos = contentAreaStart + masonryBoxOffset + sz.mPosition +
7116 margin.Start(masonryChildAxis, childWM);
7117 LogicalPoint pos = child->GetLogicalNormalPosition(wm, aContainerSize);
7118 auto delta = newPos - pos.Pos(masonryAxis, wm);
7119 if (delta != 0 || forceReposition) {
7120 LogicalPoint logicalDelta(wm);
7121 logicalDelta.Pos(masonryAxis, wm) = delta;
7122 child->MovePositionBy(wm, logicalDelta);
7125 } else if (masonryBoxOffset != nscoord(0)) {
7126 // TODO move placeholders too
7127 auto delta = masonryBoxOffset;
7128 LogicalPoint logicalDelta(wm);
7129 logicalDelta.Pos(masonryAxis, wm) = delta;
7130 for (const auto& item : mGridItems) {
7131 if (item.mArea.LineRangeForAxis(gridAxis).mStart != i) {
7132 continue;
7134 item.mFrame->MovePositionBy(wm, logicalDelta);
7138 masonryAxisTracks.mSizes = std::move(savedSizes);
7142 * Return a Fragmentainer object if we have a fragmentainer frame in our
7143 * ancestor chain of containing block (CB) reflow inputs. We'll only
7144 * continue traversing the ancestor chain as long as the CBs have
7145 * the same writing-mode and have overflow:visible.
7147 Maybe<nsGridContainerFrame::Fragmentainer>
7148 nsGridContainerFrame::GetNearestFragmentainer(
7149 const GridReflowInput& aState) const {
7150 Maybe<nsGridContainerFrame::Fragmentainer> data;
7151 const ReflowInput* gridRI = aState.mReflowInput;
7152 if (!gridRI->IsInFragmentedContext()) {
7153 return data;
7155 WritingMode wm = aState.mWM;
7156 const ReflowInput* cbRI = gridRI->mCBReflowInput;
7157 for (; cbRI; cbRI = cbRI->mCBReflowInput) {
7158 nsIScrollableFrame* sf = do_QueryFrame(cbRI->mFrame);
7159 if (sf) {
7160 break;
7162 if (wm.IsOrthogonalTo(cbRI->GetWritingMode())) {
7163 break;
7165 LayoutFrameType frameType = cbRI->mFrame->Type();
7166 if ((frameType == LayoutFrameType::Canvas &&
7167 PresContext()->IsPaginated()) ||
7168 frameType == LayoutFrameType::ColumnSet) {
7169 data.emplace();
7170 data->mIsTopOfPage = gridRI->mFlags.mIsTopOfPage;
7171 if (gridRI->AvailableBSize() != NS_UNCONSTRAINEDSIZE) {
7172 data->mToFragmentainerEnd = aState.mFragBStart +
7173 gridRI->AvailableBSize() -
7174 aState.mBorderPadding.BStart(wm);
7175 } else {
7176 // This occurs when nsColumnSetFrame reflows its last column in
7177 // unconstrained available block-size.
7178 data->mToFragmentainerEnd = NS_UNCONSTRAINEDSIZE;
7180 const auto numRows = aState.mRows.mSizes.Length();
7181 data->mCanBreakAtStart =
7182 numRows > 0 && aState.mRows.mSizes[0].mPosition > 0;
7183 nscoord bSize = gridRI->ComputedBSize();
7184 data->mIsAutoBSize = bSize == NS_UNCONSTRAINEDSIZE;
7185 if (data->mIsAutoBSize) {
7186 bSize = gridRI->ComputedMinBSize();
7187 } else {
7188 bSize = NS_CSS_MINMAX(bSize, gridRI->ComputedMinBSize(),
7189 gridRI->ComputedMaxBSize());
7191 nscoord gridEnd =
7192 aState.mRows.GridLineEdge(numRows, GridLineSide::BeforeGridGap);
7193 data->mCanBreakAtEnd = bSize > gridEnd && bSize > aState.mFragBStart;
7194 break;
7197 return data;
7200 void nsGridContainerFrame::ReflowInFlowChild(
7201 nsIFrame* aChild, const GridItemInfo* aGridItemInfo, nsSize aContainerSize,
7202 const Maybe<nscoord>& aStretchBSize, const Fragmentainer* aFragmentainer,
7203 const GridReflowInput& aState, const LogicalRect& aContentArea,
7204 ReflowOutput& aDesiredSize, nsReflowStatus& aStatus) {
7205 nsPresContext* pc = PresContext();
7206 ComputedStyle* containerSC = Style();
7207 WritingMode wm = aState.mReflowInput->GetWritingMode();
7208 const bool isGridItem = !!aGridItemInfo;
7209 MOZ_ASSERT(isGridItem == !aChild->IsPlaceholderFrame());
7210 LogicalRect cb(wm);
7211 WritingMode childWM = aChild->GetWritingMode();
7212 bool isConstrainedBSize = false;
7213 nscoord toFragmentainerEnd;
7214 // The part of the child's grid area that's in previous container fragments.
7215 nscoord consumedGridAreaBSize = 0;
7216 const bool isOrthogonal = wm.IsOrthogonalTo(childWM);
7217 if (MOZ_LIKELY(isGridItem)) {
7218 MOZ_ASSERT(aGridItemInfo->mFrame == aChild);
7219 const GridArea& area = aGridItemInfo->mArea;
7220 MOZ_ASSERT(area.IsDefinite());
7221 cb = aState.ContainingBlockFor(area);
7222 if (aFragmentainer && !wm.IsOrthogonalTo(childWM)) {
7223 // |gridAreaBOffset| is the offset of the child's grid area in this
7224 // container fragment (if negative, that distance is the child CB size
7225 // consumed in previous container fragments). Note that cb.BStart
7226 // (initially) and aState.mFragBStart are in "global" grid coordinates
7227 // (like all track positions).
7228 nscoord gridAreaBOffset = cb.BStart(wm) - aState.mFragBStart;
7229 consumedGridAreaBSize = std::max(0, -gridAreaBOffset);
7230 cb.BStart(wm) = std::max(0, gridAreaBOffset);
7231 if (aFragmentainer->mToFragmentainerEnd != NS_UNCONSTRAINEDSIZE) {
7232 toFragmentainerEnd = aFragmentainer->mToFragmentainerEnd -
7233 aState.mFragBStart - cb.BStart(wm);
7234 toFragmentainerEnd = std::max(toFragmentainerEnd, 0);
7235 isConstrainedBSize = true;
7238 cb += aContentArea.Origin(wm);
7239 aState.mRows.AlignBaselineSubtree(*aGridItemInfo);
7240 aState.mCols.AlignBaselineSubtree(*aGridItemInfo);
7241 // Setup [align|justify]-content:[last ]baseline related frame properties.
7242 // These are added to the padding in SizeComputationInput::InitOffsets.
7243 // (a negative value signals the value is for 'last baseline' and should be
7244 // added to the (logical) end padding)
7245 typedef const FramePropertyDescriptor<SmallValueHolder<nscoord>>* Prop;
7246 auto SetProp = [aGridItemInfo, aChild](LogicalAxis aGridAxis, Prop aProp) {
7247 auto state = aGridItemInfo->mState[aGridAxis];
7248 auto baselineAdjust = (state & ItemState::eContentBaseline)
7249 ? aGridItemInfo->mBaselineOffset[aGridAxis]
7250 : nscoord(0);
7251 if (baselineAdjust < nscoord(0)) {
7252 // This happens when the subtree overflows its track.
7253 // XXX spec issue? it's unclear how to handle this.
7254 baselineAdjust = nscoord(0);
7255 } else if (GridItemInfo::BaselineAlignmentAffectsEndSide(state)) {
7256 baselineAdjust = -baselineAdjust;
7258 if (baselineAdjust != nscoord(0)) {
7259 aChild->SetProperty(aProp, baselineAdjust);
7260 } else {
7261 aChild->RemoveProperty(aProp);
7264 SetProp(eLogicalAxisBlock,
7265 isOrthogonal ? IBaselinePadProperty() : BBaselinePadProperty());
7266 SetProp(eLogicalAxisInline,
7267 isOrthogonal ? BBaselinePadProperty() : IBaselinePadProperty());
7268 } else {
7269 // By convention, for frames that perform CSS Box Alignment, we position
7270 // placeholder children at the start corner of their alignment container,
7271 // and in this case that's usually the grid's content-box.
7272 // ("Usually" - the exception is when the grid *also* forms the
7273 // abs.pos. containing block. In that case, the alignment container isn't
7274 // the content-box -- it's some grid area instead. But that case doesn't
7275 // require any special handling here, because we handle it later using a
7276 // special flag (ReflowInput::InitFlag::StaticPosIsCBOrigin) which will make
7277 // us ignore the placeholder's position entirely.)
7278 cb = aContentArea;
7279 aChild->AddStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN);
7282 LogicalSize reflowSize(cb.Size(wm));
7283 if (isConstrainedBSize) {
7284 reflowSize.BSize(wm) = toFragmentainerEnd;
7286 LogicalSize childCBSize = reflowSize.ConvertTo(childWM, wm);
7288 // Setup the ClampMarginBoxMinSize reflow flags and property, if needed.
7289 ComputeSizeFlags csFlags;
7290 if (aGridItemInfo) {
7291 // AlignJustifyTracksInMasonryAxis stretches items in a masonry-axis so we
7292 // don't do that here.
7293 auto* pos = aChild->StylePosition();
7294 auto j = IsMasonry(eLogicalAxisInline) ? StyleAlignFlags::START
7295 : pos->UsedJustifySelf(Style())._0;
7296 auto a = IsMasonry(eLogicalAxisBlock) ? StyleAlignFlags::START
7297 : pos->UsedAlignSelf(Style())._0;
7298 bool stretch[2];
7299 stretch[eLogicalAxisInline] =
7300 j == StyleAlignFlags::NORMAL || j == StyleAlignFlags::STRETCH;
7301 stretch[eLogicalAxisBlock] =
7302 a == StyleAlignFlags::NORMAL || a == StyleAlignFlags::STRETCH;
7303 auto childIAxis = isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline;
7304 // Clamp during reflow if we're stretching in that axis.
7305 if (stretch[childIAxis]) {
7306 if (aGridItemInfo->mState[childIAxis] &
7307 ItemState::eClampMarginBoxMinSize) {
7308 csFlags += ComputeSizeFlag::IClampMarginBoxMinSize;
7310 } else {
7311 csFlags += ComputeSizeFlag::ShrinkWrap;
7314 auto childBAxis = GetOrthogonalAxis(childIAxis);
7315 if (stretch[childBAxis] &&
7316 aGridItemInfo->mState[childBAxis] & ItemState::eClampMarginBoxMinSize) {
7317 csFlags += ComputeSizeFlag::BClampMarginBoxMinSize;
7318 aChild->SetProperty(BClampMarginBoxMinSizeProperty(),
7319 childCBSize.BSize(childWM));
7320 } else {
7321 aChild->RemoveProperty(BClampMarginBoxMinSizeProperty());
7324 if ((aGridItemInfo->mState[childIAxis] & ItemState::eApplyAutoMinSize)) {
7325 csFlags += ComputeSizeFlag::IApplyAutoMinSize;
7329 if (!isConstrainedBSize) {
7330 childCBSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE;
7332 LogicalSize percentBasis(cb.Size(wm).ConvertTo(childWM, wm));
7333 ReflowInput childRI(pc, *aState.mReflowInput, aChild, childCBSize,
7334 Some(percentBasis), {}, {}, csFlags);
7335 childRI.mFlags.mIsTopOfPage =
7336 aFragmentainer ? aFragmentainer->mIsTopOfPage : false;
7338 // FIXME (perf): It would be faster to do this only if the previous reflow of
7339 // the child was a measuring reflow, and only if the child does some of the
7340 // things that are affected by ComputeSizeFlag::IsGridMeasuringReflow.
7341 childRI.SetBResize(true);
7342 childRI.mFlags.mIsBResizeForPercentages = true;
7344 // If the child is stretching in its block axis, and we might be fragmenting
7345 // it in that axis, then setup a frame property to tell
7346 // nsBlockFrame::ComputeFinalSize the size.
7347 if (isConstrainedBSize && !wm.IsOrthogonalTo(childWM)) {
7348 bool stretch = false;
7349 if (!childRI.mStyleMargin->HasBlockAxisAuto(childWM) &&
7350 childRI.mStylePosition->BSize(childWM).IsAuto()) {
7351 auto blockAxisAlignment = childRI.mStylePosition->UsedAlignSelf(Style());
7352 if (!IsMasonry(eLogicalAxisBlock) &&
7353 (blockAxisAlignment._0 == StyleAlignFlags::NORMAL ||
7354 blockAxisAlignment._0 == StyleAlignFlags::STRETCH)) {
7355 stretch = true;
7358 if (stretch) {
7359 aChild->SetProperty(FragStretchBSizeProperty(), *aStretchBSize);
7360 } else {
7361 aChild->RemoveProperty(FragStretchBSizeProperty());
7365 // We need the width of the child before we can correctly convert
7366 // the writing-mode of its origin, so we reflow at (0, 0) using a dummy
7367 // aContainerSize, and then pass the correct position to FinishReflowChild.
7368 ReflowOutput childSize(childRI);
7369 const nsSize dummyContainerSize;
7371 // XXXdholbert The childPos that we use for ReflowChild shouldn't matter,
7372 // since we finalize it in FinishReflowChild. However, it does matter if the
7373 // child happens to be XUL (which sizes menu popup frames based on the
7374 // position within the viewport, during this ReflowChild call). So we make an
7375 // educated guess that the child will be at the origin of its containing
7376 // block, and then use align/justify to correct that as-needed further
7377 // down. (If the child has a different writing mode than its parent, though,
7378 // then we can't express the CB origin until we've reflowed the child and
7379 // determined its size. In that case, we throw up our hands and don't bother
7380 // trying to guess the position up-front after all.)
7381 // XXXdholbert We'll remove this special case in bug 1600542, and then we can
7382 // go back to just setting childPos in a single call after ReflowChild.
7383 LogicalPoint childPos(childWM);
7384 if (MOZ_LIKELY(childWM == wm)) {
7385 // Initially, assume the child will be at the containing block origin.
7386 // (This may get corrected during alignment/justification below.)
7387 childPos = cb.Origin(wm);
7389 ReflowChild(aChild, pc, childSize, childRI, childWM, childPos,
7390 dummyContainerSize, ReflowChildFlags::Default, aStatus);
7391 if (MOZ_UNLIKELY(childWM != wm)) {
7392 // As above: assume the child will be at the containing block origin.
7393 // (which we can now compute in terms of the childWM, now that we know the
7394 // child's size).
7395 childPos = cb.Origin(wm).ConvertTo(
7396 childWM, wm, aContainerSize - childSize.PhysicalSize());
7398 // Apply align/justify-self and reflow again if that affects the size.
7399 if (MOZ_LIKELY(isGridItem)) {
7400 LogicalSize size = childSize.Size(childWM); // from the ReflowChild()
7401 auto applyItemSelfAlignment = [&](LogicalAxis aAxis, nscoord aCBSize) {
7402 auto align =
7403 childRI.mStylePosition->UsedSelfAlignment(aAxis, containerSC);
7404 auto state = aGridItemInfo->mState[aAxis];
7405 auto flags = AlignJustifyFlags::NoFlags;
7406 if (IsMasonry(aAxis)) {
7407 // In a masonry axis, we inhibit applying 'stretch' and auto-margins
7408 // here since AlignJustifyTracksInMasonryAxis deals with that.
7409 // The only other {align,justify}-{self,content} values that have an
7410 // effect are '[last] baseline', the rest behave as 'start'.
7411 if (MOZ_LIKELY(!(state & ItemState::eSelfBaseline))) {
7412 align = {StyleAlignFlags::START};
7413 } else {
7414 auto group = (state & ItemState::eFirstBaseline)
7415 ? BaselineSharingGroup::First
7416 : BaselineSharingGroup::Last;
7417 auto itemStart = aGridItemInfo->mArea.LineRangeForAxis(aAxis).mStart;
7418 aCBSize = aState.TracksFor(aAxis)
7419 .mSizes[itemStart]
7420 .mBaselineSubtreeSize[group];
7422 flags = AlignJustifyFlags::IgnoreAutoMargins;
7423 } else if (state & ItemState::eContentBaseline) {
7424 align = {(state & ItemState::eFirstBaseline)
7425 ? StyleAlignFlags::SELF_START
7426 : StyleAlignFlags::SELF_END};
7428 if (aAxis == eLogicalAxisBlock) {
7429 AlignSelf(*aGridItemInfo, align, aCBSize, wm, childRI, size, flags,
7430 &childPos);
7431 } else {
7432 JustifySelf(*aGridItemInfo, align, aCBSize, wm, childRI, size, flags,
7433 &childPos);
7436 if (aStatus.IsComplete()) {
7437 applyItemSelfAlignment(eLogicalAxisBlock,
7438 cb.BSize(wm) - consumedGridAreaBSize);
7440 applyItemSelfAlignment(eLogicalAxisInline, cb.ISize(wm));
7441 } // else, nsAbsoluteContainingBlock.cpp will handle align/justify-self.
7443 FinishReflowChild(aChild, pc, childSize, &childRI, childWM, childPos,
7444 aContainerSize, ReflowChildFlags::ApplyRelativePositioning);
7445 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, aChild);
7448 nscoord nsGridContainerFrame::ReflowInFragmentainer(
7449 GridReflowInput& aState, const LogicalRect& aContentArea,
7450 ReflowOutput& aDesiredSize, nsReflowStatus& aStatus,
7451 Fragmentainer& aFragmentainer, const nsSize& aContainerSize) {
7452 MOZ_ASSERT(aStatus.IsEmpty());
7453 MOZ_ASSERT(aState.mReflowInput);
7455 // Collect our grid items and sort them in row order. Collect placeholders
7456 // and put them in a separate array.
7457 nsTArray<const GridItemInfo*> sortedItems(aState.mGridItems.Length());
7458 nsTArray<nsIFrame*> placeholders(aState.mAbsPosItems.Length());
7459 aState.mIter.Reset(CSSOrderAwareFrameIterator::ChildFilter::IncludeAll);
7460 for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
7461 nsIFrame* child = *aState.mIter;
7462 if (!child->IsPlaceholderFrame()) {
7463 const GridItemInfo* info = &aState.mGridItems[aState.mIter.ItemIndex()];
7464 sortedItems.AppendElement(info);
7465 } else {
7466 placeholders.AppendElement(child);
7469 // NOTE: We don't need stable_sort here, except in Masonry layout. There are
7470 // no dependencies on having content order between items on the same row in
7471 // the code below in the non-Masonry case.
7472 if (IsMasonry()) {
7473 std::stable_sort(sortedItems.begin(), sortedItems.end(),
7474 GridItemInfo::IsStartRowLessThan);
7475 } else {
7476 std::sort(sortedItems.begin(), sortedItems.end(),
7477 GridItemInfo::IsStartRowLessThan);
7480 // Reflow our placeholder children; they must all be complete.
7481 for (auto child : placeholders) {
7482 nsReflowStatus childStatus;
7483 ReflowInFlowChild(child, nullptr, aContainerSize, Nothing(),
7484 &aFragmentainer, aState, aContentArea, aDesiredSize,
7485 childStatus);
7486 MOZ_ASSERT(childStatus.IsComplete(),
7487 "nsPlaceholderFrame should never need to be fragmented");
7490 // The available size for children - we'll set this to the edge of the last
7491 // row in most cases below, but for now use the full size.
7492 nscoord childAvailableSize = aFragmentainer.mToFragmentainerEnd;
7493 const uint32_t startRow = aState.mStartRow;
7494 const uint32_t numRows = aState.mRows.mSizes.Length();
7495 bool isBDBClone = aState.mReflowInput->mStyleBorder->mBoxDecorationBreak ==
7496 StyleBoxDecorationBreak::Clone;
7497 nscoord bpBEnd = aState.mBorderPadding.BEnd(aState.mWM);
7499 // Set |endRow| to the first row that doesn't fit.
7500 uint32_t endRow = numRows;
7501 for (uint32_t row = startRow; row < numRows; ++row) {
7502 auto& sz = aState.mRows.mSizes[row];
7503 const nscoord bEnd = sz.mPosition + sz.mBase;
7504 nscoord remainingAvailableSize = childAvailableSize - bEnd;
7505 if (remainingAvailableSize < 0 ||
7506 (isBDBClone && remainingAvailableSize < bpBEnd)) {
7507 endRow = row;
7508 break;
7512 // Check for forced breaks on the items if available block-size for children
7513 // is constrained. That is, ignore forced breaks if available block-size for
7514 // children is unconstrained since our parent expected us to be fully
7515 // complete.
7516 bool isForcedBreak = false;
7517 const bool avoidBreakInside = ShouldAvoidBreakInside(*aState.mReflowInput);
7518 if (childAvailableSize != NS_UNCONSTRAINEDSIZE) {
7519 const bool isTopOfPage = aFragmentainer.mIsTopOfPage;
7520 for (const GridItemInfo* info : sortedItems) {
7521 uint32_t itemStartRow = info->mArea.mRows.mStart;
7522 if (itemStartRow == endRow) {
7523 break;
7525 const auto* disp = info->mFrame->StyleDisplay();
7526 if (disp->BreakBefore()) {
7527 // Propagate break-before on the first row to the container unless we're
7528 // already at top-of-page.
7529 if ((itemStartRow == 0 && !isTopOfPage) || avoidBreakInside) {
7530 aStatus.SetInlineLineBreakBeforeAndReset();
7531 return aState.mFragBStart;
7533 if ((itemStartRow > startRow ||
7534 (itemStartRow == startRow && !isTopOfPage)) &&
7535 itemStartRow < endRow) {
7536 endRow = itemStartRow;
7537 isForcedBreak = true;
7538 // reset any BREAK_AFTER we found on an earlier item
7539 aStatus.Reset();
7540 break; // we're done since the items are sorted in row order
7543 uint32_t itemEndRow = info->mArea.mRows.mEnd;
7544 if (disp->BreakAfter()) {
7545 if (itemEndRow != numRows) {
7546 if (itemEndRow > startRow && itemEndRow < endRow) {
7547 endRow = itemEndRow;
7548 isForcedBreak = true;
7549 // No "break;" here since later items with break-after may have
7550 // a shorter span.
7552 } else {
7553 // Propagate break-after on the last row to the container, we may
7554 // still find a break-before on this row though (and reset aStatus).
7555 aStatus.SetInlineLineBreakAfter(); // tentative
7560 // Consume at least one row in each fragment until we have consumed them
7561 // all. Except for the first row if there's a break opportunity before it.
7562 if (startRow == endRow && startRow != numRows &&
7563 (startRow != 0 || !aFragmentainer.mCanBreakAtStart)) {
7564 ++endRow;
7567 // Honor break-inside:avoid if we can't fit all rows.
7568 if (avoidBreakInside && endRow < numRows) {
7569 aStatus.SetInlineLineBreakBeforeAndReset();
7570 return aState.mFragBStart;
7574 // Calculate the block-size including this fragment.
7575 nscoord bEndRow =
7576 aState.mRows.GridLineEdge(endRow, GridLineSide::BeforeGridGap);
7577 nscoord bSize;
7578 if (aFragmentainer.mIsAutoBSize) {
7579 // We only apply min-bsize once all rows are complete (when bsize is auto).
7580 if (endRow < numRows) {
7581 bSize = bEndRow;
7582 auto clampedBSize = ClampToCSSMaxBSize(bSize, aState.mReflowInput);
7583 if (MOZ_UNLIKELY(clampedBSize != bSize)) {
7584 // We apply max-bsize in all fragments though.
7585 bSize = clampedBSize;
7586 } else if (!isBDBClone) {
7587 // The max-bsize won't make this fragment COMPLETE, so the block-end
7588 // border will be in a later fragment.
7589 bpBEnd = 0;
7591 } else {
7592 bSize = NS_CSS_MINMAX(bEndRow, aState.mReflowInput->ComputedMinBSize(),
7593 aState.mReflowInput->ComputedMaxBSize());
7595 } else {
7596 bSize = NS_CSS_MINMAX(aState.mReflowInput->ComputedBSize(),
7597 aState.mReflowInput->ComputedMinBSize(),
7598 aState.mReflowInput->ComputedMaxBSize());
7601 // Check for overflow and set aStatus INCOMPLETE if so.
7602 bool overflow = bSize + bpBEnd > childAvailableSize;
7603 if (overflow) {
7604 if (avoidBreakInside) {
7605 aStatus.SetInlineLineBreakBeforeAndReset();
7606 return aState.mFragBStart;
7608 bool breakAfterLastRow = endRow == numRows && aFragmentainer.mCanBreakAtEnd;
7609 if (breakAfterLastRow) {
7610 MOZ_ASSERT(bEndRow < bSize, "bogus aFragmentainer.mCanBreakAtEnd");
7611 nscoord availableSize = childAvailableSize;
7612 if (isBDBClone) {
7613 availableSize -= bpBEnd;
7615 // Pretend we have at least 1px available size, otherwise we'll never make
7616 // progress in consuming our bSize.
7617 availableSize =
7618 std::max(availableSize, aState.mFragBStart + AppUnitsPerCSSPixel());
7619 // Fill the fragmentainer, but not more than our desired block-size and
7620 // at least to the size of the last row (even if that overflows).
7621 nscoord newBSize = std::min(bSize, availableSize);
7622 newBSize = std::max(newBSize, bEndRow);
7623 // If it's just the border+padding that is overflowing and we have
7624 // box-decoration-break:clone then we are technically COMPLETE. There's
7625 // no point in creating another zero-bsize fragment in this case.
7626 if (newBSize < bSize || !isBDBClone) {
7627 aStatus.SetIncomplete();
7629 bSize = newBSize;
7630 } else if (bSize <= bEndRow && startRow + 1 < endRow) {
7631 if (endRow == numRows) {
7632 // We have more than one row in this fragment, so we can break before
7633 // the last row instead.
7634 --endRow;
7635 bEndRow =
7636 aState.mRows.GridLineEdge(endRow, GridLineSide::BeforeGridGap);
7637 bSize = bEndRow;
7638 if (aFragmentainer.mIsAutoBSize) {
7639 bSize = ClampToCSSMaxBSize(bSize, aState.mReflowInput);
7642 aStatus.SetIncomplete();
7643 } else if (endRow < numRows) {
7644 bSize = ClampToCSSMaxBSize(bEndRow, aState.mReflowInput, &aStatus);
7645 } // else - no break opportunities.
7646 } else {
7647 // Even though our block-size fits we need to honor forced breaks, or if
7648 // a row doesn't fit in an auto-sized container (unless it's constrained
7649 // by a max-bsize which make us overflow-incomplete).
7650 if (endRow < numRows &&
7651 (isForcedBreak || (aFragmentainer.mIsAutoBSize && bEndRow == bSize))) {
7652 bSize = ClampToCSSMaxBSize(bEndRow, aState.mReflowInput, &aStatus);
7656 // If we can't fit all rows then we're at least overflow-incomplete.
7657 if (endRow < numRows) {
7658 childAvailableSize = bEndRow;
7659 if (aStatus.IsComplete()) {
7660 aStatus.SetOverflowIncomplete();
7661 aStatus.SetNextInFlowNeedsReflow();
7663 } else {
7664 // Children always have the full size of the rows in this fragment.
7665 childAvailableSize = std::max(childAvailableSize, bEndRow);
7668 return ReflowRowsInFragmentainer(aState, aContentArea, aDesiredSize, aStatus,
7669 aFragmentainer, aContainerSize, sortedItems,
7670 startRow, endRow, bSize, childAvailableSize);
7673 nscoord nsGridContainerFrame::ReflowRowsInFragmentainer(
7674 GridReflowInput& aState, const LogicalRect& aContentArea,
7675 ReflowOutput& aDesiredSize, nsReflowStatus& aStatus,
7676 Fragmentainer& aFragmentainer, const nsSize& aContainerSize,
7677 const nsTArray<const GridItemInfo*>& aSortedItems, uint32_t aStartRow,
7678 uint32_t aEndRow, nscoord aBSize, nscoord aAvailableSize) {
7679 FrameHashtable pushedItems;
7680 FrameHashtable incompleteItems;
7681 FrameHashtable overflowIncompleteItems;
7682 Maybe<nsTArray<nscoord>> masonryAxisPos;
7683 const auto rowCount = aState.mRows.mSizes.Length();
7684 nscoord masonryAxisGap;
7685 const auto wm = aState.mWM;
7686 const bool isColMasonry = IsMasonry(eLogicalAxisInline);
7687 if (isColMasonry) {
7688 for (auto& sz : aState.mCols.mSizes) {
7689 sz.mPosition = 0;
7691 masonryAxisGap = nsLayoutUtils::ResolveGapToLength(
7692 aState.mGridStyle->mColumnGap, aContentArea.ISize(wm));
7693 aState.mCols.mGridGap = masonryAxisGap;
7694 masonryAxisPos.emplace(rowCount);
7695 masonryAxisPos->SetLength(rowCount);
7696 PodZero(masonryAxisPos->Elements(), rowCount);
7698 bool isBDBClone = aState.mReflowInput->mStyleBorder->mBoxDecorationBreak ==
7699 StyleBoxDecorationBreak::Clone;
7700 bool didGrowRow = false;
7701 // As we walk across rows, we track whether the current row is at the top
7702 // of its grid-fragment, to help decide whether we can break before it. When
7703 // this function starts, our row is at the top of the current fragment if:
7704 // - we're starting with a nonzero row (i.e. we're a continuation)
7705 // OR:
7706 // - we're starting with the first row, & we're not allowed to break before
7707 // it (which makes it effectively at the top of its grid-fragment).
7708 bool isRowTopOfPage = aStartRow != 0 || !aFragmentainer.mCanBreakAtStart;
7709 const bool isStartRowTopOfPage = isRowTopOfPage;
7710 // Save our full available size for later.
7711 const nscoord gridAvailableSize = aFragmentainer.mToFragmentainerEnd;
7712 // Propagate the constrained size to our children.
7713 aFragmentainer.mToFragmentainerEnd = aAvailableSize;
7714 // Reflow the items in row order up to |aEndRow| and push items after that.
7715 uint32_t row = 0;
7716 // |i| is intentionally signed, so we can set it to -1 to restart the loop.
7717 for (int32_t i = 0, len = aSortedItems.Length(); i < len; ++i) {
7718 const GridItemInfo* const info = aSortedItems[i];
7719 nsIFrame* child = info->mFrame;
7720 row = info->mArea.mRows.mStart;
7721 MOZ_ASSERT(child->GetPrevInFlow() ? row < aStartRow : row >= aStartRow,
7722 "unexpected child start row");
7723 if (row >= aEndRow) {
7724 pushedItems.Insert(child);
7725 continue;
7728 bool rowCanGrow = false;
7729 nscoord maxRowSize = 0;
7730 if (row >= aStartRow) {
7731 if (row > aStartRow) {
7732 isRowTopOfPage = false;
7734 // Can we grow this row? Only consider span=1 items per spec...
7735 rowCanGrow = !didGrowRow && info->mArea.mRows.Extent() == 1;
7736 if (rowCanGrow) {
7737 auto& sz = aState.mRows.mSizes[row];
7738 // and only min-/max-content rows or flex rows in an auto-sized
7739 // container
7740 rowCanGrow = (sz.mState & TrackSize::eMinOrMaxContentMinSizing) ||
7741 ((sz.mState & TrackSize::eFlexMaxSizing) &&
7742 aFragmentainer.mIsAutoBSize);
7743 if (rowCanGrow) {
7744 if (isBDBClone) {
7745 maxRowSize = gridAvailableSize - aState.mBorderPadding.BEnd(wm);
7746 } else {
7747 maxRowSize = gridAvailableSize;
7749 maxRowSize -= sz.mPosition;
7750 // ...and only if there is space for it to grow.
7751 rowCanGrow = maxRowSize > sz.mBase;
7756 if (isColMasonry) {
7757 const auto& cols = info->mArea.mCols;
7758 MOZ_ASSERT((cols.mStart == 0 || cols.mStart == 1) && cols.Extent() == 1);
7759 aState.mCols.mSizes[cols.mStart].mPosition = masonryAxisPos.ref()[row];
7762 // aFragmentainer.mIsTopOfPage is propagated to the child reflow input.
7763 // When it's false the child may request InlineBreak::Before. We set it
7764 // to false when the row is growable (as determined in the CSS Grid
7765 // Fragmentation spec) and there is a non-zero space between it and the
7766 // fragmentainer end (that can be used to grow it). If the child reports
7767 // a forced break in this case, we grow this row to fill the fragment and
7768 // restart the loop. We also restart the loop with |aEndRow = row|
7769 // (but without growing any row) for a InlineBreak::Before child if it spans
7770 // beyond the last row in this fragment. This is to avoid fragmenting it.
7771 // We only restart the loop once.
7772 aFragmentainer.mIsTopOfPage = isRowTopOfPage && !rowCanGrow;
7773 nsReflowStatus childStatus;
7774 // Pass along how much to stretch this fragment, in case it's needed.
7775 nscoord bSize =
7776 aState.mRows.GridLineEdge(std::min(aEndRow, info->mArea.mRows.mEnd),
7777 GridLineSide::BeforeGridGap) -
7778 aState.mRows.GridLineEdge(std::max(aStartRow, row),
7779 GridLineSide::AfterGridGap);
7780 ReflowInFlowChild(child, info, aContainerSize, Some(bSize), &aFragmentainer,
7781 aState, aContentArea, aDesiredSize, childStatus);
7782 MOZ_ASSERT(childStatus.IsInlineBreakBefore() ||
7783 !childStatus.IsFullyComplete() || !child->GetNextInFlow(),
7784 "fully-complete reflow should destroy any NIFs");
7786 if (childStatus.IsInlineBreakBefore()) {
7787 MOZ_ASSERT(
7788 !child->GetPrevInFlow(),
7789 "continuations should never report InlineBreak::Before status");
7790 MOZ_ASSERT(!aFragmentainer.mIsTopOfPage,
7791 "got IsInlineBreakBefore() at top of page");
7792 if (!didGrowRow) {
7793 if (rowCanGrow) {
7794 // Grow this row and restart with the next row as |aEndRow|.
7795 aState.mRows.ResizeRow(row, maxRowSize);
7796 if (aState.mSharedGridData) {
7797 aState.mSharedGridData->mRows.ResizeRow(row, maxRowSize);
7799 didGrowRow = true;
7800 aEndRow = row + 1; // growing this row makes the next one not fit
7801 i = -1; // i == 0 after the next loop increment
7802 isRowTopOfPage = isStartRowTopOfPage;
7803 overflowIncompleteItems.Clear();
7804 incompleteItems.Clear();
7805 nscoord bEndRow =
7806 aState.mRows.GridLineEdge(aEndRow, GridLineSide::BeforeGridGap);
7807 aFragmentainer.mToFragmentainerEnd = bEndRow;
7808 if (aFragmentainer.mIsAutoBSize) {
7809 aBSize = ClampToCSSMaxBSize(bEndRow, aState.mReflowInput, &aStatus);
7810 } else if (aStatus.IsIncomplete()) {
7811 aBSize = NS_CSS_MINMAX(aState.mReflowInput->ComputedBSize(),
7812 aState.mReflowInput->ComputedMinBSize(),
7813 aState.mReflowInput->ComputedMaxBSize());
7814 aBSize = std::min(bEndRow, aBSize);
7816 continue;
7819 if (!isRowTopOfPage) {
7820 // We can break before this row - restart with it as the new end row.
7821 aEndRow = row;
7822 aBSize =
7823 aState.mRows.GridLineEdge(aEndRow, GridLineSide::BeforeGridGap);
7824 i = -1; // i == 0 after the next loop increment
7825 isRowTopOfPage = isStartRowTopOfPage;
7826 overflowIncompleteItems.Clear();
7827 incompleteItems.Clear();
7828 aStatus.SetIncomplete();
7829 continue;
7831 NS_ERROR("got InlineBreak::Before at top-of-page");
7832 childStatus.Reset();
7833 } else {
7834 // We got InlineBreak::Before again after growing the row - this can
7835 // happen if the child isn't splittable, e.g. some form controls.
7836 childStatus.Reset();
7837 if (child->GetNextInFlow()) {
7838 // The child already has a fragment, so we know it's splittable.
7839 childStatus.SetIncomplete();
7840 } // else, report that it's complete
7842 } else if (childStatus.IsInlineBreakAfter()) {
7843 MOZ_ASSERT_UNREACHABLE("unexpected child reflow status");
7846 MOZ_ASSERT(!childStatus.IsInlineBreakBefore(),
7847 "should've handled InlineBreak::Before above");
7848 if (childStatus.IsIncomplete()) {
7849 incompleteItems.Insert(child);
7850 } else if (!childStatus.IsFullyComplete()) {
7851 overflowIncompleteItems.Insert(child);
7853 if (isColMasonry) {
7854 auto childWM = child->GetWritingMode();
7855 auto childAxis =
7856 !childWM.IsOrthogonalTo(wm) ? eLogicalAxisInline : eLogicalAxisBlock;
7857 auto normalPos = child->GetLogicalNormalPosition(wm, aContainerSize);
7858 auto sz =
7859 childAxis == eLogicalAxisBlock ? child->BSize() : child->ISize();
7860 auto pos = normalPos.Pos(eLogicalAxisInline, wm) + sz +
7861 child->GetLogicalUsedMargin(childWM).End(childAxis, childWM);
7862 masonryAxisPos.ref()[row] =
7863 pos + masonryAxisGap - aContentArea.Start(eLogicalAxisInline, wm);
7867 // Record a break before |aEndRow|.
7868 aState.mNextFragmentStartRow = aEndRow;
7869 if (aEndRow < rowCount) {
7870 aState.mRows.BreakBeforeRow(aEndRow);
7871 if (aState.mSharedGridData) {
7872 aState.mSharedGridData->mRows.BreakBeforeRow(aEndRow);
7876 const bool childrenMoved = PushIncompleteChildren(
7877 pushedItems, incompleteItems, overflowIncompleteItems);
7878 if (childrenMoved && aStatus.IsComplete()) {
7879 aStatus.SetOverflowIncomplete();
7880 aStatus.SetNextInFlowNeedsReflow();
7882 if (!pushedItems.IsEmpty()) {
7883 AddStateBits(NS_STATE_GRID_DID_PUSH_ITEMS);
7884 // NOTE since we messed with our child list here, we intentionally
7885 // make aState.mIter invalid to avoid any use of it after this point.
7886 aState.mIter.Invalidate();
7888 if (!incompleteItems.IsEmpty()) {
7889 // NOTE since we messed with our child list here, we intentionally
7890 // make aState.mIter invalid to avoid any use of it after this point.
7891 aState.mIter.Invalidate();
7894 if (isColMasonry) {
7895 nscoord maxSize = 0;
7896 for (auto pos : masonryAxisPos.ref()) {
7897 maxSize = std::max(maxSize, pos);
7899 maxSize = std::max(nscoord(0), maxSize - masonryAxisGap);
7900 aState.AlignJustifyContentInMasonryAxis(maxSize, aContentArea.ISize(wm));
7903 return aBSize;
7906 // Here's a brief overview of how Masonry layout is implemented:
7907 // We setup two synthetic tracks in the Masonry axis so that the Reflow code
7908 // can treat it the same as for normal grid layout. The first track is
7909 // fixed (during item placement/layout) at the content box start and contains
7910 // the start items for each grid-axis track. The second track contains
7911 // all other items and is moved to the position where we want to position
7912 // the currently laid out item (like a sliding window as we place items).
7913 // Once item layout is done, the tracks are resized to be the size of
7914 // the "masonry box", which is the offset from the content box start to
7915 // the margin-box end of the item that is furthest away (this happens in
7916 // AlignJustifyContentInMasonryAxis() called at the end of this method).
7917 // This is to prepare for AlignJustifyTracksInMasonryAxis, which is called
7918 // later by our caller.
7919 // Both tracks store their first-/last-baseline group offsets as usual.
7920 // The first-baseline of the start track, and the last-baseline of the last
7921 // track (if they exist) are exported as the grid container's baselines, or
7922 // we fall back to picking an item's baseline (all this is per normal grid
7923 // layout). There's a slight difference in which items belongs to which
7924 // group though - see InitializeItemBaselinesInMasonryAxis for details.
7925 // This method returns the "masonry box" size (in the masonry axis).
7926 nscoord nsGridContainerFrame::MasonryLayout(GridReflowInput& aState,
7927 const LogicalRect& aContentArea,
7928 SizingConstraint aConstraint,
7929 ReflowOutput& aDesiredSize,
7930 nsReflowStatus& aStatus,
7931 Fragmentainer* aFragmentainer,
7932 const nsSize& aContainerSize) {
7933 using BaselineAlignmentSet = Tracks::BaselineAlignmentSet;
7935 auto recordAutoPlacement = [this, &aState](GridItemInfo* aItem,
7936 LogicalAxis aGridAxis) {
7937 // When we're auto-placing an item in a continuation we need to record
7938 // the placement in mSharedGridData.
7939 if (MOZ_UNLIKELY(aState.mSharedGridData && GetPrevInFlow()) &&
7940 (aItem->mState[aGridAxis] & ItemState::eAutoPlacement)) {
7941 auto* child = aItem->mFrame;
7942 MOZ_RELEASE_ASSERT(!child->GetPrevInFlow(),
7943 "continuations should never be auto-placed");
7944 for (auto& sharedItem : aState.mSharedGridData->mGridItems) {
7945 if (sharedItem.mFrame == child) {
7946 sharedItem.mArea.LineRangeForAxis(aGridAxis) =
7947 aItem->mArea.LineRangeForAxis(aGridAxis);
7948 MOZ_ASSERT(sharedItem.mState[aGridAxis] & ItemState::eAutoPlacement);
7949 sharedItem.mState[aGridAxis] &= ~ItemState::eAutoPlacement;
7950 break;
7954 aItem->mState[aGridAxis] &= ~ItemState::eAutoPlacement;
7957 // Collect our grid items and sort them in grid order.
7958 nsTArray<GridItemInfo*> sortedItems(aState.mGridItems.Length());
7959 aState.mIter.Reset(CSSOrderAwareFrameIterator::ChildFilter::IncludeAll);
7960 size_t absposIndex = 0;
7961 const LogicalAxis masonryAxis =
7962 IsMasonry(eLogicalAxisBlock) ? eLogicalAxisBlock : eLogicalAxisInline;
7963 const auto wm = aState.mWM;
7964 for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
7965 nsIFrame* child = *aState.mIter;
7966 if (MOZ_LIKELY(!child->IsPlaceholderFrame())) {
7967 GridItemInfo* item = &aState.mGridItems[aState.mIter.ItemIndex()];
7968 sortedItems.AppendElement(item);
7969 } else if (aConstraint == SizingConstraint::NoConstraint) {
7970 // (we only collect placeholders in the NoConstraint case since they
7971 // don't affect intrinsic sizing in any way)
7972 GridItemInfo* item = nullptr;
7973 auto* ph = static_cast<nsPlaceholderFrame*>(child);
7974 if (ph->GetOutOfFlowFrame()->GetParent() == this) {
7975 item = &aState.mAbsPosItems[absposIndex++];
7976 MOZ_RELEASE_ASSERT(item->mFrame == ph->GetOutOfFlowFrame());
7977 auto masonryStart = item->mArea.LineRangeForAxis(masonryAxis).mStart;
7978 // If the item was placed by the author at line 1 (masonryStart == 0)
7979 // then include it to be placed at the masonry-box start. If it's
7980 // auto-placed and has an `auto` inset value in the masonry axis then
7981 // we include it to be placed after the last grid item with the same
7982 // grid-axis start track.
7983 // XXXmats this is all a bit experimental at this point, pending a spec
7984 if (masonryStart == 0 ||
7985 (masonryStart == kAutoLine && item->mFrame->StylePosition()
7986 ->mOffset.Start(masonryAxis, wm)
7987 .IsAuto())) {
7988 sortedItems.AppendElement(item);
7989 } else {
7990 item = nullptr;
7993 if (!item) {
7994 // It wasn't included above - just reflow it and be done with it.
7995 nsReflowStatus childStatus;
7996 ReflowInFlowChild(child, nullptr, aContainerSize, Nothing(), nullptr,
7997 aState, aContentArea, aDesiredSize, childStatus);
8001 const auto masonryAutoFlow = aState.mGridStyle->mMasonryAutoFlow;
8002 const bool definiteFirst =
8003 masonryAutoFlow.order == StyleMasonryItemOrder::DefiniteFirst;
8004 if (masonryAxis == eLogicalAxisBlock) {
8005 std::stable_sort(sortedItems.begin(), sortedItems.end(),
8006 definiteFirst ? GridItemInfo::RowMasonryDefiniteFirst
8007 : GridItemInfo::RowMasonryOrdered);
8008 } else {
8009 std::stable_sort(sortedItems.begin(), sortedItems.end(),
8010 definiteFirst ? GridItemInfo::ColMasonryDefiniteFirst
8011 : GridItemInfo::ColMasonryOrdered);
8014 FrameHashtable pushedItems;
8015 FrameHashtable incompleteItems;
8016 FrameHashtable overflowIncompleteItems;
8017 nscoord toFragmentainerEnd = nscoord_MAX;
8018 nscoord fragStartPos = aState.mFragBStart;
8019 const bool avoidBreakInside =
8020 aFragmentainer && ShouldAvoidBreakInside(*aState.mReflowInput);
8021 const bool isTopOfPageAtStart =
8022 aFragmentainer && aFragmentainer->mIsTopOfPage;
8023 if (aFragmentainer) {
8024 toFragmentainerEnd = std::max(0, aFragmentainer->mToFragmentainerEnd);
8026 const LogicalAxis gridAxis = GetOrthogonalAxis(masonryAxis);
8027 const auto gridAxisTrackCount = aState.TracksFor(gridAxis).mSizes.Length();
8028 auto& masonryTracks = aState.TracksFor(masonryAxis);
8029 auto& masonrySizes = masonryTracks.mSizes;
8030 MOZ_ASSERT(masonrySizes.Length() == 2);
8031 for (auto& sz : masonrySizes) {
8032 sz.mPosition = fragStartPos;
8034 // The current running position for each grid-axis track where the next item
8035 // should be positioned. When an item is placed we'll update the tracks it
8036 // spans to the end of its margin box + 'gap'.
8037 nsTArray<nscoord> currentPos(gridAxisTrackCount);
8038 currentPos.SetLength(gridAxisTrackCount);
8039 for (auto& sz : currentPos) {
8040 sz = fragStartPos;
8042 nsTArray<nscoord> lastPos(currentPos.Clone());
8043 nsTArray<GridItemInfo*> lastItems(gridAxisTrackCount);
8044 lastItems.SetLength(gridAxisTrackCount);
8045 PodZero(lastItems.Elements(), gridAxisTrackCount);
8046 const nscoord gap = nsLayoutUtils::ResolveGapToLength(
8047 masonryAxis == eLogicalAxisBlock ? aState.mGridStyle->mRowGap
8048 : aState.mGridStyle->mColumnGap,
8049 masonryTracks.mContentBoxSize);
8050 masonryTracks.mGridGap = gap;
8051 uint32_t cursor = 0;
8052 const auto containerToMasonryBoxOffset =
8053 fragStartPos - aContentArea.Start(masonryAxis, wm);
8054 const bool isPack = masonryAutoFlow.placement == StyleMasonryPlacement::Pack;
8055 bool didAlignStartAlignedFirstItems = false;
8057 // Return true if any of the lastItems in aRange are baseline-aligned in
8058 // the masonry axis.
8059 auto lastItemHasBaselineAlignment = [&](const LineRange& aRange) {
8060 for (auto i : aRange.Range()) {
8061 if (auto* child = lastItems[i] ? lastItems[i]->mFrame : nullptr) {
8062 const auto& pos = child->StylePosition();
8063 auto selfAlignment = pos->UsedSelfAlignment(masonryAxis, this->Style());
8064 if (selfAlignment == StyleAlignFlags::BASELINE ||
8065 selfAlignment == StyleAlignFlags::LAST_BASELINE) {
8066 return true;
8068 auto childAxis = masonryAxis;
8069 if (child->GetWritingMode().IsOrthogonalTo(wm)) {
8070 childAxis = gridAxis;
8072 auto contentAlignment = pos->UsedContentAlignment(childAxis).primary;
8073 if (contentAlignment == StyleAlignFlags::BASELINE ||
8074 contentAlignment == StyleAlignFlags::LAST_BASELINE) {
8075 return true;
8079 return false;
8082 // Resolve aItem's placement, unless it's definite already. Return its
8083 // masonry axis position with that placement.
8084 auto placeItem = [&](GridItemInfo* aItem) -> nscoord {
8085 auto& masonryAxisRange = aItem->mArea.LineRangeForAxis(masonryAxis);
8086 MOZ_ASSERT(masonryAxisRange.mStart != 0, "item placement is already final");
8087 auto& gridAxisRange = aItem->mArea.LineRangeForAxis(gridAxis);
8088 bool isAutoPlaced = aItem->mState[gridAxis] & ItemState::eAutoPlacement;
8089 uint32_t start = isAutoPlaced ? 0 : gridAxisRange.mStart;
8090 if (isAutoPlaced && !isPack) {
8091 start = cursor;
8092 isAutoPlaced = false;
8094 const uint32_t extent = gridAxisRange.Extent();
8095 if (start + extent > gridAxisTrackCount) {
8096 // Note that this will only happen to auto-placed items since the grid is
8097 // always wide enough to fit other items.
8098 start = 0;
8100 // This keeps track of the smallest `maxPosForRange` value that
8101 // we discover in the loop below:
8102 nscoord minPos = nscoord_MAX;
8103 MOZ_ASSERT(extent <= gridAxisTrackCount);
8104 const uint32_t iEnd = gridAxisTrackCount + 1 - extent;
8105 for (uint32_t i = start; i < iEnd; ++i) {
8106 // Find the max `currentPos` value for the tracks that we would span
8107 // if we were to use `i` as our start track:
8108 nscoord maxPosForRange = 0;
8109 for (auto j = i, jEnd = j + extent; j < jEnd; ++j) {
8110 maxPosForRange = std::max(currentPos[j], maxPosForRange);
8112 if (maxPosForRange < minPos) {
8113 minPos = maxPosForRange;
8114 start = i;
8116 if (!isAutoPlaced) {
8117 break;
8120 gridAxisRange.mStart = start;
8121 gridAxisRange.mEnd = start + extent;
8122 bool isFirstItem = true;
8123 for (uint32_t i : gridAxisRange.Range()) {
8124 if (lastItems[i]) {
8125 isFirstItem = false;
8126 break;
8129 // If this is the first item in its spanned grid tracks, then place it in
8130 // the first masonry track. Otherwise, place it in the second masonry track.
8131 masonryAxisRange.mStart = isFirstItem ? 0 : 1;
8132 masonryAxisRange.mEnd = masonryAxisRange.mStart + 1;
8133 return minPos;
8136 // Handle the resulting reflow status after reflowing aItem.
8137 // This may set aStatus to BreakBefore which the caller is expected
8138 // to handle by returning from MasonryLayout.
8139 // @return true if this item should consume all remaining space
8140 auto handleChildStatus = [&](GridItemInfo* aItem,
8141 const nsReflowStatus& aChildStatus) {
8142 bool result = false;
8143 if (MOZ_UNLIKELY(aFragmentainer)) {
8144 auto* child = aItem->mFrame;
8145 if (!aChildStatus.IsComplete() || aChildStatus.IsInlineBreakBefore() ||
8146 aChildStatus.IsInlineBreakAfter() ||
8147 child->StyleDisplay()->BreakAfter()) {
8148 if (!isTopOfPageAtStart && avoidBreakInside) {
8149 aStatus.SetInlineLineBreakBeforeAndReset();
8150 return result;
8152 result = true;
8154 if (aChildStatus.IsInlineBreakBefore()) {
8155 aStatus.SetIncomplete();
8156 pushedItems.Insert(child);
8157 } else if (aChildStatus.IsIncomplete()) {
8158 recordAutoPlacement(aItem, gridAxis);
8159 aStatus.SetIncomplete();
8160 incompleteItems.Insert(child);
8161 } else if (!aChildStatus.IsFullyComplete()) {
8162 recordAutoPlacement(aItem, gridAxis);
8163 overflowIncompleteItems.Insert(child);
8166 return result;
8169 // @return the distance from the masonry-box start to the end of the margin-
8170 // box of aChild
8171 auto offsetToMarginBoxEnd = [&](nsIFrame* aChild) {
8172 auto childWM = aChild->GetWritingMode();
8173 auto childAxis = !childWM.IsOrthogonalTo(wm) ? masonryAxis : gridAxis;
8174 auto normalPos = aChild->GetLogicalNormalPosition(wm, aContainerSize);
8175 auto sz =
8176 childAxis == eLogicalAxisBlock ? aChild->BSize() : aChild->ISize();
8177 return containerToMasonryBoxOffset + normalPos.Pos(masonryAxis, wm) + sz +
8178 aChild->GetLogicalUsedMargin(childWM).End(childAxis, childWM);
8181 // Apply baseline alignment to items belonging to the given set.
8182 nsTArray<Tracks::ItemBaselineData> firstBaselineItems;
8183 nsTArray<Tracks::ItemBaselineData> lastBaselineItems;
8184 auto applyBaselineAlignment = [&](BaselineAlignmentSet aSet) {
8185 firstBaselineItems.ClearAndRetainStorage();
8186 lastBaselineItems.ClearAndRetainStorage();
8187 masonryTracks.InitializeItemBaselinesInMasonryAxis(
8188 aState, aState.mGridItems, aSet, aContainerSize, currentPos,
8189 firstBaselineItems, lastBaselineItems);
8191 bool didBaselineAdjustment = false;
8192 nsTArray<Tracks::ItemBaselineData>* baselineItems[] = {&firstBaselineItems,
8193 &lastBaselineItems};
8194 for (const auto* items : baselineItems) {
8195 for (const auto& data : *items) {
8196 GridItemInfo* item = data.mGridItem;
8197 MOZ_ASSERT((item->mState[masonryAxis] & ItemState::eIsBaselineAligned));
8198 nscoord baselineOffset = item->mBaselineOffset[masonryAxis];
8199 if (baselineOffset == nscoord(0)) {
8200 continue; // no adjustment needed for this item
8202 didBaselineAdjustment = true;
8203 auto* child = item->mFrame;
8204 auto masonryAxisStart =
8205 item->mArea.LineRangeForAxis(masonryAxis).mStart;
8206 auto gridAxisRange = item->mArea.LineRangeForAxis(gridAxis);
8207 masonrySizes[masonryAxisStart].mPosition =
8208 aSet.mItemSet == BaselineAlignmentSet::LastItems
8209 ? lastPos[gridAxisRange.mStart]
8210 : fragStartPos;
8211 bool consumeAllSpace = false;
8212 const auto state = item->mState[masonryAxis];
8213 if ((state & ItemState::eContentBaseline) ||
8214 MOZ_UNLIKELY(aFragmentainer)) {
8215 if (MOZ_UNLIKELY(aFragmentainer)) {
8216 aFragmentainer->mIsTopOfPage =
8217 isTopOfPageAtStart &&
8218 masonrySizes[masonryAxisStart].mPosition == fragStartPos;
8220 nsReflowStatus childStatus;
8221 ReflowInFlowChild(child, item, aContainerSize, Nothing(),
8222 aFragmentainer, aState, aContentArea, aDesiredSize,
8223 childStatus);
8224 consumeAllSpace = handleChildStatus(item, childStatus);
8225 if (aStatus.IsInlineBreakBefore()) {
8226 return false;
8228 } else if (!(state & ItemState::eEndSideBaseline)) {
8229 // `align/justify-self` baselines on the start side can be handled by
8230 // just moving the frame (except in a fragmentainer in which case we
8231 // reflow it above instead since it might make it INCOMPLETE).
8232 LogicalPoint logicalDelta(wm);
8233 logicalDelta.Pos(masonryAxis, wm) = baselineOffset;
8234 child->MovePositionBy(wm, logicalDelta);
8236 if ((state & ItemState::eEndSideBaseline) && !consumeAllSpace) {
8237 // Account for an end-side baseline adjustment.
8238 for (uint32_t i : gridAxisRange.Range()) {
8239 currentPos[i] += baselineOffset;
8241 } else {
8242 nscoord pos = consumeAllSpace ? toFragmentainerEnd
8243 : offsetToMarginBoxEnd(child);
8244 pos += gap;
8245 for (uint32_t i : gridAxisRange.Range()) {
8246 currentPos[i] = pos;
8251 return didBaselineAdjustment;
8254 // Place and reflow items. We'll use two fake tracks in the masonry axis.
8255 // The first contains items that were placed there by the regular grid
8256 // placement algo (PlaceGridItems) and we may add some items here if there
8257 // are still empty slots. The second track contains all other items.
8258 // Both tracks always have the size of the content box in the masonry axis.
8259 // The position of the first track is always at the start. The position
8260 // of the second track is updated as we go to a position where we want
8261 // the current item to be positioned.
8262 for (GridItemInfo* item : sortedItems) {
8263 auto* child = item->mFrame;
8264 auto& masonryRange = item->mArea.LineRangeForAxis(masonryAxis);
8265 auto& gridRange = item->mArea.LineRangeForAxis(gridAxis);
8266 nsReflowStatus childStatus;
8267 if (MOZ_UNLIKELY(child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW))) {
8268 auto contentArea = aContentArea;
8269 nscoord pos = nscoord_MAX;
8270 // XXXmats take mEnd into consideration...
8271 if (gridRange.mStart == kAutoLine) {
8272 for (auto p : currentPos) {
8273 pos = std::min(p, pos);
8275 } else if (gridRange.mStart < currentPos.Length()) {
8276 pos = currentPos[gridRange.mStart];
8277 } else if (currentPos.Length() > 0) {
8278 pos = currentPos.LastElement();
8280 if (pos == nscoord_MAX) {
8281 pos = nscoord(0);
8283 contentArea.Start(masonryAxis, wm) = pos;
8284 child = child->GetPlaceholderFrame();
8285 ReflowInFlowChild(child, nullptr, aContainerSize, Nothing(), nullptr,
8286 aState, contentArea, aDesiredSize, childStatus);
8287 } else {
8288 MOZ_ASSERT(gridRange.Extent() > 0 &&
8289 gridRange.Extent() <= gridAxisTrackCount);
8290 MOZ_ASSERT((masonryRange.mStart == 0 || masonryRange.mStart == 1) &&
8291 masonryRange.Extent() == 1);
8292 if (masonryRange.mStart != 0) {
8293 masonrySizes[1].mPosition = placeItem(item);
8296 // If this is the first item NOT in the first track and if any of
8297 // the grid-axis tracks we span has a baseline-aligned item then we
8298 // need to do that baseline alignment now since it may affect
8299 // the placement of this and later items.
8300 if (!didAlignStartAlignedFirstItems &&
8301 aConstraint == SizingConstraint::NoConstraint &&
8302 masonryRange.mStart != 0 && lastItemHasBaselineAlignment(gridRange)) {
8303 didAlignStartAlignedFirstItems = true;
8304 if (applyBaselineAlignment({BaselineAlignmentSet::FirstItems,
8305 BaselineAlignmentSet::StartStretch})) {
8306 // Baseline alignment resized some items - redo our placement.
8307 masonrySizes[1].mPosition = placeItem(item);
8309 if (aStatus.IsInlineBreakBefore()) {
8310 return fragStartPos;
8314 for (uint32_t i : gridRange.Range()) {
8315 lastItems[i] = item;
8317 cursor = gridRange.mEnd;
8318 if (cursor >= gridAxisTrackCount) {
8319 cursor = 0;
8322 nscoord pos;
8323 if (aConstraint == SizingConstraint::NoConstraint) {
8324 const auto* disp = child->StyleDisplay();
8325 if (MOZ_UNLIKELY(aFragmentainer)) {
8326 aFragmentainer->mIsTopOfPage =
8327 isTopOfPageAtStart &&
8328 masonrySizes[masonryRange.mStart].mPosition == fragStartPos;
8329 if (!aFragmentainer->mIsTopOfPage &&
8330 (disp->BreakBefore() ||
8331 masonrySizes[masonryRange.mStart].mPosition >=
8332 toFragmentainerEnd)) {
8333 childStatus.SetInlineLineBreakBeforeAndReset();
8336 if (!childStatus.IsInlineBreakBefore()) {
8337 ReflowInFlowChild(child, item, aContainerSize, Nothing(),
8338 aFragmentainer, aState, aContentArea, aDesiredSize,
8339 childStatus);
8341 bool consumeAllSpace = handleChildStatus(item, childStatus);
8342 if (aStatus.IsInlineBreakBefore()) {
8343 return fragStartPos;
8345 pos =
8346 consumeAllSpace ? toFragmentainerEnd : offsetToMarginBoxEnd(child);
8347 } else {
8348 LogicalSize percentBasis(
8349 aState.PercentageBasisFor(eLogicalAxisInline, *item));
8350 IntrinsicISizeType type = aConstraint == SizingConstraint::MaxContent
8351 ? IntrinsicISizeType::PrefISize
8352 : IntrinsicISizeType::MinISize;
8353 auto sz =
8354 ::ContentContribution(*item, aState, &aState.mRenderingContext, wm,
8355 masonryAxis, Some(percentBasis), type);
8356 pos = sz + masonrySizes[masonryRange.mStart].mPosition;
8358 pos += gap;
8359 for (uint32_t i : gridRange.Range()) {
8360 lastPos[i] = currentPos[i];
8361 currentPos[i] = pos;
8366 // Do the remaining baseline alignment sets.
8367 if (aConstraint == SizingConstraint::NoConstraint) {
8368 for (auto*& item : lastItems) {
8369 if (item) {
8370 item->mState[masonryAxis] |= ItemState::eIsLastItemInMasonryTrack;
8373 BaselineAlignmentSet baselineSets[] = {
8374 {BaselineAlignmentSet::FirstItems, BaselineAlignmentSet::StartStretch},
8375 {BaselineAlignmentSet::FirstItems, BaselineAlignmentSet::EndStretch},
8376 {BaselineAlignmentSet::LastItems, BaselineAlignmentSet::StartStretch},
8377 {BaselineAlignmentSet::LastItems, BaselineAlignmentSet::EndStretch},
8379 for (uint32_t i = 0; i < ArrayLength(baselineSets); ++i) {
8380 if (i == 0 && didAlignStartAlignedFirstItems) {
8381 continue;
8383 applyBaselineAlignment(baselineSets[i]);
8387 const bool childrenMoved = PushIncompleteChildren(
8388 pushedItems, incompleteItems, overflowIncompleteItems);
8389 if (childrenMoved && aStatus.IsComplete()) {
8390 aStatus.SetOverflowIncomplete();
8391 aStatus.SetNextInFlowNeedsReflow();
8393 if (!pushedItems.IsEmpty()) {
8394 AddStateBits(NS_STATE_GRID_DID_PUSH_ITEMS);
8395 // NOTE since we messed with our child list here, we intentionally
8396 // make aState.mIter invalid to avoid any use of it after this point.
8397 aState.mIter.Invalidate();
8399 if (!incompleteItems.IsEmpty()) {
8400 // NOTE since we messed with our child list here, we intentionally
8401 // make aState.mIter invalid to avoid any use of it after this point.
8402 aState.mIter.Invalidate();
8405 nscoord masonryBoxSize = 0;
8406 for (auto pos : currentPos) {
8407 masonryBoxSize = std::max(masonryBoxSize, pos);
8409 masonryBoxSize = std::max(nscoord(0), masonryBoxSize - gap);
8410 if (aConstraint == SizingConstraint::NoConstraint) {
8411 aState.AlignJustifyContentInMasonryAxis(masonryBoxSize,
8412 masonryTracks.mContentBoxSize);
8414 return masonryBoxSize;
8417 nsGridContainerFrame* nsGridContainerFrame::ParentGridContainerForSubgrid()
8418 const {
8419 MOZ_ASSERT(IsSubgrid());
8420 nsIFrame* p = GetParent();
8421 while (p->GetContent() == GetContent()) {
8422 p = p->GetParent();
8424 MOZ_ASSERT(p->IsGridContainerFrame());
8425 auto* parent = static_cast<nsGridContainerFrame*>(p);
8426 MOZ_ASSERT(parent->HasSubgridItems());
8427 return parent;
8430 nscoord nsGridContainerFrame::ReflowChildren(GridReflowInput& aState,
8431 const LogicalRect& aContentArea,
8432 const nsSize& aContainerSize,
8433 ReflowOutput& aDesiredSize,
8434 nsReflowStatus& aStatus) {
8435 WritingMode wm = aState.mReflowInput->GetWritingMode();
8436 nscoord bSize = aContentArea.BSize(wm);
8437 MOZ_ASSERT(aState.mReflowInput);
8438 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
8439 if (HidesContentForLayout()) {
8440 return bSize;
8443 OverflowAreas ocBounds;
8444 nsReflowStatus ocStatus;
8445 if (GetPrevInFlow()) {
8446 ReflowOverflowContainerChildren(PresContext(), *aState.mReflowInput,
8447 ocBounds, ReflowChildFlags::Default,
8448 ocStatus, MergeSortedFrameListsFor);
8451 Maybe<Fragmentainer> fragmentainer = GetNearestFragmentainer(aState);
8452 // MasonryLayout() can only handle fragmentation in the masonry-axis,
8453 // so we let ReflowInFragmentainer() deal with grid-axis fragmentation
8454 // in the else-clause below.
8455 if (IsMasonry() &&
8456 !(IsMasonry(eLogicalAxisInline) && fragmentainer.isSome())) {
8457 aState.mInFragmentainer = fragmentainer.isSome();
8458 nscoord sz = MasonryLayout(
8459 aState, aContentArea, SizingConstraint::NoConstraint, aDesiredSize,
8460 aStatus, fragmentainer.ptrOr(nullptr), aContainerSize);
8461 if (IsMasonry(eLogicalAxisBlock)) {
8462 bSize = aState.mReflowInput->ComputedBSize();
8463 if (bSize == NS_UNCONSTRAINEDSIZE) {
8464 bSize = NS_CSS_MINMAX(sz, aState.mReflowInput->ComputedMinBSize(),
8465 aState.mReflowInput->ComputedMaxBSize());
8468 } else if (MOZ_UNLIKELY(fragmentainer.isSome())) {
8469 if (IsMasonry(eLogicalAxisInline) && !GetPrevInFlow()) {
8470 // First we do an unconstrained reflow to resolve the item placement
8471 // which is then kept as-is in the constrained reflow below.
8472 MasonryLayout(aState, aContentArea, SizingConstraint::NoConstraint,
8473 aDesiredSize, aStatus, nullptr, aContainerSize);
8475 aState.mInFragmentainer = true;
8476 bSize = ReflowInFragmentainer(aState, aContentArea, aDesiredSize, aStatus,
8477 *fragmentainer, aContainerSize);
8478 } else {
8479 aState.mIter.Reset(CSSOrderAwareFrameIterator::ChildFilter::IncludeAll);
8480 for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
8481 nsIFrame* child = *aState.mIter;
8482 const GridItemInfo* info = nullptr;
8483 if (!child->IsPlaceholderFrame()) {
8484 info = &aState.mGridItems[aState.mIter.ItemIndex()];
8486 ReflowInFlowChild(child, info, aContainerSize, Nothing(), nullptr, aState,
8487 aContentArea, aDesiredSize, aStatus);
8488 MOZ_ASSERT(aStatus.IsComplete(),
8489 "child should be complete in unconstrained reflow");
8493 // Merge overflow container bounds and status.
8494 aDesiredSize.mOverflowAreas.UnionWith(ocBounds);
8495 aStatus.MergeCompletionStatusFrom(ocStatus);
8497 if (IsAbsoluteContainer()) {
8498 const nsFrameList& children = GetChildList(GetAbsoluteListID());
8499 if (!children.IsEmpty()) {
8500 // 'gridOrigin' is the origin of the grid (the start of the first track),
8501 // with respect to the grid container's padding-box (CB).
8502 LogicalMargin pad(aState.mReflowInput->ComputedLogicalPadding(wm));
8503 const LogicalPoint gridOrigin(wm, pad.IStart(wm), pad.BStart(wm));
8504 const LogicalRect gridCB(wm, 0, 0,
8505 aContentArea.ISize(wm) + pad.IStartEnd(wm),
8506 bSize + pad.BStartEnd(wm));
8507 const nsSize gridCBPhysicalSize = gridCB.Size(wm).GetPhysicalSize(wm);
8508 size_t i = 0;
8509 for (nsIFrame* child : children) {
8510 MOZ_ASSERT(i < aState.mAbsPosItems.Length());
8511 MOZ_ASSERT(aState.mAbsPosItems[i].mFrame == child);
8512 GridArea& area = aState.mAbsPosItems[i].mArea;
8513 LogicalRect itemCB =
8514 aState.ContainingBlockForAbsPos(area, gridOrigin, gridCB);
8515 // nsAbsoluteContainingBlock::Reflow uses physical coordinates.
8516 nsRect* cb = child->GetProperty(GridItemContainingBlockRect());
8517 if (!cb) {
8518 cb = new nsRect;
8519 child->SetProperty(GridItemContainingBlockRect(), cb);
8521 *cb = itemCB.GetPhysicalRect(wm, gridCBPhysicalSize);
8522 ++i;
8524 // We pass a dummy rect as CB because each child has its own CB rect.
8525 // The eIsGridContainerCB flag tells nsAbsoluteContainingBlock::Reflow to
8526 // use those instead.
8527 nsRect dummyRect;
8528 AbsPosReflowFlags flags =
8529 AbsPosReflowFlags::CBWidthAndHeightChanged; // XXX could be optimized
8530 flags |= AbsPosReflowFlags::ConstrainHeight;
8531 flags |= AbsPosReflowFlags::IsGridContainerCB;
8532 GetAbsoluteContainingBlock()->Reflow(
8533 this, PresContext(), *aState.mReflowInput, aStatus, dummyRect, flags,
8534 &aDesiredSize.mOverflowAreas);
8537 return bSize;
8540 void nsGridContainerFrame::Reflow(nsPresContext* aPresContext,
8541 ReflowOutput& aDesiredSize,
8542 const ReflowInput& aReflowInput,
8543 nsReflowStatus& aStatus) {
8544 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
8545 return;
8548 MarkInReflow();
8549 DO_GLOBAL_REFLOW_COUNT("nsGridContainerFrame");
8550 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
8551 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
8553 if (IsFrameTreeTooDeep(aReflowInput, aDesiredSize, aStatus)) {
8554 return;
8557 NormalizeChildLists();
8559 #ifdef DEBUG
8560 mDidPushItemsBitMayLie = false;
8561 SanityCheckChildListsBeforeReflow();
8562 #endif // DEBUG
8564 for (auto& perAxisBaseline : mBaseline) {
8565 for (auto& baseline : perAxisBaseline) {
8566 baseline = NS_INTRINSIC_ISIZE_UNKNOWN;
8570 const nsStylePosition* stylePos = aReflowInput.mStylePosition;
8571 auto prevInFlow = static_cast<nsGridContainerFrame*>(GetPrevInFlow());
8572 if (MOZ_LIKELY(!prevInFlow)) {
8573 InitImplicitNamedAreas(stylePos);
8574 } else {
8575 MOZ_ASSERT(prevInFlow->HasAnyStateBits(kIsSubgridBits) ==
8576 HasAnyStateBits(kIsSubgridBits),
8577 "continuations should have same kIsSubgridBits");
8579 GridReflowInput gridReflowInput(this, aReflowInput);
8580 if (gridReflowInput.mIter.ItemsAreAlreadyInOrder()) {
8581 AddStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER);
8582 } else {
8583 RemoveStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER);
8585 if (gridReflowInput.mIter.AtEnd() ||
8586 aReflowInput.mStyleDisplay->IsContainLayout()) {
8587 // We have no grid items, or we're layout-contained. So, we have no
8588 // baseline, and our parent should synthesize a baseline if needed.
8589 AddStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE);
8590 } else {
8591 RemoveStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE);
8593 const nscoord computedBSize = aReflowInput.ComputedBSize();
8594 const nscoord computedISize = aReflowInput.ComputedISize();
8595 const WritingMode& wm = gridReflowInput.mWM;
8596 const LogicalSize computedSize(wm, computedISize, computedBSize);
8598 nscoord consumedBSize = 0;
8599 nscoord bSize = 0;
8600 if (MOZ_LIKELY(!prevInFlow)) {
8601 Grid grid;
8602 if (MOZ_LIKELY(!IsSubgrid())) {
8603 RepeatTrackSizingInput repeatSizing(aReflowInput.ComputedMinSize(),
8604 computedSize,
8605 aReflowInput.ComputedMaxSize());
8606 grid.PlaceGridItems(gridReflowInput, repeatSizing);
8607 } else {
8608 auto* subgrid = GetProperty(Subgrid::Prop());
8609 MOZ_ASSERT(subgrid, "an ancestor forgot to call PlaceGridItems?");
8610 gridReflowInput.mGridItems = subgrid->mGridItems.Clone();
8611 gridReflowInput.mAbsPosItems = subgrid->mAbsPosItems.Clone();
8612 grid.mGridColEnd = subgrid->mGridColEnd;
8613 grid.mGridRowEnd = subgrid->mGridRowEnd;
8615 // XXX Technically incorrect: 'contain-intrinsic-block-size: none' is
8616 // treated as 0, ignoring our row sizes, when really we should use them but
8617 // *they* should be computed as if we had no children. To be fixed in bug
8618 // 1488878.
8619 const Maybe<nscoord> containBSize =
8620 aReflowInput.mFrame->ContainIntrinsicBSize();
8621 const nscoord trackSizingBSize = [&] {
8622 // This clamping only applies to auto sizes.
8623 if (containBSize && computedBSize == NS_UNCONSTRAINEDSIZE) {
8624 return NS_CSS_MINMAX(*containBSize, aReflowInput.ComputedMinBSize(),
8625 aReflowInput.ComputedMaxBSize());
8627 return computedBSize;
8628 }();
8629 const LogicalSize containLogicalSize(wm, computedISize, trackSizingBSize);
8630 gridReflowInput.CalculateTrackSizes(grid, containLogicalSize,
8631 SizingConstraint::NoConstraint);
8632 if (containBSize) {
8633 bSize = *containBSize;
8634 } else {
8635 if (IsMasonry(eLogicalAxisBlock)) {
8636 bSize = computedBSize;
8637 } else {
8638 const auto& rowSizes = gridReflowInput.mRows.mSizes;
8639 if (MOZ_LIKELY(!IsSubgrid(eLogicalAxisBlock))) {
8640 // Note: we can't use GridLineEdge here since we haven't calculated
8641 // the rows' mPosition yet (happens in AlignJustifyContent below).
8642 for (const auto& sz : rowSizes) {
8643 bSize += sz.mBase;
8645 bSize += gridReflowInput.mRows.SumOfGridGaps();
8646 } else if (computedBSize == NS_UNCONSTRAINEDSIZE) {
8647 bSize = gridReflowInput.mRows.GridLineEdge(
8648 rowSizes.Length(), GridLineSide::BeforeGridGap);
8652 } else {
8653 consumedBSize = CalcAndCacheConsumedBSize();
8654 gridReflowInput.InitializeForContinuation(this, consumedBSize);
8655 // XXX Technically incorrect: 'contain-intrinsic-block-size: none' is
8656 // treated as 0, ignoring our row sizes, when really we should use them but
8657 // *they* should be computed as if we had no children. To be fixed in bug
8658 // 1488878.
8659 if (Maybe<nscoord> containBSize =
8660 aReflowInput.mFrame->ContainIntrinsicBSize()) {
8661 bSize = *containBSize;
8662 } else {
8663 const uint32_t numRows = gridReflowInput.mRows.mSizes.Length();
8664 bSize = gridReflowInput.mRows.GridLineEdge(numRows,
8665 GridLineSide::AfterGridGap);
8668 if (computedBSize == NS_UNCONSTRAINEDSIZE) {
8669 bSize = NS_CSS_MINMAX(bSize, aReflowInput.ComputedMinBSize(),
8670 aReflowInput.ComputedMaxBSize());
8671 } else if (aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis()) {
8672 nscoord contentBSize = NS_CSS_MINMAX(bSize, aReflowInput.ComputedMinBSize(),
8673 aReflowInput.ComputedMaxBSize());
8674 bSize = std::max(contentBSize, computedBSize);
8675 } else {
8676 bSize = computedBSize;
8678 if (bSize != NS_UNCONSTRAINEDSIZE) {
8679 bSize = std::max(bSize - consumedBSize, 0);
8681 auto& bp = gridReflowInput.mBorderPadding;
8682 LogicalRect contentArea(wm, bp.IStart(wm), bp.BStart(wm), computedISize,
8683 bSize);
8685 if (!prevInFlow) {
8686 const auto& rowSizes = gridReflowInput.mRows.mSizes;
8687 if (!IsRowSubgrid()) {
8688 // Apply 'align-content' to the grid.
8689 if (computedBSize == NS_UNCONSTRAINEDSIZE &&
8690 stylePos->mRowGap.IsLengthPercentage() &&
8691 stylePos->mRowGap.AsLengthPercentage().HasPercent()) {
8692 // Re-resolve the row-gap now that we know our intrinsic block-size.
8693 gridReflowInput.mRows.mGridGap =
8694 nsLayoutUtils::ResolveGapToLength(stylePos->mRowGap, bSize);
8696 if (!gridReflowInput.mRows.mIsMasonry) {
8697 auto alignment = stylePos->mAlignContent;
8698 gridReflowInput.mRows.AlignJustifyContent(stylePos, alignment, wm,
8699 bSize, false);
8701 } else {
8702 if (computedBSize == NS_UNCONSTRAINEDSIZE) {
8703 bSize = gridReflowInput.mRows.GridLineEdge(rowSizes.Length(),
8704 GridLineSide::BeforeGridGap);
8705 contentArea.BSize(wm) = std::max(bSize, nscoord(0));
8708 // Save the final row sizes for use by subgrids, if needed.
8709 if (HasSubgridItems() || IsSubgrid()) {
8710 StoreUsedTrackSizes(eLogicalAxisBlock, rowSizes);
8714 nsSize containerSize = contentArea.Size(wm).GetPhysicalSize(wm);
8715 bool repositionChildren = false;
8716 if (containerSize.width == NS_UNCONSTRAINEDSIZE && wm.IsVerticalRL()) {
8717 // Note that writing-mode:vertical-rl is the only case where the block
8718 // logical direction progresses in a negative physical direction, and
8719 // therefore block-dir coordinate conversion depends on knowing the width
8720 // of the coordinate space in order to translate between the logical and
8721 // physical origins.
8723 // A masonry axis size may be unconstrained, otherwise in a regular grid
8724 // our intrinsic size is always known by now. We'll re-position
8725 // the children below once our size is known.
8726 repositionChildren = true;
8727 containerSize.width = 0;
8729 containerSize.width += bp.LeftRight(wm);
8730 containerSize.height += bp.TopBottom(wm);
8732 bSize = ReflowChildren(gridReflowInput, contentArea, containerSize,
8733 aDesiredSize, aStatus);
8734 bSize = std::max(bSize - consumedBSize, 0);
8736 // Skip our block-end border if we're INCOMPLETE.
8737 if (!aStatus.IsComplete() && !gridReflowInput.mSkipSides.BEnd() &&
8738 StyleBorder()->mBoxDecorationBreak != StyleBoxDecorationBreak::Clone) {
8739 bp.BEnd(wm) = nscoord(0);
8742 LogicalSize desiredSize(wm, computedISize + bp.IStartEnd(wm),
8743 bSize + bp.BStartEnd(wm));
8744 aDesiredSize.SetSize(wm, desiredSize);
8745 nsRect frameRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height());
8746 aDesiredSize.mOverflowAreas.UnionAllWith(frameRect);
8748 if (repositionChildren) {
8749 nsPoint physicalDelta(aDesiredSize.Width() - bp.LeftRight(wm), 0);
8750 for (const auto& item : gridReflowInput.mGridItems) {
8751 auto* child = item.mFrame;
8752 child->MovePositionBy(physicalDelta);
8753 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, child);
8757 if (Style()->GetPseudoType() == PseudoStyleType::scrolledContent) {
8758 // Per spec, the grid area is included in a grid container's scrollable
8759 // overflow region [1], as well as the padding on the end-edge sides that
8760 // would satisfy the requirements of 'place-content: end' alignment [2].
8762 // Note that we include the padding from all sides of the grid area, not
8763 // just the end sides; this is fine because the grid area is relative to our
8764 // content-box origin. The inflated bounds won't go beyond our padding-box
8765 // edges on the start sides.
8767 // The margin areas of grid item boxes are also included in the scrollable
8768 // overflow region [2].
8770 // [1] https://drafts.csswg.org/css-grid-1/#overflow
8771 // [2] https://drafts.csswg.org/css-overflow-3/#scrollable
8773 // Synthesize a grid area covering all columns and rows, and compute its
8774 // rect relative to our border-box.
8776 // Note: the grid columns and rows exist only if there is an explicit grid;
8777 // or when an implicit grid is needed to place any grid items. See
8778 // nsGridContainerFrame::Grid::PlaceGridItems().
8779 const auto numCols =
8780 static_cast<int32_t>(gridReflowInput.mCols.mSizes.Length());
8781 const auto numRows =
8782 static_cast<int32_t>(gridReflowInput.mRows.mSizes.Length());
8783 if (numCols > 0 && numRows > 0) {
8784 const GridArea gridArea(LineRange(0, numCols), LineRange(0, numRows));
8785 const LogicalRect gridAreaRect =
8786 gridReflowInput.ContainingBlockFor(gridArea) +
8787 LogicalPoint(wm, bp.IStart(wm), bp.BStart(wm));
8789 MOZ_ASSERT(bp == aReflowInput.ComputedLogicalPadding(wm),
8790 "A scrolled inner frame shouldn't have any border!");
8791 const LogicalMargin& padding = bp;
8792 nsRect physicalGridAreaRectWithPadding =
8793 gridAreaRect.GetPhysicalRect(wm, containerSize);
8794 physicalGridAreaRectWithPadding.Inflate(padding.GetPhysicalMargin(wm));
8795 aDesiredSize.mOverflowAreas.UnionAllWith(physicalGridAreaRectWithPadding);
8798 nsRect gridItemMarginBoxBounds;
8799 for (const auto& item : gridReflowInput.mGridItems) {
8800 gridItemMarginBoxBounds =
8801 gridItemMarginBoxBounds.Union(item.mFrame->GetMarginRect());
8803 aDesiredSize.mOverflowAreas.UnionAllWith(gridItemMarginBoxBounds);
8806 // TODO: fix align-tracks alignment in fragments
8807 if ((IsMasonry(eLogicalAxisBlock) && !prevInFlow) ||
8808 IsMasonry(eLogicalAxisInline)) {
8809 gridReflowInput.AlignJustifyTracksInMasonryAxis(
8810 contentArea.Size(wm), aDesiredSize.PhysicalSize());
8813 // Convert INCOMPLETE -> OVERFLOW_INCOMPLETE and zero bsize if we're an OC.
8814 if (HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
8815 if (!aStatus.IsComplete()) {
8816 aStatus.SetOverflowIncomplete();
8817 aStatus.SetNextInFlowNeedsReflow();
8819 bSize = 0;
8820 desiredSize.BSize(wm) = bSize + bp.BStartEnd(wm);
8821 aDesiredSize.SetSize(wm, desiredSize);
8824 if (!gridReflowInput.mInFragmentainer) {
8825 MOZ_ASSERT(gridReflowInput.mIter.IsValid());
8826 auto sz = frameRect.Size();
8827 CalculateBaselines(BaselineSet::eBoth, &gridReflowInput.mIter,
8828 &gridReflowInput.mGridItems, gridReflowInput.mCols, 0,
8829 gridReflowInput.mCols.mSizes.Length(), wm, sz,
8830 bp.IStart(wm), bp.IEnd(wm), desiredSize.ISize(wm));
8831 CalculateBaselines(BaselineSet::eBoth, &gridReflowInput.mIter,
8832 &gridReflowInput.mGridItems, gridReflowInput.mRows, 0,
8833 gridReflowInput.mRows.mSizes.Length(), wm, sz,
8834 bp.BStart(wm), bp.BEnd(wm), desiredSize.BSize(wm));
8835 } else {
8836 // Only compute 'first baseline' if this fragment contains the first track.
8837 // XXXmats maybe remove this condition? bug 1306499
8838 BaselineSet baselines = BaselineSet::eNone;
8839 if (gridReflowInput.mStartRow == 0 &&
8840 gridReflowInput.mStartRow != gridReflowInput.mNextFragmentStartRow) {
8841 baselines = BaselineSet::eFirst;
8843 // Only compute 'last baseline' if this fragment contains the last track.
8844 // XXXmats maybe remove this condition? bug 1306499
8845 uint32_t len = gridReflowInput.mRows.mSizes.Length();
8846 if (gridReflowInput.mStartRow != len &&
8847 gridReflowInput.mNextFragmentStartRow == len) {
8848 baselines = BaselineSet(baselines | BaselineSet::eLast);
8850 Maybe<CSSOrderAwareFrameIterator> iter;
8851 Maybe<nsTArray<GridItemInfo>> gridItems;
8852 if (baselines != BaselineSet::eNone) {
8853 // We need to create a new iterator and GridItemInfo array because we
8854 // might have pushed some children at this point.
8855 // Even if the gridReflowInput iterator is invalid we can reuse its
8856 // state about order to optimize initialization of the new iterator.
8857 // An ordered child list can't become unordered by pushing frames.
8858 // An unordered list can become ordered in a number of cases, but we
8859 // ignore that here and guess that the child list is still unordered.
8860 // XXX this is O(n^2) in the number of items in this fragment: bug 1306705
8861 using Filter = CSSOrderAwareFrameIterator::ChildFilter;
8862 using Order = CSSOrderAwareFrameIterator::OrderState;
8863 bool ordered = gridReflowInput.mIter.ItemsAreAlreadyInOrder();
8864 auto orderState = ordered ? Order::Ordered : Order::Unordered;
8865 iter.emplace(this, FrameChildListID::Principal, Filter::SkipPlaceholders,
8866 orderState);
8867 gridItems.emplace();
8868 for (; !iter->AtEnd(); iter->Next()) {
8869 auto child = **iter;
8870 for (const auto& info : gridReflowInput.mGridItems) {
8871 if (info.mFrame == child) {
8872 gridItems->AppendElement(info);
8877 auto sz = frameRect.Size();
8878 CalculateBaselines(baselines, iter.ptrOr(nullptr), gridItems.ptrOr(nullptr),
8879 gridReflowInput.mCols, 0,
8880 gridReflowInput.mCols.mSizes.Length(), wm, sz,
8881 bp.IStart(wm), bp.IEnd(wm), desiredSize.ISize(wm));
8882 CalculateBaselines(baselines, iter.ptrOr(nullptr), gridItems.ptrOr(nullptr),
8883 gridReflowInput.mRows, gridReflowInput.mStartRow,
8884 gridReflowInput.mNextFragmentStartRow, wm, sz,
8885 bp.BStart(wm), bp.BEnd(wm), desiredSize.BSize(wm));
8888 if (ShouldGenerateComputedInfo()) {
8889 // This state bit will never be cleared, since reflow can be called
8890 // multiple times in fragmented grids, and it's challenging to scope
8891 // the bit to only that sequence of calls. This is relatively harmless
8892 // since this bit is only set by accessing a ChromeOnly property, and
8893 // therefore can't unduly slow down normal web browsing.
8895 // Clear our GridFragmentInfo property, which might be holding a stale
8896 // dom::Grid object built from previously-computed info. This will
8897 // ensure that the next call to GetGridFragments will create a new one.
8898 if (mozilla::dom::Grid* grid = TakeProperty(GridFragmentInfo())) {
8899 grid->ForgetFrame();
8902 // Now that we know column and row sizes and positions, set
8903 // the ComputedGridTrackInfo and related properties
8905 const auto* subgrid = GetProperty(Subgrid::Prop());
8906 const auto* subgridColRange = subgrid && IsSubgrid(eLogicalAxisInline)
8907 ? &subgrid->SubgridCols()
8908 : nullptr;
8910 LineNameMap colLineNameMap(
8911 gridReflowInput.mGridStyle, GetImplicitNamedAreas(),
8912 gridReflowInput.mColFunctions, nullptr, subgridColRange, true);
8913 uint32_t colTrackCount = gridReflowInput.mCols.mSizes.Length();
8914 nsTArray<nscoord> colTrackPositions(colTrackCount);
8915 nsTArray<nscoord> colTrackSizes(colTrackCount);
8916 nsTArray<uint32_t> colTrackStates(colTrackCount);
8917 nsTArray<bool> colRemovedRepeatTracks(
8918 gridReflowInput.mColFunctions.mRemovedRepeatTracks.Clone());
8919 uint32_t col = 0;
8920 for (const TrackSize& sz : gridReflowInput.mCols.mSizes) {
8921 colTrackPositions.AppendElement(sz.mPosition);
8922 colTrackSizes.AppendElement(sz.mBase);
8923 bool isRepeat =
8924 ((col >= gridReflowInput.mColFunctions.mRepeatAutoStart) &&
8925 (col < gridReflowInput.mColFunctions.mRepeatAutoEnd));
8926 colTrackStates.AppendElement(
8927 isRepeat ? (uint32_t)mozilla::dom::GridTrackState::Repeat
8928 : (uint32_t)mozilla::dom::GridTrackState::Static);
8930 col++;
8932 // Get the number of explicit tracks first. The order of argument evaluation
8933 // is implementation-defined. We should be OK here because colTrackSizes is
8934 // taken by rvalue, but computing the size first prevents any changes in the
8935 // argument types of the constructor from breaking this.
8936 const uint32_t numColExplicitTracks =
8937 IsSubgrid(eLogicalAxisInline)
8938 ? colTrackSizes.Length()
8939 : gridReflowInput.mColFunctions.NumExplicitTracks();
8940 ComputedGridTrackInfo* colInfo = new ComputedGridTrackInfo(
8941 gridReflowInput.mColFunctions.mExplicitGridOffset, numColExplicitTracks,
8942 0, col, std::move(colTrackPositions), std::move(colTrackSizes),
8943 std::move(colTrackStates), std::move(colRemovedRepeatTracks),
8944 gridReflowInput.mColFunctions.mRepeatAutoStart,
8945 colLineNameMap.GetResolvedLineNamesForComputedGridTrackInfo(),
8946 IsSubgrid(eLogicalAxisInline), IsMasonry(eLogicalAxisInline));
8947 SetProperty(GridColTrackInfo(), colInfo);
8949 const auto* subgridRowRange = subgrid && IsSubgrid(eLogicalAxisBlock)
8950 ? &subgrid->SubgridRows()
8951 : nullptr;
8952 LineNameMap rowLineNameMap(
8953 gridReflowInput.mGridStyle, GetImplicitNamedAreas(),
8954 gridReflowInput.mRowFunctions, nullptr, subgridRowRange, true);
8955 uint32_t rowTrackCount = gridReflowInput.mRows.mSizes.Length();
8956 nsTArray<nscoord> rowTrackPositions(rowTrackCount);
8957 nsTArray<nscoord> rowTrackSizes(rowTrackCount);
8958 nsTArray<uint32_t> rowTrackStates(rowTrackCount);
8959 nsTArray<bool> rowRemovedRepeatTracks(
8960 gridReflowInput.mRowFunctions.mRemovedRepeatTracks.Clone());
8961 uint32_t row = 0;
8962 for (const TrackSize& sz : gridReflowInput.mRows.mSizes) {
8963 rowTrackPositions.AppendElement(sz.mPosition);
8964 rowTrackSizes.AppendElement(sz.mBase);
8965 bool isRepeat =
8966 ((row >= gridReflowInput.mRowFunctions.mRepeatAutoStart) &&
8967 (row < gridReflowInput.mRowFunctions.mRepeatAutoEnd));
8968 rowTrackStates.AppendElement(
8969 isRepeat ? (uint32_t)mozilla::dom::GridTrackState::Repeat
8970 : (uint32_t)mozilla::dom::GridTrackState::Static);
8972 row++;
8974 // Get the number of explicit tracks first. The order of argument evaluation
8975 // is implementation-defined. We should be OK here because colTrackSizes is
8976 // taken by rvalue, but computing the size first prevents any changes in the
8977 // argument types of the constructor from breaking this.
8978 const uint32_t numRowExplicitTracks =
8979 IsSubgrid(eLogicalAxisBlock)
8980 ? rowTrackSizes.Length()
8981 : gridReflowInput.mRowFunctions.NumExplicitTracks();
8982 // Row info has to accommodate fragmentation of the grid, which may happen
8983 // in later calls to Reflow. For now, presume that no more fragmentation
8984 // will occur.
8985 ComputedGridTrackInfo* rowInfo = new ComputedGridTrackInfo(
8986 gridReflowInput.mRowFunctions.mExplicitGridOffset, numRowExplicitTracks,
8987 gridReflowInput.mStartRow, row, std::move(rowTrackPositions),
8988 std::move(rowTrackSizes), std::move(rowTrackStates),
8989 std::move(rowRemovedRepeatTracks),
8990 gridReflowInput.mRowFunctions.mRepeatAutoStart,
8991 rowLineNameMap.GetResolvedLineNamesForComputedGridTrackInfo(),
8992 IsSubgrid(eLogicalAxisBlock), IsMasonry(eLogicalAxisBlock));
8993 SetProperty(GridRowTrackInfo(), rowInfo);
8995 if (prevInFlow) {
8996 // This frame is fragmenting rows from a previous frame, so patch up
8997 // the prior GridRowTrackInfo with a new end row.
8999 // FIXME: This can be streamlined and/or removed when bug 1151204 lands.
9001 ComputedGridTrackInfo* priorRowInfo =
9002 prevInFlow->GetProperty(GridRowTrackInfo());
9004 // Adjust track positions based on the first track in this fragment.
9005 if (priorRowInfo->mPositions.Length() >
9006 priorRowInfo->mStartFragmentTrack) {
9007 nscoord delta =
9008 priorRowInfo->mPositions[priorRowInfo->mStartFragmentTrack];
9009 for (nscoord& pos : priorRowInfo->mPositions) {
9010 pos -= delta;
9014 ComputedGridTrackInfo* revisedPriorRowInfo = new ComputedGridTrackInfo(
9015 priorRowInfo->mNumLeadingImplicitTracks,
9016 priorRowInfo->mNumExplicitTracks, priorRowInfo->mStartFragmentTrack,
9017 gridReflowInput.mStartRow, std::move(priorRowInfo->mPositions),
9018 std::move(priorRowInfo->mSizes), std::move(priorRowInfo->mStates),
9019 std::move(priorRowInfo->mRemovedRepeatTracks),
9020 priorRowInfo->mRepeatFirstTrack,
9021 std::move(priorRowInfo->mResolvedLineNames), priorRowInfo->mIsSubgrid,
9022 priorRowInfo->mIsMasonry);
9023 prevInFlow->SetProperty(GridRowTrackInfo(), revisedPriorRowInfo);
9026 // Generate the line info properties. We need to provide the number of
9027 // repeat tracks produced in the reflow. Only explicit names are assigned
9028 // to lines here; the mozilla::dom::GridLines class will later extract
9029 // implicit names from grid areas and assign them to the appropriate lines.
9031 auto& colFunctions = gridReflowInput.mColFunctions;
9033 // Generate column lines first.
9034 uint32_t capacity = gridReflowInput.mCols.mSizes.Length();
9035 nsTArray<nsTArray<RefPtr<nsAtom>>> columnLineNames(capacity);
9036 for (col = 0; col <= gridReflowInput.mCols.mSizes.Length(); col++) {
9037 // Offset col by the explicit grid offset, to get the original names.
9038 nsTArray<RefPtr<nsAtom>> explicitNames =
9039 colLineNameMap.GetExplicitLineNamesAtIndex(
9040 col - colFunctions.mExplicitGridOffset);
9042 columnLineNames.EmplaceBack(std::move(explicitNames));
9044 // Get the explicit names that follow a repeat auto declaration.
9045 nsTArray<RefPtr<nsAtom>> colNamesFollowingRepeat;
9046 nsTArray<RefPtr<nsAtom>> colBeforeRepeatAuto;
9047 nsTArray<RefPtr<nsAtom>> colAfterRepeatAuto;
9048 // Note: the following is only used for a non-subgridded axis.
9049 if (colLineNameMap.HasRepeatAuto()) {
9050 MOZ_ASSERT(!colFunctions.mTemplate.IsSubgrid());
9051 // The line name list after the repeatAutoIndex holds the line names
9052 // for the first explicit line after the repeat auto declaration.
9053 uint32_t repeatAutoEnd = colLineNameMap.RepeatAutoStart() + 1;
9054 for (auto* list : colLineNameMap.ExpandedLineNames()[repeatAutoEnd]) {
9055 for (auto& name : list->AsSpan()) {
9056 colNamesFollowingRepeat.AppendElement(name.AsAtom());
9059 auto names = colLineNameMap.TrackAutoRepeatLineNames();
9060 for (auto& name : names[0].AsSpan()) {
9061 colBeforeRepeatAuto.AppendElement(name.AsAtom());
9063 for (auto& name : names[1].AsSpan()) {
9064 colAfterRepeatAuto.AppendElement(name.AsAtom());
9068 ComputedGridLineInfo* columnLineInfo = new ComputedGridLineInfo(
9069 std::move(columnLineNames), std::move(colBeforeRepeatAuto),
9070 std::move(colAfterRepeatAuto), std::move(colNamesFollowingRepeat));
9071 SetProperty(GridColumnLineInfo(), columnLineInfo);
9073 // Generate row lines next.
9074 auto& rowFunctions = gridReflowInput.mRowFunctions;
9075 capacity = gridReflowInput.mRows.mSizes.Length();
9076 nsTArray<nsTArray<RefPtr<nsAtom>>> rowLineNames(capacity);
9077 for (row = 0; row <= gridReflowInput.mRows.mSizes.Length(); row++) {
9078 // Offset row by the explicit grid offset, to get the original names.
9079 nsTArray<RefPtr<nsAtom>> explicitNames =
9080 rowLineNameMap.GetExplicitLineNamesAtIndex(
9081 row - rowFunctions.mExplicitGridOffset);
9082 rowLineNames.EmplaceBack(std::move(explicitNames));
9084 // Get the explicit names that follow a repeat auto declaration.
9085 nsTArray<RefPtr<nsAtom>> rowNamesFollowingRepeat;
9086 nsTArray<RefPtr<nsAtom>> rowBeforeRepeatAuto;
9087 nsTArray<RefPtr<nsAtom>> rowAfterRepeatAuto;
9088 // Note: the following is only used for a non-subgridded axis.
9089 if (rowLineNameMap.HasRepeatAuto()) {
9090 MOZ_ASSERT(!rowFunctions.mTemplate.IsSubgrid());
9091 // The line name list after the repeatAutoIndex holds the line names
9092 // for the first explicit line after the repeat auto declaration.
9093 uint32_t repeatAutoEnd = rowLineNameMap.RepeatAutoStart() + 1;
9094 for (auto* list : rowLineNameMap.ExpandedLineNames()[repeatAutoEnd]) {
9095 for (auto& name : list->AsSpan()) {
9096 rowNamesFollowingRepeat.AppendElement(name.AsAtom());
9099 auto names = rowLineNameMap.TrackAutoRepeatLineNames();
9100 for (auto& name : names[0].AsSpan()) {
9101 rowBeforeRepeatAuto.AppendElement(name.AsAtom());
9103 for (auto& name : names[1].AsSpan()) {
9104 rowAfterRepeatAuto.AppendElement(name.AsAtom());
9108 ComputedGridLineInfo* rowLineInfo = new ComputedGridLineInfo(
9109 std::move(rowLineNames), std::move(rowBeforeRepeatAuto),
9110 std::move(rowAfterRepeatAuto), std::move(rowNamesFollowingRepeat));
9111 SetProperty(GridRowLineInfo(), rowLineInfo);
9113 // Generate area info for explicit areas. Implicit areas are handled
9114 // elsewhere.
9115 if (!gridReflowInput.mGridStyle->mGridTemplateAreas.IsNone()) {
9116 auto* areas = new StyleOwnedSlice<NamedArea>(
9117 gridReflowInput.mGridStyle->mGridTemplateAreas.AsAreas()->areas);
9118 SetProperty(ExplicitNamedAreasProperty(), areas);
9119 } else {
9120 RemoveProperty(ExplicitNamedAreasProperty());
9124 if (!prevInFlow) {
9125 SharedGridData* sharedGridData = GetProperty(SharedGridData::Prop());
9126 if (!aStatus.IsFullyComplete()) {
9127 if (!sharedGridData) {
9128 sharedGridData = new SharedGridData;
9129 SetProperty(SharedGridData::Prop(), sharedGridData);
9131 sharedGridData->mCols.mSizes = std::move(gridReflowInput.mCols.mSizes);
9132 sharedGridData->mCols.mContentBoxSize =
9133 gridReflowInput.mCols.mContentBoxSize;
9134 sharedGridData->mCols.mBaselineSubtreeAlign =
9135 gridReflowInput.mCols.mBaselineSubtreeAlign;
9136 sharedGridData->mCols.mIsMasonry = gridReflowInput.mCols.mIsMasonry;
9137 sharedGridData->mRows.mSizes = std::move(gridReflowInput.mRows.mSizes);
9138 // Save the original row grid sizes and gaps so we can restore them later
9139 // in GridReflowInput::Initialize for the continuations.
9140 auto& origRowData = sharedGridData->mOriginalRowData;
9141 origRowData.ClearAndRetainStorage();
9142 origRowData.SetCapacity(sharedGridData->mRows.mSizes.Length());
9143 nscoord prevTrackEnd = 0;
9144 for (auto& sz : sharedGridData->mRows.mSizes) {
9145 SharedGridData::RowData data = {sz.mBase, sz.mPosition - prevTrackEnd};
9146 origRowData.AppendElement(data);
9147 prevTrackEnd = sz.mPosition + sz.mBase;
9149 sharedGridData->mRows.mContentBoxSize =
9150 gridReflowInput.mRows.mContentBoxSize;
9151 sharedGridData->mRows.mBaselineSubtreeAlign =
9152 gridReflowInput.mRows.mBaselineSubtreeAlign;
9153 sharedGridData->mRows.mIsMasonry = gridReflowInput.mRows.mIsMasonry;
9154 sharedGridData->mGridItems = std::move(gridReflowInput.mGridItems);
9155 sharedGridData->mAbsPosItems = std::move(gridReflowInput.mAbsPosItems);
9157 sharedGridData->mGenerateComputedGridInfo = ShouldGenerateComputedInfo();
9158 } else if (sharedGridData && !GetNextInFlow()) {
9159 RemoveProperty(SharedGridData::Prop());
9163 FinishAndStoreOverflow(&aDesiredSize);
9166 void nsGridContainerFrame::UpdateSubgridFrameState() {
9167 nsFrameState oldBits = GetStateBits() & kIsSubgridBits;
9168 nsFrameState newBits = ComputeSelfSubgridMasonryBits() & kIsSubgridBits;
9169 if (newBits != oldBits) {
9170 RemoveStateBits(kIsSubgridBits);
9171 if (!newBits) {
9172 RemoveProperty(Subgrid::Prop());
9173 } else {
9174 AddStateBits(newBits);
9179 nsFrameState nsGridContainerFrame::ComputeSelfSubgridMasonryBits() const {
9180 // 'contain:layout/paint' makes us an "independent formatting context",
9181 // which prevents us from being a subgrid in this case (but not always).
9182 // We will also need to check our containing scroll frame for this property.
9183 // https://drafts.csswg.org/css-display-3/#establish-an-independent-formatting-context
9184 const auto* display = StyleDisplay();
9185 const bool inhibitSubgrid =
9186 display->IsContainLayout() || display->IsContainPaint();
9188 nsFrameState bits = nsFrameState(0);
9189 const auto* pos = StylePosition();
9191 // We can only have masonry layout in one axis.
9192 if (pos->mGridTemplateRows.IsMasonry()) {
9193 bits |= NS_STATE_GRID_IS_ROW_MASONRY;
9194 } else if (pos->mGridTemplateColumns.IsMasonry()) {
9195 bits |= NS_STATE_GRID_IS_COL_MASONRY;
9198 // Skip our scroll frame and such if we have it.
9199 // This will store the outermost frame that shares our content node:
9200 const nsIFrame* outerFrame = this;
9201 // ...and this will store that frame's parent:
9202 auto* parent = GetParent();
9203 while (parent && parent->GetContent() == GetContent()) {
9204 // If we find our containing frame has 'contain:layout/paint' we can't be
9205 // subgrid, for the same reasons as above. This can happen when this frame
9206 // is itself a grid item.
9207 const auto* parentDisplay = parent->StyleDisplay();
9208 if (parentDisplay->IsContainLayout() || parentDisplay->IsContainPaint()) {
9209 return nsFrameState(0);
9211 outerFrame = parent;
9212 parent = parent->GetParent();
9214 const nsGridContainerFrame* gridParent = do_QueryFrame(parent);
9215 if (gridParent) {
9216 bool isOrthogonal =
9217 GetWritingMode().IsOrthogonalTo(parent->GetWritingMode());
9218 // NOTE: our NS_FRAME_OUT_OF_FLOW isn't set yet so we check our style.
9219 bool isOutOfFlow =
9220 outerFrame->StyleDisplay()->IsAbsolutelyPositionedStyle();
9221 bool isColSubgrid =
9222 pos->mGridTemplateColumns.IsSubgrid() && !inhibitSubgrid;
9223 // Subgridding a parent masonry axis makes us use masonry layout too,
9224 // unless our other axis is a masonry axis.
9225 if (isColSubgrid &&
9226 parent->HasAnyStateBits(isOrthogonal ? NS_STATE_GRID_IS_ROW_MASONRY
9227 : NS_STATE_GRID_IS_COL_MASONRY)) {
9228 isColSubgrid = false;
9229 if (!HasAnyStateBits(NS_STATE_GRID_IS_ROW_MASONRY)) {
9230 bits |= NS_STATE_GRID_IS_COL_MASONRY;
9233 // OOF subgrids don't create tracks in the parent, so we need to check that
9234 // it has one anyway. Otherwise we refuse to subgrid that axis since we
9235 // can't place grid items inside a subgrid without at least one track.
9236 if (isColSubgrid && isOutOfFlow) {
9237 auto parentAxis = isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline;
9238 if (!gridParent->WillHaveAtLeastOneTrackInAxis(parentAxis)) {
9239 isColSubgrid = false;
9242 if (isColSubgrid) {
9243 bits |= NS_STATE_GRID_IS_COL_SUBGRID;
9246 bool isRowSubgrid = pos->mGridTemplateRows.IsSubgrid() && !inhibitSubgrid;
9247 if (isRowSubgrid &&
9248 parent->HasAnyStateBits(isOrthogonal ? NS_STATE_GRID_IS_COL_MASONRY
9249 : NS_STATE_GRID_IS_ROW_MASONRY)) {
9250 isRowSubgrid = false;
9251 if (!HasAnyStateBits(NS_STATE_GRID_IS_COL_MASONRY)) {
9252 bits |= NS_STATE_GRID_IS_ROW_MASONRY;
9255 if (isRowSubgrid && isOutOfFlow) {
9256 auto parentAxis = isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock;
9257 if (!gridParent->WillHaveAtLeastOneTrackInAxis(parentAxis)) {
9258 isRowSubgrid = false;
9261 if (isRowSubgrid) {
9262 bits |= NS_STATE_GRID_IS_ROW_SUBGRID;
9265 return bits;
9268 bool nsGridContainerFrame::WillHaveAtLeastOneTrackInAxis(
9269 LogicalAxis aAxis) const {
9270 if (IsSubgrid(aAxis)) {
9271 // This is enforced by refusing to be a subgrid unless our parent has
9272 // at least one track in aAxis by ComputeSelfSubgridMasonryBits above.
9273 return true;
9275 if (IsMasonry(aAxis)) {
9276 return false;
9278 const auto* pos = StylePosition();
9279 const auto& gridTemplate = aAxis == eLogicalAxisBlock
9280 ? pos->mGridTemplateRows
9281 : pos->mGridTemplateColumns;
9282 if (gridTemplate.IsTrackList()) {
9283 return true;
9285 for (nsIFrame* child : PrincipalChildList()) {
9286 if (!child->IsPlaceholderFrame()) {
9287 // A grid item triggers at least one implicit track in each axis.
9288 return true;
9291 if (!pos->mGridTemplateAreas.IsNone()) {
9292 return true;
9294 return false;
9297 void nsGridContainerFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
9298 nsIFrame* aPrevInFlow) {
9299 nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
9301 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER)) {
9302 AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
9305 nsFrameState bits = nsFrameState(0);
9306 if (MOZ_LIKELY(!aPrevInFlow)) {
9307 bits = ComputeSelfSubgridMasonryBits();
9308 } else {
9309 bits = aPrevInFlow->GetStateBits() &
9310 (NS_STATE_GRID_IS_ROW_MASONRY | NS_STATE_GRID_IS_COL_MASONRY |
9311 kIsSubgridBits | NS_STATE_GRID_HAS_COL_SUBGRID_ITEM |
9312 NS_STATE_GRID_HAS_ROW_SUBGRID_ITEM);
9314 AddStateBits(bits);
9317 void nsGridContainerFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
9318 nsContainerFrame::DidSetComputedStyle(aOldStyle);
9320 if (!aOldStyle) {
9321 return; // Init() already initialized the bits.
9323 UpdateSubgridFrameState();
9326 nscoord nsGridContainerFrame::IntrinsicISize(gfxContext* aRenderingContext,
9327 IntrinsicISizeType aType) {
9328 // Calculate the sum of column sizes under intrinsic sizing.
9329 // http://dev.w3.org/csswg/css-grid/#intrinsic-sizes
9330 NormalizeChildLists();
9331 GridReflowInput state(this, *aRenderingContext);
9332 InitImplicitNamedAreas(state.mGridStyle); // XXX optimize
9334 // The min/sz/max sizes are the input to the "repeat-to-fill" algorithm:
9335 // https://drafts.csswg.org/css-grid/#auto-repeat
9336 // They're only used for auto-repeat so we skip computing them otherwise.
9337 RepeatTrackSizingInput repeatSizing(state.mWM);
9338 if (!IsColSubgrid() && state.mColFunctions.mHasRepeatAuto) {
9339 repeatSizing.InitFromStyle(eLogicalAxisInline, state.mWM,
9340 state.mFrame->Style());
9342 if ((!IsRowSubgrid() && state.mRowFunctions.mHasRepeatAuto &&
9343 !(state.mGridStyle->mGridAutoFlow & StyleGridAutoFlow::ROW)) ||
9344 IsMasonry(eLogicalAxisInline)) {
9345 // Only 'grid-auto-flow:column' can create new implicit columns, so that's
9346 // the only case where our block-size can affect the number of columns.
9347 // Masonry layout always depends on how many rows we have though.
9348 repeatSizing.InitFromStyle(eLogicalAxisBlock, state.mWM,
9349 state.mFrame->Style());
9352 Grid grid;
9353 if (MOZ_LIKELY(!IsSubgrid())) {
9354 grid.PlaceGridItems(state, repeatSizing); // XXX optimize
9355 } else {
9356 auto* subgrid = GetProperty(Subgrid::Prop());
9357 state.mGridItems = subgrid->mGridItems.Clone();
9358 state.mAbsPosItems = subgrid->mAbsPosItems.Clone();
9359 grid.mGridColEnd = subgrid->mGridColEnd;
9360 grid.mGridRowEnd = subgrid->mGridRowEnd;
9363 auto constraint = aType == IntrinsicISizeType::MinISize
9364 ? SizingConstraint::MinContent
9365 : SizingConstraint::MaxContent;
9366 if (IsMasonry(eLogicalAxisInline)) {
9367 ReflowOutput desiredSize(state.mWM);
9368 nsSize containerSize;
9369 LogicalRect contentArea(state.mWM);
9370 nsReflowStatus status;
9371 state.mRows.mSizes.SetLength(grid.mGridRowEnd);
9372 state.CalculateTrackSizesForAxis(eLogicalAxisInline, grid,
9373 NS_UNCONSTRAINEDSIZE, constraint);
9374 return MasonryLayout(state, contentArea, constraint, desiredSize, status,
9375 nullptr, containerSize);
9378 if (grid.mGridColEnd == 0) {
9379 return nscoord(0);
9382 state.CalculateTrackSizesForAxis(eLogicalAxisInline, grid,
9383 NS_UNCONSTRAINEDSIZE, constraint);
9385 if (MOZ_LIKELY(!IsSubgrid())) {
9386 return state.mCols.SumOfGridTracksAndGaps();
9388 const auto& last = state.mCols.mSizes.LastElement();
9389 return last.mPosition + last.mBase;
9392 nscoord nsGridContainerFrame::GetMinISize(gfxContext* aRC) {
9393 auto* f = static_cast<nsGridContainerFrame*>(FirstContinuation());
9394 if (f != this) {
9395 return f->GetMinISize(aRC);
9398 DISPLAY_MIN_INLINE_SIZE(this, mCachedMinISize);
9399 if (mCachedMinISize == NS_INTRINSIC_ISIZE_UNKNOWN) {
9400 Maybe<nscoord> containISize = ContainIntrinsicISize();
9401 mCachedMinISize = containISize
9402 ? *containISize
9403 : IntrinsicISize(aRC, IntrinsicISizeType::MinISize);
9405 return mCachedMinISize;
9408 nscoord nsGridContainerFrame::GetPrefISize(gfxContext* aRC) {
9409 auto* f = static_cast<nsGridContainerFrame*>(FirstContinuation());
9410 if (f != this) {
9411 return f->GetPrefISize(aRC);
9414 DISPLAY_PREF_INLINE_SIZE(this, mCachedPrefISize);
9415 if (mCachedPrefISize == NS_INTRINSIC_ISIZE_UNKNOWN) {
9416 Maybe<nscoord> containISize = ContainIntrinsicISize();
9417 mCachedPrefISize = containISize
9418 ? *containISize
9419 : IntrinsicISize(aRC, IntrinsicISizeType::PrefISize);
9421 return mCachedPrefISize;
9424 void nsGridContainerFrame::MarkIntrinsicISizesDirty() {
9425 mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
9426 mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
9427 for (auto& perAxisBaseline : mBaseline) {
9428 for (auto& baseline : perAxisBaseline) {
9429 baseline = NS_INTRINSIC_ISIZE_UNKNOWN;
9432 nsContainerFrame::MarkIntrinsicISizesDirty();
9435 void nsGridContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
9436 const nsDisplayListSet& aLists) {
9437 DisplayBorderBackgroundOutline(aBuilder, aLists);
9438 if (GetPrevInFlow()) {
9439 DisplayOverflowContainers(aBuilder, aLists);
9442 // Our children are all grid-level boxes, which behave the same as
9443 // inline-blocks in painting, so their borders/backgrounds all go on
9444 // the BlockBorderBackgrounds list.
9445 typedef CSSOrderAwareFrameIterator::OrderState OrderState;
9446 OrderState order =
9447 HasAnyStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER)
9448 ? OrderState::Ordered
9449 : OrderState::Unordered;
9450 CSSOrderAwareFrameIterator iter(
9451 this, FrameChildListID::Principal,
9452 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll, order);
9453 const auto flags = DisplayFlagsForFlexOrGridItem();
9454 for (; !iter.AtEnd(); iter.Next()) {
9455 nsIFrame* child = *iter;
9456 BuildDisplayListForChild(aBuilder, child, aLists, flags);
9460 bool nsGridContainerFrame::DrainSelfOverflowList() {
9461 return DrainAndMergeSelfOverflowList();
9464 void nsGridContainerFrame::AppendFrames(ChildListID aListID,
9465 nsFrameList&& aFrameList) {
9466 NoteNewChildren(aListID, aFrameList);
9467 nsContainerFrame::AppendFrames(aListID, std::move(aFrameList));
9470 void nsGridContainerFrame::InsertFrames(
9471 ChildListID aListID, nsIFrame* aPrevFrame,
9472 const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) {
9473 NoteNewChildren(aListID, aFrameList);
9474 nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
9475 std::move(aFrameList));
9478 void nsGridContainerFrame::RemoveFrame(DestroyContext& aContext,
9479 ChildListID aListID,
9480 nsIFrame* aOldFrame) {
9481 MOZ_ASSERT(aListID == FrameChildListID::Principal, "unexpected child list");
9483 #ifdef DEBUG
9484 SetDidPushItemsBitIfNeeded(aListID, aOldFrame);
9485 #endif
9487 nsContainerFrame::RemoveFrame(aContext, aListID, aOldFrame);
9490 StyleAlignFlags nsGridContainerFrame::CSSAlignmentForAbsPosChild(
9491 const ReflowInput& aChildRI, LogicalAxis aLogicalAxis) const {
9492 MOZ_ASSERT(aChildRI.mFrame->IsAbsolutelyPositioned(),
9493 "This method should only be called for abspos children");
9495 StyleAlignFlags alignment =
9496 (aLogicalAxis == eLogicalAxisInline)
9497 ? aChildRI.mStylePosition->UsedJustifySelf(Style())._0
9498 : aChildRI.mStylePosition->UsedAlignSelf(Style())._0;
9500 // Extract and strip the flag bits
9501 StyleAlignFlags alignmentFlags = alignment & StyleAlignFlags::FLAG_BITS;
9502 alignment &= ~StyleAlignFlags::FLAG_BITS;
9504 if (alignment == StyleAlignFlags::NORMAL) {
9505 // "the 'normal' keyword behaves as 'start' on replaced
9506 // absolutely-positioned boxes, and behaves as 'stretch' on all other
9507 // absolutely-positioned boxes."
9508 // https://drafts.csswg.org/css-align/#align-abspos
9509 // https://drafts.csswg.org/css-align/#justify-abspos
9510 alignment = aChildRI.mFrame->IsFrameOfType(nsIFrame::eReplaced)
9511 ? StyleAlignFlags::START
9512 : StyleAlignFlags::STRETCH;
9513 } else if (alignment == StyleAlignFlags::FLEX_START) {
9514 alignment = StyleAlignFlags::START;
9515 } else if (alignment == StyleAlignFlags::FLEX_END) {
9516 alignment = StyleAlignFlags::END;
9517 } else if (alignment == StyleAlignFlags::LEFT ||
9518 alignment == StyleAlignFlags::RIGHT) {
9519 if (aLogicalAxis == eLogicalAxisInline) {
9520 const bool isLeft = (alignment == StyleAlignFlags::LEFT);
9521 WritingMode wm = GetWritingMode();
9522 alignment = (isLeft == wm.IsBidiLTR()) ? StyleAlignFlags::START
9523 : StyleAlignFlags::END;
9524 } else {
9525 alignment = StyleAlignFlags::START;
9527 } else if (alignment == StyleAlignFlags::BASELINE) {
9528 alignment = StyleAlignFlags::START;
9529 } else if (alignment == StyleAlignFlags::LAST_BASELINE) {
9530 alignment = StyleAlignFlags::END;
9533 return (alignment | alignmentFlags);
9536 nscoord nsGridContainerFrame::SynthesizeBaseline(
9537 const FindItemInGridOrderResult& aGridOrderItem, LogicalAxis aAxis,
9538 BaselineSharingGroup aGroup, const nsSize& aCBPhysicalSize, nscoord aCBSize,
9539 WritingMode aCBWM) {
9540 if (MOZ_UNLIKELY(!aGridOrderItem.mItem)) {
9541 // No item in this fragment - synthesize a baseline from our border-box.
9542 return ::SynthesizeBaselineFromBorderBox(aGroup, aCBWM, aCBSize);
9544 auto GetBBaseline = [](BaselineSharingGroup aGroup, WritingMode aWM,
9545 const nsIFrame* aFrame, nscoord* aBaseline) {
9546 return aGroup == BaselineSharingGroup::First
9547 ? nsLayoutUtils::GetFirstLineBaseline(aWM, aFrame, aBaseline)
9548 : nsLayoutUtils::GetLastLineBaseline(aWM, aFrame, aBaseline);
9550 nsIFrame* child = aGridOrderItem.mItem->mFrame;
9551 nsGridContainerFrame* grid = do_QueryFrame(child);
9552 auto childWM = child->GetWritingMode();
9553 bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM);
9554 nscoord baseline;
9555 nscoord start;
9556 nscoord size;
9557 if (aAxis == eLogicalAxisBlock) {
9558 start = child->GetLogicalNormalPosition(aCBWM, aCBPhysicalSize).B(aCBWM);
9559 size = child->BSize(aCBWM);
9560 if (grid && aGridOrderItem.mIsInEdgeTrack) {
9561 baseline = isOrthogonal ? grid->GetIBaseline(aGroup)
9562 : grid->GetBBaseline(aGroup);
9563 } else if (!isOrthogonal && aGridOrderItem.mIsInEdgeTrack) {
9564 baseline = child
9565 ->GetNaturalBaselineBOffset(childWM, aGroup,
9566 BaselineExportContext::Other)
9567 .valueOrFrom([aGroup, child, childWM]() {
9568 return Baseline::SynthesizeBOffsetFromBorderBox(
9569 child, childWM, aGroup);
9571 } else {
9572 baseline = ::SynthesizeBaselineFromBorderBox(aGroup, childWM, size);
9574 } else {
9575 start = child->GetLogicalNormalPosition(aCBWM, aCBPhysicalSize).I(aCBWM);
9576 size = child->ISize(aCBWM);
9577 if (grid && aGridOrderItem.mIsInEdgeTrack) {
9578 baseline = isOrthogonal ? grid->GetBBaseline(aGroup)
9579 : grid->GetIBaseline(aGroup);
9580 } else if (isOrthogonal && aGridOrderItem.mIsInEdgeTrack &&
9581 GetBBaseline(aGroup, childWM, child, &baseline)) {
9582 if (aGroup == BaselineSharingGroup::Last) {
9583 baseline = size - baseline; // convert to distance from border-box end
9585 } else {
9586 baseline = ::SynthesizeBaselineFromBorderBox(aGroup, childWM, size);
9589 return aGroup == BaselineSharingGroup::First
9590 ? start + baseline
9591 : aCBSize - start - size + baseline;
9594 void nsGridContainerFrame::CalculateBaselines(
9595 BaselineSet aBaselineSet, CSSOrderAwareFrameIterator* aIter,
9596 const nsTArray<GridItemInfo>* aGridItems, const Tracks& aTracks,
9597 uint32_t aFragmentStartTrack, uint32_t aFirstExcludedTrack, WritingMode aWM,
9598 const nsSize& aCBPhysicalSize, nscoord aCBBorderPaddingStart,
9599 nscoord aCBBorderPaddingEnd, nscoord aCBSize) {
9600 const auto axis = aTracks.mAxis;
9601 auto firstBaseline = aTracks.mBaseline[BaselineSharingGroup::First];
9602 if (!(aBaselineSet & BaselineSet::eFirst)) {
9603 mBaseline[axis][BaselineSharingGroup::First] =
9604 ::SynthesizeBaselineFromBorderBox(BaselineSharingGroup::First, aWM,
9605 aCBSize);
9606 } else if (firstBaseline == NS_INTRINSIC_ISIZE_UNKNOWN) {
9607 FindItemInGridOrderResult gridOrderFirstItem = FindFirstItemInGridOrder(
9608 *aIter, *aGridItems,
9609 axis == eLogicalAxisBlock ? &GridArea::mRows : &GridArea::mCols,
9610 axis == eLogicalAxisBlock ? &GridArea::mCols : &GridArea::mRows,
9611 aFragmentStartTrack);
9612 mBaseline[axis][BaselineSharingGroup::First] = SynthesizeBaseline(
9613 gridOrderFirstItem, axis, BaselineSharingGroup::First, aCBPhysicalSize,
9614 aCBSize, aWM);
9615 } else {
9616 // We have a 'first baseline' group in the start track in this fragment.
9617 // Convert it from track to grid container border-box coordinates.
9618 MOZ_ASSERT(!aGridItems->IsEmpty());
9619 nscoord gapBeforeStartTrack =
9620 aFragmentStartTrack == 0
9621 ? aTracks.GridLineEdge(aFragmentStartTrack,
9622 GridLineSide::AfterGridGap)
9623 : nscoord(0); // no content gap at start of fragment
9624 mBaseline[axis][BaselineSharingGroup::First] =
9625 aCBBorderPaddingStart + gapBeforeStartTrack + firstBaseline;
9628 auto lastBaseline = aTracks.mBaseline[BaselineSharingGroup::Last];
9629 if (!(aBaselineSet & BaselineSet::eLast)) {
9630 mBaseline[axis][BaselineSharingGroup::Last] =
9631 ::SynthesizeBaselineFromBorderBox(BaselineSharingGroup::Last, aWM,
9632 aCBSize);
9633 } else if (lastBaseline == NS_INTRINSIC_ISIZE_UNKNOWN) {
9634 // For finding items for the 'last baseline' we need to create a reverse
9635 // iterator ('aIter' is the forward iterator from the GridReflowInput).
9636 using Iter = ReverseCSSOrderAwareFrameIterator;
9637 auto orderState = aIter->ItemsAreAlreadyInOrder()
9638 ? Iter::OrderState::Ordered
9639 : Iter::OrderState::Unordered;
9640 Iter iter(this, FrameChildListID::Principal,
9641 Iter::ChildFilter::SkipPlaceholders, orderState);
9642 iter.SetItemCount(aGridItems->Length());
9643 FindItemInGridOrderResult gridOrderLastItem = FindLastItemInGridOrder(
9644 iter, *aGridItems,
9645 axis == eLogicalAxisBlock ? &GridArea::mRows : &GridArea::mCols,
9646 axis == eLogicalAxisBlock ? &GridArea::mCols : &GridArea::mRows,
9647 aFragmentStartTrack, aFirstExcludedTrack);
9648 mBaseline[axis][BaselineSharingGroup::Last] =
9649 SynthesizeBaseline(gridOrderLastItem, axis, BaselineSharingGroup::Last,
9650 aCBPhysicalSize, aCBSize, aWM);
9651 } else {
9652 // We have a 'last baseline' group in the end track in this fragment.
9653 // Convert it from track to grid container border-box coordinates.
9654 MOZ_ASSERT(!aGridItems->IsEmpty());
9655 auto borderBoxStartToEndOfEndTrack =
9656 aCBBorderPaddingStart +
9657 aTracks.GridLineEdge(aFirstExcludedTrack, GridLineSide::BeforeGridGap) -
9658 aTracks.GridLineEdge(aFragmentStartTrack, GridLineSide::BeforeGridGap);
9659 mBaseline[axis][BaselineSharingGroup::Last] =
9660 (aCBSize - borderBoxStartToEndOfEndTrack) + lastBaseline;
9664 #ifdef DEBUG_FRAME_DUMP
9665 nsresult nsGridContainerFrame::GetFrameName(nsAString& aResult) const {
9666 return MakeFrameName(u"GridContainer"_ns, aResult);
9669 void nsGridContainerFrame::ExtraContainerFrameInfo(nsACString& aTo) const {
9670 if (const void* const subgrid = GetProperty(Subgrid::Prop())) {
9671 aTo += nsPrintfCString(" [subgrid=%p]", subgrid);
9675 #endif
9677 /* static */ nsGridContainerFrame::FindItemInGridOrderResult
9678 nsGridContainerFrame::FindFirstItemInGridOrder(
9679 CSSOrderAwareFrameIterator& aIter, const nsTArray<GridItemInfo>& aGridItems,
9680 LineRange GridArea::*aMajor, LineRange GridArea::*aMinor,
9681 uint32_t aFragmentStartTrack) {
9682 FindItemInGridOrderResult result = {nullptr, false};
9683 uint32_t minMajor = kTranslatedMaxLine + 1;
9684 uint32_t minMinor = kTranslatedMaxLine + 1;
9685 aIter.Reset();
9686 for (; !aIter.AtEnd(); aIter.Next()) {
9687 const GridItemInfo& item = aGridItems[aIter.ItemIndex()];
9688 if ((item.mArea.*aMajor).mEnd <= aFragmentStartTrack) {
9689 continue; // item doesn't span any track in this fragment
9691 uint32_t major = (item.mArea.*aMajor).mStart;
9692 uint32_t minor = (item.mArea.*aMinor).mStart;
9693 if (major < minMajor || (major == minMajor && minor < minMinor)) {
9694 minMajor = major;
9695 minMinor = minor;
9696 result.mItem = &item;
9697 result.mIsInEdgeTrack = major == 0U;
9700 return result;
9703 /* static */ nsGridContainerFrame::FindItemInGridOrderResult
9704 nsGridContainerFrame::FindLastItemInGridOrder(
9705 ReverseCSSOrderAwareFrameIterator& aIter,
9706 const nsTArray<GridItemInfo>& aGridItems, LineRange GridArea::*aMajor,
9707 LineRange GridArea::*aMinor, uint32_t aFragmentStartTrack,
9708 uint32_t aFirstExcludedTrack) {
9709 FindItemInGridOrderResult result = {nullptr, false};
9710 int32_t maxMajor = -1;
9711 int32_t maxMinor = -1;
9712 aIter.Reset();
9713 int32_t lastMajorTrack = int32_t(aFirstExcludedTrack) - 1;
9714 for (; !aIter.AtEnd(); aIter.Next()) {
9715 const GridItemInfo& item = aGridItems[aIter.ItemIndex()];
9716 // Subtract 1 from the end line to get the item's last track index.
9717 int32_t major = (item.mArea.*aMajor).mEnd - 1;
9718 // Currently, this method is only called with aFirstExcludedTrack ==
9719 // the first track in the next fragment, so we take the opportunity
9720 // to assert this item really belongs to this fragment.
9721 MOZ_ASSERT((item.mArea.*aMajor).mStart < aFirstExcludedTrack,
9722 "found an item that belongs to some later fragment");
9723 if (major < int32_t(aFragmentStartTrack)) {
9724 continue; // item doesn't span any track in this fragment
9726 int32_t minor = (item.mArea.*aMinor).mEnd - 1;
9727 MOZ_ASSERT(minor >= 0 && major >= 0, "grid item must have span >= 1");
9728 if (major > maxMajor || (major == maxMajor && minor > maxMinor)) {
9729 maxMajor = major;
9730 maxMinor = minor;
9731 result.mItem = &item;
9732 result.mIsInEdgeTrack = major == lastMajorTrack;
9735 return result;
9738 nsGridContainerFrame::UsedTrackSizes* nsGridContainerFrame::GetUsedTrackSizes()
9739 const {
9740 return GetProperty(UsedTrackSizes::Prop());
9743 void nsGridContainerFrame::StoreUsedTrackSizes(
9744 LogicalAxis aAxis, const nsTArray<TrackSize>& aSizes) {
9745 auto* uts = GetUsedTrackSizes();
9746 if (!uts) {
9747 uts = new UsedTrackSizes();
9748 SetProperty(UsedTrackSizes::Prop(), uts);
9750 uts->mSizes[aAxis] = aSizes.Clone();
9751 uts->mCanResolveLineRangeSize[aAxis] = true;
9752 // XXX is resetting these bits necessary?
9753 for (auto& sz : uts->mSizes[aAxis]) {
9754 sz.mState &= ~(TrackSize::eFrozen | TrackSize::eSkipGrowUnlimited |
9755 TrackSize::eInfinitelyGrowable);
9759 #ifdef DEBUG
9760 void nsGridContainerFrame::SetInitialChildList(ChildListID aListID,
9761 nsFrameList&& aChildList) {
9762 ChildListIDs supportedLists = {FrameChildListID::Principal};
9763 // We don't handle the FrameChildListID::Backdrop frames in any way, but it
9764 // only contains a placeholder for ::backdrop which is OK to not reflow (for
9765 // now anyway).
9766 supportedLists += FrameChildListID::Backdrop;
9767 MOZ_ASSERT(supportedLists.contains(aListID), "unexpected child list");
9769 return nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
9772 void nsGridContainerFrame::TrackSize::DumpStateBits(StateBits aState) {
9773 printf("min:");
9774 if (aState & eAutoMinSizing) {
9775 printf("auto-min ");
9776 } else if (aState & eMinContentMinSizing) {
9777 printf("min-content ");
9778 } else if (aState & eMaxContentMinSizing) {
9779 printf("max-content ");
9781 printf(" max:");
9782 if (aState & eAutoMaxSizing) {
9783 printf("auto ");
9784 } else if (aState & eMinContentMaxSizing) {
9785 printf("min-content ");
9786 } else if (aState & eMaxContentMaxSizing) {
9787 printf("max-content ");
9788 } else if (aState & eFlexMaxSizing) {
9789 printf("flex ");
9791 if (aState & eFrozen) {
9792 printf("frozen ");
9794 if (aState & eModified) {
9795 printf("modified ");
9797 if (aState & eBreakBefore) {
9798 printf("break-before ");
9802 void nsGridContainerFrame::TrackSize::Dump() const {
9803 printf("mPosition=%d mBase=%d mLimit=%d ", mPosition, mBase, mLimit);
9804 DumpStateBits(mState);
9807 #endif // DEBUG
9809 nsGridContainerFrame* nsGridContainerFrame::GetGridContainerFrame(
9810 nsIFrame* aFrame) {
9811 nsGridContainerFrame* gridFrame = nullptr;
9813 if (aFrame) {
9814 nsIFrame* inner = aFrame;
9815 if (MOZ_UNLIKELY(aFrame->IsFieldSetFrame())) {
9816 inner = static_cast<nsFieldSetFrame*>(aFrame)->GetInner();
9818 // Since "Get" methods like GetInner and GetContentInsertionFrame can
9819 // return null, we check the return values before dereferencing. Our
9820 // calling pattern makes this unlikely, but we're being careful.
9821 nsIFrame* insertionFrame =
9822 inner ? inner->GetContentInsertionFrame() : nullptr;
9823 nsIFrame* possibleGridFrame = insertionFrame ? insertionFrame : aFrame;
9824 gridFrame = possibleGridFrame->IsGridContainerFrame()
9825 ? static_cast<nsGridContainerFrame*>(possibleGridFrame)
9826 : nullptr;
9828 return gridFrame;
9831 nsGridContainerFrame* nsGridContainerFrame::GetGridFrameWithComputedInfo(
9832 nsIFrame* aFrame) {
9833 nsGridContainerFrame* gridFrame = GetGridContainerFrame(aFrame);
9834 if (!gridFrame) {
9835 return nullptr;
9838 auto HasComputedInfo = [](const nsGridContainerFrame& aFrame) -> bool {
9839 return aFrame.HasProperty(GridColTrackInfo()) &&
9840 aFrame.HasProperty(GridRowTrackInfo()) &&
9841 aFrame.HasProperty(GridColumnLineInfo()) &&
9842 aFrame.HasProperty(GridRowLineInfo());
9845 if (HasComputedInfo(*gridFrame)) {
9846 return gridFrame;
9849 // Trigger a reflow that generates additional grid property data.
9850 // Hold onto aFrame while we do this, in case reflow destroys it.
9851 AutoWeakFrame weakFrameRef(gridFrame);
9853 RefPtr<mozilla::PresShell> presShell = gridFrame->PresShell();
9854 gridFrame->SetShouldGenerateComputedInfo(true);
9855 presShell->FrameNeedsReflow(gridFrame, IntrinsicDirty::None,
9856 NS_FRAME_IS_DIRTY);
9857 presShell->FlushPendingNotifications(FlushType::Layout);
9859 // If the weakFrameRef is no longer valid, then we must bail out.
9860 if (!weakFrameRef.IsAlive()) {
9861 return nullptr;
9864 // This can happen if for some reason we ended up not reflowing, like in print
9865 // preview under some circumstances.
9866 if (MOZ_UNLIKELY(!HasComputedInfo(*gridFrame))) {
9867 return nullptr;
9870 return gridFrame;
9873 // TODO: This is a rather dumb implementation of nsILineIterator, but it's
9874 // better than our pre-existing behavior. Ideally, we should probably use the
9875 // grid information to return a meaningful number of lines etc.
9876 bool nsGridContainerFrame::IsLineIteratorFlowRTL() { return false; }
9878 int32_t nsGridContainerFrame::GetNumLines() const {
9879 return mFrames.GetLength();
9882 Result<nsILineIterator::LineInfo, nsresult> nsGridContainerFrame::GetLine(
9883 int32_t aLineNumber) {
9884 if (aLineNumber < 0 || aLineNumber >= GetNumLines()) {
9885 return Err(NS_ERROR_FAILURE);
9887 LineInfo rv;
9888 nsIFrame* f = mFrames.FrameAt(aLineNumber);
9889 rv.mLineBounds = f->GetRect();
9890 rv.mFirstFrameOnLine = f;
9891 rv.mNumFramesOnLine = 1;
9892 return rv;
9895 int32_t nsGridContainerFrame::FindLineContaining(nsIFrame* aFrame,
9896 int32_t aStartLine) {
9897 const int32_t index = mFrames.IndexOf(aFrame);
9898 if (index < 0) {
9899 return -1;
9901 if (index < aStartLine) {
9902 return -1;
9904 return index;
9907 NS_IMETHODIMP
9908 nsGridContainerFrame::CheckLineOrder(int32_t aLine, bool* aIsReordered,
9909 nsIFrame** aFirstVisual,
9910 nsIFrame** aLastVisual) {
9911 *aIsReordered = false;
9912 *aFirstVisual = nullptr;
9913 *aLastVisual = nullptr;
9914 return NS_OK;
9917 NS_IMETHODIMP
9918 nsGridContainerFrame::FindFrameAt(int32_t aLineNumber, nsPoint aPos,
9919 nsIFrame** aFrameFound,
9920 bool* aPosIsBeforeFirstFrame,
9921 bool* aPosIsAfterLastFrame) {
9922 const auto wm = GetWritingMode();
9923 const LogicalPoint pos(wm, aPos, GetSize());
9925 *aFrameFound = nullptr;
9926 *aPosIsBeforeFirstFrame = true;
9927 *aPosIsAfterLastFrame = false;
9929 nsIFrame* f = mFrames.FrameAt(aLineNumber);
9930 if (!f) {
9931 return NS_OK;
9934 auto rect = f->GetLogicalRect(wm, GetSize());
9935 *aFrameFound = f;
9936 *aPosIsBeforeFirstFrame = pos.I(wm) < rect.IStart(wm);
9937 *aPosIsAfterLastFrame = pos.I(wm) > rect.IEnd(wm);
9938 return NS_OK;