nsGridContainerFrame.cpp (465449B)
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/. */ 6 7 /* rendering object for CSS "display: grid | inline-grid" */ 8 9 #include "nsGridContainerFrame.h" 10 11 #include <stdlib.h> // for div() 12 13 #include <functional> 14 #include <type_traits> 15 16 #include "fmt/format.h" 17 #include "gfxContext.h" 18 #include "mozilla/AbsoluteContainingBlock.h" 19 #include "mozilla/AutoRestore.h" 20 #include "mozilla/Baseline.h" 21 #include "mozilla/CSSAlignUtils.h" 22 #include "mozilla/ComputedStyle.h" 23 #include "mozilla/IntegerRange.h" 24 #include "mozilla/Maybe.h" 25 #include "mozilla/PodOperations.h" // for PodZero 26 #include "mozilla/PresShell.h" 27 #include "mozilla/ScrollContainerFrame.h" 28 #include "mozilla/StaticPrefs_layout.h" 29 #include "mozilla/dom/Grid.h" 30 #include "mozilla/dom/GridBinding.h" 31 #include "nsCSSFrameConstructor.h" 32 #include "nsDisplayList.h" 33 #include "nsFieldSetFrame.h" 34 #include "nsHashKeys.h" 35 #include "nsIFrameInlines.h" // for nsIFrame::GetLogicalNormalPosition (don't remove) 36 #include "nsLayoutUtils.h" 37 #include "nsPlaceholderFrame.h" 38 #include "nsPresContext.h" 39 #include "nsReadableUtils.h" 40 #include "nsTableWrapperFrame.h" 41 42 using namespace mozilla; 43 44 using AlignJustifyFlag = CSSAlignUtils::AlignJustifyFlag; 45 using AlignJustifyFlags = CSSAlignUtils::AlignJustifyFlags; 46 using GridItemCachedBAxisMeasurement = 47 nsGridContainerFrame::CachedBAxisMeasurement; 48 using GridTemplate = StyleGridTemplateComponent; 49 using NameList = StyleOwnedSlice<StyleCustomIdent>; 50 using SizingConstraint = nsGridContainerFrame::SizingConstraint; 51 using TrackListValue = 52 StyleGenericTrackListValue<LengthPercentage, StyleInteger>; 53 using TrackRepeat = StyleGenericTrackRepeat<LengthPercentage, StyleInteger>; 54 using TrackSize = nsGridContainerFrame::TrackSize; 55 56 static mozilla::LazyLogModule gGridContainerLog("GridContainer"); 57 #define GRID_LOG(...) \ 58 MOZ_LOG(gGridContainerLog, LogLevel::Debug, (__VA_ARGS__)); 59 60 static const int32_t kMaxLine = StyleMAX_GRID_LINE; 61 static const int32_t kMinLine = StyleMIN_GRID_LINE; 62 // The maximum line number, in the zero-based translated grid. 63 static const uint32_t kTranslatedMaxLine = uint32_t(kMaxLine - kMinLine); 64 static const uint32_t kAutoLine = kTranslatedMaxLine + 3457U; 65 66 static const nsFrameState kIsSubgridBits = 67 (NS_STATE_GRID_IS_COL_SUBGRID | NS_STATE_GRID_IS_ROW_SUBGRID); 68 69 namespace mozilla { 70 71 template <> 72 inline Span<const StyleOwnedSlice<StyleCustomIdent>> 73 GridTemplate::LineNameLists(bool aIsSubgrid) const { 74 if (IsTrackList()) { 75 return AsTrackList()->line_names.AsSpan(); 76 } 77 if (IsSubgrid() && aIsSubgrid) { 78 // For subgrid, we need to resolve <line-name-list> from each 79 // StyleGenericLineNameListValue, so return empty. 80 return {}; 81 } 82 MOZ_ASSERT(IsNone() || IsMasonry() || (IsSubgrid() && !aIsSubgrid)); 83 return {}; 84 } 85 86 template <> 87 inline const StyleTrackBreadth& StyleTrackSize::GetMax() const { 88 if (IsBreadth()) { 89 return AsBreadth(); 90 } 91 if (IsMinmax()) { 92 return AsMinmax()._1; 93 } 94 MOZ_ASSERT(IsFitContent()); 95 return AsFitContent(); 96 } 97 98 template <> 99 inline const StyleTrackBreadth& StyleTrackSize::GetMin() const { 100 static const StyleTrackBreadth kAuto = StyleTrackBreadth::Auto(); 101 if (IsBreadth()) { 102 // <flex> behaves like minmax(auto, <flex>) 103 return AsBreadth().IsFr() ? kAuto : AsBreadth(); 104 } 105 if (IsMinmax()) { 106 return AsMinmax()._0; 107 } 108 MOZ_ASSERT(IsFitContent()); 109 return kAuto; 110 } 111 112 } // namespace mozilla 113 114 static nscoord ClampToCSSMaxBSize(nscoord aSize, 115 const ReflowInput* aReflowInput) { 116 auto maxSize = aReflowInput->ComputedMaxBSize(); 117 if (MOZ_UNLIKELY(maxSize != NS_UNCONSTRAINEDSIZE)) { 118 MOZ_ASSERT(aReflowInput->ComputedMinBSize() <= maxSize); 119 aSize = std::min(aSize, maxSize); 120 } 121 return aSize; 122 } 123 124 // Same as above and set aStatus INCOMPLETE if aSize wasn't clamped. 125 // (If we clamp aSize it means our size is less than the break point, 126 // i.e. we're effectively breaking in our overflow, so we should leave 127 // aStatus as is (it will likely be set to OVERFLOW_INCOMPLETE later)). 128 static nscoord ClampToCSSMaxBSize(nscoord aSize, 129 const ReflowInput* aReflowInput, 130 nsReflowStatus* aStatus) { 131 auto maxSize = aReflowInput->ComputedMaxBSize(); 132 if (MOZ_UNLIKELY(maxSize != NS_UNCONSTRAINEDSIZE)) { 133 MOZ_ASSERT(aReflowInput->ComputedMinBSize() <= maxSize); 134 if (aSize < maxSize) { 135 aStatus->SetIncomplete(); 136 } else { 137 aSize = maxSize; 138 } 139 } else { 140 aStatus->SetIncomplete(); 141 } 142 return aSize; 143 } 144 145 template <typename Size> 146 static bool IsPercentOfIndefiniteSize(const Size& aCoord, 147 nscoord aPercentBasis) { 148 return aPercentBasis == NS_UNCONSTRAINEDSIZE && aCoord.HasPercent(); 149 } 150 151 static nscoord ResolveToDefiniteSize(const StyleTrackBreadth& aBreadth, 152 nscoord aPercentBasis) { 153 MOZ_ASSERT(aBreadth.IsBreadth()); 154 if (::IsPercentOfIndefiniteSize(aBreadth.AsBreadth(), aPercentBasis)) { 155 return nscoord(0); 156 } 157 return std::max(nscoord(0), aBreadth.AsBreadth().Resolve(aPercentBasis)); 158 } 159 160 // Synthesize a baseline from a border box. For an alphabetical baseline 161 // this is the end edge of the border box. For a central baseline it's 162 // the center of the border box. 163 // https://drafts.csswg.org/css-align-3/#synthesize-baseline 164 // For a 'first baseline' the measure is from the border-box start edge and 165 // for a 'last baseline' the measure is from the border-box end edge. 166 // 167 // The 'LogicalAxis aAxis' represents the axis (in terms of aWM) that the 168 // baseline corresponds to. (Typically, baselines are a measurement in the 169 // block axis; e.g. for English horizontal-tb text, a traditional baseline 170 // would be a y-axis measurement. But in some cases (e.g. orthogonal WMs), we 171 // may need to synthesize a baseline in a child's inline axis, which is when 172 // this function might receive an aAxis of LogicalAxis::Inline. In that case, we 173 // assume that the writing mode's preference for central vs. alphabetic 174 // baselines is irrelevant, since that's a choice about its block-axis 175 // baselines, and we just unconditionally use the alphabetic baseline 176 // (e.g. border-box bottom edge). 177 static nscoord SynthesizeBaselineFromBorderBox(BaselineSharingGroup aGroup, 178 WritingMode aWM, 179 LogicalAxis aAxis, 180 nscoord aBorderBoxSize) { 181 const bool useAlphabeticBaseline = 182 (aAxis == LogicalAxis::Inline) ? true : aWM.IsAlphabeticalBaseline(); 183 184 if (aGroup == BaselineSharingGroup::First) { 185 return useAlphabeticBaseline ? aBorderBoxSize : aBorderBoxSize / 2; 186 } 187 MOZ_ASSERT(aGroup == BaselineSharingGroup::Last); 188 // Round up for central baseline offset, to be consistent with eFirst. 189 return useAlphabeticBaseline ? 0 190 : (aBorderBoxSize / 2) + (aBorderBoxSize % 2); 191 } 192 193 // The helper struct to hold the box sizing adjustment. 194 struct BoxSizingAdjustment { 195 BoxSizingAdjustment() = delete; 196 BoxSizingAdjustment(const WritingMode aWM, const ComputedStyle& aStyle) 197 : mWM(aWM), mStyle(aStyle) {} 198 199 const LogicalSize& EnsureAndGet() { 200 if (mValue) { 201 return mValue.ref(); 202 } 203 204 if (mStyle.StylePosition()->mBoxSizing != StyleBoxSizing::Border) { 205 // Use default, (0, 0). 206 mValue.emplace(mWM); 207 return mValue.ref(); 208 } 209 210 const auto& padding = mStyle.StylePadding()->mPadding; 211 LogicalMargin border(mWM, mStyle.StyleBorder()->GetComputedBorder()); 212 // We can use zero percentage basis since this is only called from 213 // intrinsic sizing code. 214 const nscoord percentageBasis = 0; 215 const nscoord iBP = 216 std::max(padding.GetIStart(mWM).Resolve(percentageBasis), 0) + 217 std::max(padding.GetIEnd(mWM).Resolve(percentageBasis), 0) + 218 border.IStartEnd(mWM); 219 const nscoord bBP = 220 std::max(padding.GetBStart(mWM).Resolve(percentageBasis), 0) + 221 std::max(padding.GetBEnd(mWM).Resolve(percentageBasis), 0) + 222 border.BStartEnd(mWM); 223 mValue.emplace(mWM, iBP, bBP); 224 return mValue.ref(); 225 } 226 227 private: 228 const WritingMode mWM; 229 const ComputedStyle& mStyle; 230 // The wrapped value we would like to use for the box sizing adjustment. 231 Maybe<LogicalSize> mValue; 232 }; 233 234 static Maybe<nscoord> GetPercentageBasisForAR( 235 const LogicalAxis aRatioDeterminingAxis, const WritingMode aWM, 236 const Maybe<LogicalSize>& aContainingBlockSize) { 237 if (!aContainingBlockSize) { 238 return Nothing(); 239 } 240 241 const nscoord basis = aContainingBlockSize->Size(aRatioDeterminingAxis, aWM); 242 // If the basis is unconstrained (because we are still computing the 243 // containing block size), we should treat it as no basis. 244 return basis == NS_UNCONSTRAINEDSIZE ? Nothing() : Some(basis); 245 } 246 247 template <typename Type> 248 static Maybe<nscoord> ComputeTransferredSize( 249 const Type& aRatioDeterminingSize, const LogicalAxis aAxis, 250 const WritingMode aWM, const AspectRatio& aAspectRatio, 251 BoxSizingAdjustment& aBoxSizingAdjustment, 252 const Maybe<LogicalSize>& aContainingBlockSize) { 253 // Use GetOrthogonalAxis() to get the ratio-determining axis. 254 const Maybe<nscoord> basis = GetPercentageBasisForAR( 255 GetOrthogonalAxis(aAxis), aWM, aContainingBlockSize); 256 nscoord rdSize = 0; 257 if (aRatioDeterminingSize->ConvertsToLength()) { 258 rdSize = aRatioDeterminingSize->ToLength(); 259 } else if (aRatioDeterminingSize->HasPercent() && basis) { 260 rdSize = aRatioDeterminingSize->AsLengthPercentage().Resolve(*basis); 261 } else { 262 // Either we are not using LengthPercentage or there is no percentage basis. 263 return Nothing(); 264 } 265 return Some(aAspectRatio.ComputeRatioDependentSize( 266 aAxis, aWM, rdSize, aBoxSizingAdjustment.EnsureAndGet())); 267 } 268 269 // A cached result for a grid item's block-axis measuring reflow. This 270 // cache prevents us from doing exponential reflows in cases of deeply 271 // nested grid frames. 272 // 273 // We store the cached value in the grid item's frame property table. 274 // 275 // We cache the following as a "key" 276 // - The size of the grid area in the item's inline axis 277 // - The item's block axis baseline padding 278 // ...and we cache the following as the "value", 279 // - The item's border-box BSize 280 class nsGridContainerFrame::CachedBAxisMeasurement final { 281 public: 282 NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, CachedBAxisMeasurement) 283 284 CachedBAxisMeasurement(const nsIFrame* aFrame, const LogicalSize& aCBSize, 285 const nscoord aBSize) 286 : mKey(aFrame, aCBSize), mBSize(aBSize) {} 287 288 bool IsValidFor(const nsIFrame* aFrame, const LogicalSize& aCBSize) const { 289 if (aFrame->IsSubtreeDirty()) { 290 return false; 291 } 292 return mKey == Key(aFrame, aCBSize); 293 } 294 295 nscoord BSize() const { return mBSize; } 296 297 void Update(const nsIFrame* aFrame, const LogicalSize& aCBSize, 298 const nscoord aBSize) { 299 mKey.Update(aFrame, aCBSize); 300 mBSize = aBSize; 301 } 302 303 private: 304 struct Key final { 305 nscoord mCBSizeInItemInlineAxis; 306 nscoord mBaselinePaddingInItemBlockAxis; 307 308 Key(const nsIFrame* aFrame, const LogicalSize& aCBSize) { 309 Update(aFrame, aCBSize); 310 } 311 312 void Update(const nsIFrame* aFrame, const LogicalSize& aCBSize) { 313 mCBSizeInItemInlineAxis = aCBSize.ISize(aFrame->GetWritingMode()); 314 mBaselinePaddingInItemBlockAxis = 315 aFrame->GetProperty(nsIFrame::BBaselinePadProperty()); 316 } 317 318 bool operator==(const Key& aOther) const = default; 319 }; 320 321 Key mKey; 322 nscoord mBSize; 323 }; 324 325 // The input sizes for calculating the number of repeat(auto-fill/fit) tracks. 326 // https://drafts.csswg.org/css-grid-2/#auto-repeat 327 struct RepeatTrackSizingInput { 328 explicit RepeatTrackSizingInput(WritingMode aWM) 329 : mMin(aWM, 0, 0), 330 mSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE), 331 mMax(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) {} 332 333 RepeatTrackSizingInput(const LogicalSize& aMin, const LogicalSize& aSize, 334 const LogicalSize& aMax) 335 : mMin(aMin), mSize(aSize), mMax(aMax) {} 336 337 // This should be used in intrinsic sizing (i.e. when we can't initialize 338 // the sizes directly from ReflowInput values). 339 void InitFromStyle(LogicalAxis aAxis, WritingMode aWM, const nsIFrame* aFrame, 340 const ComputedStyle* aStyle, 341 const AspectRatio& aAspectRatio, 342 const Maybe<LogicalSize>& aContainingBlockSize) { 343 const auto& pos = aStyle->StylePosition(); 344 const AnchorPosResolutionParams anchorResolutionParams{ 345 aFrame, aStyle->StyleDisplay()->mPosition}; 346 BoxSizingAdjustment boxSizingAdjustment(aWM, *aStyle); 347 const nscoord cbSizeInAxis = aContainingBlockSize 348 ? aContainingBlockSize->Size(aAxis, aWM) 349 : NS_UNCONSTRAINEDSIZE; 350 351 auto adjustForBoxSizing = [aWM, aAxis, 352 &boxSizingAdjustment](nscoord aSize) { 353 return std::max( 354 aSize - boxSizingAdjustment.EnsureAndGet().Size(aAxis, aWM), 0); 355 }; 356 357 nscoord& min = mMin.Size(aAxis, aWM); 358 const auto styleMinSize = pos->MinSize(aAxis, aWM, anchorResolutionParams); 359 if (styleMinSize->ConvertsToLength()) { 360 min = adjustForBoxSizing(styleMinSize->ToLength()); 361 } else if (styleMinSize->HasPercent() && 362 cbSizeInAxis != NS_UNCONSTRAINEDSIZE) { 363 min = adjustForBoxSizing( 364 styleMinSize->AsLengthPercentage().Resolve(cbSizeInAxis)); 365 } else if (aAspectRatio && styleMinSize->BehavesLikeInitialValue(aAxis)) { 366 // Use GetOrthogonalAxis() to get the ratio-determining axis. Same for max 367 // and size below in this function. 368 const auto styleRDMinSize = 369 pos->MinSize(GetOrthogonalAxis(aAxis), aWM, anchorResolutionParams); 370 if (Maybe<nscoord> resolvedMinSize = ComputeTransferredSize( 371 styleRDMinSize, aAxis, aWM, aAspectRatio, boxSizingAdjustment, 372 aContainingBlockSize)) { 373 min = *resolvedMinSize; 374 } 375 } 376 377 nscoord& max = mMax.Size(aAxis, aWM); 378 const auto styleMaxSize = pos->MaxSize(aAxis, aWM, anchorResolutionParams); 379 if (styleMaxSize->ConvertsToLength()) { 380 max = std::max(min, adjustForBoxSizing(styleMaxSize->ToLength())); 381 } else if (styleMaxSize->HasPercent() && 382 cbSizeInAxis != NS_UNCONSTRAINEDSIZE) { 383 max = std::max( 384 min, adjustForBoxSizing( 385 styleMaxSize->AsLengthPercentage().Resolve(cbSizeInAxis))); 386 } else if (aAspectRatio && styleMaxSize->BehavesLikeInitialValue(aAxis)) { 387 const auto styleRDMaxSize = 388 pos->MaxSize(GetOrthogonalAxis(aAxis), aWM, anchorResolutionParams); 389 if (Maybe<nscoord> resolvedMaxSize = ComputeTransferredSize( 390 styleRDMaxSize, aAxis, aWM, aAspectRatio, boxSizingAdjustment, 391 aContainingBlockSize)) { 392 max = std::max(min, *resolvedMaxSize); 393 } 394 } 395 396 nscoord& size = mSize.Size(aAxis, aWM); 397 // When computing the intrinsic inline size, disregard the explicit 398 // inline-size property as it should not affect the final result. 399 const auto styleSize = aAxis == LogicalAxis::Inline 400 ? AnchorResolvedSizeHelper::Auto() 401 : pos->BSize(aWM, anchorResolutionParams); 402 if (styleSize->ConvertsToLength()) { 403 size = std::clamp(adjustForBoxSizing(styleSize->ToLength()), min, max); 404 } else if (styleSize->HasPercent() && 405 cbSizeInAxis != NS_UNCONSTRAINEDSIZE) { 406 size = 407 std::clamp(adjustForBoxSizing( 408 styleSize->AsLengthPercentage().Resolve(cbSizeInAxis)), 409 min, max); 410 } else if (aAspectRatio && styleSize->BehavesLikeInitialValue(aAxis)) { 411 const auto styleRDSize = 412 pos->Size(GetOrthogonalAxis(aAxis), aWM, anchorResolutionParams); 413 if (Maybe<nscoord> resolvedSize = ComputeTransferredSize( 414 styleRDSize, aAxis, aWM, aAspectRatio, boxSizingAdjustment, 415 aContainingBlockSize)) { 416 size = std::clamp(*resolvedSize, min, max); 417 } 418 } 419 } 420 421 LogicalSize mMin; 422 LogicalSize mSize; 423 LogicalSize mMax; 424 }; 425 426 enum class GridLineSide { 427 BeforeGridGap, 428 AfterGridGap, 429 }; 430 431 struct nsGridContainerFrame::TrackSize { 432 enum StateBits : uint16_t { 433 eAutoMinSizing = 1 << 0, 434 eMinContentMinSizing = 1 << 1, 435 eMaxContentMinSizing = 1 << 2, 436 eMinOrMaxContentMinSizing = eMinContentMinSizing | eMaxContentMinSizing, 437 eIntrinsicMinSizing = eMinOrMaxContentMinSizing | eAutoMinSizing, 438 eModified = 1 << 3, 439 eAutoMaxSizing = 1 << 4, 440 eMinContentMaxSizing = 1 << 5, 441 eMaxContentMaxSizing = 1 << 6, 442 eAutoOrMaxContentMaxSizing = eAutoMaxSizing | eMaxContentMaxSizing, 443 eIntrinsicMaxSizing = eAutoOrMaxContentMaxSizing | eMinContentMaxSizing, 444 eFlexMaxSizing = 1 << 7, 445 eFrozen = 1 << 8, 446 eSkipGrowUnlimited1 = 1 << 9, 447 eSkipGrowUnlimited2 = 1 << 10, 448 eSkipGrowUnlimited = eSkipGrowUnlimited1 | eSkipGrowUnlimited2, 449 eBreakBefore = 1 << 11, 450 eApplyFitContentClamping = 1 << 12, 451 eInfinitelyGrowable = 1 << 13, 452 453 // These are only used in the masonry axis. They share the same value 454 // as *MinSizing above, but that's OK because we don't use those in 455 // the masonry axis. 456 // 457 // This track corresponds to an item margin-box size that is stretching. 458 eItemStretchSize = 1 << 0, 459 // This bit says that we should clamp that size to mLimit. 460 eClampToLimit = 1 << 1, 461 // This bit says that the corresponding item has `auto` margin(s). 462 eItemHasAutoMargin = 1 << 2, 463 }; 464 465 StateBits Initialize(nscoord aPercentageBasis, const StyleTrackSize&); 466 bool IsFrozen() const { return mState & eFrozen; } 467 #ifdef DEBUG 468 static void DumpStateBits(StateBits aState); 469 void Dump() const; 470 #endif 471 472 static bool IsDefiniteMaxSizing(StateBits aStateBits) { 473 return (aStateBits & (eIntrinsicMaxSizing | eFlexMaxSizing)) == 0; 474 } 475 476 // Base size of this track. 477 // https://drafts.csswg.org/css-grid-2/#base-size 478 nscoord mBase; 479 480 // Growth limit of this track. 481 // https://drafts.csswg.org/css-grid-2/#growth-limit 482 nscoord mLimit; 483 484 nscoord mPosition; // zero until we apply 'align/justify-content' 485 // mBaselineSubtreeSize is the size of a baseline-aligned subtree within 486 // this track. One subtree per baseline-sharing group (per track). 487 PerBaseline<nscoord> mBaselineSubtreeSize; 488 StateBits mState; 489 }; 490 491 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(TrackSize::StateBits) 492 493 static_assert( 494 std::is_trivially_copyable<nsGridContainerFrame::TrackSize>::value, 495 "Must be trivially copyable"); 496 static_assert( 497 std::is_trivially_destructible<nsGridContainerFrame::TrackSize>::value, 498 "Must be trivially destructible"); 499 500 TrackSize::StateBits nsGridContainerFrame::TrackSize::Initialize( 501 nscoord aPercentageBasis, const StyleTrackSize& aSize) { 502 using Tag = StyleTrackBreadth::Tag; 503 504 MOZ_ASSERT(mBase == 0 && mLimit == 0 && mState == 0, 505 "track size data is expected to be initialized to zero"); 506 mBaselineSubtreeSize[BaselineSharingGroup::First] = nscoord(0); 507 mBaselineSubtreeSize[BaselineSharingGroup::Last] = nscoord(0); 508 509 auto& min = aSize.GetMin(); 510 auto& max = aSize.GetMax(); 511 512 Tag minSizeTag = min.tag; 513 Tag maxSizeTag = max.tag; 514 if (aSize.IsFitContent()) { 515 // In layout, fit-content(size) behaves as minmax(auto, max-content), with 516 // 'size' as an additional upper-bound. 517 if (!::IsPercentOfIndefiniteSize(aSize.AsFitContent(), aPercentageBasis)) { 518 mState = eApplyFitContentClamping; 519 } 520 minSizeTag = Tag::Auto; 521 maxSizeTag = Tag::MaxContent; 522 } 523 if (::IsPercentOfIndefiniteSize(min, aPercentageBasis)) { 524 // https://drafts.csswg.org/css-grid-2/#valdef-grid-template-columns-length-percentage-0 525 // "If the inline or block size of the grid container is indefinite, 526 // <percentage> values relative to that size are treated as 'auto'." 527 minSizeTag = Tag::Auto; 528 } 529 if (::IsPercentOfIndefiniteSize(max, aPercentageBasis)) { 530 maxSizeTag = Tag::Auto; 531 } 532 533 // https://drafts.csswg.org/css-grid-2/#algo-init 534 switch (minSizeTag) { 535 case Tag::Auto: 536 mState |= eAutoMinSizing; 537 break; 538 case Tag::MinContent: 539 mState |= eMinContentMinSizing; 540 break; 541 case Tag::MaxContent: 542 mState |= eMaxContentMinSizing; 543 break; 544 default: 545 MOZ_ASSERT(!min.IsFr(), "<flex> min-sizing is invalid as a track size"); 546 mBase = ::ResolveToDefiniteSize(min, aPercentageBasis); 547 } 548 switch (maxSizeTag) { 549 case Tag::Auto: 550 mState |= eAutoMaxSizing; 551 mLimit = NS_UNCONSTRAINEDSIZE; 552 break; 553 case Tag::MinContent: 554 case Tag::MaxContent: 555 mState |= maxSizeTag == Tag::MinContent ? eMinContentMaxSizing 556 : eMaxContentMaxSizing; 557 mLimit = NS_UNCONSTRAINEDSIZE; 558 break; 559 case Tag::Fr: 560 mState |= eFlexMaxSizing; 561 mLimit = NS_UNCONSTRAINEDSIZE; 562 break; 563 default: 564 mLimit = ::ResolveToDefiniteSize(max, aPercentageBasis); 565 if (mLimit < mBase) { 566 mLimit = mBase; 567 } 568 } 569 return mState; 570 } 571 572 // Indicates if we are in intrinsic sizing step 3 (spanning items not 573 // spanning any flex tracks) or step 4 (spanning items that span one or more 574 // flex tracks). 575 // https://drafts.csswg.org/css-grid-2/#algo-content 576 enum class TrackSizingStep { 577 NotFlex, // https://drafts.csswg.org/css-grid-2/#algo-spanning-items 578 Flex, // https://drafts.csswg.org/css-grid-2/#algo-spanning-flex-items 579 }; 580 581 // Sizing phases, used in intrinsic sizing steps 3 and 4. 582 // https://drafts.csswg.org/css-grid-2/#algo-spanning-items 583 enum class TrackSizingPhase { 584 IntrinsicMinimums, 585 ContentBasedMinimums, 586 MaxContentMinimums, 587 IntrinsicMaximums, 588 MaxContentMaximums, 589 }; 590 591 // Used for grid items intrinsic size types. 592 // See CachedIntrinsicSizes which uses this for content contributions. 593 enum class GridIntrinsicSizeType { 594 // MinContribution is the "minimum contribution", defined at 595 // https://drafts.csswg.org/css-grid-2/#min-size-contribution 596 MinContribution, 597 MinContentContribution, 598 MaxContentContribution 599 }; 600 601 static constexpr GridIntrinsicSizeType kAllGridIntrinsicSizeTypes[] = { 602 GridIntrinsicSizeType::MinContribution, 603 GridIntrinsicSizeType::MinContentContribution, 604 GridIntrinsicSizeType::MaxContentContribution}; 605 606 // Glue to make mozilla::EnumeratedArray work with GridIntrinsicSizeType. 607 namespace mozilla { 608 template <> 609 struct MaxContiguousEnumValue<GridIntrinsicSizeType> { 610 static constexpr GridIntrinsicSizeType value = 611 GridIntrinsicSizeType::MaxContentContribution; 612 }; 613 } // namespace mozilla 614 615 // Convert a track sizing phase into which GridIntrinsicSizeType is applicable. 616 static GridIntrinsicSizeType SizeTypeForPhase(TrackSizingPhase aPhase) { 617 switch (aPhase) { 618 case TrackSizingPhase::IntrinsicMinimums: 619 return GridIntrinsicSizeType::MinContribution; 620 case TrackSizingPhase::ContentBasedMinimums: 621 case TrackSizingPhase::IntrinsicMaximums: 622 return GridIntrinsicSizeType::MinContentContribution; 623 case TrackSizingPhase::MaxContentMinimums: 624 case TrackSizingPhase::MaxContentMaximums: 625 return GridIntrinsicSizeType::MaxContentContribution; 626 } 627 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected phase"); 628 } 629 630 class nsGridContainerFrame::TrackPlan { 631 public: 632 TrackPlan() = default; 633 634 explicit TrackPlan(size_t aCapacity) : mTrackSizes(aCapacity) {} 635 636 TrackPlan(const TrackPlan& aOther) : mTrackSizes(aOther.mTrackSizes) {} 637 638 TrackPlan(TrackPlan&& aOther) : mTrackSizes(std::move(aOther.mTrackSizes)) {} 639 640 ~TrackPlan() = default; 641 642 TrackPlan& operator=(const TrackPlan& aOther) { 643 mTrackSizes.Assign(aOther.mTrackSizes); 644 return *this; 645 } 646 TrackPlan& operator=(TrackPlan&& aOther) { 647 mTrackSizes.Assign(std::move(aOther.mTrackSizes)); 648 return *this; 649 } 650 651 size_t Length() const { return mTrackSizes.Length(); } 652 653 void SetLength(size_t aSize) { mTrackSizes.SetLength(aSize); } 654 655 bool IsEmpty() const { return mTrackSizes.IsEmpty(); } 656 657 void Assign(const TrackPlan& aRHS) { mTrackSizes.Assign(aRHS.mTrackSizes); } 658 659 nsGridContainerFrame::TrackSize* AppendElement( 660 nsGridContainerFrame::TrackSize aElement) { 661 return mTrackSizes.AppendElement(aElement); 662 } 663 664 nsGridContainerFrame::TrackSize& LastElement() { 665 return mTrackSizes.LastElement(); 666 } 667 668 nsGridContainerFrame::TrackSize& operator[](size_t aIndex) { 669 return mTrackSizes[aIndex]; 670 } 671 672 const nsGridContainerFrame::TrackSize& operator[](size_t aIndex) const { 673 return mTrackSizes[aIndex]; 674 } 675 676 void ClearAndRetainStorage() { mTrackSizes.ClearAndRetainStorage(); } 677 678 void ZeroInitialize() { 679 PodZero(mTrackSizes.Elements(), mTrackSizes.Length()); 680 } 681 682 using iterator = nsTArray<nsGridContainerFrame::TrackSize>::iterator; 683 iterator begin() { return mTrackSizes.begin(); } 684 iterator end() { return mTrackSizes.end(); } 685 686 using const_iterator = 687 nsTArray<nsGridContainerFrame::TrackSize>::const_iterator; 688 const_iterator begin() const { return mTrackSizes.begin(); } 689 const_iterator end() const { return mTrackSizes.end(); } 690 691 void Initialize(TrackSizingPhase aPhase, const Tracks& aTracks); 692 693 // Distribute space to all flex tracks this item spans. 694 // https://drafts.csswg.org/css-grid-2/#algo-spanning-flex-items 695 nscoord DistributeToFlexTrackSizes( 696 nscoord aAvailableSpace, const nsTArray<uint32_t>& aGrowableTracks, 697 const TrackSizingFunctions& aFunctions, 698 const nsGridContainerFrame::Tracks& aTracks); 699 700 private: 701 CopyableTArray<nsGridContainerFrame::TrackSize> mTrackSizes; 702 }; 703 704 using TrackPlan = nsGridContainerFrame::TrackPlan; 705 706 class nsGridContainerFrame::ItemPlan { 707 public: 708 ItemPlan() = default; 709 710 explicit ItemPlan(size_t aCapacity) : mTrackSizes(aCapacity) {} 711 712 ~ItemPlan() = default; 713 714 void SetLength(size_t aSize) { mTrackSizes.SetLength(aSize); } 715 716 nsGridContainerFrame::TrackSize& operator[](size_t aIndex) { 717 return mTrackSizes[aIndex]; 718 } 719 720 void Initialize(TrackSizingPhase aPhase, 721 const nsTArray<uint32_t>& aGrowableTracks, 722 const nsGridContainerFrame::Tracks& aTracks); 723 724 using FitContentClamper = 725 std::function<bool(uint32_t aTrack, nscoord aMinSize, nscoord* aSize)>; 726 727 /** 728 * Grow the planned size for tracks in aGrowableTracks up to their limit 729 * and then freeze them (all aGrowableTracks must be unfrozen on entry). 730 * Subtract the space added from aAvailableSpace and return that. 731 */ 732 nscoord GrowTracksToLimit(nscoord aAvailableSpace, 733 const nsTArray<uint32_t>& aGrowableTracks, 734 const FitContentClamper& aFitContentClamper); 735 736 /** 737 * Helper for GrowSelectedTracksUnlimited. For the set of tracks (S) that 738 * match aMinSizingSelector: if a track in S doesn't match aMaxSizingSelector 739 * then mark it with aSkipFlag. If all tracks in S were marked then unmark 740 * them. Return aNumGrowable minus the number of tracks marked. It is 741 * assumed that this plan has no aSkipFlag set for tracks in aGrowableTracks 742 * on entry to this method. 743 */ 744 uint32_t MarkExcludedTracks(uint32_t aNumGrowable, 745 const nsTArray<uint32_t>& aGrowableTracks, 746 TrackSize::StateBits aMinSizingSelector, 747 TrackSize::StateBits aMaxSizingSelector, 748 TrackSize::StateBits aSkipFlag); 749 750 /** 751 * Mark all tracks in aGrowableTracks with an eSkipGrowUnlimited bit if 752 * they *shouldn't* grow unlimited in §12.5.1.2.4 "Distribute space beyond 753 * growth limits" https://drafts.csswg.org/css-grid-2/#extra-space 754 * Return the number of tracks that are still growable. 755 */ 756 uint32_t MarkExcludedTracks(TrackSizingPhase aPhase, 757 const nsTArray<uint32_t>& aGrowableTracks, 758 SizingConstraint aConstraint); 759 760 /** 761 * Increase the planned size for tracks in aGrowableTracks that aren't 762 * marked with a eSkipGrowUnlimited flag beyond their limit. 763 * This implements the "Distribute space beyond growth limits" step in 764 * https://drafts.csswg.org/css-grid-2/#distribute-extra-space 765 */ 766 void GrowSelectedTracksUnlimited(nscoord aAvailableSpace, 767 const nsTArray<uint32_t>& aGrowableTracks, 768 uint32_t aNumGrowable, 769 const FitContentClamper& aFitContentClamper); 770 771 private: 772 nsTArray<nsGridContainerFrame::TrackSize> mTrackSizes; 773 }; 774 775 using ItemPlan = nsGridContainerFrame::ItemPlan; 776 777 /** 778 * A LineRange can be definite or auto - when it's definite it represents 779 * a consecutive set of tracks between a starting line and an ending line. 780 * Before it's definite it can also represent an auto position with a span, 781 * where mStart == kAutoLine and mEnd is the (non-zero positive) span. 782 * For normal-flow items, the invariant mStart < mEnd holds when both 783 * lines are definite. 784 * 785 * For abs.pos. grid items, mStart and mEnd may both be kAutoLine, meaning 786 * "attach this side to the grid container containing block edge". 787 * Additionally, mStart <= mEnd holds when both are definite (non-kAutoLine), 788 * i.e. the invariant is slightly relaxed compared to normal flow items. 789 */ 790 struct nsGridContainerFrame::LineRange { 791 LineRange(int32_t aStart, int32_t aEnd) 792 : mUntranslatedStart(aStart), mUntranslatedEnd(aEnd) { 793 #ifdef DEBUG 794 if (!IsAutoAuto()) { 795 if (IsAuto()) { 796 MOZ_ASSERT(aEnd >= kMinLine && aEnd <= kMaxLine, "invalid span"); 797 } else { 798 MOZ_ASSERT(aStart >= kMinLine && aStart <= kMaxLine, 799 "invalid start line"); 800 MOZ_ASSERT(aEnd == int32_t(kAutoLine) || 801 (aEnd >= kMinLine && aEnd <= kMaxLine), 802 "invalid end line"); 803 } 804 } 805 #endif 806 } 807 bool IsAutoAuto() const { return mStart == kAutoLine && mEnd == kAutoLine; } 808 bool IsAuto() const { return mStart == kAutoLine; } 809 bool IsDefinite() const { return mStart != kAutoLine; } 810 uint32_t Extent() const { 811 MOZ_ASSERT(mEnd != kAutoLine, "Extent is undefined for abs.pos. 'auto'"); 812 if (IsAuto()) { 813 MOZ_ASSERT(mEnd >= 1 && mEnd < uint32_t(kMaxLine), "invalid span"); 814 return mEnd; 815 } 816 return mEnd - mStart; 817 } 818 819 /** 820 * Return an object suitable for iterating this range. 821 */ 822 auto Range() const { return IntegerRange<uint32_t>(mStart, mEnd); } 823 824 /** 825 * Resolve this auto range to start at aStart, making it definite. 826 * @param aClampMaxLine the maximum allowed line number (zero-based) 827 * Precondition: this range IsAuto() 828 */ 829 void ResolveAutoPosition(uint32_t aStart, uint32_t aClampMaxLine) { 830 MOZ_ASSERT(IsAuto(), "Why call me?"); 831 mStart = aStart; 832 mEnd += aStart; 833 // Clamp to aClampMaxLine, which is where kMaxLine is in the explicit 834 // grid in a non-subgrid axis; this implements clamping per 835 // https://drafts.csswg.org/css-grid-2/#overlarge-grids 836 // In a subgrid axis it's the end of the grid in that axis. 837 if (MOZ_UNLIKELY(mStart >= aClampMaxLine)) { 838 mEnd = aClampMaxLine; 839 mStart = mEnd - 1; 840 } else if (MOZ_UNLIKELY(mEnd > aClampMaxLine)) { 841 mEnd = aClampMaxLine; 842 } 843 } 844 /** 845 * Translate the lines to account for (empty) removed tracks. This method 846 * is only for grid items and should only be called after placement. 847 * aNumRemovedTracks contains a count for each line in the grid how many 848 * tracks were removed between the start of the grid and that line. 849 */ 850 void AdjustForRemovedTracks(const nsTArray<uint32_t>& aNumRemovedTracks) { 851 MOZ_ASSERT(mStart != kAutoLine, "invalid resolved line for a grid item"); 852 MOZ_ASSERT(mEnd != kAutoLine, "invalid resolved line for a grid item"); 853 uint32_t numRemovedTracks = aNumRemovedTracks[mStart]; 854 MOZ_ASSERT(numRemovedTracks == aNumRemovedTracks[mEnd], 855 "tracks that a grid item spans can't be removed"); 856 mStart -= numRemovedTracks; 857 mEnd -= numRemovedTracks; 858 } 859 /** 860 * Translate the lines to account for (empty) removed tracks. This method 861 * is only for abs.pos. children and should only be called after placement. 862 * Same as for in-flow items, but we don't touch 'auto' lines here and we 863 * also need to adjust areas that span into the removed tracks. 864 */ 865 void AdjustAbsPosForRemovedTracks( 866 const nsTArray<uint32_t>& aNumRemovedTracks) { 867 if (mStart != kAutoLine) { 868 mStart -= aNumRemovedTracks[mStart]; 869 } 870 if (mEnd != kAutoLine) { 871 MOZ_ASSERT(mStart == kAutoLine || mEnd > mStart, "invalid line range"); 872 mEnd -= aNumRemovedTracks[mEnd]; 873 } 874 } 875 876 /** 877 * Return the contribution of this line range for step 2 in 878 * https://drafts.csswg.org/css-grid-2/#auto-placement-algo 879 */ 880 uint32_t HypotheticalEnd() const { return mEnd; } 881 882 /** 883 * Given an array of track sizes, return the starting position and length 884 * of the tracks in this line range. 885 */ 886 void ToPositionAndLength(const TrackPlan& aTrackPlan, nscoord* aPos, 887 nscoord* aLength) const; 888 889 /** 890 * Given an array of track sizes, return the length of the tracks in this 891 * line range. 892 */ 893 nscoord ToLength(const TrackPlan& aTrackPlan) const; 894 895 /** 896 * Given an array of track sizes and a grid origin coordinate, adjust the 897 * abs.pos. containing block along an axis given by aPos and aLength. 898 * aPos and aLength should already be initialized to the grid container 899 * containing block for this axis before calling this method. 900 */ 901 void ToPositionAndLengthForAbsPos(const Tracks& aTracks, nscoord aGridOrigin, 902 nscoord* aPos, nscoord* aLength) const; 903 904 void Translate(int32_t aOffset) { 905 MOZ_ASSERT(IsDefinite()); 906 mStart += aOffset; 907 mEnd += aOffset; 908 } 909 910 /** Swap the start/end sides of this range. */ 911 void ReverseDirection(uint32_t aGridEnd) { 912 MOZ_ASSERT(IsDefinite()); 913 MOZ_ASSERT(aGridEnd >= mEnd); 914 uint32_t newStart = aGridEnd - mEnd; 915 mEnd = aGridEnd - mStart; 916 mStart = newStart; 917 } 918 919 /** 920 * @note We'll use the signed member while resolving definite positions 921 * to line numbers (1-based), which may become negative for implicit lines 922 * to the top/left of the explicit grid. PlaceGridItems() then translates 923 * the whole grid to a 0,0 origin and we'll use the unsigned member from 924 * there on. 925 */ 926 union { 927 uint32_t mStart; 928 int32_t mUntranslatedStart; 929 }; 930 union { 931 uint32_t mEnd; 932 int32_t mUntranslatedEnd; 933 }; 934 935 protected: 936 LineRange() : mStart(0), mEnd(0) {} 937 }; 938 939 /** 940 * Helper class to construct a LineRange from translated lines. 941 * The ctor only accepts translated definite line numbers. 942 */ 943 struct nsGridContainerFrame::TranslatedLineRange : public LineRange { 944 TranslatedLineRange(uint32_t aStart, uint32_t aEnd) { 945 MOZ_ASSERT(aStart < aEnd && aEnd <= kTranslatedMaxLine); 946 mStart = aStart; 947 mEnd = aEnd; 948 } 949 }; 950 951 /** 952 * A GridArea is the area in the grid for a grid item. 953 * The area is represented by two LineRanges, both of which can be auto 954 * (@see LineRange) in intermediate steps while the item is being placed. 955 * @see PlaceGridItems 956 */ 957 struct nsGridContainerFrame::GridArea { 958 GridArea(const LineRange& aCols, const LineRange& aRows) 959 : mCols(aCols), mRows(aRows) {} 960 bool IsDefinite() const { return mCols.IsDefinite() && mRows.IsDefinite(); } 961 LineRange& LineRangeForAxis(LogicalAxis aAxis) { 962 return aAxis == LogicalAxis::Inline ? mCols : mRows; 963 } 964 const LineRange& LineRangeForAxis(LogicalAxis aAxis) const { 965 return aAxis == LogicalAxis::Inline ? mCols : mRows; 966 } 967 LineRange mCols; 968 LineRange mRows; 969 }; 970 971 struct nsGridContainerFrame::GridItemInfo { 972 /** 973 * Item state per axis. 974 */ 975 enum StateBits : uint16_t { 976 // Does the item span a flex track? 977 eIsFlexing = 0x1, 978 979 // First or last baseline alignment preference. They are mutually exclusive. 980 // This does *NOT* represent the baseline alignment group. See the member 981 // variable for that. 982 // <https://drafts.csswg.org/css-align-3/#baseline-alignment-preference> 983 eFirstBaseline = 0x2, 984 eLastBaseline = 0x4, 985 eIsBaselineAligned = eFirstBaseline | eLastBaseline, 986 987 // One of e[Self|Content]Baseline is set when eIsBaselineAligned is true 988 eSelfBaseline = 0x8, // is it *-self:[last ]baseline alignment? 989 // Ditto *-content:[last ]baseline. Mutually exclusive w. eSelfBaseline. 990 eContentBaseline = 0x10, 991 992 // The baseline affects the margin or padding on the item's end side when 993 // this bit is set. In a grid-axis it's always set for eLastBaseline and 994 // always unset for eFirstBaseline. In a masonry-axis, it's set for 995 // baseline groups in the EndStretch set and unset for the StartStretch set. 996 eEndSideBaseline = 0x20, 997 998 // Set when the grid item is in the last baseline sharing group, otherwise 999 // assume the first baseline sharing group. The baseline sharing group might 1000 // differ from the specified baseline alignment due to baseline alignment 1001 // rules. 1002 eLastBaselineSharingGroup = 0x40, 1003 1004 eAllBaselineBits = eIsBaselineAligned | eSelfBaseline | eContentBaseline | 1005 eEndSideBaseline | eLastBaselineSharingGroup, 1006 1007 // Automatic Minimum Size is content based. If not set, automatic minimum 1008 // size is zero. 1009 // https://drafts.csswg.org/css-grid-2/#min-size-auto 1010 // https://drafts.csswg.org/css-grid-2/#content-based-minimum-size 1011 eContentBasedAutoMinSize = 0x80, 1012 // Clamp per https://drafts.csswg.org/css-grid-2/#min-size-auto 1013 eClampMarginBoxMinSize = 0x100, 1014 eIsSubgrid = 0x200, 1015 // set on subgrids and items in subgrids if they are adjacent to the grid 1016 // start/end edge (excluding grid-aligned abs.pos. frames) 1017 eStartEdge = 0x400, 1018 eEndEdge = 0x800, 1019 eEdgeBits = eStartEdge | eEndEdge, 1020 // Set if this item was auto-placed in this axis. 1021 eAutoPlacement = 0x1000, 1022 // Set if this item is the last item in its track (masonry layout only) 1023 eIsLastItemInMasonryTrack = 0x2000, 1024 1025 // Bits set during the track sizing step. 1026 eTrackSizingBits = 1027 eIsFlexing | eContentBasedAutoMinSize | eClampMarginBoxMinSize, 1028 }; 1029 1030 GridItemInfo(nsIFrame* aFrame, const GridArea& aArea); 1031 1032 GridItemInfo(const GridItemInfo& aOther) 1033 : mFrame(aOther.mFrame), mArea(aOther.mArea) { 1034 mBaselineOffset = aOther.mBaselineOffset; 1035 mState = aOther.mState; 1036 } 1037 1038 GridItemInfo& operator=(const GridItemInfo&) = delete; 1039 1040 static bool BaselineAlignmentAffectsEndSide(StateBits state) { 1041 return state & StateBits::eEndSideBaseline; 1042 } 1043 1044 /** 1045 * Inhibit subgrid layout unless the item is placed in the first "track" in 1046 * a parent masonry-axis, or has definite placement or spans all tracks in 1047 * the parent grid-axis. 1048 * TODO: this is stricter than what the Masonry proposal currently states 1049 * (bug 1627581) 1050 */ 1051 void MaybeInhibitSubgridInMasonry(nsGridContainerFrame* aParent, 1052 uint32_t aGridAxisTrackCount); 1053 1054 /** 1055 * Inhibit subgridding in aAxis for this item. 1056 */ 1057 void InhibitSubgrid(nsGridContainerFrame* aParent, LogicalAxis aAxis); 1058 1059 /** 1060 * Return a copy of this item with its row/column data swapped. 1061 */ 1062 GridItemInfo Transpose() const { 1063 GridItemInfo info(mFrame, GridArea(mArea.mRows, mArea.mCols)); 1064 info.mState[LogicalAxis::Block] = mState[LogicalAxis::Inline]; 1065 info.mState[LogicalAxis::Inline] = mState[LogicalAxis::Block]; 1066 info.mBaselineOffset[LogicalAxis::Block] = 1067 mBaselineOffset[LogicalAxis::Inline]; 1068 info.mBaselineOffset[LogicalAxis::Inline] = 1069 mBaselineOffset[LogicalAxis::Block]; 1070 return info; 1071 } 1072 1073 // Reset bits in mState in aAxis that were set during the track sizing step. 1074 void ResetTrackSizingBits(LogicalAxis aAxis); 1075 1076 /** Swap the start/end sides in aAxis. */ 1077 inline void ReverseDirection(LogicalAxis aAxis, uint32_t aGridEnd); 1078 1079 // Is this item a subgrid in the given container axis? 1080 bool IsSubgrid(LogicalAxis aAxis) const { 1081 return mState[aAxis] & StateBits::eIsSubgrid; 1082 } 1083 1084 // Is this item a subgrid in either axis? 1085 bool IsSubgrid() const { 1086 return IsSubgrid(LogicalAxis::Inline) || IsSubgrid(LogicalAxis::Block); 1087 } 1088 1089 // Return the (inner) grid container frame associated with this subgrid item. 1090 nsGridContainerFrame* SubgridFrame() const { 1091 MOZ_ASSERT(IsSubgrid()); 1092 nsGridContainerFrame* gridFrame = GetGridContainerFrame(mFrame); 1093 MOZ_ASSERT(gridFrame && gridFrame->IsSubgrid()); 1094 return gridFrame; 1095 } 1096 1097 /** 1098 * Adjust our grid areas to account for removed auto-fit tracks in aAxis. 1099 */ 1100 void AdjustForRemovedTracks(LogicalAxis aAxis, 1101 const nsTArray<uint32_t>& aNumRemovedTracks); 1102 1103 /** 1104 * If the item is [align|justify]-self:[last ]baseline aligned in the given 1105 * axis then set aBaselineOffset to the baseline offset and return aAlign. 1106 * Otherwise, return a fallback alignment. 1107 */ 1108 StyleAlignFlags GetSelfBaseline(StyleAlignFlags aAlign, LogicalAxis aAxis, 1109 nscoord* aBaselineOffset) const { 1110 MOZ_ASSERT(aAlign == StyleAlignFlags::BASELINE || 1111 aAlign == StyleAlignFlags::LAST_BASELINE); 1112 if (!(mState[aAxis] & eSelfBaseline)) { 1113 return aAlign == StyleAlignFlags::BASELINE ? StyleAlignFlags::SELF_START 1114 : StyleAlignFlags::SELF_END; 1115 } 1116 *aBaselineOffset = mBaselineOffset[aAxis]; 1117 return aAlign; 1118 } 1119 1120 // Return true if we should use MinContribution on items that do not span 1121 // any flex tracks to determine the minimum contribution, and if we should 1122 // set the eContentBasedAutoMinSize flag on grid items. 1123 // 1124 // In part this is determined by whether or not the minimum contribution 1125 // of the item is content-based. 1126 // https://drafts.csswg.org/css-grid-2/#min-size-auto 1127 // 1128 // @note the caller should also check that the item has a span length of 1, 1129 // and that the item's track has a min track sizing function that is 'auto'. 1130 bool MinContributionDependsOnAutoMinSize(WritingMode aContainerWM, 1131 LogicalAxis aContainerAxis) const { 1132 MOZ_ASSERT( 1133 mArea.LineRangeForAxis(aContainerAxis).Extent() == 1, 1134 "Should not be called with grid items that span multiple tracks."); 1135 const LogicalAxis itemAxis = 1136 aContainerWM.ConvertAxisTo(aContainerAxis, mFrame->GetWritingMode()); 1137 const auto* styleFrame = mFrame->IsTableWrapperFrame() 1138 ? mFrame->PrincipalChildList().FirstChild() 1139 : mFrame; 1140 const auto* pos = styleFrame->StylePosition(); 1141 const auto anchorResolutionParams = 1142 AnchorPosResolutionParams::From(styleFrame); 1143 const auto size = 1144 pos->Size(aContainerAxis, aContainerWM, anchorResolutionParams); 1145 // max-content and min-content should behave as initial value in block axis. 1146 // FIXME: Bug 567039: moz-fit-content and -moz-available are not supported 1147 // for block size dimension on sizing properties (e.g. height), so we 1148 // treat it as `auto`. 1149 bool isAuto = size->BehavesLikeInitialValue(itemAxis); 1150 // This check for HasPercent is intended to correspond to whether or not 1151 // the item's preferred size depends on the size of its containing block. 1152 // 1153 // TODO alaskanemily: This probably shouldn't be a special case here. 1154 // This is part of how EnsureContributions with the MinSize flag is 1155 // implemented, where this forces ResolveIntrinsicSizeForNonSpanningItems 1156 // to use MinSize instead of Min/MaxContentContribution, which 1157 // EnsureContributions will then translate to/from MinContentContribution 1158 // 1159 // https://drafts.csswg.org/css-grid-2/#algo-single-span-items 1160 // Section "For auto minimums" 1161 if (!isAuto && !size->HasPercent()) { 1162 return false; 1163 } 1164 const auto minSize = 1165 pos->MinSize(aContainerAxis, aContainerWM, anchorResolutionParams); 1166 // max-content and min-content should behave as initial value in block axis. 1167 // FIXME: Bug 567039: moz-fit-content and -moz-available are not supported 1168 // for block size dimension on sizing properties (e.g. height), so we 1169 // treat it as `auto`. 1170 isAuto = minSize->BehavesLikeInitialValue(itemAxis); 1171 return isAuto && !mFrame->StyleDisplay()->IsScrollableOverflow(); 1172 } 1173 1174 #ifdef DEBUG 1175 void Dump() const; 1176 #endif 1177 1178 static bool IsStartRowLessThan(const GridItemInfo* a, const GridItemInfo* b) { 1179 return a->mArea.mRows.mStart < b->mArea.mRows.mStart; 1180 } 1181 1182 // Sorting functions for 'masonry-auto-flow:next'. We sort the items that 1183 // were placed into the first track by the Grid placement algorithm first 1184 // (to honor that placement). All other items will be placed by the Masonry 1185 // layout algorithm (their Grid placement in the masonry axis is irrelevant). 1186 static bool RowMasonryOrdered(const GridItemInfo* a, const GridItemInfo* b) { 1187 return a->mArea.mRows.mStart == 0 && b->mArea.mRows.mStart != 0 && 1188 !a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW); 1189 } 1190 static bool ColMasonryOrdered(const GridItemInfo* a, const GridItemInfo* b) { 1191 return a->mArea.mCols.mStart == 0 && b->mArea.mCols.mStart != 0 && 1192 !a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW); 1193 } 1194 1195 // Sorting functions for 'masonry-auto-flow:definite-first'. Similar to 1196 // the above, but here we also sort items with a definite item placement in 1197 // the grid axis in track order before 'auto'-placed items. We also sort all 1198 // continuations first since they use the same placement as their 1199 // first-in-flow (we treat them as "definite" regardless of eAutoPlacement). 1200 static bool RowMasonryDefiniteFirst(const GridItemInfo* a, 1201 const GridItemInfo* b) { 1202 bool isContinuationA = a->mFrame->GetPrevInFlow(); 1203 bool isContinuationB = b->mFrame->GetPrevInFlow(); 1204 if (isContinuationA != isContinuationB) { 1205 return isContinuationA; 1206 } 1207 auto masonryA = a->mArea.mRows.mStart; 1208 auto gridA = a->mState[LogicalAxis::Inline] & StateBits::eAutoPlacement; 1209 auto masonryB = b->mArea.mRows.mStart; 1210 auto gridB = b->mState[LogicalAxis::Inline] & StateBits::eAutoPlacement; 1211 return (masonryA == 0 ? masonryB != 0 : (masonryB != 0 && gridA < gridB)) && 1212 !a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW); 1213 } 1214 static bool ColMasonryDefiniteFirst(const GridItemInfo* a, 1215 const GridItemInfo* b) { 1216 MOZ_ASSERT(!a->mFrame->GetPrevInFlow() && !b->mFrame->GetPrevInFlow(), 1217 "fragmentation not supported in inline axis"); 1218 auto masonryA = a->mArea.mCols.mStart; 1219 auto gridA = a->mState[LogicalAxis::Block] & StateBits::eAutoPlacement; 1220 auto masonryB = b->mArea.mCols.mStart; 1221 auto gridB = b->mState[LogicalAxis::Block] & StateBits::eAutoPlacement; 1222 return (masonryA == 0 ? masonryB != 0 : (masonryB != 0 && gridA < gridB)) && 1223 !a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW); 1224 } 1225 1226 // Return true if this items block size is dependent on the size of the 1227 // container it is in. 1228 bool IsBSizeDependentOnContainerSize(WritingMode aContainerWM) const { 1229 const auto IsDependentOnContainerSize = [](const auto& size) -> bool { 1230 // XXXdholbert The BehavesLikeStretchOnInlineAxis usage seems like 1231 // maybe it should be considering block-axis instead? 1232 return size.HasPercent() || size.BehavesLikeStretchOnInlineAxis(); 1233 }; 1234 1235 const nsStylePosition* stylePos = mFrame->StylePosition(); 1236 const auto anchorResolutionParams = AnchorPosResolutionParams::From(mFrame); 1237 bool isItemAutoSize = IsDependentOnContainerSize(*stylePos->BSize( 1238 aContainerWM, anchorResolutionParams)) || 1239 IsDependentOnContainerSize(*stylePos->MinBSize( 1240 aContainerWM, anchorResolutionParams)) || 1241 IsDependentOnContainerSize(*stylePos->MaxBSize( 1242 aContainerWM, anchorResolutionParams)); 1243 1244 return isItemAutoSize; 1245 } 1246 1247 nsIFrame* const mFrame; 1248 GridArea mArea; 1249 1250 // Offset from the margin edge to the baseline (LogicalAxis index). It's from 1251 // the start edge for first baseline sharing group, otherwise from the end 1252 // edge. 1253 // It's mutable since we update the value fairly late (just before reflowing 1254 // the item). 1255 mutable PerLogicalAxis<nscoord> mBaselineOffset; 1256 1257 // State bits per axis. 1258 mutable PerLogicalAxis<StateBits> mState; 1259 }; 1260 1261 using GridItemInfo = nsGridContainerFrame::GridItemInfo; 1262 using ItemState = GridItemInfo::StateBits; 1263 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ItemState) 1264 1265 GridItemInfo::GridItemInfo(nsIFrame* aFrame, const GridArea& aArea) 1266 : mFrame(aFrame), mArea(aArea), mBaselineOffset{0, 0} { 1267 mState[LogicalAxis::Block] = 1268 StateBits(mArea.mRows.mStart == kAutoLine ? eAutoPlacement : 0); 1269 mState[LogicalAxis::Inline] = 1270 StateBits(mArea.mCols.mStart == kAutoLine ? eAutoPlacement : 0); 1271 1272 if (auto* gridFrame = GetGridContainerFrame(mFrame)) { 1273 auto parentWM = aFrame->GetParent()->GetWritingMode(); 1274 bool isOrthogonal = parentWM.IsOrthogonalTo(gridFrame->GetWritingMode()); 1275 if (gridFrame->IsColSubgrid()) { 1276 mState[isOrthogonal ? LogicalAxis::Block : LogicalAxis::Inline] |= 1277 StateBits::eIsSubgrid; 1278 } 1279 if (gridFrame->IsRowSubgrid()) { 1280 mState[isOrthogonal ? LogicalAxis::Inline : LogicalAxis::Block] |= 1281 StateBits::eIsSubgrid; 1282 } 1283 } 1284 } 1285 1286 void GridItemInfo::ResetTrackSizingBits(LogicalAxis aAxis) { 1287 mState[aAxis] &= ~StateBits::eTrackSizingBits; 1288 } 1289 1290 void GridItemInfo::ReverseDirection(LogicalAxis aAxis, uint32_t aGridEnd) { 1291 mArea.LineRangeForAxis(aAxis).ReverseDirection(aGridEnd); 1292 ItemState& state = mState[aAxis]; 1293 ItemState newState = state & ~ItemState::eEdgeBits; 1294 if (state & ItemState::eStartEdge) { 1295 newState |= ItemState::eEndEdge; 1296 } 1297 if (state & ItemState::eEndEdge) { 1298 newState |= ItemState::eStartEdge; 1299 } 1300 state = newState; 1301 } 1302 1303 void GridItemInfo::InhibitSubgrid(nsGridContainerFrame* aParent, 1304 LogicalAxis aAxis) { 1305 MOZ_ASSERT(IsSubgrid(aAxis)); 1306 auto bit = NS_STATE_GRID_IS_COL_SUBGRID; 1307 if (aParent->GetWritingMode().IsOrthogonalTo(mFrame->GetWritingMode()) != 1308 (aAxis == LogicalAxis::Block)) { 1309 bit = NS_STATE_GRID_IS_ROW_SUBGRID; 1310 } 1311 MOZ_ASSERT(SubgridFrame()->HasAnyStateBits(bit)); 1312 SubgridFrame()->RemoveStateBits(bit); 1313 mState[aAxis] &= StateBits(~StateBits::eIsSubgrid); 1314 } 1315 1316 void GridItemInfo::MaybeInhibitSubgridInMasonry(nsGridContainerFrame* aParent, 1317 uint32_t aGridAxisTrackCount) { 1318 if (IsSubgrid(LogicalAxis::Inline) && aParent->IsRowMasonry() && 1319 mArea.mRows.mStart != 0 && mArea.mCols.Extent() != aGridAxisTrackCount && 1320 (mState[LogicalAxis::Inline] & eAutoPlacement)) { 1321 InhibitSubgrid(aParent, LogicalAxis::Inline); 1322 return; 1323 } 1324 if (IsSubgrid(LogicalAxis::Block) && aParent->IsColMasonry() && 1325 mArea.mCols.mStart != 0 && mArea.mRows.Extent() != aGridAxisTrackCount && 1326 (mState[LogicalAxis::Block] & eAutoPlacement)) { 1327 InhibitSubgrid(aParent, LogicalAxis::Block); 1328 } 1329 } 1330 1331 // Each subgrid stores this data about its items etc on a frame property. 1332 struct nsGridContainerFrame::Subgrid { 1333 Subgrid(const GridArea& aArea, bool aIsOrthogonal, WritingMode aCBWM) 1334 : mArea(aArea), 1335 mGridColEnd(0), 1336 mGridRowEnd(0), 1337 mMarginBorderPadding(aCBWM), 1338 mIsOrthogonal(aIsOrthogonal) {} 1339 1340 // Return the relevant line range for the subgrid column axis. 1341 const LineRange& SubgridCols() const { 1342 return mIsOrthogonal ? mArea.mRows : mArea.mCols; 1343 } 1344 // Return the relevant line range for the subgrid row axis. 1345 const LineRange& SubgridRows() const { 1346 return mIsOrthogonal ? mArea.mCols : mArea.mRows; 1347 } 1348 1349 // The subgrid's items. 1350 nsTArray<GridItemInfo> mGridItems; 1351 // The subgrid's abs.pos. items. 1352 nsTArray<GridItemInfo> mAbsPosItems; 1353 // The subgrid's area as a grid item, i.e. in its parent's grid space. 1354 GridArea mArea; 1355 // The (inner) grid size for the subgrid, zero-based. 1356 uint32_t mGridColEnd; 1357 uint32_t mGridRowEnd; 1358 // The margin+border+padding for the subgrid box in its parent grid's WM. 1359 // (This also includes the size of any scrollbars.) 1360 LogicalMargin mMarginBorderPadding; 1361 // Does the subgrid frame have orthogonal writing-mode to its parent grid 1362 // container? 1363 bool mIsOrthogonal; 1364 1365 NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, Subgrid) 1366 }; 1367 using Subgrid = nsGridContainerFrame::Subgrid; 1368 1369 void GridItemInfo::AdjustForRemovedTracks( 1370 LogicalAxis aAxis, const nsTArray<uint32_t>& aNumRemovedTracks) { 1371 const bool abspos = mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW); 1372 auto& lines = mArea.LineRangeForAxis(aAxis); 1373 if (abspos) { 1374 lines.AdjustAbsPosForRemovedTracks(aNumRemovedTracks); 1375 } else { 1376 lines.AdjustForRemovedTracks(aNumRemovedTracks); 1377 } 1378 if (IsSubgrid()) { 1379 auto* subgrid = SubgridFrame()->GetProperty(Subgrid::Prop()); 1380 if (subgrid) { 1381 auto& lines = subgrid->mArea.LineRangeForAxis(aAxis); 1382 if (abspos) { 1383 lines.AdjustAbsPosForRemovedTracks(aNumRemovedTracks); 1384 } else { 1385 lines.AdjustForRemovedTracks(aNumRemovedTracks); 1386 } 1387 } 1388 } 1389 } 1390 1391 /** 1392 * Track size data for use by subgrids (which don't do sizing of their own 1393 * in a subgridded axis). A non-subgrid container stores its resolved sizes, 1394 * but only if it has any subgrid children. A subgrid always stores one. 1395 * In a subgridded axis, we copy the parent's sizes (see CopyUsedTrackSizes). 1396 * 1397 * This struct us stored on a frame property, which may be null before the track 1398 * sizing step for the given container. A null property is semantically 1399 * equivalent to mCanResolveLineRangeSize being false in both axes. 1400 * @note the axis used to access this data is in the grid container's own 1401 * writing-mode, same as in other track-sizing functions. 1402 */ 1403 struct nsGridContainerFrame::UsedTrackSizes { 1404 UsedTrackSizes() : mCanResolveLineRangeSize{false, false} {} 1405 1406 /** 1407 * Setup mTrackPlans by copying track sizes from aFrame's grid container 1408 * parent when aAxis is subgridded (and recurse if the parent is a subgrid 1409 * that doesn't have sizes yet), or by running the Track Sizing Algo when 1410 * the axis is not subgridded (for a subgrid). 1411 * Set mCanResolveLineRangeSize[aAxis] to true once we have obtained 1412 * sizes for an axis (if it's already true then this method is a NOP). 1413 */ 1414 void ResolveTrackSizesForAxis(nsGridContainerFrame* aFrame, LogicalAxis aAxis, 1415 gfxContext& aRC); 1416 1417 /** Helper function for the above method */ 1418 void ResolveSubgridTrackSizesForAxis(nsGridContainerFrame* aFrame, 1419 LogicalAxis aAxis, Subgrid* aSubgrid, 1420 gfxContext& aRC, 1421 nscoord aContentBoxSize); 1422 1423 // This only has valid sizes when mCanResolveLineRangeSize is true in 1424 // the same axis. It may have zero tracks (a grid with only abs.pos. 1425 // subgrids/items may have zero tracks). 1426 PerLogicalAxis<TrackPlan> mTrackPlans; 1427 // True if mTrackPlans can be used to resolve line range sizes in an axis. 1428 PerLogicalAxis<bool> mCanResolveLineRangeSize; 1429 1430 NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, UsedTrackSizes) 1431 }; 1432 using UsedTrackSizes = nsGridContainerFrame::UsedTrackSizes; 1433 1434 #ifdef DEBUG 1435 void nsGridContainerFrame::GridItemInfo::Dump() const { 1436 auto Dump1 = [this](const char* aMsg, LogicalAxis aAxis) { 1437 auto state = mState[aAxis]; 1438 if (!state) { 1439 return; 1440 } 1441 printf("%s", aMsg); 1442 if (state & ItemState::eEdgeBits) { 1443 printf("subgrid-adjacent-edges("); 1444 if (state & ItemState::eStartEdge) { 1445 printf("start "); 1446 } 1447 if (state & ItemState::eEndEdge) { 1448 printf("end"); 1449 } 1450 printf(") "); 1451 } 1452 if (state & ItemState::eAutoPlacement) { 1453 printf("masonry-auto "); 1454 } 1455 if (state & ItemState::eIsSubgrid) { 1456 printf("subgrid "); 1457 } 1458 if (state & ItemState::eIsFlexing) { 1459 printf("flexing "); 1460 } 1461 if (state & ItemState::eContentBasedAutoMinSize) { 1462 printf("auto-min-size "); 1463 } 1464 if (state & ItemState::eClampMarginBoxMinSize) { 1465 printf("clamp "); 1466 } 1467 if (state & ItemState::eIsLastItemInMasonryTrack) { 1468 printf("last-in-track "); 1469 } 1470 if (state & ItemState::eFirstBaseline) { 1471 printf("first baseline %s-alignment ", 1472 (state & ItemState::eSelfBaseline) ? "self" : "content"); 1473 } 1474 if (state & ItemState::eLastBaseline) { 1475 printf("last baseline %s-alignment ", 1476 (state & ItemState::eSelfBaseline) ? "self" : "content"); 1477 } 1478 if (state & ItemState::eIsBaselineAligned) { 1479 printf("%.2fpx", NSAppUnitsToFloatPixels(mBaselineOffset[aAxis], 1480 AppUnitsPerCSSPixel())); 1481 } 1482 printf("\n"); 1483 }; 1484 printf("grid-row: %d %d\n", mArea.mRows.mStart, mArea.mRows.mEnd); 1485 Dump1(" grid block-axis: ", LogicalAxis::Block); 1486 printf("grid-column: %d %d\n", mArea.mCols.mStart, mArea.mCols.mEnd); 1487 Dump1(" grid inline-axis: ", LogicalAxis::Inline); 1488 } 1489 #endif 1490 1491 /** 1492 * Encapsulates CSS track-sizing functions. 1493 */ 1494 struct nsGridContainerFrame::TrackSizingFunctions { 1495 private: 1496 TrackSizingFunctions(const GridTemplate& aTemplate, 1497 const StyleImplicitGridTracks& aAutoSizing, 1498 const Maybe<size_t>& aRepeatAutoIndex, bool aIsSubgrid) 1499 : mTemplate(aTemplate), 1500 mTrackListValues(aTemplate.TrackListValues()), 1501 mAutoSizing(aAutoSizing), 1502 mExplicitGridOffset(0), 1503 mRepeatAutoStart(aRepeatAutoIndex.valueOr(0)), 1504 mRepeatAutoEnd(mRepeatAutoStart), 1505 mHasRepeatAuto(aRepeatAutoIndex.isSome()) { 1506 MOZ_ASSERT(!mHasRepeatAuto || !aIsSubgrid, 1507 "a track-list for a subgrid can't have an <auto-repeat> track"); 1508 if (!aIsSubgrid) { 1509 ExpandNonRepeatAutoTracks(); 1510 } 1511 1512 #ifdef DEBUG 1513 if (mHasRepeatAuto) { 1514 MOZ_ASSERT(mExpandedTracks.Length() >= 1); 1515 const unsigned maxTrack = kMaxLine - 1; 1516 // If the exanded tracks are out of range of the maximum track, we 1517 // can't compare the repeat-auto start. It will be removed later during 1518 // grid item placement in that situation. 1519 if (mExpandedTracks.Length() < maxTrack) { 1520 MOZ_ASSERT(mRepeatAutoStart < mExpandedTracks.Length()); 1521 } 1522 } 1523 #endif 1524 } 1525 1526 public: 1527 TrackSizingFunctions(const GridTemplate& aGridTemplate, 1528 const StyleImplicitGridTracks& aAutoSizing, 1529 bool aIsSubgrid) 1530 : TrackSizingFunctions(aGridTemplate, aAutoSizing, 1531 aGridTemplate.RepeatAutoIndex(), aIsSubgrid) {} 1532 1533 private: 1534 enum { ForSubgridFallbackTag }; 1535 TrackSizingFunctions(const GridTemplate& aGridTemplate, 1536 const StyleImplicitGridTracks& aAutoSizing, 1537 decltype(ForSubgridFallbackTag)) 1538 : TrackSizingFunctions(aGridTemplate, aAutoSizing, Nothing(), 1539 /* aIsSubgrid */ true) {} 1540 1541 public: 1542 /** 1543 * This is used in a subgridded axis to resolve sizes before its parent's 1544 * sizes are known for intrinsic sizing purposes. It copies the slice of 1545 * the nearest non-subgridded axis' track sizing functions spanned by 1546 * the subgrid. 1547 * 1548 * FIXME: this was written before there was a spec... the spec now says: 1549 * "If calculating the layout of a grid item in this step depends on 1550 * the available space in the block axis, assume the available space 1551 * that it would have if any row with a definite max track sizing 1552 * function had that size and all other rows were infinite." 1553 * https://drafts.csswg.org/css-grid-2/#subgrid-sizing 1554 */ 1555 static TrackSizingFunctions ForSubgridFallback( 1556 nsGridContainerFrame* aSubgridFrame, const Subgrid* aSubgrid, 1557 nsGridContainerFrame* aParentGridContainer, LogicalAxis aParentAxis) { 1558 MOZ_ASSERT(aSubgrid); 1559 MOZ_ASSERT(aSubgridFrame->IsSubgrid(aSubgrid->mIsOrthogonal 1560 ? GetOrthogonalAxis(aParentAxis) 1561 : aParentAxis)); 1562 nsGridContainerFrame* parent = aParentGridContainer; 1563 auto parentAxis = aParentAxis; 1564 LineRange range = aSubgrid->mArea.LineRangeForAxis(parentAxis); 1565 // Find our nearest non-subgridded axis and use its track sizing functions. 1566 while (parent->IsSubgrid(parentAxis)) { 1567 const auto* parentSubgrid = parent->GetProperty(Subgrid::Prop()); 1568 auto* grandParent = parent->ParentGridContainerForSubgrid(); 1569 auto grandParentWM = grandParent->GetWritingMode(); 1570 bool isSameDirInAxis = 1571 parent->GetWritingMode().ParallelAxisStartsOnSameSide(parentAxis, 1572 grandParentWM); 1573 if (MOZ_UNLIKELY(!isSameDirInAxis)) { 1574 auto end = parentAxis == LogicalAxis::Block 1575 ? parentSubgrid->mGridRowEnd 1576 : parentSubgrid->mGridColEnd; 1577 range.ReverseDirection(end); 1578 // range is now in the same direction as the grand-parent's axis 1579 } 1580 auto grandParentAxis = parentSubgrid->mIsOrthogonal 1581 ? GetOrthogonalAxis(parentAxis) 1582 : parentAxis; 1583 const auto& parentRange = 1584 parentSubgrid->mArea.LineRangeForAxis(grandParentAxis); 1585 range.Translate(parentRange.mStart); 1586 // range is now in the grand-parent's coordinates 1587 parentAxis = grandParentAxis; 1588 parent = grandParent; 1589 } 1590 const auto* pos = parent->StylePosition(); 1591 const auto isInlineAxis = parentAxis == LogicalAxis::Inline; 1592 const auto& szf = 1593 isInlineAxis ? pos->mGridTemplateRows : pos->mGridTemplateColumns; 1594 const auto& autoSizing = 1595 isInlineAxis ? pos->mGridAutoColumns : pos->mGridAutoRows; 1596 return TrackSizingFunctions(szf, autoSizing, ForSubgridFallbackTag); 1597 } 1598 1599 /** 1600 * Initialize the number of auto-fill/fit tracks to use. 1601 * This can be zero if no auto-fill/fit track was specified, or if the repeat 1602 * begins after the maximum allowed track. 1603 */ 1604 void InitRepeatTracks(const NonNegativeLengthPercentageOrNormal& aGridGap, 1605 nscoord aMinSize, nscoord aSize, nscoord aMaxSize) { 1606 const uint32_t maxTrack = kMaxLine - 1; 1607 // Check for a repeat after the maximum allowed track. 1608 if (MOZ_UNLIKELY(mRepeatAutoStart >= maxTrack)) { 1609 mHasRepeatAuto = false; 1610 mRepeatAutoStart = 0; 1611 mRepeatAutoEnd = 0; 1612 return; 1613 } 1614 uint32_t repeatTracks = 1615 CalculateRepeatFillCount(aGridGap, aMinSize, aSize, aMaxSize) * 1616 NumRepeatTracks(); 1617 // Clamp the number of repeat tracks to the maximum possible track. 1618 repeatTracks = std::min(repeatTracks, maxTrack - mRepeatAutoStart); 1619 SetNumRepeatTracks(repeatTracks); 1620 // Blank out the removed flags for each of these tracks. 1621 mRemovedRepeatTracks.SetLength(repeatTracks); 1622 for (auto& track : mRemovedRepeatTracks) { 1623 track = false; 1624 } 1625 } 1626 1627 uint32_t CalculateRepeatFillCount( 1628 const NonNegativeLengthPercentageOrNormal& aGridGap, nscoord aMinSize, 1629 nscoord aSize, nscoord aMaxSize) const { 1630 if (!mHasRepeatAuto) { 1631 return 0; 1632 } 1633 // At this point no tracks will have been collapsed, so the RepeatEndDelta 1634 // should not be negative. 1635 MOZ_ASSERT(RepeatEndDelta() >= 0); 1636 // Note that this uses NumRepeatTracks and mRepeatAutoStart/End, although 1637 // the result of this method is used to change those values to a fully 1638 // expanded value. Spec quotes are from 1639 // https://drafts.csswg.org/css-grid-2/#repeat-notation 1640 const uint32_t numTracks = mExpandedTracks.Length() + RepeatEndDelta(); 1641 MOZ_ASSERT(numTracks >= 1, "expected at least the repeat() track"); 1642 if (MOZ_UNLIKELY(numTracks >= kMaxLine)) { 1643 // The fixed tracks plus an entire repetition is either larger or as 1644 // large as the maximum track, so we do not need to measure how many 1645 // repetitions will fit. This also avoids needing to check for if 1646 // kMaxLine - numTracks would underflow at the end where we clamp the 1647 // result. 1648 return 1; 1649 } 1650 nscoord maxFill = aSize != NS_UNCONSTRAINEDSIZE ? aSize : aMaxSize; 1651 if (maxFill == NS_UNCONSTRAINEDSIZE && aMinSize == 0) { 1652 // "Otherwise, the specified track list repeats only once." 1653 return 1; 1654 } 1655 nscoord repeatTrackSum = 0; 1656 // Note that one repeat() track size is included in |sum| in this loop. 1657 nscoord sum = 0; 1658 const nscoord percentBasis = aSize; 1659 for (uint32_t i = 0; i < numTracks; ++i) { 1660 // "treating each track as its max track sizing function if that is 1661 // definite or as its minimum track sizing function otherwise" 1662 // https://drafts.csswg.org/css-grid-2/#valdef-repeat-auto-fill 1663 nscoord trackSize; 1664 { 1665 const auto& sizingFunction = SizingFor(i); 1666 const auto& maxCoord = sizingFunction.GetMax(); 1667 const auto& minCoord = sizingFunction.GetMin(); 1668 if (maxCoord.IsBreadth() && minCoord.IsBreadth()) { 1669 // If the max is less than the min, then the max will be floored by 1670 // the min (essentially yielding minmax(min, min)) 1671 // https://drafts.csswg.org/css-grid-2/#funcdef-grid-template-columns-minmax 1672 const nscoord minSize = 1673 ::ResolveToDefiniteSize(minCoord, percentBasis); 1674 const nscoord maxSize = 1675 ::ResolveToDefiniteSize(maxCoord, percentBasis); 1676 trackSize = std::max(maxSize, minSize); 1677 } else { 1678 const auto* coord = &maxCoord; 1679 if (!coord->IsBreadth()) { 1680 coord = &minCoord; 1681 if (!coord->IsBreadth()) { 1682 return 1; 1683 } 1684 } 1685 trackSize = ::ResolveToDefiniteSize(*coord, percentBasis); 1686 } 1687 } 1688 1689 if (i >= mRepeatAutoStart && i < mRepeatAutoEnd) { 1690 // Use a minimum 1px for the repeat() track-size. 1691 if (trackSize < AppUnitsPerCSSPixel()) { 1692 trackSize = AppUnitsPerCSSPixel(); 1693 } 1694 repeatTrackSum += trackSize; 1695 } 1696 sum += trackSize; 1697 } 1698 nscoord gridGap = nsLayoutUtils::ResolveGapToLength(aGridGap, aSize); 1699 if (numTracks > 1) { 1700 // Add grid-gaps for all the tracks including the repeat() track. 1701 sum += gridGap * (numTracks - 1); 1702 } 1703 // Calculate the max number of tracks that fits without overflow. 1704 nscoord available = maxFill != NS_UNCONSTRAINEDSIZE ? maxFill : aMinSize; 1705 nscoord spaceToFill = available - sum; 1706 if (spaceToFill <= 0) { 1707 // "if any number of repetitions would overflow, then 1 repetition" 1708 return 1; 1709 } 1710 // Calculate the max number of tracks that fits without overflow. 1711 // Since we already have one repetition in sum, we can simply add one grid 1712 // gap for each element in the repeat. 1713 div_t q = div(spaceToFill, repeatTrackSum + gridGap * NumRepeatTracks()); 1714 // The +1 here is for the one repeat track we already accounted for above. 1715 uint32_t numRepeatTracks = q.quot + 1; 1716 if (q.rem != 0 && maxFill == NS_UNCONSTRAINEDSIZE) { 1717 // "Otherwise, if the grid container has a definite min size in 1718 // the relevant axis, the number of repetitions is the largest possible 1719 // positive integer that fulfills that minimum requirement." 1720 ++numRepeatTracks; // one more to ensure the grid is at least min-size 1721 } 1722 // Clamp the number of repeat tracks so that the last line <= kMaxLine. 1723 // (note that |numTracks| already includes one repeat() track) 1724 MOZ_ASSERT(numTracks >= NumRepeatTracks()); 1725 const uint32_t maxRepeatTrackCount = kMaxLine - numTracks; 1726 const uint32_t maxRepetitions = maxRepeatTrackCount / NumRepeatTracks(); 1727 return std::min(numRepeatTracks, maxRepetitions); 1728 } 1729 1730 /** 1731 * Compute the explicit grid end line number (in a zero-based grid). 1732 * @param aGridTemplateAreasEnd 'grid-template-areas' end line in this axis 1733 */ 1734 uint32_t ComputeExplicitGridEnd(uint32_t aGridTemplateAreasEnd) { 1735 uint32_t end = NumExplicitTracks() + 1; 1736 end = std::max(end, aGridTemplateAreasEnd); 1737 end = std::min(end, uint32_t(kMaxLine)); 1738 return end; 1739 } 1740 const StyleTrackSize& SizingFor(uint32_t aTrackIndex) const { 1741 static const StyleTrackSize kAutoTrackSize = 1742 StyleTrackSize::Breadth(StyleTrackBreadth::Auto()); 1743 // |aIndex| is the relative index to mAutoSizing. A negative value means it 1744 // is the last Nth element. 1745 auto getImplicitSize = [this](int32_t aIndex) -> const StyleTrackSize& { 1746 MOZ_ASSERT(!(mAutoSizing.Length() == 1 && 1747 mAutoSizing.AsSpan()[0] == kAutoTrackSize), 1748 "It's impossible to have one track with auto value because we " 1749 "filter out this case during parsing"); 1750 1751 if (mAutoSizing.IsEmpty()) { 1752 return kAutoTrackSize; 1753 } 1754 1755 // If multiple track sizes are given, the pattern is repeated as necessary 1756 // to find the size of the implicit tracks. 1757 int32_t i = aIndex % int32_t(mAutoSizing.Length()); 1758 if (i < 0) { 1759 i += mAutoSizing.Length(); 1760 } 1761 return mAutoSizing.AsSpan()[i]; 1762 }; 1763 1764 if (MOZ_UNLIKELY(aTrackIndex < mExplicitGridOffset)) { 1765 // The last implicit grid track before the explicit grid receives the 1766 // last specified size, and so on backwards. Therefore we pass the 1767 // negative relative index to imply that we should get the implicit size 1768 // from the last Nth specified grid auto size. 1769 return getImplicitSize(int32_t(aTrackIndex) - 1770 int32_t(mExplicitGridOffset)); 1771 } 1772 uint32_t index = aTrackIndex - mExplicitGridOffset; 1773 MOZ_ASSERT(mRepeatAutoStart <= mRepeatAutoEnd); 1774 1775 if (index >= mRepeatAutoStart) { 1776 if (index < mRepeatAutoEnd) { 1777 // Expand the repeat tracks. 1778 const auto& indices = mExpandedTracks[mRepeatAutoStart]; 1779 const TrackListValue& value = mTrackListValues[indices.first]; 1780 1781 // We expect the default to be used for all track repeats. 1782 MOZ_ASSERT(indices.second == 0); 1783 1784 const auto& repeatTracks = value.AsTrackRepeat().track_sizes.AsSpan(); 1785 1786 // Find the repeat track to use, skipping over any collapsed tracks. 1787 const uint32_t finalRepeatIndex = (index - mRepeatAutoStart); 1788 uint32_t repeatWithCollapsed = 0; 1789 // NOTE: We need SizingFor before the final collapsed tracks are known. 1790 // We know that it's invalid to have empty mRemovedRepeatTracks when 1791 // there are any repeat tracks, so we can detect that situation here. 1792 if (mRemovedRepeatTracks.IsEmpty()) { 1793 repeatWithCollapsed = finalRepeatIndex; 1794 } else { 1795 // Count up through the repeat tracks, until we have seen 1796 // finalRepeatIndex number of non-collapsed tracks. 1797 for (uint32_t repeatNoCollapsed = 0; 1798 repeatNoCollapsed < finalRepeatIndex; repeatWithCollapsed++) { 1799 if (!mRemovedRepeatTracks[repeatWithCollapsed]) { 1800 repeatNoCollapsed++; 1801 } 1802 } 1803 // If we stopped iterating on a collapsed track, continue to the next 1804 // non-collapsed track. 1805 while (mRemovedRepeatTracks[repeatWithCollapsed]) { 1806 repeatWithCollapsed++; 1807 } 1808 } 1809 return repeatTracks[repeatWithCollapsed % repeatTracks.Length()]; 1810 } else { 1811 // The index is after the repeat auto range, adjust it to skip over the 1812 // repeat value. This will have no effect if there is no auto repeat, 1813 // since then RepeatEndDelta will return zero. 1814 index -= RepeatEndDelta(); 1815 } 1816 } 1817 if (index >= mExpandedTracks.Length()) { 1818 return getImplicitSize(index - mExpandedTracks.Length()); 1819 } 1820 auto& indices = mExpandedTracks[index]; 1821 const TrackListValue& value = mTrackListValues[indices.first]; 1822 if (value.IsTrackSize()) { 1823 MOZ_ASSERT(indices.second == 0); 1824 return value.AsTrackSize(); 1825 } 1826 return value.AsTrackRepeat().track_sizes.AsSpan()[indices.second]; 1827 } 1828 const StyleTrackBreadth& MaxSizingFor(uint32_t aTrackIndex) const { 1829 return SizingFor(aTrackIndex).GetMax(); 1830 } 1831 const StyleTrackBreadth& MinSizingFor(uint32_t aTrackIndex) const { 1832 return SizingFor(aTrackIndex).GetMin(); 1833 } 1834 uint32_t NumExplicitTracks() const { 1835 return mExpandedTracks.Length() + RepeatEndDelta(); 1836 } 1837 uint32_t NumRepeatTracks() const { return mRepeatAutoEnd - mRepeatAutoStart; } 1838 // The difference between mExplicitGridEnd and mSizingFunctions.Length(). 1839 int32_t RepeatEndDelta() const { 1840 return mHasRepeatAuto ? int32_t(NumRepeatTracks()) - 1 : 0; 1841 } 1842 void SetNumRepeatTracks(uint32_t aNumRepeatTracks) { 1843 MOZ_ASSERT(mHasRepeatAuto || aNumRepeatTracks == 0); 1844 mRepeatAutoEnd = mRepeatAutoStart + aNumRepeatTracks; 1845 } 1846 1847 // Store mTrackListValues into mExpandedTracks with `repeat(INTEGER, ...)` 1848 // tracks expanded. 1849 void ExpandNonRepeatAutoTracks() { 1850 for (size_t i = 0; i < mTrackListValues.Length(); ++i) { 1851 auto& value = mTrackListValues[i]; 1852 if (value.IsTrackSize()) { 1853 mExpandedTracks.EmplaceBack(i, 0); 1854 continue; 1855 } 1856 auto& repeat = value.AsTrackRepeat(); 1857 if (!repeat.count.IsNumber()) { 1858 MOZ_ASSERT(i == mRepeatAutoStart); 1859 mRepeatAutoStart = mExpandedTracks.Length(); 1860 mRepeatAutoEnd = mRepeatAutoStart + repeat.track_sizes.Length(); 1861 mExpandedTracks.EmplaceBack(i, 0); 1862 continue; 1863 } 1864 for (auto j : IntegerRange(repeat.count.AsNumber())) { 1865 (void)j; 1866 size_t trackSizesCount = repeat.track_sizes.Length(); 1867 for (auto k : IntegerRange(trackSizesCount)) { 1868 mExpandedTracks.EmplaceBack(i, k); 1869 } 1870 } 1871 } 1872 if (MOZ_UNLIKELY(mExpandedTracks.Length() > kMaxLine - 1)) { 1873 mExpandedTracks.TruncateLength(kMaxLine - 1); 1874 if (mHasRepeatAuto && mRepeatAutoStart > kMaxLine - 1) { 1875 // The `repeat(auto-fill/fit)` track is outside the clamped grid. 1876 mHasRepeatAuto = false; 1877 } 1878 } 1879 } 1880 1881 // Some style data references, for easy access. 1882 const GridTemplate& mTemplate; 1883 const Span<const TrackListValue> mTrackListValues; 1884 const StyleImplicitGridTracks& mAutoSizing; 1885 // An array from expanded track sizes (without expanding auto-repeat, which is 1886 // included just once at `mRepeatAutoStart`). 1887 // 1888 // Each entry contains two indices, the first into mTrackListValues, and a 1889 // second one inside mTrackListValues' repeat value, if any, or zero 1890 // otherwise. 1891 nsTArray<std::pair<size_t, size_t>> mExpandedTracks; 1892 // Offset from the start of the implicit grid to the first explicit track. 1893 uint32_t mExplicitGridOffset; 1894 // The index of the repeat(auto-fill/fit) track, or zero if there is none. 1895 // Relative to mExplicitGridOffset (repeat tracks are explicit by definition). 1896 uint32_t mRepeatAutoStart; 1897 // The (hypothetical) index of the last such repeat() track. 1898 uint32_t mRepeatAutoEnd; 1899 // True if there is a specified repeat(auto-fill/fit) track. 1900 bool mHasRepeatAuto; 1901 // True if this track (relative to mRepeatAutoStart) is a removed auto-fit. 1902 // Indexed relative to mExplicitGridOffset + mRepeatAutoStart. 1903 nsTArray<bool> mRemovedRepeatTracks; 1904 }; 1905 1906 /** 1907 * Utility class to find line names. It provides an interface to lookup line 1908 * names with a dynamic number of repeat(auto-fill/fit) tracks taken into 1909 * account. 1910 */ 1911 class MOZ_STACK_CLASS nsGridContainerFrame::LineNameMap { 1912 public: 1913 /** 1914 * Create a LineNameMap. 1915 * @param aStylePosition the style for the grid container 1916 * @param aImplicitNamedAreas the implicit areas for the grid container 1917 * @param aGridTemplate is the grid-template-rows/columns data for this axis 1918 * @param aParentLineNameMap the parent grid's map parallel to this map, or 1919 * null if this map isn't for a subgrid 1920 * @param aRange the subgrid's range in the parent grid, or null 1921 * @param aIsSameDirection true if our axis progresses in the same direction 1922 * in the subgrid and parent 1923 */ 1924 LineNameMap(const nsStylePosition* aStylePosition, 1925 const ImplicitNamedAreas* aImplicitNamedAreas, 1926 const TrackSizingFunctions& aTracks, 1927 const LineNameMap* aParentLineNameMap, const LineRange* aRange, 1928 bool aIsSameDirection) 1929 : mStylePosition(aStylePosition), 1930 mAreas(aImplicitNamedAreas), 1931 mRepeatAutoStart(aTracks.mRepeatAutoStart), 1932 mRepeatAutoEnd(aTracks.mRepeatAutoEnd), 1933 mRepeatEndDelta(aTracks.RepeatEndDelta()), 1934 mParentLineNameMap(aParentLineNameMap), 1935 mRange(aRange), 1936 mIsSameDirection(aIsSameDirection), 1937 mHasRepeatAuto(aTracks.mHasRepeatAuto) { 1938 if (MOZ_UNLIKELY(aRange)) { // subgrid case 1939 mClampMinLine = 1; 1940 mClampMaxLine = 1 + aRange->Extent(); 1941 MOZ_ASSERT(aTracks.mTemplate.IsSubgrid(), "Should be subgrid type"); 1942 ExpandRepeatLineNamesForSubgrid(*aTracks.mTemplate.AsSubgrid()); 1943 // we've expanded all subgrid auto-fill lines in 1944 // ExpandRepeatLineNamesForSubgrid() 1945 mRepeatAutoStart = 0; 1946 mRepeatAutoEnd = mRepeatAutoStart; 1947 mHasRepeatAuto = false; 1948 } else { 1949 mClampMinLine = kMinLine; 1950 mClampMaxLine = kMaxLine; 1951 if (mHasRepeatAuto) { 1952 mTrackAutoRepeatLineNames = 1953 aTracks.mTemplate.GetRepeatAutoValue()->line_names.AsSpan(); 1954 } 1955 ExpandRepeatLineNames(aTracks); 1956 } 1957 if (mHasRepeatAuto) { 1958 // We need mTemplateLinesEnd to be after all line names. 1959 // mExpandedLineNames has one repetition of the repeat(auto-fit/fill) 1960 // track name lists already, so we must subtract the number of repeat 1961 // track name lists to get to the number of non-repeat tracks, minus 2 1962 // because the first and last line name lists are shared with the 1963 // preceding and following non-repeat line name lists. We then add 1964 // mRepeatEndDelta to include the interior line name lists from repeat 1965 // tracks. 1966 mTemplateLinesEnd = mExpandedLineNames.Length() - 1967 (mTrackAutoRepeatLineNames.Length() - 2) + 1968 mRepeatEndDelta; 1969 } else { 1970 mTemplateLinesEnd = mExpandedLineNames.Length(); 1971 } 1972 MOZ_ASSERT(mHasRepeatAuto || mRepeatEndDelta <= 0); 1973 MOZ_ASSERT(!mHasRepeatAuto || aRange || 1974 (mExpandedLineNames.Length() >= 2 && 1975 mRepeatAutoStart <= mExpandedLineNames.Length())); 1976 } 1977 1978 // Store line names into mExpandedLineNames with `repeat(INTEGER, ...)` 1979 // expanded for non-subgrid. 1980 void ExpandRepeatLineNames(const TrackSizingFunctions& aTracks) { 1981 auto lineNameLists = aTracks.mTemplate.LineNameLists(false); 1982 1983 const auto& trackListValues = aTracks.mTrackListValues; 1984 const NameList* nameListToMerge = nullptr; 1985 // NOTE(emilio): We rely on std::move clearing out the array. 1986 SmallPointerArray<const NameList> names; 1987 const uint32_t end = 1988 std::min<uint32_t>(lineNameLists.Length(), mClampMaxLine + 1); 1989 for (uint32_t i = 0; i < end; ++i) { 1990 if (nameListToMerge) { 1991 names.AppendElement(nameListToMerge); 1992 nameListToMerge = nullptr; 1993 } 1994 names.AppendElement(&lineNameLists[i]); 1995 if (i >= trackListValues.Length()) { 1996 mExpandedLineNames.AppendElement(std::move(names)); 1997 continue; 1998 } 1999 const auto& value = trackListValues[i]; 2000 if (value.IsTrackSize()) { 2001 mExpandedLineNames.AppendElement(std::move(names)); 2002 continue; 2003 } 2004 const auto& repeat = value.AsTrackRepeat(); 2005 if (!repeat.count.IsNumber()) { 2006 const auto repeatNames = repeat.line_names.AsSpan(); 2007 // If the repeat was truncated due to more than kMaxLine tracks, then 2008 // the repeat will no longer be set on mRepeatAutoStart). 2009 MOZ_ASSERT(!mHasRepeatAuto || 2010 mRepeatAutoStart == mExpandedLineNames.Length()); 2011 MOZ_ASSERT(repeatNames.Length() >= 2); 2012 for (const auto j : IntegerRange(repeatNames.Length() - 1)) { 2013 names.AppendElement(&repeatNames[j]); 2014 mExpandedLineNames.AppendElement(std::move(names)); 2015 } 2016 nameListToMerge = &repeatNames[repeatNames.Length() - 1]; 2017 continue; 2018 } 2019 for (auto j : IntegerRange(repeat.count.AsNumber())) { 2020 (void)j; 2021 if (nameListToMerge) { 2022 names.AppendElement(nameListToMerge); 2023 nameListToMerge = nullptr; 2024 } 2025 size_t trackSizesCount = repeat.track_sizes.Length(); 2026 auto repeatLineNames = repeat.line_names.AsSpan(); 2027 MOZ_ASSERT(repeatLineNames.Length() == trackSizesCount || 2028 repeatLineNames.Length() == trackSizesCount + 1); 2029 for (auto k : IntegerRange(trackSizesCount)) { 2030 names.AppendElement(&repeatLineNames[k]); 2031 mExpandedLineNames.AppendElement(std::move(names)); 2032 } 2033 if (repeatLineNames.Length() == trackSizesCount + 1) { 2034 nameListToMerge = &repeatLineNames[trackSizesCount]; 2035 } 2036 } 2037 } 2038 2039 if (MOZ_UNLIKELY(mExpandedLineNames.Length() > uint32_t(mClampMaxLine))) { 2040 mExpandedLineNames.TruncateLength(mClampMaxLine); 2041 } 2042 } 2043 2044 // Store line names into mExpandedLineNames with `repeat(INTEGER, ...)` 2045 // expanded, and all `repeat(...)` expanded for subgrid. 2046 // https://drafts.csswg.org/css-grid-2/#resolved-track-list-subgrid 2047 void ExpandRepeatLineNamesForSubgrid( 2048 const StyleGenericLineNameList<StyleInteger>& aStyleLineNameList) { 2049 const auto& lineNameList = aStyleLineNameList.line_names.AsSpan(); 2050 const uint32_t maxCount = mClampMaxLine + 1; 2051 const uint32_t end = lineNameList.Length(); 2052 for (uint32_t i = 0; i < end && mExpandedLineNames.Length() < maxCount; 2053 ++i) { 2054 const auto& item = lineNameList[i]; 2055 if (item.IsLineNames()) { 2056 // <line-names> case. Just copy it. 2057 SmallPointerArray<const NameList> names; 2058 names.AppendElement(&item.AsLineNames()); 2059 mExpandedLineNames.AppendElement(std::move(names)); 2060 continue; 2061 } 2062 2063 MOZ_ASSERT(item.IsRepeat()); 2064 const auto& repeat = item.AsRepeat(); 2065 const auto repeatLineNames = repeat.line_names.AsSpan(); 2066 2067 if (repeat.count.IsNumber()) { 2068 // Clone all <line-names>+ (repeated by N) into 2069 // |mExpandedLineNames|. 2070 for (uint32_t repeatCount = 0; 2071 repeatCount < (uint32_t)repeat.count.AsNumber(); ++repeatCount) { 2072 for (const NameList& lineNames : repeatLineNames) { 2073 SmallPointerArray<const NameList> names; 2074 names.AppendElement(&lineNames); 2075 mExpandedLineNames.AppendElement(std::move(names)); 2076 if (mExpandedLineNames.Length() >= maxCount) { 2077 break; 2078 } 2079 } 2080 } 2081 continue; 2082 } 2083 2084 MOZ_ASSERT(repeat.count.IsAutoFill(), 2085 "RepeatCount of subgrid is number or auto-fill"); 2086 2087 const size_t fillLen = repeatLineNames.Length(); 2088 const int32_t extraAutoFillLineCount = 2089 mClampMaxLine - 2090 (int32_t)aStyleLineNameList.expanded_line_names_length; 2091 // Maximum possible number of repeat name lists. 2092 // Note: |expanded_line_names_length| doesn't include auto repeat. 2093 const uint32_t possibleRepeatLength = 2094 std::max<int32_t>(0, extraAutoFillLineCount); 2095 const uint32_t repeatRemainder = possibleRepeatLength % fillLen; 2096 2097 // Note: Expand 'auto-fill' names for subgrid for now since 2098 // HasNameAt() only deals with auto-repeat **tracks** currently. 2099 const size_t len = possibleRepeatLength - repeatRemainder; 2100 for (size_t j = 0; j < len; ++j) { 2101 SmallPointerArray<const NameList> names; 2102 names.AppendElement(&repeatLineNames[j % fillLen]); 2103 mExpandedLineNames.AppendElement(std::move(names)); 2104 if (mExpandedLineNames.Length() >= maxCount) { 2105 break; 2106 } 2107 } 2108 } 2109 2110 if (MOZ_UNLIKELY(mExpandedLineNames.Length() > uint32_t(mClampMaxLine))) { 2111 mExpandedLineNames.TruncateLength(mClampMaxLine); 2112 } 2113 } 2114 2115 /** 2116 * Find the aNth occurrence of aName, searching forward if aNth is positive, 2117 * and in reverse if aNth is negative (aNth == 0 is invalid), starting from 2118 * aFromIndex (not inclusive), and return a 1-based line number. 2119 * Also take into account there is an unconditional match at the lines in 2120 * aImplicitLines. 2121 * Return zero if aNth occurrences can't be found. In that case, aNth has 2122 * been decremented with the number of occurrences that were found (if any). 2123 * 2124 * E.g. to search for "A 2" forward from the start of the grid: aName is "A" 2125 * aNth is 2 and aFromIndex is zero. To search for "A -2", aNth is -2 and 2126 * aFromIndex is ExplicitGridEnd + 1 (which is the line "before" the last 2127 * line when we're searching in reverse). For "span A 2", aNth is 2 when 2128 * used on a grid-[row|column]-end property and -2 for a *-start property, 2129 * and aFromIndex is the line (which we should skip) on the opposite property. 2130 */ 2131 uint32_t FindNamedLine(nsAtom* aName, int32_t* aNth, uint32_t aFromIndex, 2132 const nsTArray<uint32_t>& aImplicitLines) const { 2133 MOZ_ASSERT(aName); 2134 MOZ_ASSERT(!aName->IsEmpty()); 2135 MOZ_ASSERT(aNth && *aNth != 0); 2136 if (*aNth > 0) { 2137 return FindLine(aName, aNth, aFromIndex, aImplicitLines); 2138 } 2139 int32_t nth = -*aNth; 2140 int32_t line = RFindLine(aName, &nth, aFromIndex, aImplicitLines); 2141 *aNth = -nth; 2142 return line; 2143 } 2144 2145 /** 2146 * Return a set of lines in aImplicitLines which matches the area name aName 2147 * on aSide. For example, for aName "a" and aSide being an end side, it 2148 * returns the line numbers which would match "a-end" in the relevant axis. 2149 * For subgrids it includes searching the relevant axis in all ancestor 2150 * grids too (within this subgrid's spanned area). If an ancestor has 2151 * opposite direction, we switch aSide to the opposite logical side so we 2152 * match on the same physical side as the original subgrid we're resolving 2153 * the name for. 2154 */ 2155 void FindNamedAreas(nsAtom* aName, LogicalSide aSide, 2156 nsTArray<uint32_t>& aImplicitLines) const { 2157 // True if we're currently in a map that has the same direction as 'this'. 2158 bool sameDirectionAsThis = true; 2159 uint32_t min = !mParentLineNameMap ? 1 : mClampMinLine; 2160 uint32_t max = mClampMaxLine; 2161 for (auto* map = this; true;) { 2162 uint32_t line = map->FindNamedArea(aName, aSide, min, max); 2163 if (line > 0) { 2164 if (MOZ_LIKELY(sameDirectionAsThis)) { 2165 line -= min - 1; 2166 } else { 2167 line = max - line + 1; 2168 } 2169 aImplicitLines.AppendElement(line); 2170 } 2171 auto* parent = map->mParentLineNameMap; 2172 if (!parent) { 2173 if (MOZ_UNLIKELY(aImplicitLines.Length() > 1)) { 2174 // Remove duplicates and sort in ascending order. 2175 aImplicitLines.Sort(); 2176 for (size_t i = 0; i < aImplicitLines.Length(); ++i) { 2177 uint32_t prev = aImplicitLines[i]; 2178 auto j = i + 1; 2179 const auto start = j; 2180 while (j < aImplicitLines.Length() && aImplicitLines[j] == prev) { 2181 ++j; 2182 } 2183 if (j != start) { 2184 aImplicitLines.RemoveElementsAt(start, j - start); 2185 } 2186 } 2187 } 2188 return; 2189 } 2190 if (MOZ_UNLIKELY(!map->mIsSameDirection)) { 2191 aSide = GetOppositeSide(aSide); 2192 sameDirectionAsThis = !sameDirectionAsThis; 2193 } 2194 min = map->TranslateToParentMap(min); 2195 max = map->TranslateToParentMap(max); 2196 if (min > max) { 2197 MOZ_ASSERT(!map->mIsSameDirection); 2198 std::swap(min, max); 2199 } 2200 map = parent; 2201 } 2202 } 2203 2204 /** 2205 * Return true if any implicit named areas match aName, in this map or 2206 * in any of our ancestor maps. 2207 */ 2208 bool HasImplicitNamedArea(nsAtom* aName) const { 2209 const auto* map = this; 2210 do { 2211 if (map->mAreas && map->mAreas->has(aName)) { 2212 return true; 2213 } 2214 map = map->mParentLineNameMap; 2215 } while (map); 2216 return false; 2217 } 2218 2219 // For generating line name data for devtools. 2220 nsTArray<nsTArray<StyleCustomIdent>> 2221 GetResolvedLineNamesForComputedGridTrackInfo() const { 2222 nsTArray<nsTArray<StyleCustomIdent>> result; 2223 for (auto& expandedLine : mExpandedLineNames) { 2224 nsTArray<StyleCustomIdent> line; 2225 for (auto* chunk : expandedLine) { 2226 for (auto& name : chunk->AsSpan()) { 2227 line.AppendElement(name); 2228 } 2229 } 2230 result.AppendElement(std::move(line)); 2231 } 2232 return result; 2233 } 2234 2235 nsTArray<RefPtr<nsAtom>> GetExplicitLineNamesAtIndex(uint32_t aIndex) const { 2236 nsTArray<RefPtr<nsAtom>> lineNames; 2237 if (aIndex < mTemplateLinesEnd) { 2238 const auto nameLists = GetLineNamesAt(aIndex); 2239 for (const NameList* nameList : nameLists) { 2240 for (const auto& name : nameList->AsSpan()) { 2241 lineNames.AppendElement(name.AsAtom()); 2242 } 2243 } 2244 } 2245 return lineNames; 2246 } 2247 2248 const nsTArray<SmallPointerArray<const NameList>>& ExpandedLineNames() const { 2249 return mExpandedLineNames; 2250 } 2251 const Span<const StyleOwnedSlice<StyleCustomIdent>>& 2252 TrackAutoRepeatLineNames() const { 2253 return mTrackAutoRepeatLineNames; 2254 } 2255 bool HasRepeatAuto() const { return mHasRepeatAuto; } 2256 uint32_t NumRepeatTracks() const { return mRepeatAutoEnd - mRepeatAutoStart; } 2257 uint32_t RepeatAutoStart() const { return mRepeatAutoStart; } 2258 2259 // The min/max line number (1-based) for clamping. 2260 int32_t mClampMinLine; 2261 int32_t mClampMaxLine; 2262 2263 private: 2264 // Return true if this map represents a subgridded axis. 2265 bool IsSubgridded() const { return mParentLineNameMap != nullptr; } 2266 2267 /** 2268 * @see FindNamedLine, this function searches forward. 2269 */ 2270 uint32_t FindLine(nsAtom* aName, int32_t* aNth, uint32_t aFromIndex, 2271 const nsTArray<uint32_t>& aImplicitLines) const { 2272 MOZ_ASSERT(aNth && *aNth > 0); 2273 int32_t nth = *aNth; 2274 // For a subgrid we need to search to the end of the grid rather than 2275 // the end of the local name list, since ancestors might match. 2276 const uint32_t end = IsSubgridded() ? mClampMaxLine : mTemplateLinesEnd; 2277 uint32_t line; 2278 uint32_t i = aFromIndex; 2279 for (; i < end; i = line) { 2280 line = i + 1; 2281 if (Contains(i, aName) || aImplicitLines.Contains(line)) { 2282 if (--nth == 0) { 2283 return line; 2284 } 2285 } 2286 } 2287 for (auto implicitLine : aImplicitLines) { 2288 if (implicitLine > i) { 2289 // implicitLine is after the lines we searched above so it's last. 2290 // (grid-template-areas has more tracks than 2291 // grid-template-[rows|columns]) 2292 if (--nth == 0) { 2293 return implicitLine; 2294 } 2295 } 2296 } 2297 MOZ_ASSERT(nth > 0, "should have returned a valid line above already"); 2298 *aNth = nth; 2299 return 0; 2300 } 2301 2302 /** 2303 * @see FindNamedLine, this function searches in reverse. 2304 */ 2305 uint32_t RFindLine(nsAtom* aName, int32_t* aNth, uint32_t aFromIndex, 2306 const nsTArray<uint32_t>& aImplicitLines) const { 2307 MOZ_ASSERT(aNth && *aNth > 0); 2308 if (MOZ_UNLIKELY(aFromIndex == 0)) { 2309 return 0; // There are no named lines beyond the start of the explicit 2310 // grid. 2311 } 2312 --aFromIndex; // (shift aFromIndex so we can treat it as inclusive) 2313 int32_t nth = *aNth; 2314 // Implicit lines may be beyond the explicit grid so we match those 2315 // first if it's within the mTemplateLinesEnd..aFromIndex range. 2316 // aImplicitLines is presumed sorted. 2317 // For a subgrid we need to search to the end of the grid rather than 2318 // the end of the local name list, since ancestors might match. 2319 const uint32_t end = IsSubgridded() ? mClampMaxLine : mTemplateLinesEnd; 2320 for (auto implicitLine : Reversed(aImplicitLines)) { 2321 if (implicitLine <= end) { 2322 break; 2323 } 2324 if (implicitLine < aFromIndex) { 2325 if (--nth == 0) { 2326 return implicitLine; 2327 } 2328 } 2329 } 2330 for (uint32_t i = std::min(aFromIndex, end); i; --i) { 2331 if (Contains(i - 1, aName) || aImplicitLines.Contains(i)) { 2332 if (--nth == 0) { 2333 return i; 2334 } 2335 } 2336 } 2337 MOZ_ASSERT(nth > 0, "should have returned a valid line above already"); 2338 *aNth = nth; 2339 return 0; 2340 } 2341 2342 // Return true if aName exists at aIndex in this map or any parent map. 2343 bool Contains(uint32_t aIndex, nsAtom* aName) const { 2344 const auto* map = this; 2345 while (true) { 2346 if (aIndex < map->mTemplateLinesEnd && map->HasNameAt(aIndex, aName)) { 2347 return true; 2348 } 2349 auto* parent = map->mParentLineNameMap; 2350 if (!parent) { 2351 return false; 2352 } 2353 uint32_t line = map->TranslateToParentMap(aIndex + 1); 2354 MOZ_ASSERT(line >= 1, "expected a 1-based line number"); 2355 aIndex = line - 1; 2356 map = parent; 2357 } 2358 MOZ_ASSERT_UNREACHABLE("we always return from inside the loop above"); 2359 } 2360 2361 static bool Contains(Span<const StyleCustomIdent> aNames, nsAtom* aName) { 2362 for (auto& name : aNames) { 2363 if (name.AsAtom() == aName) { 2364 return true; 2365 } 2366 } 2367 return false; 2368 } 2369 2370 // Return true if aName exists at aIndex in this map. 2371 bool HasNameAt(const uint32_t aIndex, nsAtom* const aName) const { 2372 const auto nameLists = GetLineNamesAt(aIndex); 2373 for (const NameList* nameList : nameLists) { 2374 if (Contains(nameList->AsSpan(), aName)) { 2375 return true; 2376 } 2377 } 2378 return false; 2379 } 2380 2381 // Get the line names at an index. 2382 // This accounts for auto repeat. The results may be spread over multiple name 2383 // lists returned in the array, which is done to avoid unneccessarily copying 2384 // the arrays to concatenate them. 2385 SmallPointerArray<const NameList> GetLineNamesAt( 2386 const uint32_t aIndex) const { 2387 SmallPointerArray<const NameList> names; 2388 // The index into mExpandedLineNames to use, if aIndex doesn't point to a 2389 // name inside of a auto repeat. 2390 uint32_t repeatAdjustedIndex = aIndex; 2391 // Note: For subgrid, |mHasRepeatAuto| is always false because we have 2392 // expanded it in the constructor of LineNameMap. 2393 if (mHasRepeatAuto) { 2394 // If the index is inside of the auto repeat, use the repeat line 2395 // names. Otherwise, if the index is past the end of the repeat it must 2396 // be adjusted to acount for the repeat tracks. 2397 // mExpandedLineNames has the first and last line name lists from the 2398 // repeat in it already, so we can just ignore aIndex == mRepeatAutoStart 2399 // and treat when aIndex == mRepeatAutoEnd the same as any line after the 2400 // the repeat. 2401 const uint32_t maxRepeatLine = mTrackAutoRepeatLineNames.Length() - 1; 2402 if (aIndex > mRepeatAutoStart && aIndex < mRepeatAutoEnd) { 2403 // The index is inside the auto repeat. Calculate the lines to use, 2404 // including the previous repetitions final names when we roll over 2405 // from one repetition to the next. 2406 const uint32_t repeatIndex = 2407 (aIndex - mRepeatAutoStart) % maxRepeatLine; 2408 if (repeatIndex == 0) { 2409 // The index is at the start of a new repetition. The start of the 2410 // first repetition is intentionally ignored above, so this will 2411 // consider both the end of the previous repetition and the start 2412 // the one that contains aIndex. 2413 names.AppendElement(&mTrackAutoRepeatLineNames[maxRepeatLine]); 2414 } 2415 names.AppendElement(&mTrackAutoRepeatLineNames[repeatIndex]); 2416 return names; 2417 } 2418 if (aIndex != mRepeatAutoStart && aIndex >= mRepeatAutoEnd) { 2419 // Adjust the index to account for the line names of the repeat. 2420 repeatAdjustedIndex -= mRepeatEndDelta; 2421 repeatAdjustedIndex += mTrackAutoRepeatLineNames.Length() - 2; 2422 } 2423 } 2424 MOZ_ASSERT(repeatAdjustedIndex < mExpandedLineNames.Length(), 2425 "Incorrect repeatedAdjustedIndex"); 2426 MOZ_ASSERT(names.IsEmpty()); 2427 // The index is not inside the repeat tracks, or no repeat tracks exist. 2428 const auto& nameLists = mExpandedLineNames[repeatAdjustedIndex]; 2429 for (const NameList* nameList : nameLists) { 2430 names.AppendElement(nameList); 2431 } 2432 return names; 2433 } 2434 2435 // Translate a subgrid line (1-based) to a parent line (1-based). 2436 uint32_t TranslateToParentMap(uint32_t aLine) const { 2437 if (MOZ_LIKELY(mIsSameDirection)) { 2438 return aLine + mRange->mStart; 2439 } 2440 MOZ_ASSERT(mRange->mEnd + 1 >= aLine); 2441 return mRange->mEnd - (aLine - 1) + 1; 2442 } 2443 2444 /** 2445 * Return the 1-based line that match aName in 'grid-template-areas' 2446 * on the side aSide. Clamp the result to aMin..aMax but require 2447 * that some part of the area is inside for it to match. 2448 * Return zero if there is no match. 2449 */ 2450 uint32_t FindNamedArea(nsAtom* aName, LogicalSide aSide, int32_t aMin, 2451 int32_t aMax) const { 2452 if (const NamedArea* area = FindNamedArea(aName)) { 2453 int32_t start = IsBlock(aSide) ? area->rows.start : area->columns.start; 2454 int32_t end = IsBlock(aSide) ? area->rows.end : area->columns.end; 2455 if (IsStart(aSide)) { 2456 if (start >= aMin) { 2457 if (start <= aMax) { 2458 return start; 2459 } 2460 } else if (end >= aMin) { 2461 return aMin; 2462 } 2463 } else { 2464 if (end <= aMax) { 2465 if (end >= aMin) { 2466 return end; 2467 } 2468 } else if (start <= aMax) { 2469 return aMax; 2470 } 2471 } 2472 } 2473 return 0; // no match 2474 } 2475 2476 /** 2477 * A convenience method to lookup a name in 'grid-template-areas'. 2478 * @return null if not found 2479 */ 2480 const NamedArea* FindNamedArea(nsAtom* aName) const { 2481 if (mStylePosition->mGridTemplateAreas.IsNone()) { 2482 return nullptr; 2483 } 2484 const auto areas = mStylePosition->mGridTemplateAreas.AsAreas(); 2485 for (const NamedArea& area : areas->areas.AsSpan()) { 2486 if (area.name.AsAtom() == aName) { 2487 return &area; 2488 } 2489 } 2490 return nullptr; 2491 } 2492 2493 // Some style data references, for easy access. 2494 const nsStylePosition* mStylePosition; 2495 const ImplicitNamedAreas* mAreas; 2496 // The expanded list of line-names. Each entry is usually a single NameList, 2497 // but can be multiple in the case where repeat() expands to something that 2498 // has a line name list at the end. 2499 nsTArray<SmallPointerArray<const NameList>> mExpandedLineNames; 2500 // The repeat(auto-fill/fit) track value, if any. (always empty for subgrid) 2501 Span<const StyleOwnedSlice<StyleCustomIdent>> mTrackAutoRepeatLineNames; 2502 // The index of the repeat(auto-fill/fit) track, or zero if there is none. 2503 uint32_t mRepeatAutoStart; 2504 // The index one past the end of the repeat(auto-fill/fit) tracks. Equal to 2505 // mRepeatAutoStart if there are no repeat(auto-fill/fit) tracks. 2506 uint32_t mRepeatAutoEnd; 2507 // The total number of repeat tracks minus 1. 2508 int32_t mRepeatEndDelta; 2509 // The end of the line name lists with repeat(auto-fill/fit) tracks accounted 2510 // for. 2511 uint32_t mTemplateLinesEnd; 2512 2513 // The parent line map, or null if this map isn't for a subgrid. 2514 const LineNameMap* mParentLineNameMap; 2515 // The subgrid's range, or null if this map isn't for a subgrid. 2516 const LineRange* mRange; 2517 // True if the subgrid/parent axes progresses in the same direction. 2518 const bool mIsSameDirection; 2519 2520 // True if there is a specified repeat(auto-fill/fit) track. 2521 bool mHasRepeatAuto; 2522 }; 2523 2524 struct CachedIntrinsicSizes; 2525 2526 /** 2527 * State for the tracks in one dimension. 2528 */ 2529 struct nsGridContainerFrame::Tracks { 2530 explicit Tracks(LogicalAxis aAxis) 2531 : mContentBoxSize(NS_UNCONSTRAINEDSIZE), 2532 mGridGap(NS_UNCONSTRAINEDSIZE), 2533 mStateUnion(TrackSize::StateBits{0}), 2534 mAxis(aAxis), 2535 mCanResolveLineRangeSize(false), 2536 mIsMasonry(false) { 2537 mBaselineSubtreeAlign[BaselineSharingGroup::First] = StyleAlignFlags::AUTO; 2538 mBaselineSubtreeAlign[BaselineSharingGroup::Last] = StyleAlignFlags::AUTO; 2539 } 2540 2541 void Initialize(const TrackSizingFunctions& aFunctions, 2542 const NonNegativeLengthPercentageOrNormal& aGridGap, 2543 uint32_t aNumTracks, nscoord aContentBoxSize); 2544 2545 /** 2546 * Return the union of the state bits for the tracks in aRange. 2547 */ 2548 TrackSize::StateBits StateBitsForRange(const LineRange& aRange) const; 2549 2550 // Some data we collect for aligning baseline-aligned items. 2551 struct ItemBaselineData { 2552 uint32_t mBaselineTrack; 2553 nscoord mBaseline; 2554 nscoord mSize; 2555 GridItemInfo* mGridItem; 2556 static bool IsBaselineTrackLessThan(const ItemBaselineData& a, 2557 const ItemBaselineData& b) { 2558 return a.mBaselineTrack < b.mBaselineTrack; 2559 } 2560 }; 2561 2562 /** 2563 * Calculate baseline offsets for the given set of items. 2564 * Helper for InitialzeItemBaselines. 2565 */ 2566 void CalculateItemBaselines(nsTArray<ItemBaselineData>& aBaselineItems, 2567 BaselineSharingGroup aBaselineGroup); 2568 2569 /** 2570 * Initialize grid item baseline state and offsets. 2571 */ 2572 void InitializeItemBaselines(GridReflowInput& aGridRI, 2573 nsTArray<GridItemInfo>& aGridItems); 2574 2575 /** 2576 * A masonry axis has four baseline alignment sets and each set can have 2577 * a first- and last-baseline alignment group, for a total of eight possible 2578 * baseline alignment groups, as follows: 2579 * set 1: the first item in each `start` or `stretch` grid track 2580 * set 2: the last item in each `start` grid track 2581 * set 3: the last item in each `end` or `stretch` grid track 2582 * set 4: the first item in each `end` grid track 2583 * (`start`/`end`/`stretch` refers to the relevant `align/justify-tracks` 2584 * value of the (grid-axis) start track for the item) Baseline-alignment for 2585 * set 1 and 2 always adjusts the item's padding or margin on the start side, 2586 * and set 3 and 4 on the end side, for both first- and last-baseline groups 2587 * in the set. (This is similar to regular grid which always adjusts 2588 * first-baseline groups on the start side and last-baseline groups on the 2589 * end-side. The crux is that those groups are always aligned to the track's 2590 * start/end side respectively.) 2591 */ 2592 struct BaselineAlignmentSet { 2593 bool MatchTrackAlignment(StyleAlignFlags aTrackAlignment) const { 2594 if (mTrackAlignmentSet == BaselineAlignmentSet::StartStretch) { 2595 return aTrackAlignment == StyleAlignFlags::START || 2596 (aTrackAlignment == StyleAlignFlags::STRETCH && 2597 mItemSet == BaselineAlignmentSet::FirstItems); 2598 } 2599 return aTrackAlignment == StyleAlignFlags::END || 2600 (aTrackAlignment == StyleAlignFlags::STRETCH && 2601 mItemSet == BaselineAlignmentSet::LastItems); 2602 } 2603 2604 enum ItemSet { FirstItems, LastItems }; 2605 ItemSet mItemSet = FirstItems; 2606 enum TrackAlignmentSet { StartStretch, EndStretch }; 2607 TrackAlignmentSet mTrackAlignmentSet = StartStretch; 2608 }; 2609 void InitializeItemBaselinesInMasonryAxis( 2610 GridReflowInput& aGridRI, nsTArray<GridItemInfo>& aGridItems, 2611 BaselineAlignmentSet aSet, const nsSize& aContainerSize, 2612 nsTArray<nscoord>& aTrackSizes, 2613 nsTArray<ItemBaselineData>& aFirstBaselineItems, 2614 nsTArray<ItemBaselineData>& aLastBaselineItems); 2615 2616 /** 2617 * Apply the additional alignment needed to align the baseline-aligned subtree 2618 * the item belongs to within its baseline track. 2619 */ 2620 void AlignBaselineSubtree(const GridItemInfo& aGridItem) const; 2621 2622 static TrackSize::StateBits SelectorForPhase(TrackSizingPhase aPhase, 2623 SizingConstraint aConstraint) { 2624 switch (aPhase) { 2625 case TrackSizingPhase::IntrinsicMinimums: 2626 return TrackSize::eIntrinsicMinSizing; 2627 case TrackSizingPhase::ContentBasedMinimums: 2628 return aConstraint == SizingConstraint::MinContent 2629 ? TrackSize::eIntrinsicMinSizing 2630 : TrackSize::eMinOrMaxContentMinSizing; 2631 case TrackSizingPhase::MaxContentMinimums: 2632 return aConstraint == SizingConstraint::MaxContent 2633 ? (TrackSize::eMaxContentMinSizing | 2634 TrackSize::eAutoMinSizing) 2635 : TrackSize::eMaxContentMinSizing; 2636 case TrackSizingPhase::IntrinsicMaximums: 2637 return TrackSize::eIntrinsicMaxSizing; 2638 case TrackSizingPhase::MaxContentMaximums: 2639 return TrackSize::eAutoOrMaxContentMaxSizing; 2640 } 2641 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected phase"); 2642 } 2643 2644 // Some data we collect on each item that spans more than one track for step 3 2645 // and 4 of the Track Sizing Algorithm in ResolveIntrinsicSize below. 2646 // https://drafts.csswg.org/css-grid-2/#algo-spanning-items 2647 struct SpanningItemData final { 2648 uint32_t mSpan; 2649 TrackSize::StateBits mState; 2650 LineRange mLineRange; 2651 EnumeratedArray<GridIntrinsicSizeType, nscoord> mSizes; 2652 nsIFrame* mFrame; 2653 2654 static bool IsSpanLessThan(const SpanningItemData& a, 2655 const SpanningItemData& b) { 2656 return a.mSpan < b.mSpan; 2657 } 2658 2659 nscoord SizeContributionForPhase(TrackSizingPhase aPhase) const { 2660 return mSizes[SizeTypeForPhase(aPhase)]; 2661 } 2662 2663 #ifdef DEBUG 2664 void Dump() const { 2665 printf( 2666 "SpanningItemData { mSpan: %d, mState: %d, mLineRange: (%d, %d), " 2667 "mSizes: {MinContribution: %d, MinContentContribution: %d, " 2668 "MaxContentContribution: %d}, mFrame: %p\n", 2669 mSpan, mState, mLineRange.mStart, mLineRange.mEnd, 2670 mSizes[GridIntrinsicSizeType::MinContribution], 2671 mSizes[GridIntrinsicSizeType::MinContentContribution], 2672 mSizes[GridIntrinsicSizeType::MaxContentContribution], mFrame); 2673 } 2674 #endif 2675 }; 2676 2677 using FitContentClamper = 2678 std::function<bool(uint32_t aTrack, nscoord aMinSize, nscoord* aSize)>; 2679 2680 // Helper method for ResolveIntrinsicSize. 2681 bool GrowSizeForSpanningItems( 2682 TrackSizingStep aStep, TrackSizingPhase aPhase, 2683 nsTArray<SpanningItemData>::iterator aIter, 2684 nsTArray<SpanningItemData>::iterator aIterEnd, 2685 nsTArray<uint32_t>& aTracks, TrackPlan& aTrackPlan, ItemPlan& aItemPlan, 2686 SizingConstraint aConstraint, bool aIsGridIntrinsicSizing, 2687 const TrackSizingFunctions& aFunctions, 2688 const FitContentClamper& aFitContentClamper = nullptr, 2689 bool aNeedInfinitelyGrowableFlag = false); 2690 2691 // Helper method for calculating CachedIntrinsicSizes.mMinSizeClamp. 2692 // 2693 // The caller should set ItemState::eClampMarginBoxMinSize on the 2694 // corresponding grid item if this returns something. 2695 Maybe<nscoord> ComputeMinSizeClamp(const TrackSizingFunctions& aFunctions, 2696 nscoord aPercentageBasis, 2697 const LineRange& aLineRange) const { 2698 return ComputeMinSizeClamp(aFunctions, aPercentageBasis, aLineRange, 2699 StateBitsForRange(aLineRange)); 2700 } 2701 2702 // More efficient version of ComputeMinSizeClamp if the caller has already 2703 // computed the state bits for this line range. 2704 Maybe<nscoord> ComputeMinSizeClamp(const TrackSizingFunctions& aFunctions, 2705 nscoord aPercentageBasis, 2706 const LineRange& aLineRange, 2707 const TrackSize::StateBits aState) const; 2708 2709 /** 2710 * Resolve Intrinsic Track Sizes. 2711 * https://drafts.csswg.org/css-grid-2/#algo-content 2712 */ 2713 void ResolveIntrinsicSize(GridReflowInput& aGridRI, 2714 nsTArray<GridItemInfo>& aGridItems, 2715 const TrackSizingFunctions& aFunctions, 2716 LineRange GridArea::* aRange, 2717 nscoord aPercentageBasis, 2718 SizingConstraint aConstraint); 2719 2720 /** 2721 * Helper for ResolveIntrinsicSize. It implements "Resolve Intrinsic Track 2722 * Sizes" step 2: "Size tracks to fit non-spanning items" in the spec. 2723 * https://drafts.csswg.org/css-grid-2/#algo-single-span-items 2724 */ 2725 void ResolveIntrinsicSizeForNonSpanningItems( 2726 GridReflowInput& aGridRI, const TrackSizingFunctions& aFunctions, 2727 nscoord aPercentageBasis, SizingConstraint aConstraint, 2728 const LineRange& aRange, const GridItemInfo& aGridItem); 2729 2730 // Helper method that returns the track size to use in §12.5.1.2 2731 // https://drafts.csswg.org/css-grid-2/#extra-space 2732 static nscoord StartSizeInDistribution(TrackSizingPhase aPhase, 2733 const TrackSize& aSize) { 2734 switch (aPhase) { 2735 case TrackSizingPhase::IntrinsicMinimums: 2736 case TrackSizingPhase::ContentBasedMinimums: 2737 case TrackSizingPhase::MaxContentMinimums: 2738 return aSize.mBase; 2739 case TrackSizingPhase::IntrinsicMaximums: 2740 case TrackSizingPhase::MaxContentMaximums: 2741 if (aSize.mLimit == NS_UNCONSTRAINEDSIZE) { 2742 return aSize.mBase; 2743 } 2744 return aSize.mLimit; 2745 } 2746 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected phase"); 2747 } 2748 2749 /** 2750 * Collect the tracks which are growable (matching the sizing step/phase 2751 * and sizing constraint) into aGrowableTracks, and return the amount of 2752 * space that can be used to grow those tracks. This method implements 2753 * CSS Grid 2 §12.5.1.2. 2754 * https://drafts.csswg.org/css-grid-2/#extra-space 2755 */ 2756 nscoord CollectGrowable(TrackSizingStep aStep, TrackSizingPhase aPhase, 2757 nscoord aAvailableSpace, const LineRange& aRange, 2758 SizingConstraint aConstraint, 2759 nsTArray<uint32_t>& aGrowableTracks) const { 2760 MOZ_ASSERT(aAvailableSpace > 0, "why call me?"); 2761 nscoord space = aAvailableSpace - mGridGap * (aRange.Extent() - 1); 2762 const TrackSize::StateBits selector = SelectorForPhase(aPhase, aConstraint); 2763 for (auto i : aRange.Range()) { 2764 const TrackSize& sz = mSizes[i]; 2765 space -= StartSizeInDistribution(aPhase, sz); 2766 if (space <= 0) { 2767 return 0; 2768 } 2769 // Only flex tracks can be modified during step 4. 2770 if (aStep == TrackSizingStep::Flex && 2771 !(sz.mState & TrackSize::eFlexMaxSizing)) { 2772 continue; 2773 } 2774 if (sz.mState & selector) { 2775 aGrowableTracks.AppendElement(i); 2776 } 2777 } 2778 return aGrowableTracks.IsEmpty() ? 0 : space; 2779 } 2780 2781 void CopyPlanToSize(TrackSizingPhase aPhase, const TrackPlan& aTrackPlan, 2782 bool aNeedInfinitelyGrowableFlag) { 2783 MOZ_ASSERT(aTrackPlan.Length() == mSizes.Length()); 2784 auto plan = aTrackPlan.begin(); 2785 auto sz = mSizes.begin(); 2786 for (; plan != aTrackPlan.end() && sz != mSizes.end(); plan++, sz++) { 2787 MOZ_ASSERT(plan->mBase >= 0); 2788 switch (aPhase) { 2789 case TrackSizingPhase::IntrinsicMinimums: 2790 case TrackSizingPhase::ContentBasedMinimums: 2791 case TrackSizingPhase::MaxContentMinimums: 2792 sz->mBase = plan->mBase; 2793 break; 2794 case TrackSizingPhase::IntrinsicMaximums: 2795 if (plan->mState & TrackSize::eModified) { 2796 if (sz->mLimit == NS_UNCONSTRAINEDSIZE && 2797 aNeedInfinitelyGrowableFlag) { 2798 sz->mState |= TrackSize::eInfinitelyGrowable; 2799 } 2800 sz->mLimit = plan->mBase; 2801 } 2802 break; 2803 case TrackSizingPhase::MaxContentMaximums: 2804 if (plan->mState & TrackSize::eModified) { 2805 sz->mLimit = plan->mBase; 2806 } 2807 sz->mState &= ~TrackSize::eInfinitelyGrowable; 2808 break; 2809 } 2810 } 2811 } 2812 2813 /** 2814 * Distribute aAvailableSpace to the planned base size for aGrowableTracks 2815 * up to their limits, then distribute the remaining space beyond the limits. 2816 */ 2817 void DistributeToTrackSizes( 2818 TrackSizingStep aStep, TrackSizingPhase aPhase, nscoord aAvailableSpace, 2819 TrackPlan& aTrackPlan, ItemPlan& aItemPlan, 2820 const nsTArray<uint32_t>& aGrowableTracks, SizingConstraint aConstraint, 2821 const TrackSizingFunctions& aFunctions, 2822 const FitContentClamper& aFitContentClamper) const { 2823 aItemPlan.Initialize(aPhase, aGrowableTracks, *this); 2824 nscoord space = aAvailableSpace; 2825 if (aStep == TrackSizingStep::Flex) { 2826 space = aTrackPlan.DistributeToFlexTrackSizes(space, aGrowableTracks, 2827 aFunctions, *this); 2828 } else { 2829 space = aItemPlan.GrowTracksToLimit(space, aGrowableTracks, 2830 aFitContentClamper); 2831 } 2832 2833 if (space > 0) { 2834 uint32_t numGrowable = 2835 aItemPlan.MarkExcludedTracks(aPhase, aGrowableTracks, aConstraint); 2836 aItemPlan.GrowSelectedTracksUnlimited(space, aGrowableTracks, numGrowable, 2837 aFitContentClamper); 2838 } 2839 2840 for (uint32_t track : aGrowableTracks) { 2841 nscoord& plannedSize = aTrackPlan[track].mBase; 2842 nscoord itemIncurredSize = aItemPlan[track].mBase; 2843 if (plannedSize < itemIncurredSize) { 2844 plannedSize = itemIncurredSize; 2845 } 2846 } 2847 } 2848 2849 /** 2850 * Distribute aAvailableSize to the tracks. This implements 12.6 at: 2851 * https://drafts.csswg.org/css-grid-2/#algo-grow-tracks 2852 */ 2853 void DistributeFreeSpace(nscoord aAvailableSize) { 2854 const uint32_t numTracks = mSizes.Length(); 2855 if (MOZ_UNLIKELY(numTracks == 0 || aAvailableSize <= 0)) { 2856 return; 2857 } 2858 if (aAvailableSize == NS_UNCONSTRAINEDSIZE) { 2859 for (TrackSize& sz : mSizes) { 2860 sz.mBase = sz.mLimit; 2861 } 2862 } else { 2863 // Compute free space and count growable tracks. 2864 nscoord space = aAvailableSize; 2865 uint32_t numGrowable = numTracks; 2866 for (const TrackSize& sz : mSizes) { 2867 space -= sz.mBase; 2868 MOZ_ASSERT(sz.mBase <= sz.mLimit); 2869 if (sz.mBase == sz.mLimit) { 2870 --numGrowable; 2871 } 2872 } 2873 // Distribute the free space evenly to the growable tracks. If not exactly 2874 // divisable the remainder is added to the leading tracks. 2875 while (space > 0 && numGrowable) { 2876 nscoord spacePerTrack = std::max<nscoord>(space / numGrowable, 1); 2877 for (TrackSize& sz : mSizes) { 2878 if (sz.mBase == sz.mLimit) { 2879 continue; 2880 } 2881 nscoord newBase = sz.mBase + spacePerTrack; 2882 if (newBase >= sz.mLimit) { 2883 space -= sz.mLimit - sz.mBase; 2884 sz.mBase = sz.mLimit; 2885 --numGrowable; 2886 } else { 2887 space -= spacePerTrack; 2888 sz.mBase = newBase; 2889 } 2890 if (space <= 0) { 2891 break; 2892 } 2893 } 2894 } 2895 } 2896 } 2897 2898 /** 2899 * Implements "12.7.1. Find the Size of an 'fr'". 2900 * https://drafts.csswg.org/css-grid-2/#algo-find-fr-size 2901 * (The returned value is a 'nscoord' divided by a factor - a floating type 2902 * is used to avoid intermediary rounding errors.) 2903 */ 2904 float FindFrUnitSize(const LineRange& aRange, 2905 const nsTArray<uint32_t>& aFlexTracks, 2906 const TrackSizingFunctions& aFunctions, 2907 nscoord aSpaceToFill) const; 2908 2909 /** 2910 * Implements the "find the used flex fraction" part of StretchFlexibleTracks. 2911 * (The returned value is a 'nscoord' divided by a factor - a floating type 2912 * is used to avoid intermediary rounding errors.) 2913 */ 2914 float FindUsedFlexFraction(GridReflowInput& aGridRI, 2915 const nsTArray<GridItemInfo>& aGridItems, 2916 const nsTArray<uint32_t>& aFlexTracks, 2917 const TrackSizingFunctions& aFunctions, 2918 nscoord aAvailableSize) const; 2919 2920 /** 2921 * Implements "12.7. Expand Flexible Tracks" 2922 * https://drafts.csswg.org/css-grid-2/#algo-flex-tracks 2923 */ 2924 void StretchFlexibleTracks(GridReflowInput& aGridRI, 2925 const nsTArray<GridItemInfo>& aGridItems, 2926 const TrackSizingFunctions& aFunctions, 2927 nscoord aAvailableSize); 2928 2929 /** 2930 * Implements "12.3. Track Sizing Algorithm" 2931 * https://drafts.csswg.org/css-grid-2/#algo-track-sizing 2932 */ 2933 void CalculateSizes(GridReflowInput& aGridRI, 2934 nsTArray<GridItemInfo>& aGridItems, 2935 const TrackSizingFunctions& aFunctions, 2936 nscoord aContentBoxSize, LineRange GridArea::* aRange, 2937 SizingConstraint aConstraint); 2938 2939 /** 2940 * Apply 'align/justify-content', whichever is relevant for this axis. 2941 * https://drafts.csswg.org/css-align-3/#propdef-align-content 2942 */ 2943 void AlignJustifyContent(const nsStylePosition* aStyle, 2944 StyleContentDistribution aAligmentStyleValue, 2945 WritingMode aWM, nscoord aContentBoxSize, 2946 bool aIsSubgridded); 2947 2948 /** 2949 * Return the sum of the resolved track and gap sizes (without any packing 2950 * space introduced by align-content or justify-content. 2951 */ 2952 nscoord TotalTrackSizeWithoutAlignment( 2953 const nsGridContainerFrame* aGridContainerFrame) const; 2954 2955 nscoord GridLineEdge(uint32_t aLine, GridLineSide aSide) const { 2956 if (MOZ_UNLIKELY(mSizes.IsEmpty())) { 2957 // https://drafts.csswg.org/css-grid-2/#grid-definition 2958 // "... the explicit grid still contains one grid line in each axis." 2959 MOZ_ASSERT(aLine == 0, "We should only resolve line 1 in an empty grid"); 2960 return nscoord(0); 2961 } 2962 MOZ_ASSERT(aLine <= mSizes.Length(), "mSizes is too small"); 2963 if (aSide == GridLineSide::BeforeGridGap) { 2964 if (aLine == 0) { 2965 return nscoord(0); 2966 } 2967 const TrackSize& sz = mSizes[aLine - 1]; 2968 return sz.mPosition + sz.mBase; 2969 } 2970 if (aLine == mSizes.Length()) { 2971 return mContentBoxSize; 2972 } 2973 return mSizes[aLine].mPosition; 2974 } 2975 2976 nscoord SumOfGridTracksAndGaps() const { 2977 return SumOfGridTracks() + SumOfGridGaps(); 2978 } 2979 2980 nscoord SumOfGridTracks() const { 2981 nscoord result = 0; 2982 for (const TrackSize& size : mSizes) { 2983 result += size.mBase; 2984 } 2985 return result; 2986 } 2987 2988 nscoord SumOfGridGaps() const { 2989 auto len = mSizes.Length(); 2990 return MOZ_LIKELY(len > 1) ? (len - 1) * mGridGap : 0; 2991 } 2992 2993 /** 2994 * Break before aRow, i.e. set the eBreakBefore flag on aRow and set the grid 2995 * gap before aRow to zero (and shift all rows after it by the removed gap). 2996 */ 2997 void BreakBeforeRow(uint32_t aRow) { 2998 MOZ_ASSERT(mAxis == LogicalAxis::Block, 2999 "Should only be fragmenting in the block axis (between rows)"); 3000 nscoord prevRowEndPos = 0; 3001 if (aRow != 0) { 3002 auto& prevSz = mSizes[aRow - 1]; 3003 prevRowEndPos = prevSz.mPosition + prevSz.mBase; 3004 } 3005 auto& sz = mSizes[aRow]; 3006 const nscoord gap = sz.mPosition - prevRowEndPos; 3007 sz.mState |= TrackSize::eBreakBefore; 3008 if (gap != 0) { 3009 for (uint32_t i = aRow, len = mSizes.Length(); i < len; ++i) { 3010 mSizes[i].mPosition -= gap; 3011 } 3012 } 3013 } 3014 3015 /** 3016 * Set the size of aRow to aSize and adjust the position of all rows after it. 3017 */ 3018 void ResizeRow(uint32_t aRow, nscoord aNewSize) { 3019 MOZ_ASSERT(mAxis == LogicalAxis::Block, 3020 "Should only be fragmenting in the block axis (between rows)"); 3021 MOZ_ASSERT(aNewSize >= 0); 3022 auto& sz = mSizes[aRow]; 3023 nscoord delta = aNewSize - sz.mBase; 3024 NS_WARNING_ASSERTION(delta != nscoord(0), "Useless call to ResizeRow"); 3025 sz.mBase = aNewSize; 3026 const uint32_t numRows = mSizes.Length(); 3027 for (uint32_t r = aRow + 1; r < numRows; ++r) { 3028 mSizes[r].mPosition += delta; 3029 } 3030 } 3031 3032 nscoord ResolveSize(const LineRange& aRange) const { 3033 MOZ_ASSERT(mCanResolveLineRangeSize); 3034 MOZ_ASSERT(aRange.Extent() > 0, "grid items cover at least one track"); 3035 return aRange.ToLength(mSizes); 3036 } 3037 3038 // Returns the offset for the given track and baseline sharing group, or the 3039 // opposite group in the same track if unavailable. 3040 // Returns `Nothing()` if neither is set. 3041 Maybe<nscoord> GetBaseline(uint32_t aTrack, 3042 BaselineSharingGroup aBaselineSharingGroup) const { 3043 if (aTrack >= mBaselines.Length()) { 3044 return {}; 3045 } 3046 3047 const auto& trackBaselines = mBaselines[aTrack]; 3048 if (auto b = trackBaselines[aBaselineSharingGroup]) { 3049 return b; 3050 } 3051 if (auto b = trackBaselines[GetOppositeBaselineSharingGroup( 3052 aBaselineSharingGroup)]) { 3053 // First and last baseline offsets are from the start or end edges 3054 // respectively and requesting the opposite baseline requires adjusting 3055 // the offset to the opposite side. 3056 return Some(mSizes[aTrack].mBase - *b); 3057 } 3058 3059 return {}; 3060 } 3061 3062 #ifdef DEBUG 3063 void Dump() const; 3064 #endif 3065 3066 TrackPlan mSizes; 3067 nscoord mContentBoxSize; 3068 nscoord mGridGap; 3069 3070 // Stores per-track baselines for grid baseline calculations. 3071 CopyableTArray<PerBaseline<Maybe<nscoord>>> mBaselines; 3072 3073 // The union of the track min/max-sizing state bits in this axis. 3074 TrackSize::StateBits mStateUnion; 3075 LogicalAxis mAxis; 3076 // Used for aligning a baseline-aligned subtree of items. The only possible 3077 // values are StyleAlignFlags::{START,END,CENTER,AUTO}. AUTO means there are 3078 // no baseline-aligned items in any track in that axis. 3079 // There is one alignment value for each BaselineSharingGroup. 3080 PerBaseline<StyleAlignFlags> mBaselineSubtreeAlign; 3081 // True if track positions and sizes are final in this axis. 3082 bool mCanResolveLineRangeSize; 3083 // True if this axis has masonry layout. 3084 bool mIsMasonry; 3085 }; 3086 3087 #ifdef DEBUG 3088 void nsGridContainerFrame::Tracks::Dump() const { 3089 const size_t numTracks = mSizes.Length(); 3090 const char* trackName = mAxis == LogicalAxis::Inline ? "column" : "row"; 3091 3092 auto BaselineToStr = [](Maybe<nscoord> aBaseline) { 3093 if (!aBaseline) { 3094 return std::string("not set"); 3095 } 3096 3097 return std::to_string(*aBaseline); 3098 }; 3099 3100 auto CoordToStr = [](nscoord aCoord) { 3101 return aCoord == NS_UNCONSTRAINEDSIZE ? std::string("unconstrained") 3102 : std::to_string(aCoord); 3103 }; 3104 3105 fmt::print(FMT_STRING("{} {} {}{}, track union bits: "), numTracks, 3106 mIsMasonry ? "masonry" : "grid", trackName, 3107 numTracks > 1 ? "s" : ""); 3108 TrackSize::DumpStateBits(mStateUnion); 3109 printf("\n"); 3110 3111 for (uint32_t i = 0; i < numTracks; ++i) { 3112 fmt::print(FMT_STRING(" {} {}: "), trackName, i); 3113 mSizes[i].Dump(); 3114 printf("\n"); 3115 } 3116 3117 fmt::println(FMT_STRING(" first baseline: {}, last baseline: {}"), 3118 BaselineToStr(GetBaseline(0, BaselineSharingGroup::First)), 3119 BaselineToStr(GetBaseline(mBaselines.Length() - 1, 3120 BaselineSharingGroup::Last))); 3121 fmt::println(FMT_STRING(" {} gap: {}, content-box {}-size: {}"), trackName, 3122 CoordToStr(mGridGap), 3123 mAxis == LogicalAxis::Inline ? "inline" : "block", 3124 CoordToStr(mContentBoxSize)); 3125 } 3126 #endif 3127 3128 /** 3129 * Grid data shared by all continuations, owned by the first-in-flow. 3130 * The data is initialized from the first-in-flow's GridReflowInput at 3131 * the end of its reflow. Fragmentation will modify mRows.mSizes - 3132 * the mPosition to remove the row gap at the break boundary, the mState 3133 * by setting the eBreakBefore flag, and mBase is modified when we decide 3134 * to grow a row. mOriginalRowData is setup by the first-in-flow and 3135 * not modified after that. It's used for undoing the changes to mRows. 3136 * mCols, mGridItems, mAbsPosItems are used for initializing the grid 3137 * reflow input for continuations, see GridReflowInput::Initialize below. 3138 */ 3139 struct nsGridContainerFrame::SharedGridData { 3140 SharedGridData() 3141 : mCols(LogicalAxis::Inline), 3142 mRows(LogicalAxis::Block), 3143 mGenerateComputedGridInfo(false) {} 3144 Tracks mCols; 3145 Tracks mRows; 3146 struct RowData { 3147 nscoord mBase; // the original track size 3148 nscoord mGap; // the original gap before a track 3149 }; 3150 nsTArray<RowData> mOriginalRowData; 3151 nsTArray<GridItemInfo> mGridItems; 3152 nsTArray<GridItemInfo> mAbsPosItems; 3153 bool mGenerateComputedGridInfo; 3154 3155 /** 3156 * Only set on the first-in-flow. Continuations will Initialize() their 3157 * GridReflowInput from it. 3158 */ 3159 NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, SharedGridData) 3160 }; 3161 3162 struct MOZ_STACK_CLASS nsGridContainerFrame::GridReflowInput { 3163 GridReflowInput(nsGridContainerFrame* aFrame, const ReflowInput& aRI) 3164 : GridReflowInput(aFrame, *aRI.mRenderingContext, &aRI, 3165 aRI.mStylePosition, aRI.GetWritingMode()) {} 3166 GridReflowInput(nsGridContainerFrame* aFrame, gfxContext& aRC) 3167 : GridReflowInput(aFrame, aRC, nullptr, aFrame->StylePosition(), 3168 aFrame->GetWritingMode()) {} 3169 3170 /** 3171 * Initialize our track sizes and grid item info using the shared 3172 * state from aGridContainerFrame first-in-flow. 3173 */ 3174 void InitializeForContinuation(nsGridContainerFrame* aGridContainerFrame, 3175 nscoord aConsumedBSize) { 3176 MOZ_ASSERT(aGridContainerFrame->GetPrevInFlow(), 3177 "don't call this on the first-in-flow"); 3178 MOZ_ASSERT(mGridItems.IsEmpty() && mAbsPosItems.IsEmpty(), 3179 "shouldn't have any item data yet"); 3180 3181 // Get the SharedGridData from the first-in-flow. Also calculate the number 3182 // of fragments before this so that we can figure out our start row below. 3183 uint32_t fragment = 0; 3184 nsIFrame* firstInFlow = aGridContainerFrame; 3185 for (auto pif = aGridContainerFrame->GetPrevInFlow(); pif; 3186 pif = pif->GetPrevInFlow()) { 3187 ++fragment; 3188 firstInFlow = pif; 3189 } 3190 mSharedGridData = firstInFlow->GetProperty(SharedGridData::Prop()); 3191 MOZ_ASSERT(mSharedGridData, "first-in-flow must have SharedGridData"); 3192 3193 // Find the start row for this fragment and undo breaks after that row 3194 // since the breaks might be different from the last reflow. 3195 auto& rowSizes = mSharedGridData->mRows.mSizes; 3196 const uint32_t numRows = rowSizes.Length(); 3197 mStartRow = numRows; 3198 for (uint32_t row = 0, breakCount = 0; row < numRows; ++row) { 3199 if (rowSizes[row].mState & TrackSize::eBreakBefore) { 3200 if (fragment == ++breakCount) { 3201 mStartRow = row; 3202 mFragBStart = rowSizes[row].mPosition; 3203 // Restore the original size for |row| and grid gaps / state after it. 3204 const auto& origRowData = mSharedGridData->mOriginalRowData; 3205 rowSizes[row].mBase = origRowData[row].mBase; 3206 nscoord prevEndPos = rowSizes[row].mPosition + rowSizes[row].mBase; 3207 while (++row < numRows) { 3208 auto& sz = rowSizes[row]; 3209 const auto& orig = origRowData[row]; 3210 sz.mPosition = prevEndPos + orig.mGap; 3211 sz.mBase = orig.mBase; 3212 sz.mState &= ~TrackSize::eBreakBefore; 3213 prevEndPos = sz.mPosition + sz.mBase; 3214 } 3215 break; 3216 } 3217 } 3218 } 3219 if (mStartRow == numRows || aGridContainerFrame->IsRowMasonry()) { 3220 // All of the grid's rows fit inside of previous grid-container fragments, 3221 // or it's a masonry axis. 3222 mFragBStart = aConsumedBSize; 3223 } 3224 3225 // Copy the shared track state. 3226 // XXX consider temporarily swapping the array elements instead and swapping 3227 // XXX them back after we're done reflowing, for better performance. 3228 // XXX (bug 1252002) 3229 mCols = mSharedGridData->mCols; 3230 mRows = mSharedGridData->mRows; 3231 3232 if (firstInFlow->GetProperty(UsedTrackSizes::Prop())) { 3233 auto* prop = aGridContainerFrame->GetProperty(UsedTrackSizes::Prop()); 3234 if (!prop) { 3235 prop = new UsedTrackSizes(); 3236 aGridContainerFrame->SetProperty(UsedTrackSizes::Prop(), prop); 3237 } 3238 prop->mCanResolveLineRangeSize = {true, true}; 3239 prop->mTrackPlans[LogicalAxis::Inline].Assign(mCols.mSizes); 3240 prop->mTrackPlans[LogicalAxis::Block].Assign(mRows.mSizes); 3241 } 3242 3243 // Copy item data from each child's first-in-flow data in mSharedGridData. 3244 // XXX NOTE: This is O(n^2) in the number of items. (bug 1252186) 3245 mIter.Reset(); 3246 for (; !mIter.AtEnd(); mIter.Next()) { 3247 nsIFrame* child = *mIter; 3248 nsIFrame* childFirstInFlow = child->FirstInFlow(); 3249 DebugOnly<size_t> len = mGridItems.Length(); 3250 for (auto& itemInfo : mSharedGridData->mGridItems) { 3251 if (itemInfo.mFrame == childFirstInFlow) { 3252 auto item = 3253 mGridItems.AppendElement(GridItemInfo(child, itemInfo.mArea)); 3254 // Copy the item's baseline data so that the item's last fragment can 3255 // do 'last baseline' alignment if necessary. 3256 item->mState[LogicalAxis::Block] |= 3257 itemInfo.mState[LogicalAxis::Block] & ItemState::eAllBaselineBits; 3258 item->mState[LogicalAxis::Inline] |= 3259 itemInfo.mState[LogicalAxis::Inline] & 3260 ItemState::eAllBaselineBits; 3261 item->mBaselineOffset[LogicalAxis::Block] = 3262 itemInfo.mBaselineOffset[LogicalAxis::Block]; 3263 item->mBaselineOffset[LogicalAxis::Inline] = 3264 itemInfo.mBaselineOffset[LogicalAxis::Inline]; 3265 item->mState[LogicalAxis::Block] |= 3266 itemInfo.mState[LogicalAxis::Block] & ItemState::eAutoPlacement; 3267 item->mState[LogicalAxis::Inline] |= 3268 itemInfo.mState[LogicalAxis::Inline] & ItemState::eAutoPlacement; 3269 break; 3270 } 3271 } 3272 MOZ_ASSERT(mGridItems.Length() == len + 1, "can't find GridItemInfo"); 3273 } 3274 3275 if (auto* absCB = aGridContainerFrame->GetAbsoluteContainingBlock()) { 3276 // Prepare absolute frames before constructing GridItemInfo. 3277 absCB->PrepareAbsoluteFrames(aGridContainerFrame); 3278 } 3279 // XXX NOTE: This is O(n^2) in the number of abs.pos. items. (bug 1252186) 3280 const nsFrameList& absPosChildren = aGridContainerFrame->GetChildList( 3281 aGridContainerFrame->GetAbsoluteListID()); 3282 for (auto f : absPosChildren) { 3283 nsIFrame* childFirstInFlow = f->FirstInFlow(); 3284 DebugOnly<size_t> len = mAbsPosItems.Length(); 3285 for (auto& itemInfo : mSharedGridData->mAbsPosItems) { 3286 if (itemInfo.mFrame == childFirstInFlow) { 3287 mAbsPosItems.AppendElement(GridItemInfo(f, itemInfo.mArea)); 3288 break; 3289 } 3290 } 3291 MOZ_ASSERT(mAbsPosItems.Length() == len + 1, "can't find GridItemInfo"); 3292 } 3293 3294 // Copy in the computed grid info state bit 3295 if (mSharedGridData->mGenerateComputedGridInfo) { 3296 aGridContainerFrame->AddStateBits(NS_STATE_GRID_COMPUTED_INFO); 3297 } 3298 } 3299 3300 /** 3301 * Calculate our track sizes in the given axis. 3302 */ 3303 void CalculateTrackSizesForAxis(LogicalAxis aAxis, const Grid& aGrid, 3304 nscoord aCBSize, 3305 SizingConstraint aConstraint); 3306 3307 /** 3308 * Invalidate track sizes for the given axis by clearing track sizing bits for 3309 * all grid items and mark the track sizes and positions as needing recompute. 3310 * 3311 * This helper must be called before invoking CalculateTrackSizesForAxis() 3312 * again in aAxis; otherwise, assertions will fire. 3313 */ 3314 void InvalidateTrackSizesForAxis(LogicalAxis aAxis); 3315 3316 /** 3317 * Return the percentage basis for a grid item in its writing-mode based on 3318 * track sizes and the grid area occupied by the grid item. 3319 * 3320 * @param aAxis the axis we're currently calculating track sizes for 3321 */ 3322 LogicalSize PercentageBasisFor(LogicalAxis aAxis, 3323 const GridItemInfo& aGridItem) const; 3324 3325 /** 3326 * Return the containing block for a grid item occupying aArea. 3327 */ 3328 LogicalRect ContainingBlockFor(const GridArea& aArea) const; 3329 3330 /** 3331 * Return the containing block for an abs.pos. grid item occupying aArea. 3332 * Any 'auto' lines in the grid area will be aligned with grid container 3333 * containing block on that side. 3334 * @param aGridOrigin the origin of the grid 3335 * @param aGridCB the grid container containing block (its padding area) 3336 */ 3337 LogicalRect ContainingBlockForAbsPos(const GridArea& aArea, 3338 const LogicalPoint& aGridOrigin, 3339 const LogicalRect& aGridCB) const; 3340 3341 /** 3342 * Apply `align/justify-content` alignment in our masonry axis. 3343 * This aligns the "masonry box" within our content box size. 3344 */ 3345 void AlignJustifyContentInMasonryAxis(nscoord aMasonryBoxSize, 3346 nscoord aContentBoxSize); 3347 /** 3348 * Apply `align/justify-tracks` alignment in our masonry axis. 3349 */ 3350 void AlignJustifyTracksInMasonryAxis(const LogicalSize& aContentSize, 3351 const nsSize& aContainerSize); 3352 3353 // Recursive helper for CollectSubgridItemsForAxis(). 3354 static void CollectSubgridItemsForAxisHelper( 3355 LogicalAxis aAxis, WritingMode aContainerWM, 3356 const LineRange& aRangeInAxis, const LineRange& aRangeInOppositeAxis, 3357 const GridItemInfo& aItem, const nsTArray<GridItemInfo>& aItems, 3358 nsTArray<GridItemInfo>& aResult) { 3359 const auto oppositeAxis = GetOrthogonalAxis(aAxis); 3360 bool itemIsSubgridInOppositeAxis = aItem.IsSubgrid(oppositeAxis); 3361 auto subgridWM = aItem.mFrame->GetWritingMode(); 3362 bool isOrthogonal = subgridWM.IsOrthogonalTo(aContainerWM); 3363 bool isSameDirInAxis = 3364 subgridWM.ParallelAxisStartsOnSameSide(aAxis, aContainerWM); 3365 bool isSameDirInOppositeAxis = 3366 subgridWM.ParallelAxisStartsOnSameSide(oppositeAxis, aContainerWM); 3367 if (isOrthogonal) { 3368 // We'll Transpose the area below so these needs to be transposed as well. 3369 std::swap(isSameDirInAxis, isSameDirInOppositeAxis); 3370 } 3371 uint32_t offsetInAxis = aRangeInAxis.mStart; 3372 uint32_t gridEndInAxis = aRangeInAxis.Extent(); 3373 uint32_t offsetInOppositeAxis = aRangeInOppositeAxis.mStart; 3374 uint32_t gridEndInOppositeAxis = aRangeInOppositeAxis.Extent(); 3375 for (const auto& subgridItem : aItems) { 3376 auto newItem = aResult.AppendElement( 3377 isOrthogonal ? subgridItem.Transpose() : subgridItem); 3378 if (MOZ_UNLIKELY(!isSameDirInAxis)) { 3379 newItem->ReverseDirection(aAxis, gridEndInAxis); 3380 } 3381 newItem->mArea.LineRangeForAxis(aAxis).Translate(offsetInAxis); 3382 if (itemIsSubgridInOppositeAxis) { 3383 if (MOZ_UNLIKELY(!isSameDirInOppositeAxis)) { 3384 newItem->ReverseDirection(oppositeAxis, gridEndInOppositeAxis); 3385 } 3386 LineRange& range = newItem->mArea.LineRangeForAxis(oppositeAxis); 3387 range.Translate(offsetInOppositeAxis); 3388 } 3389 if (newItem->IsSubgrid(aAxis)) { 3390 auto* subgrid = 3391 subgridItem.SubgridFrame()->GetProperty(Subgrid::Prop()); 3392 CollectSubgridItemsForAxisHelper( 3393 aAxis, aContainerWM, newItem->mArea.LineRangeForAxis(aAxis), 3394 newItem->mArea.LineRangeForAxis(oppositeAxis), *newItem, 3395 subgrid->mGridItems, aResult); 3396 } 3397 } 3398 } 3399 3400 // Copy all descendant items from all our subgrid children that are subgridded 3401 // in aAxis recursively into aResult. All item grid area's and state are 3402 // translated to our coordinates. 3403 void CollectSubgridItemsForAxis(LogicalAxis aAxis, 3404 nsTArray<GridItemInfo>& aResult) const { 3405 for (const auto& item : mGridItems) { 3406 if (item.IsSubgrid(aAxis)) { 3407 const auto oppositeAxis = GetOrthogonalAxis(aAxis); 3408 auto* subgrid = item.SubgridFrame()->GetProperty(Subgrid::Prop()); 3409 CollectSubgridItemsForAxisHelper( 3410 aAxis, mWM, item.mArea.LineRangeForAxis(aAxis), 3411 item.mArea.LineRangeForAxis(oppositeAxis), item, 3412 subgrid->mGridItems, aResult); 3413 } 3414 } 3415 } 3416 3417 /** 3418 * Recursive helper for CopyBaselineMetricsToSubgridItems(). 3419 * 3420 * @param aAxis The LogicalAxis for the axis whose baseline metrics we're 3421 * copying here (with respect to the outermost parent grid's 3422 * writing mode). 3423 * @param aContainerWM The writing mode of that outermost parent grid. 3424 * @param aSubgridFrame The subgrid whose subgrid-items we're considering 3425 * in this recursive traversal (whose items we're copying over 3426 * baseline-alignment metrics for). 3427 * @param aContainerGridItems The outermost parent grid's array of 3428 * GridItemInfo objects. (The final portion of this array is 3429 * all for subgrid items, and that's the portion that we're 3430 * recursively iterating over.) 3431 * @param aContainerGridItemsIdx [in/out] The index for the item that we're 3432 * currently considering in aContainerGridItemsIdx. When 3433 * this function returns, this will be the index just beyond the 3434 * last item that we handled here, i.e. the index of the next 3435 * item to be handled. 3436 */ 3437 static void CopyBaselineMetricsToSubgridItemsHelper( 3438 LogicalAxis aAxis, WritingMode aContainerWM, nsIFrame* aSubgridFrame, 3439 const nsTArray<GridItemInfo>& aContainerGridItems, 3440 size_t& aContainerGridItemsIdx) { 3441 // Get the canonical GridItemInfo structs for the grid items that live 3442 // inside of aSubgridFrame: 3443 Subgrid* subgridProp = aSubgridFrame->GetProperty(Subgrid::Prop()); 3444 nsTArray<GridItemInfo>& subgridItems = subgridProp->mGridItems; 3445 3446 // Use aSubgridFrame's writing-mode to determine subgridAxis. 3447 // Grids & subgrids store various data on a per-LogicalAxis basis, with 3448 // respect to their own WritingMode. Here, subgridAxis is aSubgridFrame's 3449 // axis that maps to the same physical axis that aAxis does for the 3450 // outermost parent grid. 3451 auto subgridWM = aSubgridFrame->GetWritingMode(); 3452 bool isOrthogonal = subgridWM.IsOrthogonalTo(aContainerWM); 3453 LogicalAxis subgridAxis = isOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis; 3454 3455 // Do a parallel walk through (1) subgridItems and (2) the portion of 3456 // aContainerGridItems that starts at offset aContainerGridItems, 3457 // descending to traverse child subgrids own items as we encounter them in 3458 // subgridItems. We expect to have an exact correspondence, because this 3459 // is precisely how we built up this portion of aContainerGridItems in 3460 // CollectSubgridItemsForAxis. (But if we happen to overstep the end of an 3461 // array, or find a GridItemInfo for a frame that we don't expect, we 3462 // gracefully bail out.) 3463 for (auto& subgridItem : subgridItems) { 3464 if (MOZ_UNLIKELY(aContainerGridItemsIdx >= 3465 aContainerGridItems.Length())) { 3466 // We failed to make the same traversal as CollectSubgridItemsForAxis; 3467 // whoops! This shouldn't happen; but if it does, we gracefully bail 3468 // out, instead of crashing. 3469 MOZ_ASSERT_UNREACHABLE("Out-of-bounds aContainerGridItemsIdx"); 3470 return; 3471 } 3472 const auto& itemFromContainer = 3473 aContainerGridItems[aContainerGridItemsIdx]; 3474 aContainerGridItemsIdx++; 3475 3476 if (MOZ_UNLIKELY(subgridItem.mFrame != itemFromContainer.mFrame)) { 3477 // We failed to make the same traversal as CollectSubgridItemsForAxis; 3478 // whoops! This shouldn't happen; but if it does, we gracefully bail 3479 // out, instead of copying baseline-alignment data for the wrong frame. 3480 MOZ_ASSERT_UNREACHABLE("Found unexpected frame during traversal"); 3481 return; 3482 } 3483 3484 // This pattern of bits will be truthy if the item is baseline-aligned in 3485 // this axis (in which case the exact pattern of bits will have some 3486 // additional significance that doesn't matter here, but we do need to 3487 // copy it over). 3488 const auto baselineStateBits = 3489 itemFromContainer.mState[aAxis] & ItemState::eAllBaselineBits; 3490 3491 if (subgridItem.IsSubgrid(subgridAxis)) { 3492 // This item is in fact a nested subgrid. It shouldn't itself be 3493 // baseline-aligned, but we need to make a recursive call to copy 3494 // baseline metrics to its items. 3495 MOZ_ASSERT(!baselineStateBits, 3496 "subgrids themselves can't be baseline-aligned " 3497 "(or self-aligned in any way) in their subgrid axis"); 3498 CopyBaselineMetricsToSubgridItemsHelper( 3499 aAxis, aContainerWM, subgridItem.SubgridFrame(), 3500 aContainerGridItems, aContainerGridItemsIdx); 3501 } else if (baselineStateBits) { 3502 // This item is a baseline-aligned grid item (in the subgrid that we're 3503 // traversing). Copy over its baseline metrics. 3504 subgridItem.mState[subgridAxis] |= baselineStateBits; 3505 subgridItem.mBaselineOffset[subgridAxis] = 3506 itemFromContainer.mBaselineOffset[aAxis]; 3507 } 3508 } 3509 } 3510 3511 /** 3512 * This function here is responsible for propagating baseline-alignment 3513 * metrics for subgrid-items from mGridItems over to the "canonical" 3514 * GridItemInfo structs for those grid items (which live on the subgrid that 3515 * owns them). The outermost parent grid *computes* those metrics as part of 3516 * doing track sizing, but it does this using *temporary* GridItemInfo 3517 * objects for any grid items that live in subgrids (aka subgrid items). So 3518 * that's why we need to rescue this baseline-alignment information before 3519 * those temporary objects are discarded. 3520 * 3521 * (The temporary subgrid-items all live at the end of mGridItems; they were 3522 * appended there by CollectSubgridItemsForAxis(). So, it's important that 3523 * we perform the exact same traversal that CollectSubgridItemsForAxis() did, 3524 * in order to properly match up the temporary & canonical GridItemInfo 3525 * objects for these subgrid items.) 3526 */ 3527 // traversal that CollectSubgridItemsForAxis (and its recursive helper) does. 3528 void CopyBaselineMetricsToSubgridItems(LogicalAxis aAxis, 3529 size_t aOriginalLength) { 3530 MOZ_ASSERT(aOriginalLength <= mGridItems.Length(), 3531 "aOriginalLength is the length that mGridItems had *before* we " 3532 "appended temporary copies of subgrid items to it, so it's not " 3533 "possible for it to be more than the current length"); 3534 3535 // This index 'subgridItemIdx' traverses the final portion of mGridItems, 3536 // the portion that currently has temporary GridItemInfo structs that we 3537 // built for the items that live in our subgrids. (Our caller is about to 3538 // discard this temporary portion of mGridItems, and we're trying to 3539 // transfer some baseline-alignment data to the canonical GridItemInfo 3540 // structs before that happens.) 3541 // 3542 // Our recursive helper updates subgridItemIdx internally. When this index 3543 // reaches mGridItems.Length(), we can stop looping; that means we've 3544 // finished copying out all the data from these temporary structs. 3545 size_t subgridItemIdx = aOriginalLength; 3546 3547 for (size_t i = 0; 3548 (i < aOriginalLength && subgridItemIdx < mGridItems.Length()); i++) { 3549 const auto& item = mGridItems[i]; 3550 if (item.IsSubgrid(aAxis)) { 3551 CopyBaselineMetricsToSubgridItemsHelper(aAxis, mWM, item.SubgridFrame(), 3552 mGridItems, subgridItemIdx); 3553 } 3554 } 3555 } 3556 3557 Tracks& TracksFor(LogicalAxis aAxis) { 3558 return aAxis == LogicalAxis::Block ? mRows : mCols; 3559 } 3560 const Tracks& TracksFor(LogicalAxis aAxis) const { 3561 return aAxis == LogicalAxis::Block ? mRows : mCols; 3562 } 3563 3564 CSSOrderAwareFrameIterator mIter; 3565 const nsStylePosition* const mGridStyle; 3566 Tracks mCols; 3567 Tracks mRows; 3568 TrackSizingFunctions mColFunctions; 3569 TrackSizingFunctions mRowFunctions; 3570 /** 3571 * Info about each (normal flow) grid item. 3572 */ 3573 nsTArray<GridItemInfo> mGridItems; 3574 /** 3575 * Info about each grid-aligned abs.pos. child. 3576 */ 3577 nsTArray<GridItemInfo> mAbsPosItems; 3578 3579 /** 3580 * @note mReflowInput may be null when using the 2nd ctor above. In this case 3581 * we'll construct a dummy parent reflow input if we need it to calculate 3582 * min/max-content contributions when sizing tracks. 3583 */ 3584 const ReflowInput* const mReflowInput; 3585 gfxContext& mRenderingContext; 3586 nsGridContainerFrame* const mFrame; 3587 /** [weak] owned by mFrame's first-in-flow. */ 3588 SharedGridData* mSharedGridData = nullptr; 3589 /** Computed border+padding with mSkipSides applied. */ 3590 LogicalMargin mBorderPadding; 3591 /** 3592 * BStart of this fragment in "grid space" (i.e. the concatenation of content 3593 * areas of all fragments). Equal to mRows.mSizes[mStartRow].mPosition, 3594 * or, if this fragment starts after the last row, the ConsumedBSize(). 3595 */ 3596 nscoord mFragBStart = 0; 3597 /** The start row for this fragment. */ 3598 uint32_t mStartRow = 0; 3599 /** 3600 * The start row for the next fragment, if any. If mNextFragmentStartRow == 3601 * mStartRow then there are no rows in this fragment. 3602 */ 3603 uint32_t mNextFragmentStartRow = 0; 3604 /** Our tentative ApplySkipSides bits. */ 3605 LogicalSides mSkipSides; 3606 const WritingMode mWM; 3607 /** Initialized lazily, when we find the fragmentainer. */ 3608 bool mInFragmentainer = false; 3609 /** Set when the grid itself is having its intrinsic size measured. */ 3610 bool mIsGridIntrinsicSizing = false; 3611 3612 private: 3613 GridReflowInput(nsGridContainerFrame* aFrame, gfxContext& aRenderingContext, 3614 const ReflowInput* aReflowInput, 3615 const nsStylePosition* aGridStyle, const WritingMode& aWM) 3616 : mIter(aFrame, FrameChildListID::Principal), 3617 mGridStyle(aGridStyle), 3618 mCols(LogicalAxis::Inline), 3619 mRows(LogicalAxis::Block), 3620 mColFunctions(mGridStyle->mGridTemplateColumns, 3621 mGridStyle->mGridAutoColumns, aFrame->IsColSubgrid()), 3622 mRowFunctions(mGridStyle->mGridTemplateRows, mGridStyle->mGridAutoRows, 3623 aFrame->IsRowSubgrid()), 3624 mReflowInput(aReflowInput), 3625 mRenderingContext(aRenderingContext), 3626 mFrame(aFrame), 3627 mBorderPadding(aWM), 3628 mSkipSides(aFrame->GetWritingMode()), 3629 mWM(aWM) { 3630 MOZ_ASSERT(!aReflowInput || aReflowInput->mFrame == mFrame); 3631 if (aReflowInput) { 3632 mBorderPadding = aReflowInput->ComputedLogicalBorderPadding(mWM); 3633 mSkipSides = aFrame->PreReflowBlockLevelLogicalSkipSides(); 3634 mBorderPadding.ApplySkipSides(mSkipSides); 3635 } 3636 mCols.mIsMasonry = aFrame->IsColMasonry(); 3637 mRows.mIsMasonry = aFrame->IsRowMasonry(); 3638 MOZ_ASSERT(!(mCols.mIsMasonry && mRows.mIsMasonry), 3639 "can't have masonry layout in both axes"); 3640 } 3641 }; 3642 3643 using GridReflowInput = nsGridContainerFrame::GridReflowInput; 3644 3645 /** 3646 * The Grid implements grid item placement and the state of the grid - 3647 * the size of the explicit/implicit grid, which cells are occupied etc. 3648 */ 3649 struct MOZ_STACK_CLASS nsGridContainerFrame::Grid { 3650 explicit Grid(const Grid* aParentGrid = nullptr) : mParentGrid(aParentGrid) {} 3651 3652 /** 3653 * Place all child frames into the grid and expand the (implicit) grid as 3654 * needed. The allocated GridAreas are stored in the GridAreaProperty 3655 * frame property on the child frame. 3656 * @param aRepeatSizing the container's [min-|max-]*size - used to determine 3657 * the number of repeat(auto-fill/fit) tracks. 3658 */ 3659 void PlaceGridItems(GridReflowInput& aGridRI, 3660 const RepeatTrackSizingInput& aRepeatSizing); 3661 3662 void SubgridPlaceGridItems(GridReflowInput& aParentGridRI, Grid* aParentGrid, 3663 const GridItemInfo& aGridItem); 3664 3665 /** 3666 * As above but for an abs.pos. child. Any 'auto' lines will be represented 3667 * by kAutoLine in the LineRange result. 3668 * @param aGridStart the first line in the final, but untranslated grid 3669 * @param aGridEnd the last line in the final, but untranslated grid 3670 */ 3671 LineRange ResolveAbsPosLineRange(const StyleGridLine& aStart, 3672 const StyleGridLine& aEnd, 3673 const LineNameMap& aNameMap, 3674 LogicalAxis aAxis, uint32_t aExplicitGridEnd, 3675 int32_t aGridStart, int32_t aGridEnd, 3676 const nsStylePosition* aStyle); 3677 3678 /** 3679 * Return a GridArea for abs.pos. item with non-auto lines placed at 3680 * a definite line (1-based) with placement errors resolved. One or both 3681 * positions may still be 'auto'. 3682 * @param aChild the abs.pos. grid item to place 3683 * @param aStyle the StylePosition() for the grid container 3684 */ 3685 GridArea PlaceAbsPos(nsIFrame* aChild, const LineNameMap& aColLineNameMap, 3686 const LineNameMap& aRowLineNameMap, 3687 const nsStylePosition* aStyle); 3688 3689 /** 3690 * Find the first column in row aLockedRow starting at aStartCol where aArea 3691 * could be placed without overlapping other items. The returned column may 3692 * cause aArea to overflow the current implicit grid bounds if placed there. 3693 */ 3694 uint32_t FindAutoCol(uint32_t aStartCol, uint32_t aLockedRow, 3695 const GridArea* aArea) const; 3696 3697 /** 3698 * Place aArea in the first column (in row aArea->mRows.mStart) starting at 3699 * aStartCol without overlapping other items. The resulting aArea may 3700 * overflow the current implicit grid bounds. 3701 * @param aClampMaxColLine the maximum allowed column line number (zero-based) 3702 * Pre-condition: aArea->mRows.IsDefinite() is true. 3703 * Post-condition: aArea->IsDefinite() is true. 3704 */ 3705 void PlaceAutoCol(uint32_t aStartCol, GridArea* aArea, 3706 uint32_t aClampMaxColLine) const; 3707 3708 /** 3709 * Find the first row in column aLockedCol starting at aStartRow where aArea 3710 * could be placed without overlapping other items. The returned row may 3711 * cause aArea to overflow the current implicit grid bounds if placed there. 3712 */ 3713 uint32_t FindAutoRow(uint32_t aLockedCol, uint32_t aStartRow, 3714 const GridArea* aArea) const; 3715 3716 /** 3717 * Place aArea in the first row (in column aArea->mCols.mStart) starting at 3718 * aStartRow without overlapping other items. The resulting aArea may 3719 * overflow the current implicit grid bounds. 3720 * @param aClampMaxRowLine the maximum allowed row line number (zero-based) 3721 * Pre-condition: aArea->mCols.IsDefinite() is true. 3722 * Post-condition: aArea->IsDefinite() is true. 3723 */ 3724 void PlaceAutoRow(uint32_t aStartRow, GridArea* aArea, 3725 uint32_t aClampMaxRowLine) const; 3726 3727 /** 3728 * Place aArea in the first column starting at aStartCol,aStartRow without 3729 * causing it to overlap other items or overflow mGridColEnd. 3730 * If there's no such column in aStartRow, continue in position 1,aStartRow+1. 3731 * @param aClampMaxColLine the maximum allowed column line number (zero-based) 3732 * @param aClampMaxRowLine the maximum allowed row line number (zero-based) 3733 * Pre-condition: aArea->mCols.IsAuto() && aArea->mRows.IsAuto() is true. 3734 * Post-condition: aArea->IsDefinite() is true. 3735 */ 3736 void PlaceAutoAutoInRowOrder(uint32_t aStartCol, uint32_t aStartRow, 3737 GridArea* aArea, uint32_t aClampMaxColLine, 3738 uint32_t aClampMaxRowLine) const; 3739 3740 /** 3741 * Place aArea in the first row starting at aStartCol,aStartRow without 3742 * causing it to overlap other items or overflow mGridRowEnd. 3743 * If there's no such row in aStartCol, continue in position aStartCol+1,1. 3744 * @param aClampMaxColLine the maximum allowed column line number (zero-based) 3745 * @param aClampMaxRowLine the maximum allowed row line number (zero-based) 3746 * Pre-condition: aArea->mCols.IsAuto() && aArea->mRows.IsAuto() is true. 3747 * Post-condition: aArea->IsDefinite() is true. 3748 */ 3749 void PlaceAutoAutoInColOrder(uint32_t aStartCol, uint32_t aStartRow, 3750 GridArea* aArea, uint32_t aClampMaxColLine, 3751 uint32_t aClampMaxRowLine) const; 3752 3753 /** 3754 * Return aLine if it's inside the aMin..aMax range (inclusive), 3755 * otherwise return kAutoLine. 3756 */ 3757 static int32_t AutoIfOutside(int32_t aLine, int32_t aMin, int32_t aMax) { 3758 MOZ_ASSERT(aMin <= aMax); 3759 if (aLine < aMin || aLine > aMax) { 3760 return kAutoLine; 3761 } 3762 return aLine; 3763 } 3764 3765 /** 3766 * Inflate the implicit grid to include aArea. 3767 * @param aArea may be definite or auto 3768 */ 3769 void InflateGridFor(const GridArea& aArea) { 3770 mGridColEnd = std::max(mGridColEnd, aArea.mCols.HypotheticalEnd()); 3771 mGridRowEnd = std::max(mGridRowEnd, aArea.mRows.HypotheticalEnd()); 3772 MOZ_ASSERT(mGridColEnd <= kTranslatedMaxLine && 3773 mGridRowEnd <= kTranslatedMaxLine); 3774 } 3775 3776 /** 3777 * Calculates the empty tracks in a repeat(auto-fit). 3778 * @param aOutNumEmptyLines Outputs the number of tracks which are empty. 3779 * @param aSizingFunctions Sizing functions for the relevant axis. 3780 * @param aNumGridLines Number of grid lines for the relevant axis. 3781 * @param aIsEmptyFunc Functor to check if a cell is empty. This should be 3782 * mCellMap.IsColEmpty or mCellMap.IsRowEmpty, depending on the axis. 3783 */ 3784 template <typename IsEmptyFuncT> 3785 static Maybe<nsTArray<uint32_t>> CalculateAdjustForAutoFitElements( 3786 uint32_t* aOutNumEmptyTracks, TrackSizingFunctions& aSizingFunctions, 3787 uint32_t aNumGridLines, IsEmptyFuncT aIsEmptyFunc); 3788 3789 /** 3790 * Return a line number for (non-auto) aLine, per: 3791 * https://drafts.csswg.org/css-grid-2/#line-placement 3792 * @param aLine style data for the line (must be non-auto) 3793 * @param aNth a number of lines to find from aFromIndex, negative if the 3794 * search should be in reverse order. In the case aLine has 3795 * a specified line name, it's permitted to pass in zero which 3796 * will be treated as one. 3797 * @param aFromIndex the zero-based index to start counting from 3798 * @param aLineNameList the explicit named lines 3799 * @param aSide the axis+edge we're resolving names for (e.g. if we're 3800 resolving a grid-row-start line, pass LogicalSide::BStart) 3801 * @param aExplicitGridEnd the last line in the explicit grid 3802 * @param aStyle the StylePosition() for the grid container 3803 * @return a definite line (1-based), clamped to 3804 * the mClampMinLine..mClampMaxLine range 3805 */ 3806 int32_t ResolveLine(const StyleGridLine& aLine, int32_t aNth, 3807 uint32_t aFromIndex, const LineNameMap& aNameMap, 3808 LogicalSide aSide, uint32_t aExplicitGridEnd, 3809 const nsStylePosition* aStyle); 3810 3811 /** 3812 * Helper method for ResolveLineRange. 3813 * @see ResolveLineRange 3814 * @return a pair (start,end) of lines 3815 */ 3816 typedef std::pair<int32_t, int32_t> LinePair; 3817 LinePair ResolveLineRangeHelper(const StyleGridLine& aStart, 3818 const StyleGridLine& aEnd, 3819 const LineNameMap& aNameMap, 3820 LogicalAxis aAxis, uint32_t aExplicitGridEnd, 3821 const nsStylePosition* aStyle); 3822 3823 /** 3824 * Return a LineRange based on the given style data. Non-auto lines 3825 * are resolved to a definite line number (1-based) per: 3826 * https://drafts.csswg.org/css-grid-2/#line-placement 3827 * with placement errors corrected per: 3828 * https://drafts.csswg.org/css-grid-2/#grid-placement-errors 3829 * @param aStyle the StylePosition() for the grid container 3830 * @param aStart style data for the start line 3831 * @param aEnd style data for the end line 3832 * @param aLineNameList the explicit named lines 3833 * @param aAxis the axis we're resolving names in 3834 * @param aExplicitGridEnd the last line in the explicit grid 3835 * @param aStyle the StylePosition() for the grid container 3836 */ 3837 LineRange ResolveLineRange(const StyleGridLine& aStart, 3838 const StyleGridLine& aEnd, 3839 const LineNameMap& aNameMap, LogicalAxis aAxis, 3840 uint32_t aExplicitGridEnd, 3841 const nsStylePosition* aStyle); 3842 3843 /** 3844 * Return a GridArea with non-auto lines placed at a definite line (1-based) 3845 * with placement errors resolved. One or both positions may still 3846 * be 'auto'. 3847 * @param aChild the grid item 3848 * @param aStyle the StylePosition() for the grid container 3849 */ 3850 GridArea PlaceDefinite(nsIFrame* aChild, const LineNameMap& aColLineNameMap, 3851 const LineNameMap& aRowLineNameMap, 3852 const nsStylePosition* aStyle); 3853 3854 bool HasImplicitNamedArea(nsAtom* aName) const { 3855 return mAreas && mAreas->has(aName); 3856 } 3857 3858 // Return true if aString ends in aSuffix and has at least one character 3859 // before the suffix. Assign aIndex to where the suffix starts. 3860 static bool IsNameWithSuffix(nsAtom* aString, const nsString& aSuffix, 3861 uint32_t* aIndex) { 3862 if (StringEndsWith(nsDependentAtomString(aString), aSuffix)) { 3863 *aIndex = aString->GetLength() - aSuffix.Length(); 3864 return *aIndex != 0; 3865 } 3866 return false; 3867 } 3868 3869 static bool IsNameWithEndSuffix(nsAtom* aString, uint32_t* aIndex) { 3870 return IsNameWithSuffix(aString, u"-end"_ns, aIndex); 3871 } 3872 3873 static bool IsNameWithStartSuffix(nsAtom* aString, uint32_t* aIndex) { 3874 return IsNameWithSuffix(aString, u"-start"_ns, aIndex); 3875 } 3876 3877 // Return the relevant parent LineNameMap for the given subgrid axis aAxis. 3878 const LineNameMap* ParentLineMapForAxis(bool aIsOrthogonal, 3879 LogicalAxis aAxis) const { 3880 if (!mParentGrid) { 3881 return nullptr; 3882 } 3883 bool isRows = aIsOrthogonal == (aAxis == LogicalAxis::Inline); 3884 return isRows ? mParentGrid->mRowNameMap : mParentGrid->mColNameMap; 3885 } 3886 3887 void SetLineMaps(const LineNameMap* aColNameMap, 3888 const LineNameMap* aRowNameMap) { 3889 mColNameMap = aColNameMap; 3890 mRowNameMap = aRowNameMap; 3891 } 3892 3893 /** 3894 * A CellMap holds state for each cell in the grid. 3895 * It's row major. It's sparse in the sense that it only has enough rows to 3896 * cover the last row that has a grid item. Each row only has enough entries 3897 * to cover columns that are occupied *on that row*, i.e. it's not a full 3898 * matrix covering the entire implicit grid. An absent Cell means that it's 3899 * unoccupied by any grid item. 3900 */ 3901 struct CellMap { 3902 struct Cell { 3903 constexpr Cell() : mIsOccupied(false) {} 3904 bool mIsOccupied : 1; 3905 }; 3906 3907 void Fill(const GridArea& aGridArea) { 3908 MOZ_ASSERT(aGridArea.IsDefinite()); 3909 MOZ_ASSERT(aGridArea.mRows.mStart < aGridArea.mRows.mEnd); 3910 MOZ_ASSERT(aGridArea.mCols.mStart < aGridArea.mCols.mEnd); 3911 const auto numRows = aGridArea.mRows.mEnd; 3912 const auto numCols = aGridArea.mCols.mEnd; 3913 mCells.EnsureLengthAtLeast(numRows); 3914 for (auto i = aGridArea.mRows.mStart; i < numRows; ++i) { 3915 nsTArray<Cell>& cellsInRow = mCells[i]; 3916 cellsInRow.EnsureLengthAtLeast(numCols); 3917 for (auto j = aGridArea.mCols.mStart; j < numCols; ++j) { 3918 cellsInRow[j].mIsOccupied = true; 3919 } 3920 } 3921 } 3922 3923 uint32_t IsEmptyCol(uint32_t aCol) const { 3924 for (auto& row : mCells) { 3925 if (aCol < row.Length() && row[aCol].mIsOccupied) { 3926 return false; 3927 } 3928 } 3929 return true; 3930 } 3931 uint32_t IsEmptyRow(uint32_t aRow) const { 3932 if (aRow >= mCells.Length()) { 3933 return true; 3934 } 3935 for (const Cell& cell : mCells[aRow]) { 3936 if (cell.mIsOccupied) { 3937 return false; 3938 } 3939 } 3940 return true; 3941 } 3942 #ifdef DEBUG 3943 void Dump() const { 3944 const size_t numRows = mCells.Length(); 3945 for (size_t i = 0; i < numRows; ++i) { 3946 const nsTArray<Cell>& cellsInRow = mCells[i]; 3947 const size_t numCols = cellsInRow.Length(); 3948 printf("%lu:\t", (unsigned long)i + 1); 3949 for (size_t j = 0; j < numCols; ++j) { 3950 printf(cellsInRow[j].mIsOccupied ? "X " : ". "); 3951 } 3952 printf("\n"); 3953 } 3954 } 3955 #endif 3956 3957 nsTArray<nsTArray<Cell>> mCells; 3958 }; 3959 3960 /** 3961 * State for each cell in the grid. 3962 */ 3963 CellMap mCellMap; 3964 /** 3965 * @see HasImplicitNamedArea. 3966 */ 3967 ImplicitNamedAreas* mAreas; 3968 /** 3969 * The last column grid line (1-based) in the explicit grid. 3970 * (i.e. the number of explicit columns + 1) 3971 */ 3972 uint32_t mExplicitGridColEnd; 3973 /** 3974 * The last row grid line (1-based) in the explicit grid. 3975 * (i.e. the number of explicit rows + 1) 3976 */ 3977 uint32_t mExplicitGridRowEnd; 3978 // Same for the implicit grid, except these become zero-based after 3979 // resolving definite lines. 3980 uint32_t mGridColEnd; 3981 uint32_t mGridRowEnd; 3982 3983 /** 3984 * Offsets from the start of the implicit grid to the start of the translated 3985 * explicit grid. They are zero if there are no implicit lines before 1,1. 3986 * e.g. "grid-column: span 3 / 1" makes mExplicitGridOffsetCol = 3 and the 3987 * corresponding GridArea::mCols will be 0 / 3 in the zero-based translated 3988 * grid. 3989 */ 3990 uint32_t mExplicitGridOffsetCol; 3991 uint32_t mExplicitGridOffsetRow; 3992 3993 /** 3994 * Our parent grid if any. 3995 */ 3996 const Grid* mParentGrid; 3997 3998 /** 3999 * Our LineNameMaps. 4000 */ 4001 const LineNameMap* mColNameMap; 4002 const LineNameMap* mRowNameMap; 4003 }; 4004 4005 /** 4006 * Compute margin+border+padding for aGridItem.mFrame (a subgrid) and store it 4007 * on its Subgrid property (and return that property). 4008 * aPercentageBasis is in the grid item's writing-mode. 4009 */ 4010 static Subgrid* SubgridComputeMarginBorderPadding( 4011 const GridItemInfo& aGridItem, const LogicalSize& aPercentageBasis) { 4012 auto* subgridFrame = aGridItem.SubgridFrame(); 4013 auto cbWM = aGridItem.mFrame->GetParent()->GetWritingMode(); 4014 auto* subgrid = subgridFrame->GetProperty(Subgrid::Prop()); 4015 auto wm = subgridFrame->GetWritingMode(); 4016 auto pmPercentageBasis = cbWM.IsOrthogonalTo(wm) ? aPercentageBasis.BSize(wm) 4017 : aPercentageBasis.ISize(wm); 4018 SizeComputationInput sz(subgridFrame, nullptr, cbWM, pmPercentageBasis); 4019 subgrid->mMarginBorderPadding = 4020 sz.ComputedLogicalMargin(cbWM) + sz.ComputedLogicalBorderPadding(cbWM); 4021 if (aGridItem.mFrame == subgridFrame) { 4022 return subgrid; 4023 } 4024 4025 bool scroller = false; 4026 nsIFrame* outerFrame = nullptr; 4027 if (ScrollContainerFrame* scrollContainerFrame = 4028 aGridItem.mFrame->GetScrollTargetFrame()) { 4029 scroller = true; 4030 outerFrame = scrollContainerFrame; 4031 } 4032 4033 if (outerFrame) { 4034 MOZ_ASSERT(sz.ComputedLogicalMargin(cbWM) == LogicalMargin(cbWM) && 4035 sz.ComputedLogicalBorder(cbWM) == LogicalMargin(cbWM), 4036 "A scrolled inner frame / button content frame " 4037 "should not have any margin or border / padding!"); 4038 4039 // Add the margin and border from the (outer) frame. Padding is factored-in 4040 // for scrollers already (except for the scrollbar gutter), but not for 4041 // button-content. 4042 SizeComputationInput szOuterFrame(outerFrame, nullptr, cbWM, 4043 pmPercentageBasis); 4044 subgrid->mMarginBorderPadding += szOuterFrame.ComputedLogicalMargin(cbWM) + 4045 szOuterFrame.ComputedLogicalBorder(cbWM); 4046 if (scroller) { 4047 nsMargin ssz = static_cast<ScrollContainerFrame*>(outerFrame) 4048 ->IntrinsicScrollbarGutterSize(); 4049 subgrid->mMarginBorderPadding += LogicalMargin(cbWM, ssz); 4050 } else { 4051 subgrid->mMarginBorderPadding += 4052 szOuterFrame.ComputedLogicalPadding(cbWM); 4053 } 4054 } 4055 4056 if (nsFieldSetFrame* f = do_QueryFrame(aGridItem.mFrame)) { 4057 const auto* inner = f->GetInner(); 4058 auto wm = inner->GetWritingMode(); 4059 LogicalPoint pos = inner->GetLogicalPosition(aGridItem.mFrame->GetSize()); 4060 // The legend is always on the BStart side and it inflates the fieldset's 4061 // "border area" size. The inner frame's b-start pos equals that size. 4062 LogicalMargin offsets(wm, pos.B(wm), 0, 0, 0); 4063 subgrid->mMarginBorderPadding += offsets.ConvertTo(cbWM, wm); 4064 } 4065 4066 return subgrid; 4067 } 4068 4069 static void CopyUsedTrackSizes(TrackPlan& aResult, 4070 const nsGridContainerFrame* aUsedTrackSizesFrame, 4071 const UsedTrackSizes* aUsedTrackSizes, 4072 const nsGridContainerFrame* aSubgridFrame, 4073 const Subgrid* aSubgrid, 4074 LogicalAxis aSubgridAxis) { 4075 MOZ_ASSERT(aSubgridFrame->ParentGridContainerForSubgrid() == 4076 aUsedTrackSizesFrame); 4077 aResult.SetLength(aSubgridAxis == LogicalAxis::Inline 4078 ? aSubgrid->mGridColEnd 4079 : aSubgrid->mGridRowEnd); 4080 auto parentAxis = 4081 aSubgrid->mIsOrthogonal ? GetOrthogonalAxis(aSubgridAxis) : aSubgridAxis; 4082 const auto& parentSizes = aUsedTrackSizes->mTrackPlans[parentAxis]; 4083 MOZ_ASSERT(aUsedTrackSizes->mCanResolveLineRangeSize[parentAxis]); 4084 if (parentSizes.IsEmpty()) { 4085 return; 4086 } 4087 const auto& range = aSubgrid->mArea.LineRangeForAxis(parentAxis); 4088 const auto cbwm = aUsedTrackSizesFrame->GetWritingMode(); 4089 const auto wm = aSubgridFrame->GetWritingMode(); 4090 // Recompute the MBP to resolve percentages against the resolved track sizes. 4091 if (parentAxis == LogicalAxis::Inline) { 4092 // Find the subgrid's grid item frame in its parent grid container. This 4093 // is usually the same as aSubgridFrame but it may also have a ScrollFrame, 4094 // FieldSetFrame etc. We just loop until we see the first ancestor 4095 // GridContainerFrame and pick the last frame we saw before that. 4096 // Note that all subgrids are inside a parent (sub)grid container. 4097 const nsIFrame* outerGridItemFrame = aSubgridFrame; 4098 for (nsIFrame* parent = aSubgridFrame->GetParent(); 4099 parent != aUsedTrackSizesFrame; parent = parent->GetParent()) { 4100 MOZ_ASSERT(!parent->IsGridContainerFrame()); 4101 outerGridItemFrame = parent; 4102 } 4103 auto sizeInAxis = range.ToLength(aUsedTrackSizes->mTrackPlans[parentAxis]); 4104 LogicalSize pmPercentageBasis = 4105 aSubgrid->mIsOrthogonal ? LogicalSize(wm, nscoord(0), sizeInAxis) 4106 : LogicalSize(wm, sizeInAxis, nscoord(0)); 4107 GridItemInfo info(const_cast<nsIFrame*>(outerGridItemFrame), 4108 aSubgrid->mArea); 4109 SubgridComputeMarginBorderPadding(info, pmPercentageBasis); 4110 } 4111 const LogicalMargin& mbp = aSubgrid->mMarginBorderPadding; 4112 nscoord startMBP; 4113 nscoord endMBP; 4114 if (MOZ_LIKELY(cbwm.ParallelAxisStartsOnSameSide(parentAxis, wm))) { 4115 startMBP = mbp.Start(parentAxis, cbwm); 4116 endMBP = mbp.End(parentAxis, cbwm); 4117 uint32_t i = range.mStart; 4118 nscoord startPos = parentSizes[i].mPosition + startMBP; 4119 for (auto& sz : aResult) { 4120 sz = parentSizes[i++]; 4121 sz.mPosition -= startPos; 4122 } 4123 } else { 4124 startMBP = mbp.End(parentAxis, cbwm); 4125 endMBP = mbp.Start(parentAxis, cbwm); 4126 uint32_t i = range.mEnd - 1; 4127 const auto& parentEnd = parentSizes[i]; 4128 nscoord parentEndPos = parentEnd.mPosition + parentEnd.mBase - startMBP; 4129 for (auto& sz : aResult) { 4130 sz = parentSizes[i--]; 4131 sz.mPosition = parentEndPos - (sz.mPosition + sz.mBase); 4132 } 4133 } 4134 auto& startTrack = aResult[0]; 4135 startTrack.mPosition = 0; 4136 startTrack.mBase -= startMBP; 4137 if (MOZ_UNLIKELY(startTrack.mBase < nscoord(0))) { 4138 // Our MBP doesn't fit in the start track. Adjust the track position 4139 // to maintain track alignment with our parent. 4140 startTrack.mPosition = startTrack.mBase; 4141 startTrack.mBase = nscoord(0); 4142 } 4143 auto& endTrack = aResult.LastElement(); 4144 endTrack.mBase -= endMBP; 4145 if (MOZ_UNLIKELY(endTrack.mBase < nscoord(0))) { 4146 endTrack.mBase = nscoord(0); 4147 } 4148 } 4149 4150 void nsGridContainerFrame::UsedTrackSizes::ResolveTrackSizesForAxis( 4151 nsGridContainerFrame* aFrame, LogicalAxis aAxis, gfxContext& aRC) { 4152 if (mCanResolveLineRangeSize[aAxis]) { 4153 return; 4154 } 4155 if (!aFrame->IsSubgrid()) { 4156 // We can't resolve sizes in this axis at this point. aFrame is the top grid 4157 // container, which will store its final track sizes later once they're 4158 // resolved in this axis (in GridReflowInput::CalculateTrackSizesForAxis). 4159 // The single caller of this method only needs track sizes for 4160 // calculating a CB size and it will treat it as indefinite when 4161 // this happens. 4162 return; 4163 } 4164 auto* parent = aFrame->ParentGridContainerForSubgrid(); 4165 auto* parentSizes = parent->GetUsedTrackSizes(); 4166 if (!parentSizes) { 4167 parentSizes = new UsedTrackSizes(); 4168 parent->SetProperty(UsedTrackSizes::Prop(), parentSizes); 4169 } 4170 auto* subgrid = aFrame->GetProperty(Subgrid::Prop()); 4171 const auto parentAxis = 4172 subgrid->mIsOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis; 4173 parentSizes->ResolveTrackSizesForAxis(parent, parentAxis, aRC); 4174 if (!parentSizes->mCanResolveLineRangeSize[parentAxis]) { 4175 if (aFrame->IsSubgrid(aAxis)) { 4176 ResolveSubgridTrackSizesForAxis(aFrame, aAxis, subgrid, aRC, 4177 NS_UNCONSTRAINEDSIZE); 4178 } 4179 return; 4180 } 4181 if (aFrame->IsSubgrid(aAxis)) { 4182 CopyUsedTrackSizes(mTrackPlans[aAxis], parent, parentSizes, aFrame, subgrid, 4183 aAxis); 4184 mCanResolveLineRangeSize[aAxis] = true; 4185 } else { 4186 const auto& range = subgrid->mArea.LineRangeForAxis(parentAxis); 4187 nscoord contentBoxSize = 4188 range.ToLength(parentSizes->mTrackPlans[parentAxis]); 4189 auto parentWM = aFrame->GetParent()->GetWritingMode(); 4190 contentBoxSize -= 4191 subgrid->mMarginBorderPadding.StartEnd(parentAxis, parentWM); 4192 contentBoxSize = std::max(nscoord(0), contentBoxSize); 4193 ResolveSubgridTrackSizesForAxis(aFrame, aAxis, subgrid, aRC, 4194 contentBoxSize); 4195 } 4196 } 4197 4198 void nsGridContainerFrame::UsedTrackSizes::ResolveSubgridTrackSizesForAxis( 4199 nsGridContainerFrame* aFrame, LogicalAxis aAxis, Subgrid* aSubgrid, 4200 gfxContext& aRC, nscoord aContentBoxSize) { 4201 GridReflowInput gridRI(aFrame, aRC); 4202 gridRI.mGridItems = aSubgrid->mGridItems.Clone(); 4203 Grid grid; 4204 grid.mGridColEnd = aSubgrid->mGridColEnd; 4205 grid.mGridRowEnd = aSubgrid->mGridRowEnd; 4206 gridRI.CalculateTrackSizesForAxis(aAxis, grid, aContentBoxSize, 4207 SizingConstraint::NoConstraint); 4208 const auto& tracks = gridRI.TracksFor(aAxis); 4209 mTrackPlans[aAxis].Assign(tracks.mSizes); 4210 mCanResolveLineRangeSize[aAxis] = tracks.mCanResolveLineRangeSize; 4211 MOZ_ASSERT(mCanResolveLineRangeSize[aAxis]); 4212 } 4213 4214 void nsGridContainerFrame::GridReflowInput::CalculateTrackSizesForAxis( 4215 LogicalAxis aAxis, const Grid& aGrid, nscoord aContentBoxSize, 4216 SizingConstraint aConstraint) { 4217 auto& tracks = TracksFor(aAxis); 4218 const auto& sizingFunctions = 4219 aAxis == LogicalAxis::Inline ? mColFunctions : mRowFunctions; 4220 const auto& gapStyle = aAxis == LogicalAxis::Inline ? mGridStyle->mColumnGap 4221 : mGridStyle->mRowGap; 4222 if (tracks.mIsMasonry) { 4223 // See comment on nsGridContainerFrame::MasonryLayout(). 4224 tracks.Initialize(sizingFunctions, gapStyle, 2, aContentBoxSize); 4225 tracks.mCanResolveLineRangeSize = true; 4226 return; 4227 } 4228 uint32_t gridEnd = 4229 aAxis == LogicalAxis::Inline ? aGrid.mGridColEnd : aGrid.mGridRowEnd; 4230 Maybe<TrackSizingFunctions> fallbackTrackSizing; 4231 4232 bool useParentGaps = false; 4233 const bool isSubgriddedAxis = mFrame->IsSubgrid(aAxis); 4234 if (MOZ_LIKELY(!isSubgriddedAxis)) { 4235 tracks.Initialize(sizingFunctions, gapStyle, gridEnd, aContentBoxSize); 4236 } else { 4237 tracks.mGridGap = 4238 nsLayoutUtils::ResolveGapToLength(gapStyle, aContentBoxSize); 4239 tracks.mContentBoxSize = aContentBoxSize; 4240 const auto* subgrid = mFrame->GetProperty(Subgrid::Prop()); 4241 tracks.mSizes.SetLength(gridEnd); 4242 auto* parent = mFrame->ParentGridContainerForSubgrid(); 4243 auto parentAxis = subgrid->mIsOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis; 4244 const auto* parentSizes = parent->GetUsedTrackSizes(); 4245 if (parentSizes && parentSizes->mCanResolveLineRangeSize[parentAxis]) { 4246 CopyUsedTrackSizes(tracks.mSizes, parent, parentSizes, mFrame, subgrid, 4247 aAxis); 4248 useParentGaps = gapStyle.IsNormal(); 4249 } else { 4250 fallbackTrackSizing.emplace(TrackSizingFunctions::ForSubgridFallback( 4251 mFrame, subgrid, parent, parentAxis)); 4252 tracks.Initialize(*fallbackTrackSizing, gapStyle, gridEnd, 4253 aContentBoxSize); 4254 } 4255 } 4256 4257 // We run the Track Sizing Algorithm in non-subgridded axes, and in some 4258 // cases in a subgridded axis when our parent track sizes aren't resolved yet. 4259 if (MOZ_LIKELY(!isSubgriddedAxis) || fallbackTrackSizing.isSome()) { 4260 const size_t origGridItemCount = mGridItems.Length(); 4261 const bool hasSubgridItems = mFrame->HasSubgridItems(aAxis); 4262 if (hasSubgridItems) { 4263 AutoTArray<GridItemInfo, 8> collectedItems; 4264 CollectSubgridItemsForAxis(aAxis, collectedItems); 4265 mGridItems.AppendElements(collectedItems); 4266 } 4267 tracks.CalculateSizes( 4268 *this, mGridItems, 4269 fallbackTrackSizing ? *fallbackTrackSizing : sizingFunctions, 4270 aContentBoxSize, 4271 aAxis == LogicalAxis::Inline ? &GridArea::mCols : &GridArea::mRows, 4272 aConstraint); 4273 4274 if (hasSubgridItems && 4275 StaticPrefs::layout_css_grid_subgrid_baselines_enabled()) { 4276 // If any of the subgrid items are baseline-aligned, we've just recorded 4277 // their baseline-alignment offsets in our own copy of their GridItemInfo 4278 // structs. Before we get rid of those copies (via TruncateLength), we 4279 // have to copy these offsets back to the subgrids' versions of the 4280 // GridItemInfo structs. 4281 // 4282 // XXXdholbert This new behavior is behind a pref due to bug 1871719. 4283 CopyBaselineMetricsToSubgridItems(aAxis, origGridItemCount); 4284 } 4285 mGridItems.TruncateLength(origGridItemCount); 4286 } 4287 if (isSubgriddedAxis) { 4288 // XXXdholbert This is a bit hacky, but this is something that 4289 // tracks.CalculateSizes does internally (unconditionally, if there are 4290 // baseline-aligned items), and it seems like subgrids need to do it too, 4291 // or else they hit the "unexpected baseline subtree alignment" 4292 // fatal-assert when aligning their children with the baseline-alignment 4293 // information that they received from the outer grid. 4294 // (This might be entirely unnecessary? Aside from the default ::AUTO 4295 // value, it looks like the ::First entry is always set to ::START and 4296 // the ::Last entry is always set to ::END...) 4297 tracks.mBaselineSubtreeAlign[BaselineSharingGroup::First] = 4298 StyleAlignFlags::START; 4299 tracks.mBaselineSubtreeAlign[BaselineSharingGroup::Last] = 4300 StyleAlignFlags::END; 4301 } 4302 4303 if (aContentBoxSize != NS_UNCONSTRAINEDSIZE) { 4304 auto alignment = mGridStyle->UsedContentAlignment(tracks.mAxis); 4305 tracks.AlignJustifyContent(mGridStyle, alignment, mWM, aContentBoxSize, 4306 isSubgriddedAxis); 4307 } else if (!useParentGaps) { 4308 const nscoord gridGap = tracks.mGridGap; 4309 nscoord pos = 0; 4310 for (TrackSize& sz : tracks.mSizes) { 4311 sz.mPosition = pos; 4312 pos += sz.mBase + gridGap; 4313 } 4314 } 4315 4316 if (aConstraint == SizingConstraint::NoConstraint && 4317 (mFrame->HasSubgridItems() || mFrame->IsSubgrid())) { 4318 mFrame->StoreUsedTrackSizes(aAxis, tracks.mSizes); 4319 } 4320 4321 // positions and sizes are now final 4322 tracks.mCanResolveLineRangeSize = true; 4323 } 4324 4325 void nsGridContainerFrame::GridReflowInput::InvalidateTrackSizesForAxis( 4326 LogicalAxis aAxis) { 4327 for (auto& item : mGridItems) { 4328 item.ResetTrackSizingBits(aAxis); 4329 } 4330 TracksFor(aAxis).mCanResolveLineRangeSize = false; 4331 } 4332 4333 // Align an item's margin box in its aAxis inside aCBSize. 4334 static void AlignJustifySelf(StyleAlignFlags aAlignment, LogicalAxis aAxis, 4335 AlignJustifyFlags aFlags, nscoord aBaselineAdjust, 4336 nscoord aCBSize, const ReflowInput& aRI, 4337 const LogicalSize& aChildSize, 4338 LogicalPoint* aPos) { 4339 MOZ_ASSERT(aAlignment != StyleAlignFlags::AUTO, 4340 "unexpected 'auto' " 4341 "computed value for normal flow grid item"); 4342 4343 // NOTE: this is the resulting frame offset (border box). 4344 nscoord offset = CSSAlignUtils::AlignJustifySelf( 4345 aAlignment, aAxis, aFlags, aBaselineAdjust, aCBSize, aRI, aChildSize); 4346 4347 // Set the position (aPos) for the requested alignment. 4348 if (offset != 0) { 4349 WritingMode wm = aRI.GetWritingMode(); 4350 nscoord& pos = aAxis == LogicalAxis::Block ? aPos->B(wm) : aPos->I(wm); 4351 pos += MOZ_LIKELY(aFlags.contains(AlignJustifyFlag::SameSide)) ? offset 4352 : -offset; 4353 } 4354 } 4355 4356 static void AlignSelf(const nsGridContainerFrame::GridItemInfo& aGridItem, 4357 StyleAlignFlags aAlignSelf, nscoord aCBSize, 4358 const WritingMode aCBWM, const ReflowInput& aRI, 4359 const LogicalSize& aSize, AlignJustifyFlags aFlags, 4360 LogicalPoint* aPos) { 4361 AlignJustifyFlags flags = aFlags; 4362 if (aAlignSelf & StyleAlignFlags::SAFE) { 4363 flags += AlignJustifyFlag::OverflowSafe; 4364 } 4365 aAlignSelf &= ~StyleAlignFlags::FLAG_BITS; 4366 4367 WritingMode childWM = aRI.GetWritingMode(); 4368 if (aCBWM.ParallelAxisStartsOnSameSide(LogicalAxis::Block, childWM)) { 4369 flags += AlignJustifyFlag::SameSide; 4370 } 4371 4372 if (aGridItem.mState[LogicalAxis::Block] & 4373 GridItemInfo::eLastBaselineSharingGroup) { 4374 flags += AlignJustifyFlag::LastBaselineSharingGroup; 4375 } 4376 4377 // Grid's 'align-self' axis is never parallel to the container's inline axis. 4378 if (aAlignSelf == StyleAlignFlags::LEFT || 4379 aAlignSelf == StyleAlignFlags::RIGHT) { 4380 aAlignSelf = StyleAlignFlags::START; 4381 } 4382 if (MOZ_LIKELY(aAlignSelf == StyleAlignFlags::NORMAL)) { 4383 aAlignSelf = StyleAlignFlags::STRETCH; 4384 } 4385 4386 nscoord baselineAdjust = 0; 4387 if (aAlignSelf == StyleAlignFlags::BASELINE || 4388 aAlignSelf == StyleAlignFlags::LAST_BASELINE) { 4389 aAlignSelf = aGridItem.GetSelfBaseline(aAlignSelf, LogicalAxis::Block, 4390 &baselineAdjust); 4391 } 4392 4393 const auto bAxisInChildWM = aCBWM.ConvertAxisTo(LogicalAxis::Block, childWM); 4394 AlignJustifySelf(aAlignSelf, bAxisInChildWM, flags, baselineAdjust, aCBSize, 4395 aRI, aSize, aPos); 4396 } 4397 4398 static void JustifySelf(const nsGridContainerFrame::GridItemInfo& aGridItem, 4399 StyleAlignFlags aJustifySelf, nscoord aCBSize, 4400 const WritingMode aCBWM, const ReflowInput& aRI, 4401 const LogicalSize& aSize, AlignJustifyFlags aFlags, 4402 LogicalPoint* aPos) { 4403 AlignJustifyFlags flags = aFlags; 4404 if (aJustifySelf & StyleAlignFlags::SAFE) { 4405 flags += AlignJustifyFlag::OverflowSafe; 4406 } 4407 aJustifySelf &= ~StyleAlignFlags::FLAG_BITS; 4408 4409 WritingMode childWM = aRI.GetWritingMode(); 4410 if (aCBWM.ParallelAxisStartsOnSameSide(LogicalAxis::Inline, childWM)) { 4411 flags += AlignJustifyFlag::SameSide; 4412 } 4413 4414 if (aGridItem.mState[LogicalAxis::Inline] & 4415 GridItemInfo::eLastBaselineSharingGroup) { 4416 flags += AlignJustifyFlag::LastBaselineSharingGroup; 4417 } 4418 4419 if (MOZ_LIKELY(aJustifySelf == StyleAlignFlags::NORMAL)) { 4420 aJustifySelf = StyleAlignFlags::STRETCH; 4421 } 4422 4423 nscoord baselineAdjust = 0; 4424 // Grid's 'justify-self' axis is always parallel to the container's inline 4425 // axis, so justify-self:left|right always applies. 4426 if (aJustifySelf == StyleAlignFlags::LEFT) { 4427 aJustifySelf = 4428 aCBWM.IsBidiLTR() ? StyleAlignFlags::START : StyleAlignFlags::END; 4429 } else if (aJustifySelf == StyleAlignFlags::RIGHT) { 4430 aJustifySelf = 4431 aCBWM.IsBidiLTR() ? StyleAlignFlags::END : StyleAlignFlags::START; 4432 } else if (aJustifySelf == StyleAlignFlags::BASELINE || 4433 aJustifySelf == StyleAlignFlags::LAST_BASELINE) { 4434 aJustifySelf = aGridItem.GetSelfBaseline(aJustifySelf, LogicalAxis::Inline, 4435 &baselineAdjust); 4436 } 4437 4438 const auto iAxisInChildWM = aCBWM.ConvertAxisTo(LogicalAxis::Inline, childWM); 4439 AlignJustifySelf(aJustifySelf, iAxisInChildWM, flags, baselineAdjust, aCBSize, 4440 aRI, aSize, aPos); 4441 } 4442 4443 static StyleAlignFlags GetAlignJustifyValue(StyleAlignFlags aAlignment, 4444 const WritingMode aWM, 4445 const bool aIsAlign, 4446 bool* aOverflowSafe) { 4447 *aOverflowSafe = bool(aAlignment & StyleAlignFlags::SAFE); 4448 aAlignment &= ~StyleAlignFlags::FLAG_BITS; 4449 4450 // Map some alignment values to 'start' / 'end'. 4451 if (aAlignment == StyleAlignFlags::LEFT || 4452 aAlignment == StyleAlignFlags::RIGHT) { 4453 if (aIsAlign) { 4454 // Grid's 'align-content' axis is never parallel to the inline axis. 4455 return StyleAlignFlags::START; 4456 } 4457 bool isStart = aWM.IsBidiLTR() == (aAlignment == StyleAlignFlags::LEFT); 4458 return isStart ? StyleAlignFlags::START : StyleAlignFlags::END; 4459 } 4460 if (aAlignment == StyleAlignFlags::FLEX_START) { 4461 return StyleAlignFlags::START; // same as 'start' for Grid 4462 } 4463 if (aAlignment == StyleAlignFlags::FLEX_END) { 4464 return StyleAlignFlags::END; // same as 'end' for Grid 4465 } 4466 return aAlignment; 4467 } 4468 4469 static Maybe<StyleAlignFlags> GetAlignJustifyDistributionFallback( 4470 const StyleContentDistribution& aDistribution, bool* aOverflowSafe) { 4471 // See "4.3. Distributed Alignment" for the default fallback alignment values: 4472 // https://drafts.csswg.org/css-align-3/#distribution-values 4473 // 4474 // TODO: Extend this function to handle explicitly specified fallback 4475 // alignment once the CSS Alignment Module introduces that syntax: 4476 // https://github.com/w3c/csswg-drafts/issues/1002. 4477 if (aDistribution.primary == StyleAlignFlags::SPACE_BETWEEN) { 4478 *aOverflowSafe = true; 4479 return Some(StyleAlignFlags::START); 4480 } 4481 if (aDistribution.primary == StyleAlignFlags::SPACE_AROUND || 4482 aDistribution.primary == StyleAlignFlags::SPACE_EVENLY) { 4483 *aOverflowSafe = true; 4484 return Some(StyleAlignFlags::CENTER); 4485 } 4486 if (aDistribution.primary == StyleAlignFlags::STRETCH) { 4487 *aOverflowSafe = false; 4488 return Some(StyleAlignFlags::START); 4489 } 4490 return Nothing(); 4491 } 4492 4493 //---------------------------------------------------------------------- 4494 4495 // Frame class boilerplate 4496 // ======================= 4497 4498 NS_QUERYFRAME_HEAD(nsGridContainerFrame) 4499 NS_QUERYFRAME_ENTRY(nsGridContainerFrame) 4500 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 4501 4502 NS_IMPL_FRAMEARENA_HELPERS(nsGridContainerFrame) 4503 4504 nsContainerFrame* NS_NewGridContainerFrame(PresShell* aPresShell, 4505 ComputedStyle* aStyle) { 4506 return new (aPresShell) 4507 nsGridContainerFrame(aStyle, aPresShell->GetPresContext()); 4508 } 4509 4510 //---------------------------------------------------------------------- 4511 4512 // nsGridContainerFrame Method Implementations 4513 // =========================================== 4514 4515 /*static*/ const nsRect& nsGridContainerFrame::GridItemCB(nsIFrame* aChild) { 4516 MOZ_ASSERT(aChild->IsAbsolutelyPositioned()); 4517 nsRect* cb = aChild->GetProperty(GridItemContainingBlockRect()); 4518 MOZ_ASSERT(cb, 4519 "this method must only be called on grid items, and the grid " 4520 "container should've reflowed this item by now and set up cb"); 4521 return *cb; 4522 } 4523 4524 void nsGridContainerFrame::AddImplicitNamedAreasInternal( 4525 LineNameList& aNameList, 4526 nsGridContainerFrame::ImplicitNamedAreas*& aAreas) { 4527 for (const auto& nameIdent : aNameList.AsSpan()) { 4528 nsAtom* name = nameIdent.AsAtom(); 4529 uint32_t indexOfSuffix; 4530 if (Grid::IsNameWithStartSuffix(name, &indexOfSuffix) || 4531 Grid::IsNameWithEndSuffix(name, &indexOfSuffix)) { 4532 // Extract the name that was found earlier. 4533 nsDependentSubstring areaName(nsDependentAtomString(name), 0, 4534 indexOfSuffix); 4535 4536 // Lazily create the ImplicitNamedAreas. 4537 if (!aAreas) { 4538 aAreas = new nsGridContainerFrame::ImplicitNamedAreas; 4539 SetProperty(nsGridContainerFrame::ImplicitNamedAreasProperty(), aAreas); 4540 } 4541 4542 RefPtr<nsAtom> name = NS_Atomize(areaName); 4543 auto addPtr = aAreas->lookupForAdd(name); 4544 if (!addPtr) { 4545 if (!aAreas->add(addPtr, name, 4546 nsGridContainerFrame::NamedArea{ 4547 StyleAtom(do_AddRef(name)), {0, 0}, {0, 0}})) { 4548 MOZ_CRASH("OOM while adding grid name lists"); 4549 } 4550 } 4551 } 4552 } 4553 } 4554 4555 void nsGridContainerFrame::AddImplicitNamedAreas( 4556 Span<LineNameList> aLineNameLists) { 4557 // https://drafts.csswg.org/css-grid-2/#implicit-named-areas 4558 // Note: recording these names for fast lookup later is just an optimization. 4559 ImplicitNamedAreas* areas = GetImplicitNamedAreas(); 4560 const uint32_t len = std::min(aLineNameLists.Length(), size_t(kMaxLine)); 4561 for (uint32_t i = 0; i < len; ++i) { 4562 AddImplicitNamedAreasInternal(aLineNameLists[i], areas); 4563 } 4564 } 4565 4566 void nsGridContainerFrame::AddImplicitNamedAreas( 4567 Span<StyleLineNameListValue> aLineNameList) { 4568 // https://drafts.csswg.org/css-grid-2/#implicit-named-areas 4569 // Note: recording these names for fast lookup later is just an optimization. 4570 uint32_t count = 0; 4571 ImplicitNamedAreas* areas = GetImplicitNamedAreas(); 4572 for (const auto& nameList : aLineNameList) { 4573 if (nameList.IsRepeat()) { 4574 for (const auto& repeatNameList : 4575 nameList.AsRepeat().line_names.AsSpan()) { 4576 AddImplicitNamedAreasInternal(repeatNameList, areas); 4577 ++count; 4578 } 4579 } else { 4580 MOZ_ASSERT(nameList.IsLineNames()); 4581 AddImplicitNamedAreasInternal(nameList.AsLineNames(), areas); 4582 ++count; 4583 } 4584 4585 if (count >= size_t(kMaxLine)) { 4586 break; 4587 } 4588 } 4589 } 4590 4591 void nsGridContainerFrame::InitImplicitNamedAreas( 4592 const nsStylePosition* aStyle) { 4593 ImplicitNamedAreas* areas = GetImplicitNamedAreas(); 4594 if (areas) { 4595 // Clear it, but reuse the hashtable itself for now. We'll remove it 4596 // below if it isn't needed anymore. 4597 areas->clear(); 4598 } 4599 auto Add = [&](const GridTemplate& aTemplate, bool aIsSubgrid) { 4600 AddImplicitNamedAreas(aTemplate.LineNameLists(aIsSubgrid)); 4601 for (auto& value : aTemplate.TrackListValues()) { 4602 if (value.IsTrackRepeat()) { 4603 AddImplicitNamedAreas(value.AsTrackRepeat().line_names.AsSpan()); 4604 } 4605 } 4606 4607 if (aIsSubgrid && aTemplate.IsSubgrid()) { 4608 // For subgrid, |aTemplate.LineNameLists(aIsSubgrid)| returns an empty 4609 // list so we have to manually add each item. 4610 AddImplicitNamedAreas(aTemplate.AsSubgrid()->line_names.AsSpan()); 4611 } 4612 }; 4613 Add(aStyle->mGridTemplateColumns, IsColSubgrid()); 4614 Add(aStyle->mGridTemplateRows, IsRowSubgrid()); 4615 if (areas && areas->count() == 0) { 4616 RemoveProperty(ImplicitNamedAreasProperty()); 4617 } 4618 } 4619 4620 int32_t nsGridContainerFrame::Grid::ResolveLine( 4621 const StyleGridLine& aLine, int32_t aNth, uint32_t aFromIndex, 4622 const LineNameMap& aNameMap, LogicalSide aSide, uint32_t aExplicitGridEnd, 4623 const nsStylePosition* aStyle) { 4624 MOZ_ASSERT(!aLine.IsAuto()); 4625 int32_t line = 0; 4626 if (aLine.LineName()->IsEmpty()) { 4627 MOZ_ASSERT(aNth != 0, "css-grid 9.2: <integer> must not be zero."); 4628 line = int32_t(aFromIndex) + aNth; 4629 } else { 4630 if (aNth == 0) { 4631 // <integer> was omitted; treat it as 1. 4632 aNth = 1; 4633 } 4634 bool isNameOnly = !aLine.is_span && aLine.line_num == 0; 4635 if (isNameOnly) { 4636 AutoTArray<uint32_t, 16> implicitLines; 4637 aNameMap.FindNamedAreas(aLine.ident.AsAtom(), aSide, implicitLines); 4638 if (!implicitLines.IsEmpty() || 4639 aNameMap.HasImplicitNamedArea(aLine.LineName())) { 4640 // aName is a named area - look for explicit lines named 4641 // <name>-start/-end depending on which side we're resolving. 4642 // https://drafts.csswg.org/css-grid-2/#grid-placement-slot 4643 nsAutoString lineName(nsDependentAtomString(aLine.LineName())); 4644 if (IsStart(aSide)) { 4645 lineName.AppendLiteral("-start"); 4646 } else { 4647 lineName.AppendLiteral("-end"); 4648 } 4649 RefPtr<nsAtom> name = NS_Atomize(lineName); 4650 line = aNameMap.FindNamedLine(name, &aNth, aFromIndex, implicitLines); 4651 } 4652 } 4653 4654 if (line == 0) { 4655 // If LineName() ends in -start/-end, try the prefix as a named area. 4656 AutoTArray<uint32_t, 16> implicitLines; 4657 uint32_t index; 4658 bool useStart = IsNameWithStartSuffix(aLine.LineName(), &index); 4659 if (useStart || IsNameWithEndSuffix(aLine.LineName(), &index)) { 4660 auto side = MakeLogicalSide( 4661 GetAxis(aSide), useStart ? LogicalEdge::Start : LogicalEdge::End); 4662 RefPtr<nsAtom> name = NS_Atomize(nsDependentSubstring( 4663 nsDependentAtomString(aLine.LineName()), 0, index)); 4664 aNameMap.FindNamedAreas(name, side, implicitLines); 4665 } 4666 line = aNameMap.FindNamedLine(aLine.LineName(), &aNth, aFromIndex, 4667 implicitLines); 4668 } 4669 4670 if (line == 0) { 4671 MOZ_ASSERT(aNth != 0, "we found all N named lines but 'line' is zero!"); 4672 int32_t edgeLine; 4673 if (aLine.is_span) { 4674 // https://drafts.csswg.org/css-grid-2/#grid-placement-span-int 4675 // 'span <custom-ident> N' 4676 edgeLine = IsStart(aSide) ? 1 : aExplicitGridEnd; 4677 } else { 4678 // https://drafts.csswg.org/css-grid-2/#grid-placement-int 4679 // '<custom-ident> N' 4680 edgeLine = aNth < 0 ? 1 : aExplicitGridEnd; 4681 } 4682 // "If not enough lines with that name exist, all lines in the implicit 4683 // grid are assumed to have that name..." 4684 line = edgeLine + aNth; 4685 } 4686 } 4687 // Note: at this point, 'line' might be outside of aNameMap's allowed range, 4688 // [mClampMinLin, mClampMaxLine]. This is fine; we'll clamp once we've 4689 // resolved *both* the start and end line -- in particular, we clamp in 4690 // ResolveLineRange(). If we clamped here, it'd be premature -- if one line 4691 // is definite and the other is specified as a span to some named line 4692 // (i.e. we need to perform a name-search that starts from the definite 4693 // line), then it matters whether we clamp the definite line before or after 4694 // that search. See https://bugzilla.mozilla.org/show_bug.cgi?id=1800566#c6 4695 // for more. 4696 return line; 4697 } 4698 4699 nsGridContainerFrame::Grid::LinePair 4700 nsGridContainerFrame::Grid::ResolveLineRangeHelper( 4701 const StyleGridLine& aStart, const StyleGridLine& aEnd, 4702 const LineNameMap& aNameMap, LogicalAxis aAxis, uint32_t aExplicitGridEnd, 4703 const nsStylePosition* aStyle) { 4704 MOZ_ASSERT(int32_t(kAutoLine) > kMaxLine); 4705 4706 if (aStart.is_span) { 4707 if (aEnd.is_span || aEnd.IsAuto()) { 4708 // https://drafts.csswg.org/css-grid-2/#grid-placement-errors 4709 if (aStart.LineName()->IsEmpty()) { 4710 // span <integer> / span * 4711 // span <integer> / auto 4712 return LinePair(kAutoLine, aStart.line_num); 4713 } 4714 // span <custom-ident> / span * 4715 // span <custom-ident> / auto 4716 return LinePair(kAutoLine, 1); // XXX subgrid explicit size instead of 1? 4717 } 4718 4719 uint32_t from = aEnd.line_num < 0 ? aExplicitGridEnd + 1 : 0; 4720 auto end = ResolveLine(aEnd, aEnd.line_num, from, aNameMap, 4721 MakeLogicalSide(aAxis, LogicalEdge::End), 4722 aExplicitGridEnd, aStyle); 4723 int32_t span = aStart.line_num == 0 ? 1 : aStart.line_num; 4724 if (end <= 1) { 4725 // The end is at or before the first explicit line, thus all lines before 4726 // it match <custom-ident> since they're implicit. 4727 int32_t start = std::max(end - span, aNameMap.mClampMinLine); 4728 return LinePair(start, end); 4729 } 4730 auto start = ResolveLine(aStart, -span, end, aNameMap, 4731 MakeLogicalSide(aAxis, LogicalEdge::Start), 4732 aExplicitGridEnd, aStyle); 4733 return LinePair(start, end); 4734 } 4735 4736 int32_t start = kAutoLine; 4737 if (aStart.IsAuto()) { 4738 if (aEnd.IsAuto()) { 4739 // auto / auto 4740 return LinePair(start, 1); // XXX subgrid explicit size instead of 1? 4741 } 4742 if (aEnd.is_span) { 4743 if (aEnd.LineName()->IsEmpty()) { 4744 // auto / span <integer> 4745 MOZ_ASSERT(aEnd.line_num != 0); 4746 return LinePair(start, aEnd.line_num); 4747 } 4748 // https://drafts.csswg.org/css-grid-2/#grid-placement-errors 4749 // auto / span <custom-ident> 4750 return LinePair(start, 1); // XXX subgrid explicit size instead of 1? 4751 } 4752 } else { 4753 uint32_t from = aStart.line_num < 0 ? aExplicitGridEnd + 1 : 0; 4754 start = ResolveLine(aStart, aStart.line_num, from, aNameMap, 4755 MakeLogicalSide(aAxis, LogicalEdge::Start), 4756 aExplicitGridEnd, aStyle); 4757 if (aEnd.IsAuto()) { 4758 // A "definite line / auto" should resolve the auto to 'span 1'. 4759 // The error handling in ResolveLineRange will make that happen and also 4760 // clamp the end line correctly if we return "start / start". 4761 return LinePair(start, start); 4762 } 4763 } 4764 4765 uint32_t from; 4766 int32_t nth = aEnd.line_num == 0 ? 1 : aEnd.line_num; 4767 if (aEnd.is_span) { 4768 if (MOZ_UNLIKELY(start < 0)) { 4769 if (aEnd.LineName()->IsEmpty()) { 4770 return LinePair(start, start + nth); 4771 } 4772 from = 0; 4773 } else { 4774 if (start >= int32_t(aExplicitGridEnd)) { 4775 // The start is at or after the last explicit line, thus all lines 4776 // after it match <custom-ident> since they're implicit. 4777 return LinePair(start, std::min(start + nth, aNameMap.mClampMaxLine)); 4778 } 4779 from = start; 4780 } 4781 } else { 4782 from = aEnd.line_num < 0 ? aExplicitGridEnd + 1 : 0; 4783 } 4784 auto end = ResolveLine(aEnd, nth, from, aNameMap, 4785 MakeLogicalSide(aAxis, LogicalEdge::End), 4786 aExplicitGridEnd, aStyle); 4787 if (start == int32_t(kAutoLine)) { 4788 // auto / definite line 4789 start = std::max(aNameMap.mClampMinLine, end - 1); 4790 } 4791 return LinePair(start, end); 4792 } 4793 4794 nsGridContainerFrame::LineRange nsGridContainerFrame::Grid::ResolveLineRange( 4795 const StyleGridLine& aStart, const StyleGridLine& aEnd, 4796 const LineNameMap& aNameMap, LogicalAxis aAxis, uint32_t aExplicitGridEnd, 4797 const nsStylePosition* aStyle) { 4798 LinePair r = ResolveLineRangeHelper(aStart, aEnd, aNameMap, aAxis, 4799 aExplicitGridEnd, aStyle); 4800 MOZ_ASSERT(r.second != int32_t(kAutoLine)); 4801 4802 if (r.first == int32_t(kAutoLine)) { 4803 // r.second is a span, clamp it to aNameMap.mClampMaxLine - 1 so that 4804 // the returned range has a HypotheticalEnd <= aNameMap.mClampMaxLine. 4805 // https://drafts.csswg.org/css-grid-2/#overlarge-grids 4806 r.second = std::min(r.second, aNameMap.mClampMaxLine - 1); 4807 } else { 4808 // Clamp the lines to be within our limits, per 4809 // https://drafts.csswg.org/css-grid-2/#overlarge-grids 4810 // Note that our limits here might come from the [kMinLine, kMaxLine] 4811 // extremes; or, they might just be the bounds of a subgrid's explicit 4812 // grid. We use the same clamping approach either way, per 4813 // https://drafts.csswg.org/css-grid-2/#subgrid-implicit ("using the same 4814 // procedure as for clamping placement in an overly-large grid"). 4815 // 4816 // Note that these two clamped assignments might collapse our range to 4817 // have both edges pointing at the same line (spanning 0 tracks); this 4818 // might happen here if e.g. r.first were mClampMaxLine, and r.second gets 4819 // clamped from some higher number down to mClampMaxLine. We'll handle this 4820 // by shifting the inner line (r.first in this hypothetical) inwards by 1, 4821 // in the #grid-placement-errors section; that achieves the outcome of 4822 // the #overlarge-grids clamping spec text that says "its span must be 4823 // truncated to 1" when clamping an item that was completely outside the 4824 // limits. 4825 r.first = 4826 std::clamp(r.first, aNameMap.mClampMinLine, aNameMap.mClampMaxLine); 4827 r.second = 4828 std::clamp(r.second, aNameMap.mClampMinLine, aNameMap.mClampMaxLine); 4829 4830 // Handle grid placement errors. 4831 // https://drafts.csswg.org/css-grid-2/#grid-placement-errors 4832 if (r.first > r.second) { 4833 std::swap(r.first, r.second); 4834 } else if (r.first == r.second) { 4835 // (This is #grid-placement-errors fixup, but it's also where we ensure 4836 // that any #overlarge-grids fixup that we did above will end up 4837 // truncating the range to a span of 1 rather than 0 -- i.e. sliding 4838 // inwards if needed.) 4839 if (MOZ_UNLIKELY(r.first == aNameMap.mClampMaxLine)) { 4840 r.first = aNameMap.mClampMaxLine - 1; 4841 } 4842 r.second = r.first + 1; 4843 } 4844 } 4845 return LineRange(r.first, r.second); 4846 } 4847 4848 nsGridContainerFrame::GridArea nsGridContainerFrame::Grid::PlaceDefinite( 4849 nsIFrame* aChild, const LineNameMap& aColLineNameMap, 4850 const LineNameMap& aRowLineNameMap, const nsStylePosition* aStyle) { 4851 const nsStylePosition* itemStyle = aChild->StylePosition(); 4852 return GridArea( 4853 ResolveLineRange(itemStyle->mGridColumnStart, itemStyle->mGridColumnEnd, 4854 aColLineNameMap, LogicalAxis::Inline, 4855 mExplicitGridColEnd, aStyle), 4856 ResolveLineRange(itemStyle->mGridRowStart, itemStyle->mGridRowEnd, 4857 aRowLineNameMap, LogicalAxis::Block, mExplicitGridRowEnd, 4858 aStyle)); 4859 } 4860 4861 nsGridContainerFrame::LineRange 4862 nsGridContainerFrame::Grid::ResolveAbsPosLineRange( 4863 const StyleGridLine& aStart, const StyleGridLine& aEnd, 4864 const LineNameMap& aNameMap, LogicalAxis aAxis, uint32_t aExplicitGridEnd, 4865 int32_t aGridStart, int32_t aGridEnd, const nsStylePosition* aStyle) { 4866 if (aStart.IsAuto()) { 4867 if (aEnd.IsAuto()) { 4868 return LineRange(kAutoLine, kAutoLine); 4869 } 4870 uint32_t from = aEnd.line_num < 0 ? aExplicitGridEnd + 1 : 0; 4871 int32_t end = ResolveLine(aEnd, aEnd.line_num, from, aNameMap, 4872 MakeLogicalSide(aAxis, LogicalEdge::End), 4873 aExplicitGridEnd, aStyle); 4874 if (aEnd.is_span) { 4875 ++end; 4876 } 4877 // A line outside the existing grid is treated as 'auto' for abs.pos (10.1). 4878 end = AutoIfOutside(end, aGridStart, aGridEnd); 4879 return LineRange(kAutoLine, end); 4880 } 4881 4882 if (aEnd.IsAuto()) { 4883 uint32_t from = aStart.line_num < 0 ? aExplicitGridEnd + 1 : 0; 4884 int32_t start = ResolveLine(aStart, aStart.line_num, from, aNameMap, 4885 MakeLogicalSide(aAxis, LogicalEdge::Start), 4886 aExplicitGridEnd, aStyle); 4887 if (aStart.is_span) { 4888 start = std::max(aGridEnd - start, aGridStart); 4889 } 4890 start = AutoIfOutside(start, aGridStart, aGridEnd); 4891 return LineRange(start, kAutoLine); 4892 } 4893 4894 LineRange r = 4895 ResolveLineRange(aStart, aEnd, aNameMap, aAxis, aExplicitGridEnd, aStyle); 4896 if (r.IsAuto()) { 4897 MOZ_ASSERT(aStart.is_span && aEnd.is_span, 4898 "span / span is the only case " 4899 "leading to IsAuto here -- we dealt with the other cases above"); 4900 // The second span was ignored per 9.2.1. For abs.pos., 10.1 says that this 4901 // case should result in "auto / auto" unlike normal flow grid items. 4902 return LineRange(kAutoLine, kAutoLine); 4903 } 4904 4905 return LineRange(AutoIfOutside(r.mUntranslatedStart, aGridStart, aGridEnd), 4906 AutoIfOutside(r.mUntranslatedEnd, aGridStart, aGridEnd)); 4907 } 4908 4909 nsGridContainerFrame::GridArea nsGridContainerFrame::Grid::PlaceAbsPos( 4910 nsIFrame* aChild, const LineNameMap& aColLineNameMap, 4911 const LineNameMap& aRowLineNameMap, const nsStylePosition* aStyle) { 4912 const nsStylePosition* itemStyle = aChild->StylePosition(); 4913 int32_t gridColStart = 1 - mExplicitGridOffsetCol; 4914 int32_t gridRowStart = 1 - mExplicitGridOffsetRow; 4915 return GridArea(ResolveAbsPosLineRange( 4916 itemStyle->mGridColumnStart, itemStyle->mGridColumnEnd, 4917 aColLineNameMap, LogicalAxis::Inline, mExplicitGridColEnd, 4918 gridColStart, mGridColEnd, aStyle), 4919 ResolveAbsPosLineRange( 4920 itemStyle->mGridRowStart, itemStyle->mGridRowEnd, 4921 aRowLineNameMap, LogicalAxis::Block, mExplicitGridRowEnd, 4922 gridRowStart, mGridRowEnd, aStyle)); 4923 } 4924 4925 uint32_t nsGridContainerFrame::Grid::FindAutoCol(uint32_t aStartCol, 4926 uint32_t aLockedRow, 4927 const GridArea* aArea) const { 4928 const uint32_t extent = aArea->mCols.Extent(); 4929 const uint32_t iStart = aLockedRow; 4930 const uint32_t iEnd = iStart + aArea->mRows.Extent(); 4931 uint32_t candidate = aStartCol; 4932 for (uint32_t i = iStart; i < iEnd;) { 4933 if (i >= mCellMap.mCells.Length()) { 4934 break; 4935 } 4936 const nsTArray<CellMap::Cell>& cellsInRow = mCellMap.mCells[i]; 4937 const uint32_t len = cellsInRow.Length(); 4938 const uint32_t lastCandidate = candidate; 4939 // Find the first gap in the current row that's at least 'extent' wide. 4940 // ('gap' tracks how wide the current column gap is.) 4941 for (uint32_t j = candidate, gap = 0; j < len && gap < extent; ++j) { 4942 if (!cellsInRow[j].mIsOccupied) { 4943 ++gap; 4944 continue; 4945 } 4946 candidate = j + 1; 4947 gap = 0; 4948 } 4949 if (lastCandidate < candidate && i != iStart) { 4950 // Couldn't fit 'extent' tracks at 'lastCandidate' here so we must 4951 // restart from the beginning with the new 'candidate'. 4952 i = iStart; 4953 } else { 4954 ++i; 4955 } 4956 } 4957 return candidate; 4958 } 4959 4960 void nsGridContainerFrame::Grid::PlaceAutoCol(uint32_t aStartCol, 4961 GridArea* aArea, 4962 uint32_t aClampMaxColLine) const { 4963 MOZ_ASSERT(aArea->mRows.IsDefinite() && aArea->mCols.IsAuto()); 4964 uint32_t col = FindAutoCol(aStartCol, aArea->mRows.mStart, aArea); 4965 aArea->mCols.ResolveAutoPosition(col, aClampMaxColLine); 4966 MOZ_ASSERT(aArea->IsDefinite()); 4967 } 4968 4969 uint32_t nsGridContainerFrame::Grid::FindAutoRow(uint32_t aLockedCol, 4970 uint32_t aStartRow, 4971 const GridArea* aArea) const { 4972 const uint32_t extent = aArea->mRows.Extent(); 4973 const uint32_t jStart = aLockedCol; 4974 const uint32_t jEnd = jStart + aArea->mCols.Extent(); 4975 const uint32_t iEnd = mCellMap.mCells.Length(); 4976 uint32_t candidate = aStartRow; 4977 // Find the first gap in the rows that's at least 'extent' tall. 4978 // ('gap' tracks how tall the current row gap is.) 4979 for (uint32_t i = candidate, gap = 0; i < iEnd && gap < extent; ++i) { 4980 ++gap; // tentative, but we may reset it below if a column is occupied 4981 const nsTArray<CellMap::Cell>& cellsInRow = mCellMap.mCells[i]; 4982 const uint32_t clampedJEnd = std::min<uint32_t>(jEnd, cellsInRow.Length()); 4983 // Check if the current row is unoccupied from jStart to jEnd. 4984 for (uint32_t j = jStart; j < clampedJEnd; ++j) { 4985 if (cellsInRow[j].mIsOccupied) { 4986 // Couldn't fit 'extent' rows at 'candidate' here; we hit something 4987 // at row 'i'. So, try the row after 'i' as our next candidate. 4988 candidate = i + 1; 4989 gap = 0; 4990 break; 4991 } 4992 } 4993 } 4994 return candidate; 4995 } 4996 4997 void nsGridContainerFrame::Grid::PlaceAutoRow(uint32_t aStartRow, 4998 GridArea* aArea, 4999 uint32_t aClampMaxRowLine) const { 5000 MOZ_ASSERT(aArea->mCols.IsDefinite() && aArea->mRows.IsAuto()); 5001 uint32_t row = FindAutoRow(aArea->mCols.mStart, aStartRow, aArea); 5002 aArea->mRows.ResolveAutoPosition(row, aClampMaxRowLine); 5003 MOZ_ASSERT(aArea->IsDefinite()); 5004 } 5005 5006 void nsGridContainerFrame::Grid::PlaceAutoAutoInRowOrder( 5007 uint32_t aStartCol, uint32_t aStartRow, GridArea* aArea, 5008 uint32_t aClampMaxColLine, uint32_t aClampMaxRowLine) const { 5009 MOZ_ASSERT(aArea->mCols.IsAuto() && aArea->mRows.IsAuto()); 5010 const uint32_t colExtent = aArea->mCols.Extent(); 5011 const uint32_t gridRowEnd = mGridRowEnd; 5012 const uint32_t gridColEnd = mGridColEnd; 5013 uint32_t col = aStartCol; 5014 uint32_t row = aStartRow; 5015 for (; row < gridRowEnd; ++row) { 5016 col = FindAutoCol(col, row, aArea); 5017 if (col + colExtent <= gridColEnd) { 5018 break; 5019 } 5020 col = 0; 5021 } 5022 MOZ_ASSERT(row < gridRowEnd || col == 0, 5023 "expected column 0 for placing in a new row"); 5024 aArea->mCols.ResolveAutoPosition(col, aClampMaxColLine); 5025 aArea->mRows.ResolveAutoPosition(row, aClampMaxRowLine); 5026 MOZ_ASSERT(aArea->IsDefinite()); 5027 } 5028 5029 void nsGridContainerFrame::Grid::PlaceAutoAutoInColOrder( 5030 uint32_t aStartCol, uint32_t aStartRow, GridArea* aArea, 5031 uint32_t aClampMaxColLine, uint32_t aClampMaxRowLine) const { 5032 MOZ_ASSERT(aArea->mCols.IsAuto() && aArea->mRows.IsAuto()); 5033 const uint32_t rowExtent = aArea->mRows.Extent(); 5034 const uint32_t gridRowEnd = mGridRowEnd; 5035 const uint32_t gridColEnd = mGridColEnd; 5036 uint32_t col = aStartCol; 5037 uint32_t row = aStartRow; 5038 for (; col < gridColEnd; ++col) { 5039 row = FindAutoRow(col, row, aArea); 5040 if (row + rowExtent <= gridRowEnd) { 5041 break; 5042 } 5043 row = 0; 5044 } 5045 MOZ_ASSERT(col < gridColEnd || row == 0, 5046 "expected row 0 for placing in a new column"); 5047 aArea->mCols.ResolveAutoPosition(col, aClampMaxColLine); 5048 aArea->mRows.ResolveAutoPosition(row, aClampMaxRowLine); 5049 MOZ_ASSERT(aArea->IsDefinite()); 5050 } 5051 5052 template <typename IsEmptyFuncT> 5053 Maybe<nsTArray<uint32_t>> 5054 nsGridContainerFrame::Grid::CalculateAdjustForAutoFitElements( 5055 uint32_t* const aOutNumEmptyLines, TrackSizingFunctions& aSizingFunctions, 5056 uint32_t aNumGridLines, IsEmptyFuncT aIsEmptyFunc) { 5057 Maybe<nsTArray<uint32_t>> trackAdjust; 5058 uint32_t& numEmptyLines = *aOutNumEmptyLines; 5059 numEmptyLines = 0; 5060 if (aSizingFunctions.NumRepeatTracks() > 0) { 5061 MOZ_ASSERT(aSizingFunctions.mHasRepeatAuto); 5062 // Since this loop is concerned with just the repeat tracks, we 5063 // iterate from 0..NumRepeatTracks() which is the natural range of 5064 // mRemoveRepeatTracks. This means we have to add 5065 // (mExplicitGridOffset + mRepeatAutoStart) to get a zero-based 5066 // index for arrays like mCellMap/aIsEmptyFunc and trackAdjust. We'll then 5067 // fill out the trackAdjust array for all the remaining lines. 5068 const uint32_t repeatStart = (aSizingFunctions.mExplicitGridOffset + 5069 aSizingFunctions.mRepeatAutoStart); 5070 const uint32_t numRepeats = aSizingFunctions.NumRepeatTracks(); 5071 for (uint32_t i = 0; i < numRepeats; ++i) { 5072 if (numEmptyLines) { 5073 MOZ_ASSERT(trackAdjust.isSome()); 5074 (*trackAdjust)[repeatStart + i] = numEmptyLines; 5075 } 5076 if (aIsEmptyFunc(repeatStart + i)) { 5077 ++numEmptyLines; 5078 if (trackAdjust.isNothing()) { 5079 trackAdjust.emplace(aNumGridLines); 5080 trackAdjust->SetLength(aNumGridLines); 5081 PodZero(trackAdjust->Elements(), trackAdjust->Length()); 5082 } 5083 5084 aSizingFunctions.mRemovedRepeatTracks[i] = true; 5085 } 5086 } 5087 // Fill out the trackAdjust array for all the tracks after the repeats. 5088 if (numEmptyLines) { 5089 for (uint32_t line = repeatStart + numRepeats; line < aNumGridLines; 5090 ++line) { 5091 (*trackAdjust)[line] = numEmptyLines; 5092 } 5093 } 5094 } 5095 5096 return trackAdjust; 5097 } 5098 5099 void nsGridContainerFrame::Grid::SubgridPlaceGridItems( 5100 GridReflowInput& aParentGridRI, Grid* aParentGrid, 5101 const GridItemInfo& aGridItem) { 5102 MOZ_ASSERT(aGridItem.mArea.IsDefinite() || 5103 aGridItem.mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW), 5104 "the subgrid's lines should be resolved by now"); 5105 if (aGridItem.IsSubgrid(LogicalAxis::Inline)) { 5106 aParentGridRI.mFrame->AddStateBits(NS_STATE_GRID_HAS_COL_SUBGRID_ITEM); 5107 } 5108 if (aGridItem.IsSubgrid(LogicalAxis::Block)) { 5109 aParentGridRI.mFrame->AddStateBits(NS_STATE_GRID_HAS_ROW_SUBGRID_ITEM); 5110 } 5111 auto* childGrid = aGridItem.SubgridFrame(); 5112 const auto* pos = childGrid->StylePosition(); 5113 childGrid->NormalizeChildLists(); 5114 GridReflowInput gridRI(childGrid, aParentGridRI.mRenderingContext); 5115 childGrid->InitImplicitNamedAreas(pos); 5116 5117 const bool isOrthogonal = aParentGridRI.mWM.IsOrthogonalTo(gridRI.mWM); 5118 // Record the subgrid's GridArea in a frame property. 5119 auto* subgrid = childGrid->GetProperty(Subgrid::Prop()); 5120 if (!subgrid) { 5121 subgrid = new Subgrid(aGridItem.mArea, isOrthogonal, aParentGridRI.mWM); 5122 childGrid->SetProperty(Subgrid::Prop(), subgrid); 5123 } else { 5124 subgrid->mArea = aGridItem.mArea; 5125 subgrid->mIsOrthogonal = isOrthogonal; 5126 subgrid->mGridItems.Clear(); 5127 subgrid->mAbsPosItems.Clear(); 5128 } 5129 5130 // Abs.pos. subgrids may have kAutoLine in their area. Map those to the edge 5131 // line in the parent's grid (zero-based line numbers). 5132 if (MOZ_UNLIKELY(subgrid->mArea.mCols.mStart == kAutoLine)) { 5133 subgrid->mArea.mCols.mStart = 0; 5134 } 5135 if (MOZ_UNLIKELY(subgrid->mArea.mCols.mEnd == kAutoLine)) { 5136 subgrid->mArea.mCols.mEnd = aParentGrid->mGridColEnd - 1; 5137 } 5138 if (MOZ_UNLIKELY(subgrid->mArea.mRows.mStart == kAutoLine)) { 5139 subgrid->mArea.mRows.mStart = 0; 5140 } 5141 if (MOZ_UNLIKELY(subgrid->mArea.mRows.mEnd == kAutoLine)) { 5142 subgrid->mArea.mRows.mEnd = aParentGrid->mGridRowEnd - 1; 5143 } 5144 5145 MOZ_ASSERT((subgrid->mArea.mCols.Extent() > 0 && 5146 subgrid->mArea.mRows.Extent() > 0) || 5147 gridRI.mGridItems.IsEmpty(), 5148 "subgrid needs at least one track for its items"); 5149 5150 // The min/sz/max sizes are the input to the "repeat-to-fill" algorithm: 5151 // https://drafts.csswg.org/css-grid-2/#auto-repeat 5152 // They're only used for auto-repeat in a non-subgridded axis so we skip 5153 // computing them otherwise. 5154 RepeatTrackSizingInput repeatSizing(gridRI.mWM); 5155 if (!childGrid->IsColSubgrid() && gridRI.mColFunctions.mHasRepeatAuto) { 5156 // FIXME: Bug 1918794. Figure out if it is fine to pass Nothing() here. It 5157 // seems we use a different way to calculate the size if the container is a 5158 // subgrid. Otherwise, we may have to know the area size that this grid item 5159 // is placed, and pass the area size as the containing block size to this 5160 // function. 5161 repeatSizing.InitFromStyle(LogicalAxis::Inline, gridRI.mWM, gridRI.mFrame, 5162 gridRI.mFrame->Style(), 5163 gridRI.mFrame->GetAspectRatio(), Nothing()); 5164 } 5165 if (!childGrid->IsRowSubgrid() && gridRI.mRowFunctions.mHasRepeatAuto) { 5166 // FIXME: Bug 1918794. Same as above. 5167 repeatSizing.InitFromStyle(LogicalAxis::Block, gridRI.mWM, gridRI.mFrame, 5168 gridRI.mFrame->Style(), 5169 gridRI.mFrame->GetAspectRatio(), Nothing()); 5170 } 5171 5172 PlaceGridItems(gridRI, repeatSizing); 5173 5174 subgrid->mGridItems = std::move(gridRI.mGridItems); 5175 subgrid->mAbsPosItems = std::move(gridRI.mAbsPosItems); 5176 subgrid->mGridColEnd = mGridColEnd; 5177 subgrid->mGridRowEnd = mGridRowEnd; 5178 } 5179 5180 void nsGridContainerFrame::Grid::PlaceGridItems( 5181 GridReflowInput& aGridRI, const RepeatTrackSizingInput& aSizes) { 5182 MOZ_ASSERT(mCellMap.mCells.IsEmpty(), "unexpected entries in cell map"); 5183 5184 mAreas = aGridRI.mFrame->GetImplicitNamedAreas(); 5185 5186 if (aGridRI.mFrame->HasSubgridItems() || aGridRI.mFrame->IsSubgrid()) { 5187 if (auto* uts = aGridRI.mFrame->GetUsedTrackSizes()) { 5188 uts->mCanResolveLineRangeSize = {false, false}; 5189 uts->mTrackPlans[LogicalAxis::Inline].ClearAndRetainStorage(); 5190 uts->mTrackPlans[LogicalAxis::Block].ClearAndRetainStorage(); 5191 } 5192 } 5193 5194 // SubgridPlaceGridItems will set these if we find any subgrid items. 5195 aGridRI.mFrame->RemoveStateBits(NS_STATE_GRID_HAS_COL_SUBGRID_ITEM | 5196 NS_STATE_GRID_HAS_ROW_SUBGRID_ITEM); 5197 5198 // https://drafts.csswg.org/css-grid-2/#grid-definition 5199 // Initialize the end lines of the Explicit Grid (mExplicitGridCol[Row]End). 5200 // This is determined by the larger of the number of rows/columns defined 5201 // by 'grid-template-areas' and the 'grid-template-rows'/'-columns', plus one. 5202 // Also initialize the Implicit Grid (mGridCol[Row]End) to the same values. 5203 // Note that this is for a grid with a 1,1 origin. We'll change that 5204 // to a 0,0 based grid after placing definite lines. 5205 const nsStylePosition* const gridStyle = aGridRI.mGridStyle; 5206 const auto* areas = gridStyle->mGridTemplateAreas.IsNone() 5207 ? nullptr 5208 : &*gridStyle->mGridTemplateAreas.AsAreas(); 5209 const LineNameMap* parentLineNameMap = nullptr; 5210 const LineRange* subgridRange = nullptr; 5211 bool subgridAxisIsSameDirection = true; 5212 if (!aGridRI.mFrame->IsColSubgrid()) { 5213 aGridRI.mColFunctions.InitRepeatTracks( 5214 gridStyle->mColumnGap, aSizes.mMin.ISize(aGridRI.mWM), 5215 aSizes.mSize.ISize(aGridRI.mWM), aSizes.mMax.ISize(aGridRI.mWM)); 5216 uint32_t areaCols = areas ? areas->width + 1 : 1; 5217 mExplicitGridColEnd = 5218 aGridRI.mColFunctions.ComputeExplicitGridEnd(areaCols); 5219 } else { 5220 const auto* subgrid = aGridRI.mFrame->GetProperty(Subgrid::Prop()); 5221 subgridRange = &subgrid->SubgridCols(); 5222 uint32_t extent = subgridRange->Extent(); 5223 mExplicitGridColEnd = extent + 1; // the grid is 1-based at this point 5224 parentLineNameMap = 5225 ParentLineMapForAxis(subgrid->mIsOrthogonal, LogicalAxis::Inline); 5226 auto parentWM = 5227 aGridRI.mFrame->ParentGridContainerForSubgrid()->GetWritingMode(); 5228 subgridAxisIsSameDirection = 5229 aGridRI.mWM.ParallelAxisStartsOnSameSide(LogicalAxis::Inline, parentWM); 5230 } 5231 mGridColEnd = mExplicitGridColEnd; 5232 LineNameMap colLineNameMap(gridStyle, mAreas, aGridRI.mColFunctions, 5233 parentLineNameMap, subgridRange, 5234 subgridAxisIsSameDirection); 5235 5236 if (!aGridRI.mFrame->IsRowSubgrid()) { 5237 const Maybe<nscoord> containBSize = aGridRI.mFrame->ContainIntrinsicBSize(); 5238 const nscoord repeatTrackSizingBSize = [&] { 5239 // This clamping only applies to auto sizes. 5240 if (containBSize && 5241 aSizes.mSize.BSize(aGridRI.mWM) == NS_UNCONSTRAINEDSIZE) { 5242 return CSSMinMax(*containBSize, aSizes.mMin.BSize(aGridRI.mWM), 5243 aSizes.mMax.BSize(aGridRI.mWM)); 5244 } 5245 return aSizes.mSize.BSize(aGridRI.mWM); 5246 }(); 5247 aGridRI.mRowFunctions.InitRepeatTracks( 5248 gridStyle->mRowGap, aSizes.mMin.BSize(aGridRI.mWM), 5249 repeatTrackSizingBSize, aSizes.mMax.BSize(aGridRI.mWM)); 5250 uint32_t areaRows = areas ? areas->strings.Length() + 1 : 1; 5251 mExplicitGridRowEnd = 5252 aGridRI.mRowFunctions.ComputeExplicitGridEnd(areaRows); 5253 parentLineNameMap = nullptr; 5254 subgridRange = nullptr; 5255 } else { 5256 const auto* subgrid = aGridRI.mFrame->GetProperty(Subgrid::Prop()); 5257 subgridRange = &subgrid->SubgridRows(); 5258 uint32_t extent = subgridRange->Extent(); 5259 mExplicitGridRowEnd = extent + 1; // the grid is 1-based at this point 5260 parentLineNameMap = 5261 ParentLineMapForAxis(subgrid->mIsOrthogonal, LogicalAxis::Block); 5262 auto parentWM = 5263 aGridRI.mFrame->ParentGridContainerForSubgrid()->GetWritingMode(); 5264 subgridAxisIsSameDirection = 5265 aGridRI.mWM.ParallelAxisStartsOnSameSide(LogicalAxis::Block, parentWM); 5266 } 5267 mGridRowEnd = mExplicitGridRowEnd; 5268 LineNameMap rowLineNameMap(gridStyle, mAreas, aGridRI.mRowFunctions, 5269 parentLineNameMap, subgridRange, 5270 subgridAxisIsSameDirection); 5271 5272 const bool isSubgridOrItemInSubgrid = 5273 aGridRI.mFrame->IsSubgrid() || !!mParentGrid; 5274 auto SetSubgridChildEdgeBits = 5275 [this, isSubgridOrItemInSubgrid](GridItemInfo& aItem) -> void { 5276 if (isSubgridOrItemInSubgrid) { 5277 const auto& area = aItem.mArea; 5278 if (area.mCols.mStart == 0) { 5279 aItem.mState[LogicalAxis::Inline] |= ItemState::eStartEdge; 5280 } 5281 if (area.mCols.mEnd == mGridColEnd) { 5282 aItem.mState[LogicalAxis::Inline] |= ItemState::eEndEdge; 5283 } 5284 if (area.mRows.mStart == 0) { 5285 aItem.mState[LogicalAxis::Block] |= ItemState::eStartEdge; 5286 } 5287 if (area.mRows.mEnd == mGridRowEnd) { 5288 aItem.mState[LogicalAxis::Block] |= ItemState::eEndEdge; 5289 } 5290 } 5291 }; 5292 5293 SetLineMaps(&colLineNameMap, &rowLineNameMap); 5294 5295 // https://drafts.csswg.org/css-grid-2/#line-placement 5296 // Resolve definite positions per spec chapter 8.3. 5297 int32_t minCol = 1; 5298 int32_t minRow = 1; 5299 aGridRI.mGridItems.ClearAndRetainStorage(); 5300 aGridRI.mIter.Reset(); 5301 for (; !aGridRI.mIter.AtEnd(); aGridRI.mIter.Next()) { 5302 nsIFrame* child = *aGridRI.mIter; 5303 GridItemInfo* info = aGridRI.mGridItems.AppendElement(GridItemInfo( 5304 child, 5305 PlaceDefinite(child, colLineNameMap, rowLineNameMap, gridStyle))); 5306 MOZ_ASSERT(aGridRI.mIter.ItemIndex() == aGridRI.mGridItems.Length() - 1, 5307 "ItemIndex() is broken"); 5308 GridArea& area = info->mArea; 5309 if (area.mCols.IsDefinite()) { 5310 minCol = std::min(minCol, area.mCols.mUntranslatedStart); 5311 } 5312 if (area.mRows.IsDefinite()) { 5313 minRow = std::min(minRow, area.mRows.mUntranslatedStart); 5314 } 5315 } 5316 5317 // Translate the whole grid so that the top-/left-most area is at 0,0. 5318 mExplicitGridOffsetCol = 1 - minCol; // minCol/Row is always <= 1, see above 5319 mExplicitGridOffsetRow = 1 - minRow; 5320 aGridRI.mColFunctions.mExplicitGridOffset = mExplicitGridOffsetCol; 5321 aGridRI.mRowFunctions.mExplicitGridOffset = mExplicitGridOffsetRow; 5322 const int32_t offsetToColZero = int32_t(mExplicitGridOffsetCol) - 1; 5323 const int32_t offsetToRowZero = int32_t(mExplicitGridOffsetRow) - 1; 5324 const bool isRowMasonry = aGridRI.mFrame->IsRowMasonry(); 5325 const bool isColMasonry = aGridRI.mFrame->IsColMasonry(); 5326 const bool isMasonry = isColMasonry || isRowMasonry; 5327 mGridColEnd += offsetToColZero; 5328 mGridRowEnd += offsetToRowZero; 5329 const uint32_t gridAxisTrackCount = isRowMasonry ? mGridColEnd : mGridRowEnd; 5330 aGridRI.mIter.Reset(); 5331 for (; !aGridRI.mIter.AtEnd(); aGridRI.mIter.Next()) { 5332 auto& item = aGridRI.mGridItems[aGridRI.mIter.ItemIndex()]; 5333 GridArea& area = item.mArea; 5334 if (area.mCols.IsDefinite()) { 5335 area.mCols.mStart = area.mCols.mUntranslatedStart + offsetToColZero; 5336 area.mCols.mEnd = area.mCols.mUntranslatedEnd + offsetToColZero; 5337 } 5338 if (area.mRows.IsDefinite()) { 5339 area.mRows.mStart = area.mRows.mUntranslatedStart + offsetToRowZero; 5340 area.mRows.mEnd = area.mRows.mUntranslatedEnd + offsetToRowZero; 5341 } 5342 if (area.IsDefinite()) { 5343 if (isMasonry) { 5344 item.MaybeInhibitSubgridInMasonry(aGridRI.mFrame, gridAxisTrackCount); 5345 } 5346 if (item.IsSubgrid()) { 5347 Grid grid(this); 5348 grid.SubgridPlaceGridItems(aGridRI, this, item); 5349 } 5350 mCellMap.Fill(area); 5351 InflateGridFor(area); 5352 SetSubgridChildEdgeBits(item); 5353 } 5354 } 5355 5356 // https://drafts.csswg.org/css-grid-2/#auto-placement-algo 5357 // Step 1, place 'auto' items that have one definite position - 5358 // definite row (column) for grid-auto-flow:row (column). 5359 auto flowStyle = gridStyle->mGridAutoFlow; 5360 const bool isRowOrder = 5361 isMasonry ? isRowMasonry : !!(flowStyle & StyleGridAutoFlow::ROW); 5362 const bool isSparse = !(flowStyle & StyleGridAutoFlow::DENSE); 5363 uint32_t clampMaxColLine = colLineNameMap.mClampMaxLine + offsetToColZero; 5364 uint32_t clampMaxRowLine = rowLineNameMap.mClampMaxLine + offsetToRowZero; 5365 // We need 1 cursor per row (or column) if placement is sparse. 5366 { 5367 Maybe<nsTHashMap<nsUint32HashKey, uint32_t>> cursors; 5368 if (isSparse) { 5369 cursors.emplace(); 5370 } 5371 auto placeAutoMinorFunc = 5372 isRowOrder ? &Grid::PlaceAutoCol : &Grid::PlaceAutoRow; 5373 uint32_t clampMaxLine = isRowOrder ? clampMaxColLine : clampMaxRowLine; 5374 aGridRI.mIter.Reset(); 5375 for (; !aGridRI.mIter.AtEnd(); aGridRI.mIter.Next()) { 5376 auto& item = aGridRI.mGridItems[aGridRI.mIter.ItemIndex()]; 5377 GridArea& area = item.mArea; 5378 LineRange& major = isRowOrder ? area.mRows : area.mCols; 5379 LineRange& minor = isRowOrder ? area.mCols : area.mRows; 5380 if (major.IsDefinite() && minor.IsAuto()) { 5381 // Items with 'auto' in the minor dimension only. 5382 const uint32_t cursor = isSparse ? cursors->Get(major.mStart) : 0; 5383 (this->*placeAutoMinorFunc)(cursor, &area, clampMaxLine); 5384 if (isMasonry) { 5385 item.MaybeInhibitSubgridInMasonry(aGridRI.mFrame, gridAxisTrackCount); 5386 } 5387 if (item.IsSubgrid()) { 5388 Grid grid(this); 5389 grid.SubgridPlaceGridItems(aGridRI, this, item); 5390 } 5391 mCellMap.Fill(area); 5392 SetSubgridChildEdgeBits(item); 5393 if (isSparse) { 5394 cursors->InsertOrUpdate(major.mStart, minor.mEnd); 5395 } 5396 } 5397 InflateGridFor(area); // Step 2, inflating for auto items too 5398 } 5399 } 5400 5401 // XXX NOTE possible spec issue. 5402 // XXX It's unclear if the remaining major-dimension auto and 5403 // XXX auto in both dimensions should use the same cursor or not, 5404 // XXX https://www.w3.org/Bugs/Public/show_bug.cgi?id=16044 5405 // XXX seems to indicate it shouldn't. 5406 // XXX https://drafts.csswg.org/css-grid-2/#auto-placement-algo 5407 // XXX now says it should (but didn't in earlier versions) 5408 5409 // Step 3, place the remaining grid items 5410 uint32_t cursorMajor = 0; // for 'dense' these two cursors will stay at 0,0 5411 uint32_t cursorMinor = 0; 5412 auto placeAutoMajorFunc = 5413 isRowOrder ? &Grid::PlaceAutoRow : &Grid::PlaceAutoCol; 5414 uint32_t clampMaxMajorLine = isRowOrder ? clampMaxRowLine : clampMaxColLine; 5415 aGridRI.mIter.Reset(); 5416 for (; !aGridRI.mIter.AtEnd(); aGridRI.mIter.Next()) { 5417 auto& item = aGridRI.mGridItems[aGridRI.mIter.ItemIndex()]; 5418 GridArea& area = item.mArea; 5419 MOZ_ASSERT(*aGridRI.mIter == item.mFrame, 5420 "iterator out of sync with aState.mGridItems"); 5421 LineRange& major = isRowOrder ? area.mRows : area.mCols; 5422 LineRange& minor = isRowOrder ? area.mCols : area.mRows; 5423 if (major.IsAuto()) { 5424 if (minor.IsDefinite()) { 5425 // Items with 'auto' in the major dimension only. 5426 if (isSparse) { 5427 if (minor.mStart < cursorMinor) { 5428 ++cursorMajor; 5429 } 5430 cursorMinor = minor.mStart; 5431 } 5432 (this->*placeAutoMajorFunc)(cursorMajor, &area, clampMaxMajorLine); 5433 if (isSparse) { 5434 cursorMajor = major.mStart; 5435 } 5436 } else { 5437 // Items with 'auto' in both dimensions. 5438 if (isRowOrder) { 5439 PlaceAutoAutoInRowOrder(cursorMinor, cursorMajor, &area, 5440 clampMaxColLine, clampMaxRowLine); 5441 } else { 5442 PlaceAutoAutoInColOrder(cursorMajor, cursorMinor, &area, 5443 clampMaxColLine, clampMaxRowLine); 5444 } 5445 if (isSparse) { 5446 cursorMajor = major.mStart; 5447 cursorMinor = minor.mEnd; 5448 #ifdef DEBUG 5449 uint32_t gridMajorEnd = isRowOrder ? mGridRowEnd : mGridColEnd; 5450 uint32_t gridMinorEnd = isRowOrder ? mGridColEnd : mGridRowEnd; 5451 MOZ_ASSERT(cursorMajor <= gridMajorEnd, 5452 "we shouldn't need to place items further than 1 track " 5453 "past the current end of the grid, in major dimension"); 5454 MOZ_ASSERT(cursorMinor <= gridMinorEnd, 5455 "we shouldn't add implicit minor tracks for auto/auto"); 5456 #endif 5457 } 5458 } 5459 if (isMasonry) { 5460 item.MaybeInhibitSubgridInMasonry(aGridRI.mFrame, gridAxisTrackCount); 5461 } 5462 if (item.IsSubgrid()) { 5463 Grid grid(this); 5464 grid.SubgridPlaceGridItems(aGridRI, this, item); 5465 } 5466 mCellMap.Fill(area); 5467 InflateGridFor(area); 5468 SetSubgridChildEdgeBits(item); 5469 // XXXmats it might be possible to optimize this a bit for masonry layout 5470 // if this item was placed in the 2nd row && !isSparse, or the 1st row 5471 // is full. Still gotta inflate the grid for all items though to make 5472 // the grid large enough... 5473 } 5474 } 5475 5476 // Force all items into the 1st/2nd track and have span 1 in the masonry axis. 5477 // (See comment on nsGridContainerFrame::MasonryLayout().) 5478 if (isMasonry) { 5479 auto masonryAxis = isRowMasonry ? LogicalAxis::Block : LogicalAxis::Inline; 5480 aGridRI.mIter.Reset(); 5481 for (; !aGridRI.mIter.AtEnd(); aGridRI.mIter.Next()) { 5482 auto& item = aGridRI.mGridItems[aGridRI.mIter.ItemIndex()]; 5483 auto& masonryRange = item.mArea.LineRangeForAxis(masonryAxis); 5484 masonryRange.mStart = std::min(masonryRange.mStart, 1U); 5485 masonryRange.mEnd = masonryRange.mStart + 1U; 5486 } 5487 } 5488 5489 if (auto* absCB = aGridRI.mFrame->GetAbsoluteContainingBlock(); 5490 absCB && absCB->PrepareAbsoluteFrames(aGridRI.mFrame)) { 5491 // 10.1. With a Grid Container as Containing Block 5492 // https://drafts.csswg.org/css-grid-2/#abspos-items 5493 // We only resolve definite lines here; we'll align auto positions to the 5494 // grid container later during reflow. 5495 const nsFrameList& children = absCB->GetChildList(); 5496 const int32_t offsetToColZero = int32_t(mExplicitGridOffsetCol) - 1; 5497 const int32_t offsetToRowZero = int32_t(mExplicitGridOffsetRow) - 1; 5498 // Untranslate the grid again temporarily while resolving abs.pos. lines. 5499 AutoRestore<uint32_t> zeroOffsetGridColEnd(mGridColEnd); 5500 AutoRestore<uint32_t> zeroOffsetGridRowEnd(mGridRowEnd); 5501 mGridColEnd -= offsetToColZero; 5502 mGridRowEnd -= offsetToRowZero; 5503 aGridRI.mAbsPosItems.ClearAndRetainStorage(); 5504 for (nsIFrame* child : children) { 5505 GridItemInfo* info = aGridRI.mAbsPosItems.AppendElement(GridItemInfo( 5506 child, 5507 PlaceAbsPos(child, colLineNameMap, rowLineNameMap, gridStyle))); 5508 GridArea& area = info->mArea; 5509 if (area.mCols.mUntranslatedStart != int32_t(kAutoLine)) { 5510 area.mCols.mStart = area.mCols.mUntranslatedStart + offsetToColZero; 5511 if (isColMasonry) { 5512 // XXXmats clamp any non-auto line to 0 or 1. This is intended to 5513 // allow authors to address the start/end of the masonry box. 5514 // This is experimental at this point though and needs author feedback 5515 // and spec work to sort out what is desired and how it should work. 5516 // See https://github.com/w3c/csswg-drafts/issues/4650 5517 area.mCols.mStart = std::min(area.mCols.mStart, 1U); 5518 } 5519 } 5520 if (area.mCols.mUntranslatedEnd != int32_t(kAutoLine)) { 5521 area.mCols.mEnd = area.mCols.mUntranslatedEnd + offsetToColZero; 5522 if (isColMasonry) { 5523 // ditto 5524 area.mCols.mEnd = std::min(area.mCols.mEnd, 1U); 5525 } 5526 } 5527 if (area.mRows.mUntranslatedStart != int32_t(kAutoLine)) { 5528 area.mRows.mStart = area.mRows.mUntranslatedStart + offsetToRowZero; 5529 if (isRowMasonry) { 5530 // ditto 5531 area.mRows.mStart = std::min(area.mRows.mStart, 1U); 5532 } 5533 } 5534 if (area.mRows.mUntranslatedEnd != int32_t(kAutoLine)) { 5535 area.mRows.mEnd = area.mRows.mUntranslatedEnd + offsetToRowZero; 5536 if (isRowMasonry) { 5537 // ditto 5538 area.mRows.mEnd = std::min(area.mRows.mEnd, 1U); 5539 } 5540 } 5541 if (isMasonry) { 5542 info->MaybeInhibitSubgridInMasonry(aGridRI.mFrame, gridAxisTrackCount); 5543 } 5544 5545 // An abs.pos. subgrid with placement auto/1 or -1/auto technically 5546 // doesn't span any parent tracks. Inhibit subgridding in this case. 5547 if (info->IsSubgrid(LogicalAxis::Inline)) { 5548 if (info->mArea.mCols.mStart == zeroOffsetGridColEnd.SavedValue() || 5549 info->mArea.mCols.mEnd == 0) { 5550 info->InhibitSubgrid(aGridRI.mFrame, LogicalAxis::Inline); 5551 } 5552 } 5553 if (info->IsSubgrid(LogicalAxis::Block)) { 5554 if (info->mArea.mRows.mStart == zeroOffsetGridRowEnd.SavedValue() || 5555 info->mArea.mRows.mEnd == 0) { 5556 info->InhibitSubgrid(aGridRI.mFrame, LogicalAxis::Block); 5557 } 5558 } 5559 5560 if (info->IsSubgrid()) { 5561 Grid grid(this); 5562 grid.SubgridPlaceGridItems(aGridRI, this, *info); 5563 } 5564 } 5565 } 5566 5567 // Count empty 'auto-fit' tracks in the repeat() range. 5568 // |colAdjust| will have a count for each line in the grid of how many 5569 // tracks were empty between the start of the grid and that line. 5570 5571 Maybe<nsTArray<uint32_t>> colAdjust; 5572 uint32_t numEmptyCols = 0; 5573 if (aGridRI.mColFunctions.mHasRepeatAuto && 5574 gridStyle->mGridTemplateColumns.GetRepeatAutoValue()->count.IsAutoFit()) { 5575 const auto& cellMap = mCellMap; 5576 colAdjust = CalculateAdjustForAutoFitElements( 5577 &numEmptyCols, aGridRI.mColFunctions, mGridColEnd + 1, 5578 [&cellMap](uint32_t i) -> bool { return cellMap.IsEmptyCol(i); }); 5579 } 5580 5581 // Do similar work for the row tracks, with the same logic. 5582 Maybe<nsTArray<uint32_t>> rowAdjust; 5583 uint32_t numEmptyRows = 0; 5584 if (aGridRI.mRowFunctions.mHasRepeatAuto && 5585 gridStyle->mGridTemplateRows.GetRepeatAutoValue()->count.IsAutoFit()) { 5586 const auto& cellMap = mCellMap; 5587 rowAdjust = CalculateAdjustForAutoFitElements( 5588 &numEmptyRows, aGridRI.mRowFunctions, mGridRowEnd + 1, 5589 [&cellMap](uint32_t i) -> bool { return cellMap.IsEmptyRow(i); }); 5590 } 5591 MOZ_ASSERT((numEmptyCols > 0) == colAdjust.isSome()); 5592 MOZ_ASSERT((numEmptyRows > 0) == rowAdjust.isSome()); 5593 // Remove the empty 'auto-fit' tracks we found above, if any. 5594 if (numEmptyCols || numEmptyRows) { 5595 // Adjust the line numbers in the grid areas. 5596 for (auto& item : aGridRI.mGridItems) { 5597 if (numEmptyCols) { 5598 item.AdjustForRemovedTracks(LogicalAxis::Inline, *colAdjust); 5599 } 5600 if (numEmptyRows) { 5601 item.AdjustForRemovedTracks(LogicalAxis::Block, *rowAdjust); 5602 } 5603 } 5604 for (auto& item : aGridRI.mAbsPosItems) { 5605 if (numEmptyCols) { 5606 item.AdjustForRemovedTracks(LogicalAxis::Inline, *colAdjust); 5607 } 5608 if (numEmptyRows) { 5609 item.AdjustForRemovedTracks(LogicalAxis::Block, *rowAdjust); 5610 } 5611 } 5612 // Adjust the grid size. 5613 mGridColEnd -= numEmptyCols; 5614 mExplicitGridColEnd -= numEmptyCols; 5615 mGridRowEnd -= numEmptyRows; 5616 mExplicitGridRowEnd -= numEmptyRows; 5617 // Adjust the track mapping to unmap the removed tracks. 5618 auto colRepeatCount = aGridRI.mColFunctions.NumRepeatTracks(); 5619 aGridRI.mColFunctions.SetNumRepeatTracks(colRepeatCount - numEmptyCols); 5620 auto rowRepeatCount = aGridRI.mRowFunctions.NumRepeatTracks(); 5621 aGridRI.mRowFunctions.SetNumRepeatTracks(rowRepeatCount - numEmptyRows); 5622 } 5623 5624 // Update the line boundaries of the implicit grid areas, if needed. 5625 if (mAreas && aGridRI.mFrame->HasAnyStateBits(NS_STATE_GRID_COMPUTED_INFO)) { 5626 for (auto iter = mAreas->iter(); !iter.done(); iter.next()) { 5627 auto& areaInfo = iter.get().value(); 5628 5629 // Resolve the lines for the area. We use the name of the area as the 5630 // name of the lines, knowing that the line placement algorithm will 5631 // add the -start and -end suffixes as appropriate for layout. 5632 StyleGridLine lineStartAndEnd; 5633 lineStartAndEnd.ident._0 = areaInfo.name; 5634 5635 LineRange columnLines = 5636 ResolveLineRange(lineStartAndEnd, lineStartAndEnd, colLineNameMap, 5637 LogicalAxis::Inline, mExplicitGridColEnd, gridStyle); 5638 5639 LineRange rowLines = 5640 ResolveLineRange(lineStartAndEnd, lineStartAndEnd, rowLineNameMap, 5641 LogicalAxis::Block, mExplicitGridRowEnd, gridStyle); 5642 5643 // Put the resolved line indices back into the area structure. 5644 areaInfo.columns.start = columnLines.mStart + mExplicitGridOffsetCol; 5645 areaInfo.columns.end = columnLines.mEnd + mExplicitGridOffsetCol; 5646 areaInfo.rows.start = rowLines.mStart + mExplicitGridOffsetRow; 5647 areaInfo.rows.end = rowLines.mEnd + mExplicitGridOffsetRow; 5648 } 5649 } 5650 } 5651 5652 void nsGridContainerFrame::Tracks::Initialize( 5653 const TrackSizingFunctions& aFunctions, 5654 const NonNegativeLengthPercentageOrNormal& aGridGap, uint32_t aNumTracks, 5655 nscoord aContentBoxSize) { 5656 mSizes.SetLength(aNumTracks); 5657 mSizes.ZeroInitialize(); 5658 for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) { 5659 auto& sz = mSizes[i]; 5660 mStateUnion |= sz.Initialize(aContentBoxSize, aFunctions.SizingFor(i)); 5661 if (mIsMasonry) { 5662 sz.mBase = aContentBoxSize; 5663 sz.mLimit = aContentBoxSize; 5664 } 5665 } 5666 mGridGap = nsLayoutUtils::ResolveGapToLength(aGridGap, aContentBoxSize); 5667 mContentBoxSize = aContentBoxSize; 5668 } 5669 5670 /** 5671 * Reflow aChild in the given aAvailableSize. 5672 */ 5673 static nscoord MeasuringReflow(nsIFrame* aChild, 5674 const ReflowInput* aReflowInput, gfxContext* aRC, 5675 const LogicalSize& aAvailableSize, 5676 const LogicalSize& aCBSize, 5677 nscoord aIMinSizeClamp = NS_MAXSIZE, 5678 nscoord aBMinSizeClamp = NS_MAXSIZE) { 5679 MOZ_ASSERT(aChild->IsGridItem(), "aChild should be a grid item!"); 5680 auto* parent = static_cast<nsGridContainerFrame*>(aChild->GetParent()); 5681 nsPresContext* pc = aChild->PresContext(); 5682 Maybe<ReflowInput> dummyParentState; 5683 const ReflowInput* rs = aReflowInput; 5684 if (!aReflowInput) { 5685 MOZ_ASSERT(!parent->HasAnyStateBits(NS_FRAME_IN_REFLOW)); 5686 dummyParentState.emplace( 5687 pc, parent, aRC, 5688 LogicalSize(parent->GetWritingMode(), 0, NS_UNCONSTRAINEDSIZE), 5689 ReflowInput::InitFlag::DummyParentReflowInput); 5690 rs = dummyParentState.ptr(); 5691 } 5692 #ifdef DEBUG 5693 // This will suppress various ABSURD_SIZE warnings for this reflow. 5694 parent->SetProperty(nsContainerFrame::DebugReflowingWithInfiniteISize(), 5695 true); 5696 #endif 5697 auto wm = aChild->GetWritingMode(); 5698 ComputeSizeFlags csFlags = ComputeSizeFlag::IsGridMeasuringReflow; 5699 // Shrink-wrap grid items that will be aligned (rather than stretched) in 5700 // their own inline axis. 5701 if (!parent->GridItemShouldStretch(aChild, LogicalAxis::Inline)) { 5702 csFlags += ComputeSizeFlag::ShrinkWrap; 5703 } 5704 if (aAvailableSize.ISize(wm) == INFINITE_ISIZE_COORD) { 5705 csFlags += ComputeSizeFlag::ShrinkWrap; 5706 } 5707 if (aIMinSizeClamp != NS_MAXSIZE) { 5708 csFlags += ComputeSizeFlag::IClampMarginBoxMinSize; 5709 } 5710 if (aBMinSizeClamp != NS_MAXSIZE) { 5711 csFlags += ComputeSizeFlag::BClampMarginBoxMinSize; 5712 aChild->SetProperty(nsIFrame::BClampMarginBoxMinSizeProperty(), 5713 aBMinSizeClamp); 5714 } else { 5715 aChild->RemoveProperty(nsIFrame::BClampMarginBoxMinSizeProperty()); 5716 } 5717 ReflowInput childRI(pc, *rs, aChild, aAvailableSize, Some(aCBSize), {}, {}, 5718 csFlags); 5719 5720 // FIXME (perf): It would be faster to do this only if the previous reflow of 5721 // the child was not a measuring reflow, and only if the child does some of 5722 // the things that are affected by ComputeSizeFlag::IsGridMeasuringReflow. 5723 childRI.SetBResize(true); 5724 // Not 100% sure this is needed, but be conservative for now: 5725 childRI.SetBResizeForPercentages(true); 5726 5727 ReflowOutput childSize(childRI); 5728 nsReflowStatus childStatus; 5729 const nsIFrame::ReflowChildFlags flags = 5730 nsIFrame::ReflowChildFlags::NoMoveFrame | 5731 nsIFrame::ReflowChildFlags::NoDeleteNextInFlowChild; 5732 5733 // Reflowing the child might invalidate the cache, so we declare the variable 5734 // inside the if-statement to ensure it isn't accessed after it may have 5735 // become invalid. 5736 if (const GridItemCachedBAxisMeasurement* cachedMeasurement = 5737 aChild->GetProperty(GridItemCachedBAxisMeasurement::Prop()); 5738 cachedMeasurement && cachedMeasurement->IsValidFor(aChild, aCBSize)) { 5739 childSize.BSize(wm) = cachedMeasurement->BSize(); 5740 childSize.ISize(wm) = aChild->ISize(wm); 5741 nsContainerFrame::FinishReflowChild(aChild, pc, childSize, &childRI, wm, 5742 LogicalPoint(wm), nsSize(), flags); 5743 GRID_LOG( 5744 "[perf] MeasuringReflow accepted cached value=%d, child=%p, " 5745 "aCBSize.ISize=%d", 5746 cachedMeasurement->BSize(), aChild, aCBSize.ISize(wm)); 5747 return cachedMeasurement->BSize(); 5748 } 5749 5750 parent->ReflowChild(aChild, pc, childSize, childRI, wm, LogicalPoint(wm), 5751 nsSize(), flags, childStatus); 5752 nsContainerFrame::FinishReflowChild(aChild, pc, childSize, &childRI, wm, 5753 LogicalPoint(wm), nsSize(), flags); 5754 #ifdef DEBUG 5755 parent->RemoveProperty(nsContainerFrame::DebugReflowingWithInfiniteISize()); 5756 #endif 5757 5758 if (GridItemCachedBAxisMeasurement* cachedMeasurement = 5759 aChild->GetProperty(GridItemCachedBAxisMeasurement::Prop())) { 5760 cachedMeasurement->Update(aChild, aCBSize, childSize.BSize(wm)); 5761 GRID_LOG( 5762 "[perf] MeasuringReflow rejected but updated cached value=%d, " 5763 "child=%p, aCBSize.ISize=%d", 5764 cachedMeasurement->BSize(), aChild, aCBSize.ISize(wm)); 5765 } else { 5766 cachedMeasurement = new GridItemCachedBAxisMeasurement(aChild, aCBSize, 5767 childSize.BSize(wm)); 5768 aChild->SetProperty(GridItemCachedBAxisMeasurement::Prop(), 5769 cachedMeasurement); 5770 GRID_LOG( 5771 "[perf] MeasuringReflow created new cached value=%d, child=%p, " 5772 "aCBSize.ISize=%d", 5773 cachedMeasurement->BSize(), aChild, aCBSize.ISize(wm)); 5774 } 5775 5776 return childSize.BSize(wm); 5777 } 5778 5779 /** 5780 * Return the accumulated margin+border+padding in aAxis for aFrame (a subgrid) 5781 * and its ancestor subgrids. 5782 */ 5783 static LogicalMargin SubgridAccumulatedMarginBorderPadding( 5784 nsIFrame* aFrame, const Subgrid* aSubgrid, WritingMode aResultWM, 5785 LogicalAxis aAxis) { 5786 MOZ_ASSERT(aFrame->IsGridContainerFrame()); 5787 auto* subgridFrame = static_cast<nsGridContainerFrame*>(aFrame); 5788 LogicalMargin result(aSubgrid->mMarginBorderPadding); 5789 auto* parent = subgridFrame->ParentGridContainerForSubgrid(); 5790 auto subgridCBWM = parent->GetWritingMode(); 5791 auto childRange = aSubgrid->mArea.LineRangeForAxis(aAxis); 5792 bool skipStartSide = false; 5793 bool skipEndSide = false; 5794 auto axis = aSubgrid->mIsOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis; 5795 // If aFrame's parent is also a subgrid, then add its MBP on the edges that 5796 // are adjacent (i.e. start or end in the same track), recursively. 5797 // ("parent" refers to the grid-frame we're currently adding MBP for, 5798 // and "grandParent" its parent, as we walk up the chain.) 5799 while (parent->IsSubgrid(axis)) { 5800 auto* parentSubgrid = parent->GetProperty(Subgrid::Prop()); 5801 auto* grandParent = parent->ParentGridContainerForSubgrid(); 5802 auto parentCBWM = grandParent->GetWritingMode(); 5803 if (parentCBWM.IsOrthogonalTo(subgridCBWM)) { 5804 axis = GetOrthogonalAxis(axis); 5805 } 5806 const auto& parentRange = parentSubgrid->mArea.LineRangeForAxis(axis); 5807 bool sameDir = parentCBWM.ParallelAxisStartsOnSameSide(axis, subgridCBWM); 5808 if (sameDir) { 5809 skipStartSide |= childRange.mStart != 0; 5810 skipEndSide |= childRange.mEnd != parentRange.Extent(); 5811 } else { 5812 skipEndSide |= childRange.mStart != 0; 5813 skipStartSide |= childRange.mEnd != parentRange.Extent(); 5814 } 5815 if (skipStartSide && skipEndSide) { 5816 break; 5817 } 5818 auto mbp = 5819 parentSubgrid->mMarginBorderPadding.ConvertTo(subgridCBWM, parentCBWM); 5820 if (skipStartSide) { 5821 mbp.Start(aAxis, subgridCBWM) = nscoord(0); 5822 } 5823 if (skipEndSide) { 5824 mbp.End(aAxis, subgridCBWM) = nscoord(0); 5825 } 5826 result += mbp; 5827 parent = grandParent; 5828 childRange = parentRange; 5829 } 5830 return result.ConvertTo(aResultWM, subgridCBWM); 5831 } 5832 5833 /** 5834 * Return the [min|max]-content contribution of aChild to its parent (i.e. 5835 * the child's margin-box) in aAxis. 5836 */ 5837 static nscoord ContentContribution(const GridItemInfo& aGridItem, 5838 const GridReflowInput& aGridRI, 5839 LogicalAxis aAxis, 5840 const LogicalSize& aPercentageBasis, 5841 IntrinsicISizeType aConstraint, 5842 nscoord aMinSizeClamp = NS_MAXSIZE, 5843 const StyleSizeOverrides& aOverrides = {}) { 5844 nsIFrame* child = aGridItem.mFrame; 5845 5846 const WritingMode gridWM = aGridRI.mWM; 5847 nscoord extraMargin = 0; 5848 nsGridContainerFrame::Subgrid* subgrid = nullptr; 5849 if (child->GetParent() != aGridRI.mFrame) { 5850 // |child| is a subgrid descendant, so it contributes its subgrids' 5851 // margin+border+padding for any edge tracks that it spans. 5852 auto* subgridFrame = child->GetParent(); 5853 subgrid = subgridFrame->GetProperty(Subgrid::Prop()); 5854 const auto itemEdgeBits = aGridItem.mState[aAxis] & ItemState::eEdgeBits; 5855 if (itemEdgeBits) { 5856 LogicalMargin mbp = SubgridAccumulatedMarginBorderPadding( 5857 subgridFrame, subgrid, gridWM, aAxis); 5858 if (itemEdgeBits & ItemState::eStartEdge) { 5859 extraMargin += mbp.Start(aAxis, gridWM); 5860 } 5861 if (itemEdgeBits & ItemState::eEndEdge) { 5862 extraMargin += mbp.End(aAxis, gridWM); 5863 } 5864 } 5865 // It also contributes (half of) the subgrid's gap on its edges (if any) 5866 // subtracted by the non-subgrid ancestor grid container's gap. 5867 // Note that this can also be negative since it's considered a margin. 5868 if (itemEdgeBits != ItemState::eEdgeBits) { 5869 const auto subgridAxis = 5870 gridWM.ConvertAxisTo(aAxis, subgridFrame->GetWritingMode()); 5871 auto& gapStyle = subgridAxis == LogicalAxis::Block 5872 ? subgridFrame->StylePosition()->mRowGap 5873 : subgridFrame->StylePosition()->mColumnGap; 5874 if (!gapStyle.IsNormal()) { 5875 auto subgridExtent = subgridAxis == LogicalAxis::Block 5876 ? subgrid->mGridRowEnd 5877 : subgrid->mGridColEnd; 5878 if (subgridExtent > 1) { 5879 nscoord subgridGap = 5880 nsLayoutUtils::ResolveGapToLength(gapStyle, NS_UNCONSTRAINEDSIZE); 5881 const auto& tracks = aGridRI.TracksFor(aAxis); 5882 auto gapDelta = subgridGap - tracks.mGridGap; 5883 if (!itemEdgeBits) { 5884 extraMargin += gapDelta; 5885 } else { 5886 extraMargin += gapDelta / 2; 5887 } 5888 } 5889 } 5890 } 5891 } 5892 5893 gfxContext* rc = &aGridRI.mRenderingContext; 5894 PhysicalAxis axis = gridWM.PhysicalAxis(aAxis); 5895 nscoord size = nsLayoutUtils::IntrinsicForAxis( 5896 axis, rc, child, aConstraint, Some(aPercentageBasis), 5897 nsLayoutUtils::BAIL_IF_REFLOW_NEEDED, aMinSizeClamp, aOverrides); 5898 auto childWM = child->GetWritingMode(); 5899 const bool isOrthogonal = childWM.IsOrthogonalTo(gridWM); 5900 auto childAxis = isOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis; 5901 if (size == NS_INTRINSIC_ISIZE_UNKNOWN && childAxis == LogicalAxis::Block) { 5902 if (aGridRI.mIsGridIntrinsicSizing && aAxis == LogicalAxis::Block) { 5903 // We may reach here while computing the grid container's min-content 5904 // contribution in ComputeIntrinsicISize(), potentially during row size 5905 // resolution. In this context, the main reason for computing row sizes is 5906 // to transfer the child's block-size to the inline-axis via aspect-ratio, 5907 // contributing to the grid container's intrinsic inline-size in a later 5908 // column size resolution. Since an indefinite block-size cannot be 5909 // transferred in this way, we can safely skip MeasuringReflow() and 5910 // simply use zero as a dummy value because the value does not affect the 5911 // result. 5912 size = 0; 5913 } else { 5914 // We need to reflow the child to find its BSize contribution. 5915 nscoord availISize = INFINITE_ISIZE_COORD; 5916 nscoord availBSize = NS_UNCONSTRAINEDSIZE; 5917 // The next two variables are MinSizeClamp values in the child's axes. 5918 nscoord iMinSizeClamp = NS_MAXSIZE; 5919 nscoord bMinSizeClamp = NS_MAXSIZE; 5920 LogicalSize cbSize = aPercentageBasis; 5921 // Below, we try to resolve the child's grid-area size in its inline-axis 5922 // to use as the CB/Available size in the MeasuringReflow that follows. 5923 if (child->GetParent() != aGridRI.mFrame) { 5924 // This item is a child of a subgrid descendant. 5925 auto* subgridFrame = 5926 static_cast<nsGridContainerFrame*>(child->GetParent()); 5927 MOZ_ASSERT(subgridFrame->IsGridContainerFrame()); 5928 auto* uts = subgridFrame->GetProperty(UsedTrackSizes::Prop()); 5929 if (!uts) { 5930 uts = new UsedTrackSizes(); 5931 subgridFrame->SetProperty(UsedTrackSizes::Prop(), uts); 5932 } 5933 // The grid-item's inline-axis as expressed in the subgrid's WM. 5934 const auto subgridAxis = childWM.ConvertAxisTo( 5935 LogicalAxis::Inline, subgridFrame->GetWritingMode()); 5936 uts->ResolveTrackSizesForAxis(subgridFrame, subgridAxis, *rc); 5937 if (uts->mCanResolveLineRangeSize[subgridAxis]) { 5938 auto* subgrid = 5939 subgridFrame->GetProperty(nsGridContainerFrame::Subgrid::Prop()); 5940 const GridItemInfo* originalItem = nullptr; 5941 for (const auto& item : subgrid->mGridItems) { 5942 if (item.mFrame == child) { 5943 originalItem = &item; 5944 break; 5945 } 5946 } 5947 MOZ_ASSERT(originalItem, "huh?"); 5948 const auto& range = originalItem->mArea.LineRangeForAxis(subgridAxis); 5949 const nscoord sz = range.ToLength(uts->mTrackPlans[subgridAxis]); 5950 if (childWM.IsOrthogonalTo(subgridFrame->GetWritingMode())) { 5951 availBSize = sz; 5952 cbSize.BSize(childWM) = sz; 5953 if (aGridItem.mState[aAxis] & ItemState::eClampMarginBoxMinSize) { 5954 bMinSizeClamp = sz; 5955 } 5956 } else { 5957 availISize = sz; 5958 cbSize.ISize(childWM) = sz; 5959 if (aGridItem.mState[aAxis] & ItemState::eClampMarginBoxMinSize) { 5960 iMinSizeClamp = sz; 5961 } 5962 } 5963 } 5964 } else { 5965 const LogicalAxis inlineAxisInChildWM = 5966 isOrthogonal ? LogicalAxis::Block : LogicalAxis::Inline; 5967 const nscoord colSize = cbSize.Size(inlineAxisInChildWM, childWM); 5968 if (colSize != NS_UNCONSTRAINEDSIZE) { 5969 MOZ_ASSERT(aGridRI.mCols.mCanResolveLineRangeSize, 5970 "Grid column sizes should be resolvable!"); 5971 if (isOrthogonal) { 5972 availBSize = colSize; 5973 if (aGridItem.mState[aAxis] & ItemState::eClampMarginBoxMinSize) { 5974 bMinSizeClamp = colSize; 5975 } 5976 } else { 5977 availISize = colSize; 5978 if (aGridItem.mState[aAxis] & ItemState::eClampMarginBoxMinSize) { 5979 iMinSizeClamp = colSize; 5980 } 5981 } 5982 } 5983 } 5984 if (isOrthogonal == (aAxis == LogicalAxis::Inline)) { 5985 bMinSizeClamp = aMinSizeClamp; 5986 } else { 5987 iMinSizeClamp = aMinSizeClamp; 5988 } 5989 LogicalSize availableSize(childWM, availISize, availBSize); 5990 size = ::MeasuringReflow(child, aGridRI.mReflowInput, rc, availableSize, 5991 cbSize, iMinSizeClamp, bMinSizeClamp); 5992 } 5993 size += child->GetLogicalUsedMargin(childWM).BStartEnd(childWM); 5994 nscoord overflow = size - aMinSizeClamp; 5995 if (MOZ_UNLIKELY(overflow > 0)) { 5996 nscoord contentSize = child->ContentBSize(childWM); 5997 nscoord newContentSize = std::max(nscoord(0), contentSize - overflow); 5998 // XXXmats deal with percentages better, see bug 1300369 comment 27. 5999 size -= contentSize - newContentSize; 6000 } 6001 } 6002 MOZ_ASSERT(aGridItem.mBaselineOffset[aAxis] >= 0, 6003 "baseline offset should be non-negative at this point"); 6004 MOZ_ASSERT((aGridItem.mState[aAxis] & ItemState::eIsBaselineAligned) || 6005 aGridItem.mBaselineOffset[aAxis] == nscoord(0), 6006 "baseline offset should be zero when not baseline-aligned"); 6007 size += aGridItem.mBaselineOffset[aAxis]; 6008 size += extraMargin; 6009 return std::max(size, 0); 6010 } 6011 6012 struct CachedIntrinsicSizes { 6013 CachedIntrinsicSizes() = delete; 6014 CachedIntrinsicSizes(const GridItemInfo& aGridItem, 6015 const GridReflowInput& aGridRI, const LogicalAxis aAxis) 6016 : mPercentageBasis(aGridRI.PercentageBasisFor(aAxis, aGridItem)) {} 6017 6018 void EnsureContributions(EnumSet<GridIntrinsicSizeType> aTypes, 6019 const GridItemInfo& aGridItem, 6020 const GridReflowInput& aGridRI, LogicalAxis aAxis) { 6021 // max-content and min-content should behave as initial value in block axis. 6022 // XXXalaskanemily: The specifics might have changed in the spec? 6023 // https://drafts.csswg.org/css-sizing-3/#valdef-width-min-content 6024 // https://drafts.csswg.org/css-sizing-3/#valdef-width-max-content 6025 6026 // If we need to calculate GridIntrinsicSizeType::MinContribution, we might 6027 // need to substitute GridIntrinsicSizeType::MinContentContribution instead. 6028 // Per https://drafts.csswg.org/css-grid-2/#algo-single-span-items 6029 // Section "For auto minimums": 6030 // * "if the item's computed preferred size behaves as auto or depends on 6031 // the size of its containing block in the relevant axis," then we do in 6032 // fact need the used minimum size." 6033 // * "...else the item's minimum contribution is its min-content 6034 // contribution" in which case we make a recursive call to compute 6035 // GridIntrinsicSizeType::MinContentContribution instead, and do a fixup 6036 // to place that value in the MinContentContribution slot. 6037 // Note that we use BehavesLikeInitialValue and HasPercent to implement 6038 // the spec check for "behaves as auto or depends on the size of its 6039 // containing block". 6040 // We make a similar check in MinContributionDependsOnAutoMinSize as 6041 // an earlier test for whether we need the used minimum size. 6042 if (aTypes.contains(GridIntrinsicSizeType::MinContribution)) { 6043 nsIFrame* const child = aGridItem.mFrame; 6044 const nsStylePosition* const stylePos = child->StylePosition(); 6045 const auto anchorResolutionParams = 6046 AnchorPosResolutionParams::From(child); 6047 const WritingMode cbwm = aGridRI.mWM; 6048 auto styleSize = stylePos->Size(aAxis, cbwm, anchorResolutionParams); 6049 const LogicalAxis axisInItemWM = 6050 cbwm.ConvertAxisTo(aAxis, child->GetWritingMode()); 6051 // FIXME: Bug 567039: moz-fit-content and -moz-available are not 6052 // supported for block size dimension on sizing properties (e.g. height), 6053 // so we treat it as `auto`. 6054 if (!styleSize->BehavesLikeInitialValue(axisInItemWM) && 6055 !styleSize->HasPercent()) { 6056 // Calculate without MinSize, but ensuring MinContentContribution. 6057 aTypes -= GridIntrinsicSizeType::MinContribution; 6058 aTypes += GridIntrinsicSizeType::MinContentContribution; 6059 EnsureContributions(aTypes, aGridItem, aGridRI, aAxis); 6060 // Copy the MinSize from the MinContentContribution. 6061 mSizes[GridIntrinsicSizeType::MinContribution] = 6062 mSizes[GridIntrinsicSizeType::MinContentContribution]; 6063 return; 6064 } 6065 } 6066 6067 for (const GridIntrinsicSizeType type : aTypes) { 6068 if (mSizes[type].isNothing()) { 6069 mSizes[type].emplace(ComputeContribution( 6070 type, aGridItem, aGridRI, aAxis, mPercentageBasis, mMinSizeClamp)); 6071 } 6072 } 6073 } 6074 6075 private: 6076 // Computes the MinSize, MinContentContribution, or MaxContentContribution of 6077 // an item in the given axis. 6078 // This helps to implement EnsureContributions. It's here to prevent other 6079 // places from using it, as it is not general purpose and requires that the 6080 // caller has made checks for when we will use the MinContentContribution as 6081 // the MinSize, as EnsureContributions does. 6082 static nscoord ComputeContribution(GridIntrinsicSizeType aType, 6083 const GridItemInfo& aGridItem, 6084 const GridReflowInput& aGridRI, 6085 LogicalAxis aAxis, 6086 const LogicalSize& aPercentageBasis, 6087 nscoord aMinSizeClamp) { 6088 const WritingMode containerWM = aGridRI.mWM; 6089 gfxContext* const rc = &aGridRI.mRenderingContext; 6090 switch (aType) { 6091 case GridIntrinsicSizeType::MinContentContribution: 6092 return ContentContribution(aGridItem, aGridRI, aAxis, aPercentageBasis, 6093 IntrinsicISizeType::MinISize, aMinSizeClamp); 6094 case GridIntrinsicSizeType::MaxContentContribution: 6095 return ContentContribution(aGridItem, aGridRI, aAxis, aPercentageBasis, 6096 IntrinsicISizeType::PrefISize, 6097 aMinSizeClamp); 6098 case GridIntrinsicSizeType::MinContribution: { 6099 // Compute the min-size contribution for a grid item, as defined at 6100 // https://drafts.csswg.org/css-grid-2/#min-size-contribution 6101 nsIFrame* const child = aGridItem.mFrame; 6102 const nsStylePosition* const stylePos = child->StylePosition(); 6103 const auto anchorResolutionParams = 6104 AnchorPosResolutionParams::From(child); 6105 const LogicalAxis axisInItemWM = 6106 containerWM.ConvertAxisTo(aAxis, child->GetWritingMode()); 6107 #ifdef DEBUG 6108 // The caller must handle this case separately. 6109 // See EnsureContributions. 6110 { 6111 const auto styleSize = 6112 stylePos->Size(aAxis, containerWM, anchorResolutionParams); 6113 MOZ_ASSERT(styleSize->BehavesLikeInitialValue(axisInItemWM) || 6114 styleSize->HasPercent(), 6115 "Should have been caught in EnsureContributions"); 6116 } 6117 #endif 6118 // https://drafts.csswg.org/css-grid-2/#min-size-auto 6119 // This calculates the min-content contribution from either a definite 6120 // min-width (or min-height depending on aAxis), or the 6121 // "specified / transferred size" for min-width:auto if 6122 // overflow == visible (as min-width:0 otherwise), or 6123 // NS_UNCONSTRAINEDSIZE for other min-width intrinsic values 6124 // (which results in always taking the "content size" part below). 6125 MOZ_ASSERT(aGridItem.mBaselineOffset[aAxis] >= 0, 6126 "baseline offset should be non-negative at this point"); 6127 MOZ_ASSERT((aGridItem.mState[aAxis] & ItemState::eIsBaselineAligned) || 6128 aGridItem.mBaselineOffset[aAxis] == (nscoord)0, 6129 "baseline offset should be zero when not baseline-aligned"); 6130 const auto styleMinSize = 6131 stylePos->MinSize(aAxis, containerWM, anchorResolutionParams); 6132 6133 // max-content and min-content should behave as initial value in block 6134 // axis. 6135 // FIXME: Bug 567039: moz-fit-content and -moz-available are not 6136 // supported for block size dimension on sizing properties 6137 // (e.g. height), so we treat it as `auto`. 6138 const bool isAuto = styleMinSize->BehavesLikeInitialValue(axisInItemWM); 6139 nscoord s = aGridItem.mBaselineOffset[aAxis]; 6140 6141 // Check if the min-size style of the grid item is auto and the 6142 // minimum contribution is content-based. 6143 // While the eContentBasedAutoMinSize flag is not synonymous with 6144 // an item having content-based automatic minimum contribution, 6145 // the previous checks should catch the other cases in which the 6146 // automatic minimum contribution is zero instead. 6147 // 6148 // See bug 1951821 for this discrepency between the flag's usage 6149 // and the specification: 6150 // https://drafts.csswg.org/css-grid-2/#min-size-auto 6151 if (!isAuto || 6152 (aGridItem.mState[aAxis] & ItemState::eContentBasedAutoMinSize)) { 6153 nscoord contrib = nsLayoutUtils::MinSizeContributionForAxis( 6154 containerWM.PhysicalAxis(aAxis), rc, child, 6155 IntrinsicISizeType::MinISize, aPercentageBasis); 6156 if (contrib == NS_UNCONSTRAINEDSIZE) { 6157 s = contrib; 6158 } else { 6159 s += contrib; 6160 } 6161 6162 if ((axisInItemWM == LogicalAxis::Inline && 6163 nsIFrame::ToExtremumLength(*styleMinSize)) || 6164 (isAuto && !child->StyleDisplay()->IsScrollableOverflow())) { 6165 // "if the item's computed preferred size behaves as auto or 6166 // depends on the size of its containing block in the relevant 6167 // axis, its minimum contribution is the outer size that would 6168 // result from assuming the item's used minimum size as its 6169 // preferred size" 6170 // 6171 // The "auto or depends on the size of its containing block" is 6172 // checked above with ItemState::eContentBasedAutoMinSize. 6173 // 6174 // https://drafts.csswg.org/css-grid-2/#minimum-contribution 6175 StyleSizeOverrides overrides; 6176 if (axisInItemWM == LogicalAxis::Inline) { 6177 overrides.mStyleISize.emplace(*styleMinSize.get()); 6178 } else { 6179 overrides.mStyleBSize.emplace(*styleMinSize.get()); 6180 } 6181 // Now calculate the "content size" part and return whichever is 6182 // smaller. 6183 MOZ_ASSERT(isAuto || s == NS_UNCONSTRAINEDSIZE); 6184 s = std::min(s, ContentContribution(aGridItem, aGridRI, aAxis, 6185 aPercentageBasis, 6186 IntrinsicISizeType::MinISize, 6187 aMinSizeClamp, overrides)); 6188 } 6189 } 6190 return s; 6191 } 6192 } 6193 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected contribution type"); 6194 } 6195 6196 public: 6197 EnumeratedArray<GridIntrinsicSizeType, nscoord> SizesOrDefault() const { 6198 EnumeratedArray<GridIntrinsicSizeType, nscoord> sizes; 6199 for (GridIntrinsicSizeType type : kAllGridIntrinsicSizeTypes) { 6200 sizes[type] = mSizes[type].valueOr(0); 6201 } 6202 return sizes; 6203 } 6204 6205 EnumeratedArray<GridIntrinsicSizeType, Maybe<nscoord>> mSizes; 6206 6207 // The item's percentage basis for intrinsic sizing purposes. 6208 const LogicalSize mPercentageBasis; 6209 6210 // "if the grid item spans only grid tracks that have a fixed max track 6211 // sizing function, its automatic minimum size in that dimension is 6212 // further clamped to less than or equal to the size necessary to fit its 6213 // margin box within the resulting grid area (flooring at zero)" 6214 // https://drafts.csswg.org/css-grid-2/#min-size-auto 6215 // This is the clamp value to use for that: 6216 nscoord mMinSizeClamp = NS_MAXSIZE; 6217 }; 6218 6219 void nsGridContainerFrame::Tracks::CalculateSizes( 6220 GridReflowInput& aGridRI, nsTArray<GridItemInfo>& aGridItems, 6221 const TrackSizingFunctions& aFunctions, nscoord aContentBoxSize, 6222 LineRange GridArea::* aRange, SizingConstraint aConstraint) { 6223 // Implement the intrinsic sizing algorithm, step 12.5 as described in: 6224 // https://drafts.csswg.org/css-grid-2/#algo-content 6225 nscoord percentageBasis = aContentBoxSize; 6226 if (percentageBasis == NS_UNCONSTRAINEDSIZE) { 6227 percentageBasis = 0; 6228 } 6229 // 12.5 step 1: Shim baseline-aligned items so their intrinsic size 6230 // contributions reflect their baseline alignment 6231 // https://drafts.csswg.org/css-grid-2/#algo-baseline-shims 6232 InitializeItemBaselines(aGridRI, aGridItems); 6233 6234 // 12.5 steps 2-5 6235 ResolveIntrinsicSize(aGridRI, aGridItems, aFunctions, aRange, percentageBasis, 6236 aConstraint); 6237 6238 // Neither 12.6 nor 12.7 will occur under min content constraint. 6239 if (aConstraint != SizingConstraint::MinContent) { 6240 nscoord freeSpace = aContentBoxSize; 6241 if (freeSpace != NS_UNCONSTRAINEDSIZE) { 6242 freeSpace -= SumOfGridGaps(); 6243 } 6244 // 12.6 maximize tracks by distributing free space. 6245 // https://drafts.csswg.org/css-grid-2/#algo-grow-tracks 6246 DistributeFreeSpace(freeSpace); 6247 6248 // 12.7 Expand flexible tracks. 6249 // https://drafts.csswg.org/css-grid-2/#algo-flex-tracks 6250 StretchFlexibleTracks(aGridRI, aGridItems, aFunctions, freeSpace); 6251 } 6252 } 6253 6254 TrackSize::StateBits nsGridContainerFrame::Tracks::StateBitsForRange( 6255 const LineRange& aRange) const { 6256 MOZ_ASSERT(!aRange.IsAuto(), "must have a definite range"); 6257 TrackSize::StateBits state = TrackSize::StateBits{0}; 6258 for (auto i : aRange.Range()) { 6259 state |= mSizes[i].mState; 6260 } 6261 return state; 6262 } 6263 6264 static void AddSubgridContribution(TrackSize& aSize, 6265 nscoord aMarginBorderPadding) { 6266 if (aSize.mState & TrackSize::eIntrinsicMinSizing) { 6267 aSize.mBase = std::max(aSize.mBase, aMarginBorderPadding); 6268 aSize.mLimit = std::max(aSize.mLimit, aSize.mBase); 6269 } 6270 // XXX maybe eFlexMaxSizing too? 6271 // (once we implement https://github.com/w3c/csswg-drafts/issues/2177) 6272 if (aSize.mState & 6273 (TrackSize::eIntrinsicMaxSizing | TrackSize::eApplyFitContentClamping)) { 6274 aSize.mLimit = std::max(aSize.mLimit, aMarginBorderPadding); 6275 } 6276 } 6277 6278 Maybe<nscoord> nsGridContainerFrame::Tracks::ComputeMinSizeClamp( 6279 const TrackSizingFunctions& aFunctions, nscoord aPercentageBasis, 6280 const LineRange& aLineRange, const TrackSize::StateBits aState) const { 6281 if (!TrackSize::IsDefiniteMaxSizing(aState)) { 6282 return Nothing(); 6283 } 6284 nscoord minSizeClamp = 0; 6285 for (auto i : aLineRange.Range()) { 6286 minSizeClamp += 6287 aFunctions.MaxSizingFor(i).AsBreadth().Resolve(aPercentageBasis); 6288 } 6289 minSizeClamp += mGridGap * (aLineRange.Extent() - 1); 6290 return Some(minSizeClamp); 6291 } 6292 6293 void nsGridContainerFrame::Tracks::ResolveIntrinsicSizeForNonSpanningItems( 6294 GridReflowInput& aGridRI, const TrackSizingFunctions& aFunctions, 6295 nscoord aPercentageBasis, SizingConstraint aConstraint, 6296 const LineRange& aRange, const GridItemInfo& aGridItem) { 6297 // Calculate track sizes for fit non-spanning items. 6298 // https://drafts.csswg.org/css-grid-2/#algo-single-span-items 6299 CachedIntrinsicSizes cache{aGridItem, aGridRI, mAxis}; 6300 TrackSize& sz = mSizes[aRange.mStart]; 6301 6302 // Contribution type to use as the base size. 6303 // This is a Maybe as we might not need to calculate a contribution at all, 6304 // for instance if the base sizing function is a definite length. 6305 Maybe<GridIntrinsicSizeType> baseSizeType; 6306 if (sz.mState & TrackSize::eAutoMinSizing) { 6307 // "For auto minimums:" 6308 // "If the track has an 'auto' min track sizing function and the grid 6309 // container is being sized under a min-/max-content constraint, set 6310 // the track's base size to the maximum of its items' limited 6311 // min-content contributions" 6312 if (aGridItem.MinContributionDependsOnAutoMinSize(aGridRI.mWM, mAxis)) { 6313 // Clamp it if it's spanning a definite track max-sizing function. 6314 if (const Maybe<nscoord> minSizeClamp = 6315 ComputeMinSizeClamp(aFunctions, aPercentageBasis, aRange)) { 6316 cache.mMinSizeClamp = *minSizeClamp; 6317 aGridItem.mState[mAxis] |= ItemState::eClampMarginBoxMinSize; 6318 } 6319 // Use the content-based contribution. 6320 baseSizeType.emplace((aConstraint == SizingConstraint::MaxContent) 6321 ? GridIntrinsicSizeType::MaxContentContribution 6322 : GridIntrinsicSizeType::MinContentContribution); 6323 } else { 6324 // Use the minimum contribution. 6325 // Note that this could still become MinContentContribution in practice. 6326 // MinContributionDependsOnAutoMinSize can return false when the item's 6327 // size depends on the size of its containing block. In that case, using 6328 // EnsureContributions to compute MinSize will instead compute 6329 // MinContentContribution, which will then be placed in the MinSize 6330 // slot on the cache. 6331 baseSizeType.emplace(GridIntrinsicSizeType::MinContribution); 6332 } 6333 } else if (sz.mState & TrackSize::eMinContentMinSizing) { 6334 // "For min-content minimums:" 6335 // "If the track has a 'min-content' min track sizing function, set its 6336 // base size to the maximum of the items' min-content contributions" 6337 baseSizeType.emplace(GridIntrinsicSizeType::MinContentContribution); 6338 } else if (sz.mState & TrackSize::eMaxContentMinSizing) { 6339 // "For max-content minimums:" 6340 // "If the track has a 'max-content' min track sizing function, set its 6341 // base size to the maximum of the items' max-content contributions" 6342 baseSizeType.emplace(GridIntrinsicSizeType::MaxContentContribution); 6343 } 6344 6345 // Size of fit-content maximum, if any. 6346 Maybe<nscoord> fitContentClamp; 6347 // Contribution type to use as the growth limit. 6348 // This is a Maybe as we might not need to calculate a contribution at all, 6349 // for instance if the growth limit sizing function is a definite length. 6350 Maybe<GridIntrinsicSizeType> limitType; 6351 if (sz.mState & TrackSize::eMinContentMaxSizing) { 6352 // "For min-content maximums:" 6353 // "If the track has a 'min-content' max track sizing function, set its 6354 // growth limit to the maximum of the items' min-content contributions" 6355 limitType.emplace(GridIntrinsicSizeType::MinContentContribution); 6356 } else if (sz.mState & 6357 (TrackSize::eAutoMaxSizing | TrackSize::eMaxContentMaxSizing)) { 6358 // "For max-content maximums:" 6359 // "If the track has a 'max-content' max track sizing function, set its 6360 // growth limit to the maximum of the items' max-content contributions" 6361 limitType.emplace(GridIntrinsicSizeType::MaxContentContribution); 6362 if (MOZ_UNLIKELY(sz.mState & TrackSize::eApplyFitContentClamping)) { 6363 // "For fit-content() maximums, furthermore clamp this growth limit by 6364 // the fit-content() argument." 6365 fitContentClamp.emplace(aFunctions.SizingFor(aRange.mStart) 6366 .AsFitContent() 6367 .AsBreadth() 6368 .Resolve(aPercentageBasis)); 6369 } 6370 } 6371 6372 // Even if it was possible to use the minimum contribution as the limit in 6373 // the spec, this could get trashed by the checks for whether the item's auto 6374 // minimum size depends on the size implemented in 6375 // GridItemInfo::MinContributionDependsOnAutoMinSize and 6376 // CachedIntrinsicSizes::EnsureContributions. 6377 MOZ_ASSERT( 6378 limitType != Some(GridIntrinsicSizeType::MinContribution), 6379 "We should never be using the minimum contribution as the limit size."); 6380 6381 // Accumulate the required size types and compute the contributions. 6382 { 6383 EnumSet<GridIntrinsicSizeType> sizeTypesToCalculate; 6384 for (const auto& maybeType : {baseSizeType, limitType}) { 6385 if (maybeType) { 6386 sizeTypesToCalculate += *maybeType; 6387 } 6388 } 6389 cache.EnsureContributions(sizeTypesToCalculate, aGridItem, aGridRI, mAxis); 6390 } 6391 6392 if (baseSizeType) { 6393 sz.mBase = std::max(sz.mBase, *cache.mSizes[*baseSizeType]); 6394 } 6395 6396 // Limit based on max size type. 6397 if (limitType) { 6398 if (sz.mLimit == NS_UNCONSTRAINEDSIZE) { 6399 sz.mLimit = 0; // Use only the contribution instead. 6400 } 6401 sz.mLimit = std::max(sz.mLimit, *cache.mSizes[*limitType]); 6402 if (fitContentClamp) { 6403 // "furthermore clamp this growth limit by the fit-content() argument." 6404 sz.mLimit = std::min(sz.mLimit, *fitContentClamp); 6405 } 6406 } 6407 6408 // "In all cases, if a track's growth limit is now less than its base size, 6409 // increase the growth limit to match the base size." 6410 sz.mLimit = std::max(sz.mLimit, sz.mBase); 6411 } 6412 6413 void nsGridContainerFrame::Tracks::CalculateItemBaselines( 6414 nsTArray<ItemBaselineData>& aBaselineItems, 6415 BaselineSharingGroup aBaselineGroup) { 6416 if (aBaselineItems.IsEmpty()) { 6417 return; 6418 } 6419 6420 // Sort the collected items on their baseline track. 6421 std::sort(aBaselineItems.begin(), aBaselineItems.end(), 6422 ItemBaselineData::IsBaselineTrackLessThan); 6423 6424 MOZ_ASSERT(mSizes.Length() > 0, "having an item implies at least one track"); 6425 6426 // Make sure we have enough space to store the baselines. Use the highest 6427 // track number (+1 to account for the 0 based indexing). 6428 auto baselineCount = aBaselineItems.LastElement().mBaselineTrack + 1; 6429 mBaselines.EnsureLengthAtLeast(baselineCount); 6430 6431 nscoord maxBaseline = 0; 6432 nscoord maxDescent = 0; 6433 uint32_t currentTrack = kAutoLine; // guaranteed to not match any item 6434 uint32_t trackStartIndex = 0; 6435 for (uint32_t i = 0, len = aBaselineItems.Length(); true; ++i) { 6436 // Find the maximum baseline and descent in the current track. 6437 if (i != len) { 6438 const ItemBaselineData& item = aBaselineItems[i]; 6439 if (currentTrack == item.mBaselineTrack) { 6440 maxBaseline = std::max(maxBaseline, item.mBaseline); 6441 maxDescent = std::max(maxDescent, item.mSize - item.mBaseline); 6442 continue; 6443 } 6444 } 6445 // Iterate the current track again and update the baseline offsets making 6446 // all items baseline-aligned within this group in this track. 6447 for (uint32_t j = trackStartIndex; j < i; ++j) { 6448 const ItemBaselineData& item = aBaselineItems[j]; 6449 item.mGridItem->mBaselineOffset[mAxis] = maxBaseline - item.mBaseline; 6450 MOZ_ASSERT(item.mGridItem->mBaselineOffset[mAxis] >= 0); 6451 } 6452 if (i != 0) { 6453 // Store the size of this baseline-aligned subtree. 6454 mSizes[currentTrack].mBaselineSubtreeSize[aBaselineGroup] = 6455 maxBaseline + maxDescent; 6456 6457 // Record the baseline for the current track. 6458 mBaselines[currentTrack][aBaselineGroup] = Some(maxBaseline); 6459 } 6460 if (i == len) { 6461 break; 6462 } 6463 // Initialize data for the next track with baseline-aligned items. 6464 const ItemBaselineData& item = aBaselineItems[i]; 6465 currentTrack = item.mBaselineTrack; 6466 trackStartIndex = i; 6467 maxBaseline = item.mBaseline; 6468 maxDescent = item.mSize - item.mBaseline; 6469 } 6470 } 6471 6472 void nsGridContainerFrame::Tracks::InitializeItemBaselines( 6473 GridReflowInput& aGridRI, nsTArray<GridItemInfo>& aGridItems) { 6474 MOZ_ASSERT(!mIsMasonry); 6475 if (aGridRI.mFrame->IsSubgrid(mAxis)) { 6476 // A grid container's subgridded axis doesn't have a baseline. 6477 return; 6478 } 6479 6480 nsTArray<ItemBaselineData> firstBaselineItems; 6481 nsTArray<ItemBaselineData> lastBaselineItems; 6482 const WritingMode containerWM = aGridRI.mWM; 6483 ComputedStyle* containerStyle = aGridRI.mFrame->Style(); 6484 6485 for (GridItemInfo& gridItem : aGridItems) { 6486 if (gridItem.IsSubgrid(mAxis)) { 6487 // A subgrid itself is never baseline-aligned. 6488 continue; 6489 } 6490 6491 nsIFrame* child = gridItem.mFrame; 6492 uint32_t baselineTrack = kAutoLine; 6493 auto state = ItemState(0); 6494 const auto childWM = child->GetWritingMode(); 6495 6496 const bool isOrthogonal = containerWM.IsOrthogonalTo(childWM); 6497 const bool isInlineAxis = mAxis == LogicalAxis::Inline; // i.e. columns 6498 const bool itemHasBaselineParallelToTrack = isInlineAxis == isOrthogonal; 6499 6500 // [align|justify]-self:[last ]baseline. 6501 auto selfAlignment = 6502 child->StylePosition()->UsedSelfAlignment(mAxis, containerStyle); 6503 selfAlignment &= ~StyleAlignFlags::FLAG_BITS; 6504 if (selfAlignment == StyleAlignFlags::BASELINE) { 6505 state |= ItemState::eFirstBaseline | ItemState::eSelfBaseline; 6506 } else if (selfAlignment == StyleAlignFlags::LAST_BASELINE) { 6507 state |= ItemState::eLastBaseline | ItemState::eSelfBaseline; 6508 } 6509 6510 // https://drafts.csswg.org/css-align-3/#baseline-align-content 6511 // Baseline content-alignment can only apply if the align-content axis is 6512 // parallel with the box’s block axis; otherwise the fallback alignment is 6513 // used. 6514 if (!isInlineAxis) { 6515 // Handle align-content:[last ]baseline (if present) 6516 auto alignContent = child->StylePosition()->mAlignContent.primary; 6517 alignContent &= ~StyleAlignFlags::FLAG_BITS; 6518 if (alignContent == StyleAlignFlags::BASELINE || 6519 alignContent == StyleAlignFlags::LAST_BASELINE) { 6520 const auto selfAlignEdge = alignContent == StyleAlignFlags::BASELINE 6521 ? StyleAlignFlags::SELF_START 6522 : StyleAlignFlags::SELF_END; 6523 bool validCombo = selfAlignment == StyleAlignFlags::NORMAL || 6524 selfAlignment == StyleAlignFlags::STRETCH || 6525 selfAlignment == selfAlignEdge; 6526 if (!validCombo) { 6527 // We're doing alignment in the axis that's orthogonal to mAxis here. 6528 LogicalAxis alignAxis = GetOrthogonalAxis(mAxis); 6529 // |sameSide| is true if the container's start side in this axis is 6530 // the same as the child's start side, in the child's parallel axis. 6531 bool sameSide = 6532 containerWM.ParallelAxisStartsOnSameSide(alignAxis, childWM); 6533 if (selfAlignment == StyleAlignFlags::LEFT) { 6534 selfAlignment = containerWM.IsBidiLTR() ? StyleAlignFlags::START 6535 : StyleAlignFlags::END; 6536 } else if (selfAlignment == StyleAlignFlags::RIGHT) { 6537 selfAlignment = StyleAlignFlags::START; 6538 } 6539 6540 if (selfAlignment == StyleAlignFlags::START || 6541 selfAlignment == StyleAlignFlags::FLEX_START) { 6542 validCombo = 6543 sameSide == (alignContent == StyleAlignFlags::BASELINE); 6544 } else if (selfAlignment == StyleAlignFlags::END || 6545 selfAlignment == StyleAlignFlags::FLEX_END) { 6546 validCombo = 6547 sameSide == (alignContent == StyleAlignFlags::LAST_BASELINE); 6548 } 6549 } 6550 if (validCombo) { 6551 const GridArea& area = gridItem.mArea; 6552 if (alignContent == StyleAlignFlags::BASELINE) { 6553 state |= ItemState::eFirstBaseline | ItemState::eContentBaseline; 6554 baselineTrack = area.mRows.mStart; 6555 } else if (alignContent == StyleAlignFlags::LAST_BASELINE) { 6556 state |= ItemState::eLastBaseline | ItemState::eContentBaseline; 6557 baselineTrack = area.mRows.mEnd - 1; 6558 } 6559 } 6560 } 6561 } 6562 6563 if (state & ItemState::eIsBaselineAligned) { 6564 // The item is baseline aligned, so calculate the baseline sharing group. 6565 // <https://drafts.csswg.org/css-align-3/#baseline-terms> 6566 bool isFirstBaseline = (state & ItemState::eFirstBaseline) != 0; 6567 BaselineSharingGroup baselineAlignment = isFirstBaseline 6568 ? BaselineSharingGroup::First 6569 : BaselineSharingGroup::Last; 6570 // Baseline alignment occurs along `mAxis`, but baselines are defined in 6571 // the orthogonal axis (the axis of the baseline context that defines the 6572 // baseline sharing group). 6573 auto baselineWM = WritingMode::DetermineWritingModeForBaselineSynthesis( 6574 containerWM, childWM, GetOrthogonalAxis(mAxis)); 6575 6576 auto sameSideInBaselineWM = 6577 containerWM.ParallelAxisStartsOnSameSide(mAxis, baselineWM); 6578 auto baselineSharingGroup = BaselineSharingGroup::First; 6579 if (sameSideInBaselineWM != isFirstBaseline) { 6580 baselineSharingGroup = BaselineSharingGroup::Last; 6581 state |= ItemState::eLastBaselineSharingGroup; 6582 6583 baselineTrack = (isInlineAxis ? gridItem.mArea.mCols.mEnd 6584 : gridItem.mArea.mRows.mEnd) - 6585 1; 6586 } else { 6587 baselineTrack = isInlineAxis ? gridItem.mArea.mCols.mStart 6588 : gridItem.mArea.mRows.mStart; 6589 } 6590 6591 // XXXmats if |child| is a descendant of a subgrid then the metrics 6592 // below needs to account for the accumulated MPB somehow... 6593 6594 auto* rc = &aGridRI.mRenderingContext; 6595 // XXX figure out if we can avoid/merge this reflow with the main reflow. 6596 // XXX (after bug 1174569 is sorted out) 6597 // 6598 // XXX How should we handle percentage padding here? (bug 1330866) 6599 // XXX (see ::ContentContribution and how it deals with percentages) 6600 // XXX What if the true baseline after line-breaking differs from this 6601 // XXX hypothetical baseline based on an infinite inline size? 6602 // XXX Maybe we should just call ::ContentContribution here instead? 6603 const LogicalSize cbSize = aGridRI.PercentageBasisFor(mAxis, gridItem); 6604 LogicalSize avail(childWM, INFINITE_ISIZE_COORD, NS_UNCONSTRAINEDSIZE); 6605 const LogicalAxis inlineAxisInChildWM = 6606 isOrthogonal ? LogicalAxis::Block : LogicalAxis::Inline; 6607 const nscoord colSize = cbSize.Size(inlineAxisInChildWM, childWM); 6608 if (colSize != NS_UNCONSTRAINEDSIZE) { 6609 avail.Size(inlineAxisInChildWM, childWM) = colSize; 6610 } 6611 ::MeasuringReflow(child, aGridRI.mReflowInput, rc, avail, cbSize); 6612 6613 nsGridContainerFrame* grid = do_QueryFrame(child); 6614 auto frameSize = 6615 isInlineAxis ? child->ISize(containerWM) : child->BSize(containerWM); 6616 auto margin = child->GetLogicalUsedMargin(containerWM); 6617 auto alignSize = 6618 frameSize + (isInlineAxis ? margin.IStartEnd(containerWM) 6619 : margin.BStartEnd(containerWM)); 6620 6621 Maybe<nscoord> baseline; 6622 if (grid) { 6623 baseline.emplace((isOrthogonal == isInlineAxis) 6624 ? grid->GetBBaseline(baselineAlignment) 6625 : grid->GetIBaseline(baselineAlignment)); 6626 } else { 6627 if (itemHasBaselineParallelToTrack) { 6628 baseline = child->GetNaturalBaselineBOffset( 6629 childWM, baselineAlignment, BaselineExportContext::Other); 6630 } 6631 6632 if (!baseline) { 6633 // If baseline alignment is specified on a grid item whose size in 6634 // that axis depends on the size of an intrinsically-sized track, that 6635 // item does not participate in baseline alignment, and instead uses 6636 // its fallback alignment as if that were originally specified. 6637 // https://drafts.csswg.org/css-grid-2/#row-align 6638 6639 // Check if the item crosses any tracks that are intrinsically sized. 6640 auto range = gridItem.mArea.LineRangeForAxis(mAxis).Range(); 6641 auto isTrackAutoSize = 6642 std::find_if(range.begin(), range.end(), [&](auto track) { 6643 constexpr auto intrinsicSizeFlags = 6644 TrackSize::eIntrinsicMinSizing | 6645 TrackSize::eIntrinsicMaxSizing | 6646 TrackSize::eApplyFitContentClamping | 6647 TrackSize::eFlexMaxSizing; 6648 return (mSizes[track].mState & intrinsicSizeFlags) != 0; 6649 }) != range.end(); 6650 6651 // If either the track or the item is not auto sized, then the item 6652 // participates in baseline alignment. 6653 if (!isTrackAutoSize || 6654 !gridItem.IsBSizeDependentOnContainerSize(containerWM)) { 6655 // We're synthesizing the baseline from the child's border-box 6656 // (frameSize is the size of the border-box). See: 6657 // https://drafts.csswg.org/css-align-3/#baseline-export. 6658 6659 if (containerWM.IsCentralBaseline()) { 6660 // We want to use the exact same central position within the 6661 // frame, regardless of which side we're measuring from. To 6662 // achieve that, we round *up* if we're in the first baseline 6663 // sharing group, and *down* if we're in the last baseline sharing 6664 // group. 6665 const bool isFirstBaselineSharingGroup = 6666 baselineSharingGroup == BaselineSharingGroup::First; 6667 baseline.emplace(frameSize / 2 + (isFirstBaselineSharingGroup 6668 ? 0 6669 : frameSize % 2)); 6670 } else { 6671 // The baseline offset is measured from the block-{start,end} edge 6672 // of the container, using the block axis of 'baselineWM' (which 6673 // may differ from the child or container’s writing mode). 6674 // 6675 // If we're synthesizing a baseline from the edge nearest to the 6676 // container's reference side (start for the first baseline group, 6677 // end for the last), the offset is `0`. Otherwise, it's from the 6678 // opposite edge, so we use `frameSize`. 6679 // 6680 // This logic depends on whether we're in the first or last 6681 // baseline-sharing group, and whether the line is inverted (e.g., 6682 // in vertical-rl mode), which affects which edge is considered 6683 // the "start" or "end". 6684 baseline.emplace((isFirstBaseline == baselineWM.IsLineInverted()) 6685 ? 0 6686 : frameSize); 6687 } 6688 } 6689 } 6690 } 6691 6692 if (baseline) { 6693 nscoord finalBaseline = *baseline; 6694 NS_ASSERTION(finalBaseline != NS_INTRINSIC_ISIZE_UNKNOWN, 6695 "about to use an unknown baseline"); 6696 6697 nscoord marginAdjust = 0; 6698 if (baselineSharingGroup == BaselineSharingGroup::First) { 6699 marginAdjust = isInlineAxis ? margin.IStart(containerWM) 6700 : margin.BStart(containerWM); 6701 } else { 6702 marginAdjust = isInlineAxis ? margin.IEnd(containerWM) 6703 : margin.BEnd(containerWM); 6704 6705 // This flag is used in ::AlignSelf(...) to check whether the item is 6706 // last baseline aligned, but this flag should go away. 6707 state |= GridItemInfo::eEndSideBaseline; 6708 } 6709 finalBaseline += marginAdjust; 6710 6711 auto& baselineItems = 6712 (baselineSharingGroup == BaselineSharingGroup::First) 6713 ? firstBaselineItems 6714 : lastBaselineItems; 6715 baselineItems.AppendElement(ItemBaselineData{ 6716 baselineTrack, finalBaseline, alignSize, &gridItem}); 6717 } else { 6718 state &= ~ItemState::eAllBaselineBits; 6719 } 6720 } 6721 6722 MOZ_ASSERT( 6723 (state & (ItemState::eFirstBaseline | ItemState::eLastBaseline)) != 6724 (ItemState::eFirstBaseline | ItemState::eLastBaseline), 6725 "first/last baseline bits are mutually exclusive"); 6726 MOZ_ASSERT( 6727 (state & (ItemState::eSelfBaseline | ItemState::eContentBaseline)) != 6728 (ItemState::eSelfBaseline | ItemState::eContentBaseline), 6729 "*-self and *-content baseline bits are mutually exclusive"); 6730 MOZ_ASSERT( 6731 !(state & (ItemState::eFirstBaseline | ItemState::eLastBaseline)) == 6732 !(state & (ItemState::eSelfBaseline | ItemState::eContentBaseline)), 6733 "first/last bit requires self/content bit and vice versa"); 6734 6735 gridItem.mState[mAxis] |= state; 6736 gridItem.mBaselineOffset[mAxis] = nscoord(0); 6737 } 6738 6739 if (firstBaselineItems.IsEmpty() && lastBaselineItems.IsEmpty()) { 6740 return; 6741 } 6742 6743 // TODO: CSS Align spec issue - how to align a baseline subtree in a track? 6744 // https://lists.w3.org/Archives/Public/www-style/2016May/0141.html 6745 mBaselineSubtreeAlign[BaselineSharingGroup::First] = StyleAlignFlags::START; 6746 mBaselineSubtreeAlign[BaselineSharingGroup::Last] = StyleAlignFlags::END; 6747 6748 CalculateItemBaselines(firstBaselineItems, BaselineSharingGroup::First); 6749 CalculateItemBaselines(lastBaselineItems, BaselineSharingGroup::Last); 6750 } 6751 6752 // TODO: we store the wrong baseline group offset in some cases (bug 1632200) 6753 void nsGridContainerFrame::Tracks::InitializeItemBaselinesInMasonryAxis( 6754 GridReflowInput& aGridRI, nsTArray<GridItemInfo>& aGridItems, 6755 BaselineAlignmentSet aSet, const nsSize& aContainerSize, 6756 nsTArray<nscoord>& aTrackSizes, 6757 nsTArray<ItemBaselineData>& aFirstBaselineItems, 6758 nsTArray<ItemBaselineData>& aLastBaselineItems) { 6759 MOZ_ASSERT(mIsMasonry); 6760 WritingMode wm = aGridRI.mWM; 6761 ComputedStyle* containerSC = aGridRI.mFrame->Style(); 6762 for (GridItemInfo& gridItem : aGridItems) { 6763 if (gridItem.IsSubgrid(mAxis)) { 6764 // A subgrid itself is never baseline-aligned. 6765 continue; 6766 } 6767 const auto& area = gridItem.mArea; 6768 if (aSet.mItemSet == BaselineAlignmentSet::LastItems) { 6769 // NOTE: eIsLastItemInMasonryTrack is set also if the item is the ONLY 6770 // item in its track; the eIsBaselineAligned check excludes it though 6771 // since it participates in the start baseline groups in that case. 6772 // 6773 // XXX what if it's the only item in THAT baseline group? 6774 // XXX should it participate in the last-item group instead then 6775 // if there are more baseline-aligned items there? 6776 if (!(gridItem.mState[mAxis] & ItemState::eIsLastItemInMasonryTrack) || 6777 (gridItem.mState[mAxis] & ItemState::eIsBaselineAligned)) { 6778 continue; 6779 } 6780 } else { 6781 if (area.LineRangeForAxis(mAxis).mStart > 0 || 6782 (gridItem.mState[mAxis] & ItemState::eIsBaselineAligned)) { 6783 continue; 6784 } 6785 } 6786 if (!aSet.MatchTrackAlignment(StyleAlignFlags::START)) { 6787 continue; 6788 } 6789 6790 nsIFrame* child = gridItem.mFrame; 6791 uint32_t baselineTrack = kAutoLine; 6792 auto state = ItemState(0); 6793 auto childWM = child->GetWritingMode(); 6794 const bool isOrthogonal = wm.IsOrthogonalTo(childWM); 6795 const bool isInlineAxis = mAxis == LogicalAxis::Inline; // i.e. columns 6796 // XXX update the line below to include orthogonal grid/table boxes 6797 // XXX since they have baselines in both dimensions. And flexbox with 6798 // XXX reversed main/cross axis? 6799 const bool itemHasBaselineParallelToTrack = isInlineAxis == isOrthogonal; 6800 if (itemHasBaselineParallelToTrack) { 6801 const auto* pos = child->StylePosition(); 6802 // [align|justify]-self:[last ]baseline. 6803 auto selfAlignment = pos->UsedSelfAlignment(mAxis, containerSC); 6804 selfAlignment &= ~StyleAlignFlags::FLAG_BITS; 6805 if (selfAlignment == StyleAlignFlags::BASELINE) { 6806 state |= ItemState::eFirstBaseline | ItemState::eSelfBaseline; 6807 baselineTrack = isInlineAxis ? area.mCols.mStart : area.mRows.mStart; 6808 } else if (selfAlignment == StyleAlignFlags::LAST_BASELINE) { 6809 state |= ItemState::eLastBaseline | ItemState::eSelfBaseline; 6810 baselineTrack = (isInlineAxis ? area.mCols.mEnd : area.mRows.mEnd) - 1; 6811 } else { 6812 // [align|justify]-content:[last ]baseline. 6813 auto childAxis = isOrthogonal ? GetOrthogonalAxis(mAxis) : mAxis; 6814 auto alignContent = pos->UsedContentAlignment(childAxis).primary; 6815 alignContent &= ~StyleAlignFlags::FLAG_BITS; 6816 if (alignContent == StyleAlignFlags::BASELINE) { 6817 state |= ItemState::eFirstBaseline | ItemState::eContentBaseline; 6818 baselineTrack = isInlineAxis ? area.mCols.mStart : area.mRows.mStart; 6819 } else if (alignContent == StyleAlignFlags::LAST_BASELINE) { 6820 state |= ItemState::eLastBaseline | ItemState::eContentBaseline; 6821 baselineTrack = 6822 (isInlineAxis ? area.mCols.mEnd : area.mRows.mEnd) - 1; 6823 } 6824 } 6825 } 6826 6827 if (state & ItemState::eIsBaselineAligned) { 6828 // XXXmats if |child| is a descendant of a subgrid then the metrics 6829 // below needs to account for the accumulated MPB somehow... 6830 6831 nscoord baseline; 6832 nsGridContainerFrame* grid = do_QueryFrame(child); 6833 if (state & ItemState::eFirstBaseline) { 6834 if (grid) { 6835 if (isOrthogonal == isInlineAxis) { 6836 baseline = grid->GetBBaseline(BaselineSharingGroup::First); 6837 } else { 6838 baseline = grid->GetIBaseline(BaselineSharingGroup::First); 6839 } 6840 } 6841 if (grid || nsLayoutUtils::GetFirstLineBaseline(wm, child, &baseline)) { 6842 NS_ASSERTION(baseline != NS_INTRINSIC_ISIZE_UNKNOWN, 6843 "about to use an unknown baseline"); 6844 auto frameSize = isInlineAxis ? child->ISize(wm) : child->BSize(wm); 6845 nscoord alignSize; 6846 LogicalPoint pos = 6847 child->GetLogicalNormalPosition(wm, aContainerSize); 6848 baseline += pos.Pos(mAxis, wm); 6849 if (aSet.mTrackAlignmentSet == BaselineAlignmentSet::EndStretch) { 6850 state |= ItemState::eEndSideBaseline; 6851 // Convert to distance from the track end. 6852 baseline = 6853 aTrackSizes[gridItem.mArea 6854 .LineRangeForAxis(GetOrthogonalAxis(mAxis)) 6855 .mStart] - 6856 baseline; 6857 } 6858 alignSize = frameSize; 6859 aFirstBaselineItems.AppendElement(ItemBaselineData( 6860 {baselineTrack, baseline, alignSize, &gridItem})); 6861 } else { 6862 state &= ~ItemState::eAllBaselineBits; 6863 } 6864 } else { 6865 if (grid) { 6866 if (isOrthogonal == isInlineAxis) { 6867 baseline = grid->GetBBaseline(BaselineSharingGroup::Last); 6868 } else { 6869 baseline = grid->GetIBaseline(BaselineSharingGroup::Last); 6870 } 6871 } 6872 if (grid || nsLayoutUtils::GetLastLineBaseline(wm, child, &baseline)) { 6873 NS_ASSERTION(baseline != NS_INTRINSIC_ISIZE_UNKNOWN, 6874 "about to use an unknown baseline"); 6875 auto frameSize = isInlineAxis ? child->ISize(wm) : child->BSize(wm); 6876 auto m = child->GetLogicalUsedMargin(wm); 6877 if (!grid && 6878 aSet.mTrackAlignmentSet == BaselineAlignmentSet::EndStretch) { 6879 // Convert to distance from border-box end. 6880 state |= ItemState::eEndSideBaseline; 6881 LogicalPoint pos = 6882 child->GetLogicalNormalPosition(wm, aContainerSize); 6883 baseline += pos.Pos(mAxis, wm); 6884 baseline = 6885 aTrackSizes[gridItem.mArea 6886 .LineRangeForAxis(GetOrthogonalAxis(mAxis)) 6887 .mStart] - 6888 baseline; 6889 } else if (grid && aSet.mTrackAlignmentSet == 6890 BaselineAlignmentSet::StartStretch) { 6891 // Convert to distance from border-box start. 6892 baseline = frameSize - baseline; 6893 } 6894 if (aSet.mItemSet == BaselineAlignmentSet::LastItems && 6895 aSet.mTrackAlignmentSet == BaselineAlignmentSet::StartStretch) { 6896 LogicalPoint pos = 6897 child->GetLogicalNormalPosition(wm, aContainerSize); 6898 baseline += pos.B(wm); 6899 } 6900 if (aSet.mTrackAlignmentSet == BaselineAlignmentSet::EndStretch) { 6901 state |= ItemState::eEndSideBaseline; 6902 } 6903 auto descent = 6904 baseline + ((state & ItemState::eEndSideBaseline) 6905 ? (isInlineAxis ? m.IEnd(wm) : m.BEnd(wm)) 6906 : (isInlineAxis ? m.IStart(wm) : m.BStart(wm))); 6907 auto alignSize = 6908 frameSize + (isInlineAxis ? m.IStartEnd(wm) : m.BStartEnd(wm)); 6909 aLastBaselineItems.AppendElement( 6910 ItemBaselineData({baselineTrack, descent, alignSize, &gridItem})); 6911 } else { 6912 state &= ~ItemState::eAllBaselineBits; 6913 } 6914 } 6915 } 6916 MOZ_ASSERT( 6917 (state & (ItemState::eFirstBaseline | ItemState::eLastBaseline)) != 6918 (ItemState::eFirstBaseline | ItemState::eLastBaseline), 6919 "first/last baseline bits are mutually exclusive"); 6920 MOZ_ASSERT( 6921 (state & (ItemState::eSelfBaseline | ItemState::eContentBaseline)) != 6922 (ItemState::eSelfBaseline | ItemState::eContentBaseline), 6923 "*-self and *-content baseline bits are mutually exclusive"); 6924 MOZ_ASSERT( 6925 !(state & (ItemState::eFirstBaseline | ItemState::eLastBaseline)) == 6926 !(state & (ItemState::eSelfBaseline | ItemState::eContentBaseline)), 6927 "first/last bit requires self/content bit and vice versa"); 6928 gridItem.mState[mAxis] |= state; 6929 gridItem.mBaselineOffset[mAxis] = nscoord(0); 6930 } 6931 6932 CalculateItemBaselines(aFirstBaselineItems, BaselineSharingGroup::First); 6933 CalculateItemBaselines(aLastBaselineItems, BaselineSharingGroup::Last); 6934 6935 // TODO: make sure the mBaselines (i.e. the baselines we export from 6936 // the grid container) are offset from the correct container edge. 6937 // Also, which of the baselines do we pick to export exactly? 6938 6939 MOZ_ASSERT(aFirstBaselineItems.Length() != 1 || 6940 aFirstBaselineItems[0].mGridItem->mBaselineOffset[mAxis] == 0, 6941 "a baseline group that contains only one item should not " 6942 "produce a non-zero item baseline offset"); 6943 MOZ_ASSERT(aLastBaselineItems.Length() != 1 || 6944 aLastBaselineItems[0].mGridItem->mBaselineOffset[mAxis] == 0, 6945 "a baseline group that contains only one item should not " 6946 "produce a non-zero item baseline offset"); 6947 } 6948 6949 void nsGridContainerFrame::Tracks::AlignBaselineSubtree( 6950 const GridItemInfo& aGridItem) const { 6951 if (mIsMasonry) { 6952 return; 6953 } 6954 auto state = aGridItem.mState[mAxis]; 6955 if (!(state & ItemState::eIsBaselineAligned)) { 6956 return; 6957 } 6958 const GridArea& area = aGridItem.mArea; 6959 int32_t baselineTrack; 6960 const bool isFirstBaseline = state & ItemState::eFirstBaseline; 6961 if (isFirstBaseline) { 6962 baselineTrack = 6963 mAxis == LogicalAxis::Block ? area.mRows.mStart : area.mCols.mStart; 6964 } else { 6965 baselineTrack = 6966 (mAxis == LogicalAxis::Block ? area.mRows.mEnd : area.mCols.mEnd) - 1; 6967 } 6968 const TrackSize& sz = mSizes[baselineTrack]; 6969 auto baselineGroup = isFirstBaseline ? BaselineSharingGroup::First 6970 : BaselineSharingGroup::Last; 6971 nscoord delta = sz.mBase - sz.mBaselineSubtreeSize[baselineGroup]; 6972 const auto subtreeAlign = mBaselineSubtreeAlign[baselineGroup]; 6973 if (subtreeAlign == StyleAlignFlags::START) { 6974 if (state & ItemState::eLastBaseline) { 6975 aGridItem.mBaselineOffset[mAxis] += delta; 6976 } 6977 } else if (subtreeAlign == StyleAlignFlags::END) { 6978 if (isFirstBaseline) { 6979 aGridItem.mBaselineOffset[mAxis] += delta; 6980 } 6981 } else if (subtreeAlign == StyleAlignFlags::CENTER) { 6982 aGridItem.mBaselineOffset[mAxis] += delta / 2; 6983 } else { 6984 MOZ_ASSERT_UNREACHABLE("unexpected baseline subtree alignment"); 6985 } 6986 } 6987 6988 bool nsGridContainerFrame::Tracks::GrowSizeForSpanningItems( 6989 TrackSizingStep aStep, TrackSizingPhase aPhase, 6990 nsTArray<SpanningItemData>::iterator aIter, 6991 nsTArray<SpanningItemData>::iterator aIterEnd, nsTArray<uint32_t>& aTracks, 6992 TrackPlan& aTrackPlan, ItemPlan& aItemPlan, SizingConstraint aConstraint, 6993 bool aIsGridIntrinsicSizing, const TrackSizingFunctions& aFunctions, 6994 const FitContentClamper& aFitContentClamper, 6995 bool aNeedInfinitelyGrowableFlag) { 6996 const bool isMaxSizingPhase = aPhase == TrackSizingPhase::IntrinsicMaximums || 6997 aPhase == TrackSizingPhase::MaxContentMaximums; 6998 bool needToUpdateSizes = false; 6999 aTrackPlan.Initialize(aPhase, *this); 7000 for (; aIter != aIterEnd; ++aIter) { 7001 const SpanningItemData& item = *aIter; 7002 if (!(item.mState & SelectorForPhase(aPhase, aConstraint))) { 7003 continue; 7004 } 7005 if (isMaxSizingPhase) { 7006 for (auto i : item.mLineRange.Range()) { 7007 aTrackPlan[i].mState |= TrackSize::eModified; 7008 } 7009 } 7010 if (aStep == TrackSizingStep::Flex && aIsGridIntrinsicSizing) { 7011 // We could only ever grow flex tracks, and when measuring we shouldn't 7012 // grow flex tracks, so the remaining space will always be zero. 7013 continue; 7014 } 7015 nscoord space = item.SizeContributionForPhase(aPhase); 7016 if (space <= 0) { 7017 continue; 7018 } 7019 aTracks.ClearAndRetainStorage(); 7020 space = CollectGrowable(aStep, aPhase, space, item.mLineRange, aConstraint, 7021 aTracks); 7022 if (space > 0) { 7023 DistributeToTrackSizes(aStep, aPhase, space, aTrackPlan, aItemPlan, 7024 aTracks, aConstraint, aFunctions, 7025 aFitContentClamper); 7026 needToUpdateSizes = true; 7027 } 7028 } 7029 if (isMaxSizingPhase) { 7030 needToUpdateSizes = true; 7031 } 7032 if (needToUpdateSizes) { 7033 CopyPlanToSize(aPhase, aTrackPlan, aNeedInfinitelyGrowableFlag); 7034 } 7035 return needToUpdateSizes; 7036 } 7037 7038 void nsGridContainerFrame::Tracks::ResolveIntrinsicSize( 7039 GridReflowInput& aGridRI, nsTArray<GridItemInfo>& aGridItems, 7040 const TrackSizingFunctions& aFunctions, LineRange GridArea::* aRange, 7041 nscoord aPercentageBasis, SizingConstraint aConstraint) { 7042 // Intrinsic sizing algorithm 12.5 steps 2-5 7043 // https://drafts.csswg.org/css-grid-2/#algo-content 7044 // 7045 // We're also setting eIsFlexing on the item state here to speed up 7046 // FindUsedFlexFraction later. 7047 7048 // nonFlexSpanningItems has spanning items that do not span any flex tracks. 7049 // flexSpanningItems has spanning items that span one or more flex tracks. 7050 nsTArray<SpanningItemData> nonFlexSpanningItems, flexSpanningItems; 7051 // max span of items in `nonFlexSpanningItems` and `flexSpanningItems`. 7052 uint32_t maxSpan = 0; 7053 7054 const auto orthogonalAxis = GetOrthogonalAxis(mAxis); 7055 const bool isMasonryInOtherAxis = aGridRI.mFrame->IsMasonry(orthogonalAxis); 7056 7057 for (auto& gridItem : aGridItems) { 7058 MOZ_ASSERT(!(gridItem.mState[mAxis] & 7059 (ItemState::eContentBasedAutoMinSize | ItemState::eIsFlexing | 7060 ItemState::eClampMarginBoxMinSize)), 7061 "Why are any of these bits set already?"); 7062 7063 const GridArea& area = gridItem.mArea; 7064 const LineRange& lineRange = area.*aRange; 7065 const TrackSize::StateBits state = StateBitsForRange(lineRange); 7066 // Set flex sizing flag as soon as possible to ensure 7067 // MinContributionDependsOnAutoMinSize will function properly. 7068 if (state & TrackSize::eFlexMaxSizing) { 7069 gridItem.mState[mAxis] |= ItemState::eIsFlexing; 7070 } 7071 7072 // If we have masonry layout in the other axis then skip this item unless 7073 // it's in the first masonry track, or has definite placement in this axis, 7074 // or spans all tracks in this axis (since that implies it will be placed 7075 // at line 1 regardless of layout results of other items). 7076 if (isMasonryInOtherAxis && 7077 gridItem.mArea.LineRangeForAxis(orthogonalAxis).mStart != 0 && 7078 (gridItem.mState[mAxis] & ItemState::eAutoPlacement) && 7079 gridItem.mArea.LineRangeForAxis(mAxis).Extent() != mSizes.Length()) { 7080 continue; 7081 } 7082 7083 uint32_t span = lineRange.Extent(); 7084 if (MOZ_UNLIKELY(gridItem.mState[mAxis] & ItemState::eIsSubgrid)) { 7085 auto itemWM = gridItem.mFrame->GetWritingMode(); 7086 auto percentageBasis = aGridRI.PercentageBasisFor(mAxis, gridItem); 7087 7088 if (percentageBasis.ISize(itemWM) == NS_UNCONSTRAINEDSIZE) { 7089 percentageBasis.ISize(itemWM) = nscoord(0); 7090 } 7091 7092 if (percentageBasis.BSize(itemWM) == NS_UNCONSTRAINEDSIZE) { 7093 percentageBasis.BSize(itemWM) = nscoord(0); 7094 } 7095 7096 const WritingMode wm = aGridRI.mWM; 7097 auto* subgrid = 7098 SubgridComputeMarginBorderPadding(gridItem, percentageBasis); 7099 LogicalMargin mbp = SubgridAccumulatedMarginBorderPadding( 7100 gridItem.SubgridFrame(), subgrid, wm, mAxis); 7101 7102 if (span == 1) { 7103 AddSubgridContribution(mSizes[lineRange.mStart], 7104 mbp.StartEnd(mAxis, wm)); 7105 } else { 7106 AddSubgridContribution(mSizes[lineRange.mStart], mbp.Start(mAxis, wm)); 7107 AddSubgridContribution(mSizes[lineRange.mEnd - 1], mbp.End(mAxis, wm)); 7108 } 7109 continue; 7110 } 7111 7112 // Set eContentBasedAutoMinSize if and only if the grid item has 7113 // content-based automatic minimum size. This is the case if all of the 7114 // following are true of the item: 7115 // 1. its computed overflow is not a scrollable overflow value 7116 // 2. it spans at least one track in that axis whose min track sizing 7117 // function is auto 7118 // 3. if it spans more than one track in that axis, none of those tracks 7119 // are flexible 7120 // https://drafts.csswg.org/css-grid-2/#min-size-auto 7121 if (!gridItem.mFrame->StyleDisplay()->IsScrollableOverflow() && 7122 state & TrackSize::eAutoMinSizing && 7123 (span == 1 || !(state & TrackSize::eFlexMaxSizing))) { 7124 gridItem.mState[mAxis] |= ItemState::eContentBasedAutoMinSize; 7125 } 7126 7127 if (span == 1) { 7128 // Step 2. Size tracks to fit non-spanning items. 7129 // https://drafts.csswg.org/css-grid-2/#algo-single-span-items 7130 ResolveIntrinsicSizeForNonSpanningItems(aGridRI, aFunctions, 7131 aPercentageBasis, aConstraint, 7132 lineRange, gridItem); 7133 } else { 7134 // Collect information for step 3. 7135 // https://drafts.csswg.org/css-grid-2/#algo-spanning-items 7136 7137 nsTArray<SpanningItemData>* items; 7138 if (state & TrackSize::eFlexMaxSizing) { 7139 // Set eIsFlexing on the item state here to speed up 7140 // FindUsedFlexFraction later. 7141 gridItem.mState[mAxis] |= ItemState::eIsFlexing; 7142 items = &flexSpanningItems; 7143 } else { 7144 items = &nonFlexSpanningItems; 7145 } 7146 7147 if (state & 7148 (TrackSize::eIntrinsicMinSizing | TrackSize::eIntrinsicMaxSizing)) { 7149 maxSpan = std::max(maxSpan, span); 7150 CachedIntrinsicSizes cache{gridItem, aGridRI, mAxis}; 7151 7152 // Calculate data for "Automatic Minimum Size" clamping, if needed. 7153 if (gridItem.mState[mAxis] & ItemState::eContentBasedAutoMinSize) { 7154 if (const Maybe<nscoord> minSizeClamp = ComputeMinSizeClamp( 7155 aFunctions, aPercentageBasis, lineRange, state)) { 7156 cache.mMinSizeClamp = *minSizeClamp; 7157 gridItem.mState[mAxis] |= ItemState::eClampMarginBoxMinSize; 7158 } 7159 } 7160 7161 // Collect the various grid item size contributions we need. 7162 EnumSet<GridIntrinsicSizeType> sizeTypesToCalculate; 7163 // For 3.1 7164 TrackSize::StateBits selector = 7165 SelectorForPhase(TrackSizingPhase::IntrinsicMinimums, aConstraint); 7166 7167 if (state & selector) { 7168 sizeTypesToCalculate += GridIntrinsicSizeType::MinContribution; 7169 } 7170 7171 // For 3.2 and 3.5 7172 selector = 7173 SelectorForPhase(TrackSizingPhase::IntrinsicMaximums, aConstraint) | 7174 SelectorForPhase(TrackSizingPhase::ContentBasedMinimums, 7175 aConstraint); 7176 if (state & selector) { 7177 sizeTypesToCalculate += GridIntrinsicSizeType::MinContentContribution; 7178 } 7179 7180 // For 3.3 and 3.6 7181 selector = 7182 SelectorForPhase(TrackSizingPhase::MaxContentMinimums, 7183 aConstraint) | 7184 SelectorForPhase(TrackSizingPhase::MaxContentMaximums, aConstraint); 7185 if (state & selector) { 7186 sizeTypesToCalculate += GridIntrinsicSizeType::MaxContentContribution; 7187 } 7188 7189 cache.EnsureContributions(sizeTypesToCalculate, gridItem, aGridRI, 7190 mAxis); 7191 items->AppendElement(SpanningItemData( 7192 {span, state, lineRange, cache.SizesOrDefault(), gridItem.mFrame})); 7193 } 7194 } 7195 7196 MOZ_ASSERT( 7197 !(gridItem.mState[mAxis] & ItemState::eClampMarginBoxMinSize) || 7198 (gridItem.mState[mAxis] & ItemState::eContentBasedAutoMinSize), 7199 "clamping only applies to Automatic Minimum Size"); 7200 } 7201 7202 MOZ_ASSERT(maxSpan != 1, "Should only count spans greater than 1"); 7203 // Step 3 - Increase sizes to accommodate spanning items crossing 7204 // content-sized tracks. 7205 if (maxSpan) { 7206 auto fitContentClamper = [&aFunctions, aPercentageBasis](uint32_t aTrack, 7207 nscoord aMinSize, 7208 nscoord* aSize) { 7209 nscoord fitContentLimit = ::ResolveToDefiniteSize( 7210 aFunctions.MaxSizingFor(aTrack), aPercentageBasis); 7211 if (*aSize > fitContentLimit) { 7212 *aSize = std::max(aMinSize, fitContentLimit); 7213 return true; 7214 } 7215 return false; 7216 }; 7217 7218 // Step 3 should "Repeat incrementally for items with greater spans until 7219 // all items have been considered." 7220 // Sort the collected items on span length, shortest first. There's no need 7221 // for a stable sort here since the sizing isn't order dependent within 7222 // a group of items with the same span length. 7223 // We don't need to sort flexSpanningItems, those items are all considered 7224 // "together, rather than grouped by span size" for step 4. 7225 std::sort(nonFlexSpanningItems.begin(), nonFlexSpanningItems.end(), 7226 SpanningItemData::IsSpanLessThan); 7227 7228 nsTArray<uint32_t> tracks(maxSpan); 7229 TrackPlan plan(mSizes.Length()); 7230 plan.SetLength(mSizes.Length()); 7231 ItemPlan itemPlan(mSizes.Length()); 7232 itemPlan.SetLength(mSizes.Length()); 7233 7234 // Start / end iterator for items of the same span length: 7235 auto spanGroupStart = nonFlexSpanningItems.begin(); 7236 auto spanGroupEnd = spanGroupStart; 7237 const auto end = nonFlexSpanningItems.end(); 7238 7239 // nonFlexSpanningItems is sorted by span size. Each iteration will process 7240 // one span size. 7241 for (; spanGroupStart != end; spanGroupStart = spanGroupEnd) { 7242 const uint32_t span = spanGroupStart->mSpan; 7243 TrackSize::StateBits stateBitsForSpan{0}; 7244 MOZ_ASSERT(spanGroupEnd == spanGroupStart); 7245 // Find the end of this group if items with the same span size. 7246 // Accumulate state bits for the items with this span size to avoid 7247 // calculations below that are not applicable to any of those items. 7248 do { 7249 stateBitsForSpan |= StateBitsForRange(spanGroupEnd->mLineRange); 7250 } while (++spanGroupEnd != end && spanGroupEnd->mSpan == span); 7251 MOZ_ASSERT(!(stateBitsForSpan & TrackSize::eFlexMaxSizing), 7252 "Non-flex spanning items should not include any flex tracks"); 7253 bool updatedBase = false; // Did we update any mBase in step 3.1..3.3? 7254 TrackSizingPhase phase = TrackSizingPhase::IntrinsicMinimums; 7255 if (stateBitsForSpan & SelectorForPhase(phase, aConstraint)) { 7256 // Step 3.1 MinSize to intrinsic min-sizing. 7257 updatedBase = GrowSizeForSpanningItems( 7258 TrackSizingStep::NotFlex, phase, spanGroupStart, spanGroupEnd, 7259 tracks, plan, itemPlan, aConstraint, aGridRI.mIsGridIntrinsicSizing, 7260 aFunctions); 7261 } 7262 7263 phase = TrackSizingPhase::ContentBasedMinimums; 7264 if (stateBitsForSpan & SelectorForPhase(phase, aConstraint)) { 7265 // Step 3.2 MinContentContribution to min-/max-content (and 'auto' when 7266 // sizing under a min-content constraint) min-sizing. 7267 updatedBase |= GrowSizeForSpanningItems( 7268 TrackSizingStep::NotFlex, phase, spanGroupStart, spanGroupEnd, 7269 tracks, plan, itemPlan, aConstraint, aGridRI.mIsGridIntrinsicSizing, 7270 aFunctions); 7271 } 7272 7273 phase = TrackSizingPhase::MaxContentMinimums; 7274 if (stateBitsForSpan & SelectorForPhase(phase, aConstraint)) { 7275 // Step 3.3 MaxContentContribution to max-content (and 'auto' when 7276 // sizing under a max-content constraint) min-sizing. 7277 updatedBase |= GrowSizeForSpanningItems( 7278 TrackSizingStep::NotFlex, phase, spanGroupStart, spanGroupEnd, 7279 tracks, plan, itemPlan, aConstraint, aGridRI.mIsGridIntrinsicSizing, 7280 aFunctions); 7281 } 7282 7283 if (updatedBase) { 7284 // Step 3.4 7285 for (TrackSize& sz : mSizes) { 7286 if (sz.mBase > sz.mLimit) { 7287 sz.mLimit = sz.mBase; 7288 } 7289 } 7290 } 7291 7292 phase = TrackSizingPhase::IntrinsicMaximums; 7293 bool willRunStep3_6 = false; 7294 if (stateBitsForSpan & SelectorForPhase(phase, aConstraint)) { 7295 willRunStep3_6 = 7296 stateBitsForSpan & TrackSize::eAutoOrMaxContentMaxSizing; 7297 // Step 3.5 MinContentContribution to intrinsic max-sizing. 7298 GrowSizeForSpanningItems( 7299 TrackSizingStep::NotFlex, phase, spanGroupStart, spanGroupEnd, 7300 tracks, plan, itemPlan, aConstraint, aGridRI.mIsGridIntrinsicSizing, 7301 aFunctions, fitContentClamper, willRunStep3_6); 7302 } 7303 if (willRunStep3_6) { 7304 // Step 2.6 MaxContentContribution to max-content max-sizing. 7305 phase = TrackSizingPhase::MaxContentMaximums; 7306 GrowSizeForSpanningItems( 7307 TrackSizingStep::NotFlex, phase, spanGroupStart, spanGroupEnd, 7308 tracks, plan, itemPlan, aConstraint, aGridRI.mIsGridIntrinsicSizing, 7309 aFunctions, fitContentClamper); 7310 } 7311 } 7312 7313 // Step 4 7314 TrackSize::StateBits stateBitsForSpan{0}; 7315 for (const SpanningItemData& spanningData : flexSpanningItems) { 7316 const TrackSize::StateBits bits = 7317 StateBitsForRange(spanningData.mLineRange); 7318 MOZ_ASSERT(bits & TrackSize::eFlexMaxSizing, 7319 "All flex spanning items should have at least one flex track"); 7320 stateBitsForSpan |= bits; 7321 } 7322 bool updatedBase = false; // Did we update any mBase in step 4.1..4.3? 7323 TrackSizingPhase phase = TrackSizingPhase::IntrinsicMinimums; 7324 if (stateBitsForSpan & SelectorForPhase(phase, aConstraint)) { 7325 // Step 4.1 MinSize to intrinsic min-sizing. 7326 updatedBase = GrowSizeForSpanningItems( 7327 TrackSizingStep::Flex, phase, flexSpanningItems.begin(), 7328 flexSpanningItems.end(), tracks, plan, itemPlan, aConstraint, 7329 aGridRI.mIsGridIntrinsicSizing, aFunctions); 7330 } 7331 7332 phase = TrackSizingPhase::ContentBasedMinimums; 7333 if (stateBitsForSpan & SelectorForPhase(phase, aConstraint)) { 7334 // Step 4.2 MinContentContribution to min-/max-content (and 'auto' when 7335 // sizing under a min-content constraint) min-sizing. 7336 updatedBase |= GrowSizeForSpanningItems( 7337 TrackSizingStep::Flex, phase, flexSpanningItems.begin(), 7338 flexSpanningItems.end(), tracks, plan, itemPlan, aConstraint, 7339 aGridRI.mIsGridIntrinsicSizing, aFunctions); 7340 } 7341 7342 phase = TrackSizingPhase::MaxContentMinimums; 7343 if (stateBitsForSpan & SelectorForPhase(phase, aConstraint)) { 7344 // Step 4.3 MaxContentContribution to max-content (and 'auto' when 7345 // sizing under a max-content constraint) min-sizing. 7346 updatedBase |= GrowSizeForSpanningItems( 7347 TrackSizingStep::Flex, phase, flexSpanningItems.begin(), 7348 flexSpanningItems.end(), tracks, plan, itemPlan, aConstraint, 7349 aGridRI.mIsGridIntrinsicSizing, aFunctions); 7350 } 7351 7352 if (updatedBase) { 7353 // Step 4.4 7354 for (TrackSize& sz : mSizes) { 7355 if (sz.mBase > sz.mLimit) { 7356 sz.mLimit = sz.mBase; 7357 } 7358 } 7359 } 7360 } 7361 7362 // Step 5 - If any track still has an infinite growth limit, set its growth 7363 // limit to its base size. 7364 for (TrackSize& sz : mSizes) { 7365 if (sz.mLimit == NS_UNCONSTRAINEDSIZE) { 7366 sz.mLimit = sz.mBase; 7367 } 7368 } 7369 } 7370 7371 float nsGridContainerFrame::Tracks::FindFrUnitSize( 7372 const LineRange& aRange, const nsTArray<uint32_t>& aFlexTracks, 7373 const TrackSizingFunctions& aFunctions, nscoord aSpaceToFill) const { 7374 MOZ_ASSERT(aSpaceToFill > 0 && !aFlexTracks.IsEmpty()); 7375 float flexFactorSum = 0.0f; 7376 nscoord leftOverSpace = aSpaceToFill; 7377 for (auto i : aRange.Range()) { 7378 const TrackSize& sz = mSizes[i]; 7379 if (sz.mState & TrackSize::eFlexMaxSizing) { 7380 flexFactorSum += aFunctions.MaxSizingFor(i).AsFr(); 7381 } else { 7382 leftOverSpace -= sz.mBase; 7383 if (leftOverSpace <= 0) { 7384 return 0.0f; 7385 } 7386 } 7387 } 7388 bool restart; 7389 float hypotheticalFrSize; 7390 nsTArray<uint32_t> flexTracks(aFlexTracks.Clone()); 7391 uint32_t numFlexTracks = flexTracks.Length(); 7392 do { 7393 restart = false; 7394 7395 // 12.7.1.2: If flexFactorSum is less than 1, set it to 1 instead. 7396 hypotheticalFrSize = leftOverSpace / std::max(flexFactorSum, 1.0f); 7397 for (uint32_t& track : flexTracks) { 7398 if (track == kAutoLine) { 7399 continue; // Track marked as inflexible in a prev. iter of this loop. 7400 } 7401 float flexFactor = aFunctions.MaxSizingFor(track).AsFr(); 7402 const nscoord base = mSizes[track].mBase; 7403 if (flexFactor * hypotheticalFrSize < base) { 7404 // 12.7.1.4: Treat this track as inflexible. 7405 track = kAutoLine; 7406 flexFactorSum -= flexFactor; 7407 leftOverSpace -= base; 7408 --numFlexTracks; 7409 if (numFlexTracks == 0 || leftOverSpace <= 0) { 7410 return 0.0f; 7411 } 7412 restart = true; 7413 // break; XXX (bug 1176621 comment 16) measure which is more common 7414 } 7415 } 7416 } while (restart); 7417 return hypotheticalFrSize; 7418 } 7419 7420 float nsGridContainerFrame::Tracks::FindUsedFlexFraction( 7421 GridReflowInput& aGridRI, const nsTArray<GridItemInfo>& aGridItems, 7422 const nsTArray<uint32_t>& aFlexTracks, 7423 const TrackSizingFunctions& aFunctions, nscoord aAvailableSize) const { 7424 if (aAvailableSize != NS_UNCONSTRAINEDSIZE) { 7425 // Use all of the grid tracks and a 'space to fill' of the available space. 7426 const TranslatedLineRange range(0, mSizes.Length()); 7427 return FindFrUnitSize(range, aFlexTracks, aFunctions, aAvailableSize); 7428 } 7429 7430 // The used flex fraction is the maximum of: 7431 // ... each flexible track's base size divided by its flex factor (which is 7432 // floored at 1). 7433 float fr = 0.0f; 7434 for (uint32_t track : aFlexTracks) { 7435 float flexFactor = aFunctions.MaxSizingFor(track).AsFr(); 7436 float possiblyDividedBaseSize = (flexFactor > 1.0f) 7437 ? mSizes[track].mBase / flexFactor 7438 : mSizes[track].mBase; 7439 fr = std::max(fr, possiblyDividedBaseSize); 7440 } 7441 // ... the result of 'finding the size of an fr' for each item that spans 7442 // a flex track with its max-content contribution as 'space to fill' 7443 for (const GridItemInfo& item : aGridItems) { 7444 if (item.mState[mAxis] & ItemState::eIsFlexing) { 7445 // XXX optimize: bug 1194446 7446 const auto percentageBasis = aGridRI.PercentageBasisFor(mAxis, item); 7447 nscoord spaceToFill = ContentContribution( 7448 item, aGridRI, mAxis, percentageBasis, IntrinsicISizeType::PrefISize); 7449 const LineRange& range = 7450 mAxis == LogicalAxis::Inline ? item.mArea.mCols : item.mArea.mRows; 7451 MOZ_ASSERT(range.Extent() >= 1); 7452 const auto spannedGaps = range.Extent() - 1; 7453 if (spannedGaps > 0) { 7454 spaceToFill -= mGridGap * spannedGaps; 7455 } 7456 if (spaceToFill <= 0) { 7457 continue; 7458 } 7459 // ... and all its spanned tracks as input. 7460 nsTArray<uint32_t> itemFlexTracks; 7461 for (auto i : range.Range()) { 7462 if (mSizes[i].mState & TrackSize::eFlexMaxSizing) { 7463 itemFlexTracks.AppendElement(i); 7464 } 7465 } 7466 float itemFr = 7467 FindFrUnitSize(range, itemFlexTracks, aFunctions, spaceToFill); 7468 fr = std::max(fr, itemFr); 7469 } 7470 } 7471 return fr; 7472 } 7473 7474 void nsGridContainerFrame::Tracks::StretchFlexibleTracks( 7475 GridReflowInput& aGridRI, const nsTArray<GridItemInfo>& aGridItems, 7476 const TrackSizingFunctions& aFunctions, nscoord aAvailableSize) { 7477 if (aAvailableSize <= 0) { 7478 return; 7479 } 7480 nsTArray<uint32_t> flexTracks(mSizes.Length()); 7481 for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) { 7482 if (mSizes[i].mState & TrackSize::eFlexMaxSizing) { 7483 flexTracks.AppendElement(i); 7484 } 7485 } 7486 if (flexTracks.IsEmpty()) { 7487 return; 7488 } 7489 nscoord minSize = 0; 7490 nscoord maxSize = NS_UNCONSTRAINEDSIZE; 7491 if (aGridRI.mReflowInput) { 7492 auto* ri = aGridRI.mReflowInput; 7493 minSize = mAxis == LogicalAxis::Block ? ri->ComputedMinBSize() 7494 : ri->ComputedMinISize(); 7495 maxSize = mAxis == LogicalAxis::Block ? ri->ComputedMaxBSize() 7496 : ri->ComputedMaxISize(); 7497 } 7498 Maybe<TrackPlan> origSizes; 7499 bool applyMinMax = (minSize != 0 || maxSize != NS_UNCONSTRAINEDSIZE) && 7500 aAvailableSize == NS_UNCONSTRAINEDSIZE; 7501 // We iterate twice at most. The 2nd time if the grid size changed after 7502 // applying a min/max-size (can only occur if aAvailableSize is indefinite). 7503 while (true) { 7504 float fr = FindUsedFlexFraction(aGridRI, aGridItems, flexTracks, aFunctions, 7505 aAvailableSize); 7506 if (fr != 0.0f) { 7507 for (uint32_t i : flexTracks) { 7508 float flexFactor = aFunctions.MaxSizingFor(i).AsFr(); 7509 nscoord flexLength = NSToCoordRound(flexFactor * fr); 7510 nscoord& base = mSizes[i].mBase; 7511 if (flexLength > base) { 7512 if (applyMinMax && origSizes.isNothing()) { 7513 origSizes.emplace(mSizes); 7514 } 7515 base = flexLength; 7516 } 7517 } 7518 } 7519 if (applyMinMax) { 7520 applyMinMax = false; 7521 // https://drafts.csswg.org/css-grid-2/#algo-flex-tracks 7522 // "If using this flex fraction would cause the grid to be smaller than 7523 // the grid container’s min-width/height (or larger than the grid 7524 // container’s max-width/height), then redo this step, treating the free 7525 // space as definite [...]" 7526 const auto sumOfGridGaps = SumOfGridGaps(); 7527 nscoord newSize = SumOfGridTracks() + sumOfGridGaps; 7528 if (newSize > maxSize) { 7529 aAvailableSize = maxSize; 7530 } else if (newSize < minSize) { 7531 aAvailableSize = minSize; 7532 } 7533 if (aAvailableSize != NS_UNCONSTRAINEDSIZE) { 7534 aAvailableSize = std::max(0, aAvailableSize - sumOfGridGaps); 7535 // Restart with the original track sizes and definite aAvailableSize. 7536 if (origSizes.isSome()) { 7537 mSizes = std::move(*origSizes); 7538 origSizes.reset(); 7539 } // else, no mSizes[].mBase were changed above so it's still correct 7540 if (aAvailableSize == 0) { 7541 break; // zero available size wouldn't change any sizes though... 7542 } 7543 continue; 7544 } 7545 } 7546 break; 7547 } 7548 } 7549 7550 void nsGridContainerFrame::Tracks::AlignJustifyContent( 7551 const nsStylePosition* aStyle, StyleContentDistribution aAligmentStyleValue, 7552 WritingMode aWM, nscoord aContentBoxSize, bool aIsSubgriddedAxis) { 7553 const bool isAlign = mAxis == LogicalAxis::Block; 7554 // Align-/justify-content doesn't apply in a subgridded axis. 7555 // Gap properties do apply though so we need to stretch/position the tracks 7556 // to center-align the gaps with the parent's gaps. 7557 if (MOZ_UNLIKELY(aIsSubgriddedAxis)) { 7558 auto& gap = isAlign ? aStyle->mRowGap : aStyle->mColumnGap; 7559 if (gap.IsNormal()) { 7560 return; 7561 } 7562 auto len = mSizes.Length(); 7563 if (len <= 1) { 7564 return; 7565 } 7566 // This stores the gap deltas between the subgrid gap and the gaps in 7567 // the used track sizes (as encoded in its tracks' mPosition): 7568 nsTArray<nscoord> gapDeltas; 7569 const size_t numGaps = len - 1; 7570 gapDeltas.SetLength(numGaps); 7571 for (size_t i = 0; i < numGaps; ++i) { 7572 TrackSize& sz1 = mSizes[i]; 7573 TrackSize& sz2 = mSizes[i + 1]; 7574 nscoord currentGap = sz2.mPosition - (sz1.mPosition + sz1.mBase); 7575 gapDeltas[i] = mGridGap - currentGap; 7576 } 7577 // Recompute the tracks' size/position so that they end up with 7578 // a subgrid-gap centered on the original track gap. 7579 nscoord currentPos = mSizes[0].mPosition; 7580 nscoord lastHalfDelta(0); 7581 for (size_t i = 0; i < numGaps; ++i) { 7582 TrackSize& sz = mSizes[i]; 7583 nscoord delta = gapDeltas[i]; 7584 nscoord halfDelta; 7585 nscoord roundingError = NSCoordDivRem(delta, 2, &halfDelta); 7586 auto newSize = sz.mBase - (halfDelta + roundingError) - lastHalfDelta; 7587 lastHalfDelta = halfDelta; 7588 // If the gap delta (in particular 'halfDelta + lastHalfDelta') is larger 7589 // than the current track size, newSize can be negative. Don't let the new 7590 // track size (mBase) be negative. 7591 sz.mBase = std::max(newSize, 0); 7592 sz.mPosition = currentPos; 7593 currentPos += newSize + mGridGap; 7594 } 7595 auto& lastTrack = mSizes.LastElement(); 7596 auto newSize = lastTrack.mBase - lastHalfDelta; 7597 lastTrack.mBase = std::max(newSize, 0); 7598 lastTrack.mPosition = currentPos; 7599 return; 7600 } 7601 7602 if (mSizes.IsEmpty()) { 7603 return; 7604 } 7605 7606 bool overflowSafe; 7607 auto alignment = ::GetAlignJustifyValue(aAligmentStyleValue.primary, aWM, 7608 isAlign, &overflowSafe); 7609 if (alignment == StyleAlignFlags::NORMAL) { 7610 alignment = StyleAlignFlags::STRETCH; 7611 // we may need a fallback for 'stretch' below 7612 aAligmentStyleValue = {alignment}; 7613 } 7614 7615 // Compute the free space and count auto-sized tracks. 7616 size_t numAutoTracks = 0; 7617 nscoord space; 7618 if (alignment != StyleAlignFlags::START) { 7619 nscoord trackSizeSum = 0; 7620 if (aIsSubgriddedAxis) { 7621 numAutoTracks = mSizes.Length(); 7622 } else { 7623 for (const TrackSize& sz : mSizes) { 7624 trackSizeSum += sz.mBase; 7625 if (sz.mState & TrackSize::eAutoMaxSizing) { 7626 ++numAutoTracks; 7627 } 7628 } 7629 } 7630 space = aContentBoxSize - trackSizeSum - SumOfGridGaps(); 7631 // Use the fallback value instead when applicable. 7632 if (space < 0 || 7633 (alignment == StyleAlignFlags::SPACE_BETWEEN && mSizes.Length() == 1)) { 7634 auto fallback = GetAlignJustifyDistributionFallback(aAligmentStyleValue, 7635 &overflowSafe); 7636 if (fallback) { 7637 alignment = *fallback; 7638 } 7639 } 7640 if (space == 0 || (space < 0 && overflowSafe)) { 7641 // XXX check that this makes sense also for [last ]baseline (bug 1151204). 7642 alignment = StyleAlignFlags::START; 7643 } 7644 } 7645 7646 // Optimize the cases where we just need to set each track's position. 7647 nscoord pos = 0; 7648 bool distribute = true; 7649 if (alignment == StyleAlignFlags::BASELINE || 7650 alignment == StyleAlignFlags::LAST_BASELINE) { 7651 NS_WARNING("NYI: 'first/last baseline' (bug 1151204)"); // XXX 7652 alignment = StyleAlignFlags::START; 7653 } 7654 if (alignment == StyleAlignFlags::START) { 7655 distribute = false; 7656 } else if (alignment == StyleAlignFlags::END) { 7657 pos = space; 7658 distribute = false; 7659 } else if (alignment == StyleAlignFlags::CENTER) { 7660 pos = space / 2; 7661 distribute = false; 7662 } else if (alignment == StyleAlignFlags::STRETCH) { 7663 distribute = numAutoTracks != 0; 7664 } 7665 if (!distribute) { 7666 for (TrackSize& sz : mSizes) { 7667 sz.mPosition = pos; 7668 pos += sz.mBase + mGridGap; 7669 } 7670 return; 7671 } 7672 7673 // Distribute free space to/between tracks and set their position. 7674 MOZ_ASSERT(space > 0, "should've handled that on the fallback path above"); 7675 nscoord between, roundingError; 7676 if (alignment == StyleAlignFlags::STRETCH) { 7677 MOZ_ASSERT(numAutoTracks > 0, "we handled numAutoTracks == 0 above"); 7678 // The outer loop typically only runs once - it repeats only in a masonry 7679 // axis when some stretchable items reach their `max-size`. 7680 // It's O(n^2) worst case; if all items are stretchable with a `max-size` 7681 // and exactly one item reaches its `max-size` each round. 7682 while (space) { 7683 pos = 0; 7684 nscoord spacePerTrack; 7685 roundingError = NSCoordDivRem(space, numAutoTracks, &spacePerTrack); 7686 space = 0; 7687 for (TrackSize& sz : mSizes) { 7688 sz.mPosition = pos; 7689 if (!(sz.mState & TrackSize::eAutoMaxSizing)) { 7690 pos += sz.mBase + mGridGap; 7691 continue; 7692 } 7693 nscoord stretch = spacePerTrack; 7694 if (roundingError) { 7695 roundingError -= 1; 7696 stretch += 1; 7697 } 7698 nscoord newBase = sz.mBase + stretch; 7699 if (mIsMasonry && (sz.mState & TrackSize::eClampToLimit)) { 7700 auto clampedSize = std::min(newBase, sz.mLimit); 7701 auto sizeOverLimit = newBase - clampedSize; 7702 if (sizeOverLimit > 0) { 7703 newBase = clampedSize; 7704 sz.mState &= ~(sz.mState & TrackSize::eAutoMaxSizing); 7705 // This repeats the outer loop to distribute the superfluous space: 7706 space += sizeOverLimit; 7707 if (--numAutoTracks == 0) { 7708 // ... except if we don't have any stretchable items left. 7709 space = 0; 7710 } 7711 } 7712 } 7713 sz.mBase = newBase; 7714 pos += newBase + mGridGap; 7715 } 7716 } 7717 MOZ_ASSERT(!roundingError, "we didn't distribute all rounding error?"); 7718 return; 7719 } 7720 if (alignment == StyleAlignFlags::SPACE_BETWEEN) { 7721 MOZ_ASSERT(mSizes.Length() > 1, "should've used a fallback above"); 7722 roundingError = NSCoordDivRem(space, mSizes.Length() - 1, &between); 7723 } else if (alignment == StyleAlignFlags::SPACE_AROUND) { 7724 roundingError = NSCoordDivRem(space, mSizes.Length(), &between); 7725 pos = between / 2; 7726 } else if (alignment == StyleAlignFlags::SPACE_EVENLY) { 7727 roundingError = NSCoordDivRem(space, mSizes.Length() + 1, &between); 7728 pos = between; 7729 } else { 7730 MOZ_ASSERT_UNREACHABLE("unknown align-/justify-content value"); 7731 between = 0; // just to avoid a compiler warning 7732 roundingError = 0; // just to avoid a compiler warning 7733 } 7734 between += mGridGap; 7735 for (TrackSize& sz : mSizes) { 7736 sz.mPosition = pos; 7737 nscoord spacing = between; 7738 if (roundingError) { 7739 roundingError -= 1; 7740 spacing += 1; 7741 } 7742 pos += sz.mBase + spacing; 7743 } 7744 MOZ_ASSERT(!roundingError, "we didn't distribute all rounding error?"); 7745 } 7746 7747 nscoord nsGridContainerFrame::Tracks::TotalTrackSizeWithoutAlignment( 7748 const nsGridContainerFrame* aGridContainerFrame) const { 7749 if (aGridContainerFrame->IsSubgrid(mAxis)) { 7750 // TODO: Investigate whether GridLineEdge here may include extra packing 7751 // space introduced by align-content or justify-content, and if that could 7752 // lead to inconsistent metrics vs. the non-subgrid path. 7753 return GridLineEdge(mSizes.Length(), GridLineSide::BeforeGridGap); 7754 } 7755 7756 // This method allows for the possibility that AlignJustifyContent() might not 7757 // be called yet. Therefore, we can't use GridLineEdge() here, as mPosition 7758 // may not be calculated. 7759 return SumOfGridTracksAndGaps(); 7760 } 7761 7762 void nsGridContainerFrame::LineRange::ToPositionAndLength( 7763 const TrackPlan& aTrackSizes, nscoord* aPos, nscoord* aLength) const { 7764 MOZ_ASSERT(mStart != kAutoLine && mEnd != kAutoLine, 7765 "expected a definite LineRange"); 7766 MOZ_ASSERT(mStart < mEnd); 7767 nscoord startPos = aTrackSizes[mStart].mPosition; 7768 const TrackSize& sz = aTrackSizes[mEnd - 1]; 7769 *aPos = startPos; 7770 *aLength = (sz.mPosition + sz.mBase) - startPos; 7771 } 7772 7773 nscoord nsGridContainerFrame::LineRange::ToLength( 7774 const TrackPlan& aTrackSizes) const { 7775 MOZ_ASSERT(mStart != kAutoLine && mEnd != kAutoLine, 7776 "expected a definite LineRange"); 7777 MOZ_ASSERT(mStart < mEnd); 7778 nscoord startPos = aTrackSizes[mStart].mPosition; 7779 const TrackSize& sz = aTrackSizes[mEnd - 1]; 7780 return (sz.mPosition + sz.mBase) - startPos; 7781 } 7782 7783 void nsGridContainerFrame::LineRange::ToPositionAndLengthForAbsPos( 7784 const Tracks& aTracks, nscoord aGridOrigin, nscoord* aPos, 7785 nscoord* aLength) const { 7786 // kAutoLine for abspos children contributes the corresponding edge 7787 // of the grid container's padding-box. 7788 if (mEnd == kAutoLine) { 7789 if (mStart == kAutoLine) { 7790 // done 7791 } else { 7792 const nscoord endPos = *aPos + *aLength; 7793 auto side = mStart == aTracks.mSizes.Length() 7794 ? GridLineSide::BeforeGridGap 7795 : GridLineSide::AfterGridGap; 7796 nscoord startPos = aTracks.GridLineEdge(mStart, side); 7797 *aPos = aGridOrigin + startPos; 7798 *aLength = std::max(endPos - *aPos, 0); 7799 } 7800 } else { 7801 if (mStart == kAutoLine) { 7802 auto side = 7803 mEnd == 0 ? GridLineSide::AfterGridGap : GridLineSide::BeforeGridGap; 7804 nscoord endPos = aTracks.GridLineEdge(mEnd, side); 7805 *aLength = std::max(aGridOrigin + endPos, 0); 7806 } else if (MOZ_LIKELY(mStart != mEnd)) { 7807 nscoord pos; 7808 ToPositionAndLength(aTracks.mSizes, &pos, aLength); 7809 *aPos = aGridOrigin + pos; 7810 } else { 7811 // The grid area only covers removed 'auto-fit' tracks. 7812 nscoord pos = aTracks.GridLineEdge(mStart, GridLineSide::BeforeGridGap); 7813 *aPos = aGridOrigin + pos; 7814 *aLength = nscoord(0); 7815 } 7816 } 7817 } 7818 7819 LogicalSize nsGridContainerFrame::GridReflowInput::PercentageBasisFor( 7820 LogicalAxis aAxis, const GridItemInfo& aGridItem) const { 7821 auto wm = aGridItem.mFrame->GetWritingMode(); 7822 const auto* itemParent = aGridItem.mFrame->GetParent(); 7823 if (MOZ_UNLIKELY(itemParent != mFrame)) { 7824 // The item comes from a descendant subgrid. Use the subgrid's 7825 // used track sizes to resolve the grid area size, if present. 7826 MOZ_ASSERT(itemParent->IsGridContainerFrame()); 7827 auto* subgridFrame = static_cast<const nsGridContainerFrame*>(itemParent); 7828 MOZ_ASSERT(subgridFrame->IsSubgrid()); 7829 if (auto* uts = subgridFrame->GetUsedTrackSizes()) { 7830 auto subgridWM = subgridFrame->GetWritingMode(); 7831 LogicalSize cbSize(subgridWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); 7832 if (!subgridFrame->IsColSubgrid() && 7833 uts->mCanResolveLineRangeSize[LogicalAxis::Inline]) { 7834 // NOTE: At this point aGridItem.mArea is in this->mFrame coordinates 7835 // and thus may have been transposed. The range values in a non- 7836 // subgridded axis still has its original values in subgridFrame's 7837 // coordinates though. 7838 const auto subgridIAxisInGridWM = 7839 subgridWM.ConvertAxisTo(LogicalAxis::Inline, mWM); 7840 const auto& range = 7841 aGridItem.mArea.LineRangeForAxis(subgridIAxisInGridWM); 7842 cbSize.ISize(subgridWM) = 7843 range.ToLength(uts->mTrackPlans[LogicalAxis::Inline]); 7844 } 7845 if (!subgridFrame->IsRowSubgrid() && 7846 uts->mCanResolveLineRangeSize[LogicalAxis::Block]) { 7847 const auto subgridBAxisInGridWM = 7848 subgridWM.ConvertAxisTo(LogicalAxis::Block, mWM); 7849 const auto& range = 7850 aGridItem.mArea.LineRangeForAxis(subgridBAxisInGridWM); 7851 cbSize.BSize(subgridWM) = 7852 range.ToLength(uts->mTrackPlans[LogicalAxis::Block]); 7853 } 7854 return cbSize.ConvertTo(wm, subgridWM); 7855 } 7856 7857 return LogicalSize(wm, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); 7858 } 7859 7860 // Get row size and column size for the grid area occupied by aGridItem. 7861 const nscoord colSize = mCols.mCanResolveLineRangeSize 7862 ? mCols.ResolveSize(aGridItem.mArea.mCols) 7863 : NS_UNCONSTRAINEDSIZE; 7864 const nscoord rowSize = mRows.mCanResolveLineRangeSize 7865 ? mRows.ResolveSize(aGridItem.mArea.mRows) 7866 : NS_UNCONSTRAINEDSIZE; 7867 return !wm.IsOrthogonalTo(mWM) ? LogicalSize(wm, colSize, rowSize) 7868 : LogicalSize(wm, rowSize, colSize); 7869 } 7870 7871 LogicalRect nsGridContainerFrame::GridReflowInput::ContainingBlockFor( 7872 const GridArea& aArea) const { 7873 nscoord i, b, iSize, bSize; 7874 MOZ_ASSERT(aArea.mCols.Extent() > 0, "grid items cover at least one track"); 7875 MOZ_ASSERT(aArea.mRows.Extent() > 0, "grid items cover at least one track"); 7876 aArea.mCols.ToPositionAndLength(mCols.mSizes, &i, &iSize); 7877 aArea.mRows.ToPositionAndLength(mRows.mSizes, &b, &bSize); 7878 return LogicalRect(mWM, i, b, iSize, bSize); 7879 } 7880 7881 LogicalRect nsGridContainerFrame::GridReflowInput::ContainingBlockForAbsPos( 7882 const GridArea& aArea, const LogicalPoint& aGridOrigin, 7883 const LogicalRect& aGridCB) const { 7884 nscoord i = aGridCB.IStart(mWM); 7885 nscoord b = aGridCB.BStart(mWM); 7886 nscoord iSize = aGridCB.ISize(mWM); 7887 nscoord bSize = aGridCB.BSize(mWM); 7888 aArea.mCols.ToPositionAndLengthForAbsPos(mCols, aGridOrigin.I(mWM), &i, 7889 &iSize); 7890 aArea.mRows.ToPositionAndLengthForAbsPos(mRows, aGridOrigin.B(mWM), &b, 7891 &bSize); 7892 return LogicalRect(mWM, i, b, iSize, bSize); 7893 } 7894 7895 void nsGridContainerFrame::GridReflowInput::AlignJustifyContentInMasonryAxis( 7896 nscoord aMasonryBoxSize, nscoord aContentBoxSize) { 7897 if (aContentBoxSize == NS_UNCONSTRAINEDSIZE) { 7898 aContentBoxSize = aMasonryBoxSize; 7899 } 7900 auto& masonryAxisTracks = mRows.mIsMasonry ? mRows : mCols; 7901 MOZ_ASSERT(masonryAxisTracks.mSizes.Length() == 2, 7902 "unexpected masonry axis tracks"); 7903 const auto masonryAxis = masonryAxisTracks.mAxis; 7904 const auto contentAlignment = mGridStyle->UsedContentAlignment(masonryAxis); 7905 if (contentAlignment.primary == StyleAlignFlags::NORMAL || 7906 contentAlignment.primary == StyleAlignFlags::STRETCH) { 7907 // Stretch the "masonry box" to the full content box if it's smaller. 7908 nscoord cbSize = std::max(aMasonryBoxSize, aContentBoxSize); 7909 for (auto& sz : masonryAxisTracks.mSizes) { 7910 sz.mBase = cbSize; 7911 } 7912 return; 7913 } 7914 7915 // Save our current track sizes; replace them with one track sized to 7916 // the masonry box and align that within our content box. 7917 auto savedTrackSizes(std::move(masonryAxisTracks.mSizes)); 7918 masonryAxisTracks.mSizes.AppendElement(savedTrackSizes[0]); 7919 masonryAxisTracks.mSizes[0].mBase = aMasonryBoxSize; 7920 masonryAxisTracks.AlignJustifyContent(mGridStyle, contentAlignment, mWM, 7921 aContentBoxSize, false); 7922 nscoord masonryBoxOffset = masonryAxisTracks.mSizes[0].mPosition; 7923 // Restore the original track sizes... 7924 masonryAxisTracks.mSizes = std::move(savedTrackSizes); 7925 // ...then reposition and resize all of them to the aligned result. 7926 for (auto& sz : masonryAxisTracks.mSizes) { 7927 sz.mPosition = masonryBoxOffset; 7928 sz.mBase = aMasonryBoxSize; 7929 } 7930 } 7931 7932 // XXX This function was gutted when the 'align-tracks' and 'justify-tracks' 7933 // properties were removed in 7934 // https://bugzilla.mozilla.org/show_bug.cgi?id=1900195 7935 // Possibly the current design of the Masonry code doesn't make much sense now 7936 // without those properties, or at the very least this function should be 7937 // renamed? 7938 // 7939 // Note: this is called after all items have been positioned/reflowed. 7940 // The masonry-axis tracks have the size of the "masonry box" at this point 7941 // and are positioned according to 'align/justify-content'. 7942 void nsGridContainerFrame::GridReflowInput::AlignJustifyTracksInMasonryAxis( 7943 const LogicalSize& aContentSize, const nsSize& aContainerSize) { 7944 auto& masonryAxisTracks = mRows.mIsMasonry ? mRows : mCols; 7945 MOZ_ASSERT(masonryAxisTracks.mSizes.Length() == 2, 7946 "unexpected masonry axis tracks"); 7947 // The offset to the "masonry box" from our content-box start edge. 7948 const nscoord masonryBoxOffset = masonryAxisTracks.mSizes[0].mPosition; 7949 if (masonryBoxOffset == 0) { 7950 return; 7951 } 7952 7953 const auto masonryAxis = masonryAxisTracks.mAxis; 7954 auto gridAxis = GetOrthogonalAxis(masonryAxis); 7955 auto& gridAxisTracks = TracksFor(gridAxis); 7956 auto wm = mWM; 7957 7958 for (auto i : IntegerRange(gridAxisTracks.mSizes.Length())) { 7959 // TODO move placeholders too 7960 auto delta = masonryBoxOffset; 7961 LogicalPoint logicalDelta(wm); 7962 logicalDelta.Pos(masonryAxis, wm) = delta; 7963 for (const auto& item : mGridItems) { 7964 if (item.mArea.LineRangeForAxis(gridAxis).mStart != i) { 7965 continue; 7966 } 7967 item.mFrame->MovePositionBy(wm, logicalDelta); 7968 } 7969 } 7970 } 7971 7972 /** 7973 * Return a Fragmentainer object if we have a fragmentainer frame in our 7974 * ancestor chain of containing block (CB) reflow inputs. We'll only 7975 * continue traversing the ancestor chain as long as the CBs have 7976 * the same writing-mode and have overflow:visible. 7977 */ 7978 Maybe<nsGridContainerFrame::Fragmentainer> 7979 nsGridContainerFrame::GetNearestFragmentainer( 7980 const GridReflowInput& aGridRI) const { 7981 Maybe<nsGridContainerFrame::Fragmentainer> data; 7982 const ReflowInput* gridRI = aGridRI.mReflowInput; 7983 if (!gridRI->IsInFragmentedContext()) { 7984 return data; 7985 } 7986 WritingMode wm = aGridRI.mWM; 7987 const ReflowInput* cbRI = gridRI->mCBReflowInput; 7988 for (; cbRI; cbRI = cbRI->mCBReflowInput) { 7989 ScrollContainerFrame* sf = do_QueryFrame(cbRI->mFrame); 7990 if (sf) { 7991 break; 7992 } 7993 if (wm.IsOrthogonalTo(cbRI->GetWritingMode())) { 7994 break; 7995 } 7996 LayoutFrameType frameType = cbRI->mFrame->Type(); 7997 if ((frameType == LayoutFrameType::Canvas && 7998 PresContext()->IsPaginated()) || 7999 frameType == LayoutFrameType::ColumnSet) { 8000 data.emplace(); 8001 data->mIsTopOfPage = gridRI->mFlags.mIsTopOfPage; 8002 if (gridRI->AvailableBSize() != NS_UNCONSTRAINEDSIZE) { 8003 data->mToFragmentainerEnd = aGridRI.mFragBStart + 8004 gridRI->AvailableBSize() - 8005 aGridRI.mBorderPadding.BStart(wm); 8006 } else { 8007 // This occurs when nsColumnSetFrame reflows its last column in 8008 // unconstrained available block-size. 8009 data->mToFragmentainerEnd = NS_UNCONSTRAINEDSIZE; 8010 } 8011 const auto numRows = aGridRI.mRows.mSizes.Length(); 8012 data->mCanBreakAtStart = 8013 numRows > 0 && aGridRI.mRows.mSizes[0].mPosition > 0; 8014 nscoord bSize = gridRI->ComputedBSize(); 8015 data->mIsAutoBSize = bSize == NS_UNCONSTRAINEDSIZE; 8016 if (data->mIsAutoBSize) { 8017 bSize = gridRI->ComputedMinBSize(); 8018 } else { 8019 bSize = gridRI->ApplyMinMaxBSize(bSize); 8020 } 8021 nscoord gridEnd = 8022 aGridRI.mRows.GridLineEdge(numRows, GridLineSide::BeforeGridGap); 8023 data->mCanBreakAtEnd = bSize > gridEnd && bSize > aGridRI.mFragBStart; 8024 break; 8025 } 8026 } 8027 return data; 8028 } 8029 8030 void nsGridContainerFrame::ReflowInFlowChild( 8031 nsIFrame* aChild, const GridItemInfo* aGridItemInfo, nsSize aContainerSize, 8032 const Maybe<nscoord>& aStretchBSize, const Fragmentainer* aFragmentainer, 8033 const GridReflowInput& aGridRI, const LogicalRect& aContentArea, 8034 ReflowOutput& aDesiredSize, nsReflowStatus& aStatus) { 8035 nsPresContext* pc = PresContext(); 8036 ComputedStyle* containerSC = Style(); 8037 WritingMode wm = aGridRI.mReflowInput->GetWritingMode(); 8038 const bool isGridItem = !!aGridItemInfo; 8039 MOZ_ASSERT(isGridItem == !aChild->IsPlaceholderFrame()); 8040 LogicalRect cb(wm); 8041 WritingMode childWM = aChild->GetWritingMode(); 8042 bool isConstrainedBSize = false; 8043 nscoord toFragmentainerEnd; 8044 // The part of the child's grid area that's in previous container fragments. 8045 nscoord consumedGridAreaBSize = 0; 8046 if (MOZ_LIKELY(isGridItem)) { 8047 MOZ_ASSERT(aGridItemInfo->mFrame == aChild); 8048 const GridArea& area = aGridItemInfo->mArea; 8049 MOZ_ASSERT(area.IsDefinite()); 8050 cb = aGridRI.ContainingBlockFor(area); 8051 if (aFragmentainer && !wm.IsOrthogonalTo(childWM)) { 8052 // |gridAreaBOffset| is the offset of the child's grid area in this 8053 // container fragment (if negative, that distance is the child CB size 8054 // consumed in previous container fragments). Note that cb.BStart 8055 // (initially) and aState.mFragBStart are in "global" grid coordinates 8056 // (like all track positions). 8057 nscoord gridAreaBOffset = cb.BStart(wm) - aGridRI.mFragBStart; 8058 consumedGridAreaBSize = std::max(0, -gridAreaBOffset); 8059 cb.BStart(wm) = std::max(0, gridAreaBOffset); 8060 if (aFragmentainer->mToFragmentainerEnd != NS_UNCONSTRAINEDSIZE) { 8061 toFragmentainerEnd = aFragmentainer->mToFragmentainerEnd - 8062 aGridRI.mFragBStart - cb.BStart(wm); 8063 toFragmentainerEnd = std::max(toFragmentainerEnd, 0); 8064 isConstrainedBSize = true; 8065 } 8066 } 8067 cb += aContentArea.Origin(wm); 8068 aGridRI.mRows.AlignBaselineSubtree(*aGridItemInfo); 8069 aGridRI.mCols.AlignBaselineSubtree(*aGridItemInfo); 8070 // Setup [align|justify]-content:[last ]baseline related frame properties. 8071 // These are added to the padding in SizeComputationInput::InitOffsets. 8072 // (a negative value signals the value is for 'last baseline' and should be 8073 // added to the (logical) end padding) 8074 typedef const FramePropertyDescriptor<SmallValueHolder<nscoord>>* Prop; 8075 auto SetProp = [aGridItemInfo, aChild](LogicalAxis aGridAxis, Prop aProp) { 8076 auto state = aGridItemInfo->mState[aGridAxis]; 8077 auto baselineAdjust = (state & ItemState::eContentBaseline) 8078 ? aGridItemInfo->mBaselineOffset[aGridAxis] 8079 : nscoord(0); 8080 if (baselineAdjust < nscoord(0)) { 8081 // This happens when the subtree overflows its track. 8082 // XXX spec issue? it's unclear how to handle this. 8083 baselineAdjust = nscoord(0); 8084 } else if (state & ItemState::eLastBaseline) { 8085 // FIXME: We're not setting the ItemState::eEndSideBaseline flag any 8086 // more as the new baseline sharing group calculation handles most of 8087 // the cases we need. For non-masonry grids this flag was always set 8088 // for LAST_BASELINE items, so we're just mimicking that behavior here. 8089 // That said, masonry grids might not work 100% any more.. 8090 baselineAdjust = -baselineAdjust; 8091 } 8092 if (baselineAdjust != nscoord(0)) { 8093 aChild->SetProperty(aProp, baselineAdjust); 8094 } else { 8095 aChild->RemoveProperty(aProp); 8096 } 8097 }; 8098 const bool isOrthogonal = wm.IsOrthogonalTo(childWM); 8099 SetProp(LogicalAxis::Block, 8100 isOrthogonal ? IBaselinePadProperty() : BBaselinePadProperty()); 8101 SetProp(LogicalAxis::Inline, 8102 isOrthogonal ? BBaselinePadProperty() : IBaselinePadProperty()); 8103 } else { 8104 // By convention, for frames that perform CSS Box Alignment, we position 8105 // placeholder children at the start corner of their alignment container, 8106 // and in this case that's usually the grid's content-box. 8107 // ("Usually" - the exception is when the grid *also* forms the 8108 // abs.pos. containing block. In that case, the alignment container isn't 8109 // the content-box -- it's some grid area instead. But that case doesn't 8110 // require any special handling here, because we handle it later using a 8111 // special flag (ReflowInput::InitFlag::StaticPosIsCBOrigin) which will make 8112 // us ignore the placeholder's position entirely.) 8113 cb = aContentArea; 8114 aChild->AddStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN); 8115 } 8116 8117 LogicalSize reflowSize(cb.Size(wm)); 8118 if (isConstrainedBSize) { 8119 reflowSize.BSize(wm) = toFragmentainerEnd; 8120 } 8121 LogicalSize childCBSize = reflowSize.ConvertTo(childWM, wm); 8122 8123 // Setup the ClampMarginBoxMinSize reflow flags and property, if needed. 8124 ComputeSizeFlags csFlags; 8125 if (aGridItemInfo) { 8126 const auto childIAxisInWM = childWM.ConvertAxisTo(LogicalAxis::Inline, wm); 8127 // Clamp during reflow if we're stretching in that axis. 8128 if (GridItemShouldStretch(aChild, LogicalAxis::Inline)) { 8129 if (aGridItemInfo->mState[childIAxisInWM] & 8130 ItemState::eClampMarginBoxMinSize) { 8131 csFlags += ComputeSizeFlag::IClampMarginBoxMinSize; 8132 } 8133 } else { 8134 csFlags += ComputeSizeFlag::ShrinkWrap; 8135 } 8136 8137 const auto childBAxisInWM = GetOrthogonalAxis(childIAxisInWM); 8138 if (GridItemShouldStretch(aChild, LogicalAxis::Block) && 8139 aGridItemInfo->mState[childBAxisInWM] & 8140 ItemState::eClampMarginBoxMinSize) { 8141 csFlags += ComputeSizeFlag::BClampMarginBoxMinSize; 8142 aChild->SetProperty(BClampMarginBoxMinSizeProperty(), 8143 childCBSize.BSize(childWM)); 8144 } else { 8145 aChild->RemoveProperty(BClampMarginBoxMinSizeProperty()); 8146 } 8147 8148 if ((aGridItemInfo->mState[childIAxisInWM] & 8149 ItemState::eContentBasedAutoMinSize)) { 8150 csFlags += ComputeSizeFlag::IApplyAutoMinSize; 8151 } 8152 } 8153 8154 if (!isConstrainedBSize) { 8155 childCBSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE; 8156 } 8157 LogicalSize percentBasis(cb.Size(wm).ConvertTo(childWM, wm)); 8158 ReflowInput childRI(pc, *aGridRI.mReflowInput, aChild, childCBSize, 8159 Some(percentBasis), {}, {}, csFlags); 8160 childRI.mFlags.mIsTopOfPage = 8161 aFragmentainer ? aFragmentainer->mIsTopOfPage : false; 8162 8163 // FIXME (perf): It would be faster to do this only if the previous reflow of 8164 // the child was a measuring reflow, and only if the child does some of the 8165 // things that are affected by ComputeSizeFlag::IsGridMeasuringReflow. 8166 childRI.SetBResize(true); 8167 childRI.SetBResizeForPercentages(true); 8168 8169 // If the child is stretching in its block axis, and we might be fragmenting 8170 // it in that axis, then setup a frame property to tell 8171 // nsBlockFrame::ComputeFinalSize the size. 8172 if (isConstrainedBSize && !wm.IsOrthogonalTo(childWM)) { 8173 const bool stretch = 8174 childRI.mStylePosition 8175 ->BSize(childWM, AnchorPosResolutionParams::From(&childRI)) 8176 ->IsAuto() && 8177 GridItemShouldStretch(aChild, LogicalAxis::Block); 8178 if (stretch) { 8179 aChild->SetProperty(FragStretchBSizeProperty(), *aStretchBSize); 8180 } else { 8181 aChild->RemoveProperty(FragStretchBSizeProperty()); 8182 } 8183 } 8184 8185 // We need the width of the child before we can correctly convert 8186 // the writing-mode of its origin, so we reflow at (0, 0) using a dummy 8187 // aContainerSize, and then pass the correct position to FinishReflowChild. 8188 ReflowOutput childSize(childRI); 8189 const nsSize dummyContainerSize; 8190 8191 ReflowChild(aChild, pc, childSize, childRI, childWM, LogicalPoint(childWM), 8192 dummyContainerSize, ReflowChildFlags::Default, aStatus); 8193 8194 // childPos here initially represents the position that the child would have 8195 // (expressed as an istart,bstart corner *in its own writing-mode*) if it 8196 // were placed at the cb origin: 8197 LogicalPoint childPos = cb.Origin(wm).ConvertRectOriginTo( 8198 childWM, wm, childSize.PhysicalSize(), aContainerSize); 8199 8200 // Apply align/justify-self and reflow again if that affects the size. 8201 if (MOZ_LIKELY(isGridItem)) { 8202 LogicalSize size = childSize.Size(childWM); // from the ReflowChild() 8203 auto applyItemSelfAlignment = [&](LogicalAxis aAxis, nscoord aCBSize) { 8204 auto align = 8205 childRI.mStylePosition->UsedSelfAlignment(aAxis, containerSC); 8206 auto state = aGridItemInfo->mState[aAxis]; 8207 AlignJustifyFlags flags; 8208 if (IsMasonry(aAxis)) { 8209 // In a masonry axis, we inhibit applying 'stretch' and auto-margins 8210 // here since AlignJustifyTracksInMasonryAxis deals with that. 8211 // The only other {align,justify}-{self,content} values that have an 8212 // effect are '[last] baseline', the rest behave as 'start'. 8213 if (MOZ_LIKELY(!(state & ItemState::eSelfBaseline))) { 8214 align = {StyleAlignFlags::START}; 8215 } else { 8216 auto group = (state & ItemState::eFirstBaseline) 8217 ? BaselineSharingGroup::First 8218 : BaselineSharingGroup::Last; 8219 auto itemStart = aGridItemInfo->mArea.LineRangeForAxis(aAxis).mStart; 8220 aCBSize = aGridRI.TracksFor(aAxis) 8221 .mSizes[itemStart] 8222 .mBaselineSubtreeSize[group]; 8223 } 8224 flags += AlignJustifyFlag::IgnoreAutoMargins; 8225 } else if (state & ItemState::eContentBaseline) { 8226 align = {(state & ItemState::eFirstBaseline) 8227 ? StyleAlignFlags::SELF_START 8228 : StyleAlignFlags::SELF_END}; 8229 } 8230 if (aAxis == LogicalAxis::Block) { 8231 AlignSelf(*aGridItemInfo, align, aCBSize, wm, childRI, size, flags, 8232 &childPos); 8233 } else { 8234 JustifySelf(*aGridItemInfo, align, aCBSize, wm, childRI, size, flags, 8235 &childPos); 8236 } 8237 }; 8238 if (aStatus.IsComplete()) { 8239 applyItemSelfAlignment(LogicalAxis::Block, 8240 cb.BSize(wm) - consumedGridAreaBSize); 8241 } 8242 applyItemSelfAlignment(LogicalAxis::Inline, cb.ISize(wm)); 8243 } // else, AbsoluteContainingBlock.cpp will handle align/justify-self. 8244 8245 FinishReflowChild(aChild, pc, childSize, &childRI, childWM, childPos, 8246 aContainerSize, ReflowChildFlags::ApplyRelativePositioning); 8247 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, aChild); 8248 } 8249 8250 nscoord nsGridContainerFrame::ReflowInFragmentainer( 8251 GridReflowInput& aGridRI, const LogicalRect& aContentArea, 8252 ReflowOutput& aDesiredSize, nsReflowStatus& aStatus, 8253 Fragmentainer& aFragmentainer, const nsSize& aContainerSize) { 8254 MOZ_ASSERT(aStatus.IsEmpty()); 8255 MOZ_ASSERT(aGridRI.mReflowInput); 8256 8257 // Collect our grid items and sort them in row order. Collect placeholders 8258 // and put them in a separate array. 8259 nsTArray<const GridItemInfo*> sortedItems(aGridRI.mGridItems.Length()); 8260 nsTArray<nsIFrame*> placeholders(aGridRI.mAbsPosItems.Length()); 8261 aGridRI.mIter.Reset(CSSOrderAwareFrameIterator::ChildFilter::IncludeAll); 8262 for (; !aGridRI.mIter.AtEnd(); aGridRI.mIter.Next()) { 8263 nsIFrame* child = *aGridRI.mIter; 8264 if (!child->IsPlaceholderFrame()) { 8265 const GridItemInfo* info = &aGridRI.mGridItems[aGridRI.mIter.ItemIndex()]; 8266 sortedItems.AppendElement(info); 8267 } else { 8268 placeholders.AppendElement(child); 8269 } 8270 } 8271 // NOTE: We don't need stable_sort here, except in Masonry layout. There are 8272 // no dependencies on having content order between items on the same row in 8273 // the code below in the non-Masonry case. 8274 if (IsMasonry()) { 8275 std::stable_sort(sortedItems.begin(), sortedItems.end(), 8276 GridItemInfo::IsStartRowLessThan); 8277 } else { 8278 std::sort(sortedItems.begin(), sortedItems.end(), 8279 GridItemInfo::IsStartRowLessThan); 8280 } 8281 8282 // Reflow our placeholder children; they must all be complete. 8283 for (auto child : placeholders) { 8284 nsReflowStatus childStatus; 8285 ReflowInFlowChild(child, nullptr, aContainerSize, Nothing(), 8286 &aFragmentainer, aGridRI, aContentArea, aDesiredSize, 8287 childStatus); 8288 MOZ_ASSERT(childStatus.IsComplete(), 8289 "nsPlaceholderFrame should never need to be fragmented"); 8290 } 8291 8292 // The available size for children - we'll set this to the edge of the last 8293 // row in most cases below, but for now use the full size. 8294 nscoord childAvailableSize = aFragmentainer.mToFragmentainerEnd; 8295 const uint32_t startRow = aGridRI.mStartRow; 8296 const uint32_t numRows = aGridRI.mRows.mSizes.Length(); 8297 bool isBDBClone = aGridRI.mReflowInput->mStyleBorder->mBoxDecorationBreak == 8298 StyleBoxDecorationBreak::Clone; 8299 nscoord bpBEnd = aGridRI.mBorderPadding.BEnd(aGridRI.mWM); 8300 8301 // Set |endRow| to the first row that doesn't fit. 8302 uint32_t endRow = numRows; 8303 for (uint32_t row = startRow; row < numRows; ++row) { 8304 auto& sz = aGridRI.mRows.mSizes[row]; 8305 const nscoord bEnd = sz.mPosition + sz.mBase; 8306 nscoord remainingAvailableSize = childAvailableSize - bEnd; 8307 if (remainingAvailableSize < 0 || 8308 (isBDBClone && remainingAvailableSize < bpBEnd)) { 8309 endRow = row; 8310 break; 8311 } 8312 } 8313 8314 // Check for forced breaks on the items if available block-size for children 8315 // is constrained. That is, ignore forced breaks if available block-size for 8316 // children is unconstrained since our parent expected us to be fully 8317 // complete. 8318 bool isForcedBreak = false; 8319 const bool avoidBreakInside = ShouldAvoidBreakInside(*aGridRI.mReflowInput); 8320 if (childAvailableSize != NS_UNCONSTRAINEDSIZE) { 8321 const bool isTopOfPage = aFragmentainer.mIsTopOfPage; 8322 for (const GridItemInfo* info : sortedItems) { 8323 uint32_t itemStartRow = info->mArea.mRows.mStart; 8324 if (itemStartRow == endRow) { 8325 break; 8326 } 8327 const auto* disp = info->mFrame->StyleDisplay(); 8328 if (disp->BreakBefore()) { 8329 // Propagate break-before on the first row to the container unless we're 8330 // already at top-of-page. 8331 if ((itemStartRow == 0 && !isTopOfPage) || avoidBreakInside) { 8332 aStatus.SetInlineLineBreakBeforeAndReset(); 8333 return aGridRI.mFragBStart; 8334 } 8335 if ((itemStartRow > startRow || 8336 (itemStartRow == startRow && !isTopOfPage)) && 8337 itemStartRow < endRow) { 8338 endRow = itemStartRow; 8339 isForcedBreak = true; 8340 // reset any BREAK_AFTER we found on an earlier item 8341 aStatus.Reset(); 8342 break; // we're done since the items are sorted in row order 8343 } 8344 } 8345 uint32_t itemEndRow = info->mArea.mRows.mEnd; 8346 if (disp->BreakAfter()) { 8347 if (itemEndRow != numRows) { 8348 if (itemEndRow > startRow && itemEndRow < endRow) { 8349 endRow = itemEndRow; 8350 isForcedBreak = true; 8351 // No "break;" here since later items with break-after may have 8352 // a shorter span. 8353 } 8354 } else { 8355 // Propagate break-after on the last row to the container, we may 8356 // still find a break-before on this row though (and reset aStatus). 8357 aStatus.SetInlineLineBreakAfter(); // tentative 8358 } 8359 } 8360 } 8361 8362 // Consume at least one row in each fragment until we have consumed them 8363 // all. Except for the first row if there's a break opportunity before it. 8364 if (startRow == endRow && startRow != numRows && 8365 (startRow != 0 || !aFragmentainer.mCanBreakAtStart)) { 8366 ++endRow; 8367 } 8368 8369 // Honor break-inside:avoid if we can't fit all rows. 8370 if (avoidBreakInside && endRow < numRows) { 8371 aStatus.SetInlineLineBreakBeforeAndReset(); 8372 return aGridRI.mFragBStart; 8373 } 8374 } 8375 8376 // Calculate the block-size including this fragment. 8377 nscoord bEndRow = 8378 aGridRI.mRows.GridLineEdge(endRow, GridLineSide::BeforeGridGap); 8379 nscoord bSize; 8380 if (aFragmentainer.mIsAutoBSize) { 8381 // We only apply min-bsize once all rows are complete (when bsize is auto). 8382 if (endRow < numRows) { 8383 bSize = bEndRow; 8384 auto clampedBSize = ClampToCSSMaxBSize(bSize, aGridRI.mReflowInput); 8385 if (MOZ_UNLIKELY(clampedBSize != bSize)) { 8386 // We apply max-bsize in all fragments though. 8387 bSize = clampedBSize; 8388 } else if (!isBDBClone) { 8389 // The max-bsize won't make this fragment COMPLETE, so the block-end 8390 // border will be in a later fragment. 8391 bpBEnd = 0; 8392 } 8393 } else { 8394 bSize = aGridRI.mReflowInput->ApplyMinMaxBSize(bEndRow); 8395 } 8396 } else { 8397 bSize = aGridRI.mReflowInput->ApplyMinMaxBSize( 8398 aGridRI.mReflowInput->ComputedBSize()); 8399 } 8400 8401 // Check for overflow and set aStatus INCOMPLETE if so. 8402 bool overflow = bSize + bpBEnd > childAvailableSize; 8403 if (overflow) { 8404 if (avoidBreakInside) { 8405 aStatus.SetInlineLineBreakBeforeAndReset(); 8406 return aGridRI.mFragBStart; 8407 } 8408 bool breakAfterLastRow = endRow == numRows && aFragmentainer.mCanBreakAtEnd; 8409 if (breakAfterLastRow) { 8410 MOZ_ASSERT(bEndRow < bSize, "bogus aFragmentainer.mCanBreakAtEnd"); 8411 nscoord availableSize = childAvailableSize; 8412 if (isBDBClone) { 8413 availableSize -= bpBEnd; 8414 } 8415 // Pretend we have at least 1px available size, otherwise we'll never make 8416 // progress in consuming our bSize. 8417 availableSize = 8418 std::max(availableSize, aGridRI.mFragBStart + AppUnitsPerCSSPixel()); 8419 // Fill the fragmentainer, but not more than our desired block-size and 8420 // at least to the size of the last row (even if that overflows). 8421 nscoord newBSize = std::min(bSize, availableSize); 8422 newBSize = std::max(newBSize, bEndRow); 8423 // If it's just the border+padding that is overflowing and we have 8424 // box-decoration-break:clone then we are technically COMPLETE. There's 8425 // no point in creating another zero-bsize fragment in this case. 8426 if (newBSize < bSize || !isBDBClone) { 8427 aStatus.SetIncomplete(); 8428 } 8429 bSize = newBSize; 8430 } else if (bSize <= bEndRow && startRow + 1 < endRow) { 8431 if (endRow == numRows) { 8432 // We have more than one row in this fragment, so we can break before 8433 // the last row instead. 8434 --endRow; 8435 bEndRow = 8436 aGridRI.mRows.GridLineEdge(endRow, GridLineSide::BeforeGridGap); 8437 bSize = bEndRow; 8438 if (aFragmentainer.mIsAutoBSize) { 8439 bSize = ClampToCSSMaxBSize(bSize, aGridRI.mReflowInput); 8440 } 8441 } 8442 aStatus.SetIncomplete(); 8443 } else if (endRow < numRows) { 8444 bSize = ClampToCSSMaxBSize(bEndRow, aGridRI.mReflowInput, &aStatus); 8445 } // else - no break opportunities. 8446 } else { 8447 // Even though our block-size fits we need to honor forced breaks, or if 8448 // a row doesn't fit in an auto-sized container (unless it's constrained 8449 // by a max-bsize which make us overflow-incomplete). 8450 if (endRow < numRows && 8451 (isForcedBreak || (aFragmentainer.mIsAutoBSize && bEndRow == bSize))) { 8452 bSize = ClampToCSSMaxBSize(bEndRow, aGridRI.mReflowInput, &aStatus); 8453 } 8454 } 8455 8456 // If we can't fit all rows then we're at least overflow-incomplete. 8457 if (endRow < numRows) { 8458 childAvailableSize = bEndRow; 8459 if (aStatus.IsComplete()) { 8460 aStatus.SetOverflowIncomplete(); 8461 aStatus.SetNextInFlowNeedsReflow(); 8462 } 8463 } else { 8464 // Children always have the full size of the rows in this fragment. 8465 childAvailableSize = std::max(childAvailableSize, bEndRow); 8466 } 8467 8468 return ReflowRowsInFragmentainer(aGridRI, aContentArea, aDesiredSize, aStatus, 8469 aFragmentainer, aContainerSize, sortedItems, 8470 startRow, endRow, bSize, childAvailableSize); 8471 } 8472 8473 nscoord nsGridContainerFrame::ReflowRowsInFragmentainer( 8474 GridReflowInput& aGridRI, const LogicalRect& aContentArea, 8475 ReflowOutput& aDesiredSize, nsReflowStatus& aStatus, 8476 Fragmentainer& aFragmentainer, const nsSize& aContainerSize, 8477 const nsTArray<const GridItemInfo*>& aSortedItems, uint32_t aStartRow, 8478 uint32_t aEndRow, nscoord aBSize, nscoord aAvailableSize) { 8479 FrameHashtable pushedItems; 8480 FrameHashtable incompleteItems; 8481 FrameHashtable overflowIncompleteItems; 8482 Maybe<nsTArray<nscoord>> masonryAxisPos; 8483 const auto rowCount = aGridRI.mRows.mSizes.Length(); 8484 nscoord masonryAxisGap = 0; 8485 const auto wm = aGridRI.mWM; 8486 const bool isColMasonry = IsColMasonry(); 8487 if (isColMasonry) { 8488 for (auto& sz : aGridRI.mCols.mSizes) { 8489 sz.mPosition = 0; 8490 } 8491 masonryAxisGap = nsLayoutUtils::ResolveGapToLength( 8492 aGridRI.mGridStyle->mColumnGap, aContentArea.ISize(wm)); 8493 aGridRI.mCols.mGridGap = masonryAxisGap; 8494 masonryAxisPos.emplace(rowCount); 8495 masonryAxisPos->SetLength(rowCount); 8496 PodZero(masonryAxisPos->Elements(), rowCount); 8497 } 8498 bool isBDBClone = aGridRI.mReflowInput->mStyleBorder->mBoxDecorationBreak == 8499 StyleBoxDecorationBreak::Clone; 8500 bool didGrowRow = false; 8501 // As we walk across rows, we track whether the current row is at the top 8502 // of its grid-fragment, to help decide whether we can break before it. When 8503 // this function starts, our row is at the top of the current fragment if: 8504 // - we're starting with a nonzero row (i.e. we're a continuation) 8505 // OR: 8506 // - we're starting with the first row, & we're not allowed to break before 8507 // it (which makes it effectively at the top of its grid-fragment). 8508 bool isRowTopOfPage = aStartRow != 0 || !aFragmentainer.mCanBreakAtStart; 8509 const bool isStartRowTopOfPage = isRowTopOfPage; 8510 // Save our full available size for later. 8511 const nscoord gridAvailableSize = aFragmentainer.mToFragmentainerEnd; 8512 // Propagate the constrained size to our children. 8513 aFragmentainer.mToFragmentainerEnd = aAvailableSize; 8514 // Reflow the items in row order up to |aEndRow| and push items after that. 8515 uint32_t row = 0; 8516 // |i| is intentionally signed, so we can set it to -1 to restart the loop. 8517 for (int32_t i = 0, len = aSortedItems.Length(); i < len; ++i) { 8518 const GridItemInfo* const info = aSortedItems[i]; 8519 nsIFrame* child = info->mFrame; 8520 row = info->mArea.mRows.mStart; 8521 MOZ_ASSERT(child->GetPrevInFlow() ? row < aStartRow : row >= aStartRow, 8522 "unexpected child start row"); 8523 if (row >= aEndRow) { 8524 pushedItems.Insert(child); 8525 continue; 8526 } 8527 8528 bool rowCanGrow = false; 8529 nscoord maxRowSize = 0; 8530 if (row >= aStartRow) { 8531 if (row > aStartRow) { 8532 isRowTopOfPage = false; 8533 } 8534 // Can we grow this row? Only consider span=1 items per spec... 8535 rowCanGrow = !didGrowRow && info->mArea.mRows.Extent() == 1; 8536 if (rowCanGrow) { 8537 auto& sz = aGridRI.mRows.mSizes[row]; 8538 // and only min-/max-content rows or flex rows in an auto-sized 8539 // container 8540 rowCanGrow = (sz.mState & TrackSize::eMinOrMaxContentMinSizing) || 8541 ((sz.mState & TrackSize::eFlexMaxSizing) && 8542 aFragmentainer.mIsAutoBSize); 8543 if (rowCanGrow) { 8544 if (isBDBClone) { 8545 maxRowSize = gridAvailableSize - aGridRI.mBorderPadding.BEnd(wm); 8546 } else { 8547 maxRowSize = gridAvailableSize; 8548 } 8549 maxRowSize -= sz.mPosition; 8550 // ...and only if there is space for it to grow. 8551 rowCanGrow = maxRowSize > sz.mBase; 8552 } 8553 } 8554 } 8555 8556 if (isColMasonry) { 8557 const auto& cols = info->mArea.mCols; 8558 MOZ_ASSERT((cols.mStart == 0 || cols.mStart == 1) && cols.Extent() == 1); 8559 aGridRI.mCols.mSizes[cols.mStart].mPosition = masonryAxisPos.ref()[row]; 8560 } 8561 8562 // aFragmentainer.mIsTopOfPage is propagated to the child reflow input. 8563 // When it's false the child may request InlineBreak::Before. We set it 8564 // to false when the row is growable (as determined in the CSS Grid 8565 // Fragmentation spec) and there is a non-zero space between it and the 8566 // fragmentainer end (that can be used to grow it). If the child reports 8567 // a forced break in this case, we grow this row to fill the fragment and 8568 // restart the loop. We also restart the loop with |aEndRow = row| 8569 // (but without growing any row) for a InlineBreak::Before child if it spans 8570 // beyond the last row in this fragment. This is to avoid fragmenting it. 8571 // We only restart the loop once. 8572 aFragmentainer.mIsTopOfPage = isRowTopOfPage && !rowCanGrow; 8573 nsReflowStatus childStatus; 8574 // Pass along how much to stretch this fragment, in case it's needed. 8575 nscoord bSize = 8576 aGridRI.mRows.GridLineEdge(std::min(aEndRow, info->mArea.mRows.mEnd), 8577 GridLineSide::BeforeGridGap) - 8578 aGridRI.mRows.GridLineEdge(std::max(aStartRow, row), 8579 GridLineSide::AfterGridGap); 8580 ReflowInFlowChild(child, info, aContainerSize, Some(bSize), &aFragmentainer, 8581 aGridRI, aContentArea, aDesiredSize, childStatus); 8582 MOZ_ASSERT(childStatus.IsInlineBreakBefore() || 8583 !childStatus.IsFullyComplete() || !child->GetNextInFlow(), 8584 "fully-complete reflow should destroy any NIFs"); 8585 8586 if (childStatus.IsInlineBreakBefore()) { 8587 MOZ_ASSERT( 8588 !child->GetPrevInFlow(), 8589 "continuations should never report InlineBreak::Before status"); 8590 MOZ_ASSERT(!aFragmentainer.mIsTopOfPage, 8591 "got IsInlineBreakBefore() at top of page"); 8592 if (!didGrowRow) { 8593 if (rowCanGrow) { 8594 // Grow this row and restart with the next row as |aEndRow|. 8595 aGridRI.mRows.ResizeRow(row, maxRowSize); 8596 if (aGridRI.mSharedGridData) { 8597 aGridRI.mSharedGridData->mRows.ResizeRow(row, maxRowSize); 8598 } 8599 didGrowRow = true; 8600 aEndRow = row + 1; // growing this row makes the next one not fit 8601 i = -1; // i == 0 after the next loop increment 8602 isRowTopOfPage = isStartRowTopOfPage; 8603 overflowIncompleteItems.Clear(); 8604 incompleteItems.Clear(); 8605 nscoord bEndRow = 8606 aGridRI.mRows.GridLineEdge(aEndRow, GridLineSide::BeforeGridGap); 8607 aFragmentainer.mToFragmentainerEnd = bEndRow; 8608 if (aFragmentainer.mIsAutoBSize) { 8609 aBSize = 8610 ClampToCSSMaxBSize(bEndRow, aGridRI.mReflowInput, &aStatus); 8611 } else if (aStatus.IsIncomplete()) { 8612 aBSize = aGridRI.mReflowInput->ApplyMinMaxBSize( 8613 aGridRI.mReflowInput->ComputedBSize()); 8614 aBSize = std::min(bEndRow, aBSize); 8615 } 8616 continue; 8617 } 8618 8619 if (!isRowTopOfPage) { 8620 // We can break before this row - restart with it as the new end row. 8621 aEndRow = row; 8622 aBSize = 8623 aGridRI.mRows.GridLineEdge(aEndRow, GridLineSide::BeforeGridGap); 8624 i = -1; // i == 0 after the next loop increment 8625 isRowTopOfPage = isStartRowTopOfPage; 8626 overflowIncompleteItems.Clear(); 8627 incompleteItems.Clear(); 8628 aStatus.SetIncomplete(); 8629 continue; 8630 } 8631 NS_ERROR("got InlineBreak::Before at top-of-page"); 8632 childStatus.Reset(); 8633 } else { 8634 // We got InlineBreak::Before again after growing the row - this can 8635 // happen if the child isn't splittable, e.g. some form controls. 8636 childStatus.Reset(); 8637 if (child->GetNextInFlow()) { 8638 // The child already has a fragment, so we know it's splittable. 8639 childStatus.SetIncomplete(); 8640 } // else, report that it's complete 8641 } 8642 } else if (childStatus.IsInlineBreakAfter()) { 8643 MOZ_ASSERT_UNREACHABLE("unexpected child reflow status"); 8644 } 8645 8646 MOZ_ASSERT(!childStatus.IsInlineBreakBefore(), 8647 "should've handled InlineBreak::Before above"); 8648 if (childStatus.IsIncomplete()) { 8649 incompleteItems.Insert(child); 8650 } else if (!childStatus.IsFullyComplete()) { 8651 overflowIncompleteItems.Insert(child); 8652 } 8653 if (isColMasonry) { 8654 auto childWM = child->GetWritingMode(); 8655 const auto childAxis = wm.ConvertAxisTo(LogicalAxis::Inline, childWM); 8656 auto normalPos = child->GetLogicalNormalPosition(wm, aContainerSize); 8657 auto sz = 8658 childAxis == LogicalAxis::Block ? child->BSize() : child->ISize(); 8659 auto pos = normalPos.Pos(LogicalAxis::Inline, wm) + sz + 8660 child->GetLogicalUsedMargin(childWM).End(childAxis, childWM); 8661 masonryAxisPos.ref()[row] = 8662 pos + masonryAxisGap - aContentArea.Start(LogicalAxis::Inline, wm); 8663 } 8664 } 8665 8666 // Record a break before |aEndRow|. 8667 aGridRI.mNextFragmentStartRow = aEndRow; 8668 if (aEndRow < rowCount) { 8669 aGridRI.mRows.BreakBeforeRow(aEndRow); 8670 if (aGridRI.mSharedGridData) { 8671 aGridRI.mSharedGridData->mRows.BreakBeforeRow(aEndRow); 8672 } 8673 } 8674 8675 const bool childrenMoved = PushIncompleteChildren( 8676 pushedItems, incompleteItems, overflowIncompleteItems); 8677 if (childrenMoved && aStatus.IsComplete()) { 8678 aStatus.SetOverflowIncomplete(); 8679 aStatus.SetNextInFlowNeedsReflow(); 8680 } 8681 if (!pushedItems.IsEmpty()) { 8682 AddStateBits(NS_STATE_GRID_DID_PUSH_ITEMS); 8683 // NOTE since we messed with our child list here, we intentionally 8684 // make aState.mIter invalid to avoid any use of it after this point. 8685 aGridRI.mIter.Invalidate(); 8686 } 8687 if (!incompleteItems.IsEmpty()) { 8688 // NOTE since we messed with our child list here, we intentionally 8689 // make aState.mIter invalid to avoid any use of it after this point. 8690 aGridRI.mIter.Invalidate(); 8691 } 8692 8693 if (isColMasonry) { 8694 nscoord maxSize = 0; 8695 for (auto pos : masonryAxisPos.ref()) { 8696 maxSize = std::max(maxSize, pos); 8697 } 8698 maxSize = std::max(nscoord(0), maxSize - masonryAxisGap); 8699 aGridRI.AlignJustifyContentInMasonryAxis(maxSize, aContentArea.ISize(wm)); 8700 } 8701 8702 return aBSize; 8703 } 8704 8705 // Here's a brief overview of how Masonry layout is implemented: 8706 // We setup two synthetic tracks in the Masonry axis so that the Reflow code 8707 // can treat it the same as for normal grid layout. The first track is 8708 // fixed (during item placement/layout) at the content box start and contains 8709 // the start items for each grid-axis track. The second track contains 8710 // all other items and is moved to the position where we want to position 8711 // the currently laid out item (like a sliding window as we place items). 8712 // Once item layout is done, the tracks are resized to be the size of 8713 // the "masonry box", which is the offset from the content box start to 8714 // the margin-box end of the item that is furthest away (this happens in 8715 // AlignJustifyContentInMasonryAxis() called at the end of this method). 8716 // This is to prepare for AlignJustifyTracksInMasonryAxis, which is called 8717 // later by our caller. 8718 // Both tracks store their first-/last-baseline group offsets as usual. 8719 // The first-baseline of the start track, and the last-baseline of the last 8720 // track (if they exist) are exported as the grid container's baselines, or 8721 // we fall back to picking an item's baseline (all this is per normal grid 8722 // layout). There's a slight difference in which items belongs to which 8723 // group though - see InitializeItemBaselinesInMasonryAxis for details. 8724 // This method returns the "masonry box" size (in the masonry axis). 8725 nscoord nsGridContainerFrame::MasonryLayout(GridReflowInput& aGridRI, 8726 const LogicalRect& aContentArea, 8727 SizingConstraint aConstraint, 8728 ReflowOutput& aDesiredSize, 8729 nsReflowStatus& aStatus, 8730 Fragmentainer* aFragmentainer, 8731 const nsSize& aContainerSize) { 8732 using BaselineAlignmentSet = Tracks::BaselineAlignmentSet; 8733 8734 auto recordAutoPlacement = [this, &aGridRI](GridItemInfo* aItem, 8735 LogicalAxis aGridAxis) { 8736 // When we're auto-placing an item in a continuation we need to record 8737 // the placement in mSharedGridData. 8738 if (MOZ_UNLIKELY(aGridRI.mSharedGridData && GetPrevInFlow()) && 8739 (aItem->mState[aGridAxis] & ItemState::eAutoPlacement)) { 8740 auto* child = aItem->mFrame; 8741 MOZ_RELEASE_ASSERT(!child->GetPrevInFlow(), 8742 "continuations should never be auto-placed"); 8743 for (auto& sharedItem : aGridRI.mSharedGridData->mGridItems) { 8744 if (sharedItem.mFrame == child) { 8745 sharedItem.mArea.LineRangeForAxis(aGridAxis) = 8746 aItem->mArea.LineRangeForAxis(aGridAxis); 8747 MOZ_ASSERT(sharedItem.mState[aGridAxis] & ItemState::eAutoPlacement); 8748 sharedItem.mState[aGridAxis] &= ~ItemState::eAutoPlacement; 8749 break; 8750 } 8751 } 8752 } 8753 aItem->mState[aGridAxis] &= ~ItemState::eAutoPlacement; 8754 }; 8755 8756 // Collect our grid items and sort them in grid order. 8757 nsTArray<GridItemInfo*> sortedItems(aGridRI.mGridItems.Length()); 8758 aGridRI.mIter.Reset(CSSOrderAwareFrameIterator::ChildFilter::IncludeAll); 8759 size_t absposIndex = 0; 8760 const LogicalAxis masonryAxis = 8761 IsMasonry(LogicalAxis::Block) ? LogicalAxis::Block : LogicalAxis::Inline; 8762 const auto wm = aGridRI.mWM; 8763 for (; !aGridRI.mIter.AtEnd(); aGridRI.mIter.Next()) { 8764 nsIFrame* child = *aGridRI.mIter; 8765 if (MOZ_LIKELY(!child->IsPlaceholderFrame())) { 8766 GridItemInfo* item = &aGridRI.mGridItems[aGridRI.mIter.ItemIndex()]; 8767 sortedItems.AppendElement(item); 8768 } else if (aConstraint == SizingConstraint::NoConstraint) { 8769 // (we only collect placeholders in the NoConstraint case since they 8770 // don't affect intrinsic sizing in any way) 8771 GridItemInfo* item = nullptr; 8772 auto* ph = static_cast<nsPlaceholderFrame*>(child); 8773 if (ph->GetOutOfFlowFrame()->GetParent() == this) { 8774 item = &aGridRI.mAbsPosItems[absposIndex++]; 8775 MOZ_RELEASE_ASSERT(item->mFrame == ph->GetOutOfFlowFrame()); 8776 auto masonryStart = item->mArea.LineRangeForAxis(masonryAxis).mStart; 8777 // If the item was placed by the author at line 1 (masonryStart == 0) 8778 // then include it to be placed at the masonry-box start. If it's 8779 // auto-placed and has an `auto` inset value in the masonry axis then 8780 // we include it to be placed after the last grid item with the same 8781 // grid-axis start track. 8782 // XXXmats this is all a bit experimental at this point, pending a spec 8783 const auto masonrySide = masonryAxis == LogicalAxis::Inline 8784 ? LogicalSide::IStart 8785 : LogicalSide::BStart; 8786 if (masonryStart == 0 || 8787 (masonryStart == kAutoLine && 8788 item->mFrame->StylePosition() 8789 ->GetAnchorResolvedInset( 8790 masonrySide, wm, 8791 AnchorPosOffsetResolutionParams::UseCBFrameSize( 8792 AnchorPosResolutionParams::From(item->mFrame))) 8793 ->IsAuto())) { 8794 sortedItems.AppendElement(item); 8795 } else { 8796 item = nullptr; 8797 } 8798 } 8799 if (!item) { 8800 // It wasn't included above - just reflow it and be done with it. 8801 nsReflowStatus childStatus; 8802 ReflowInFlowChild(child, nullptr, aContainerSize, Nothing(), nullptr, 8803 aGridRI, aContentArea, aDesiredSize, childStatus); 8804 } 8805 } 8806 } 8807 const auto masonryAutoFlow = aGridRI.mGridStyle->mMasonryAutoFlow; 8808 const bool definiteFirst = 8809 masonryAutoFlow.order == StyleMasonryItemOrder::DefiniteFirst; 8810 if (masonryAxis == LogicalAxis::Block) { 8811 std::stable_sort(sortedItems.begin(), sortedItems.end(), 8812 definiteFirst ? GridItemInfo::RowMasonryDefiniteFirst 8813 : GridItemInfo::RowMasonryOrdered); 8814 } else { 8815 std::stable_sort(sortedItems.begin(), sortedItems.end(), 8816 definiteFirst ? GridItemInfo::ColMasonryDefiniteFirst 8817 : GridItemInfo::ColMasonryOrdered); 8818 } 8819 8820 FrameHashtable pushedItems; 8821 FrameHashtable incompleteItems; 8822 FrameHashtable overflowIncompleteItems; 8823 nscoord toFragmentainerEnd = nscoord_MAX; 8824 nscoord fragStartPos = aGridRI.mFragBStart; 8825 const bool avoidBreakInside = 8826 aFragmentainer && ShouldAvoidBreakInside(*aGridRI.mReflowInput); 8827 const bool isTopOfPageAtStart = 8828 aFragmentainer && aFragmentainer->mIsTopOfPage; 8829 if (aFragmentainer) { 8830 toFragmentainerEnd = std::max(0, aFragmentainer->mToFragmentainerEnd); 8831 } 8832 const LogicalAxis gridAxis = GetOrthogonalAxis(masonryAxis); 8833 const auto gridAxisTrackCount = aGridRI.TracksFor(gridAxis).mSizes.Length(); 8834 auto& masonryTracks = aGridRI.TracksFor(masonryAxis); 8835 auto& masonrySizes = masonryTracks.mSizes; 8836 MOZ_ASSERT(masonrySizes.Length() == 2); 8837 for (auto& sz : masonrySizes) { 8838 sz.mPosition = fragStartPos; 8839 } 8840 // The current running position for each grid-axis track where the next item 8841 // should be positioned. When an item is placed we'll update the tracks it 8842 // spans to the end of its margin box + 'gap'. 8843 nsTArray<nscoord> currentPos(gridAxisTrackCount); 8844 currentPos.SetLength(gridAxisTrackCount); 8845 for (auto& sz : currentPos) { 8846 sz = fragStartPos; 8847 } 8848 nsTArray<nscoord> lastPos(currentPos.Clone()); 8849 nsTArray<GridItemInfo*> lastItems(gridAxisTrackCount); 8850 lastItems.SetLength(gridAxisTrackCount); 8851 PodZero(lastItems.Elements(), gridAxisTrackCount); 8852 const nscoord gap = nsLayoutUtils::ResolveGapToLength( 8853 masonryAxis == LogicalAxis::Block ? aGridRI.mGridStyle->mRowGap 8854 : aGridRI.mGridStyle->mColumnGap, 8855 masonryTracks.mContentBoxSize); 8856 masonryTracks.mGridGap = gap; 8857 uint32_t cursor = 0; 8858 const auto containerToMasonryBoxOffset = 8859 fragStartPos - aContentArea.Start(masonryAxis, wm); 8860 const bool isPack = masonryAutoFlow.placement == StyleMasonryPlacement::Pack; 8861 bool didAlignStartAlignedFirstItems = false; 8862 8863 // Return true if any of the lastItems in aRange are baseline-aligned in 8864 // the masonry axis. 8865 auto lastItemHasBaselineAlignment = [&](const LineRange& aRange) { 8866 for (auto i : aRange.Range()) { 8867 if (auto* child = lastItems[i] ? lastItems[i]->mFrame : nullptr) { 8868 const auto& pos = child->StylePosition(); 8869 auto selfAlignment = pos->UsedSelfAlignment(masonryAxis, this->Style()); 8870 if (selfAlignment == StyleAlignFlags::BASELINE || 8871 selfAlignment == StyleAlignFlags::LAST_BASELINE) { 8872 return true; 8873 } 8874 auto childAxis = masonryAxis; 8875 if (child->GetWritingMode().IsOrthogonalTo(wm)) { 8876 childAxis = gridAxis; 8877 } 8878 auto contentAlignment = pos->UsedContentAlignment(childAxis).primary; 8879 if (contentAlignment == StyleAlignFlags::BASELINE || 8880 contentAlignment == StyleAlignFlags::LAST_BASELINE) { 8881 return true; 8882 } 8883 } 8884 } 8885 return false; 8886 }; 8887 8888 // Resolve aItem's placement, unless it's definite already. Return its 8889 // masonry axis position with that placement. 8890 auto placeItem = [&](GridItemInfo* aItem) -> nscoord { 8891 auto& masonryAxisRange = aItem->mArea.LineRangeForAxis(masonryAxis); 8892 MOZ_ASSERT(masonryAxisRange.mStart != 0, "item placement is already final"); 8893 auto& gridAxisRange = aItem->mArea.LineRangeForAxis(gridAxis); 8894 bool isAutoPlaced = aItem->mState[gridAxis] & ItemState::eAutoPlacement; 8895 uint32_t start = isAutoPlaced ? 0 : gridAxisRange.mStart; 8896 if (isAutoPlaced && !isPack) { 8897 start = cursor; 8898 isAutoPlaced = false; 8899 } 8900 const uint32_t extent = gridAxisRange.Extent(); 8901 if (start + extent > gridAxisTrackCount) { 8902 // Note that this will only happen to auto-placed items since the grid is 8903 // always wide enough to fit other items. 8904 start = 0; 8905 } 8906 // This keeps track of the smallest `maxPosForRange` value that 8907 // we discover in the loop below: 8908 nscoord minPos = nscoord_MAX; 8909 MOZ_ASSERT(extent <= gridAxisTrackCount); 8910 const uint32_t iEnd = gridAxisTrackCount + 1 - extent; 8911 for (uint32_t i = start; i < iEnd; ++i) { 8912 // Find the max `currentPos` value for the tracks that we would span 8913 // if we were to use `i` as our start track: 8914 nscoord maxPosForRange = 0; 8915 for (auto j = i, jEnd = j + extent; j < jEnd; ++j) { 8916 maxPosForRange = std::max(currentPos[j], maxPosForRange); 8917 } 8918 if (maxPosForRange < minPos) { 8919 minPos = maxPosForRange; 8920 start = i; 8921 } 8922 if (!isAutoPlaced) { 8923 break; 8924 } 8925 } 8926 gridAxisRange.mStart = start; 8927 gridAxisRange.mEnd = start + extent; 8928 bool isFirstItem = true; 8929 for (uint32_t i : gridAxisRange.Range()) { 8930 if (lastItems[i]) { 8931 isFirstItem = false; 8932 break; 8933 } 8934 } 8935 // If this is the first item in its spanned grid tracks, then place it in 8936 // the first masonry track. Otherwise, place it in the second masonry track. 8937 masonryAxisRange.mStart = isFirstItem ? 0 : 1; 8938 masonryAxisRange.mEnd = masonryAxisRange.mStart + 1; 8939 return minPos; 8940 }; 8941 8942 // Handle the resulting reflow status after reflowing aItem. 8943 // This may set aStatus to BreakBefore which the caller is expected 8944 // to handle by returning from MasonryLayout. 8945 // @return true if this item should consume all remaining space 8946 auto handleChildStatus = [&](GridItemInfo* aItem, 8947 const nsReflowStatus& aChildStatus) { 8948 bool result = false; 8949 if (MOZ_UNLIKELY(aFragmentainer)) { 8950 auto* child = aItem->mFrame; 8951 if (!aChildStatus.IsComplete() || aChildStatus.IsInlineBreakBefore() || 8952 aChildStatus.IsInlineBreakAfter() || 8953 child->StyleDisplay()->BreakAfter()) { 8954 if (!isTopOfPageAtStart && avoidBreakInside) { 8955 aStatus.SetInlineLineBreakBeforeAndReset(); 8956 return result; 8957 } 8958 result = true; 8959 } 8960 if (aChildStatus.IsInlineBreakBefore()) { 8961 aStatus.SetIncomplete(); 8962 pushedItems.Insert(child); 8963 } else if (aChildStatus.IsIncomplete()) { 8964 recordAutoPlacement(aItem, gridAxis); 8965 aStatus.SetIncomplete(); 8966 incompleteItems.Insert(child); 8967 } else if (!aChildStatus.IsFullyComplete()) { 8968 recordAutoPlacement(aItem, gridAxis); 8969 overflowIncompleteItems.Insert(child); 8970 } 8971 } 8972 return result; 8973 }; 8974 8975 // @return the distance from the masonry-box start to the end of the margin- 8976 // box of aChild 8977 auto offsetToMarginBoxEnd = [&](nsIFrame* aChild) { 8978 auto childWM = aChild->GetWritingMode(); 8979 auto childAxis = !childWM.IsOrthogonalTo(wm) ? masonryAxis : gridAxis; 8980 auto normalPos = aChild->GetLogicalNormalPosition(wm, aContainerSize); 8981 auto sz = 8982 childAxis == LogicalAxis::Block ? aChild->BSize() : aChild->ISize(); 8983 return containerToMasonryBoxOffset + normalPos.Pos(masonryAxis, wm) + sz + 8984 aChild->GetLogicalUsedMargin(childWM).End(childAxis, childWM); 8985 }; 8986 8987 // Apply baseline alignment to items belonging to the given set. 8988 nsTArray<Tracks::ItemBaselineData> firstBaselineItems; 8989 nsTArray<Tracks::ItemBaselineData> lastBaselineItems; 8990 auto applyBaselineAlignment = [&](BaselineAlignmentSet aSet) { 8991 firstBaselineItems.ClearAndRetainStorage(); 8992 lastBaselineItems.ClearAndRetainStorage(); 8993 masonryTracks.InitializeItemBaselinesInMasonryAxis( 8994 aGridRI, aGridRI.mGridItems, aSet, aContainerSize, currentPos, 8995 firstBaselineItems, lastBaselineItems); 8996 8997 bool didBaselineAdjustment = false; 8998 nsTArray<Tracks::ItemBaselineData>* baselineItems[] = {&firstBaselineItems, 8999 &lastBaselineItems}; 9000 for (const auto* items : baselineItems) { 9001 for (const auto& data : *items) { 9002 GridItemInfo* item = data.mGridItem; 9003 MOZ_ASSERT((item->mState[masonryAxis] & ItemState::eIsBaselineAligned)); 9004 nscoord baselineOffset = item->mBaselineOffset[masonryAxis]; 9005 if (baselineOffset == nscoord(0)) { 9006 continue; // no adjustment needed for this item 9007 } 9008 didBaselineAdjustment = true; 9009 auto* child = item->mFrame; 9010 auto masonryAxisStart = 9011 item->mArea.LineRangeForAxis(masonryAxis).mStart; 9012 auto gridAxisRange = item->mArea.LineRangeForAxis(gridAxis); 9013 masonrySizes[masonryAxisStart].mPosition = 9014 aSet.mItemSet == BaselineAlignmentSet::LastItems 9015 ? lastPos[gridAxisRange.mStart] 9016 : fragStartPos; 9017 bool consumeAllSpace = false; 9018 const auto state = item->mState[masonryAxis]; 9019 if ((state & ItemState::eContentBaseline) || 9020 MOZ_UNLIKELY(aFragmentainer)) { 9021 if (MOZ_UNLIKELY(aFragmentainer)) { 9022 aFragmentainer->mIsTopOfPage = 9023 isTopOfPageAtStart && 9024 masonrySizes[masonryAxisStart].mPosition == fragStartPos; 9025 } 9026 nsReflowStatus childStatus; 9027 ReflowInFlowChild(child, item, aContainerSize, Nothing(), 9028 aFragmentainer, aGridRI, aContentArea, aDesiredSize, 9029 childStatus); 9030 consumeAllSpace = handleChildStatus(item, childStatus); 9031 if (aStatus.IsInlineBreakBefore()) { 9032 return false; 9033 } 9034 } else if (!(state & ItemState::eEndSideBaseline)) { 9035 // `align/justify-self` baselines on the start side can be handled by 9036 // just moving the frame (except in a fragmentainer in which case we 9037 // reflow it above instead since it might make it INCOMPLETE). 9038 LogicalPoint logicalDelta(wm); 9039 logicalDelta.Pos(masonryAxis, wm) = baselineOffset; 9040 child->MovePositionBy(wm, logicalDelta); 9041 } 9042 if ((state & ItemState::eEndSideBaseline) && !consumeAllSpace) { 9043 // Account for an end-side baseline adjustment. 9044 for (uint32_t i : gridAxisRange.Range()) { 9045 currentPos[i] += baselineOffset; 9046 } 9047 } else { 9048 nscoord pos = consumeAllSpace ? toFragmentainerEnd 9049 : offsetToMarginBoxEnd(child); 9050 pos += gap; 9051 for (uint32_t i : gridAxisRange.Range()) { 9052 currentPos[i] = pos; 9053 } 9054 } 9055 } 9056 } 9057 return didBaselineAdjustment; 9058 }; 9059 9060 // Place and reflow items. We'll use two fake tracks in the masonry axis. 9061 // The first contains items that were placed there by the regular grid 9062 // placement algo (PlaceGridItems) and we may add some items here if there 9063 // are still empty slots. The second track contains all other items. 9064 // Both tracks always have the size of the content box in the masonry axis. 9065 // The position of the first track is always at the start. The position 9066 // of the second track is updated as we go to a position where we want 9067 // the current item to be positioned. 9068 for (GridItemInfo* item : sortedItems) { 9069 auto* child = item->mFrame; 9070 auto& masonryRange = item->mArea.LineRangeForAxis(masonryAxis); 9071 auto& gridRange = item->mArea.LineRangeForAxis(gridAxis); 9072 nsReflowStatus childStatus; 9073 if (MOZ_UNLIKELY(child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW))) { 9074 auto contentArea = aContentArea; 9075 nscoord pos = nscoord_MAX; 9076 // XXXmats take mEnd into consideration... 9077 if (gridRange.mStart == kAutoLine) { 9078 for (auto p : currentPos) { 9079 pos = std::min(p, pos); 9080 } 9081 } else if (gridRange.mStart < currentPos.Length()) { 9082 pos = currentPos[gridRange.mStart]; 9083 } else if (currentPos.Length() > 0) { 9084 pos = currentPos.LastElement(); 9085 } 9086 if (pos == nscoord_MAX) { 9087 pos = nscoord(0); 9088 } 9089 contentArea.Start(masonryAxis, wm) = pos; 9090 child = child->GetPlaceholderFrame(); 9091 ReflowInFlowChild(child, nullptr, aContainerSize, Nothing(), nullptr, 9092 aGridRI, contentArea, aDesiredSize, childStatus); 9093 } else { 9094 MOZ_ASSERT(gridRange.Extent() > 0 && 9095 gridRange.Extent() <= gridAxisTrackCount); 9096 MOZ_ASSERT((masonryRange.mStart == 0 || masonryRange.mStart == 1) && 9097 masonryRange.Extent() == 1); 9098 if (masonryRange.mStart != 0) { 9099 masonrySizes[1].mPosition = placeItem(item); 9100 } 9101 9102 // If this is the first item NOT in the first track and if any of 9103 // the grid-axis tracks we span has a baseline-aligned item then we 9104 // need to do that baseline alignment now since it may affect 9105 // the placement of this and later items. 9106 if (!didAlignStartAlignedFirstItems && 9107 aConstraint == SizingConstraint::NoConstraint && 9108 masonryRange.mStart != 0 && lastItemHasBaselineAlignment(gridRange)) { 9109 didAlignStartAlignedFirstItems = true; 9110 if (applyBaselineAlignment({BaselineAlignmentSet::FirstItems, 9111 BaselineAlignmentSet::StartStretch})) { 9112 // Baseline alignment resized some items - redo our placement. 9113 masonrySizes[1].mPosition = placeItem(item); 9114 } 9115 if (aStatus.IsInlineBreakBefore()) { 9116 return fragStartPos; 9117 } 9118 } 9119 9120 for (uint32_t i : gridRange.Range()) { 9121 lastItems[i] = item; 9122 } 9123 cursor = gridRange.mEnd; 9124 if (cursor >= gridAxisTrackCount) { 9125 cursor = 0; 9126 } 9127 9128 nscoord pos; 9129 if (aConstraint == SizingConstraint::NoConstraint) { 9130 const auto* disp = child->StyleDisplay(); 9131 if (MOZ_UNLIKELY(aFragmentainer)) { 9132 aFragmentainer->mIsTopOfPage = 9133 isTopOfPageAtStart && 9134 masonrySizes[masonryRange.mStart].mPosition == fragStartPos; 9135 if (!aFragmentainer->mIsTopOfPage && 9136 (disp->BreakBefore() || 9137 masonrySizes[masonryRange.mStart].mPosition >= 9138 toFragmentainerEnd)) { 9139 childStatus.SetInlineLineBreakBeforeAndReset(); 9140 } 9141 } 9142 if (!childStatus.IsInlineBreakBefore()) { 9143 ReflowInFlowChild(child, item, aContainerSize, Nothing(), 9144 aFragmentainer, aGridRI, aContentArea, aDesiredSize, 9145 childStatus); 9146 } 9147 bool consumeAllSpace = handleChildStatus(item, childStatus); 9148 if (aStatus.IsInlineBreakBefore()) { 9149 return fragStartPos; 9150 } 9151 pos = 9152 consumeAllSpace ? toFragmentainerEnd : offsetToMarginBoxEnd(child); 9153 } else { 9154 LogicalSize percentBasis( 9155 aGridRI.PercentageBasisFor(LogicalAxis::Inline, *item)); 9156 IntrinsicISizeType type = aConstraint == SizingConstraint::MaxContent 9157 ? IntrinsicISizeType::PrefISize 9158 : IntrinsicISizeType::MinISize; 9159 auto sz = ::ContentContribution(*item, aGridRI, masonryAxis, 9160 percentBasis, type); 9161 pos = sz + masonrySizes[masonryRange.mStart].mPosition; 9162 } 9163 pos += gap; 9164 for (uint32_t i : gridRange.Range()) { 9165 lastPos[i] = currentPos[i]; 9166 currentPos[i] = pos; 9167 } 9168 } 9169 } 9170 9171 // Do the remaining baseline alignment sets. 9172 if (aConstraint == SizingConstraint::NoConstraint) { 9173 for (auto*& item : lastItems) { 9174 if (item) { 9175 item->mState[masonryAxis] |= ItemState::eIsLastItemInMasonryTrack; 9176 } 9177 } 9178 BaselineAlignmentSet baselineSets[] = { 9179 {BaselineAlignmentSet::FirstItems, BaselineAlignmentSet::StartStretch}, 9180 {BaselineAlignmentSet::FirstItems, BaselineAlignmentSet::EndStretch}, 9181 {BaselineAlignmentSet::LastItems, BaselineAlignmentSet::StartStretch}, 9182 {BaselineAlignmentSet::LastItems, BaselineAlignmentSet::EndStretch}, 9183 }; 9184 for (uint32_t i = 0; i < std::size(baselineSets); ++i) { 9185 if (i == 0 && didAlignStartAlignedFirstItems) { 9186 continue; 9187 } 9188 applyBaselineAlignment(baselineSets[i]); 9189 } 9190 } 9191 9192 const bool childrenMoved = PushIncompleteChildren( 9193 pushedItems, incompleteItems, overflowIncompleteItems); 9194 if (childrenMoved && aStatus.IsComplete()) { 9195 aStatus.SetOverflowIncomplete(); 9196 aStatus.SetNextInFlowNeedsReflow(); 9197 } 9198 if (!pushedItems.IsEmpty()) { 9199 AddStateBits(NS_STATE_GRID_DID_PUSH_ITEMS); 9200 // NOTE since we messed with our child list here, we intentionally 9201 // make aState.mIter invalid to avoid any use of it after this point. 9202 aGridRI.mIter.Invalidate(); 9203 } 9204 if (!incompleteItems.IsEmpty()) { 9205 // NOTE since we messed with our child list here, we intentionally 9206 // make aState.mIter invalid to avoid any use of it after this point. 9207 aGridRI.mIter.Invalidate(); 9208 } 9209 9210 nscoord masonryBoxSize = 0; 9211 for (auto pos : currentPos) { 9212 masonryBoxSize = std::max(masonryBoxSize, pos); 9213 } 9214 masonryBoxSize = std::max(nscoord(0), masonryBoxSize - gap); 9215 if (aConstraint == SizingConstraint::NoConstraint) { 9216 aGridRI.AlignJustifyContentInMasonryAxis(masonryBoxSize, 9217 masonryTracks.mContentBoxSize); 9218 } 9219 return masonryBoxSize; 9220 } 9221 9222 nsGridContainerFrame* nsGridContainerFrame::ParentGridContainerForSubgrid() 9223 const { 9224 MOZ_ASSERT(IsSubgrid()); 9225 nsIFrame* p = GetParent(); 9226 while (p->GetContent() == GetContent()) { 9227 p = p->GetParent(); 9228 } 9229 MOZ_ASSERT(p->IsGridContainerFrame()); 9230 auto* parent = static_cast<nsGridContainerFrame*>(p); 9231 MOZ_ASSERT(parent->HasSubgridItems()); 9232 return parent; 9233 } 9234 9235 nscoord nsGridContainerFrame::ReflowChildren(GridReflowInput& aGridRI, 9236 const LogicalRect& aContentArea, 9237 const nsSize& aContainerSize, 9238 ReflowOutput& aDesiredSize, 9239 nsReflowStatus& aStatus) { 9240 WritingMode wm = aGridRI.mReflowInput->GetWritingMode(); 9241 nscoord bSize = aContentArea.BSize(wm); 9242 MOZ_ASSERT(aGridRI.mReflowInput); 9243 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 9244 if (HidesContentForLayout()) { 9245 return bSize; 9246 } 9247 9248 OverflowAreas ocBounds; 9249 nsReflowStatus ocStatus; 9250 if (GetPrevInFlow()) { 9251 ReflowOverflowContainerChildren(PresContext(), *aGridRI.mReflowInput, 9252 ocBounds, ReflowChildFlags::Default, 9253 ocStatus, MergeSortedFrameListsFor); 9254 } 9255 9256 Maybe<Fragmentainer> fragmentainer = GetNearestFragmentainer(aGridRI); 9257 // MasonryLayout() can only handle fragmentation in the masonry-axis, 9258 // so we let ReflowInFragmentainer() deal with grid-axis fragmentation 9259 // in the else-clause below. 9260 if (IsMasonry() && !(IsColMasonry() && fragmentainer.isSome())) { 9261 aGridRI.mInFragmentainer = fragmentainer.isSome(); 9262 nscoord sz = MasonryLayout( 9263 aGridRI, aContentArea, SizingConstraint::NoConstraint, aDesiredSize, 9264 aStatus, fragmentainer.ptrOr(nullptr), aContainerSize); 9265 if (IsRowMasonry()) { 9266 bSize = aGridRI.mReflowInput->ComputedBSize(); 9267 if (bSize == NS_UNCONSTRAINEDSIZE) { 9268 bSize = aGridRI.mReflowInput->ApplyMinMaxBSize(sz); 9269 } 9270 } 9271 } else if (MOZ_UNLIKELY(fragmentainer.isSome())) { 9272 if (IsColMasonry() && !GetPrevInFlow()) { 9273 // First we do an unconstrained reflow to resolve the item placement 9274 // which is then kept as-is in the constrained reflow below. 9275 MasonryLayout(aGridRI, aContentArea, SizingConstraint::NoConstraint, 9276 aDesiredSize, aStatus, nullptr, aContainerSize); 9277 } 9278 aGridRI.mInFragmentainer = true; 9279 bSize = ReflowInFragmentainer(aGridRI, aContentArea, aDesiredSize, aStatus, 9280 *fragmentainer, aContainerSize); 9281 } else { 9282 aGridRI.mIter.Reset(CSSOrderAwareFrameIterator::ChildFilter::IncludeAll); 9283 for (; !aGridRI.mIter.AtEnd(); aGridRI.mIter.Next()) { 9284 nsIFrame* child = *aGridRI.mIter; 9285 const GridItemInfo* info = nullptr; 9286 if (!child->IsPlaceholderFrame()) { 9287 info = &aGridRI.mGridItems[aGridRI.mIter.ItemIndex()]; 9288 } 9289 nsReflowStatus childStatus; 9290 ReflowInFlowChild(child, info, aContainerSize, Nothing(), nullptr, 9291 aGridRI, aContentArea, aDesiredSize, childStatus); 9292 MOZ_ASSERT(childStatus.IsComplete(), 9293 "child should be complete in unconstrained reflow"); 9294 aStatus.MergeCompletionStatusFrom(childStatus); 9295 } 9296 } 9297 9298 // Merge overflow container bounds and status. 9299 aDesiredSize.mOverflowAreas.UnionWith(ocBounds); 9300 aStatus.MergeCompletionStatusFrom(ocStatus); 9301 9302 return bSize; 9303 } 9304 9305 void nsGridContainerFrame::ReflowAbsoluteChildren( 9306 GridReflowInput& aGridRI, const LogicalRect& aContentArea, 9307 nscoord aContentBSize, ReflowOutput& aDesiredSize, 9308 nsReflowStatus& aStatus) { 9309 WritingMode wm = aGridRI.mReflowInput->GetWritingMode(); 9310 auto* absoluteContainer = GetAbsoluteContainingBlock(); 9311 // We have prepared the absolute frames in Grid::PlaceGridItems() or in 9312 // GridReflowInput::InitializeForContinuation(). 9313 if (!absoluteContainer || !absoluteContainer->HasAbsoluteFrames()) { 9314 return; 9315 } 9316 // 'gridOrigin' is the origin of the grid (the start of the first track), 9317 // with respect to the grid container's padding-box (CB). 9318 LogicalMargin pad(aGridRI.mReflowInput->ComputedLogicalPadding(wm)); 9319 const LogicalPoint gridOrigin(wm, pad.IStart(wm), pad.BStart(wm)); 9320 const LogicalRect gridCB(wm, 0, 0, aContentArea.ISize(wm) + pad.IStartEnd(wm), 9321 aContentBSize + pad.BStartEnd(wm)); 9322 const nsSize gridCBPhysicalSize = gridCB.Size(wm).GetPhysicalSize(wm); 9323 size_t i = 0; 9324 for (nsIFrame* child : absoluteContainer->GetChildList()) { 9325 MOZ_ASSERT(i < aGridRI.mAbsPosItems.Length()); 9326 MOZ_ASSERT(aGridRI.mAbsPosItems[i].mFrame == child); 9327 GridArea& area = aGridRI.mAbsPosItems[i].mArea; 9328 LogicalRect itemCB = 9329 aGridRI.ContainingBlockForAbsPos(area, gridOrigin, gridCB); 9330 // AbsoluteContainingBlock::Reflow uses physical coordinates. 9331 nsRect* cb = child->GetProperty(GridItemContainingBlockRect()); 9332 if (!cb) { 9333 cb = new nsRect; 9334 child->SetProperty(GridItemContainingBlockRect(), cb); 9335 } 9336 *cb = itemCB.GetPhysicalRect(wm, gridCBPhysicalSize); 9337 ++i; 9338 } 9339 const auto border = aGridRI.mReflowInput->ComputedPhysicalBorder(); 9340 const nsPoint borderShift{border.left, border.top}; 9341 const nsRect paddingRect(borderShift, gridCBPhysicalSize); 9342 // XXX: To optimize the performance, set the flags only when the CB width 9343 // or height actually changes. 9344 AbsPosReflowFlags flags{ 9345 AbsPosReflowFlag::AllowFragmentation, AbsPosReflowFlag::CBWidthChanged, 9346 AbsPosReflowFlag::CBHeightChanged, AbsPosReflowFlag::IsGridContainerCB}; 9347 absoluteContainer->Reflow(this, PresContext(), *aGridRI.mReflowInput, aStatus, 9348 paddingRect, flags, &aDesiredSize.mOverflowAreas); 9349 } 9350 9351 nscoord nsGridContainerFrame::ComputeBSizeForResolvingRowSizes( 9352 GridReflowInput& aGridRI, nscoord aComputedBSize, 9353 const Maybe<nscoord>& aContainIntrinsicBSize) const { 9354 if (aComputedBSize != NS_UNCONSTRAINEDSIZE) { 9355 // We don't need to apply the min/max constraints to the computed block-size 9356 // because ReflowInput (specifically when computing the block-size in 9357 // nsIFrame::ComputeSize()) has already clamped the block-size. 9358 return aComputedBSize; 9359 } 9360 9361 if (aContainIntrinsicBSize) { 9362 // We have an unconstrained block-size, but we also have a specified 9363 // 'contain-intrinsic-block-size'. We apply the min/max constraints to the 9364 // value, and use that for track sizing. 9365 return aGridRI.mReflowInput->ApplyMinMaxBSize(*aContainIntrinsicBSize); 9366 } 9367 9368 return NS_UNCONSTRAINEDSIZE; 9369 } 9370 9371 nscoord nsGridContainerFrame::ComputeIntrinsicContentBSize( 9372 const GridReflowInput& aGridRI, nscoord aComputedBSize, 9373 nscoord aBSizeForResolvingRowSizes, 9374 const Maybe<nscoord>& aContainIntrinsicBSize) const { 9375 MOZ_ASSERT( 9376 aComputedBSize == NS_UNCONSTRAINEDSIZE || 9377 aGridRI.mReflowInput->ShouldApplyAutomaticMinimumOnBlockAxis(), 9378 "Why call this method when intrinsic content block-size is not needed?"); 9379 9380 if (aComputedBSize == NS_UNCONSTRAINEDSIZE) { 9381 // When we have an unconstrained block-size, the intrinsic content 9382 // block-size would have been determined after we resolved the row sizes the 9383 // first time. Just return that value. 9384 return aBSizeForResolvingRowSizes; 9385 } 9386 9387 if (aContainIntrinsicBSize) { 9388 // We have a specified 'contain-intrinsic-block-size' which we need to 9389 // honor. 9390 return *aContainIntrinsicBSize; 9391 } 9392 9393 if (IsRowMasonry()) { 9394 // There aren't any tracks to derive a block-size from, if we're doing 9395 // masonry rather than forming rows in the block direction. 9396 return aBSizeForResolvingRowSizes; 9397 } 9398 9399 return aGridRI.mRows.TotalTrackSizeWithoutAlignment(this); 9400 } 9401 9402 void nsGridContainerFrame::Reflow(nsPresContext* aPresContext, 9403 ReflowOutput& aDesiredSize, 9404 const ReflowInput& aReflowInput, 9405 nsReflowStatus& aStatus) { 9406 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) { 9407 return; 9408 } 9409 9410 MarkInReflow(); 9411 DO_GLOBAL_REFLOW_COUNT("nsGridContainerFrame"); 9412 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 9413 9414 GRID_LOG("Reflow grid container frame %p", this); 9415 9416 if (IsFrameTreeTooDeep(aReflowInput, aDesiredSize, aStatus)) { 9417 return; 9418 } 9419 9420 NormalizeChildLists(); 9421 9422 #ifdef DEBUG 9423 mDidPushItemsBitMayLie = false; 9424 SanityCheckChildListsBeforeReflow(); 9425 #endif // DEBUG 9426 9427 for (auto& perAxisBaseline : mBaseline) { 9428 for (auto& baseline : perAxisBaseline) { 9429 baseline = NS_INTRINSIC_ISIZE_UNKNOWN; 9430 } 9431 } 9432 9433 const nsStylePosition* stylePos = aReflowInput.mStylePosition; 9434 auto prevInFlow = static_cast<nsGridContainerFrame*>(GetPrevInFlow()); 9435 if (MOZ_LIKELY(!prevInFlow)) { 9436 InitImplicitNamedAreas(stylePos); 9437 } else { 9438 MOZ_ASSERT(prevInFlow->HasAnyStateBits(kIsSubgridBits) == 9439 HasAnyStateBits(kIsSubgridBits), 9440 "continuations should have same kIsSubgridBits"); 9441 } 9442 GridReflowInput gridRI(this, aReflowInput); 9443 if (gridRI.mIter.ItemsAreAlreadyInOrder()) { 9444 AddStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER); 9445 } else { 9446 RemoveStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER); 9447 } 9448 if (gridRI.mIter.AtEnd() || aReflowInput.mStyleDisplay->IsContainLayout()) { 9449 // We have no grid items, or we're layout-contained. So, we have no 9450 // baseline, and our parent should synthesize a baseline if needed. 9451 AddStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE); 9452 } else { 9453 RemoveStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE); 9454 } 9455 const nscoord computedBSize = aReflowInput.ComputedBSize(); 9456 const nscoord computedISize = aReflowInput.ComputedISize(); 9457 9458 // XXX Technically incorrect: 'contain-intrinsic-block-size: none' is 9459 // treated as 0, ignoring our row sizes, when really we should use them but 9460 // *they* should be computed as if we had no children. To be fixed in bug 9461 // 1488878. 9462 const Maybe<nscoord> containIntrinsicBSize = 9463 aReflowInput.mFrame->ContainIntrinsicBSize(); 9464 const WritingMode& wm = gridRI.mWM; 9465 9466 nscoord consumedBSize = 0; 9467 nscoord contentBSize = 0; 9468 if (MOZ_LIKELY(!prevInFlow)) { 9469 Grid grid; 9470 if (MOZ_LIKELY(!IsSubgrid())) { 9471 RepeatTrackSizingInput repeatSizing(aReflowInput.ComputedMinSize(), 9472 aReflowInput.ComputedSize(), 9473 aReflowInput.ComputedMaxSize()); 9474 grid.PlaceGridItems(gridRI, repeatSizing); 9475 } else { 9476 auto* subgrid = GetProperty(Subgrid::Prop()); 9477 MOZ_ASSERT(subgrid, "an ancestor forgot to call PlaceGridItems?"); 9478 gridRI.mGridItems = subgrid->mGridItems.Clone(); 9479 gridRI.mAbsPosItems = subgrid->mAbsPosItems.Clone(); 9480 grid.mGridColEnd = subgrid->mGridColEnd; 9481 grid.mGridRowEnd = subgrid->mGridRowEnd; 9482 } 9483 9484 // Resolve the column sizes with the grid container's inline size. 9485 // 12.1.1: https://drafts.csswg.org/css-grid-2/#algo-grid-sizing 9486 gridRI.CalculateTrackSizesForAxis(LogicalAxis::Inline, grid, computedISize, 9487 SizingConstraint::NoConstraint); 9488 9489 nscoord bSizeForResolvingRowSizes = ComputeBSizeForResolvingRowSizes( 9490 gridRI, computedBSize, containIntrinsicBSize); 9491 9492 // Resolve the row sizes with the determined bSizeForResolvingRowSizes. 9493 // 12.1.2: https://drafts.csswg.org/css-grid-2/#algo-grid-sizing 9494 // 9495 // If bSizeForResolvingRowSizes is unconstrained, that's fine. It forces 9496 // percent-valued row sizes to be treated as 'auto', yielding an intrinsic 9497 // content block-size needed later to *actually* resolve percent-valued row 9498 // gaps and row sizes. 9499 gridRI.CalculateTrackSizesForAxis(LogicalAxis::Block, grid, 9500 bSizeForResolvingRowSizes, 9501 SizingConstraint::NoConstraint); 9502 9503 // Invalidate the column sizes before re-resolving them. 9504 gridRI.InvalidateTrackSizesForAxis(LogicalAxis::Inline); 9505 9506 // Re-resolve the column sizes. 9507 // 12.1.3: https://drafts.csswg.org/css-grid-2/#algo-grid-sizing 9508 gridRI.CalculateTrackSizesForAxis(LogicalAxis::Inline, grid, computedISize, 9509 SizingConstraint::NoConstraint); 9510 9511 // If our bSizeForResolvingRowSizes is still indefinite, replace it with 9512 // the sum of the row sizes we just resolved, then re-resolve the row 9513 // sizes against that value. We skip this for masonry, which doesn't need 9514 // two-pass row sizes resolution. 9515 if (bSizeForResolvingRowSizes == NS_UNCONSTRAINEDSIZE && !IsRowMasonry()) { 9516 bSizeForResolvingRowSizes = 9517 std::max(gridRI.mRows.TotalTrackSizeWithoutAlignment(this), 9518 gridRI.mReflowInput->ComputedMinBSize()); 9519 9520 NS_ASSERTION(bSizeForResolvingRowSizes != NS_UNCONSTRAINEDSIZE, 9521 "The block-size for re-resolving the row sizes should be " 9522 "definite in non-masonry layout!"); 9523 9524 // Invalidate the row sizes before re-resolving them. 9525 gridRI.InvalidateTrackSizesForAxis(LogicalAxis::Block); 9526 9527 // Re-resolve the row sizes. 9528 // 12.1.4: https://drafts.csswg.org/css-grid-2/#algo-grid-sizing 9529 gridRI.CalculateTrackSizesForAxis(LogicalAxis::Block, grid, 9530 bSizeForResolvingRowSizes, 9531 SizingConstraint::NoConstraint); 9532 } 9533 9534 if (computedBSize == NS_UNCONSTRAINEDSIZE || 9535 aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis()) { 9536 // We either have an unconstrained block-size, or we have a definite 9537 // block-size derived from the inline-size (transferred via aspect-ratio) 9538 // and need to apply the automatic content-based minimum sizes on the 9539 // block-axis. In both case, we need to compute the intrinsic 9540 // content block-size. 9541 contentBSize = ComputeIntrinsicContentBSize(gridRI, computedBSize, 9542 bSizeForResolvingRowSizes, 9543 containIntrinsicBSize); 9544 } 9545 } else { 9546 consumedBSize = CalcAndCacheConsumedBSize(); 9547 gridRI.InitializeForContinuation(this, consumedBSize); 9548 if (containIntrinsicBSize) { 9549 contentBSize = *containIntrinsicBSize; 9550 } else { 9551 const uint32_t numRows = gridRI.mRows.mSizes.Length(); 9552 contentBSize = 9553 gridRI.mRows.GridLineEdge(numRows, GridLineSide::AfterGridGap); 9554 } 9555 } 9556 if (computedBSize == NS_UNCONSTRAINEDSIZE) { 9557 contentBSize = aReflowInput.ApplyMinMaxBSize(contentBSize); 9558 } else if (aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis()) { 9559 contentBSize = aReflowInput.ApplyMinMaxBSize(contentBSize); 9560 contentBSize = std::max(contentBSize, computedBSize); 9561 } else { 9562 contentBSize = computedBSize; 9563 } 9564 if (contentBSize != NS_UNCONSTRAINEDSIZE) { 9565 contentBSize = std::max(contentBSize - consumedBSize, 0); 9566 } 9567 auto& bp = gridRI.mBorderPadding; 9568 LogicalRect contentArea(wm, bp.IStart(wm), bp.BStart(wm), computedISize, 9569 contentBSize); 9570 9571 if (!prevInFlow) { 9572 const auto& rowSizes = gridRI.mRows.mSizes; 9573 if (!IsRowSubgrid()) { 9574 if (!gridRI.mRows.mIsMasonry) { 9575 // Apply 'align-content' to the grid. 9576 auto alignment = stylePos->mAlignContent; 9577 gridRI.mRows.AlignJustifyContent(stylePos, alignment, wm, contentBSize, 9578 false); 9579 } 9580 } else { 9581 if (computedBSize == NS_UNCONSTRAINEDSIZE) { 9582 contentBSize = gridRI.mRows.GridLineEdge(rowSizes.Length(), 9583 GridLineSide::BeforeGridGap); 9584 contentArea.BSize(wm) = std::max(contentBSize, nscoord(0)); 9585 } 9586 } 9587 // Save the final row sizes for use by subgrids, if needed. 9588 if (HasSubgridItems() || IsSubgrid()) { 9589 StoreUsedTrackSizes(LogicalAxis::Block, rowSizes); 9590 } 9591 } 9592 9593 nsSize containerSize = contentArea.Size(wm).GetPhysicalSize(wm); 9594 bool repositionChildren = false; 9595 if (containerSize.width == NS_UNCONSTRAINEDSIZE && wm.IsVerticalRL()) { 9596 // Note that writing-mode:vertical-rl is the only case where the block 9597 // logical direction progresses in a negative physical direction, and 9598 // therefore block-dir coordinate conversion depends on knowing the width 9599 // of the coordinate space in order to translate between the logical and 9600 // physical origins. 9601 // 9602 // A masonry axis size may be unconstrained, otherwise in a regular grid 9603 // our intrinsic size is always known by now. We'll re-position 9604 // the children below once our size is known. 9605 repositionChildren = true; 9606 containerSize.width = 0; 9607 } 9608 containerSize.width += bp.LeftRight(wm); 9609 containerSize.height += bp.TopBottom(wm); 9610 9611 contentBSize = 9612 ReflowChildren(gridRI, contentArea, containerSize, aDesiredSize, aStatus); 9613 if (Style()->GetPseudoType() == PseudoStyleType::scrolledContent) { 9614 // Per spec, the grid area is included in a grid container's scrollable 9615 // overflow region [1], as well as the padding on the end-edge sides that 9616 // would satisfy the requirements of 'place-content: end' alignment [2]. 9617 // 9618 // Note that we include the padding from all sides of the grid area, not 9619 // just the end sides; this is fine because the grid area is relative to our 9620 // content-box origin. The inflated bounds won't go beyond our padding-box 9621 // edges on the start sides. 9622 // 9623 // The margin areas of grid item boxes are also included in the scrollable 9624 // overflow region [2]. 9625 // 9626 // [1] https://drafts.csswg.org/css-grid-2/#overflow 9627 // [2] https://drafts.csswg.org/css-overflow-3/#scrollable 9628 9629 // Synthesize a grid area covering all columns and rows, and compute its 9630 // rect relative to our border-box. 9631 // 9632 // Note: the grid columns and rows exist only if there is an explicit grid; 9633 // or when an implicit grid is needed to place any grid items. See 9634 // nsGridContainerFrame::Grid::PlaceGridItems(). 9635 const auto numCols = static_cast<int32_t>(gridRI.mCols.mSizes.Length()); 9636 const auto numRows = static_cast<int32_t>(gridRI.mRows.mSizes.Length()); 9637 if (numCols > 0 && numRows > 0) { 9638 const GridArea gridArea(LineRange(0, numCols), LineRange(0, numRows)); 9639 const LogicalRect gridAreaRect = 9640 gridRI.ContainingBlockFor(gridArea) + 9641 LogicalPoint(wm, bp.IStart(wm), bp.BStart(wm)); 9642 9643 MOZ_ASSERT(bp == aReflowInput.ComputedLogicalPadding(wm), 9644 "A scrolled inner frame shouldn't have any border!"); 9645 const LogicalMargin& padding = bp; 9646 nsRect physicalGridAreaRectWithPadding = 9647 gridAreaRect.GetPhysicalRect(wm, containerSize); 9648 physicalGridAreaRectWithPadding.Inflate(padding.GetPhysicalMargin(wm)); 9649 aDesiredSize.mOverflowAreas.UnionAllWith(physicalGridAreaRectWithPadding); 9650 } 9651 9652 nsRect gridItemMarginBoxBounds; 9653 for (const auto& item : gridRI.mGridItems) { 9654 gridItemMarginBoxBounds = 9655 gridItemMarginBoxBounds.Union(item.mFrame->GetMarginRect()); 9656 } 9657 aDesiredSize.mOverflowAreas.UnionAllWith(gridItemMarginBoxBounds); 9658 } 9659 ReflowAbsoluteChildren(gridRI, contentArea, contentBSize, aDesiredSize, 9660 aStatus); 9661 contentBSize = std::max(contentBSize - consumedBSize, 0); 9662 9663 // Skip our block-end border if we're INCOMPLETE. 9664 if (!aStatus.IsComplete() && !gridRI.mSkipSides.BEnd() && 9665 StyleBorder()->mBoxDecorationBreak != StyleBoxDecorationBreak::Clone) { 9666 bp.BEnd(wm) = nscoord(0); 9667 } 9668 9669 LogicalSize desiredSize(wm, computedISize + bp.IStartEnd(wm), 9670 contentBSize + bp.BStartEnd(wm)); 9671 aDesiredSize.SetSize(wm, desiredSize); 9672 nsRect frameRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height()); 9673 aDesiredSize.mOverflowAreas.UnionAllWith(frameRect); 9674 9675 if (repositionChildren) { 9676 nsPoint physicalDelta(aDesiredSize.Width() - bp.LeftRight(wm), 0); 9677 for (const auto& item : gridRI.mGridItems) { 9678 auto* child = item.mFrame; 9679 child->MovePositionBy(physicalDelta); 9680 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, child); 9681 } 9682 } 9683 9684 // TODO: fix align-tracks alignment in fragments 9685 if ((IsRowMasonry() && !prevInFlow) || IsColMasonry()) { 9686 gridRI.AlignJustifyTracksInMasonryAxis(contentArea.Size(wm), 9687 aDesiredSize.PhysicalSize()); 9688 } 9689 9690 // Convert INCOMPLETE -> OVERFLOW_INCOMPLETE and zero bsize if we're an OC. 9691 if (HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { 9692 if (!aStatus.IsComplete()) { 9693 aStatus.SetOverflowIncomplete(); 9694 aStatus.SetNextInFlowNeedsReflow(); 9695 } 9696 contentBSize = 0; 9697 desiredSize.BSize(wm) = contentBSize + bp.BStartEnd(wm); 9698 aDesiredSize.SetSize(wm, desiredSize); 9699 } 9700 9701 if (!gridRI.mInFragmentainer) { 9702 MOZ_ASSERT(gridRI.mIter.IsValid()); 9703 auto sz = frameRect.Size(); 9704 CalculateBaselines(BaselineSet::eBoth, &gridRI.mIter, &gridRI.mGridItems, 9705 gridRI.mCols, 0, gridRI.mCols.mSizes.Length(), wm, sz, 9706 bp.IStart(wm), bp.IEnd(wm), desiredSize.ISize(wm)); 9707 CalculateBaselines(BaselineSet::eBoth, &gridRI.mIter, &gridRI.mGridItems, 9708 gridRI.mRows, 0, gridRI.mRows.mSizes.Length(), wm, sz, 9709 bp.BStart(wm), bp.BEnd(wm), desiredSize.BSize(wm)); 9710 } else { 9711 // Only compute 'first baseline' if this fragment contains the first track. 9712 // XXXmats maybe remove this condition? bug 1306499 9713 BaselineSet baselines = BaselineSet::eNone; 9714 if (gridRI.mStartRow == 0 && 9715 gridRI.mStartRow != gridRI.mNextFragmentStartRow) { 9716 baselines = BaselineSet::eFirst; 9717 } 9718 // Only compute 'last baseline' if this fragment contains the last track. 9719 // XXXmats maybe remove this condition? bug 1306499 9720 uint32_t len = gridRI.mRows.mSizes.Length(); 9721 if (gridRI.mStartRow != len && gridRI.mNextFragmentStartRow == len) { 9722 baselines = BaselineSet(baselines | BaselineSet::eLast); 9723 } 9724 Maybe<CSSOrderAwareFrameIterator> iter; 9725 Maybe<nsTArray<GridItemInfo>> gridItems; 9726 if (baselines != BaselineSet::eNone) { 9727 // We need to create a new iterator and GridItemInfo array because we 9728 // might have pushed some children at this point. 9729 // Even if gridRI.mIter is invalid, we can reuse its 9730 // state about order to optimize initialization of the new iterator. 9731 // An ordered child list can't become unordered by pushing frames. 9732 // An unordered list can become ordered in a number of cases, but we 9733 // ignore that here and guess that the child list is still unordered. 9734 // XXX this is O(n^2) in the number of items in this fragment: bug 1306705 9735 using Filter = CSSOrderAwareFrameIterator::ChildFilter; 9736 using Order = CSSOrderAwareFrameIterator::OrderState; 9737 bool ordered = gridRI.mIter.ItemsAreAlreadyInOrder(); 9738 auto orderState = ordered ? Order::Ordered : Order::Unordered; 9739 iter.emplace(this, FrameChildListID::Principal, Filter::SkipPlaceholders, 9740 orderState); 9741 gridItems.emplace(); 9742 for (; !iter->AtEnd(); iter->Next()) { 9743 auto child = **iter; 9744 for (const auto& info : gridRI.mGridItems) { 9745 if (info.mFrame == child) { 9746 gridItems->AppendElement(info); 9747 } 9748 } 9749 } 9750 } 9751 auto sz = frameRect.Size(); 9752 CalculateBaselines(baselines, iter.ptrOr(nullptr), gridItems.ptrOr(nullptr), 9753 gridRI.mCols, 0, gridRI.mCols.mSizes.Length(), wm, sz, 9754 bp.IStart(wm), bp.IEnd(wm), desiredSize.ISize(wm)); 9755 CalculateBaselines(baselines, iter.ptrOr(nullptr), gridItems.ptrOr(nullptr), 9756 gridRI.mRows, gridRI.mStartRow, 9757 gridRI.mNextFragmentStartRow, wm, sz, bp.BStart(wm), 9758 bp.BEnd(wm), desiredSize.BSize(wm)); 9759 } 9760 9761 if (HasAnyStateBits(NS_STATE_GRID_COMPUTED_INFO)) { 9762 // This state bit will never be cleared, since reflow can be called 9763 // multiple times in fragmented grids, and it's challenging to scope 9764 // the bit to only that sequence of calls. This is relatively harmless 9765 // since this bit is only set by accessing a ChromeOnly property, and 9766 // therefore can't unduly slow down normal web browsing. 9767 9768 // Clear our GridFragmentInfo property, which might be holding a stale 9769 // dom::Grid object built from previously-computed info. This will 9770 // ensure that the next call to GetGridFragments will create a new one. 9771 if (mozilla::dom::Grid* grid = TakeProperty(GridFragmentInfo())) { 9772 grid->ForgetFrame(); 9773 } 9774 9775 // Now that we know column and row sizes and positions, set 9776 // the ComputedGridTrackInfo and related properties 9777 9778 const auto* subgrid = GetProperty(Subgrid::Prop()); 9779 const auto* subgridColRange = 9780 subgrid && IsColSubgrid() ? &subgrid->SubgridCols() : nullptr; 9781 9782 LineNameMap colLineNameMap(gridRI.mGridStyle, GetImplicitNamedAreas(), 9783 gridRI.mColFunctions, nullptr, subgridColRange, 9784 true); 9785 uint32_t colTrackCount = gridRI.mCols.mSizes.Length(); 9786 nsTArray<nscoord> colTrackPositions(colTrackCount); 9787 nsTArray<nscoord> colTrackSizes(colTrackCount); 9788 nsTArray<uint32_t> colTrackStates(colTrackCount); 9789 nsTArray<bool> colRemovedRepeatTracks( 9790 gridRI.mColFunctions.mRemovedRepeatTracks.Clone()); 9791 uint32_t col = 0; 9792 for (const TrackSize& sz : gridRI.mCols.mSizes) { 9793 colTrackPositions.AppendElement(sz.mPosition); 9794 colTrackSizes.AppendElement(sz.mBase); 9795 bool isRepeat = ((col >= gridRI.mColFunctions.mRepeatAutoStart) && 9796 (col < gridRI.mColFunctions.mRepeatAutoEnd)); 9797 colTrackStates.AppendElement( 9798 isRepeat ? (uint32_t)mozilla::dom::GridTrackState::Repeat 9799 : (uint32_t)mozilla::dom::GridTrackState::Static); 9800 9801 col++; 9802 } 9803 // Get the number of explicit tracks first. The order of argument evaluation 9804 // is implementation-defined. We should be OK here because colTrackSizes is 9805 // taken by rvalue, but computing the size first prevents any changes in the 9806 // argument types of the constructor from breaking this. 9807 const uint32_t numColExplicitTracks = 9808 IsColSubgrid() ? colTrackSizes.Length() 9809 : gridRI.mColFunctions.NumExplicitTracks(); 9810 ComputedGridTrackInfo* colInfo = new ComputedGridTrackInfo( 9811 gridRI.mColFunctions.mExplicitGridOffset, numColExplicitTracks, 0, col, 9812 std::move(colTrackPositions), std::move(colTrackSizes), 9813 std::move(colTrackStates), std::move(colRemovedRepeatTracks), 9814 gridRI.mColFunctions.mRepeatAutoStart, 9815 colLineNameMap.GetResolvedLineNamesForComputedGridTrackInfo(), 9816 IsColSubgrid(), IsColMasonry()); 9817 SetProperty(GridColTrackInfo(), colInfo); 9818 9819 const auto* subgridRowRange = 9820 subgrid && IsRowSubgrid() ? &subgrid->SubgridRows() : nullptr; 9821 LineNameMap rowLineNameMap(gridRI.mGridStyle, GetImplicitNamedAreas(), 9822 gridRI.mRowFunctions, nullptr, subgridRowRange, 9823 true); 9824 uint32_t rowTrackCount = gridRI.mRows.mSizes.Length(); 9825 nsTArray<nscoord> rowTrackPositions(rowTrackCount); 9826 nsTArray<nscoord> rowTrackSizes(rowTrackCount); 9827 nsTArray<uint32_t> rowTrackStates(rowTrackCount); 9828 nsTArray<bool> rowRemovedRepeatTracks( 9829 gridRI.mRowFunctions.mRemovedRepeatTracks.Clone()); 9830 uint32_t row = 0; 9831 for (const TrackSize& sz : gridRI.mRows.mSizes) { 9832 rowTrackPositions.AppendElement(sz.mPosition); 9833 rowTrackSizes.AppendElement(sz.mBase); 9834 bool isRepeat = ((row >= gridRI.mRowFunctions.mRepeatAutoStart) && 9835 (row < gridRI.mRowFunctions.mRepeatAutoEnd)); 9836 rowTrackStates.AppendElement( 9837 isRepeat ? (uint32_t)mozilla::dom::GridTrackState::Repeat 9838 : (uint32_t)mozilla::dom::GridTrackState::Static); 9839 9840 row++; 9841 } 9842 // Get the number of explicit tracks first. The order of argument evaluation 9843 // is implementation-defined. We should be OK here because colTrackSizes is 9844 // taken by rvalue, but computing the size first prevents any changes in the 9845 // argument types of the constructor from breaking this. 9846 const uint32_t numRowExplicitTracks = 9847 IsRowSubgrid() ? rowTrackSizes.Length() 9848 : gridRI.mRowFunctions.NumExplicitTracks(); 9849 // Row info has to accommodate fragmentation of the grid, which may happen 9850 // in later calls to Reflow. For now, presume that no more fragmentation 9851 // will occur. 9852 ComputedGridTrackInfo* rowInfo = new ComputedGridTrackInfo( 9853 gridRI.mRowFunctions.mExplicitGridOffset, numRowExplicitTracks, 9854 gridRI.mStartRow, row, std::move(rowTrackPositions), 9855 std::move(rowTrackSizes), std::move(rowTrackStates), 9856 std::move(rowRemovedRepeatTracks), 9857 gridRI.mRowFunctions.mRepeatAutoStart, 9858 rowLineNameMap.GetResolvedLineNamesForComputedGridTrackInfo(), 9859 IsRowSubgrid(), IsRowMasonry()); 9860 SetProperty(GridRowTrackInfo(), rowInfo); 9861 9862 if (prevInFlow) { 9863 // This frame is fragmenting rows from a previous frame, so patch up 9864 // the prior GridRowTrackInfo with a new end row. 9865 9866 // FIXME: This can be streamlined and/or removed when bug 1151204 lands. 9867 9868 ComputedGridTrackInfo* priorRowInfo = 9869 prevInFlow->GetProperty(GridRowTrackInfo()); 9870 9871 // Adjust track positions based on the first track in this fragment. 9872 if (priorRowInfo->mPositions.Length() > 9873 priorRowInfo->mStartFragmentTrack) { 9874 nscoord delta = 9875 priorRowInfo->mPositions[priorRowInfo->mStartFragmentTrack]; 9876 for (nscoord& pos : priorRowInfo->mPositions) { 9877 pos -= delta; 9878 } 9879 } 9880 9881 ComputedGridTrackInfo* revisedPriorRowInfo = new ComputedGridTrackInfo( 9882 priorRowInfo->mNumLeadingImplicitTracks, 9883 priorRowInfo->mNumExplicitTracks, priorRowInfo->mStartFragmentTrack, 9884 gridRI.mStartRow, std::move(priorRowInfo->mPositions), 9885 std::move(priorRowInfo->mSizes), std::move(priorRowInfo->mStates), 9886 std::move(priorRowInfo->mRemovedRepeatTracks), 9887 priorRowInfo->mRepeatFirstTrack, 9888 std::move(priorRowInfo->mResolvedLineNames), priorRowInfo->mIsSubgrid, 9889 priorRowInfo->mIsMasonry); 9890 prevInFlow->SetProperty(GridRowTrackInfo(), revisedPriorRowInfo); 9891 } 9892 9893 // Generate the line info properties. We need to provide the number of 9894 // repeat tracks produced in the reflow. Only explicit names are assigned 9895 // to lines here; the mozilla::dom::GridLines class will later extract 9896 // implicit names from grid areas and assign them to the appropriate lines. 9897 9898 auto& colFunctions = gridRI.mColFunctions; 9899 9900 // Generate column lines first. 9901 uint32_t capacity = gridRI.mCols.mSizes.Length(); 9902 nsTArray<nsTArray<RefPtr<nsAtom>>> columnLineNames(capacity); 9903 for (col = 0; col <= gridRI.mCols.mSizes.Length(); col++) { 9904 // Offset col by the explicit grid offset, to get the original names. 9905 nsTArray<RefPtr<nsAtom>> explicitNames = 9906 colLineNameMap.GetExplicitLineNamesAtIndex( 9907 col - colFunctions.mExplicitGridOffset); 9908 9909 columnLineNames.EmplaceBack(std::move(explicitNames)); 9910 } 9911 // Get the explicit names that follow a repeat auto declaration. 9912 nsTArray<RefPtr<nsAtom>> colNamesFollowingRepeat; 9913 nsTArray<RefPtr<nsAtom>> colBeforeRepeatAuto; 9914 nsTArray<RefPtr<nsAtom>> colAfterRepeatAuto; 9915 // Note: the following is only used for a non-subgridded axis. 9916 if (colLineNameMap.HasRepeatAuto()) { 9917 MOZ_ASSERT(!colFunctions.mTemplate.IsSubgrid()); 9918 // The line name list after the repeatAutoIndex holds the line names 9919 // for the first explicit line after the repeat auto declaration. 9920 uint32_t repeatAutoEnd = colLineNameMap.RepeatAutoStart() + 1; 9921 for (auto* list : colLineNameMap.ExpandedLineNames()[repeatAutoEnd]) { 9922 for (auto& name : list->AsSpan()) { 9923 colNamesFollowingRepeat.AppendElement(name.AsAtom()); 9924 } 9925 } 9926 auto names = colLineNameMap.TrackAutoRepeatLineNames(); 9927 for (auto& name : names[0].AsSpan()) { 9928 colBeforeRepeatAuto.AppendElement(name.AsAtom()); 9929 } 9930 for (auto& name : names[1].AsSpan()) { 9931 colAfterRepeatAuto.AppendElement(name.AsAtom()); 9932 } 9933 } 9934 9935 ComputedGridLineInfo* columnLineInfo = new ComputedGridLineInfo( 9936 std::move(columnLineNames), std::move(colBeforeRepeatAuto), 9937 std::move(colAfterRepeatAuto), std::move(colNamesFollowingRepeat)); 9938 SetProperty(GridColumnLineInfo(), columnLineInfo); 9939 9940 // Generate row lines next. 9941 auto& rowFunctions = gridRI.mRowFunctions; 9942 capacity = gridRI.mRows.mSizes.Length(); 9943 nsTArray<nsTArray<RefPtr<nsAtom>>> rowLineNames(capacity); 9944 for (row = 0; row <= gridRI.mRows.mSizes.Length(); row++) { 9945 // Offset row by the explicit grid offset, to get the original names. 9946 nsTArray<RefPtr<nsAtom>> explicitNames = 9947 rowLineNameMap.GetExplicitLineNamesAtIndex( 9948 row - rowFunctions.mExplicitGridOffset); 9949 rowLineNames.EmplaceBack(std::move(explicitNames)); 9950 } 9951 // Get the explicit names that follow a repeat auto declaration. 9952 nsTArray<RefPtr<nsAtom>> rowNamesFollowingRepeat; 9953 nsTArray<RefPtr<nsAtom>> rowBeforeRepeatAuto; 9954 nsTArray<RefPtr<nsAtom>> rowAfterRepeatAuto; 9955 // Note: the following is only used for a non-subgridded axis. 9956 if (rowLineNameMap.HasRepeatAuto()) { 9957 MOZ_ASSERT(!rowFunctions.mTemplate.IsSubgrid()); 9958 // The line name list after the repeatAutoIndex holds the line names 9959 // for the first explicit line after the repeat auto declaration. 9960 uint32_t repeatAutoEnd = rowLineNameMap.RepeatAutoStart() + 1; 9961 for (auto* list : rowLineNameMap.ExpandedLineNames()[repeatAutoEnd]) { 9962 for (auto& name : list->AsSpan()) { 9963 rowNamesFollowingRepeat.AppendElement(name.AsAtom()); 9964 } 9965 } 9966 auto names = rowLineNameMap.TrackAutoRepeatLineNames(); 9967 for (auto& name : names[0].AsSpan()) { 9968 rowBeforeRepeatAuto.AppendElement(name.AsAtom()); 9969 } 9970 for (auto& name : names[1].AsSpan()) { 9971 rowAfterRepeatAuto.AppendElement(name.AsAtom()); 9972 } 9973 } 9974 9975 ComputedGridLineInfo* rowLineInfo = new ComputedGridLineInfo( 9976 std::move(rowLineNames), std::move(rowBeforeRepeatAuto), 9977 std::move(rowAfterRepeatAuto), std::move(rowNamesFollowingRepeat)); 9978 SetProperty(GridRowLineInfo(), rowLineInfo); 9979 9980 // Generate area info for explicit areas. Implicit areas are handled 9981 // elsewhere. 9982 if (!gridRI.mGridStyle->mGridTemplateAreas.IsNone()) { 9983 auto* areas = new StyleOwnedSlice<NamedArea>( 9984 gridRI.mGridStyle->mGridTemplateAreas.AsAreas()->areas); 9985 SetProperty(ExplicitNamedAreasProperty(), areas); 9986 } else { 9987 RemoveProperty(ExplicitNamedAreasProperty()); 9988 } 9989 } 9990 9991 if (!prevInFlow) { 9992 SharedGridData* sharedGridData = GetProperty(SharedGridData::Prop()); 9993 if (!aStatus.IsFullyComplete()) { 9994 if (!sharedGridData) { 9995 sharedGridData = new SharedGridData; 9996 SetProperty(SharedGridData::Prop(), sharedGridData); 9997 } 9998 sharedGridData->mCols.mSizes = std::move(gridRI.mCols.mSizes); 9999 sharedGridData->mCols.mContentBoxSize = gridRI.mCols.mContentBoxSize; 10000 sharedGridData->mCols.mBaselineSubtreeAlign = 10001 gridRI.mCols.mBaselineSubtreeAlign; 10002 sharedGridData->mCols.mIsMasonry = gridRI.mCols.mIsMasonry; 10003 sharedGridData->mRows.mSizes = std::move(gridRI.mRows.mSizes); 10004 // Save the original row grid sizes and gaps so we can restore them later 10005 // in GridReflowInput::Initialize for the continuations. 10006 auto& origRowData = sharedGridData->mOriginalRowData; 10007 origRowData.ClearAndRetainStorage(); 10008 origRowData.SetCapacity(sharedGridData->mRows.mSizes.Length()); 10009 nscoord prevTrackEnd = 0; 10010 for (auto& sz : sharedGridData->mRows.mSizes) { 10011 SharedGridData::RowData data = {sz.mBase, sz.mPosition - prevTrackEnd}; 10012 origRowData.AppendElement(data); 10013 prevTrackEnd = sz.mPosition + sz.mBase; 10014 } 10015 sharedGridData->mRows.mContentBoxSize = gridRI.mRows.mContentBoxSize; 10016 sharedGridData->mRows.mBaselineSubtreeAlign = 10017 gridRI.mRows.mBaselineSubtreeAlign; 10018 sharedGridData->mRows.mIsMasonry = gridRI.mRows.mIsMasonry; 10019 sharedGridData->mGridItems = std::move(gridRI.mGridItems); 10020 sharedGridData->mAbsPosItems = std::move(gridRI.mAbsPosItems); 10021 10022 sharedGridData->mGenerateComputedGridInfo = 10023 HasAnyStateBits(NS_STATE_GRID_COMPUTED_INFO); 10024 } else if (sharedGridData && !GetNextInFlow()) { 10025 RemoveProperty(SharedGridData::Prop()); 10026 } 10027 } 10028 10029 FinishAndStoreOverflow(&aDesiredSize); 10030 } 10031 10032 void nsGridContainerFrame::UpdateSubgridFrameState() { 10033 nsFrameState oldBits = GetStateBits() & kIsSubgridBits; 10034 nsFrameState newBits = ComputeSelfSubgridMasonryBits() & kIsSubgridBits; 10035 if (newBits != oldBits) { 10036 RemoveStateBits(kIsSubgridBits); 10037 if (!newBits) { 10038 RemoveProperty(Subgrid::Prop()); 10039 } else { 10040 AddStateBits(newBits); 10041 } 10042 } 10043 } 10044 10045 nsFrameState nsGridContainerFrame::ComputeSelfSubgridMasonryBits() const { 10046 nsFrameState bits = nsFrameState(0); 10047 const auto* pos = StylePosition(); 10048 10049 // We can only have masonry layout in one axis. 10050 if (pos->mGridTemplateRows.IsMasonry()) { 10051 bits |= NS_STATE_GRID_IS_ROW_MASONRY; 10052 } else if (pos->mGridTemplateColumns.IsMasonry()) { 10053 bits |= NS_STATE_GRID_IS_COL_MASONRY; 10054 } 10055 10056 // NOTE: The rest of this function is only relevant if we're a subgrid; 10057 // hence, we return early as soon as we rule out that possibility. 10058 10059 // 'contain:layout/paint' makes us an "independent formatting context", 10060 // which prevents us from being a subgrid in this case (but not always). 10061 // We will also need to check our containing scroll frame for this property. 10062 // https://drafts.csswg.org/css-display-3/#establish-an-independent-formatting-context 10063 if (ShouldInhibitSubgridDueToIFC(this)) { 10064 return bits; 10065 } 10066 10067 // Skip over our scroll frame and such if we have it, to find our "parent 10068 // grid", if we have one. 10069 10070 // After this loop, 'parent' will represent the parent of the outermost frame 10071 // that shares our content node. (Normally this is just our parent frame, but 10072 // if we're e.g. a scrolled frame, then this will be the parent of our 10073 // wrapper-scrollable-frame.) If 'parent' turns out to be a grid container, 10074 // then it's our "parent grid", and we could potentially be a subgrid of it. 10075 auto* parent = GetParent(); 10076 while (parent && parent->GetContent() == GetContent()) { 10077 // If we find our containing frame (e.g. our scroll frame) can't be a 10078 // subgrid, then we can't be a subgrid, for the same reasons as above. This 10079 // can happen when this frame is itself a grid item with "overflow:scroll" 10080 // or similar. 10081 if (ShouldInhibitSubgridDueToIFC(parent)) { 10082 return bits; 10083 } 10084 parent = parent->GetParent(); 10085 } 10086 const nsGridContainerFrame* parentGrid = do_QueryFrame(parent); 10087 if (parentGrid) { 10088 bool isOrthogonal = 10089 GetWritingMode().IsOrthogonalTo(parent->GetWritingMode()); 10090 bool isColSubgrid = pos->mGridTemplateColumns.IsSubgrid(); 10091 // Subgridding a parent masonry axis makes us use masonry layout too, 10092 // unless our other axis is a masonry axis. 10093 if (isColSubgrid && 10094 parent->HasAnyStateBits(isOrthogonal ? NS_STATE_GRID_IS_ROW_MASONRY 10095 : NS_STATE_GRID_IS_COL_MASONRY)) { 10096 isColSubgrid = false; 10097 if (!HasAnyStateBits(NS_STATE_GRID_IS_ROW_MASONRY)) { 10098 bits |= NS_STATE_GRID_IS_COL_MASONRY; 10099 } 10100 } 10101 if (isColSubgrid) { 10102 bits |= NS_STATE_GRID_IS_COL_SUBGRID; 10103 } 10104 10105 bool isRowSubgrid = pos->mGridTemplateRows.IsSubgrid(); 10106 if (isRowSubgrid && 10107 parent->HasAnyStateBits(isOrthogonal ? NS_STATE_GRID_IS_COL_MASONRY 10108 : NS_STATE_GRID_IS_ROW_MASONRY)) { 10109 isRowSubgrid = false; 10110 if (!HasAnyStateBits(NS_STATE_GRID_IS_COL_MASONRY)) { 10111 bits |= NS_STATE_GRID_IS_ROW_MASONRY; 10112 } 10113 } 10114 if (isRowSubgrid) { 10115 bits |= NS_STATE_GRID_IS_ROW_SUBGRID; 10116 } 10117 } 10118 return bits; 10119 } 10120 10121 void nsGridContainerFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 10122 nsIFrame* aPrevInFlow) { 10123 nsContainerFrame::Init(aContent, aParent, aPrevInFlow); 10124 10125 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER)) { 10126 AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT); 10127 } 10128 10129 nsFrameState bits = nsFrameState(0); 10130 if (MOZ_LIKELY(!aPrevInFlow)) { 10131 bits = ComputeSelfSubgridMasonryBits(); 10132 } else { 10133 bits = aPrevInFlow->GetStateBits() & 10134 (NS_STATE_GRID_IS_ROW_MASONRY | NS_STATE_GRID_IS_COL_MASONRY | 10135 kIsSubgridBits | NS_STATE_GRID_HAS_COL_SUBGRID_ITEM | 10136 NS_STATE_GRID_HAS_ROW_SUBGRID_ITEM); 10137 } 10138 AddStateBits(bits); 10139 } 10140 10141 void nsGridContainerFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) { 10142 nsContainerFrame::DidSetComputedStyle(aOldStyle); 10143 10144 if (!aOldStyle) { 10145 return; // Init() already initialized the bits. 10146 } 10147 UpdateSubgridFrameState(); 10148 } 10149 10150 nscoord nsGridContainerFrame::ComputeIntrinsicISize( 10151 const IntrinsicSizeInput& aInput, IntrinsicISizeType aType) { 10152 GRID_LOG("Compute %s isize for grid container frame %p", 10153 aType == IntrinsicISizeType::MinISize ? "min" : "pref", this); 10154 10155 if (Maybe<nscoord> containISize = ContainIntrinsicISize()) { 10156 return *containISize; 10157 } 10158 10159 // Calculate the sum of column sizes under intrinsic sizing. 10160 // https://drafts.csswg.org/css-grid-2/#intrinsic-sizes 10161 NormalizeChildLists(); 10162 GridReflowInput gridRI(this, *aInput.mContext); 10163 // Ensure we do not measure flex tracks against unconstrained bounds. 10164 gridRI.mIsGridIntrinsicSizing = true; 10165 InitImplicitNamedAreas(gridRI.mGridStyle); // XXX optimize 10166 10167 // The min/sz/max sizes are the input to the "repeat-to-fill" algorithm: 10168 // https://drafts.csswg.org/css-grid-2/#auto-repeat 10169 // They're only used for auto-repeat so we skip computing them otherwise. 10170 RepeatTrackSizingInput repeatSizing(gridRI.mWM); 10171 if (!IsColSubgrid() && gridRI.mColFunctions.mHasRepeatAuto) { 10172 repeatSizing.InitFromStyle( 10173 LogicalAxis::Inline, gridRI.mWM, gridRI.mFrame, gridRI.mFrame->Style(), 10174 gridRI.mFrame->GetAspectRatio(), aInput.mContainingBlockSize); 10175 } 10176 if ((!IsRowSubgrid() && gridRI.mRowFunctions.mHasRepeatAuto && 10177 !(gridRI.mGridStyle->mGridAutoFlow & StyleGridAutoFlow::ROW)) || 10178 IsColMasonry()) { 10179 // Only 'grid-auto-flow:column' can create new implicit columns, so that's 10180 // the only case where our block-size can affect the number of columns. 10181 // Masonry layout always depends on how many rows we have though. 10182 repeatSizing.InitFromStyle( 10183 LogicalAxis::Block, gridRI.mWM, gridRI.mFrame, gridRI.mFrame->Style(), 10184 gridRI.mFrame->GetAspectRatio(), aInput.mContainingBlockSize); 10185 } 10186 10187 Grid grid; 10188 if (MOZ_LIKELY(!IsSubgrid())) { 10189 grid.PlaceGridItems(gridRI, repeatSizing); // XXX optimize 10190 } else { 10191 auto* subgrid = GetProperty(Subgrid::Prop()); 10192 gridRI.mGridItems = subgrid->mGridItems.Clone(); 10193 gridRI.mAbsPosItems = subgrid->mAbsPosItems.Clone(); 10194 grid.mGridColEnd = subgrid->mGridColEnd; 10195 grid.mGridRowEnd = subgrid->mGridRowEnd; 10196 } 10197 10198 auto constraint = aType == IntrinsicISizeType::MinISize 10199 ? SizingConstraint::MinContent 10200 : SizingConstraint::MaxContent; 10201 if (IsColMasonry()) { 10202 ReflowOutput desiredSize(gridRI.mWM); 10203 nsSize containerSize; 10204 LogicalRect contentArea(gridRI.mWM); 10205 nsReflowStatus status; 10206 gridRI.mRows.mSizes.SetLength(grid.mGridRowEnd); 10207 gridRI.CalculateTrackSizesForAxis(LogicalAxis::Inline, grid, 10208 NS_UNCONSTRAINEDSIZE, constraint); 10209 return MasonryLayout(gridRI, contentArea, constraint, desiredSize, status, 10210 nullptr, containerSize); 10211 } 10212 10213 if (grid.mGridColEnd == 0) { 10214 return nscoord(0); 10215 } 10216 10217 gridRI.CalculateTrackSizesForAxis(LogicalAxis::Inline, grid, 10218 NS_UNCONSTRAINEDSIZE, constraint); 10219 10220 const nscoord contentBoxBSize = 10221 aInput.mPercentageBasisForChildren 10222 ? aInput.mPercentageBasisForChildren->BSize(gridRI.mWM) 10223 : NS_UNCONSTRAINEDSIZE; 10224 10225 // Resolve row sizes so that when we re-resolve the column sizes, grid items 10226 // with percent-valued block-sizes (and aspect ratios) have definite row 10227 // sizes as the percentage basis. Their resolved block-size can then 10228 // transfer to the inline-axis, contributing correctly to the grid 10229 // container's intrinsic inline-size. 10230 gridRI.CalculateTrackSizesForAxis(LogicalAxis::Block, grid, contentBoxBSize, 10231 SizingConstraint::NoConstraint); 10232 10233 // Invalidate the column sizes before re-resolving them. 10234 gridRI.InvalidateTrackSizesForAxis(LogicalAxis::Inline); 10235 10236 // Re-resolve the column sizes, using the resolved row sizes established 10237 // above. See 12.1.3 of the Grid Sizing Algorithm for more scenarios where 10238 // re-resolving the column sizes is necessary: 10239 // https://drafts.csswg.org/css-grid-2/#algo-grid-sizing 10240 gridRI.CalculateTrackSizesForAxis(LogicalAxis::Inline, grid, 10241 NS_UNCONSTRAINEDSIZE, constraint); 10242 10243 return gridRI.mCols.TotalTrackSizeWithoutAlignment(this); 10244 } 10245 10246 nscoord nsGridContainerFrame::IntrinsicISize(const IntrinsicSizeInput& aInput, 10247 IntrinsicISizeType aType) { 10248 auto* firstCont = static_cast<nsGridContainerFrame*>(FirstContinuation()); 10249 if (firstCont != this) { 10250 return firstCont->IntrinsicISize(aInput, aType); 10251 } 10252 return mCachedIntrinsicSizes.GetOrSet(*this, aType, aInput, [&] { 10253 return ComputeIntrinsicISize(aInput, aType); 10254 }); 10255 } 10256 10257 void nsGridContainerFrame::MarkIntrinsicISizesDirty() { 10258 mCachedIntrinsicSizes.Clear(); 10259 for (auto& perAxisBaseline : mBaseline) { 10260 for (auto& baseline : perAxisBaseline) { 10261 baseline = NS_INTRINSIC_ISIZE_UNKNOWN; 10262 } 10263 } 10264 nsContainerFrame::MarkIntrinsicISizesDirty(); 10265 } 10266 10267 void nsGridContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 10268 const nsDisplayListSet& aLists) { 10269 DisplayBorderBackgroundOutline(aBuilder, aLists); 10270 if (HidesContent()) { 10271 return; 10272 } 10273 10274 if (GetPrevInFlow()) { 10275 DisplayOverflowContainers(aBuilder, aLists); 10276 DisplayPushedAbsoluteFrames(aBuilder, aLists); 10277 } 10278 10279 // Our children are all grid-level boxes, which behave the same as 10280 // inline-blocks in painting, so their borders/backgrounds all go on 10281 // the BlockBorderBackgrounds list. 10282 typedef CSSOrderAwareFrameIterator::OrderState OrderState; 10283 OrderState order = 10284 HasAnyStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER) 10285 ? OrderState::Ordered 10286 : OrderState::Unordered; 10287 CSSOrderAwareFrameIterator iter( 10288 this, FrameChildListID::Principal, 10289 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll, order); 10290 const auto flags = DisplayFlagsForFlexOrGridItem(); 10291 for (; !iter.AtEnd(); iter.Next()) { 10292 nsIFrame* child = *iter; 10293 BuildDisplayListForChild(aBuilder, child, aLists, flags); 10294 } 10295 } 10296 10297 bool nsGridContainerFrame::DrainSelfOverflowList() { 10298 return DrainAndMergeSelfOverflowList(); 10299 } 10300 10301 void nsGridContainerFrame::AppendFrames(ChildListID aListID, 10302 nsFrameList&& aFrameList) { 10303 NoteNewChildren(aListID, aFrameList); 10304 nsContainerFrame::AppendFrames(aListID, std::move(aFrameList)); 10305 } 10306 10307 void nsGridContainerFrame::InsertFrames( 10308 ChildListID aListID, nsIFrame* aPrevFrame, 10309 const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) { 10310 NoteNewChildren(aListID, aFrameList); 10311 nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine, 10312 std::move(aFrameList)); 10313 } 10314 10315 void nsGridContainerFrame::RemoveFrame(DestroyContext& aContext, 10316 ChildListID aListID, 10317 nsIFrame* aOldFrame) { 10318 MOZ_ASSERT(aListID == FrameChildListID::Principal, "unexpected child list"); 10319 10320 #ifdef DEBUG 10321 SetDidPushItemsBitIfNeeded(aListID, aOldFrame); 10322 #endif 10323 10324 nsContainerFrame::RemoveFrame(aContext, aListID, aOldFrame); 10325 } 10326 10327 nscoord nsGridContainerFrame::SynthesizeBaseline( 10328 const FindItemInGridOrderResult& aGridOrderItem, LogicalAxis aAxis, 10329 BaselineSharingGroup aGroup, const nsSize& aCBPhysicalSize, nscoord aCBSize, 10330 WritingMode aCBWM) { 10331 if (MOZ_UNLIKELY(!aGridOrderItem.mItem)) { 10332 // No item in this fragment - synthesize a baseline from our border-box. 10333 return ::SynthesizeBaselineFromBorderBox(aGroup, aCBWM, aAxis, aCBSize); 10334 } 10335 10336 nsIFrame* child = aGridOrderItem.mItem->mFrame; 10337 nsGridContainerFrame* grid = do_QueryFrame(child); 10338 auto childWM = child->GetWritingMode(); 10339 bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM); 10340 const LogicalAxis childAxis = aCBWM.ConvertAxisTo(aAxis, childWM); 10341 nscoord baseline; 10342 nscoord start; 10343 nscoord size; 10344 10345 if (aAxis == LogicalAxis::Block) { 10346 start = child->GetLogicalNormalPosition(aCBWM, aCBPhysicalSize).B(aCBWM); 10347 size = child->BSize(aCBWM); 10348 if (grid && aGridOrderItem.mIsInEdgeTrack) { 10349 baseline = isOrthogonal ? grid->GetIBaseline(aGroup) 10350 : grid->GetBBaseline(aGroup); 10351 } else if (!isOrthogonal && aGridOrderItem.mIsInEdgeTrack) { 10352 // This assertion is mostly for documentation purposes; it must hold, 10353 // given the checks in our 'if' statements. (We know aAxis is 10354 // LogicalAxis::Block, and isOrthogonal is false, which means childAxis 10355 // must be LogicalAxis::Block). If instead we got here with a childAxis of 10356 // LogicalAxis::Inline, then our call to 10357 // Baseline::SynthesizeBaselineFromBorderBox might incorrectly think 10358 // it makes sense to use a central baseline, in an axis where that 10359 // doesn't make sense. 10360 MOZ_ASSERT(childAxis == LogicalAxis::Block, "unexpected childAxis"); 10361 baseline = child 10362 ->GetNaturalBaselineBOffset(childWM, aGroup, 10363 BaselineExportContext::Other) 10364 .valueOrFrom([aGroup, child, childWM]() { 10365 return Baseline::SynthesizeBOffsetFromBorderBox( 10366 child, childWM, aGroup); 10367 }); 10368 } else { 10369 baseline = 10370 ::SynthesizeBaselineFromBorderBox(aGroup, childWM, childAxis, size); 10371 } 10372 } else { 10373 start = child->GetLogicalNormalPosition(aCBWM, aCBPhysicalSize).I(aCBWM); 10374 size = child->ISize(aCBWM); 10375 if (grid && aGridOrderItem.mIsInEdgeTrack) { 10376 baseline = isOrthogonal ? grid->GetBBaseline(aGroup) 10377 : grid->GetIBaseline(aGroup); 10378 } else if (isOrthogonal && aGridOrderItem.mIsInEdgeTrack) { 10379 baseline = child 10380 ->GetNaturalBaselineBOffset(childWM, aGroup, 10381 BaselineExportContext::Other) 10382 .valueOrFrom([aGroup, childWM, childAxis, size]() { 10383 return ::SynthesizeBaselineFromBorderBox( 10384 aGroup, childWM, childAxis, size); 10385 }); 10386 } else { 10387 baseline = 10388 ::SynthesizeBaselineFromBorderBox(aGroup, childWM, childAxis, size); 10389 } 10390 } 10391 return aGroup == BaselineSharingGroup::First 10392 ? start + baseline 10393 : aCBSize - start - size + baseline; 10394 } 10395 10396 void nsGridContainerFrame::CalculateBaselines( 10397 BaselineSet aBaselineSet, CSSOrderAwareFrameIterator* aIter, 10398 const nsTArray<GridItemInfo>* aGridItems, const Tracks& aTracks, 10399 uint32_t aFragmentStartTrack, uint32_t aFirstExcludedTrack, WritingMode aWM, 10400 const nsSize& aCBPhysicalSize, nscoord aCBBorderPaddingStart, 10401 nscoord aCBBorderPaddingEnd, nscoord aCBSize) { 10402 const auto axis = aTracks.mAxis; 10403 10404 auto firstBaseline = aTracks.GetBaseline(0, BaselineSharingGroup::First); 10405 if (!(aBaselineSet & BaselineSet::eFirst)) { 10406 mBaseline[axis][BaselineSharingGroup::First] = 10407 ::SynthesizeBaselineFromBorderBox(BaselineSharingGroup::First, aWM, 10408 axis, aCBSize); 10409 } else if (firstBaseline.isNothing()) { 10410 FindItemInGridOrderResult gridOrderFirstItem = FindFirstItemInGridOrder( 10411 *aIter, *aGridItems, 10412 axis == LogicalAxis::Block ? &GridArea::mRows : &GridArea::mCols, 10413 axis == LogicalAxis::Block ? &GridArea::mCols : &GridArea::mRows, 10414 aFragmentStartTrack); 10415 mBaseline[axis][BaselineSharingGroup::First] = SynthesizeBaseline( 10416 gridOrderFirstItem, axis, BaselineSharingGroup::First, aCBPhysicalSize, 10417 aCBSize, aWM); 10418 } else { 10419 // We have a 'first baseline' group in the start track in this fragment. 10420 // Convert it from track to grid container border-box coordinates. 10421 MOZ_ASSERT(!aGridItems->IsEmpty()); 10422 nscoord gapBeforeStartTrack = 10423 aFragmentStartTrack == 0 10424 ? aTracks.GridLineEdge(aFragmentStartTrack, 10425 GridLineSide::AfterGridGap) 10426 : nscoord(0); // no content gap at start of fragment 10427 mBaseline[axis][BaselineSharingGroup::First] = 10428 aCBBorderPaddingStart + gapBeforeStartTrack + *firstBaseline; 10429 } 10430 10431 auto lastBaseline = aTracks.GetBaseline(aTracks.mBaselines.Length() - 1, 10432 BaselineSharingGroup::Last); 10433 if (!(aBaselineSet & BaselineSet::eLast)) { 10434 mBaseline[axis][BaselineSharingGroup::Last] = 10435 ::SynthesizeBaselineFromBorderBox(BaselineSharingGroup::Last, aWM, axis, 10436 aCBSize); 10437 } else if (lastBaseline.isNothing()) { 10438 // For finding items for the 'last baseline' we need to create a reverse 10439 // iterator ('aIter' is the forward iterator from the GridReflowInput). 10440 using Iter = ReverseCSSOrderAwareFrameIterator; 10441 auto orderState = aIter->ItemsAreAlreadyInOrder() 10442 ? Iter::OrderState::Ordered 10443 : Iter::OrderState::Unordered; 10444 Iter iter(this, FrameChildListID::Principal, 10445 Iter::ChildFilter::SkipPlaceholders, orderState); 10446 iter.SetItemCount(aGridItems->Length()); 10447 FindItemInGridOrderResult gridOrderLastItem = FindLastItemInGridOrder( 10448 iter, *aGridItems, 10449 axis == LogicalAxis::Block ? &GridArea::mRows : &GridArea::mCols, 10450 axis == LogicalAxis::Block ? &GridArea::mCols : &GridArea::mRows, 10451 aFragmentStartTrack, aFirstExcludedTrack); 10452 mBaseline[axis][BaselineSharingGroup::Last] = 10453 SynthesizeBaseline(gridOrderLastItem, axis, BaselineSharingGroup::Last, 10454 aCBPhysicalSize, aCBSize, aWM); 10455 } else { 10456 // We have a 'last baseline' group in the end track in this fragment. 10457 // Convert it from track to grid container border-box coordinates. 10458 MOZ_ASSERT(!aGridItems->IsEmpty()); 10459 auto borderBoxStartToEndOfEndTrack = 10460 aCBBorderPaddingStart + 10461 aTracks.GridLineEdge(aFirstExcludedTrack, GridLineSide::BeforeGridGap) - 10462 aTracks.GridLineEdge(aFragmentStartTrack, GridLineSide::BeforeGridGap); 10463 mBaseline[axis][BaselineSharingGroup::Last] = 10464 (aCBSize - borderBoxStartToEndOfEndTrack) + *lastBaseline; 10465 } 10466 } 10467 10468 #ifdef DEBUG_FRAME_DUMP 10469 nsresult nsGridContainerFrame::GetFrameName(nsAString& aResult) const { 10470 return MakeFrameName(u"GridContainer"_ns, aResult); 10471 } 10472 10473 void nsGridContainerFrame::ExtraContainerFrameInfo( 10474 nsACString& aTo, bool aListOnlyDeterministic) const { 10475 if (const void* const subgrid = GetProperty(Subgrid::Prop())) { 10476 aTo += "[subgrid"; 10477 ListPtr(aTo, aListOnlyDeterministic, subgrid); 10478 aTo += "]"; 10479 } 10480 } 10481 10482 #endif 10483 10484 /* static */ nsGridContainerFrame::FindItemInGridOrderResult 10485 nsGridContainerFrame::FindFirstItemInGridOrder( 10486 CSSOrderAwareFrameIterator& aIter, const nsTArray<GridItemInfo>& aGridItems, 10487 LineRange GridArea::* aMajor, LineRange GridArea::* aMinor, 10488 uint32_t aFragmentStartTrack) { 10489 FindItemInGridOrderResult result = {nullptr, false}; 10490 uint32_t minMajor = kTranslatedMaxLine + 1; 10491 uint32_t minMinor = kTranslatedMaxLine + 1; 10492 aIter.Reset(); 10493 for (; !aIter.AtEnd(); aIter.Next()) { 10494 const GridItemInfo& item = aGridItems[aIter.ItemIndex()]; 10495 if ((item.mArea.*aMajor).mEnd <= aFragmentStartTrack) { 10496 continue; // item doesn't span any track in this fragment 10497 } 10498 uint32_t major = (item.mArea.*aMajor).mStart; 10499 uint32_t minor = (item.mArea.*aMinor).mStart; 10500 if (major < minMajor || (major == minMajor && minor < minMinor)) { 10501 minMajor = major; 10502 minMinor = minor; 10503 result.mItem = &item; 10504 result.mIsInEdgeTrack = major == 0U; 10505 } 10506 } 10507 return result; 10508 } 10509 10510 /* static */ nsGridContainerFrame::FindItemInGridOrderResult 10511 nsGridContainerFrame::FindLastItemInGridOrder( 10512 ReverseCSSOrderAwareFrameIterator& aIter, 10513 const nsTArray<GridItemInfo>& aGridItems, LineRange GridArea::* aMajor, 10514 LineRange GridArea::* aMinor, uint32_t aFragmentStartTrack, 10515 uint32_t aFirstExcludedTrack) { 10516 FindItemInGridOrderResult result = {nullptr, false}; 10517 int32_t maxMajor = -1; 10518 int32_t maxMinor = -1; 10519 aIter.Reset(); 10520 int32_t lastMajorTrack = int32_t(aFirstExcludedTrack) - 1; 10521 for (; !aIter.AtEnd(); aIter.Next()) { 10522 const GridItemInfo& item = aGridItems[aIter.ItemIndex()]; 10523 // Subtract 1 from the end line to get the item's last track index. 10524 int32_t major = (item.mArea.*aMajor).mEnd - 1; 10525 // Currently, this method is only called with aFirstExcludedTrack == 10526 // the first track in the next fragment, so we take the opportunity 10527 // to assert this item really belongs to this fragment. 10528 MOZ_ASSERT((item.mArea.*aMajor).mStart < aFirstExcludedTrack, 10529 "found an item that belongs to some later fragment"); 10530 if (major < int32_t(aFragmentStartTrack)) { 10531 continue; // item doesn't span any track in this fragment 10532 } 10533 int32_t minor = (item.mArea.*aMinor).mEnd - 1; 10534 MOZ_ASSERT(minor >= 0 && major >= 0, "grid item must have span >= 1"); 10535 if (major > maxMajor || (major == maxMajor && minor > maxMinor)) { 10536 maxMajor = major; 10537 maxMinor = minor; 10538 result.mItem = &item; 10539 result.mIsInEdgeTrack = major == lastMajorTrack; 10540 } 10541 } 10542 return result; 10543 } 10544 10545 nsGridContainerFrame::UsedTrackSizes* nsGridContainerFrame::GetUsedTrackSizes() 10546 const { 10547 return GetProperty(UsedTrackSizes::Prop()); 10548 } 10549 10550 void nsGridContainerFrame::StoreUsedTrackSizes(LogicalAxis aAxis, 10551 const TrackPlan& aSizes) { 10552 auto* uts = GetUsedTrackSizes(); 10553 if (!uts) { 10554 uts = new UsedTrackSizes(); 10555 SetProperty(UsedTrackSizes::Prop(), uts); 10556 } 10557 uts->mTrackPlans[aAxis].Assign(aSizes); 10558 uts->mCanResolveLineRangeSize[aAxis] = true; 10559 // XXX is resetting these bits necessary? 10560 for (auto& sz : uts->mTrackPlans[aAxis]) { 10561 sz.mState &= ~(TrackSize::eFrozen | TrackSize::eSkipGrowUnlimited | 10562 TrackSize::eInfinitelyGrowable); 10563 } 10564 } 10565 10566 #ifdef DEBUG 10567 void nsGridContainerFrame::SetInitialChildList(ChildListID aListID, 10568 nsFrameList&& aChildList) { 10569 ChildListIDs supportedLists = {FrameChildListID::Principal}; 10570 MOZ_ASSERT(supportedLists.contains(aListID), "unexpected child list"); 10571 return nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList)); 10572 } 10573 10574 void nsGridContainerFrame::TrackSize::DumpStateBits(StateBits aState) { 10575 printf("min:"); 10576 if (aState & eAutoMinSizing) { 10577 printf("auto "); 10578 } else if (aState & eMinContentMinSizing) { 10579 printf("min-content "); 10580 } else if (aState & eMaxContentMinSizing) { 10581 printf("max-content "); 10582 } 10583 printf(" max:"); 10584 if (aState & eAutoMaxSizing) { 10585 printf("auto "); 10586 } else if (aState & eMinContentMaxSizing) { 10587 printf("min-content "); 10588 } else if (aState & eMaxContentMaxSizing) { 10589 printf("max-content "); 10590 } else if (aState & eFlexMaxSizing) { 10591 printf("flex "); 10592 } 10593 if (aState & eFrozen) { 10594 printf("frozen "); 10595 } 10596 if (aState & eModified) { 10597 printf("modified "); 10598 } 10599 if (aState & eBreakBefore) { 10600 printf("break-before "); 10601 } 10602 } 10603 10604 void nsGridContainerFrame::TrackSize::Dump() const { 10605 printf("mPosition=%d mBase=%d mLimit=%d ", mPosition, mBase, mLimit); 10606 DumpStateBits(mState); 10607 } 10608 10609 #endif // DEBUG 10610 10611 bool nsGridContainerFrame::IsMasonry(LogicalAxis aAxis) const { 10612 return HasAnyStateBits(aAxis == mozilla::LogicalAxis::Block 10613 ? NS_STATE_GRID_IS_ROW_MASONRY 10614 : NS_STATE_GRID_IS_COL_MASONRY); 10615 } 10616 10617 bool nsGridContainerFrame::GridItemShouldStretch(const nsIFrame* aChild, 10618 LogicalAxis aAxis) const { 10619 MOZ_ASSERT(aChild->IsGridItem()); 10620 10621 if (aChild->IsGridContainerFrame()) { 10622 // The subgrid is always stretched in its subgridded dimensions. 10623 // https://drafts.csswg.org/css-grid-2/#subgrid-box-alignment 10624 const auto* gridContainer = 10625 static_cast<const nsGridContainerFrame*>(aChild); 10626 if (gridContainer->IsSubgrid(aAxis)) { 10627 return true; 10628 } 10629 } 10630 10631 const auto wm = aChild->GetWritingMode(); 10632 if (aChild->StyleMargin()->HasAuto(aAxis, wm, 10633 AnchorPosResolutionParams::From(aChild))) { 10634 // Per https://drafts.csswg.org/css-grid-2/#auto-margins, any 'auto' margin 10635 // in an axis disables the alignment property in that axis. 10636 return false; 10637 } 10638 10639 const auto cbwm = GetWritingMode(); 10640 if (IsMasonry(wm, aAxis)) { 10641 // The child is in the container's masonry-axis. 10642 // AlignJustifyTracksInMasonryAxis will stretch it, so we don't report that 10643 // here. 10644 return false; 10645 } 10646 10647 const auto alignment = 10648 aChild->StylePosition()->UsedSelfAlignment(wm, aAxis, cbwm, Style()); 10649 // An item with 'normal' alignment that is a replaced frame should use its 10650 // natural size, and not fill the grid area. 10651 // https://drafts.csswg.org/css-grid-2/#grid-item-sizing 10652 if (MOZ_LIKELY(alignment == StyleAlignFlags::NORMAL)) { 10653 return !aChild->HasReplacedSizing(); 10654 } 10655 return alignment == StyleAlignFlags::STRETCH; 10656 } 10657 10658 bool nsGridContainerFrame::ShouldInhibitSubgridDueToIFC( 10659 const nsIFrame* aFrame) { 10660 // Just checking for things that make us establish an independent formatting 10661 // context (IFC) and hence prevent us from being a subgrid: 10662 // * Out-of-flow (e.g. abspos) frames also establish an IFC. Note, our 10663 // NS_FRAME_OUT_OF_FLOW bit potentially isn't set yet, so we check our style. 10664 // * contain:layout and contain:paint each make us establish an IFC. 10665 const auto* display = aFrame->StyleDisplay(); 10666 return display->IsContainLayout() || display->IsContainPaint() || 10667 display->mContainerType & 10668 (StyleContainerType::SIZE | StyleContainerType::INLINE_SIZE) || 10669 display->IsAbsolutelyPositionedStyle(); 10670 } 10671 10672 nsGridContainerFrame* nsGridContainerFrame::GetGridContainerFrame( 10673 nsIFrame* aFrame) { 10674 nsGridContainerFrame* gridFrame = nullptr; 10675 10676 if (aFrame) { 10677 nsIFrame* inner = aFrame; 10678 if (MOZ_UNLIKELY(aFrame->IsFieldSetFrame())) { 10679 inner = static_cast<nsFieldSetFrame*>(aFrame)->GetInner(); 10680 } 10681 // Since "Get" methods like GetInner and GetContentInsertionFrame can 10682 // return null, we check the return values before dereferencing. Our 10683 // calling pattern makes this unlikely, but we're being careful. 10684 nsIFrame* insertionFrame = 10685 inner ? inner->GetContentInsertionFrame() : nullptr; 10686 nsIFrame* possibleGridFrame = insertionFrame ? insertionFrame : aFrame; 10687 gridFrame = possibleGridFrame->IsGridContainerFrame() 10688 ? static_cast<nsGridContainerFrame*>(possibleGridFrame) 10689 : nullptr; 10690 } 10691 return gridFrame; 10692 } 10693 10694 nsGridContainerFrame* nsGridContainerFrame::GetGridFrameWithComputedInfo( 10695 nsIFrame* aFrame) { 10696 nsGridContainerFrame* gridFrame = GetGridContainerFrame(aFrame); 10697 if (!gridFrame) { 10698 return nullptr; 10699 } 10700 10701 auto HasComputedInfo = [](const nsGridContainerFrame& aFrame) -> bool { 10702 return aFrame.HasProperty(GridColTrackInfo()) && 10703 aFrame.HasProperty(GridRowTrackInfo()) && 10704 aFrame.HasProperty(GridColumnLineInfo()) && 10705 aFrame.HasProperty(GridRowLineInfo()); 10706 }; 10707 10708 if (HasComputedInfo(*gridFrame)) { 10709 return gridFrame; 10710 } 10711 10712 // Trigger a reflow that generates additional grid property data. 10713 // Hold onto aFrame while we do this, in case reflow destroys it. 10714 AutoWeakFrame weakFrameRef(gridFrame); 10715 10716 RefPtr<mozilla::PresShell> presShell = gridFrame->PresShell(); 10717 gridFrame->AddStateBits(NS_STATE_GRID_COMPUTED_INFO); 10718 presShell->FrameNeedsReflow(gridFrame, IntrinsicDirty::None, 10719 NS_FRAME_IS_DIRTY); 10720 presShell->FlushPendingNotifications(FlushType::Layout); 10721 10722 // If the weakFrameRef is no longer valid, then we must bail out. 10723 if (!weakFrameRef.IsAlive()) { 10724 return nullptr; 10725 } 10726 10727 // This can happen if for some reason we ended up not reflowing, like in print 10728 // preview under some circumstances. 10729 if (MOZ_UNLIKELY(!HasComputedInfo(*gridFrame))) { 10730 return nullptr; 10731 } 10732 10733 return gridFrame; 10734 } 10735 10736 void nsGridContainerFrame::MarkCachedGridMeasurementsDirty( 10737 nsIFrame* aItemFrame) { 10738 MOZ_ASSERT(aItemFrame->IsGridItem()); 10739 aItemFrame->RemoveProperty(CachedBAxisMeasurement::Prop()); 10740 } 10741 10742 // TODO: This is a rather dumb implementation of nsILineIterator, but it's 10743 // better than our pre-existing behavior. Ideally, we should probably use the 10744 // grid information to return a meaningful number of lines etc. 10745 bool nsGridContainerFrame::IsLineIteratorFlowRTL() { return false; } 10746 10747 int32_t nsGridContainerFrame::GetNumLines() const { 10748 return mFrames.GetLength(); 10749 } 10750 10751 Result<nsILineIterator::LineInfo, nsresult> nsGridContainerFrame::GetLine( 10752 int32_t aLineNumber) { 10753 if (aLineNumber < 0 || aLineNumber >= GetNumLines()) { 10754 return Err(NS_ERROR_FAILURE); 10755 } 10756 LineInfo rv; 10757 nsIFrame* f = mFrames.FrameAt(aLineNumber); 10758 rv.mLineBounds = f->GetRect(); 10759 rv.mFirstFrameOnLine = f; 10760 rv.mNumFramesOnLine = 1; 10761 return rv; 10762 } 10763 10764 int32_t nsGridContainerFrame::FindLineContaining(const nsIFrame* aFrame, 10765 int32_t aStartLine) { 10766 const int32_t index = mFrames.IndexOf(aFrame); 10767 if (index < 0) { 10768 return -1; 10769 } 10770 if (index < aStartLine) { 10771 return -1; 10772 } 10773 return index; 10774 } 10775 10776 NS_IMETHODIMP 10777 nsGridContainerFrame::CheckLineOrder(int32_t aLine, bool* aIsReordered, 10778 nsIFrame** aFirstVisual, 10779 nsIFrame** aLastVisual) { 10780 *aIsReordered = false; 10781 *aFirstVisual = nullptr; 10782 *aLastVisual = nullptr; 10783 return NS_OK; 10784 } 10785 10786 NS_IMETHODIMP 10787 nsGridContainerFrame::FindFrameAt(int32_t aLineNumber, nsPoint aPos, 10788 nsIFrame** aFrameFound, 10789 bool* aPosIsBeforeFirstFrame, 10790 bool* aPosIsAfterLastFrame) { 10791 const auto wm = GetWritingMode(); 10792 const LogicalPoint pos(wm, aPos, GetSize()); 10793 10794 *aFrameFound = nullptr; 10795 *aPosIsBeforeFirstFrame = true; 10796 *aPosIsAfterLastFrame = false; 10797 10798 nsIFrame* f = mFrames.FrameAt(aLineNumber); 10799 if (!f) { 10800 return NS_OK; 10801 } 10802 10803 auto rect = f->GetLogicalRect(wm, GetSize()); 10804 *aFrameFound = f; 10805 *aPosIsBeforeFirstFrame = pos.I(wm) < rect.IStart(wm); 10806 *aPosIsAfterLastFrame = pos.I(wm) > rect.IEnd(wm); 10807 return NS_OK; 10808 } 10809 10810 void nsGridContainerFrame::TrackPlan::Initialize(TrackSizingPhase aPhase, 10811 const Tracks& aTracks) { 10812 MOZ_ASSERT(mTrackSizes.Length() == aTracks.mSizes.Length()); 10813 auto plan = mTrackSizes.begin(); 10814 auto sz = aTracks.mSizes.begin(); 10815 for (; plan != mTrackSizes.end() && sz != aTracks.mSizes.end(); 10816 plan++, sz++) { 10817 plan->mBase = Tracks::StartSizeInDistribution(aPhase, *sz); 10818 MOZ_ASSERT(aPhase == TrackSizingPhase::MaxContentMaximums || 10819 !(sz->mState & TrackSize::eInfinitelyGrowable), 10820 "forgot to reset the eInfinitelyGrowable bit?"); 10821 plan->mState = sz->mState; 10822 } 10823 } 10824 10825 // Distribute space to all flex tracks this item spans. 10826 // https://drafts.csswg.org/css-grid-2/#algo-spanning-flex-items 10827 nscoord nsGridContainerFrame::TrackPlan::DistributeToFlexTrackSizes( 10828 nscoord aAvailableSpace, const nsTArray<uint32_t>& aGrowableTracks, 10829 const TrackSizingFunctions& aFunctions, 10830 const nsGridContainerFrame::Tracks& aTracks) { 10831 nscoord space = aAvailableSpace; 10832 // Measure used fraction. 10833 double totalFr = 0.0; 10834 // TODO alaskanemily: we should be subtracting definite-sized tracks from 10835 // the available space below. 10836 for (uint32_t track : aGrowableTracks) { 10837 MOZ_ASSERT(aTracks.mSizes[track].mState & TrackSize::eFlexMaxSizing, 10838 "Only flex-sized tracks should be growable during step 4"); 10839 totalFr += aFunctions.MaxSizingFor(track).AsFr(); 10840 } 10841 MOZ_ASSERT(totalFr >= 0.0, "flex fractions must be non-negative."); 10842 10843 double frSize = aAvailableSpace; 10844 if (totalFr > 1.0) { 10845 frSize /= totalFr; 10846 } 10847 // Distribute the space to the tracks proportionally to the fractional 10848 // sizes. 10849 for (uint32_t track : aGrowableTracks) { 10850 TrackSize& sz = mTrackSizes[track]; 10851 if (sz.IsFrozen()) { 10852 continue; 10853 } 10854 const double trackFr = aFunctions.MaxSizingFor(track).AsFr(); 10855 nscoord size = NSToCoordRoundWithClamp(frSize * trackFr); 10856 // This shouldn't happen in theory, but it could happen due to a 10857 // combination of floating-point error during the multiplication above 10858 // and loss of precision in the cast. 10859 if (MOZ_UNLIKELY(size > space)) { 10860 size = space; 10861 space = 0; 10862 } else { 10863 space -= size; 10864 } 10865 sz.mBase = std::max(sz.mBase, size); 10866 } 10867 return space; 10868 } 10869 10870 void nsGridContainerFrame::ItemPlan::Initialize( 10871 TrackSizingPhase aPhase, const nsTArray<uint32_t>& aGrowableTracks, 10872 const nsGridContainerFrame::Tracks& aTracks) { 10873 for (uint32_t track : aGrowableTracks) { 10874 auto& plan = mTrackSizes[track]; 10875 const TrackSize& sz = aTracks.mSizes[track]; 10876 plan.mBase = Tracks::StartSizeInDistribution(aPhase, sz); 10877 bool unlimited = sz.mState & TrackSize::eInfinitelyGrowable; 10878 plan.mLimit = unlimited ? NS_UNCONSTRAINEDSIZE : sz.mLimit; 10879 plan.mState = sz.mState; 10880 } 10881 } 10882 10883 nscoord nsGridContainerFrame::ItemPlan::GrowTracksToLimit( 10884 nscoord aAvailableSpace, const nsTArray<uint32_t>& aGrowableTracks, 10885 const FitContentClamper& aFitContentClamper) { 10886 MOZ_ASSERT(aAvailableSpace > 0 && aGrowableTracks.Length() > 0); 10887 nscoord space = aAvailableSpace; 10888 uint32_t numGrowable = aGrowableTracks.Length(); 10889 while (true) { 10890 nscoord spacePerTrack = std::max<nscoord>(space / numGrowable, 1); 10891 for (uint32_t track : aGrowableTracks) { 10892 TrackSize& sz = mTrackSizes[track]; 10893 if (sz.IsFrozen()) { 10894 continue; 10895 } 10896 nscoord newBase = sz.mBase + spacePerTrack; 10897 nscoord limit = sz.mLimit; 10898 if (MOZ_UNLIKELY((sz.mState & TrackSize::eApplyFitContentClamping) && 10899 aFitContentClamper)) { 10900 // Clamp the limit to the fit-content() size, for §12.5.2 step 5/6. 10901 aFitContentClamper(track, sz.mBase, &limit); 10902 } 10903 if (newBase > limit) { 10904 nscoord consumed = limit - sz.mBase; 10905 if (consumed > 0) { 10906 space -= consumed; 10907 sz.mBase = limit; 10908 } 10909 sz.mState |= TrackSize::eFrozen; 10910 if (--numGrowable == 0) { 10911 return space; 10912 } 10913 } else { 10914 sz.mBase = newBase; 10915 space -= spacePerTrack; 10916 } 10917 MOZ_ASSERT(space >= 0); 10918 if (space == 0) { 10919 return 0; 10920 } 10921 } 10922 } 10923 MOZ_ASSERT_UNREACHABLE("we don't exit the loop above except by return"); 10924 return 0; 10925 } 10926 10927 uint32_t nsGridContainerFrame::ItemPlan::MarkExcludedTracks( 10928 TrackSizingPhase aPhase, const nsTArray<uint32_t>& aGrowableTracks, 10929 SizingConstraint aConstraint) { 10930 uint32_t numGrowable = aGrowableTracks.Length(); 10931 if (aPhase == TrackSizingPhase::IntrinsicMaximums || 10932 aPhase == TrackSizingPhase::MaxContentMaximums) { 10933 // "when handling any intrinsic growth limit: all affected tracks" 10934 return numGrowable; 10935 } 10936 10937 TrackSize::StateBits selector = Tracks::SelectorForPhase(aPhase, aConstraint); 10938 numGrowable = MarkExcludedTracks( 10939 numGrowable, aGrowableTracks, TrackSize::eMaxContentMinSizing, 10940 TrackSize::eMaxContentMaxSizing, TrackSize::eSkipGrowUnlimited1); 10941 // Note that eMaxContentMinSizing is always included. We do those first: 10942 if ((selector &= ~TrackSize::eMaxContentMinSizing)) { 10943 numGrowable = MarkExcludedTracks(numGrowable, aGrowableTracks, selector, 10944 TrackSize::eIntrinsicMaxSizing, 10945 TrackSize::eSkipGrowUnlimited2); 10946 } 10947 return numGrowable; 10948 } 10949 10950 uint32_t nsGridContainerFrame::ItemPlan::MarkExcludedTracks( 10951 uint32_t aNumGrowable, const nsTArray<uint32_t>& aGrowableTracks, 10952 TrackSize::StateBits aMinSizingSelector, 10953 TrackSize::StateBits aMaxSizingSelector, TrackSize::StateBits aSkipFlag) { 10954 bool foundOneSelected = false; 10955 bool foundOneGrowable = false; 10956 uint32_t numGrowable = aNumGrowable; 10957 for (uint32_t track : aGrowableTracks) { 10958 TrackSize& sz = mTrackSizes[track]; 10959 const auto state = sz.mState; 10960 if (state & aMinSizingSelector) { 10961 foundOneSelected = true; 10962 if (state & aMaxSizingSelector) { 10963 foundOneGrowable = true; 10964 continue; 10965 } 10966 sz.mState |= aSkipFlag; 10967 MOZ_ASSERT(numGrowable != 0); 10968 --numGrowable; 10969 } 10970 } 10971 // 12.5 "if there are no such tracks, then all affected tracks" 10972 if (foundOneSelected && !foundOneGrowable) { 10973 for (uint32_t track : aGrowableTracks) { 10974 mTrackSizes[track].mState &= ~aSkipFlag; 10975 } 10976 numGrowable = aNumGrowable; 10977 } 10978 return numGrowable; 10979 } 10980 10981 void nsGridContainerFrame::ItemPlan::GrowSelectedTracksUnlimited( 10982 nscoord aAvailableSpace, const nsTArray<uint32_t>& aGrowableTracks, 10983 uint32_t aNumGrowable, const FitContentClamper& aFitContentClamper) { 10984 MOZ_ASSERT(aAvailableSpace > 0 && aGrowableTracks.Length() > 0 && 10985 aNumGrowable <= aGrowableTracks.Length()); 10986 nscoord space = aAvailableSpace; 10987 DebugOnly<bool> didClamp = false; 10988 while (aNumGrowable) { 10989 nscoord spacePerTrack = std::max<nscoord>(space / aNumGrowable, 1); 10990 for (uint32_t track : aGrowableTracks) { 10991 TrackSize& sz = mTrackSizes[track]; 10992 if (sz.mState & TrackSize::eSkipGrowUnlimited) { 10993 continue; // an excluded track 10994 } 10995 nscoord delta = spacePerTrack; 10996 nscoord newBase = sz.mBase + delta; 10997 if (MOZ_UNLIKELY((sz.mState & TrackSize::eApplyFitContentClamping) && 10998 aFitContentClamper)) { 10999 // Clamp newBase to the fit-content() size, for §12.5.2 step 5/6. 11000 if (aFitContentClamper(track, sz.mBase, &newBase)) { 11001 didClamp = true; 11002 delta = newBase - sz.mBase; 11003 MOZ_ASSERT(delta >= 0, "track size shouldn't shrink"); 11004 sz.mState |= TrackSize::eSkipGrowUnlimited1; 11005 --aNumGrowable; 11006 } 11007 } 11008 sz.mBase = newBase; 11009 space -= delta; 11010 MOZ_ASSERT(space >= 0); 11011 if (space == 0) { 11012 return; 11013 } 11014 } 11015 } 11016 MOZ_ASSERT(didClamp, 11017 "we don't exit the loop above except by return, " 11018 "unless we clamped some track's size"); 11019 }