nsFlexContainerFrame.cpp (293902B)
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: flex" */ 8 9 #include "nsFlexContainerFrame.h" 10 11 #include <algorithm> 12 13 #include "gfxContext.h" 14 #include "mozilla/Baseline.h" 15 #include "mozilla/CSSOrderAwareFrameIterator.h" 16 #include "mozilla/ComputedStyle.h" 17 #include "mozilla/Logging.h" 18 #include "mozilla/PresShell.h" 19 #include "mozilla/StaticPrefs_layout.h" 20 #include "mozilla/WritingModes.h" 21 #include "nsBlockFrame.h" 22 #include "nsContentUtils.h" 23 #include "nsDebug.h" 24 #include "nsDisplayList.h" 25 #include "nsFieldSetFrame.h" 26 #include "nsIFrameInlines.h" 27 #include "nsLayoutUtils.h" 28 #include "nsPlaceholderFrame.h" 29 #include "nsPresContext.h" 30 31 using namespace mozilla; 32 using namespace mozilla::layout; 33 34 // Convenience typedefs for helper classes that we forward-declare in .h file 35 // (so that nsFlexContainerFrame methods can use them as parameters): 36 using FlexItem = nsFlexContainerFrame::FlexItem; 37 using FlexLine = nsFlexContainerFrame::FlexLine; 38 using FlexboxAxisTracker = nsFlexContainerFrame::FlexboxAxisTracker; 39 using StrutInfo = nsFlexContainerFrame::StrutInfo; 40 using CachedBAxisMeasurement = nsFlexContainerFrame::CachedBAxisMeasurement; 41 using CachedFlexItemData = nsFlexContainerFrame::CachedFlexItemData; 42 43 static mozilla::LazyLogModule gFlexContainerLog("FlexContainer"); 44 45 // FLEX_LOG is a top-level general log print. 46 #define FLEX_LOG(message, ...) \ 47 MOZ_LOG(gFlexContainerLog, LogLevel::Debug, (message, ##__VA_ARGS__)); 48 49 // FLEX_ITEM_LOG is a top-level log print for flex item. 50 #define FLEX_ITEM_LOG(item_frame, message, ...) \ 51 MOZ_LOG(gFlexContainerLog, LogLevel::Debug, \ 52 ("Flex item %p: " message, item_frame, ##__VA_ARGS__)); 53 54 // FLEX_LOGV is a verbose log print with built-in two spaces indentation. The 55 // convention to use FLEX_LOGV is that FLEX_LOGV statements should generally be 56 // preceded by one FLEX_LOG or FLEX_ITEM_LOG so that there's no need to repeat 57 // information presented in the preceding LOG statement. If you want extra level 58 // of indentation, just add two extra spaces at the start of the message string. 59 #define FLEX_LOGV(message, ...) \ 60 MOZ_LOG(gFlexContainerLog, LogLevel::Verbose, (" " message, ##__VA_ARGS__)); 61 62 // Returns the OrderState enum we should pass to CSSOrderAwareFrameIterator 63 // (depending on whether aFlexContainer has 64 // NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER state bit). 65 static CSSOrderAwareFrameIterator::OrderState OrderStateForIter( 66 const nsFlexContainerFrame* aFlexContainer) { 67 return aFlexContainer->HasAnyStateBits( 68 NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER) 69 ? CSSOrderAwareFrameIterator::OrderState::Ordered 70 : CSSOrderAwareFrameIterator::OrderState::Unordered; 71 } 72 73 // Returns the OrderingProperty enum that we should pass to 74 // CSSOrderAwareFrameIterator (depending on whether it's a legacy box). 75 static CSSOrderAwareFrameIterator::OrderingProperty OrderingPropertyForIter( 76 const nsFlexContainerFrame* aFlexContainer) { 77 return aFlexContainer->IsLegacyWebkitBox() 78 ? CSSOrderAwareFrameIterator::OrderingProperty::BoxOrdinalGroup 79 : CSSOrderAwareFrameIterator::OrderingProperty::Order; 80 } 81 82 // Returns the "align-items" value that's equivalent to the legacy "box-align" 83 // value in the given style struct. 84 static StyleAlignFlags ConvertLegacyStyleToAlignItems( 85 const nsStyleXUL* aStyleXUL) { 86 // -[moz|webkit]-box-align corresponds to modern "align-items" 87 switch (aStyleXUL->mBoxAlign) { 88 case StyleBoxAlign::Stretch: 89 return StyleAlignFlags::STRETCH; 90 case StyleBoxAlign::Start: 91 return StyleAlignFlags::FLEX_START; 92 case StyleBoxAlign::Center: 93 return StyleAlignFlags::CENTER; 94 case StyleBoxAlign::Baseline: 95 return StyleAlignFlags::BASELINE; 96 case StyleBoxAlign::End: 97 return StyleAlignFlags::FLEX_END; 98 } 99 100 MOZ_ASSERT_UNREACHABLE("Unrecognized mBoxAlign enum value"); 101 // Fall back to default value of "align-items" property: 102 return StyleAlignFlags::STRETCH; 103 } 104 105 // Returns the "justify-content" value that's equivalent to the legacy 106 // "box-pack" value in the given style struct. 107 static StyleContentDistribution ConvertLegacyStyleToJustifyContent( 108 const nsStyleXUL* aStyleXUL) { 109 // -[moz|webkit]-box-pack corresponds to modern "justify-content" 110 switch (aStyleXUL->mBoxPack) { 111 case StyleBoxPack::Start: 112 return {StyleAlignFlags::FLEX_START}; 113 case StyleBoxPack::Center: 114 return {StyleAlignFlags::CENTER}; 115 case StyleBoxPack::End: 116 return {StyleAlignFlags::FLEX_END}; 117 case StyleBoxPack::Justify: 118 return {StyleAlignFlags::SPACE_BETWEEN}; 119 } 120 121 MOZ_ASSERT_UNREACHABLE("Unrecognized mBoxPack enum value"); 122 // Fall back to default value of "justify-content" property: 123 return {StyleAlignFlags::FLEX_START}; 124 } 125 126 // Check if the size is auto or it is a keyword in the block axis. 127 // |aIsInline| should represent whether aSize is in the inline axis, from the 128 // perspective of the writing mode of the flex item that the size comes from. 129 // 130 // max-content and min-content should behave as property's initial value. 131 // Bug 567039: We treat -moz-fit-content and -moz-available as property's 132 // initial value for now. 133 static inline bool IsAutoOrEnumOnBSize(const StyleSize& aSize, bool aIsInline) { 134 return aSize.IsAuto() || (!aIsInline && !aSize.IsLengthPercentage()); 135 } 136 137 // Returns true if the flex container should be treated as a single-line 138 // container. 139 static bool IsSingleLine(const nsIFrame* aFlexContainer, 140 const nsStylePosition* aStylePos) { 141 MOZ_ASSERT(aFlexContainer->IsFlexContainerFrame()); 142 143 if (aFlexContainer->IsLegacyWebkitBox()) { 144 // For legacy -webkit-{inline-}box, ignore the flex-wrap property. 145 // These containers are always treated as single-line. 146 return true; 147 } 148 return aStylePos->mFlexWrap == StyleFlexWrap::Nowrap; 149 } 150 151 // Encapsulates our flex container's main & cross axes. This class is backed by 152 // a FlexboxAxisInfo helper member variable, and it adds some convenience APIs 153 // on top of what that struct offers. 154 class MOZ_STACK_CLASS nsFlexContainerFrame::FlexboxAxisTracker { 155 public: 156 explicit FlexboxAxisTracker(const nsFlexContainerFrame* aFlexContainer); 157 158 // Accessors: 159 LogicalAxis MainAxis() const { 160 return IsRowOriented() ? LogicalAxis::Inline : LogicalAxis::Block; 161 } 162 LogicalAxis CrossAxis() const { 163 return IsRowOriented() ? LogicalAxis::Block : LogicalAxis::Inline; 164 } 165 166 LogicalSide MainAxisStartSide() const; 167 LogicalSide MainAxisEndSide() const { 168 return GetOppositeSide(MainAxisStartSide()); 169 } 170 171 LogicalSide CrossAxisStartSide() const; 172 LogicalSide CrossAxisEndSide() const { 173 return GetOppositeSide(CrossAxisStartSide()); 174 } 175 176 mozilla::Side MainAxisPhysicalStartSide() const { 177 return mWM.PhysicalSide(MainAxisStartSide()); 178 } 179 mozilla::Side MainAxisPhysicalEndSide() const { 180 return mWM.PhysicalSide(MainAxisEndSide()); 181 } 182 183 mozilla::Side CrossAxisPhysicalStartSide() const { 184 return mWM.PhysicalSide(CrossAxisStartSide()); 185 } 186 mozilla::Side CrossAxisPhysicalEndSide() const { 187 return mWM.PhysicalSide(CrossAxisEndSide()); 188 } 189 190 // Returns the flex container's writing mode. 191 WritingMode GetWritingMode() const { return mWM; } 192 193 // Returns true if our main axis is in the reverse direction of our 194 // writing mode's corresponding axis. (From 'flex-direction: *-reverse') 195 bool IsMainAxisReversed() const { return mAxisInfo.mIsMainAxisReversed; } 196 // Returns true if our cross axis is in the reverse direction of our 197 // writing mode's corresponding axis. (From 'flex-wrap: *-reverse') 198 bool IsCrossAxisReversed() const { return mAxisInfo.mIsCrossAxisReversed; } 199 200 bool IsRowOriented() const { return mAxisInfo.mIsRowOriented; } 201 bool IsColumnOriented() const { return !IsRowOriented(); } 202 203 // aSize is expected to match the flex container's WritingMode. 204 nscoord MainComponent(const LogicalSize& aSize) const { 205 return IsRowOriented() ? aSize.ISize(mWM) : aSize.BSize(mWM); 206 } 207 int32_t MainComponent(const LayoutDeviceIntSize& aIntSize) const { 208 return IsMainAxisHorizontal() ? aIntSize.width : aIntSize.height; 209 } 210 211 // aSize is expected to match the flex container's WritingMode. 212 nscoord CrossComponent(const LogicalSize& aSize) const { 213 return IsRowOriented() ? aSize.BSize(mWM) : aSize.ISize(mWM); 214 } 215 int32_t CrossComponent(const LayoutDeviceIntSize& aIntSize) const { 216 return IsMainAxisHorizontal() ? aIntSize.height : aIntSize.width; 217 } 218 219 // NOTE: aMargin is expected to use the flex container's WritingMode. 220 nscoord MarginSizeInMainAxis(const LogicalMargin& aMargin) const { 221 // If we're row-oriented, our main axis is the inline axis. 222 return IsRowOriented() ? aMargin.IStartEnd(mWM) : aMargin.BStartEnd(mWM); 223 } 224 nscoord MarginSizeInCrossAxis(const LogicalMargin& aMargin) const { 225 // If we're row-oriented, our cross axis is the block axis. 226 return IsRowOriented() ? aMargin.BStartEnd(mWM) : aMargin.IStartEnd(mWM); 227 } 228 229 /** 230 * Converts a "flex-relative" point (a main-axis & cross-axis coordinate) 231 * into a LogicalPoint, using the flex container's writing mode. 232 * 233 * @arg aMainCoord The main-axis coordinate -- i.e an offset from the 234 * main-start edge of the flex container's content box. 235 * @arg aCrossCoord The cross-axis coordinate -- i.e an offset from the 236 * cross-start edge of the flex container's content box. 237 * @arg aContainerMainSize The main size of flex container's content box. 238 * @arg aContainerCrossSize The cross size of flex container's content box. 239 * @return A LogicalPoint, with the flex container's writing mode, that 240 * represents the same position. The logical coordinates are 241 * relative to the flex container's content box. 242 */ 243 LogicalPoint LogicalPointFromFlexRelativePoint( 244 nscoord aMainCoord, nscoord aCrossCoord, nscoord aContainerMainSize, 245 nscoord aContainerCrossSize) const { 246 nscoord logicalCoordInMainAxis = 247 IsMainAxisReversed() ? aContainerMainSize - aMainCoord : aMainCoord; 248 nscoord logicalCoordInCrossAxis = 249 IsCrossAxisReversed() ? aContainerCrossSize - aCrossCoord : aCrossCoord; 250 251 return IsRowOriented() ? LogicalPoint(mWM, logicalCoordInMainAxis, 252 logicalCoordInCrossAxis) 253 : LogicalPoint(mWM, logicalCoordInCrossAxis, 254 logicalCoordInMainAxis); 255 } 256 257 /** 258 * Converts a "flex-relative" size (a main-axis & cross-axis size) 259 * into a LogicalSize, using the flex container's writing mode. 260 * 261 * @arg aMainSize The main-axis size. 262 * @arg aCrossSize The cross-axis size. 263 * @return A LogicalSize, with the flex container's writing mode, that 264 * represents the same size. 265 */ 266 LogicalSize LogicalSizeFromFlexRelativeSizes(nscoord aMainSize, 267 nscoord aCrossSize) const { 268 return IsRowOriented() ? LogicalSize(mWM, aMainSize, aCrossSize) 269 : LogicalSize(mWM, aCrossSize, aMainSize); 270 } 271 272 /** 273 * Converts a "flex-relative" ascent (the distance from the flex container's 274 * content-box cross-start edge to its baseline) into a logical ascent (the 275 * distance from the flex container's content-box block-start edge to its 276 * baseline). 277 */ 278 nscoord LogicalAscentFromFlexRelativeAscent( 279 nscoord aFlexRelativeAscent, nscoord aContentBoxCrossSize) const { 280 return (IsCrossAxisReversed() ? aContentBoxCrossSize - aFlexRelativeAscent 281 : aFlexRelativeAscent); 282 } 283 284 bool IsMainAxisHorizontal() const { 285 // If we're row-oriented, and our writing mode is NOT vertical, 286 // or we're column-oriented and our writing mode IS vertical, 287 // then our main axis is horizontal. This handles all cases: 288 return IsRowOriented() != mWM.IsVertical(); 289 } 290 291 // Returns true if this flex item's inline axis in aItemWM is parallel (or 292 // antiparallel) to the container's main axis. Returns false, otherwise. 293 // 294 // Note: this is a helper used before constructing FlexItem. Inside of flex 295 // reflow code, FlexItem::IsInlineAxisMainAxis() is equivalent & more optimal. 296 bool IsInlineAxisMainAxis(WritingMode aItemWM) const { 297 return IsRowOriented() != GetWritingMode().IsOrthogonalTo(aItemWM); 298 } 299 300 // Maps justify-*: 'left' or 'right' to 'start' or 'end'. 301 StyleAlignFlags ResolveJustifyLeftRight(const StyleAlignFlags& aFlags) const { 302 MOZ_ASSERT( 303 aFlags == StyleAlignFlags::LEFT || aFlags == StyleAlignFlags::RIGHT, 304 "This helper accepts only 'LEFT' or 'RIGHT' flags!"); 305 306 const auto wm = GetWritingMode(); 307 const bool isJustifyLeft = aFlags == StyleAlignFlags::LEFT; 308 if (IsColumnOriented()) { 309 if (!wm.IsVertical()) { 310 // Container's alignment axis (main axis) is *not* parallel to the 311 // line-left <-> line-right axis or the physical left <-> physical right 312 // axis, so we map both 'left' and 'right' to 'start'. 313 return StyleAlignFlags::START; 314 } 315 316 MOZ_ASSERT(wm.PhysicalAxis(MainAxis()) == PhysicalAxis::Horizontal, 317 "Vertical column-oriented flex container's main axis should " 318 "be parallel to physical left <-> right axis!"); 319 // Map 'left' or 'right' to 'start' or 'end', depending on its block flow 320 // direction. 321 return isJustifyLeft == wm.IsVerticalLR() ? StyleAlignFlags::START 322 : StyleAlignFlags::END; 323 } 324 325 MOZ_ASSERT(MainAxis() == LogicalAxis::Inline, 326 "Row-oriented flex container's main axis should be parallel to " 327 "line-left <-> line-right axis!"); 328 329 // If we get here, we're operating on the flex container's inline axis, 330 // so we map 'left' to whichever of 'start' or 'end' corresponds to the 331 // *line-relative* left side; and similar for 'right'. 332 return isJustifyLeft == wm.IsBidiLTR() ? StyleAlignFlags::START 333 : StyleAlignFlags::END; 334 } 335 336 // Delete copy-constructor & reassignment operator, to prevent accidental 337 // (unnecessary) copying. 338 FlexboxAxisTracker(const FlexboxAxisTracker&) = delete; 339 FlexboxAxisTracker& operator=(const FlexboxAxisTracker&) = delete; 340 341 private: 342 const WritingMode mWM; // The flex container's writing mode. 343 const FlexboxAxisInfo mAxisInfo; 344 }; 345 346 /** 347 * Represents a flex item. 348 * Includes the various pieces of input that the Flexbox Layout Algorithm uses 349 * to resolve a flexible width. 350 */ 351 class nsFlexContainerFrame::FlexItem final { 352 public: 353 // Normal constructor: 354 FlexItem(ReflowInput& aFlexItemReflowInput, float aFlexGrow, 355 float aFlexShrink, nscoord aFlexBaseSize, nscoord aMainMinSize, 356 nscoord aMainMaxSize, nscoord aTentativeCrossSize, 357 nscoord aCrossMinSize, nscoord aCrossMaxSize, 358 const FlexboxAxisTracker& aAxisTracker); 359 360 // Simplified constructor, to be used only for generating "struts": 361 // (NOTE: This "strut" constructor uses the *container's* writing mode, which 362 // we'll use on this FlexItem instead of the child frame's real writing mode. 363 // This is fine - it doesn't matter what writing mode we use for a 364 // strut, since it won't render any content and we already know its size.) 365 FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize, WritingMode aContainerWM, 366 const FlexboxAxisTracker& aAxisTracker); 367 368 // Clone existing FlexItem for its underlying frame's continuation. 369 // @param aContinuation a continuation in our next-in-flow chain. 370 FlexItem CloneFor(nsIFrame* const aContinuation) const { 371 MOZ_ASSERT(Frame() == aContinuation->FirstInFlow(), 372 "aContinuation should be in aItem's continuation chain!"); 373 FlexItem item(*this); 374 item.mFrame = aContinuation; 375 item.mHadMeasuringReflow = false; 376 return item; 377 } 378 379 // Accessors 380 nsIFrame* Frame() const { return mFrame; } 381 nscoord FlexBaseSize() const { return mFlexBaseSize; } 382 383 nscoord MainMinSize() const { 384 MOZ_ASSERT(!mNeedsMinSizeAutoResolution, 385 "Someone's using an unresolved 'auto' main min-size"); 386 return mMainMinSize; 387 } 388 nscoord MainMaxSize() const { return mMainMaxSize; } 389 390 // Note: These return the main-axis position and size of our *content box*. 391 nscoord MainSize() const { return mMainSize; } 392 nscoord MainPosition() const { return mMainPosn; } 393 394 nscoord CrossMinSize() const { return mCrossMinSize; } 395 nscoord CrossMaxSize() const { return mCrossMaxSize; } 396 397 // Note: These return the cross-axis position and size of our *content box*. 398 nscoord CrossSize() const { return mCrossSize; } 399 nscoord CrossPosition() const { return mCrossPosn; } 400 401 // Lazy getter for mAscent or mAscentForLast. 402 nscoord ResolvedAscent(bool aUseFirstBaseline) const { 403 // XXX We should be using the *container's* writing-mode (mCBWM) here, 404 // instead of the item's (mWM). This is essentially bug 1155322. 405 nscoord& ascent = aUseFirstBaseline ? mAscent : mAscentForLast; 406 if (ascent != ReflowOutput::ASK_FOR_BASELINE) { 407 return ascent; 408 } 409 410 // Use GetFirstLineBaseline() or GetLastLineBaseline() as appropriate: 411 bool found = aUseFirstBaseline 412 ? nsLayoutUtils::GetFirstLineBaseline(mWM, mFrame, &ascent) 413 : nsLayoutUtils::GetLastLineBaseline(mWM, mFrame, &ascent); 414 if (found) { 415 return ascent; 416 } 417 418 // If the nsLayoutUtils getter fails, then ask the frame directly: 419 auto baselineGroup = aUseFirstBaseline ? BaselineSharingGroup::First 420 : BaselineSharingGroup::Last; 421 if (auto baseline = mFrame->GetNaturalBaselineBOffset( 422 mWM, baselineGroup, BaselineExportContext::Other)) { 423 // Offset for last baseline from `GetNaturalBaselineBOffset` originates 424 // from the frame's block end, so convert it back. 425 ascent = baselineGroup == BaselineSharingGroup::First 426 ? *baseline 427 : mFrame->BSize(mWM) - *baseline; 428 return ascent; 429 } 430 431 // We couldn't determine a baseline, so we synthesize one from border box: 432 ascent = Baseline::SynthesizeBOffsetFromBorderBox( 433 mFrame, mWM, BaselineSharingGroup::First); 434 return ascent; 435 } 436 437 // Convenience methods to compute the main & cross size of our *margin-box*. 438 nscoord OuterMainSize() const { 439 return mMainSize + MarginBorderPaddingSizeInMainAxis(); 440 } 441 442 nscoord OuterCrossSize() const { 443 return mCrossSize + MarginBorderPaddingSizeInCrossAxis(); 444 } 445 446 // Convenience method to return the content-box block-size. 447 nscoord BSize() const { 448 return IsBlockAxisMainAxis() ? MainSize() : CrossSize(); 449 } 450 451 // Convenience method to return the measured content-box block-size computed 452 // in nsFlexContainerFrame::MeasureBSizeForFlexItem(). 453 Maybe<nscoord> MeasuredBSize() const; 454 455 // Convenience methods to synthesize a style main size or a style cross size 456 // with box-size considered, to provide the size overrides when constructing 457 // ReflowInput for flex items. 458 StyleSize StyleMainSize() const { 459 nscoord mainSize = MainSize(); 460 if (Frame()->StylePosition()->mBoxSizing == StyleBoxSizing::Border) { 461 mainSize += BorderPaddingSizeInMainAxis(); 462 } 463 return StyleSize::LengthPercentage( 464 LengthPercentage::FromAppUnits(mainSize)); 465 } 466 467 StyleSize StyleCrossSize() const { 468 nscoord crossSize = CrossSize(); 469 if (Frame()->StylePosition()->mBoxSizing == StyleBoxSizing::Border) { 470 crossSize += BorderPaddingSizeInCrossAxis(); 471 } 472 return StyleSize::LengthPercentage( 473 LengthPercentage::FromAppUnits(crossSize)); 474 } 475 476 // Returns the distance between this FlexItem's baseline and the cross-start 477 // edge of its margin-box. Used in baseline alignment. 478 // 479 // (This function needs to be told which physical start side we're measuring 480 // the baseline from, so that it can look up the appropriate components from 481 // margin.) 482 nscoord BaselineOffsetFromOuterCrossEdge(mozilla::Side aStartSide, 483 bool aUseFirstLineBaseline) const; 484 485 double ShareOfWeightSoFar() const { return mShareOfWeightSoFar; } 486 487 bool IsFrozen() const { return mIsFrozen; } 488 489 bool HadMinViolation() const { 490 MOZ_ASSERT(!mIsFrozen, "min violation has no meaning for frozen items."); 491 return mHadMinViolation; 492 } 493 494 bool HadMaxViolation() const { 495 MOZ_ASSERT(!mIsFrozen, "max violation has no meaning for frozen items."); 496 return mHadMaxViolation; 497 } 498 499 bool WasMinClamped() const { 500 MOZ_ASSERT(mIsFrozen, "min clamping has no meaning for unfrozen items."); 501 return mHadMinViolation; 502 } 503 504 bool WasMaxClamped() const { 505 MOZ_ASSERT(mIsFrozen, "max clamping has no meaning for unfrozen items."); 506 return mHadMaxViolation; 507 } 508 509 // Indicates whether this item received a preliminary "measuring" reflow 510 // before its actual reflow. 511 bool HadMeasuringReflow() const { return mHadMeasuringReflow; } 512 513 // Indicates whether this item's computed cross-size property is 'auto'. 514 bool IsCrossSizeAuto() const; 515 516 // Indicates whether the cross-size property is set to something definite, 517 // for the purpose of preferred aspect ratio calculations. 518 bool IsCrossSizeDefinite(const ReflowInput& aItemReflowInput) const; 519 520 // Indicates whether this item's cross-size has been stretched (from having 521 // "align-self: stretch" with an auto cross-size and no auto margins in the 522 // cross axis). 523 bool IsStretched() const { return mIsStretched; } 524 525 bool IsFlexBaseSizeContentBSize() const { 526 return mIsFlexBaseSizeContentBSize; 527 } 528 529 bool IsMainMinSizeContentBSize() const { return mIsMainMinSizeContentBSize; } 530 531 // Indicates whether we need to resolve an 'auto' value for the main-axis 532 // min-[width|height] property. 533 bool NeedsMinSizeAutoResolution() const { 534 return mNeedsMinSizeAutoResolution; 535 } 536 537 bool HasAnyAutoMargin() const { return mHasAnyAutoMargin; } 538 539 BaselineSharingGroup ItemBaselineSharingGroup() const { 540 MOZ_ASSERT(mAlignSelf == StyleAlignFlags::BASELINE || 541 mAlignSelf == StyleAlignFlags::LAST_BASELINE, 542 "mBaselineSharingGroup only gets a meaningful value " 543 "for baseline-aligned items"); 544 return mBaselineSharingGroup; 545 } 546 547 // Indicates whether this item is a "strut" left behind by an element with 548 // visibility:collapse. 549 bool IsStrut() const { return mIsStrut; } 550 551 // The main axis and cross axis are relative to mCBWM. 552 LogicalAxis MainAxis() const { return mMainAxis; } 553 LogicalAxis CrossAxis() const { return GetOrthogonalAxis(mMainAxis); } 554 555 // IsInlineAxisMainAxis() returns true if this item's inline axis is parallel 556 // (or antiparallel) to the container's main axis. Otherwise (i.e. if this 557 // item's inline axis is orthogonal to the container's main axis), this 558 // function returns false. The next 3 methods are all other ways of asking 559 // the same question, and only exist for readability at callsites (depending 560 // on which axes those callsites are reasoning about). 561 bool IsInlineAxisMainAxis() const { return mIsInlineAxisMainAxis; } 562 bool IsInlineAxisCrossAxis() const { return !mIsInlineAxisMainAxis; } 563 bool IsBlockAxisMainAxis() const { return !mIsInlineAxisMainAxis; } 564 bool IsBlockAxisCrossAxis() const { return mIsInlineAxisMainAxis; } 565 566 WritingMode GetWritingMode() const { return mWM; } 567 WritingMode ContainingBlockWM() const { return mCBWM; } 568 StyleAlignFlags AlignSelf() const { return mAlignSelf; } 569 StyleAlignFlags AlignSelfFlags() const { return mAlignSelfFlags; } 570 571 // Returns the flex factor (flex-grow or flex-shrink), depending on 572 // 'aIsUsingFlexGrow'. 573 // 574 // Asserts fatally if called on a frozen item (since frozen items are not 575 // flexible). 576 float GetFlexFactor(bool aIsUsingFlexGrow) { 577 MOZ_ASSERT(!IsFrozen(), "shouldn't need flex factor after item is frozen"); 578 579 return aIsUsingFlexGrow ? mFlexGrow : mFlexShrink; 580 } 581 582 // Returns the weight that we should use in the "resolving flexible lengths" 583 // algorithm. If we're using the flex grow factor, we just return that; 584 // otherwise, we return the "scaled flex shrink factor" (scaled by our flex 585 // base size, so that when both large and small items are shrinking, the large 586 // items shrink more). 587 // 588 // I'm calling this a "weight" instead of a "[scaled] flex-[grow|shrink] 589 // factor", to more clearly distinguish it from the actual flex-grow & 590 // flex-shrink factors. 591 // 592 // Asserts fatally if called on a frozen item (since frozen items are not 593 // flexible). 594 float GetWeight(bool aIsUsingFlexGrow) { 595 MOZ_ASSERT(!IsFrozen(), "shouldn't need weight after item is frozen"); 596 597 if (aIsUsingFlexGrow) { 598 return mFlexGrow; 599 } 600 601 // We're using flex-shrink --> return mFlexShrink * mFlexBaseSize 602 if (mFlexBaseSize == 0) { 603 // Special-case for mFlexBaseSize == 0 -- we have no room to shrink, so 604 // regardless of mFlexShrink, we should just return 0. 605 // (This is really a special-case for when mFlexShrink is infinity, to 606 // avoid performing mFlexShrink * mFlexBaseSize = inf * 0 = undefined.) 607 return 0.0f; 608 } 609 return mFlexShrink * mFlexBaseSize; 610 } 611 612 bool TreatBSizeAsIndefinite() const { return mTreatBSizeAsIndefinite; } 613 614 const AspectRatio& GetAspectRatio() const { return mAspectRatio; } 615 bool HasAspectRatio() const { return !!mAspectRatio; } 616 617 // Getters for margin: 618 // =================== 619 LogicalMargin Margin() const { return mMargin; } 620 nsMargin PhysicalMargin() const { return mMargin.GetPhysicalMargin(mCBWM); } 621 622 // Returns the margin component for a given LogicalSide in flex container's 623 // writing-mode. 624 nscoord GetMarginComponentForSide(LogicalSide aSide) const { 625 return mMargin.Side(aSide, mCBWM); 626 } 627 628 // Returns the total space occupied by this item's margins in the given axis 629 nscoord MarginSizeInMainAxis() const { 630 return mMargin.StartEnd(MainAxis(), mCBWM); 631 } 632 nscoord MarginSizeInCrossAxis() const { 633 return mMargin.StartEnd(CrossAxis(), mCBWM); 634 } 635 636 // Getters for border/padding 637 // ========================== 638 // Returns the total space occupied by this item's borders and padding in 639 // the given axis 640 LogicalMargin BorderPadding() const { return mBorderPadding; } 641 nscoord BorderPaddingSizeInMainAxis() const { 642 return mBorderPadding.StartEnd(MainAxis(), mCBWM); 643 } 644 nscoord BorderPaddingSizeInCrossAxis() const { 645 return mBorderPadding.StartEnd(CrossAxis(), mCBWM); 646 } 647 648 // Getter for combined margin/border/padding 649 // ========================================= 650 // Returns the total space occupied by this item's margins, borders and 651 // padding in the given axis 652 nscoord MarginBorderPaddingSizeInMainAxis() const { 653 return MarginSizeInMainAxis() + BorderPaddingSizeInMainAxis(); 654 } 655 nscoord MarginBorderPaddingSizeInCrossAxis() const { 656 return MarginSizeInCrossAxis() + BorderPaddingSizeInCrossAxis(); 657 } 658 659 // Setters 660 // ======= 661 // Helper to set the resolved value of min-[width|height]:auto for the main 662 // axis. (Should only be used if NeedsMinSizeAutoResolution() returns true.) 663 void UpdateMainMinSize(nscoord aNewMinSize) { 664 NS_ASSERTION(aNewMinSize >= 0, 665 "How did we end up with a negative min-size?"); 666 MOZ_ASSERT( 667 mMainMaxSize == NS_UNCONSTRAINEDSIZE || mMainMaxSize >= aNewMinSize, 668 "Should only use this function for resolving min-size:auto, " 669 "and main max-size should be an upper-bound for resolved val"); 670 MOZ_ASSERT( 671 mNeedsMinSizeAutoResolution && 672 (mMainMinSize == 0 || mFrame->IsThemed(mFrame->StyleDisplay())), 673 "Should only use this function for resolving min-size:auto, " 674 "so we shouldn't already have a nonzero min-size established " 675 "(unless it's a themed-widget-imposed minimum size)"); 676 677 if (aNewMinSize > mMainMinSize) { 678 mMainMinSize = aNewMinSize; 679 // Also clamp main-size to be >= new min-size: 680 mMainSize = std::max(mMainSize, aNewMinSize); 681 } 682 mNeedsMinSizeAutoResolution = false; 683 } 684 685 // This sets our flex base size, and then sets our main size to the 686 // resulting "hypothetical main size" (the base size clamped to our 687 // main-axis [min,max] sizing constraints). 688 void SetFlexBaseSizeAndMainSize(nscoord aNewFlexBaseSize) { 689 MOZ_ASSERT(!mIsFrozen || mFlexBaseSize == NS_UNCONSTRAINEDSIZE, 690 "flex base size shouldn't change after we're frozen " 691 "(unless we're just resolving an intrinsic size)"); 692 mFlexBaseSize = aNewFlexBaseSize; 693 694 // Before we've resolved flexible lengths, we keep mMainSize set to 695 // the 'hypothetical main size', which is the flex base size, clamped 696 // to the [min,max] range: 697 mMainSize = CSSMinMax(mFlexBaseSize, mMainMinSize, mMainMaxSize); 698 699 FLEX_ITEM_LOG(mFrame, "Set flex base size: %d, hypothetical main size: %d", 700 mFlexBaseSize, mMainSize); 701 } 702 703 // Setters used while we're resolving flexible lengths 704 // --------------------------------------------------- 705 706 // Sets the main-size of our flex item's content-box. 707 void SetMainSize(nscoord aNewMainSize) { 708 MOZ_ASSERT(!mIsFrozen, "main size shouldn't change after we're frozen"); 709 mMainSize = aNewMainSize; 710 } 711 712 void SetShareOfWeightSoFar(double aNewShare) { 713 MOZ_ASSERT(!mIsFrozen || aNewShare == 0.0, 714 "shouldn't be giving this item any share of the weight " 715 "after it's frozen"); 716 mShareOfWeightSoFar = aNewShare; 717 } 718 719 void Freeze() { 720 mIsFrozen = true; 721 // Now that we are frozen, the meaning of mHadMinViolation and 722 // mHadMaxViolation changes to indicate min and max clamping. Clear 723 // both of the member variables so that they are ready to be set 724 // as clamping state later, if necessary. 725 mHadMinViolation = false; 726 mHadMaxViolation = false; 727 } 728 729 void SetHadMinViolation() { 730 MOZ_ASSERT(!mIsFrozen, 731 "shouldn't be changing main size & having violations " 732 "after we're frozen"); 733 mHadMinViolation = true; 734 } 735 void SetHadMaxViolation() { 736 MOZ_ASSERT(!mIsFrozen, 737 "shouldn't be changing main size & having violations " 738 "after we're frozen"); 739 mHadMaxViolation = true; 740 } 741 void ClearViolationFlags() { 742 MOZ_ASSERT(!mIsFrozen, 743 "shouldn't be altering violation flags after we're " 744 "frozen"); 745 mHadMinViolation = mHadMaxViolation = false; 746 } 747 748 void SetWasMinClamped() { 749 MOZ_ASSERT(!mHadMinViolation && !mHadMaxViolation, "only clamp once"); 750 // This reuses the mHadMinViolation member variable to track clamping 751 // events. This is allowable because mHadMinViolation only reflects 752 // a violation up until the item is frozen. 753 MOZ_ASSERT(mIsFrozen, "shouldn't set clamping state when we are unfrozen"); 754 mHadMinViolation = true; 755 } 756 void SetWasMaxClamped() { 757 MOZ_ASSERT(!mHadMinViolation && !mHadMaxViolation, "only clamp once"); 758 // This reuses the mHadMaxViolation member variable to track clamping 759 // events. This is allowable because mHadMaxViolation only reflects 760 // a violation up until the item is frozen. 761 MOZ_ASSERT(mIsFrozen, "shouldn't set clamping state when we are unfrozen"); 762 mHadMaxViolation = true; 763 } 764 765 // Setters for values that are determined after we've resolved our main size 766 // ------------------------------------------------------------------------- 767 768 // Sets the main-axis position of our flex item's content-box. 769 // (This is the distance between the main-start edge of the flex container 770 // and the main-start edge of the flex item's content-box.) 771 void SetMainPosition(nscoord aPosn) { 772 MOZ_ASSERT(mIsFrozen, "main size should be resolved before this"); 773 mMainPosn = aPosn; 774 } 775 776 // Sets the cross-size of our flex item's content-box. 777 void SetCrossSize(nscoord aCrossSize) { 778 MOZ_ASSERT(!mIsStretched, 779 "Cross size shouldn't be modified after it's been stretched"); 780 mCrossSize = aCrossSize; 781 } 782 783 // Sets the cross-axis position of our flex item's content-box. 784 // (This is the distance between the cross-start edge of the flex container 785 // and the cross-start edge of the flex item.) 786 void SetCrossPosition(nscoord aPosn) { 787 MOZ_ASSERT(mIsFrozen, "main size should be resolved before this"); 788 mCrossPosn = aPosn; 789 } 790 791 // After a FlexItem has had a reflow, this method can be used to cache its 792 // (possibly-unresolved) ascent, in case it's needed later for 793 // baseline-alignment or to establish the container's baseline. 794 // (NOTE: This can be marked 'const' even though it's modifying mAscent, 795 // because mAscent is mutable. It's nice for this to be 'const', because it 796 // means our final reflow can iterate over const FlexItem pointers, and we 797 // can be sure it's not modifying those FlexItems, except via this method.) 798 void SetAscent(nscoord aAscent) const { 799 mAscent = aAscent; // NOTE: this may be ASK_FOR_BASELINE 800 } 801 802 void SetHadMeasuringReflow() { mHadMeasuringReflow = true; } 803 804 void SetIsFlexBaseSizeContentBSize() { mIsFlexBaseSizeContentBSize = true; } 805 806 void SetIsMainMinSizeContentBSize() { mIsMainMinSizeContentBSize = true; } 807 808 // Setter for margin components (for resolving "auto" margins) 809 void SetMarginComponentForSide(LogicalSide aSide, nscoord aLength) { 810 MOZ_ASSERT(mIsFrozen, "main size should be resolved before this"); 811 mMargin.Side(aSide, mCBWM) = aLength; 812 } 813 814 void ResolveStretchedCrossSize(nscoord aLineCrossSize); 815 816 // Resolves flex base size if flex-basis' used value is 'content', using this 817 // item's preferred aspect ratio and cross size. 818 void ResolveFlexBaseSizeFromAspectRatio(const ReflowInput& aItemReflowInput); 819 820 uint32_t NumAutoMarginsInMainAxis() const { 821 return NumAutoMarginsInAxis(MainAxis()); 822 }; 823 824 uint32_t NumAutoMarginsInCrossAxis() const { 825 return NumAutoMarginsInAxis(CrossAxis()); 826 }; 827 828 // Once the main size has been resolved, should we bother doing layout to 829 // establish the cross size? 830 bool CanMainSizeInfluenceCrossSize() const; 831 832 // Returns a main size, clamped by any definite min and max cross size 833 // converted through the preferred aspect ratio. The caller is responsible for 834 // ensuring that the flex item's preferred aspect ratio is not zero. 835 nscoord ClampMainSizeViaCrossAxisConstraints( 836 nscoord aMainSize, const ReflowInput& aItemReflowInput) const; 837 838 // Indicates whether we think this flex item needs a "final" reflow 839 // (after its final flexed size & final position have been determined). 840 // 841 // @param aParentReflowInput the flex container's reflow input. 842 // @return true if such a reflow is needed, or false if we believe it can 843 // simply be moved to its final position and skip the reflow. 844 bool NeedsFinalReflow(const ReflowInput& aParentReflowInput) const; 845 846 // Gets the block frame that contains the flex item's content. This is 847 // Frame() itself or one of its descendants. 848 nsBlockFrame* BlockFrame() const; 849 850 protected: 851 bool IsMinSizeAutoResolutionNeeded() const; 852 853 uint32_t NumAutoMarginsInAxis(LogicalAxis aAxis) const; 854 855 // Values that we already know in constructor, and remain unchanged: 856 // The flex item's frame. 857 nsIFrame* mFrame = nullptr; 858 float mFlexGrow = 0.0f; 859 float mFlexShrink = 0.0f; 860 AspectRatio mAspectRatio; 861 862 // The flex item's writing mode. 863 WritingMode mWM; 864 865 // The flex container's writing mode. 866 WritingMode mCBWM; 867 868 // The flex container's main axis in flex container's writing mode. 869 LogicalAxis mMainAxis; 870 871 // Stored in flex container's writing mode. 872 LogicalMargin mBorderPadding; 873 874 // Stored in flex container's writing mode. Its value can change when we 875 // resolve "auto" marigns. 876 LogicalMargin mMargin; 877 878 // These are non-const so that we can lazily update them with the item's 879 // intrinsic size (obtained via a "measuring" reflow), when necessary. 880 // (e.g. for "flex-basis:auto;height:auto" & "min-height:auto") 881 nscoord mFlexBaseSize = 0; 882 nscoord mMainMinSize = 0; 883 nscoord mMainMaxSize = 0; 884 885 // mCrossMinSize and mCrossMaxSize are not changed after constructor. 886 nscoord mCrossMinSize = 0; 887 nscoord mCrossMaxSize = 0; 888 889 // Values that we compute after constructor: 890 nscoord mMainSize = 0; 891 nscoord mMainPosn = 0; 892 nscoord mCrossSize = 0; 893 nscoord mCrossPosn = 0; 894 895 // Mutable b/c it's set & resolved lazily, sometimes via const pointer. See 896 // comment above SetAscent(). 897 // We initialize this to ASK_FOR_BASELINE, and opportunistically fill it in 898 // with a real value if we end up reflowing this flex item. (But if we don't 899 // reflow this flex item, then this sentinel tells us that we don't know it 900 // yet & anyone who cares will need to explicitly request it.) 901 // 902 // Both mAscent and mAscentForLast are distance from the frame's border-box 903 // block-start edge. 904 mutable nscoord mAscent = ReflowOutput::ASK_FOR_BASELINE; 905 mutable nscoord mAscentForLast = ReflowOutput::ASK_FOR_BASELINE; 906 907 // Temporary state, while we're resolving flexible widths (for our main size) 908 // XXXdholbert To save space, we could use a union to make these variables 909 // overlay the same memory as some other member vars that aren't touched 910 // until after main-size has been resolved. In particular, these could share 911 // memory with mMainPosn through mAscent, and mIsStretched. 912 double mShareOfWeightSoFar = 0.0; 913 914 bool mIsFrozen = false; 915 bool mHadMinViolation = false; 916 bool mHadMaxViolation = false; 917 918 // Did this item get a preliminary reflow, to measure its desired height? 919 bool mHadMeasuringReflow = false; 920 921 // See IsStretched() documentation. 922 bool mIsStretched = false; 923 924 // Is this item a "strut" left behind by an element with visibility:collapse? 925 bool mIsStrut = false; 926 927 // See IsInlineAxisMainAxis() documentation. This is not changed after 928 // constructor. 929 bool mIsInlineAxisMainAxis = true; 930 931 // Does this item need to resolve a min-[width|height]:auto (in main-axis)? 932 // 933 // Note: mNeedsMinSizeAutoResolution needs to be declared towards the end of 934 // the member variables since it's initialized in a method that depends on 935 // other members declared above such as mCBWM, mMainAxis, and 936 // mIsInlineAxisMainAxis. 937 bool mNeedsMinSizeAutoResolution = false; 938 939 // Should we take care to treat this item's resolved BSize as indefinite? 940 bool mTreatBSizeAsIndefinite = false; 941 942 // Does this item have an auto margin in either main or cross axis? 943 bool mHasAnyAutoMargin = false; 944 945 // Does this item have a content-based flex base size (and is that a size in 946 // its block-axis)? 947 bool mIsFlexBaseSizeContentBSize = false; 948 949 // Does this item have a content-based resolved auto min size (and is that a 950 // size in its block-axis)? 951 bool mIsMainMinSizeContentBSize = false; 952 953 // If this item is {first,last}-baseline-aligned using 'align-self', which of 954 // its FlexLine's baseline sharing groups does it participate in? 955 BaselineSharingGroup mBaselineSharingGroup = BaselineSharingGroup::First; 956 957 // My "align-self" computed value (with "auto" swapped out for parent"s 958 // "align-items" value, in our constructor). 959 StyleAlignFlags mAlignSelf{StyleAlignFlags::AUTO}; 960 961 // Flags for 'align-self' (safe/unsafe/legacy). 962 StyleAlignFlags mAlignSelfFlags{0}; 963 }; 964 965 /** 966 * Represents a single flex line in a flex container. 967 * Manages an array of the FlexItems that are in the line. 968 */ 969 class nsFlexContainerFrame::FlexLine final { 970 public: 971 explicit FlexLine(nscoord aMainGapSize) : mMainGapSize(aMainGapSize) {} 972 973 nscoord SumOfGaps() const { 974 return NumItems() > 0 ? (NumItems() - 1) * mMainGapSize : 0; 975 } 976 977 // Returns the sum of our FlexItems' outer hypothetical main sizes plus the 978 // sum of main axis {row,column}-gaps between items. 979 // ("outer" = margin-box, and "hypothetical" = before flexing) 980 AuCoord64 TotalOuterHypotheticalMainSize() const { 981 return mTotalOuterHypotheticalMainSize; 982 } 983 984 // Accessors for our FlexItems & information about them: 985 // 986 // Note: Callers must use IsEmpty() to ensure that the FlexLine is non-empty 987 // before calling accessors that return FlexItem. 988 FlexItem& FirstItem() { return mItems[0]; } 989 const FlexItem& FirstItem() const { return mItems[0]; } 990 991 FlexItem& LastItem() { return mItems.LastElement(); } 992 const FlexItem& LastItem() const { return mItems.LastElement(); } 993 994 // The "startmost"/"endmost" is from the perspective of the flex container's 995 // writing-mode, not from the perspective of the flex-relative main axis. 996 const FlexItem& StartmostItem(const FlexboxAxisTracker& aAxisTracker) const { 997 return aAxisTracker.IsMainAxisReversed() ? LastItem() : FirstItem(); 998 } 999 const FlexItem& EndmostItem(const FlexboxAxisTracker& aAxisTracker) const { 1000 return aAxisTracker.IsMainAxisReversed() ? FirstItem() : LastItem(); 1001 } 1002 1003 bool IsEmpty() const { return mItems.IsEmpty(); } 1004 1005 uint32_t NumItems() const { return mItems.Length(); } 1006 1007 nsTArray<FlexItem>& Items() { return mItems; } 1008 const nsTArray<FlexItem>& Items() const { return mItems; } 1009 1010 // Adds the last flex item's hypothetical outer main-size and 1011 // margin/border/padding to our totals. This should be called exactly once for 1012 // each flex item, after we've determined that this line is the correct home 1013 // for that item. 1014 void AddLastItemToMainSizeTotals() { 1015 const FlexItem& lastItem = Items().LastElement(); 1016 1017 // Update our various bookkeeping member-vars: 1018 if (lastItem.IsFrozen()) { 1019 mNumFrozenItems++; 1020 } 1021 1022 mTotalItemMBP += lastItem.MarginBorderPaddingSizeInMainAxis(); 1023 mTotalOuterHypotheticalMainSize += lastItem.OuterMainSize(); 1024 1025 // If the item added was not the first item in the line, we add in any gap 1026 // space as needed. 1027 if (NumItems() >= 2) { 1028 mTotalOuterHypotheticalMainSize += mMainGapSize; 1029 } 1030 } 1031 1032 // Computes the cross-size and baseline position of this FlexLine, based on 1033 // its FlexItems. 1034 void ComputeCrossSizeAndBaseline(const FlexboxAxisTracker& aAxisTracker); 1035 1036 // Returns the cross-size of this line. 1037 nscoord LineCrossSize() const { return mLineCrossSize; } 1038 1039 // Setter for line cross-size -- needed for cases where the flex container 1040 // imposes a cross-size on the line. (e.g. for single-line flexbox, or for 1041 // multi-line flexbox with 'align-content: stretch') 1042 void SetLineCrossSize(nscoord aLineCrossSize) { 1043 mLineCrossSize = aLineCrossSize; 1044 } 1045 1046 /** 1047 * Returns the offset within this line where any baseline-aligned FlexItems 1048 * should place their baseline. The return value represents a distance from 1049 * the line's cross-start edge. 1050 * 1051 * If there are no baseline-aligned FlexItems, returns nscoord_MIN. 1052 */ 1053 nscoord FirstBaselineOffset() const { return mFirstBaselineOffset; } 1054 1055 /** 1056 * Returns the offset within this line where any last baseline-aligned 1057 * FlexItems should place their baseline. Opposite the case of the first 1058 * baseline offset, this represents a distance from the line's cross-end 1059 * edge (since last baseline-aligned items are flush to the cross-end edge). 1060 * 1061 * If there are no last baseline-aligned FlexItems, returns nscoord_MIN. 1062 */ 1063 nscoord LastBaselineOffset() const { return mLastBaselineOffset; } 1064 1065 // Extract a baseline from this line, which would be suitable for use as the 1066 // flex container's 'aBaselineGroup' (i.e. first/last) baseline. 1067 // https://drafts.csswg.org/css-flexbox-1/#flex-baselines 1068 // 1069 // The return value always represents a distance from the line's cross-start 1070 // edge, even if we are querying last baseline. If this line has no flex items 1071 // in its aBaselineGroup group, this method falls back to trying the opposite 1072 // group. If this line has no baseline-aligned items at all, this returns 1073 // nscoord_MIN. 1074 nscoord ExtractBaselineOffset(BaselineSharingGroup aBaselineGroup) const; 1075 1076 /** 1077 * Returns the gap size in the main axis for this line. Used for gap 1078 * calculations. 1079 */ 1080 nscoord MainGapSize() const { return mMainGapSize; } 1081 1082 // Runs the "Resolving Flexible Lengths" algorithm from section 9.7 of the 1083 // CSS flexbox spec to distribute aFlexContainerMainSize among our flex items. 1084 // https://drafts.csswg.org/css-flexbox-1/#resolve-flexible-lengths 1085 void ResolveFlexibleLengths(nscoord aFlexContainerMainSize, 1086 ComputedFlexLineInfo* aLineInfo); 1087 1088 void PositionItemsInMainAxis(const StyleContentDistribution& aJustifyContent, 1089 nscoord aContentBoxMainSize, 1090 const FlexboxAxisTracker& aAxisTracker); 1091 1092 void PositionItemsInCrossAxis(nscoord aLineStartPosition, 1093 const FlexboxAxisTracker& aAxisTracker); 1094 1095 private: 1096 // Helpers for ResolveFlexibleLengths(): 1097 void FreezeItemsEarly(bool aIsUsingFlexGrow, ComputedFlexLineInfo* aLineInfo); 1098 1099 void FreezeOrRestoreEachFlexibleSize(const nscoord aTotalViolation, 1100 bool aIsFinalIteration); 1101 1102 // Sum of FlexItems' outer hypothetical main sizes and all main-axis 1103 // {row,columnm}-gaps between items. 1104 // (i.e. their flex base sizes, clamped via their min/max-size properties, 1105 // plus their main-axis margin/border/padding, plus the sum of the gaps.) 1106 // 1107 // This variable uses a 64-bit coord type to avoid integer overflow in case 1108 // several of the individual items have huge hypothetical main sizes, which 1109 // can happen with percent-width table-layout:fixed descendants. We have to 1110 // avoid integer overflow in order to shrink items properly in that scenario. 1111 AuCoord64 mTotalOuterHypotheticalMainSize = 0; 1112 1113 // Stores this line's flex items. 1114 nsTArray<FlexItem> mItems; 1115 1116 // Number of *frozen* FlexItems in this line, based on FlexItem::IsFrozen(). 1117 // Mostly used for optimization purposes, e.g. to bail out early from loops 1118 // when we can tell they have nothing left to do. 1119 uint32_t mNumFrozenItems = 0; 1120 1121 // Sum of margin/border/padding for the FlexItems in this FlexLine. 1122 nscoord mTotalItemMBP = 0; 1123 1124 nscoord mLineCrossSize = 0; 1125 nscoord mFirstBaselineOffset = nscoord_MIN; 1126 nscoord mLastBaselineOffset = nscoord_MIN; 1127 1128 // Maintain size of each {row,column}-gap in the main axis 1129 const nscoord mMainGapSize; 1130 }; 1131 1132 // The "startmost"/"endmost" is from the perspective of the flex container's 1133 // writing-mode, not from the perspective of the flex-relative cross axis. 1134 const FlexLine& StartmostLine(const nsTArray<FlexLine>& aLines, 1135 const FlexboxAxisTracker& aAxisTracker) { 1136 return aAxisTracker.IsCrossAxisReversed() ? aLines.LastElement() : aLines[0]; 1137 } 1138 const FlexLine& EndmostLine(const nsTArray<FlexLine>& aLines, 1139 const FlexboxAxisTracker& aAxisTracker) { 1140 return aAxisTracker.IsCrossAxisReversed() ? aLines[0] : aLines.LastElement(); 1141 } 1142 1143 // Information about a strut left behind by a FlexItem that's been collapsed 1144 // using "visibility:collapse". 1145 struct nsFlexContainerFrame::StrutInfo { 1146 StrutInfo(uint32_t aItemIdx, nscoord aStrutCrossSize) 1147 : mItemIdx(aItemIdx), mStrutCrossSize(aStrutCrossSize) {} 1148 1149 uint32_t mItemIdx; // Index in the child list. 1150 nscoord mStrutCrossSize; // The cross-size of this strut. 1151 }; 1152 1153 // Flex data shared by the flex container frames in a continuation chain, owned 1154 // by the first-in-flow. The data is initialized at the end of the 1155 // first-in-flow's Reflow(). 1156 struct nsFlexContainerFrame::SharedFlexData final { 1157 // The flex lines generated in DoFlexLayout() by our first-in-flow. 1158 nsTArray<FlexLine> mLines; 1159 1160 // The final content main/cross size computed by DoFlexLayout. 1161 nscoord mContentBoxMainSize = NS_UNCONSTRAINEDSIZE; 1162 nscoord mContentBoxCrossSize = NS_UNCONSTRAINEDSIZE; 1163 1164 // Update this struct. Called by the first-in-flow. 1165 void Update(FlexLayoutResult&& aFlr) { 1166 mLines = std::move(aFlr.mLines); 1167 mContentBoxMainSize = aFlr.mContentBoxMainSize; 1168 mContentBoxCrossSize = aFlr.mContentBoxCrossSize; 1169 } 1170 1171 // The frame property under which this struct is stored. Set only on the 1172 // first-in-flow. 1173 NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, SharedFlexData) 1174 }; 1175 1176 // Flex data stored in every flex container's in-flow fragment (continuation). 1177 // 1178 // It's intended to prevent quadratic operations resulting from each fragment 1179 // having to walk its full prev-in-flow chain, and also serves as an argument to 1180 // the flex container next-in-flow's ReflowChildren(), to compute the position 1181 // offset for each flex item. 1182 struct nsFlexContainerFrame::PerFragmentFlexData final { 1183 // Suppose D is the distance from a flex container fragment's content-box 1184 // block-start edge to whichever is larger of either (a) the block-end edge of 1185 // its children, or (b) the available space's block-end edge. (Note: in case 1186 // (b), D is conceptually the sum of the block-size of the children, the 1187 // packing space before & in between them, and part of the packing space after 1188 // them.) 1189 // 1190 // This variable stores the sum of the D values for the current flex container 1191 // fragments and for all its previous fragments 1192 nscoord mCumulativeContentBoxBSize = 0; 1193 1194 // This variable accumulates FirstLineOrFirstItemBAxisMetrics::mBEndEdgeShift, 1195 // for the current flex container fragment and for all its previous fragments. 1196 // See the comment of mBEndEdgeShift for its computation details. In short, 1197 // this value is the net block-end edge shift, accumulated for the children in 1198 // all the previous fragments. This number is non-negative. 1199 // 1200 // This value is also used to grow a flex container's block-size if the 1201 // container's computed block-size is unconstrained. For example: a tall item 1202 // may be pushed to the next page/column, which leaves some wasted area at the 1203 // bottom of the current flex container fragment, and causes the flex 1204 // container fragments to be (collectively) larger than the hypothetical 1205 // unfragmented size. Another example: a tall flex item may be broken into 1206 // multiple fragments, and those fragments may have a larger collective 1207 // block-size as compared to the item's original unfragmented size; the 1208 // container would need to increase its block-size to account for this. 1209 nscoord mCumulativeBEndEdgeShift = 0; 1210 1211 // The frame property under which this struct is stored. Cached on every 1212 // in-flow fragment (continuation) at the end of the flex container's 1213 // Reflow(). 1214 NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, PerFragmentFlexData) 1215 }; 1216 1217 static void BuildStrutInfoFromCollapsedItems(const nsTArray<FlexLine>& aLines, 1218 nsTArray<StrutInfo>& aStruts) { 1219 MOZ_ASSERT(aStruts.IsEmpty(), 1220 "We should only build up StrutInfo once per reflow, so " 1221 "aStruts should be empty when this is called"); 1222 1223 uint32_t itemIdxInContainer = 0; 1224 for (const FlexLine& line : aLines) { 1225 for (const FlexItem& item : line.Items()) { 1226 if (item.Frame()->StyleVisibility()->IsCollapse()) { 1227 // Note the cross size of the line as the item's strut size. 1228 aStruts.AppendElement( 1229 StrutInfo(itemIdxInContainer, line.LineCrossSize())); 1230 } 1231 itemIdxInContainer++; 1232 } 1233 } 1234 } 1235 1236 static mozilla::StyleAlignFlags SimplifyAlignOrJustifyContentForOneItem( 1237 const StyleContentDistribution& aAlignmentVal, bool aIsAlign) { 1238 // Mask away any explicit fallback, to get the main (non-fallback) part of 1239 // the specified value: 1240 StyleAlignFlags specified = aAlignmentVal.primary; 1241 1242 // XXX strip off <overflow-position> bits until we implement it (bug 1311892) 1243 specified &= ~StyleAlignFlags::FLAG_BITS; 1244 1245 // FIRST: handle a special-case for "justify-content:stretch" (or equivalent), 1246 // which requires that we ignore any author-provided explicit fallback value. 1247 if (specified == StyleAlignFlags::NORMAL) { 1248 // In a flex container, *-content: "'normal' behaves as 'stretch'". 1249 // Do that conversion early, so it benefits from our 'stretch' special-case. 1250 // https://drafts.csswg.org/css-align-3/#distribution-flex 1251 specified = StyleAlignFlags::STRETCH; 1252 } 1253 if (!aIsAlign && specified == StyleAlignFlags::STRETCH) { 1254 // In a flex container, in "justify-content Axis: [...] 'stretch' behaves 1255 // as 'flex-start' (ignoring the specified fallback alignment, if any)." 1256 // https://drafts.csswg.org/css-align-3/#distribution-flex 1257 // So, we just directly return 'flex-start', & ignore explicit fallback.. 1258 return StyleAlignFlags::FLEX_START; 1259 } 1260 1261 // TODO: Check for an explicit fallback value (and if it's present, use it) 1262 // here once we parse it, see https://github.com/w3c/csswg-drafts/issues/1002. 1263 1264 // If there's no explicit fallback, use the implied fallback values for 1265 // space-{between,around,evenly} (since those values only make sense with 1266 // multiple alignment subjects), and otherwise just use the specified value: 1267 if (specified == StyleAlignFlags::SPACE_BETWEEN) { 1268 return StyleAlignFlags::FLEX_START; 1269 } 1270 if (specified == StyleAlignFlags::SPACE_AROUND || 1271 specified == StyleAlignFlags::SPACE_EVENLY) { 1272 return StyleAlignFlags::CENTER; 1273 } 1274 return specified; 1275 } 1276 1277 bool nsFlexContainerFrame::DrainSelfOverflowList() { 1278 return DrainAndMergeSelfOverflowList(); 1279 } 1280 1281 void nsFlexContainerFrame::AppendFrames(ChildListID aListID, 1282 nsFrameList&& aFrameList) { 1283 NoteNewChildren(aListID, aFrameList); 1284 nsContainerFrame::AppendFrames(aListID, std::move(aFrameList)); 1285 } 1286 1287 void nsFlexContainerFrame::InsertFrames( 1288 ChildListID aListID, nsIFrame* aPrevFrame, 1289 const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) { 1290 NoteNewChildren(aListID, aFrameList); 1291 nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine, 1292 std::move(aFrameList)); 1293 } 1294 1295 void nsFlexContainerFrame::RemoveFrame(DestroyContext& aContext, 1296 ChildListID aListID, 1297 nsIFrame* aOldFrame) { 1298 MOZ_ASSERT(aListID == FrameChildListID::Principal, "unexpected child list"); 1299 1300 #ifdef DEBUG 1301 SetDidPushItemsBitIfNeeded(aListID, aOldFrame); 1302 #endif 1303 1304 nsContainerFrame::RemoveFrame(aContext, aListID, aOldFrame); 1305 } 1306 1307 StyleAlignFlags nsFlexContainerFrame::CSSAlignmentForAbsPosChild( 1308 const ReflowInput& aChildRI, LogicalAxis aLogicalAxis) const { 1309 const FlexboxAxisTracker axisTracker(this); 1310 1311 // If we're row-oriented and the caller is asking about our inline axis (or 1312 // alternately, if we're column-oriented and the caller is asking about our 1313 // block axis), then the caller is really asking about our *main* axis. 1314 // Otherwise, the caller is asking about our cross axis. 1315 const bool isMainAxis = 1316 (axisTracker.IsRowOriented() == (aLogicalAxis == LogicalAxis::Inline)); 1317 const nsStylePosition* containerStylePos = StylePosition(); 1318 const bool isAxisReversed = isMainAxis ? axisTracker.IsMainAxisReversed() 1319 : axisTracker.IsCrossAxisReversed(); 1320 1321 StyleAlignFlags alignment{0}; 1322 StyleAlignFlags alignmentFlags{0}; 1323 if (isMainAxis) { 1324 // We're aligning in the main axis: align according to 'justify-content'. 1325 // (We don't care about justify-self; it has no effect on children of flex 1326 // containers, unless https://github.com/w3c/csswg-drafts/issues/7644 1327 // changes that.) 1328 alignment = SimplifyAlignOrJustifyContentForOneItem( 1329 containerStylePos->mJustifyContent, 1330 /*aIsAlign = */ false); 1331 } else { 1332 // We're aligning in the cross axis: align according to 'align-self'. 1333 // (We don't care about align-content; it has no effect on abspos flex 1334 // children, per https://github.com/w3c/csswg-drafts/issues/7596 ) 1335 alignment = aChildRI.mStylePosition->UsedAlignSelf(Style())._0; 1336 // Extract and strip align flag bits 1337 alignmentFlags = alignment & StyleAlignFlags::FLAG_BITS; 1338 alignment &= ~StyleAlignFlags::FLAG_BITS; 1339 1340 if (alignment == StyleAlignFlags::NORMAL) { 1341 // "the 'normal' keyword behaves as 'start' on replaced 1342 // absolutely-positioned boxes, and behaves as 'stretch' on all other 1343 // absolutely-positioned boxes." 1344 // https://drafts.csswg.org/css-align/#align-abspos 1345 alignment = aChildRI.mFrame->IsReplaced() ? StyleAlignFlags::START 1346 : StyleAlignFlags::STRETCH; 1347 } 1348 } 1349 1350 if (alignment == StyleAlignFlags::STRETCH) { 1351 // The default fallback alignment for 'stretch' is 'flex-start'. 1352 alignment = StyleAlignFlags::FLEX_START; 1353 } 1354 1355 // Resolve flex-start, flex-end, auto, left, right, baseline, last baseline; 1356 if (alignment == StyleAlignFlags::FLEX_START) { 1357 alignment = isAxisReversed ? StyleAlignFlags::END : StyleAlignFlags::START; 1358 } else if (alignment == StyleAlignFlags::FLEX_END) { 1359 alignment = isAxisReversed ? StyleAlignFlags::START : StyleAlignFlags::END; 1360 } else if (alignment == StyleAlignFlags::LEFT || 1361 alignment == StyleAlignFlags::RIGHT) { 1362 MOZ_ASSERT(isMainAxis, "Only justify-* can have 'left' and 'right'!"); 1363 alignment = axisTracker.ResolveJustifyLeftRight(alignment); 1364 } else if (alignment == StyleAlignFlags::BASELINE) { 1365 alignment = StyleAlignFlags::START; 1366 } else if (alignment == StyleAlignFlags::LAST_BASELINE) { 1367 alignment = StyleAlignFlags::END; 1368 } 1369 1370 MOZ_ASSERT(alignment != StyleAlignFlags::STRETCH, 1371 "We should've converted 'stretch' to the fallback alignment!"); 1372 MOZ_ASSERT(alignment != StyleAlignFlags::FLEX_START && 1373 alignment != StyleAlignFlags::FLEX_END, 1374 "AbsoluteContainingBlock doesn't know how to handle " 1375 "flex-relative axis for flex containers!"); 1376 1377 return (alignment | alignmentFlags); 1378 } 1379 1380 std::pair<StyleAlignFlags, StyleAlignFlags> 1381 nsFlexContainerFrame::UsedAlignSelfAndFlagsForItem( 1382 const nsIFrame* aFlexItem) const { 1383 MOZ_ASSERT(aFlexItem->IsFlexItem()); 1384 1385 if (IsLegacyWebkitBox()) { 1386 // For -webkit-{inline-}box, we need to: 1387 // (1) Use prefixed "box-align" instead of "align-items" to determine the 1388 // container's cross-axis alignment behavior. 1389 // (2) Suppress the ability for flex items to override that with their own 1390 // cross-axis alignment. (The legacy box model doesn't support this.) 1391 // So, each FlexItem simply copies the container's converted "align-items" 1392 // value and disregards their own "align-self" property. 1393 const StyleAlignFlags alignSelf = 1394 ConvertLegacyStyleToAlignItems(StyleXUL()); 1395 const StyleAlignFlags flags = {0}; 1396 return {alignSelf, flags}; 1397 } 1398 1399 // Note: we don't need to call nsLayoutUtils::GetStyleFrame(aFlexItem) because 1400 // the table wrapper frame inherits 'align-self' property from the table 1401 // frame. 1402 StyleSelfAlignment usedAlignSelf = 1403 aFlexItem->StylePosition()->UsedAlignSelf(Style()); 1404 if (MOZ_LIKELY(usedAlignSelf._0 == StyleAlignFlags::NORMAL)) { 1405 // For flex items, 'align-self:normal' behaves as 'align-self:stretch'. 1406 // https://drafts.csswg.org/css-align-3/#align-flex 1407 usedAlignSelf = {StyleAlignFlags::STRETCH}; 1408 } 1409 1410 // Store the <overflow-position> bits in flags, and strip the bits from the 1411 // used align-self value. 1412 const StyleAlignFlags flags = usedAlignSelf._0 & StyleAlignFlags::FLAG_BITS; 1413 const StyleAlignFlags alignSelf = 1414 usedAlignSelf._0 & ~StyleAlignFlags::FLAG_BITS; 1415 return {alignSelf, flags}; 1416 } 1417 1418 void nsFlexContainerFrame::GenerateFlexItemForChild( 1419 FlexLine& aLine, nsIFrame* aChildFrame, 1420 const ReflowInput& aParentReflowInput, 1421 const FlexboxAxisTracker& aAxisTracker, 1422 const nscoord aTentativeContentBoxCrossSize) { 1423 const auto flexWM = aAxisTracker.GetWritingMode(); 1424 const auto childWM = aChildFrame->GetWritingMode(); 1425 1426 // Note: we use GetStyleFrame() to access the sizing & flex properties here. 1427 // This lets us correctly handle table wrapper frames as flex items since 1428 // their inline-size and block-size properties are always 'auto'. In order for 1429 // 'flex-basis:auto' to actually resolve to the author's specified inline-size 1430 // or block-size, we need to dig through to the inner table. 1431 const auto* styleFrame = nsLayoutUtils::GetStyleFrame(aChildFrame); 1432 const auto* stylePos = styleFrame->StylePosition(); 1433 const auto anchorResolutionParams = 1434 AnchorPosResolutionParams::From(styleFrame); 1435 1436 // Construct a StyleSizeOverrides for this flex item so that its ReflowInput 1437 // below will use and resolve its flex base size rather than its corresponding 1438 // preferred main size property (only for modern CSS flexbox). 1439 StyleSizeOverrides sizeOverrides; 1440 if (!IsLegacyWebkitBox()) { 1441 Maybe<StyleSize> styleFlexBaseSize; 1442 1443 // When resolving flex base size, flex items use their 'flex-basis' property 1444 // in place of their preferred main size (e.g. 'width') for sizing purposes, 1445 // *unless* they have 'flex-basis:auto' in which case they use their 1446 // preferred main size after all. 1447 const auto& flexBasis = stylePos->mFlexBasis; 1448 const auto styleMainSize = 1449 stylePos->Size(aAxisTracker.MainAxis(), flexWM, anchorResolutionParams); 1450 if (IsUsedFlexBasisContent(flexBasis, *styleMainSize)) { 1451 // If we get here, we're resolving the flex base size for a flex item, and 1452 // we fall into the flexbox spec section 9.2 step 3, substep C (if we have 1453 // a definite cross size) or E (if not). 1454 styleFlexBaseSize.emplace(StyleSize::MaxContent()); 1455 } else if (flexBasis.IsSize() && !flexBasis.IsAuto()) { 1456 // For all other non-'auto' flex-basis values, we just swap in the 1457 // flex-basis itself for the preferred main-size property. 1458 styleFlexBaseSize.emplace(flexBasis.AsSize()); 1459 } else { 1460 // else: flex-basis is 'auto', which is deferring to some explicit value 1461 // in the preferred main size. 1462 MOZ_ASSERT(flexBasis.IsAuto()); 1463 styleFlexBaseSize.emplace(*styleMainSize); 1464 } 1465 1466 MOZ_ASSERT(styleFlexBaseSize, "We should've emplace styleFlexBaseSize!"); 1467 1468 // Provide the size override for the preferred main size property. 1469 if (aAxisTracker.IsInlineAxisMainAxis(childWM)) { 1470 sizeOverrides.mStyleISize = std::move(styleFlexBaseSize); 1471 } else { 1472 sizeOverrides.mStyleBSize = std::move(styleFlexBaseSize); 1473 } 1474 1475 // 'flex-basis' should works on the inner table frame for a table flex item, 1476 // just like how 'height' works on a table element. 1477 sizeOverrides.mApplyOverridesVerbatim = true; 1478 } 1479 1480 // Create temporary reflow input just for sizing -- to get hypothetical 1481 // main-size and the computed values of min / max main-size property. 1482 // (This reflow input will _not_ be used for reflow.) 1483 ReflowInput childRI(PresContext(), aParentReflowInput, aChildFrame, 1484 aParentReflowInput.ComputedSize(childWM), Nothing(), {}, 1485 sizeOverrides, {ComputeSizeFlag::ShrinkWrap}); 1486 1487 // FLEX GROW & SHRINK WEIGHTS 1488 // -------------------------- 1489 float flexGrow, flexShrink; 1490 if (IsLegacyWebkitBox()) { 1491 flexGrow = flexShrink = aChildFrame->StyleXUL()->mBoxFlex; 1492 } else { 1493 flexGrow = stylePos->mFlexGrow; 1494 flexShrink = stylePos->mFlexShrink; 1495 } 1496 1497 // MAIN SIZES (flex base size, min/max size) 1498 // ----------------------------------------- 1499 const LogicalSize computedSizeInFlexWM = childRI.ComputedSize(flexWM); 1500 const LogicalSize computedMinSizeInFlexWM = childRI.ComputedMinSize(flexWM); 1501 const LogicalSize computedMaxSizeInFlexWM = childRI.ComputedMaxSize(flexWM); 1502 1503 const nscoord flexBaseSize = aAxisTracker.MainComponent(computedSizeInFlexWM); 1504 const nscoord mainMinSize = 1505 aAxisTracker.MainComponent(computedMinSizeInFlexWM); 1506 const nscoord mainMaxSize = 1507 aAxisTracker.MainComponent(computedMaxSizeInFlexWM); 1508 1509 // This is enforced by the ReflowInput where these values come from: 1510 MOZ_ASSERT(mainMinSize <= mainMaxSize, "min size is larger than max size"); 1511 1512 // CROSS SIZES (tentative cross size, min/max cross size) 1513 // ------------------------------------------------------ 1514 // Grab the cross size from the reflow input. This might be the right value, 1515 // or we might resolve it to something else in SizeItemInCrossAxis(); hence, 1516 // it's tentative. See comment under "Cross Size Determination" for more. 1517 const nscoord tentativeCrossSize = 1518 aAxisTracker.CrossComponent(computedSizeInFlexWM); 1519 const nscoord crossMinSize = 1520 aAxisTracker.CrossComponent(computedMinSizeInFlexWM); 1521 const nscoord crossMaxSize = 1522 aAxisTracker.CrossComponent(computedMaxSizeInFlexWM); 1523 1524 // Construct the flex item! 1525 FlexItem& item = *aLine.Items().EmplaceBack( 1526 childRI, flexGrow, flexShrink, flexBaseSize, mainMinSize, mainMaxSize, 1527 tentativeCrossSize, crossMinSize, crossMaxSize, aAxisTracker); 1528 1529 // We may be about to do computations based on our item's cross-size 1530 // (e.g. using it as a constraint when measuring our content in the 1531 // main axis, or using it with the preferred aspect ratio to obtain a main 1532 // size). BEFORE WE DO THAT, we need let the item "pre-stretch" its cross size 1533 // (if it's got 'align-self:stretch'), for a certain case where the spec says 1534 // the stretched cross size is considered "definite". That case is if we 1535 // have a single-line (nowrap) flex container which itself has a definite 1536 // cross-size. Otherwise, we'll wait to do stretching, since (in other 1537 // cases) we don't know how much the item should stretch yet. 1538 if (IsSingleLine(aParentReflowInput.mFrame, 1539 aParentReflowInput.mStylePosition)) { 1540 // Is container's cross size "definite"? 1541 // - If it's column-oriented, then "yes", because its cross size is its 1542 // inline-size which is always definite from its descendants' perspective. 1543 // - Otherwise (if it's row-oriented), then we check the actual size 1544 // and call it definite if it's not NS_UNCONSTRAINEDSIZE. 1545 if (aAxisTracker.IsColumnOriented() || 1546 aTentativeContentBoxCrossSize != NS_UNCONSTRAINEDSIZE) { 1547 // Container's cross size is "definite", so we can resolve the item's 1548 // stretched cross size using that. 1549 item.ResolveStretchedCrossSize(aTentativeContentBoxCrossSize); 1550 } 1551 } 1552 1553 // Before thinking about freezing the item at its base size, we need to give 1554 // it a chance to recalculate the base size from its cross size and aspect 1555 // ratio (since its cross size might've *just* now become definite due to 1556 // 'stretch' above) 1557 item.ResolveFlexBaseSizeFromAspectRatio(childRI); 1558 1559 // If we're inflexible, we can just freeze to our hypothetical main-size 1560 // up-front. 1561 if (flexGrow == 0.0f && flexShrink == 0.0f) { 1562 item.Freeze(); 1563 if (flexBaseSize < mainMinSize) { 1564 item.SetWasMinClamped(); 1565 } else if (flexBaseSize > mainMaxSize) { 1566 item.SetWasMaxClamped(); 1567 } 1568 } 1569 1570 // Resolve "flex-basis:auto" and/or "min-[width|height]:auto" (which might 1571 // require us to reflow the item to measure content height) 1572 ResolveAutoFlexBasisAndMinSize(item, childRI, aAxisTracker); 1573 } 1574 1575 nscoord nsFlexContainerFrame::PartiallyResolveAutoMinSize( 1576 const FlexItem& aFlexItem, const ReflowInput& aItemReflowInput, 1577 const FlexboxAxisTracker& aAxisTracker) const { 1578 MOZ_ASSERT(aFlexItem.NeedsMinSizeAutoResolution(), 1579 "only call for FlexItems that need min-size auto resolution"); 1580 1581 const auto itemWM = aFlexItem.GetWritingMode(); 1582 const auto cbWM = aAxisTracker.GetWritingMode(); 1583 const auto anchorResolutionParams = 1584 AnchorPosResolutionParams::From(&aItemReflowInput); 1585 const auto mainStyleSize = aItemReflowInput.mStylePosition->Size( 1586 aAxisTracker.MainAxis(), cbWM, anchorResolutionParams); 1587 const auto maxMainStyleSize = aItemReflowInput.mStylePosition->MaxSize( 1588 aAxisTracker.MainAxis(), cbWM, anchorResolutionParams); 1589 const auto boxSizingAdjust = 1590 aItemReflowInput.mStylePosition->mBoxSizing == StyleBoxSizing::Border 1591 ? aFlexItem.BorderPadding().Size(cbWM) 1592 : LogicalSize(cbWM); 1593 1594 // Return the percentage basis in cbWM for computing the specified size 1595 // suggestion. 1596 auto PercentageBasisForItem = [&]() { 1597 // If this flex item is a compressible replaced element, according to the 1598 // list in CSS Sizing 3 §5.2.2, then CSS Sizing 3 §5.2.1c requires us to 1599 // resolve the percentage part of the preferred main size property against 1600 // zero, yielding a definite specified size suggestion. Here we can use a 1601 // zero percentage basis to fulfill this requirement. 1602 if (aFlexItem.Frame()->IsPercentageResolvedAgainstZero(*mainStyleSize, 1603 *maxMainStyleSize)) { 1604 return LogicalSize(cbWM, 0, 0); 1605 } 1606 return aItemReflowInput.mContainingBlockSize.ConvertTo(cbWM, itemWM); 1607 }; 1608 1609 // Compute the specified size suggestion, which is the main-size property if 1610 // it's definite. 1611 nscoord specifiedSizeSuggestion = nscoord_MAX; 1612 1613 if (aAxisTracker.IsRowOriented()) { 1614 // TODO(dholbert): We need to handle 'stretch' (and its prefixed aliases) 1615 // here; that's tracked in bug 1936942. (Note that we do handle 'stretch' 1616 // in our column-oriented "else" clause below, via the call to 1617 // ComputeBSizeValueHandlingStretch.) 1618 if (mainStyleSize->IsLengthPercentage()) { 1619 // NOTE: We ignore extremum inline-size. This is OK because the caller is 1620 // responsible for computing the min-content inline-size and min()'ing it 1621 // with the value we return. 1622 specifiedSizeSuggestion = aFlexItem.Frame()->ComputeISizeValue( 1623 cbWM, PercentageBasisForItem(), boxSizingAdjust, 1624 mainStyleSize->AsLengthPercentage()); 1625 } 1626 } else { 1627 // NOTE: We ignore specified block-sizes that behave as 'auto', as 1628 // identified by IsAutoBSize(); that's OK because the caller is responsible 1629 // for computing the content-based block-size and and min()'ing it with the 1630 // value we return. 1631 const auto percentageBasisBSize = PercentageBasisForItem().BSize(cbWM); 1632 if (!nsLayoutUtils::IsAutoBSize(*mainStyleSize, percentageBasisBSize)) { 1633 specifiedSizeSuggestion = nsLayoutUtils::ComputeBSizeValueHandlingStretch( 1634 percentageBasisBSize, aFlexItem.MarginSizeInMainAxis(), 1635 aFlexItem.BorderPaddingSizeInMainAxis(), boxSizingAdjust.BSize(cbWM), 1636 *mainStyleSize); 1637 } 1638 } 1639 1640 if (specifiedSizeSuggestion != nscoord_MAX) { 1641 // We have the specified size suggestion. Return it now since we don't need 1642 // to consider transferred size suggestion. 1643 FLEX_LOGV("Specified size suggestion: %d", specifiedSizeSuggestion); 1644 return specifiedSizeSuggestion; 1645 } 1646 1647 // Compute the transferred size suggestion, which is the cross size converted 1648 // through the aspect ratio (if the item is replaced, and it has an aspect 1649 // ratio and a definite cross size). 1650 if (const auto& aspectRatio = aFlexItem.GetAspectRatio(); 1651 aFlexItem.Frame()->IsReplaced() && aspectRatio && 1652 aFlexItem.IsCrossSizeDefinite(aItemReflowInput)) { 1653 // We have a usable aspect ratio. (not going to divide by 0) 1654 nscoord transferredSizeSuggestion = aspectRatio.ComputeRatioDependentSize( 1655 aFlexItem.MainAxis(), cbWM, aFlexItem.CrossSize(), boxSizingAdjust); 1656 1657 // Clamp the transferred size suggestion by any definite min and max 1658 // cross size converted through the aspect ratio. 1659 transferredSizeSuggestion = aFlexItem.ClampMainSizeViaCrossAxisConstraints( 1660 transferredSizeSuggestion, aItemReflowInput); 1661 1662 FLEX_LOGV("Transferred size suggestion: %d", transferredSizeSuggestion); 1663 return transferredSizeSuggestion; 1664 } 1665 1666 return nscoord_MAX; 1667 } 1668 1669 // Note: If & when we handle "min-height: min-content" for flex items, 1670 // we may want to resolve that in this function, too. 1671 void nsFlexContainerFrame::ResolveAutoFlexBasisAndMinSize( 1672 FlexItem& aFlexItem, const ReflowInput& aItemReflowInput, 1673 const FlexboxAxisTracker& aAxisTracker) { 1674 // (Note: We can guarantee that the flex-basis will have already been 1675 // resolved if the main axis is the same as the item's inline 1676 // axis. Inline-axis values should always be resolvable without reflow.) 1677 const bool isMainSizeAuto = 1678 (!aFlexItem.IsInlineAxisMainAxis() && 1679 NS_UNCONSTRAINEDSIZE == aFlexItem.FlexBaseSize()); 1680 1681 const bool isMainMinSizeAuto = aFlexItem.NeedsMinSizeAutoResolution(); 1682 1683 if (!isMainSizeAuto && !isMainMinSizeAuto) { 1684 // Nothing to do; this function is only needed for flex items 1685 // with a used flex-basis of "auto" or a min-main-size of "auto". 1686 return; 1687 } 1688 1689 FLEX_ITEM_LOG( 1690 aFlexItem.Frame(), 1691 "Resolving auto main size? %s; resolving auto min main size? %s", 1692 YesOrNo(isMainSizeAuto), YesOrNo(isMainMinSizeAuto)); 1693 1694 nscoord resolvedMinSize; // (only set/used if isMainMinSizeAuto==true) 1695 bool minSizeNeedsToMeasureContent = false; // assume the best 1696 if (isMainMinSizeAuto) { 1697 if (IsLegacyWebkitBox()) { 1698 // Allow flex items in a legacy flex container to shrink below their 1699 // automatic minimum size by setting the resolved minimum size to zero. 1700 // This behavior is not in the spec, but it aligns with blink and webkit's 1701 // implementation. 1702 resolvedMinSize = 0; 1703 } else { 1704 // Resolve the min-size, except for considering the min-content size. 1705 // (We'll consider that later, if we need to.) 1706 resolvedMinSize = PartiallyResolveAutoMinSize(aFlexItem, aItemReflowInput, 1707 aAxisTracker); 1708 } 1709 if (resolvedMinSize > 0) { 1710 // If resolvedMinSize were already at 0, we could skip calculating content 1711 // size suggestion because it can't go any lower. 1712 minSizeNeedsToMeasureContent = true; 1713 } 1714 } 1715 1716 const bool flexBasisNeedsToMeasureContent = isMainSizeAuto; 1717 1718 // Measure content, if needed (w/ intrinsic-width method or a reflow) 1719 if (minSizeNeedsToMeasureContent || flexBasisNeedsToMeasureContent) { 1720 // Compute the content size suggestion, which is the min-content size in the 1721 // main axis. 1722 nscoord contentSizeSuggestion = nscoord_MAX; 1723 1724 if (aFlexItem.IsInlineAxisMainAxis()) { 1725 if (minSizeNeedsToMeasureContent) { 1726 // Compute the flex item's content size suggestion, which is the 1727 // 'min-content' size on the main axis. 1728 // https://drafts.csswg.org/css-flexbox-1/#content-size-suggestion 1729 const auto cbWM = aAxisTracker.GetWritingMode(); 1730 const auto itemWM = aFlexItem.GetWritingMode(); 1731 const nscoord availISize = 0; // for min-content size 1732 StyleSizeOverrides sizeOverrides; 1733 sizeOverrides.mStyleISize.emplace(StyleSize::Auto()); 1734 if (aFlexItem.IsStretched()) { 1735 sizeOverrides.mStyleBSize.emplace(aFlexItem.StyleCrossSize()); 1736 } 1737 const auto sizeInItemWM = aFlexItem.Frame()->ComputeSize( 1738 aItemReflowInput, itemWM, aItemReflowInput.mContainingBlockSize, 1739 availISize, 1740 aItemReflowInput.ComputedLogicalMargin(itemWM).Size(itemWM), 1741 aItemReflowInput.ComputedLogicalBorderPadding(itemWM).Size(itemWM), 1742 sizeOverrides, {ComputeSizeFlag::ShrinkWrap}); 1743 1744 contentSizeSuggestion = aAxisTracker.MainComponent( 1745 sizeInItemWM.mLogicalSize.ConvertTo(cbWM, itemWM)); 1746 } 1747 NS_ASSERTION(!flexBasisNeedsToMeasureContent, 1748 "flex-basis:auto should have been resolved in the " 1749 "reflow input, for horizontal flexbox. It shouldn't need " 1750 "special handling here"); 1751 } else { 1752 // If this item is flexible (in its block axis)... 1753 // OR if we're measuring its 'auto' min-BSize, with its main-size (in its 1754 // block axis) being something non-"auto"... 1755 // THEN: we assume that the computed BSize that we're reflowing with now 1756 // could be different from the one we'll use for this flex item's 1757 // "actual" reflow later on. In that case, we need to be sure the flex 1758 // item treats this as a block-axis resize (regardless of whether there 1759 // are actually any ancestors being resized in that axis). 1760 // (Note: We don't have to do this for the inline axis, because 1761 // InitResizeFlags will always turn on mIsIResize on when it sees that 1762 // the computed ISize is different from current ISize, and that's all we 1763 // need.) 1764 bool forceBResizeForMeasuringReflow = 1765 !aFlexItem.IsFrozen() || // Is the item flexible? 1766 !flexBasisNeedsToMeasureContent; // Are we *only* measuring it for 1767 // 'min-block-size:auto'? 1768 1769 const ReflowInput& flexContainerRI = *aItemReflowInput.mParentReflowInput; 1770 nscoord contentBSize = MeasureFlexItemContentBSize( 1771 aFlexItem, forceBResizeForMeasuringReflow, flexContainerRI); 1772 if (minSizeNeedsToMeasureContent) { 1773 contentSizeSuggestion = contentBSize; 1774 } 1775 if (flexBasisNeedsToMeasureContent) { 1776 aFlexItem.SetFlexBaseSizeAndMainSize(contentBSize); 1777 aFlexItem.SetIsFlexBaseSizeContentBSize(); 1778 } 1779 } 1780 1781 if (minSizeNeedsToMeasureContent) { 1782 // Clamp the content size suggestion by any definite min and max cross 1783 // size converted through the aspect ratio. 1784 if (aFlexItem.HasAspectRatio()) { 1785 contentSizeSuggestion = aFlexItem.ClampMainSizeViaCrossAxisConstraints( 1786 contentSizeSuggestion, aItemReflowInput); 1787 } 1788 1789 FLEX_LOGV("Content size suggestion: %d", contentSizeSuggestion); 1790 resolvedMinSize = std::min(resolvedMinSize, contentSizeSuggestion); 1791 1792 // Clamp the resolved min main size by the max main size if it's definite. 1793 if (aFlexItem.MainMaxSize() != NS_UNCONSTRAINEDSIZE) { 1794 resolvedMinSize = std::min(resolvedMinSize, aFlexItem.MainMaxSize()); 1795 } else if (MOZ_UNLIKELY(resolvedMinSize > nscoord_MAX)) { 1796 NS_WARNING("Bogus resolved auto min main size!"); 1797 // Our resolved min-size is bogus, probably due to some huge sizes in 1798 // the content. Clamp it to the valid nscoord range, so that we can at 1799 // least depend on it being <= the max-size (which is also the 1800 // nscoord_MAX sentinel value if we reach this point). 1801 resolvedMinSize = nscoord_MAX; 1802 } 1803 FLEX_LOGV("Resolved auto min main size: %d", resolvedMinSize); 1804 1805 if (resolvedMinSize == contentSizeSuggestion) { 1806 // When we are here, we've measured the item's content-based size, and 1807 // we used it as the resolved auto min main size. Record the fact so 1808 // that we can use it to determine whether we allow a flex item to grow 1809 // its block-size in ReflowFlexItem(). 1810 aFlexItem.SetIsMainMinSizeContentBSize(); 1811 } 1812 } 1813 } 1814 1815 if (isMainMinSizeAuto) { 1816 aFlexItem.UpdateMainMinSize(resolvedMinSize); 1817 } 1818 } 1819 1820 /** 1821 * A cached result for a flex item's block-axis measuring reflow. This cache 1822 * prevents us from doing exponential reflows in cases of deeply nested flex 1823 * and scroll frames. 1824 * 1825 * We store the cached value in the flex item's frame property table, for 1826 * simplicity. 1827 * 1828 * Right now, we cache the following as a "key", from the item's ReflowInput: 1829 * - its ComputedSize 1830 * - its min/max block size (in case its ComputedBSize is unconstrained) 1831 * - its AvailableBSize 1832 * ...and we cache the following as the "value", from the item's ReflowOutput: 1833 * - its final content-box BSize 1834 * 1835 * The assumption here is that a given flex item measurement from our "value" 1836 * won't change unless one of the pieces of the "key" change, or the flex 1837 * item's intrinsic size is marked as dirty (due to a style or DOM change). 1838 * (The latter will cause the cached value to be discarded, in 1839 * nsIFrame::MarkIntrinsicISizesDirty.) 1840 * 1841 * Note that the components of "Key" (mComputed{MinB,MaxB,}Size and 1842 * mAvailableBSize) are sufficient to catch any changes to the flex container's 1843 * size that the item may care about for its measuring reflow. Specifically: 1844 * - If the item cares about the container's size (e.g. if it has a percent 1845 * height and the container's height changes, in a horizontal-WM container) 1846 * then that'll be detectable via the item's ReflowInput's "ComputedSize()" 1847 * differing from the value in our Key. And the same applies for the 1848 * inline axis. 1849 * - If the item is fragmentable (pending bug 939897) and its measured BSize 1850 * depends on where it gets fragmented, then that sort of change can be 1851 * detected due to the item's ReflowInput's "AvailableBSize()" differing 1852 * from the value in our Key. 1853 * 1854 * One particular case to consider (& need to be sure not to break when 1855 * changing this class): the flex item's computed BSize may change between 1856 * measuring reflows due to how the mIsFlexContainerMeasuringBSize flag affects 1857 * size computation (see bug 1336708). This is one reason we need to use the 1858 * computed BSize as part of the key. 1859 */ 1860 class nsFlexContainerFrame::CachedBAxisMeasurement { 1861 struct Key { 1862 const LogicalSize mComputedSize; 1863 const nscoord mComputedMinBSize; 1864 const nscoord mComputedMaxBSize; 1865 const nscoord mAvailableBSize; 1866 1867 explicit Key(const ReflowInput& aRI) 1868 : mComputedSize(aRI.ComputedSize()), 1869 mComputedMinBSize(aRI.ComputedMinBSize()), 1870 mComputedMaxBSize(aRI.ComputedMaxBSize()), 1871 mAvailableBSize(aRI.AvailableBSize()) {} 1872 1873 bool operator==(const Key& aOther) const = default; 1874 }; 1875 1876 const Key mKey; 1877 1878 // This could/should be const, but it's non-const for now just because it's 1879 // assigned via a series of steps in the constructor body: 1880 nscoord mBSize; 1881 1882 public: 1883 CachedBAxisMeasurement(const ReflowInput& aReflowInput, 1884 const ReflowOutput& aReflowOutput) 1885 : mKey(aReflowInput) { 1886 // To get content-box bsize, we have to subtract off border & padding 1887 // (and floor at 0 in case the border/padding are too large): 1888 WritingMode itemWM = aReflowInput.GetWritingMode(); 1889 nscoord borderBoxBSize = aReflowOutput.BSize(itemWM); 1890 mBSize = 1891 borderBoxBSize - 1892 aReflowInput.ComputedLogicalBorderPadding(itemWM).BStartEnd(itemWM); 1893 mBSize = std::max(0, mBSize); 1894 } 1895 1896 /** 1897 * Returns true if this cached flex item measurement is valid for (i.e. can 1898 * be expected to match the output of) a measuring reflow whose input 1899 * parameters are given via aReflowInput. 1900 */ 1901 bool IsValidFor(const ReflowInput& aReflowInput) const { 1902 return mKey == Key(aReflowInput); 1903 } 1904 1905 nscoord BSize() const { return mBSize; } 1906 }; 1907 1908 /** 1909 * A cached copy of various metrics from a flex item's most recent final reflow. 1910 * It can be used to determine whether we can optimize away the flex item's 1911 * final reflow, when we perform an incremental reflow of its flex container. 1912 */ 1913 class CachedFinalReflowMetrics final { 1914 public: 1915 CachedFinalReflowMetrics(const ReflowInput& aReflowInput, 1916 const ReflowOutput& aReflowOutput) 1917 : CachedFinalReflowMetrics(aReflowInput.GetWritingMode(), aReflowInput, 1918 aReflowOutput) {} 1919 1920 CachedFinalReflowMetrics(const FlexItem& aItem, const LogicalSize& aSize) 1921 : mBorderPadding(aItem.BorderPadding().ConvertTo( 1922 aItem.GetWritingMode(), aItem.ContainingBlockWM())), 1923 mSize(aSize), 1924 mTreatBSizeAsIndefinite(aItem.TreatBSizeAsIndefinite()) {} 1925 1926 const LogicalSize& Size() const { return mSize; } 1927 const LogicalMargin& BorderPadding() const { return mBorderPadding; } 1928 bool TreatBSizeAsIndefinite() const { return mTreatBSizeAsIndefinite; } 1929 1930 private: 1931 // A convenience constructor with a WritingMode argument. 1932 CachedFinalReflowMetrics(WritingMode aWM, const ReflowInput& aReflowInput, 1933 const ReflowOutput& aReflowOutput) 1934 : mBorderPadding(aReflowInput.ComputedLogicalBorderPadding(aWM)), 1935 mSize(aReflowOutput.Size(aWM) - mBorderPadding.Size(aWM)), 1936 mTreatBSizeAsIndefinite(aReflowInput.mFlags.mTreatBSizeAsIndefinite) {} 1937 1938 // The flex item's border and padding, in its own writing-mode, that it used 1939 // used during its most recent "final reflow". 1940 LogicalMargin mBorderPadding; 1941 1942 // The flex item's content-box size, in its own writing-mode, that it used 1943 // during its most recent "final reflow". 1944 LogicalSize mSize; 1945 1946 // True if the flex item's BSize was considered "indefinite" in its most 1947 // recent "final reflow". (For a flex item "final reflow", this is fully 1948 // determined by the mTreatBSizeAsIndefinite flag in ReflowInput. See the 1949 // flag's documentation for more information.) 1950 bool mTreatBSizeAsIndefinite; 1951 }; 1952 1953 /** 1954 * When we instantiate/update a CachedFlexItemData, this enum must be used to 1955 * indicate the sort of reflow whose results we're capturing. This impacts 1956 * what we cache & how we use the cached information. 1957 */ 1958 enum class FlexItemReflowType { 1959 // A reflow to measure the block-axis size of a flex item (as an input to the 1960 // flex layout algorithm). 1961 Measuring, 1962 1963 // A reflow with the flex item's "final" size at the end of the flex layout 1964 // algorithm. 1965 Final, 1966 }; 1967 1968 /** 1969 * This class stores information about the conditions and results for the most 1970 * recent ReflowChild call that we made on a given flex item. This information 1971 * helps us reason about whether we can assume that a subsequent ReflowChild() 1972 * invocation is unnecessary & skippable. 1973 */ 1974 class nsFlexContainerFrame::CachedFlexItemData { 1975 public: 1976 CachedFlexItemData(const ReflowInput& aReflowInput, 1977 const ReflowOutput& aReflowOutput, 1978 FlexItemReflowType aType) { 1979 Update(aReflowInput, aReflowOutput, aType); 1980 } 1981 1982 // This method is intended to be called after we perform either a "measuring 1983 // reflow" or a "final reflow" for a given flex item. 1984 void Update(const ReflowInput& aReflowInput, 1985 const ReflowOutput& aReflowOutput, FlexItemReflowType aType) { 1986 if (aType == FlexItemReflowType::Measuring) { 1987 mBAxisMeasurement.reset(); 1988 mBAxisMeasurement.emplace(aReflowInput, aReflowOutput); 1989 // Clear any cached "last final reflow metrics", too, because now the most 1990 // recent reflow was *not* a "final reflow". 1991 mFinalReflowMetrics.reset(); 1992 return; 1993 } 1994 1995 MOZ_ASSERT(aType == FlexItemReflowType::Final); 1996 mFinalReflowMetrics.reset(); 1997 mFinalReflowMetrics.emplace(aReflowInput, aReflowOutput); 1998 } 1999 2000 // This method is intended to be called for situations where we decide to 2001 // skip a final reflow because we've just done a measuring reflow which left 2002 // us (and our descendants) with the correct sizes. In this scenario, we 2003 // still want to cache the size as if we did a final reflow (because we've 2004 // determined that the recent measuring reflow was sufficient). That way, 2005 // our flex container can still skip a final reflow for this item in the 2006 // future as long as conditions are right. 2007 void Update(const FlexItem& aItem, const LogicalSize& aSize) { 2008 MOZ_ASSERT(!mFinalReflowMetrics, 2009 "This version of the method is only intended to be called when " 2010 "the most recent reflow was a 'measuring reflow'; and that " 2011 "should have cleared out mFinalReflowMetrics"); 2012 2013 mFinalReflowMetrics.reset(); // Just in case this assert^ fails. 2014 mFinalReflowMetrics.emplace(aItem, aSize); 2015 } 2016 2017 // If the flex container needs a measuring reflow for the flex item, then the 2018 // resulting block-axis measurements can be cached here. If no measurement 2019 // has been needed so far, then this member will be Nothing(). 2020 Maybe<CachedBAxisMeasurement> mBAxisMeasurement; 2021 2022 // The metrics that the corresponding flex item used in its most recent 2023 // "final reflow". (Note: the assumption here is that this reflow was this 2024 // item's most recent reflow of any type. If the item ends up undergoing a 2025 // subsequent measuring reflow, then this value needs to be cleared, because 2026 // at that point it's no longer an accurate way of reasoning about the 2027 // current state of the frame tree.) 2028 Maybe<CachedFinalReflowMetrics> mFinalReflowMetrics; 2029 2030 // Instances of this class are stored under this frame property, on 2031 // frames that are flex items: 2032 NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, CachedFlexItemData) 2033 }; 2034 2035 void nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty( 2036 nsIFrame* aItemFrame) { 2037 MOZ_ASSERT(aItemFrame->IsFlexItem()); 2038 if (auto* cache = aItemFrame->GetProperty(CachedFlexItemData::Prop())) { 2039 cache->mBAxisMeasurement.reset(); 2040 cache->mFinalReflowMetrics.reset(); 2041 } 2042 } 2043 2044 const CachedBAxisMeasurement& nsFlexContainerFrame::MeasureBSizeForFlexItem( 2045 FlexItem& aItem, ReflowInput& aChildReflowInput) { 2046 auto* cachedData = aItem.Frame()->GetProperty(CachedFlexItemData::Prop()); 2047 2048 if (cachedData && cachedData->mBAxisMeasurement) { 2049 if (!aItem.Frame()->IsSubtreeDirty() && 2050 cachedData->mBAxisMeasurement->IsValidFor(aChildReflowInput)) { 2051 FLEX_ITEM_LOG(aItem.Frame(), 2052 "[perf] Accepted cached measurement: block-size %d", 2053 cachedData->mBAxisMeasurement->BSize()); 2054 return *(cachedData->mBAxisMeasurement); 2055 } 2056 FLEX_ITEM_LOG(aItem.Frame(), 2057 "[perf] Rejected cached measurement: block-size %d", 2058 cachedData->mBAxisMeasurement->BSize()); 2059 } else { 2060 FLEX_ITEM_LOG(aItem.Frame(), "[perf] No cached measurement"); 2061 } 2062 2063 // CachedFlexItemData is stored in item's writing mode, so we pass 2064 // aChildReflowInput into ReflowOutput's constructor. 2065 ReflowOutput childReflowOutput(aChildReflowInput); 2066 nsReflowStatus childStatus; 2067 2068 const ReflowChildFlags flags = ReflowChildFlags::NoMoveFrame; 2069 const WritingMode outerWM = GetWritingMode(); 2070 const LogicalPoint dummyPosition(outerWM); 2071 const nsSize dummyContainerSize; 2072 2073 // We use NoMoveFrame, so the position and container size used here are 2074 // unimportant. 2075 ReflowChild(aItem.Frame(), PresContext(), childReflowOutput, 2076 aChildReflowInput, outerWM, dummyPosition, dummyContainerSize, 2077 flags, childStatus); 2078 aItem.SetHadMeasuringReflow(); 2079 2080 // We always use unconstrained available block-size to measure flex items, 2081 // which means they should always complete. 2082 MOZ_ASSERT(childStatus.IsComplete(), 2083 "We gave flex item unconstrained available block-size, so it " 2084 "should be complete"); 2085 2086 // Tell the child we're done with its initial reflow. 2087 // (Necessary for e.g. GetBaseline() to work below w/out asserting) 2088 FinishReflowChild(aItem.Frame(), PresContext(), childReflowOutput, 2089 &aChildReflowInput, outerWM, dummyPosition, 2090 dummyContainerSize, flags); 2091 2092 aItem.SetAscent(childReflowOutput.BlockStartAscent()); 2093 2094 // Update (or add) our cached measurement, so that we can hopefully skip this 2095 // measuring reflow the next time around: 2096 if (cachedData) { 2097 cachedData->Update(aChildReflowInput, childReflowOutput, 2098 FlexItemReflowType::Measuring); 2099 } else { 2100 cachedData = new CachedFlexItemData(aChildReflowInput, childReflowOutput, 2101 FlexItemReflowType::Measuring); 2102 aItem.Frame()->SetProperty(CachedFlexItemData::Prop(), cachedData); 2103 } 2104 return *(cachedData->mBAxisMeasurement); 2105 } 2106 2107 /* virtual */ 2108 void nsFlexContainerFrame::MarkIntrinsicISizesDirty() { 2109 mCachedIntrinsicSizes.Clear(); 2110 nsContainerFrame::MarkIntrinsicISizesDirty(); 2111 } 2112 2113 nscoord nsFlexContainerFrame::MeasureFlexItemContentBSize( 2114 FlexItem& aFlexItem, bool aForceBResizeForMeasuringReflow, 2115 const ReflowInput& aParentReflowInput) { 2116 FLEX_ITEM_LOG(aFlexItem.Frame(), "Measuring item's content block-size"); 2117 2118 // Set up a reflow input for measuring the flex item's content block-size: 2119 WritingMode wm = aFlexItem.Frame()->GetWritingMode(); 2120 LogicalSize availSize = aParentReflowInput.ComputedSize(wm); 2121 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; 2122 2123 StyleSizeOverrides sizeOverrides; 2124 if (aFlexItem.IsStretched()) { 2125 sizeOverrides.mStyleISize.emplace(aFlexItem.StyleCrossSize()); 2126 FLEX_LOGV("Cross size override: %d", aFlexItem.CrossSize()); 2127 } 2128 sizeOverrides.mStyleBSize.emplace(StyleSize::Auto()); 2129 2130 ReflowInput childRIForMeasuringBSize( 2131 PresContext(), aParentReflowInput, aFlexItem.Frame(), availSize, 2132 Nothing(), {}, sizeOverrides, {ComputeSizeFlag::ShrinkWrap}); 2133 2134 // When measuring flex item's content block-size, disregard the item's 2135 // min-block-size and max-block-size by resetting both to to their 2136 // unconstraining (extreme) values. The flexbox layout algorithm does still 2137 // explicitly clamp both sizes when resolving the target main size. 2138 childRIForMeasuringBSize.SetComputedMinBSize(0); 2139 childRIForMeasuringBSize.SetComputedMaxBSize(NS_UNCONSTRAINEDSIZE); 2140 2141 if (aForceBResizeForMeasuringReflow) { 2142 childRIForMeasuringBSize.SetBResize(true); 2143 // Not 100% sure this is needed, but be conservative for now: 2144 childRIForMeasuringBSize.SetBResizeForPercentages(true); 2145 } 2146 2147 const CachedBAxisMeasurement& measurement = 2148 MeasureBSizeForFlexItem(aFlexItem, childRIForMeasuringBSize); 2149 2150 return measurement.BSize(); 2151 } 2152 2153 FlexItem::FlexItem(ReflowInput& aFlexItemReflowInput, float aFlexGrow, 2154 float aFlexShrink, nscoord aFlexBaseSize, 2155 nscoord aMainMinSize, nscoord aMainMaxSize, 2156 nscoord aTentativeCrossSize, nscoord aCrossMinSize, 2157 nscoord aCrossMaxSize, 2158 const FlexboxAxisTracker& aAxisTracker) 2159 : mFrame(aFlexItemReflowInput.mFrame), 2160 mFlexGrow(aFlexGrow), 2161 mFlexShrink(aFlexShrink), 2162 mAspectRatio(mFrame->GetAspectRatio()), 2163 mWM(aFlexItemReflowInput.GetWritingMode()), 2164 mCBWM(aAxisTracker.GetWritingMode()), 2165 mMainAxis(aAxisTracker.MainAxis()), 2166 mBorderPadding(aFlexItemReflowInput.ComputedLogicalBorderPadding(mCBWM)), 2167 mMargin(aFlexItemReflowInput.ComputedLogicalMargin(mCBWM)), 2168 mMainMinSize(aMainMinSize), 2169 mMainMaxSize(aMainMaxSize), 2170 mCrossMinSize(aCrossMinSize), 2171 mCrossMaxSize(aCrossMaxSize), 2172 mCrossSize(aTentativeCrossSize), 2173 mIsInlineAxisMainAxis(aAxisTracker.IsInlineAxisMainAxis(mWM)), 2174 mNeedsMinSizeAutoResolution(IsMinSizeAutoResolutionNeeded()) 2175 // mAlignSelf, mHasAnyAutoMargin see below 2176 { 2177 MOZ_ASSERT(mFrame, "expecting a non-null child frame"); 2178 MOZ_ASSERT(!mFrame->IsPlaceholderFrame(), 2179 "placeholder frames should not be treated as flex items"); 2180 MOZ_ASSERT(mFrame->IsFlexItem(), "mFrame must be a flex item!"); 2181 MOZ_ASSERT(mIsInlineAxisMainAxis == 2182 nsFlexContainerFrame::IsItemInlineAxisMainAxis(mFrame), 2183 "public API should be consistent with internal state (about " 2184 "whether flex item's inline axis is flex container's main axis)"); 2185 2186 const auto* container = 2187 static_cast<nsFlexContainerFrame*>(mFrame->GetParent()); 2188 std::tie(mAlignSelf, mAlignSelfFlags) = 2189 container->UsedAlignSelfAndFlagsForItem(mFrame); 2190 2191 // Our main-size is considered definite if any of these are true: 2192 // (a) main axis is the item's inline axis. 2193 // (b) flex container has definite main size. 2194 // (c) flex item has a definite flex basis. 2195 // 2196 // Hence, we need to take care to treat the final main-size as *indefinite* 2197 // if none of these conditions are satisfied. 2198 if (mIsInlineAxisMainAxis) { 2199 // The item's block-axis is the flex container's cross axis. We don't need 2200 // any special handling to treat cross sizes as indefinite, because the 2201 // cases where we stomp on the cross size with a definite value are all... 2202 // - situations where the spec requires us to treat the cross size as 2203 // definite; specifically, `align-self:stretch` whose cross size is 2204 // definite. 2205 // - situations where definiteness doesn't matter (e.g. for an element with 2206 // an aspect ratio, which for now are all leaf nodes and hence 2207 // can't have any percent-height descendants that would care about the 2208 // definiteness of its size. (Once bug 1528375 is fixed, we might need to 2209 // be more careful about definite vs. indefinite sizing on flex items with 2210 // aspect ratios.) 2211 mTreatBSizeAsIndefinite = false; 2212 } else { 2213 // The item's block-axis is the flex container's main axis. So, the flex 2214 // item's main size is its BSize, and is considered definite under certain 2215 // conditions laid out for definite flex-item main-sizes in the spec. 2216 const ReflowInput* containerRI = aFlexItemReflowInput.mParentReflowInput; 2217 if (aAxisTracker.IsRowOriented() || 2218 (containerRI->ComputedBSize() != NS_UNCONSTRAINEDSIZE && 2219 !containerRI->mFlags.mTreatBSizeAsIndefinite)) { 2220 // The flex *container* has a definite main-size (either by being 2221 // row-oriented [and using its own inline size which is by definition 2222 // definite, or by being column-oriented and having a definite 2223 // block-size). The spec says this means all of the flex items' 2224 // post-flexing main sizes should *also* be treated as definite. 2225 mTreatBSizeAsIndefinite = false; 2226 } else if (aFlexBaseSize != NS_UNCONSTRAINEDSIZE) { 2227 // The flex item has a definite flex basis, which we'll treat as making 2228 // its main-size definite. 2229 mTreatBSizeAsIndefinite = false; 2230 } else { 2231 // Otherwise, we have to treat the item's BSize as indefinite. 2232 mTreatBSizeAsIndefinite = true; 2233 } 2234 } 2235 2236 SetFlexBaseSizeAndMainSize(aFlexBaseSize); 2237 2238 const nsStyleMargin* styleMargin = aFlexItemReflowInput.mStyleMargin; 2239 const auto anchorResolutionParams = 2240 AnchorPosResolutionParams::From(&aFlexItemReflowInput); 2241 mHasAnyAutoMargin = 2242 styleMargin->HasInlineAxisAuto(mCBWM, anchorResolutionParams) || 2243 styleMargin->HasBlockAxisAuto(mCBWM, anchorResolutionParams); 2244 2245 // Assert that any "auto" margin components are set to 0. 2246 // (We'll resolve them later; until then, we want to treat them as 0-sized.) 2247 #ifdef DEBUG 2248 { 2249 for (const auto side : LogicalSides::All) { 2250 if (styleMargin->GetMargin(side, mCBWM, anchorResolutionParams) 2251 ->IsAuto()) { 2252 MOZ_ASSERT(GetMarginComponentForSide(side) == 0, 2253 "Someone else tried to resolve our auto margin"); 2254 } 2255 } 2256 } 2257 #endif // DEBUG 2258 2259 if (mAlignSelf == StyleAlignFlags::BASELINE || 2260 mAlignSelf == StyleAlignFlags::LAST_BASELINE) { 2261 // Check which of the item's baselines we're meant to use (first vs. last) 2262 const bool usingItemFirstBaseline = 2263 (mAlignSelf == StyleAlignFlags::BASELINE); 2264 if (IsBlockAxisCrossAxis()) { 2265 // The flex item wants to be aligned in the cross axis using one of its 2266 // baselines; and the cross axis is the item's block axis, so 2267 // baseline-alignment in that axis makes sense. 2268 2269 // To determine the item's baseline sharing group, we check whether the 2270 // item's block axis has the same vs. opposite flow direction as the 2271 // corresponding LogicalAxis on the flex container. We do this by 2272 // getting the physical side that corresponds to these axes' "logical 2273 // start" sides, and we compare those physical sides to find out if 2274 // they're the same vs. opposite. 2275 mozilla::Side itemBlockStartSide = mWM.PhysicalSide(LogicalSide::BStart); 2276 2277 // (Note: this is *not* the "flex-start" side; rather, it's the *logical* 2278 // i.e. WM-relative block-start or inline-start side.) 2279 mozilla::Side containerStartSideInCrossAxis = mCBWM.PhysicalSide( 2280 MakeLogicalSide(aAxisTracker.CrossAxis(), LogicalEdge::Start)); 2281 2282 // We already know these two Sides (the item's block-start and the 2283 // container's 'logical start' side for its cross axis) are in the same 2284 // physical axis, since we're inside of a check for 2285 // FlexItem::IsBlockAxisCrossAxis(). So these two Sides must be either 2286 // the same physical side or opposite from each other. If the Sides are 2287 // the same, then the flow direction is the same, which means the item's 2288 // {first,last} baseline participates in the {first,last} 2289 // baseline-sharing group in its FlexLine. Otherwise, the flow direction 2290 // is opposite, and so the item's {first,last} baseline participates in 2291 // the opposite i.e. {last,first} baseline-sharing group. This is 2292 // roughly per css-align-3 section 9.2, specifically the definition of 2293 // what makes baseline alignment preferences "compatible". 2294 bool itemBlockAxisFlowDirMatchesContainer = 2295 (itemBlockStartSide == containerStartSideInCrossAxis); 2296 mBaselineSharingGroup = 2297 (itemBlockAxisFlowDirMatchesContainer == usingItemFirstBaseline) 2298 ? BaselineSharingGroup::First 2299 : BaselineSharingGroup::Last; 2300 } else { 2301 // The flex item wants to be aligned in the cross axis using one of its 2302 // baselines, but we cannot get its baseline because the FlexItem's block 2303 // axis is *orthogonal* to the container's cross axis. To handle this, we 2304 // are supposed to synthesize a baseline from the item's border box and 2305 // using that for baseline alignment. 2306 mBaselineSharingGroup = usingItemFirstBaseline 2307 ? BaselineSharingGroup::First 2308 : BaselineSharingGroup::Last; 2309 } 2310 } 2311 } 2312 2313 // Simplified constructor for creating a special "strut" FlexItem, for a child 2314 // with visibility:collapse. The strut has 0 main-size, and it only exists to 2315 // impose a minimum cross size on whichever FlexLine it ends up in. 2316 FlexItem::FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize, 2317 WritingMode aContainerWM, 2318 const FlexboxAxisTracker& aAxisTracker) 2319 : mFrame(aChildFrame), 2320 mWM(aChildFrame->GetWritingMode()), 2321 mCBWM(aContainerWM), 2322 mMainAxis(aAxisTracker.MainAxis()), 2323 mBorderPadding(mCBWM), 2324 mMargin(mCBWM), 2325 mCrossSize(aCrossSize), 2326 // Struts don't do layout, so its WM doesn't matter at this point. So, we 2327 // just share container's WM for simplicity: 2328 mIsFrozen(true), 2329 mIsStrut(true), // (this is the constructor for making struts, after all) 2330 mAlignSelf(StyleAlignFlags::FLEX_START) { 2331 MOZ_ASSERT(mFrame, "expecting a non-null child frame"); 2332 MOZ_ASSERT(mFrame->StyleVisibility()->IsCollapse(), 2333 "Should only make struts for children with 'visibility:collapse'"); 2334 MOZ_ASSERT(!mFrame->IsPlaceholderFrame(), 2335 "placeholder frames should not be treated as flex items"); 2336 MOZ_ASSERT(!mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW), 2337 "out-of-flow frames should not be treated as flex items"); 2338 } 2339 2340 bool FlexItem::IsMinSizeAutoResolutionNeeded() const { 2341 // We'll need special behavior for "min-[width|height]:auto" (whichever is in 2342 // the flex container's main axis) iff: 2343 // (a) its computed value is "auto", and 2344 // (b) the item is *not* a scroll container. (A scroll container's automatic 2345 // minimum size is zero.) 2346 // https://drafts.csswg.org/css-flexbox-1/#min-size-auto 2347 // 2348 // Note that the scroll container case is redefined to be looking at the 2349 // computed value instead, see https://github.com/w3c/csswg-drafts/issues/7714 2350 const auto mainMinSize = Frame()->StylePosition()->MinSize( 2351 MainAxis(), ContainingBlockWM(), 2352 AnchorPosResolutionParams::From(Frame())); 2353 2354 // "min-{height,width}:stretch" never produces an automatic minimum size. You 2355 // might think it would result in an automatic min-size if the containing 2356 // block size is indefinite, but "stretch" is instead treated as 0px in that 2357 // case rather than auto. This WPT requires this behavior: 2358 // https://wpt.live/css/css-sizing/stretch/indefinite-4.html More details & 2359 // discussion here: https://github.com/w3c/csswg-drafts/issues/11006 2360 if (mainMinSize->BehavesLikeStretchOnBlockAxis()) { 2361 return false; 2362 } 2363 return IsAutoOrEnumOnBSize(*mainMinSize, IsInlineAxisMainAxis()) && 2364 !Frame()->StyleDisplay()->IsScrollableOverflow(); 2365 } 2366 2367 Maybe<nscoord> FlexItem::MeasuredBSize() const { 2368 auto* cachedData = 2369 Frame()->FirstInFlow()->GetProperty(CachedFlexItemData::Prop()); 2370 if (!cachedData || !cachedData->mBAxisMeasurement) { 2371 return Nothing(); 2372 } 2373 return Some(cachedData->mBAxisMeasurement->BSize()); 2374 } 2375 2376 nscoord FlexItem::BaselineOffsetFromOuterCrossEdge( 2377 mozilla::Side aStartSide, bool aUseFirstLineBaseline) const { 2378 // NOTE: 2379 // * We only use baselines for aligning in the flex container's cross axis. 2380 // * Baselines are a measurement in the item's block axis. 2381 if (IsBlockAxisMainAxis()) { 2382 // We get here if the item's block axis is *orthogonal* the container's 2383 // cross axis. For example, a flex item with writing-mode:horizontal-tb in a 2384 // column-oriented flex container. We need to synthesize the item's baseline 2385 // from its border-box edge. 2386 const bool isMainAxisHorizontal = 2387 mCBWM.PhysicalAxis(MainAxis()) == PhysicalAxis::Horizontal; 2388 2389 // When the main axis is horizontal, the synthesized baseline is the bottom 2390 // edge of the item's border-box. Otherwise, when the main axis is vertical, 2391 // the left edge. This is for compatibility with Google Chrome. 2392 nscoord marginTopOrLeftToBaseline = 2393 isMainAxisHorizontal ? PhysicalMargin().top : PhysicalMargin().left; 2394 if (mCBWM.IsAlphabeticalBaseline()) { 2395 marginTopOrLeftToBaseline += (isMainAxisHorizontal ? CrossSize() : 0); 2396 } else { 2397 MOZ_ASSERT(mCBWM.IsCentralBaseline()); 2398 marginTopOrLeftToBaseline += CrossSize() / 2; 2399 } 2400 2401 return aStartSide == mozilla::eSideTop || aStartSide == mozilla::eSideLeft 2402 ? marginTopOrLeftToBaseline 2403 : OuterCrossSize() - marginTopOrLeftToBaseline; 2404 } 2405 2406 // We get here if the item's block axis is parallel (or antiparallel) to the 2407 // container's cross axis. We call ResolvedAscent() to get the item's 2408 // baseline. If the item has no baseline, the method will synthesize one from 2409 // the border-box edge. 2410 MOZ_ASSERT(IsBlockAxisCrossAxis(), 2411 "Only expecting to be doing baseline computations when the " 2412 "cross axis is the block axis"); 2413 2414 mozilla::Side itemBlockStartSide = mWM.PhysicalSide(LogicalSide::BStart); 2415 2416 nscoord marginBStartToBaseline = ResolvedAscent(aUseFirstLineBaseline) + 2417 PhysicalMargin().Side(itemBlockStartSide); 2418 2419 return (aStartSide == itemBlockStartSide) 2420 ? marginBStartToBaseline 2421 : OuterCrossSize() - marginBStartToBaseline; 2422 } 2423 2424 bool FlexItem::IsCrossSizeAuto() const { 2425 const auto* styleFrame = nsLayoutUtils::GetStyleFrame(mFrame); 2426 const nsStylePosition* stylePos = styleFrame->StylePosition(); 2427 const auto anchorResolutionParams = 2428 AnchorPosResolutionParams::From(styleFrame); 2429 // Check whichever component is in the flex container's cross axis. 2430 // (IsInlineAxisCrossAxis() tells us whether that's our ISize or BSize, in 2431 // terms of our own WritingMode, mWM.) 2432 return IsInlineAxisCrossAxis() 2433 ? stylePos->ISize(mWM, anchorResolutionParams)->IsAuto() 2434 : stylePos->BSize(mWM, anchorResolutionParams)->IsAuto(); 2435 } 2436 2437 bool FlexItem::IsCrossSizeDefinite(const ReflowInput& aItemReflowInput) const { 2438 if (IsStretched()) { 2439 // Definite cross-size, imposed via 'align-self:stretch' & flex container. 2440 return true; 2441 } 2442 2443 const nsStylePosition* pos = aItemReflowInput.mStylePosition; 2444 const auto anchorResolutionParams = 2445 AnchorPosResolutionParams::From(&aItemReflowInput); 2446 const auto itemWM = GetWritingMode(); 2447 2448 // The logic here should be similar to the logic for isAutoISize/isAutoBSize 2449 // in nsContainerFrame::ComputeSizeWithIntrinsicDimensions(). 2450 if (IsInlineAxisCrossAxis()) { 2451 return !pos->ISize(itemWM, anchorResolutionParams)->IsAuto(); 2452 } 2453 2454 nscoord cbBSize = aItemReflowInput.mContainingBlockSize.BSize(itemWM); 2455 return !nsLayoutUtils::IsAutoBSize( 2456 *pos->BSize(itemWM, anchorResolutionParams), cbBSize); 2457 } 2458 2459 void FlexItem::ResolveFlexBaseSizeFromAspectRatio( 2460 const ReflowInput& aItemReflowInput) { 2461 // This implements the Flex Layout Algorithm Step 3B: 2462 // https://drafts.csswg.org/css-flexbox-1/#algo-main-item 2463 // If the flex item has ... 2464 // - an aspect ratio, 2465 // - a [used] flex-basis of 'content', and 2466 // - a definite cross size 2467 // then the flex base size is calculated from its inner cross size and the 2468 // flex item's preferred aspect ratio. 2469 if (HasAspectRatio() && 2470 nsFlexContainerFrame::IsUsedFlexBasisContent( 2471 aItemReflowInput.mStylePosition->mFlexBasis, 2472 *aItemReflowInput.mStylePosition->Size( 2473 MainAxis(), mCBWM, 2474 AnchorPosResolutionParams::From(&aItemReflowInput))) && 2475 IsCrossSizeDefinite(aItemReflowInput)) { 2476 const LogicalSize contentBoxSizeToBoxSizingAdjust = 2477 aItemReflowInput.mStylePosition->mBoxSizing == StyleBoxSizing::Border 2478 ? BorderPadding().Size(mCBWM) 2479 : LogicalSize(mCBWM); 2480 const nscoord mainSizeFromRatio = mAspectRatio.ComputeRatioDependentSize( 2481 MainAxis(), mCBWM, CrossSize(), contentBoxSizeToBoxSizingAdjust); 2482 SetFlexBaseSizeAndMainSize(mainSizeFromRatio); 2483 } 2484 } 2485 2486 uint32_t FlexItem::NumAutoMarginsInAxis(LogicalAxis aAxis) const { 2487 uint32_t numAutoMargins = 0; 2488 const auto* styleMargin = mFrame->StyleMargin(); 2489 const auto anchorResolutionParams = AnchorPosResolutionParams::From(mFrame); 2490 for (const auto edge : {LogicalEdge::Start, LogicalEdge::End}) { 2491 const auto side = MakeLogicalSide(aAxis, edge); 2492 if (styleMargin->GetMargin(side, mCBWM, anchorResolutionParams)->IsAuto()) { 2493 numAutoMargins++; 2494 } 2495 } 2496 2497 // Mostly for clarity: 2498 MOZ_ASSERT(numAutoMargins <= 2, 2499 "We're just looking at one item along one dimension, so we " 2500 "should only have examined 2 margins"); 2501 2502 return numAutoMargins; 2503 } 2504 2505 bool FlexItem::CanMainSizeInfluenceCrossSize() const { 2506 if (mIsStretched) { 2507 // We've already had our cross-size stretched for "align-self:stretch"). 2508 // The container is imposing its cross size on us. 2509 return false; 2510 } 2511 2512 if (mIsStrut) { 2513 // Struts (for visibility:collapse items) have a predetermined size; 2514 // no need to measure anything. 2515 return false; 2516 } 2517 2518 if (HasAspectRatio()) { 2519 // For flex items that have an aspect ratio (and maintain it, i.e. are 2520 // not stretched, which we already checked above): changes to main-size 2521 // *do* influence the cross size. 2522 return true; 2523 } 2524 2525 if (IsInlineAxisCrossAxis()) { 2526 // If we get here, this function is really asking: "can changes to this 2527 // item's block size have an influence on its inline size"? 2528 2529 // If a flex item's intrinsic inline size or its descendants' inline size 2530 // contributions depend on the item's block size, the answer is "yes". 2531 if (mFrame->HasAnyStateBits( 2532 NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) { 2533 return true; 2534 } 2535 // For blocks and tables, the answer is "no" (aside from the above special 2536 // case). 2537 if (mFrame->IsBlockFrame() || mFrame->IsTableWrapperFrame()) { 2538 // XXXdholbert (Maybe use an IsFrameOfType query or something more 2539 // general to test this across all frame types? For now, I'm just 2540 // optimizing for block and table, since those are common containers that 2541 // can contain arbitrarily-large subtrees (and that reliably have ISize 2542 // being unaffected by BSize, per CSS2). So optimizing away needless 2543 // relayout is possible & especially valuable for these containers.) 2544 return false; 2545 } 2546 // Other opt-outs can go here, as they're identified as being useful 2547 // (particularly for containers where an extra reflow is expensive). But in 2548 // general, we have to assume that a flexed BSize *could* influence the 2549 // ISize. Some examples where this can definitely happen: 2550 // * Intrinsically-sized multicol with fixed-ISize columns, which adds 2551 // columns (i.e. grows in inline axis) depending on its block size. 2552 // * Intrinsically-sized multi-line column-oriented flex container, which 2553 // adds flex lines (i.e. grows in inline axis) depending on its block size. 2554 } 2555 2556 // Default assumption, if we haven't proven otherwise: the resolved main size 2557 // *can* change the cross size. 2558 return true; 2559 } 2560 2561 nscoord FlexItem::ClampMainSizeViaCrossAxisConstraints( 2562 nscoord aMainSize, const ReflowInput& aItemReflowInput) const { 2563 MOZ_ASSERT(HasAspectRatio(), "Caller should've checked the ratio is valid!"); 2564 2565 const LogicalSize contentBoxSizeToBoxSizingAdjust = 2566 aItemReflowInput.mStylePosition->mBoxSizing == StyleBoxSizing::Border 2567 ? BorderPadding().Size(mCBWM) 2568 : LogicalSize(mCBWM); 2569 2570 const nscoord mainMinSizeFromRatio = mAspectRatio.ComputeRatioDependentSize( 2571 MainAxis(), mCBWM, CrossMinSize(), contentBoxSizeToBoxSizingAdjust); 2572 nscoord clampedMainSize = std::max(aMainSize, mainMinSizeFromRatio); 2573 2574 if (CrossMaxSize() != NS_UNCONSTRAINEDSIZE) { 2575 const nscoord mainMaxSizeFromRatio = mAspectRatio.ComputeRatioDependentSize( 2576 MainAxis(), mCBWM, CrossMaxSize(), contentBoxSizeToBoxSizingAdjust); 2577 clampedMainSize = std::min(clampedMainSize, mainMaxSizeFromRatio); 2578 } 2579 2580 return clampedMainSize; 2581 } 2582 2583 /** 2584 * Returns true if aFrame or any of its children have the 2585 * NS_FRAME_CONTAINS_RELATIVE_BSIZE flag set -- i.e. if any of these frames (or 2586 * their descendants) might have a relative-BSize dependency on aFrame (or its 2587 * ancestors). 2588 */ 2589 static bool FrameHasRelativeBSizeDependency(nsIFrame* aFrame) { 2590 if (aFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) { 2591 return true; 2592 } 2593 for (const auto& childList : aFrame->ChildLists()) { 2594 for (nsIFrame* childFrame : childList.mList) { 2595 if (childFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) { 2596 return true; 2597 } 2598 } 2599 } 2600 return false; 2601 } 2602 2603 bool FlexItem::NeedsFinalReflow(const ReflowInput& aParentReflowInput) const { 2604 if (!StaticPrefs::layout_flexbox_item_final_reflow_optimization_enabled()) { 2605 FLEX_ITEM_LOG(mFrame, 2606 "[perf] Item needed a final reflow due to optimization being " 2607 "disabled via the preference"); 2608 return true; 2609 } 2610 2611 // NOTE: We can have continuations from an earlier constrained reflow. 2612 if (mFrame->GetPrevInFlow() || mFrame->GetNextInFlow()) { 2613 // This is an item has continuation(s). Reflow it. 2614 FLEX_ITEM_LOG(mFrame, 2615 "[frag] Item needed a final reflow due to continuation(s)"); 2616 return true; 2617 } 2618 2619 // A flex item can grow its block-size in a fragmented context if there's any 2620 // force break within it (bug 1663079), or if it has a repeated table header 2621 // or footer (bug 1744363). We currently always reflow it. 2622 // 2623 // Bug 1815294: investigate if we can design a more specific condition to 2624 // prevent triggering O(n^2) behavior when printing a deeply-nested flex 2625 // container. 2626 if (aParentReflowInput.IsInFragmentedContext()) { 2627 FLEX_ITEM_LOG(mFrame, 2628 "[frag] Item needed both a measuring reflow and a final " 2629 "reflow due to being in a fragmented context"); 2630 return true; 2631 } 2632 2633 // Flex item's final content-box size (in terms of its own writing-mode): 2634 const LogicalSize finalSize = mIsInlineAxisMainAxis 2635 ? LogicalSize(mWM, mMainSize, mCrossSize) 2636 : LogicalSize(mWM, mCrossSize, mMainSize); 2637 2638 if (HadMeasuringReflow()) { 2639 // We've already reflowed this flex item once, to measure it. In that 2640 // reflow, did its frame happen to end up with the correct final size 2641 // that the flex container would like it to have? 2642 if (finalSize != mFrame->ContentSize(mWM)) { 2643 // The measuring reflow left the item with a different size than its 2644 // final flexed size. So, we need to reflow to give it the correct size. 2645 FLEX_ITEM_LOG(mFrame, 2646 "[perf] Item needed both a measuring reflow and a final " 2647 "reflow due to measured size disagreeing with final size"); 2648 return true; 2649 } 2650 2651 if (FrameHasRelativeBSizeDependency(mFrame)) { 2652 // This item has descendants with relative BSizes who may care that its 2653 // size may now be considered "definite" in the final reflow (whereas it 2654 // was indefinite during the measuring reflow). 2655 FLEX_ITEM_LOG(mFrame, 2656 "[perf] Item needed both a measuring reflow and a final " 2657 "reflow due to BSize potentially becoming definite"); 2658 return true; 2659 } 2660 2661 // If we get here, then this flex item had a measuring reflow, it left us 2662 // with the correct size, none of its descendants care that its BSize may 2663 // now be considered definite, and it can fit into the available block-size. 2664 // So it doesn't need a final reflow. 2665 // 2666 // We now cache this size as if we had done a final reflow (because we've 2667 // determined that the measuring reflow was effectively equivalent). This 2668 // way, in our next time through flex layout, we may be able to skip both 2669 // the measuring reflow *and* the final reflow (if conditions are the same 2670 // as they are now). 2671 if (auto* cache = mFrame->GetProperty(CachedFlexItemData::Prop())) { 2672 cache->Update(*this, finalSize); 2673 } 2674 2675 return false; 2676 } 2677 2678 // This item didn't receive a measuring reflow (at least, not during this 2679 // reflow of our flex container). We may still be able to skip reflowing it 2680 // (i.e. return false from this function), if its subtree is clean & its most 2681 // recent "final reflow" had it at the correct content-box size & 2682 // definiteness. 2683 // Let's check for each condition that would still require us to reflow: 2684 if (mFrame->IsSubtreeDirty()) { 2685 FLEX_ITEM_LOG( 2686 mFrame, 2687 "[perf] Item needed a final reflow due to its subtree being dirty"); 2688 return true; 2689 } 2690 2691 // Cool; this item & its subtree haven't experienced any style/content 2692 // changes that would automatically require a reflow. 2693 2694 // Did we cache the metrics from its most recent "final reflow"? 2695 auto* cache = mFrame->GetProperty(CachedFlexItemData::Prop()); 2696 if (!cache || !cache->mFinalReflowMetrics) { 2697 FLEX_ITEM_LOG(mFrame, 2698 "[perf] Item needed a final reflow due to lacking a cached " 2699 "mFinalReflowMetrics (maybe cache was cleared)"); 2700 return true; 2701 } 2702 2703 // Does the cached size match our current size? 2704 if (cache->mFinalReflowMetrics->Size() != finalSize) { 2705 FLEX_ITEM_LOG(mFrame, 2706 "[perf] Item needed a final reflow due to having a different " 2707 "content box size vs. its most recent final reflow"); 2708 return true; 2709 } 2710 2711 // Does the cached border and padding match our current ones? 2712 // 2713 // Note: this is just to detect cases where we have a percent padding whose 2714 // basis has changed. Any other sort of change to BorderPadding() (e.g. a new 2715 // specified value) should result in the frame being marked dirty via proper 2716 // change hint (see nsStylePadding::CalcDifference()), which will force it to 2717 // reflow. 2718 if (cache->mFinalReflowMetrics->BorderPadding() != 2719 BorderPadding().ConvertTo(mWM, mCBWM)) { 2720 FLEX_ITEM_LOG(mFrame, 2721 "[perf] Item needed a final reflow due to having a different " 2722 "border and padding vs. its most recent final reflow"); 2723 return true; 2724 } 2725 2726 // The flex container is giving this flex item the same size that the item 2727 // had on its most recent "final reflow". But if its definiteness changed and 2728 // one of the descendants cares, then it would still need a reflow. 2729 if (cache->mFinalReflowMetrics->TreatBSizeAsIndefinite() != 2730 mTreatBSizeAsIndefinite && 2731 FrameHasRelativeBSizeDependency(mFrame)) { 2732 FLEX_ITEM_LOG(mFrame, 2733 "[perf] Item needed a final reflow due to having its BSize " 2734 "change definiteness & having a rel-BSize child"); 2735 return true; 2736 } 2737 2738 // If we get here, we can skip the final reflow! (The item's subtree isn't 2739 // dirty, and our current conditions are sufficiently similar to the most 2740 // recent "final reflow" that it should have left our subtree in the correct 2741 // state.) 2742 FLEX_ITEM_LOG(mFrame, "[perf] Item didn't need a final reflow"); 2743 return false; 2744 } 2745 2746 // Keeps track of our position along a particular axis (where a '0' position 2747 // corresponds to the 'start' edge of that axis). 2748 // This class shouldn't be instantiated directly -- rather, it should only be 2749 // instantiated via its subclasses defined below. 2750 class MOZ_STACK_CLASS PositionTracker { 2751 public: 2752 // Accessor for the current value of the position that we're tracking. 2753 inline nscoord Position() const { return mPosition; } 2754 inline LogicalAxis Axis() const { return mAxis; } 2755 2756 inline LogicalSide StartSide() { 2757 return MakeLogicalSide( 2758 mAxis, mIsAxisReversed ? LogicalEdge::End : LogicalEdge::Start); 2759 } 2760 2761 inline LogicalSide EndSide() { 2762 return MakeLogicalSide( 2763 mAxis, mIsAxisReversed ? LogicalEdge::Start : LogicalEdge::End); 2764 } 2765 2766 // Advances our position across the start edge of the given margin, in the 2767 // axis we're tracking. 2768 void EnterMargin(const LogicalMargin& aMargin) { 2769 mPosition += aMargin.Side(StartSide(), mWM); 2770 } 2771 2772 // Advances our position across the end edge of the given margin, in the axis 2773 // we're tracking. 2774 void ExitMargin(const LogicalMargin& aMargin) { 2775 mPosition += aMargin.Side(EndSide(), mWM); 2776 } 2777 2778 // Advances our current position from the start side of a child frame's 2779 // border-box to the frame's upper or left edge (depending on our axis). 2780 // (Note that this is a no-op if our axis grows in the same direction as 2781 // the corresponding logical axis.) 2782 void EnterChildFrame(nscoord aChildFrameSize) { 2783 if (mIsAxisReversed) { 2784 mPosition += aChildFrameSize; 2785 } 2786 } 2787 2788 // Advances our current position from a frame's upper or left border-box edge 2789 // (whichever is in the axis we're tracking) to the 'end' side of the frame 2790 // in the axis that we're tracking. (Note that this is a no-op if our axis 2791 // is reversed with respect to the corresponding logical axis.) 2792 void ExitChildFrame(nscoord aChildFrameSize) { 2793 if (!mIsAxisReversed) { 2794 mPosition += aChildFrameSize; 2795 } 2796 } 2797 2798 // Delete copy-constructor & reassignment operator, to prevent accidental 2799 // (unnecessary) copying. 2800 PositionTracker(const PositionTracker&) = delete; 2801 PositionTracker& operator=(const PositionTracker&) = delete; 2802 2803 protected: 2804 // Protected constructor, to be sure we're only instantiated via a subclass. 2805 PositionTracker(WritingMode aWM, LogicalAxis aAxis, bool aIsAxisReversed) 2806 : mWM(aWM), mAxis(aAxis), mIsAxisReversed(aIsAxisReversed) {} 2807 2808 // Member data: 2809 // The position we're tracking. 2810 nscoord mPosition = 0; 2811 2812 // The flex container's writing mode. 2813 const WritingMode mWM; 2814 2815 // The axis along which we're moving. 2816 const LogicalAxis mAxis = LogicalAxis::Inline; 2817 2818 // Is the axis along which we're moving reversed (e.g. LTR vs RTL) with 2819 // respect to the corresponding axis on the flex container's WM? 2820 const bool mIsAxisReversed = false; 2821 }; 2822 2823 // Tracks our position in the main axis, when we're laying out flex items. 2824 // The "0" position represents the main-start edge of the flex container's 2825 // content-box. 2826 class MOZ_STACK_CLASS MainAxisPositionTracker : public PositionTracker { 2827 public: 2828 MainAxisPositionTracker(const FlexboxAxisTracker& aAxisTracker, 2829 const FlexLine* aLine, 2830 const StyleContentDistribution& aJustifyContent, 2831 nscoord aContentBoxMainSize); 2832 2833 ~MainAxisPositionTracker() { 2834 MOZ_ASSERT(mNumPackingSpacesRemaining == 0, 2835 "miscounted the number of packing spaces"); 2836 MOZ_ASSERT(mNumAutoMarginsInMainAxis == 0, 2837 "miscounted the number of auto margins"); 2838 } 2839 2840 // Advances past the gap space (if any) between two flex items 2841 void TraverseGap(nscoord aGapSize) { mPosition += aGapSize; } 2842 2843 // Advances past the packing space (if any) between two flex items 2844 void TraversePackingSpace(); 2845 2846 // If aItem has any 'auto' margins in the main axis, this method updates the 2847 // corresponding values in its margin. 2848 void ResolveAutoMarginsInMainAxis(FlexItem& aItem); 2849 2850 private: 2851 nscoord mPackingSpaceRemaining = 0; 2852 uint32_t mNumAutoMarginsInMainAxis = 0; 2853 uint32_t mNumPackingSpacesRemaining = 0; 2854 StyleContentDistribution mJustifyContent = {StyleAlignFlags::AUTO}; 2855 }; 2856 2857 // Utility class for managing our position along the cross axis along 2858 // the whole flex container (at a higher level than a single line). 2859 // The "0" position represents the cross-start edge of the flex container's 2860 // content-box. 2861 class MOZ_STACK_CLASS CrossAxisPositionTracker : public PositionTracker { 2862 public: 2863 CrossAxisPositionTracker(nsTArray<FlexLine>& aLines, 2864 const ReflowInput& aReflowInput, 2865 nscoord aContentBoxCrossSize, 2866 bool aIsCrossSizeDefinite, 2867 const FlexboxAxisTracker& aAxisTracker, 2868 const nscoord aCrossGapSize); 2869 2870 // Advances past the gap (if any) between two flex lines 2871 void TraverseGap() { mPosition += mCrossGapSize; } 2872 2873 // Advances past the packing space (if any) between two flex lines 2874 void TraversePackingSpace(); 2875 2876 // Advances past the given FlexLine 2877 void TraverseLine(FlexLine& aLine) { mPosition += aLine.LineCrossSize(); } 2878 2879 // Redeclare the frame-related methods from PositionTracker with 2880 // = delete, to be sure (at compile time) that no client code can invoke 2881 // them. (Unlike the other PositionTracker derived classes, this class here 2882 // deals with FlexLines, not with individual FlexItems or frames.) 2883 void EnterMargin(const LogicalMargin& aMargin) = delete; 2884 void ExitMargin(const LogicalMargin& aMargin) = delete; 2885 void EnterChildFrame(nscoord aChildFrameSize) = delete; 2886 void ExitChildFrame(nscoord aChildFrameSize) = delete; 2887 2888 private: 2889 nscoord mPackingSpaceRemaining = 0; 2890 uint32_t mNumPackingSpacesRemaining = 0; 2891 StyleContentDistribution mAlignContent = {StyleAlignFlags::AUTO}; 2892 2893 const nscoord mCrossGapSize; 2894 }; 2895 2896 // Utility class for managing our position along the cross axis, *within* a 2897 // single flex line. 2898 class MOZ_STACK_CLASS SingleLineCrossAxisPositionTracker 2899 : public PositionTracker { 2900 public: 2901 explicit SingleLineCrossAxisPositionTracker( 2902 const FlexboxAxisTracker& aAxisTracker); 2903 2904 void ResolveAutoMarginsInCrossAxis(const FlexLine& aLine, FlexItem& aItem); 2905 2906 void EnterAlignPackingSpace(const FlexLine& aLine, const FlexItem& aItem, 2907 const FlexboxAxisTracker& aAxisTracker); 2908 2909 // Resets our position to the cross-start edge of this line. 2910 inline void ResetPosition() { mPosition = 0; } 2911 }; 2912 2913 //---------------------------------------------------------------------- 2914 2915 // Frame class boilerplate 2916 // ======================= 2917 2918 NS_QUERYFRAME_HEAD(nsFlexContainerFrame) 2919 NS_QUERYFRAME_ENTRY(nsFlexContainerFrame) 2920 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 2921 2922 NS_IMPL_FRAMEARENA_HELPERS(nsFlexContainerFrame) 2923 2924 nsContainerFrame* NS_NewFlexContainerFrame(PresShell* aPresShell, 2925 ComputedStyle* aStyle) { 2926 return new (aPresShell) 2927 nsFlexContainerFrame(aStyle, aPresShell->GetPresContext()); 2928 } 2929 2930 //---------------------------------------------------------------------- 2931 2932 // nsFlexContainerFrame Method Implementations 2933 // =========================================== 2934 2935 /* virtual */ 2936 nsFlexContainerFrame::~nsFlexContainerFrame() = default; 2937 2938 /* virtual */ 2939 void nsFlexContainerFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 2940 nsIFrame* aPrevInFlow) { 2941 nsContainerFrame::Init(aContent, aParent, aPrevInFlow); 2942 2943 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER)) { 2944 AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT); 2945 } 2946 2947 auto displayInside = StyleDisplay()->DisplayInside(); 2948 // If this frame is for a scrollable element, then it will actually have 2949 // "display:block", and its *parent frame* will have the real 2950 // flex-flavored display value. So in that case, check the parent frame to 2951 // find out if we're legacy. 2952 // 2953 // TODO(emilio): Maybe ::-moz-scrolled-content and co should inherit `display` 2954 // (or a blockified version thereof, to not hit bug 456484). 2955 if (displayInside == StyleDisplayInside::Flow) { 2956 MOZ_ASSERT(StyleDisplay()->mDisplay == StyleDisplay::Block); 2957 MOZ_ASSERT(Style()->GetPseudoType() == PseudoStyleType::scrolledContent, 2958 "The only way a nsFlexContainerFrame can have 'display:block' " 2959 "should be if it's the inner part of a scrollable element"); 2960 displayInside = GetParent()->StyleDisplay()->DisplayInside(); 2961 } 2962 2963 if (displayInside == StyleDisplayInside::WebkitBox) { 2964 AddStateBits(NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX); 2965 } 2966 } 2967 2968 #ifdef DEBUG_FRAME_DUMP 2969 nsresult nsFlexContainerFrame::GetFrameName(nsAString& aResult) const { 2970 return MakeFrameName(u"FlexContainer"_ns, aResult); 2971 } 2972 #endif 2973 2974 void nsFlexContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 2975 const nsDisplayListSet& aLists) { 2976 nsDisplayListCollection tempLists(aBuilder); 2977 DisplayBorderBackgroundOutline(aBuilder, tempLists); 2978 if (HidesContent()) { 2979 tempLists.MoveTo(aLists); 2980 return; 2981 } 2982 2983 if (GetPrevInFlow()) { 2984 DisplayOverflowContainers(aBuilder, tempLists); 2985 DisplayPushedAbsoluteFrames(aBuilder, tempLists); 2986 } 2987 2988 // Our children are all block-level, so their borders/backgrounds all go on 2989 // the BlockBorderBackgrounds list. 2990 nsDisplayListSet childLists(tempLists, tempLists.BlockBorderBackgrounds()); 2991 2992 CSSOrderAwareFrameIterator iter( 2993 this, FrameChildListID::Principal, 2994 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll, 2995 OrderStateForIter(this), OrderingPropertyForIter(this)); 2996 2997 const auto flags = DisplayFlagsForFlexOrGridItem(); 2998 for (; !iter.AtEnd(); iter.Next()) { 2999 nsIFrame* childFrame = *iter; 3000 BuildDisplayListForChild(aBuilder, childFrame, childLists, flags); 3001 } 3002 3003 tempLists.MoveTo(aLists); 3004 } 3005 3006 void FlexLine::FreezeItemsEarly(bool aIsUsingFlexGrow, 3007 ComputedFlexLineInfo* aLineInfo) { 3008 // After we've established the type of flexing we're doing (growing vs. 3009 // shrinking), and before we try to flex any items, we freeze items that 3010 // obviously *can't* flex. 3011 // 3012 // Quoting the spec: 3013 // # Freeze, setting its target main size to its hypothetical main size... 3014 // # - any item that has a flex factor of zero 3015 // # - if using the flex grow factor: any item that has a flex base size 3016 // # greater than its hypothetical main size 3017 // # - if using the flex shrink factor: any item that has a flex base size 3018 // # smaller than its hypothetical main size 3019 // https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths 3020 // 3021 // (NOTE: At this point, item->MainSize() *is* the item's hypothetical 3022 // main size, since SetFlexBaseSizeAndMainSize() sets it up that way, and the 3023 // item hasn't had a chance to flex away from that yet.) 3024 3025 // Since this loop only operates on unfrozen flex items, we can break as 3026 // soon as we have seen all of them. 3027 uint32_t numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems; 3028 for (FlexItem& item : Items()) { 3029 if (numUnfrozenItemsToBeSeen == 0) { 3030 break; 3031 } 3032 3033 if (!item.IsFrozen()) { 3034 numUnfrozenItemsToBeSeen--; 3035 bool shouldFreeze = (0.0f == item.GetFlexFactor(aIsUsingFlexGrow)); 3036 if (!shouldFreeze) { 3037 if (aIsUsingFlexGrow) { 3038 if (item.FlexBaseSize() > item.MainSize()) { 3039 shouldFreeze = true; 3040 } 3041 } else { // using flex-shrink 3042 if (item.FlexBaseSize() < item.MainSize()) { 3043 shouldFreeze = true; 3044 } 3045 } 3046 } 3047 if (shouldFreeze) { 3048 // Freeze item! (at its hypothetical main size) 3049 item.Freeze(); 3050 if (item.FlexBaseSize() < item.MainSize()) { 3051 item.SetWasMinClamped(); 3052 } else if (item.FlexBaseSize() > item.MainSize()) { 3053 item.SetWasMaxClamped(); 3054 } 3055 mNumFrozenItems++; 3056 } 3057 } 3058 } 3059 3060 MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?"); 3061 } 3062 3063 // Based on the sign of aTotalViolation, this function freezes a subset of our 3064 // flexible sizes, and restores the remaining ones to their initial pref sizes. 3065 void FlexLine::FreezeOrRestoreEachFlexibleSize(const nscoord aTotalViolation, 3066 bool aIsFinalIteration) { 3067 enum FreezeType { 3068 eFreezeEverything, 3069 eFreezeMinViolations, 3070 eFreezeMaxViolations 3071 }; 3072 3073 FreezeType freezeType; 3074 if (aTotalViolation == 0) { 3075 freezeType = eFreezeEverything; 3076 } else if (aTotalViolation > 0) { 3077 freezeType = eFreezeMinViolations; 3078 } else { // aTotalViolation < 0 3079 freezeType = eFreezeMaxViolations; 3080 } 3081 3082 // Since this loop only operates on unfrozen flex items, we can break as 3083 // soon as we have seen all of them. 3084 uint32_t numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems; 3085 for (FlexItem& item : Items()) { 3086 if (numUnfrozenItemsToBeSeen == 0) { 3087 break; 3088 } 3089 3090 if (!item.IsFrozen()) { 3091 numUnfrozenItemsToBeSeen--; 3092 3093 MOZ_ASSERT(!item.HadMinViolation() || !item.HadMaxViolation(), 3094 "Can have either min or max violation, but not both"); 3095 3096 bool hadMinViolation = item.HadMinViolation(); 3097 bool hadMaxViolation = item.HadMaxViolation(); 3098 if (eFreezeEverything == freezeType || 3099 (eFreezeMinViolations == freezeType && hadMinViolation) || 3100 (eFreezeMaxViolations == freezeType && hadMaxViolation)) { 3101 MOZ_ASSERT(item.MainSize() >= item.MainMinSize(), 3102 "Freezing item at a size below its minimum"); 3103 MOZ_ASSERT(item.MainSize() <= item.MainMaxSize(), 3104 "Freezing item at a size above its maximum"); 3105 3106 item.Freeze(); 3107 if (hadMinViolation) { 3108 item.SetWasMinClamped(); 3109 } else if (hadMaxViolation) { 3110 item.SetWasMaxClamped(); 3111 } 3112 mNumFrozenItems++; 3113 } else if (MOZ_UNLIKELY(aIsFinalIteration)) { 3114 // XXXdholbert If & when bug 765861 is fixed, we should upgrade this 3115 // assertion to be fatal except in documents with enormous lengths. 3116 NS_ERROR( 3117 "Final iteration still has unfrozen items, this shouldn't" 3118 " happen unless there was nscoord under/overflow."); 3119 item.Freeze(); 3120 mNumFrozenItems++; 3121 } // else, we'll reset this item's main size to its flex base size on the 3122 // next iteration of this algorithm. 3123 3124 if (!item.IsFrozen()) { 3125 // Clear this item's violation(s), now that we've dealt with them 3126 item.ClearViolationFlags(); 3127 } 3128 } 3129 } 3130 3131 MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?"); 3132 } 3133 3134 void FlexLine::ResolveFlexibleLengths(nscoord aFlexContainerMainSize, 3135 ComputedFlexLineInfo* aLineInfo) { 3136 // In this function, we use 64-bit coord type to avoid integer overflow in 3137 // case several of the individual items have huge hypothetical main sizes, 3138 // which can happen with percent-width table-layout:fixed descendants. Here we 3139 // promote the container's main size to 64-bit to make the arithmetic 3140 // convenient. 3141 AuCoord64 flexContainerMainSize(aFlexContainerMainSize); 3142 3143 // Before we start resolving sizes: if we have an aLineInfo structure to fill 3144 // out, we inform it of each item's base size, and we initialize the "delta" 3145 // for each item to 0. (And if the flex algorithm wants to grow or shrink the 3146 // item, we'll update this delta further down.) 3147 if (aLineInfo) { 3148 uint32_t itemIndex = 0; 3149 for (FlexItem& item : Items()) { 3150 aLineInfo->mItems[itemIndex].mMainBaseSize = item.FlexBaseSize(); 3151 aLineInfo->mItems[itemIndex].mMainDeltaSize = 0; 3152 ++itemIndex; 3153 } 3154 } 3155 3156 // Determine whether we're going to be growing or shrinking items. 3157 const bool isUsingFlexGrow = 3158 (mTotalOuterHypotheticalMainSize < flexContainerMainSize); 3159 3160 if (aLineInfo) { 3161 aLineInfo->mGrowthState = 3162 isUsingFlexGrow ? mozilla::dom::FlexLineGrowthState::Growing 3163 : mozilla::dom::FlexLineGrowthState::Shrinking; 3164 } 3165 3166 // Do an "early freeze" for flex items that obviously can't flex in the 3167 // direction we've chosen: 3168 FreezeItemsEarly(isUsingFlexGrow, aLineInfo); 3169 3170 if ((mNumFrozenItems == NumItems()) && !aLineInfo) { 3171 // All our items are frozen, so we have no flexible lengths to resolve, 3172 // and we aren't being asked to generate computed line info. 3173 FLEX_LOG("No flexible length to resolve"); 3174 return; 3175 } 3176 MOZ_ASSERT(!IsEmpty() || aLineInfo, 3177 "empty lines should take the early-return above"); 3178 3179 FLEX_LOG("Resolving flexible lengths for items"); 3180 3181 // Subtract space occupied by our items' margins/borders/padding/gaps, so 3182 // we can just be dealing with the space available for our flex items' content 3183 // boxes. 3184 const AuCoord64 totalItemMBPAndGaps = mTotalItemMBP + SumOfGaps(); 3185 const AuCoord64 spaceAvailableForFlexItemsContentBoxes = 3186 flexContainerMainSize - totalItemMBPAndGaps; 3187 3188 Maybe<AuCoord64> origAvailableFreeSpace; 3189 3190 // NOTE: I claim that this chunk of the algorithm (the looping part) needs to 3191 // run the loop at MOST NumItems() times. This claim should hold up 3192 // because we'll freeze at least one item on each loop iteration, and once 3193 // we've run out of items to freeze, there's nothing left to do. However, 3194 // in most cases, we'll break out of this loop long before we hit that many 3195 // iterations. 3196 for (uint32_t iterationCounter = 0; iterationCounter < NumItems(); 3197 iterationCounter++) { 3198 // Set every not-yet-frozen item's used main size to its 3199 // flex base size, and subtract all the used main sizes from our 3200 // total amount of space to determine the 'available free space' 3201 // (positive or negative) to be distributed among our flexible items. 3202 AuCoord64 availableFreeSpace = spaceAvailableForFlexItemsContentBoxes; 3203 for (FlexItem& item : Items()) { 3204 if (!item.IsFrozen()) { 3205 item.SetMainSize(item.FlexBaseSize()); 3206 } 3207 availableFreeSpace -= item.MainSize(); 3208 } 3209 3210 FLEX_LOGV("Available free space: %" PRId64 "; flex items should \"%s\"", 3211 availableFreeSpace.value, isUsingFlexGrow ? "grow" : "shrink"); 3212 3213 // The sign of our free space should agree with the type of flexing 3214 // (grow/shrink) that we're doing. Any disagreement should've made us use 3215 // the other type of flexing, or should've been resolved in 3216 // FreezeItemsEarly. 3217 // 3218 // Note: it's possible that an individual flex item has huge 3219 // margin/border/padding that makes either its 3220 // MarginBorderPaddingSizeInMainAxis() or OuterMainSize() negative due to 3221 // integer overflow. If that happens, the accumulated 3222 // mTotalOuterHypotheticalMainSize or mTotalItemMBP could be negative due to 3223 // that one item's negative (overflowed) size. Likewise, a huge main gap 3224 // size between flex items can also make our accumulated SumOfGaps() 3225 // negative. In these case, we throw up our hands and don't require 3226 // isUsingFlexGrow to agree with availableFreeSpace. Luckily, we won't get 3227 // stuck in the algorithm below, and just distribute the wrong 3228 // availableFreeSpace with the wrong grow/shrink factors. 3229 MOZ_ASSERT(!(mTotalOuterHypotheticalMainSize >= 0 && mTotalItemMBP >= 0 && 3230 totalItemMBPAndGaps >= 0) || 3231 (isUsingFlexGrow && availableFreeSpace >= 0) || 3232 (!isUsingFlexGrow && availableFreeSpace <= 0), 3233 "availableFreeSpace's sign should match isUsingFlexGrow"); 3234 3235 // If we have any free space available, give each flexible item a portion 3236 // of availableFreeSpace. 3237 if (availableFreeSpace != AuCoord64(0)) { 3238 // The first time we do this, we initialize origAvailableFreeSpace. 3239 if (!origAvailableFreeSpace) { 3240 origAvailableFreeSpace.emplace(availableFreeSpace); 3241 } 3242 3243 // STRATEGY: On each item, we compute & store its "share" of the total 3244 // weight that we've seen so far: 3245 // curWeight / weightSum 3246 // 3247 // Then, when we go to actually distribute the space (in the next loop), 3248 // we can simply walk backwards through the elements and give each item 3249 // its "share" multiplied by the remaining available space. 3250 // 3251 // SPECIAL CASE: If the sum of the weights is larger than the 3252 // maximum representable double (overflowing to infinity), then we can't 3253 // sensibly divide out proportional shares anymore. In that case, we 3254 // simply treat the flex item(s) with the largest weights as if 3255 // their weights were infinite (dwarfing all the others), and we 3256 // distribute all of the available space among them. 3257 double weightSum = 0.0; 3258 double flexFactorSum = 0.0; 3259 double largestWeight = 0.0; 3260 uint32_t numItemsWithLargestWeight = 0; 3261 3262 // Since this loop only operates on unfrozen flex items, we can break as 3263 // soon as we have seen all of them. 3264 uint32_t numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems; 3265 for (FlexItem& item : Items()) { 3266 if (numUnfrozenItemsToBeSeen == 0) { 3267 break; 3268 } 3269 3270 if (!item.IsFrozen()) { 3271 numUnfrozenItemsToBeSeen--; 3272 3273 const double curWeight = item.GetWeight(isUsingFlexGrow); 3274 const double curFlexFactor = item.GetFlexFactor(isUsingFlexGrow); 3275 MOZ_ASSERT(curWeight >= 0.0, "weights are non-negative"); 3276 MOZ_ASSERT(curFlexFactor >= 0.0, "flex factors are non-negative"); 3277 3278 weightSum += curWeight; 3279 flexFactorSum += curFlexFactor; 3280 3281 if (std::isfinite(weightSum)) { 3282 if (curWeight == 0.0) { 3283 item.SetShareOfWeightSoFar(0.0); 3284 } else { 3285 item.SetShareOfWeightSoFar(curWeight / weightSum); 3286 } 3287 } // else, the sum of weights overflows to infinity, in which 3288 // case we don't bother with "SetShareOfWeightSoFar" since 3289 // we know we won't use it. (instead, we'll just give every 3290 // item with the largest weight an equal share of space.) 3291 3292 // Update our largest-weight tracking vars 3293 if (curWeight > largestWeight) { 3294 largestWeight = curWeight; 3295 numItemsWithLargestWeight = 1; 3296 } else if (curWeight == largestWeight) { 3297 numItemsWithLargestWeight++; 3298 } 3299 } 3300 } 3301 3302 MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?"); 3303 3304 if (weightSum != 0.0) { 3305 MOZ_ASSERT(flexFactorSum != 0.0, 3306 "flex factor sum can't be 0, if a weighted sum " 3307 "of its components (weightSum) is nonzero"); 3308 if (flexFactorSum < 1.0) { 3309 // Our unfrozen flex items don't want all of the original free space! 3310 // (Their flex factors add up to something less than 1.) 3311 // Hence, make sure we don't distribute any more than the portion of 3312 // our original free space that these items actually want. 3313 auto totalDesiredPortionOfOrigFreeSpace = 3314 AuCoord64::FromRound(*origAvailableFreeSpace * flexFactorSum); 3315 3316 // Clamp availableFreeSpace to be no larger than that ^^. 3317 // (using min or max, depending on sign). 3318 // This should not change the sign of availableFreeSpace (except 3319 // possibly by setting it to 0), as enforced by this assertion: 3320 NS_ASSERTION(totalDesiredPortionOfOrigFreeSpace == AuCoord64(0) || 3321 ((totalDesiredPortionOfOrigFreeSpace > 0) == 3322 (availableFreeSpace > 0)), 3323 "When we reduce available free space for flex " 3324 "factors < 1, we shouldn't change the sign of the " 3325 "free space..."); 3326 3327 if (availableFreeSpace > 0) { 3328 availableFreeSpace = std::min(availableFreeSpace, 3329 totalDesiredPortionOfOrigFreeSpace); 3330 } else { 3331 availableFreeSpace = std::max(availableFreeSpace, 3332 totalDesiredPortionOfOrigFreeSpace); 3333 } 3334 } 3335 3336 FLEX_LOGV("Distributing available space:"); 3337 // Since this loop only operates on unfrozen flex items, we can break as 3338 // soon as we have seen all of them. 3339 numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems; 3340 3341 // NOTE: It's important that we traverse our items in *reverse* order 3342 // here, for correct width distribution according to the items' 3343 // "ShareOfWeightSoFar" progressively-calculated values. 3344 for (FlexItem& item : Reversed(Items())) { 3345 if (numUnfrozenItemsToBeSeen == 0) { 3346 break; 3347 } 3348 3349 if (!item.IsFrozen()) { 3350 numUnfrozenItemsToBeSeen--; 3351 3352 // To avoid rounding issues, we compute the change in size for this 3353 // item, and then subtract it from the remaining available space. 3354 AuCoord64 sizeDelta = 0; 3355 if (std::isfinite(weightSum)) { 3356 double myShareOfRemainingSpace = item.ShareOfWeightSoFar(); 3357 3358 MOZ_ASSERT(myShareOfRemainingSpace >= 0.0 && 3359 myShareOfRemainingSpace <= 1.0, 3360 "my share should be nonnegative fractional amount"); 3361 3362 if (myShareOfRemainingSpace == 1.0) { 3363 // (We special-case 1.0 to avoid float error from converting 3364 // availableFreeSpace from integer*1.0 --> double --> integer) 3365 sizeDelta = availableFreeSpace; 3366 } else if (myShareOfRemainingSpace > 0.0) { 3367 sizeDelta = AuCoord64::FromRound(availableFreeSpace * 3368 myShareOfRemainingSpace); 3369 } 3370 } else if (item.GetWeight(isUsingFlexGrow) == largestWeight) { 3371 // Total flexibility is infinite, so we're just distributing 3372 // the available space equally among the items that are tied for 3373 // having the largest weight (and this is one of those items). 3374 sizeDelta = AuCoord64::FromRound( 3375 availableFreeSpace / double(numItemsWithLargestWeight)); 3376 numItemsWithLargestWeight--; 3377 } 3378 3379 availableFreeSpace -= sizeDelta; 3380 3381 item.SetMainSize(item.MainSize() + 3382 nscoord(sizeDelta.ToMinMaxClamped())); 3383 FLEX_LOGV(" Flex item %p receives %" PRId64 ", for a total of %d", 3384 item.Frame(), sizeDelta.value, item.MainSize()); 3385 } 3386 } 3387 3388 MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?"); 3389 3390 // If we have an aLineInfo structure to fill out, capture any 3391 // size changes that may have occurred in the previous loop. 3392 // We don't do this inside the previous loop, because we don't 3393 // want to burden layout when aLineInfo is null. 3394 if (aLineInfo) { 3395 uint32_t itemIndex = 0; 3396 for (FlexItem& item : Items()) { 3397 if (!item.IsFrozen()) { 3398 // Calculate a deltaSize that represents how much the flex sizing 3399 // algorithm "wants" to stretch or shrink this item during this 3400 // pass through the algorithm. Later passes through the algorithm 3401 // may overwrite this, until this item is frozen. Note that this 3402 // value may not reflect how much the size of the item is 3403 // actually changed, since the size of the item will be clamped 3404 // to min and max values later in this pass. That's intentional, 3405 // since we want to report the value that the sizing algorithm 3406 // tried to stretch or shrink the item. 3407 nscoord deltaSize = 3408 item.MainSize() - aLineInfo->mItems[itemIndex].mMainBaseSize; 3409 3410 aLineInfo->mItems[itemIndex].mMainDeltaSize = deltaSize; 3411 } 3412 ++itemIndex; 3413 } 3414 } 3415 } 3416 } 3417 3418 // Fix min/max violations: 3419 nscoord totalViolation = 0; // keeps track of adjustments for min/max 3420 FLEX_LOGV("Checking for violations:"); 3421 3422 // Since this loop only operates on unfrozen flex items, we can break as 3423 // soon as we have seen all of them. 3424 uint32_t numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems; 3425 for (FlexItem& item : Items()) { 3426 if (numUnfrozenItemsToBeSeen == 0) { 3427 break; 3428 } 3429 3430 if (!item.IsFrozen()) { 3431 numUnfrozenItemsToBeSeen--; 3432 3433 if (item.MainSize() < item.MainMinSize()) { 3434 // min violation 3435 totalViolation += item.MainMinSize() - item.MainSize(); 3436 item.SetMainSize(item.MainMinSize()); 3437 item.SetHadMinViolation(); 3438 } else if (item.MainSize() > item.MainMaxSize()) { 3439 // max violation 3440 totalViolation += item.MainMaxSize() - item.MainSize(); 3441 item.SetMainSize(item.MainMaxSize()); 3442 item.SetHadMaxViolation(); 3443 } 3444 } 3445 } 3446 3447 MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?"); 3448 3449 FreezeOrRestoreEachFlexibleSize(totalViolation, 3450 iterationCounter + 1 == NumItems()); 3451 3452 FLEX_LOGV("Total violation: %d", totalViolation); 3453 3454 if (mNumFrozenItems == NumItems()) { 3455 break; 3456 } 3457 3458 MOZ_ASSERT(totalViolation != 0, 3459 "Zero violation should've made us freeze all items & break"); 3460 } 3461 3462 #ifdef DEBUG 3463 // Post-condition: all items should've been frozen. 3464 // Make sure the counts match: 3465 MOZ_ASSERT(mNumFrozenItems == NumItems(), "All items should be frozen"); 3466 3467 // For good measure, check each item directly, in case our counts are busted: 3468 for (const FlexItem& item : Items()) { 3469 MOZ_ASSERT(item.IsFrozen(), "All items should be frozen"); 3470 } 3471 #endif // DEBUG 3472 } 3473 3474 MainAxisPositionTracker::MainAxisPositionTracker( 3475 const FlexboxAxisTracker& aAxisTracker, const FlexLine* aLine, 3476 const StyleContentDistribution& aJustifyContent, 3477 nscoord aContentBoxMainSize) 3478 : PositionTracker(aAxisTracker.GetWritingMode(), aAxisTracker.MainAxis(), 3479 aAxisTracker.IsMainAxisReversed()), 3480 // we chip away at this below 3481 mPackingSpaceRemaining(aContentBoxMainSize), 3482 mJustifyContent(aJustifyContent) { 3483 // Extract the flag portion of mJustifyContent and strip off the flag bits 3484 // NOTE: This must happen before any assignment to mJustifyContent to 3485 // avoid overwriting the flag bits. 3486 StyleAlignFlags justifyContentFlags = 3487 mJustifyContent.primary & StyleAlignFlags::FLAG_BITS; 3488 mJustifyContent.primary &= ~StyleAlignFlags::FLAG_BITS; 3489 3490 // 'normal' behaves as 'stretch', and 'stretch' behaves as 'flex-start', 3491 // in the main axis 3492 // https://drafts.csswg.org/css-align-3/#propdef-justify-content 3493 if (mJustifyContent.primary == StyleAlignFlags::NORMAL || 3494 mJustifyContent.primary == StyleAlignFlags::STRETCH) { 3495 mJustifyContent.primary = StyleAlignFlags::FLEX_START; 3496 } 3497 3498 // mPackingSpaceRemaining is initialized to the container's main size. Now 3499 // we'll subtract out the main sizes of our flex items, so that it ends up 3500 // with the *actual* amount of packing space. 3501 for (const FlexItem& item : aLine->Items()) { 3502 mPackingSpaceRemaining -= item.OuterMainSize(); 3503 mNumAutoMarginsInMainAxis += item.NumAutoMarginsInMainAxis(); 3504 } 3505 3506 // Subtract space required for row/col gap from the remaining packing space 3507 mPackingSpaceRemaining -= aLine->SumOfGaps(); 3508 3509 // If packing space is negative or we only have one item, 'space-between' 3510 // falls back to 'safe flex-start'[1], and 'space-around' & 'space-evenly' 3511 // fall back to 'safe center'. In those cases, it's simplest to just pretend 3512 // we have a different 'justify-content' value and share code. 3513 // https://drafts.csswg.org/css-align-3/#distribution-values 3514 if (mPackingSpaceRemaining < 0 || aLine->NumItems() == 1) { 3515 if (mJustifyContent.primary == StyleAlignFlags::SPACE_BETWEEN) { 3516 // [1] XXXdholbert Per spec, we should do... 3517 // justifyContentFlags = StyleAlignFlags::SAFE; 3518 // ...here, but for now let's not do that since it causes overflow to be 3519 // inadvertently clipped in situations with a reversed main axis! 3520 // See https://github.com/w3c/csswg-drafts/issues/11937 3521 mJustifyContent.primary = StyleAlignFlags::FLEX_START; 3522 } else if (mJustifyContent.primary == StyleAlignFlags::SPACE_AROUND || 3523 mJustifyContent.primary == StyleAlignFlags::SPACE_EVENLY) { 3524 justifyContentFlags = StyleAlignFlags::SAFE; 3525 mJustifyContent.primary = StyleAlignFlags::CENTER; 3526 } 3527 } 3528 3529 if (mPackingSpaceRemaining <= 0) { 3530 // No available packing space to use for resolving auto margins. 3531 mNumAutoMarginsInMainAxis = 0; 3532 // If packing space is negative and <overflow-position> is set to 'safe' 3533 // all justify options fall back to 'start' 3534 if (justifyContentFlags & StyleAlignFlags::SAFE) { 3535 mJustifyContent.primary = StyleAlignFlags::START; 3536 } 3537 } 3538 3539 // Map 'left'/'right' to 'start'/'end' 3540 if (mJustifyContent.primary == StyleAlignFlags::LEFT || 3541 mJustifyContent.primary == StyleAlignFlags::RIGHT) { 3542 mJustifyContent.primary = 3543 aAxisTracker.ResolveJustifyLeftRight(mJustifyContent.primary); 3544 } 3545 3546 // Map 'start'/'end' to 'flex-start'/'flex-end'. 3547 if (mJustifyContent.primary == StyleAlignFlags::START) { 3548 mJustifyContent.primary = aAxisTracker.IsMainAxisReversed() 3549 ? StyleAlignFlags::FLEX_END 3550 : StyleAlignFlags::FLEX_START; 3551 } else if (mJustifyContent.primary == StyleAlignFlags::END) { 3552 mJustifyContent.primary = aAxisTracker.IsMainAxisReversed() 3553 ? StyleAlignFlags::FLEX_START 3554 : StyleAlignFlags::FLEX_END; 3555 } 3556 3557 // Figure out how much space we'll set aside for auto margins or 3558 // packing spaces, and advance past any leading packing-space. 3559 if (mNumAutoMarginsInMainAxis == 0 && mPackingSpaceRemaining != 0 && 3560 !aLine->IsEmpty()) { 3561 if (mJustifyContent.primary == StyleAlignFlags::FLEX_START) { 3562 // All packing space should go at the end --> nothing to do here. 3563 } else if (mJustifyContent.primary == StyleAlignFlags::FLEX_END) { 3564 // All packing space goes at the beginning 3565 mPosition += mPackingSpaceRemaining; 3566 } else if (mJustifyContent.primary == StyleAlignFlags::CENTER) { 3567 // Half the packing space goes at the beginning 3568 mPosition += mPackingSpaceRemaining / 2; 3569 } else if (mJustifyContent.primary == StyleAlignFlags::SPACE_BETWEEN || 3570 mJustifyContent.primary == StyleAlignFlags::SPACE_AROUND || 3571 mJustifyContent.primary == StyleAlignFlags::SPACE_EVENLY) { 3572 nsFlexContainerFrame::CalculatePackingSpace( 3573 aLine->NumItems(), mJustifyContent, &mPosition, 3574 &mNumPackingSpacesRemaining, &mPackingSpaceRemaining); 3575 } else { 3576 MOZ_ASSERT_UNREACHABLE("Unexpected justify-content value"); 3577 } 3578 } 3579 3580 MOZ_ASSERT(mNumPackingSpacesRemaining == 0 || mNumAutoMarginsInMainAxis == 0, 3581 "extra space should either go to packing space or to " 3582 "auto margins, but not to both"); 3583 } 3584 3585 void MainAxisPositionTracker::ResolveAutoMarginsInMainAxis(FlexItem& aItem) { 3586 if (mNumAutoMarginsInMainAxis) { 3587 const auto* styleMargin = aItem.Frame()->StyleMargin(); 3588 const auto anchorResolutionParams = 3589 AnchorPosResolutionParams::From(aItem.Frame()); 3590 for (const auto side : {StartSide(), EndSide()}) { 3591 if (styleMargin->GetMargin(side, mWM, anchorResolutionParams)->IsAuto()) { 3592 // NOTE: This integer math will skew the distribution of remainder 3593 // app-units towards the end, which is fine. 3594 nscoord curAutoMarginSize = 3595 mPackingSpaceRemaining / mNumAutoMarginsInMainAxis; 3596 3597 MOZ_ASSERT(aItem.GetMarginComponentForSide(side) == 0, 3598 "Expecting auto margins to have value '0' before we " 3599 "resolve them"); 3600 aItem.SetMarginComponentForSide(side, curAutoMarginSize); 3601 3602 mNumAutoMarginsInMainAxis--; 3603 mPackingSpaceRemaining -= curAutoMarginSize; 3604 } 3605 } 3606 } 3607 } 3608 3609 void MainAxisPositionTracker::TraversePackingSpace() { 3610 if (mNumPackingSpacesRemaining) { 3611 MOZ_ASSERT(mJustifyContent.primary == StyleAlignFlags::SPACE_BETWEEN || 3612 mJustifyContent.primary == StyleAlignFlags::SPACE_AROUND || 3613 mJustifyContent.primary == StyleAlignFlags::SPACE_EVENLY, 3614 "mNumPackingSpacesRemaining only applies for " 3615 "space-between/space-around/space-evenly"); 3616 3617 MOZ_ASSERT(mPackingSpaceRemaining >= 0, 3618 "ran out of packing space earlier than we expected"); 3619 3620 // NOTE: This integer math will skew the distribution of remainder 3621 // app-units towards the end, which is fine. 3622 nscoord curPackingSpace = 3623 mPackingSpaceRemaining / mNumPackingSpacesRemaining; 3624 3625 mPosition += curPackingSpace; 3626 mNumPackingSpacesRemaining--; 3627 mPackingSpaceRemaining -= curPackingSpace; 3628 } 3629 } 3630 3631 CrossAxisPositionTracker::CrossAxisPositionTracker( 3632 nsTArray<FlexLine>& aLines, const ReflowInput& aReflowInput, 3633 nscoord aContentBoxCrossSize, bool aIsCrossSizeDefinite, 3634 const FlexboxAxisTracker& aAxisTracker, const nscoord aCrossGapSize) 3635 : PositionTracker(aAxisTracker.GetWritingMode(), aAxisTracker.CrossAxis(), 3636 aAxisTracker.IsCrossAxisReversed()), 3637 mAlignContent(aReflowInput.mStylePosition->mAlignContent), 3638 mCrossGapSize(aCrossGapSize) { 3639 // Extract and strip the flag bits from alignContent 3640 StyleAlignFlags alignContentFlags = 3641 mAlignContent.primary & StyleAlignFlags::FLAG_BITS; 3642 mAlignContent.primary &= ~StyleAlignFlags::FLAG_BITS; 3643 3644 // 'normal' behaves as 'stretch' 3645 if (mAlignContent.primary == StyleAlignFlags::NORMAL) { 3646 mAlignContent.primary = StyleAlignFlags::STRETCH; 3647 } 3648 3649 if (IsSingleLine(aReflowInput.mFrame, aReflowInput.mStylePosition)) { 3650 MOZ_ASSERT(aLines.Length() == 1, 3651 "If we're styled as single-line, we should only have 1 line"); 3652 // "If the flex container is single-line and has a definite cross size, the 3653 // cross size of the flex line is the flex container's inner cross size." 3654 // 3655 // SOURCE: https://drafts.csswg.org/css-flexbox/#algo-cross-line 3656 // NOTE: This means (by definition) that there's no packing space, which 3657 // means we don't need to be concerned with "align-content" at all and we 3658 // can return early. This is handy, because this is the usual case (for 3659 // single-line flexbox). 3660 if (aIsCrossSizeDefinite) { 3661 aLines[0].SetLineCrossSize(aContentBoxCrossSize); 3662 return; 3663 } 3664 3665 // "If the flex container is single-line, then clamp the line's 3666 // cross-size to be within the container's computed min and max cross-size 3667 // properties." 3668 aLines[0].SetLineCrossSize( 3669 aReflowInput.ApplyMinMaxBSize(aLines[0].LineCrossSize())); 3670 } 3671 3672 // NOTE: The rest of this function should essentially match 3673 // MainAxisPositionTracker's constructor, though with FlexLines instead of 3674 // FlexItems, and with the additional value "stretch" (and of course with 3675 // cross sizes instead of main sizes.) 3676 3677 // Figure out how much packing space we have (container's cross size minus 3678 // all the lines' cross sizes). Also, share this loop to count how many 3679 // lines we have. (We need that count in some cases below.) 3680 mPackingSpaceRemaining = aContentBoxCrossSize; 3681 uint32_t numLines = 0; 3682 for (FlexLine& line : aLines) { 3683 mPackingSpaceRemaining -= line.LineCrossSize(); 3684 numLines++; 3685 } 3686 3687 // Subtract space required for row/col gap from the remaining packing space 3688 MOZ_ASSERT(numLines >= 1, 3689 "GenerateFlexLines should've produced at least 1 line"); 3690 mPackingSpaceRemaining -= aCrossGapSize * (numLines - 1); 3691 3692 // If packing space is negative, 'stretch' behaves like 'flex-start', 3693 // 'space-between' behaves like 'safe flex-start', and 'space-around' and 3694 // 'space-evenly' behave like 'safe center'. In those cases, it's simplest to 3695 // just pretend we have a different 'align-content' value and share code. (If 3696 // we only have one line, all of the 'space-*' keywords fall back as well, but 3697 // 'stretch' doesn't because even a single line can still stretch.) 3698 // https://drafts.csswg.org/css-align-3/#distribution-values 3699 if (mPackingSpaceRemaining < 0 && 3700 mAlignContent.primary == StyleAlignFlags::STRETCH) { 3701 mAlignContent.primary = StyleAlignFlags::FLEX_START; 3702 } else if (mPackingSpaceRemaining < 0 || numLines == 1) { 3703 if (mAlignContent.primary == StyleAlignFlags::SPACE_BETWEEN) { 3704 alignContentFlags = StyleAlignFlags::SAFE; 3705 mAlignContent.primary = StyleAlignFlags::FLEX_START; 3706 } else if (mAlignContent.primary == StyleAlignFlags::SPACE_AROUND || 3707 mAlignContent.primary == StyleAlignFlags::SPACE_EVENLY) { 3708 alignContentFlags = StyleAlignFlags::SAFE; 3709 mAlignContent.primary = StyleAlignFlags::CENTER; 3710 } 3711 } 3712 3713 // If <overflow-position> is 'safe' and packing space is negative 3714 // all align options fall back to 'start' 3715 if ((alignContentFlags & StyleAlignFlags::SAFE) && 3716 mPackingSpaceRemaining < 0) { 3717 mAlignContent.primary = StyleAlignFlags::START; 3718 } 3719 3720 // Map 'start'/'end' to 'flex-start'/'flex-end'. 3721 if (mAlignContent.primary == StyleAlignFlags::START) { 3722 mAlignContent.primary = aAxisTracker.IsCrossAxisReversed() 3723 ? StyleAlignFlags::FLEX_END 3724 : StyleAlignFlags::FLEX_START; 3725 } else if (mAlignContent.primary == StyleAlignFlags::END) { 3726 mAlignContent.primary = aAxisTracker.IsCrossAxisReversed() 3727 ? StyleAlignFlags::FLEX_START 3728 : StyleAlignFlags::FLEX_END; 3729 } 3730 3731 // Figure out how much space we'll set aside for packing spaces, and advance 3732 // past any leading packing-space. 3733 if (mPackingSpaceRemaining != 0) { 3734 if (mAlignContent.primary == StyleAlignFlags::BASELINE || 3735 mAlignContent.primary == StyleAlignFlags::LAST_BASELINE) { 3736 // TODO: Bug 1480850 will implement 'align-content: [first/last] baseline' 3737 // for flexbox. Until then, behaves as if align-content is 'flex-start' by 3738 // doing nothing. 3739 } else if (mAlignContent.primary == StyleAlignFlags::FLEX_START) { 3740 // All packing space should go at the end --> nothing to do here. 3741 } else if (mAlignContent.primary == StyleAlignFlags::FLEX_END) { 3742 // All packing space goes at the beginning 3743 mPosition += mPackingSpaceRemaining; 3744 } else if (mAlignContent.primary == StyleAlignFlags::CENTER) { 3745 // Half the packing space goes at the beginning 3746 mPosition += mPackingSpaceRemaining / 2; 3747 } else if (mAlignContent.primary == StyleAlignFlags::SPACE_BETWEEN || 3748 mAlignContent.primary == StyleAlignFlags::SPACE_AROUND || 3749 mAlignContent.primary == StyleAlignFlags::SPACE_EVENLY) { 3750 nsFlexContainerFrame::CalculatePackingSpace( 3751 numLines, mAlignContent, &mPosition, &mNumPackingSpacesRemaining, 3752 &mPackingSpaceRemaining); 3753 } else if (mAlignContent.primary == StyleAlignFlags::STRETCH) { 3754 // Split space equally between the lines: 3755 MOZ_ASSERT(mPackingSpaceRemaining > 0, 3756 "negative packing space should make us use 'flex-start' " 3757 "instead of 'stretch' (and we shouldn't bother with this " 3758 "code if we have 0 packing space)"); 3759 3760 uint32_t numLinesLeft = numLines; 3761 for (FlexLine& line : aLines) { 3762 // Our share is the amount of space remaining, divided by the number 3763 // of lines remainig. 3764 MOZ_ASSERT(numLinesLeft > 0, "miscalculated num lines"); 3765 nscoord shareOfExtraSpace = mPackingSpaceRemaining / numLinesLeft; 3766 nscoord newSize = line.LineCrossSize() + shareOfExtraSpace; 3767 line.SetLineCrossSize(newSize); 3768 3769 mPackingSpaceRemaining -= shareOfExtraSpace; 3770 numLinesLeft--; 3771 } 3772 MOZ_ASSERT(numLinesLeft == 0, "miscalculated num lines"); 3773 } else { 3774 MOZ_ASSERT_UNREACHABLE("Unexpected align-content value"); 3775 } 3776 } 3777 } 3778 3779 void CrossAxisPositionTracker::TraversePackingSpace() { 3780 if (mNumPackingSpacesRemaining) { 3781 MOZ_ASSERT(mAlignContent.primary == StyleAlignFlags::SPACE_BETWEEN || 3782 mAlignContent.primary == StyleAlignFlags::SPACE_AROUND || 3783 mAlignContent.primary == StyleAlignFlags::SPACE_EVENLY, 3784 "mNumPackingSpacesRemaining only applies for " 3785 "space-between/space-around/space-evenly"); 3786 3787 MOZ_ASSERT(mPackingSpaceRemaining >= 0, 3788 "ran out of packing space earlier than we expected"); 3789 3790 // NOTE: This integer math will skew the distribution of remainder 3791 // app-units towards the end, which is fine. 3792 nscoord curPackingSpace = 3793 mPackingSpaceRemaining / mNumPackingSpacesRemaining; 3794 3795 mPosition += curPackingSpace; 3796 mNumPackingSpacesRemaining--; 3797 mPackingSpaceRemaining -= curPackingSpace; 3798 } 3799 } 3800 3801 SingleLineCrossAxisPositionTracker::SingleLineCrossAxisPositionTracker( 3802 const FlexboxAxisTracker& aAxisTracker) 3803 : PositionTracker(aAxisTracker.GetWritingMode(), aAxisTracker.CrossAxis(), 3804 aAxisTracker.IsCrossAxisReversed()) {} 3805 3806 void FlexLine::ComputeCrossSizeAndBaseline( 3807 const FlexboxAxisTracker& aAxisTracker) { 3808 // NOTE: in these "cross{Start,End}ToFurthest{First,Last}Baseline" variables, 3809 // the "first/last" term is referring to the flex *line's* baseline-sharing 3810 // groups, which may or may not match any flex *item's* exact align-self 3811 // value. See the code that sets FlexItem::mBaselineSharingGroup for more 3812 // details. 3813 nscoord crossStartToFurthestFirstBaseline = nscoord_MIN; 3814 nscoord crossEndToFurthestFirstBaseline = nscoord_MIN; 3815 nscoord crossStartToFurthestLastBaseline = nscoord_MIN; 3816 nscoord crossEndToFurthestLastBaseline = nscoord_MIN; 3817 3818 nscoord largestOuterCrossSize = 0; 3819 for (const FlexItem& item : Items()) { 3820 nscoord curOuterCrossSize = item.OuterCrossSize(); 3821 3822 if ((item.AlignSelf() == StyleAlignFlags::BASELINE || 3823 item.AlignSelf() == StyleAlignFlags::LAST_BASELINE) && 3824 item.NumAutoMarginsInCrossAxis() == 0) { 3825 const bool usingItemFirstBaseline = 3826 (item.AlignSelf() == StyleAlignFlags::BASELINE); 3827 3828 // Find distance from our item's cross-start and cross-end margin-box 3829 // edges to its baseline. 3830 // 3831 // Here's a diagram of a flex-item that we might be doing this on. 3832 // "mmm" is the margin-box, "bbb" is the border-box. The bottom of 3833 // the text "BASE" is the baseline. 3834 // 3835 // ---(cross-start)--- 3836 // ___ ___ ___ 3837 // mmmmmmmmmmmm | |margin-start | 3838 // m m | _|_ ___ | 3839 // m bbbbbbbb m |curOuterCrossSize | |crossStartToBaseline 3840 // m b b m | |ascent | 3841 // m b BASE b m | _|_ _|_ 3842 // m b b m | | 3843 // m bbbbbbbb m | |crossEndToBaseline 3844 // m m | | 3845 // mmmmmmmmmmmm _|_ _|_ 3846 // 3847 // ---(cross-end)--- 3848 // 3849 // We already have the curOuterCrossSize, margin-start, and the ascent. 3850 // * We can get crossStartToBaseline by adding margin-start + ascent. 3851 // * If we subtract that from the curOuterCrossSize, we get 3852 // crossEndToBaseline. 3853 3854 nscoord crossStartToBaseline = item.BaselineOffsetFromOuterCrossEdge( 3855 aAxisTracker.CrossAxisPhysicalStartSide(), usingItemFirstBaseline); 3856 nscoord crossEndToBaseline = curOuterCrossSize - crossStartToBaseline; 3857 3858 // Now, update our "largest" values for these (across all the flex items 3859 // in this flex line), so we can use them in computing the line's cross 3860 // size below: 3861 if (item.ItemBaselineSharingGroup() == BaselineSharingGroup::First) { 3862 crossStartToFurthestFirstBaseline = 3863 std::max(crossStartToFurthestFirstBaseline, crossStartToBaseline); 3864 crossEndToFurthestFirstBaseline = 3865 std::max(crossEndToFurthestFirstBaseline, crossEndToBaseline); 3866 } else { 3867 crossStartToFurthestLastBaseline = 3868 std::max(crossStartToFurthestLastBaseline, crossStartToBaseline); 3869 crossEndToFurthestLastBaseline = 3870 std::max(crossEndToFurthestLastBaseline, crossEndToBaseline); 3871 } 3872 } else { 3873 largestOuterCrossSize = 3874 std::max(largestOuterCrossSize, curOuterCrossSize); 3875 } 3876 } 3877 3878 // The line's baseline offset is the distance from the line's edge to the 3879 // furthest item-baseline. The item(s) with that baseline will be exactly 3880 // aligned with the line's edge. 3881 mFirstBaselineOffset = crossStartToFurthestFirstBaseline; 3882 mLastBaselineOffset = crossEndToFurthestLastBaseline; 3883 3884 // The line's cross-size is the larger of: 3885 // (a) [largest cross-start-to-baseline + largest baseline-to-cross-end] of 3886 // all baseline-aligned items with no cross-axis auto margins... 3887 // and 3888 // (b) [largest cross-start-to-baseline + largest baseline-to-cross-end] of 3889 // all last baseline-aligned items with no cross-axis auto margins... 3890 // and 3891 // (c) largest cross-size of all other children. 3892 mLineCrossSize = std::max( 3893 std::max( 3894 crossStartToFurthestFirstBaseline + crossEndToFurthestFirstBaseline, 3895 crossStartToFurthestLastBaseline + crossEndToFurthestLastBaseline), 3896 largestOuterCrossSize); 3897 } 3898 3899 nscoord FlexLine::ExtractBaselineOffset( 3900 BaselineSharingGroup aBaselineGroup) const { 3901 auto LastBaselineOffsetFromStartEdge = [this]() { 3902 // Convert the distance to be relative from the line's cross-start edge. 3903 const nscoord offset = LastBaselineOffset(); 3904 return offset != nscoord_MIN ? LineCrossSize() - offset : offset; 3905 }; 3906 3907 auto PrimaryBaseline = [=]() { 3908 return aBaselineGroup == BaselineSharingGroup::First 3909 ? FirstBaselineOffset() 3910 : LastBaselineOffsetFromStartEdge(); 3911 }; 3912 auto SecondaryBaseline = [=]() { 3913 return aBaselineGroup == BaselineSharingGroup::First 3914 ? LastBaselineOffsetFromStartEdge() 3915 : FirstBaselineOffset(); 3916 }; 3917 3918 const nscoord primaryBaseline = PrimaryBaseline(); 3919 if (primaryBaseline != nscoord_MIN) { 3920 return primaryBaseline; 3921 } 3922 return SecondaryBaseline(); 3923 } 3924 3925 void FlexItem::ResolveStretchedCrossSize(nscoord aLineCrossSize) { 3926 // We stretch IFF we are align-self:stretch, have no auto margins in 3927 // cross axis, and have cross-axis size property == "auto". If any of those 3928 // conditions don't hold up, we won't stretch. 3929 // https://drafts.csswg.org/css-flexbox-1/#valdef-align-items-stretch 3930 if (mAlignSelf != StyleAlignFlags::STRETCH || 3931 NumAutoMarginsInCrossAxis() != 0 || !IsCrossSizeAuto()) { 3932 return; 3933 } 3934 3935 // If we've already been stretched, we can bail out early, too. 3936 // No need to redo the calculation. 3937 if (mIsStretched) { 3938 return; 3939 } 3940 3941 // Reserve space for margins & border & padding, and then use whatever 3942 // remains as our item's cross-size (clamped to its min/max range). 3943 nscoord stretchedSize = aLineCrossSize - MarginBorderPaddingSizeInCrossAxis(); 3944 3945 stretchedSize = CSSMinMax(stretchedSize, mCrossMinSize, mCrossMaxSize); 3946 3947 // Update the cross-size & make a note that it's stretched, so we know to 3948 // override the reflow input's computed cross-size in our final reflow. 3949 SetCrossSize(stretchedSize); 3950 mIsStretched = true; 3951 } 3952 3953 static nsBlockFrame* FindFlexItemBlockFrame(nsIFrame* aFrame) { 3954 if (nsBlockFrame* block = do_QueryFrame(aFrame)) { 3955 return block; 3956 } 3957 for (nsIFrame* f : aFrame->PrincipalChildList()) { 3958 if (nsBlockFrame* block = FindFlexItemBlockFrame(f)) { 3959 return block; 3960 } 3961 } 3962 return nullptr; 3963 } 3964 3965 nsBlockFrame* FlexItem::BlockFrame() const { 3966 return FindFlexItemBlockFrame(Frame()); 3967 } 3968 3969 void SingleLineCrossAxisPositionTracker::ResolveAutoMarginsInCrossAxis( 3970 const FlexLine& aLine, FlexItem& aItem) { 3971 // Subtract the space that our item is already occupying, to see how much 3972 // space (if any) is available for its auto margins. 3973 nscoord spaceForAutoMargins = aLine.LineCrossSize() - aItem.OuterCrossSize(); 3974 3975 if (spaceForAutoMargins <= 0) { 3976 return; // No available space --> nothing to do 3977 } 3978 3979 uint32_t numAutoMargins = aItem.NumAutoMarginsInCrossAxis(); 3980 if (numAutoMargins == 0) { 3981 return; // No auto margins --> nothing to do. 3982 } 3983 3984 // OK, we have at least one auto margin and we have some available space. 3985 // Give each auto margin a share of the space. 3986 const auto* styleMargin = aItem.Frame()->StyleMargin(); 3987 const auto anchorResolutionParams = 3988 AnchorPosResolutionParams::From(aItem.Frame()); 3989 for (const auto side : {StartSide(), EndSide()}) { 3990 if (styleMargin->GetMargin(side, mWM, anchorResolutionParams)->IsAuto()) { 3991 MOZ_ASSERT(aItem.GetMarginComponentForSide(side) == 0, 3992 "Expecting auto margins to have value '0' before we " 3993 "update them"); 3994 3995 // NOTE: integer divison is fine here; numAutoMargins is either 1 or 2. 3996 // If it's 2 & spaceForAutoMargins is odd, 1st margin gets smaller half. 3997 nscoord curAutoMarginSize = spaceForAutoMargins / numAutoMargins; 3998 aItem.SetMarginComponentForSide(side, curAutoMarginSize); 3999 numAutoMargins--; 4000 spaceForAutoMargins -= curAutoMarginSize; 4001 } 4002 } 4003 } 4004 4005 void SingleLineCrossAxisPositionTracker::EnterAlignPackingSpace( 4006 const FlexLine& aLine, const FlexItem& aItem, 4007 const FlexboxAxisTracker& aAxisTracker) { 4008 // We don't do align-self alignment on items that have auto margins 4009 // in the cross axis. 4010 if (aItem.NumAutoMarginsInCrossAxis()) { 4011 return; 4012 } 4013 4014 StyleAlignFlags alignSelf = aItem.AlignSelf(); 4015 // NOTE: 'stretch' behaves like 'flex-start' once we've stretched any 4016 // auto-sized items (which we've already done). 4017 if (alignSelf == StyleAlignFlags::STRETCH) { 4018 alignSelf = StyleAlignFlags::FLEX_START; 4019 } 4020 4021 // Map 'self-start'/'self-end' to 'start'/'end' 4022 if (alignSelf == StyleAlignFlags::SELF_START || 4023 alignSelf == StyleAlignFlags::SELF_END) { 4024 const LogicalAxis logCrossAxis = 4025 aAxisTracker.IsRowOriented() ? LogicalAxis::Block : LogicalAxis::Inline; 4026 const WritingMode cWM = aAxisTracker.GetWritingMode(); 4027 const bool sameStart = 4028 cWM.ParallelAxisStartsOnSameSide(logCrossAxis, aItem.GetWritingMode()); 4029 alignSelf = sameStart == (alignSelf == StyleAlignFlags::SELF_START) 4030 ? StyleAlignFlags::START 4031 : StyleAlignFlags::END; 4032 } 4033 4034 // Map 'start'/'end' to 'flex-start'/'flex-end'. 4035 if (alignSelf == StyleAlignFlags::START) { 4036 alignSelf = aAxisTracker.IsCrossAxisReversed() 4037 ? StyleAlignFlags::FLEX_END 4038 : StyleAlignFlags::FLEX_START; 4039 } else if (alignSelf == StyleAlignFlags::END) { 4040 alignSelf = aAxisTracker.IsCrossAxisReversed() ? StyleAlignFlags::FLEX_START 4041 : StyleAlignFlags::FLEX_END; 4042 } 4043 4044 // 'align-self' falls back to 'flex-start' if it is 'center'/'flex-end' and we 4045 // have cross axis overflow 4046 // XXX we should really be falling back to 'start' as of bug 1472843 4047 if (aLine.LineCrossSize() < aItem.OuterCrossSize() && 4048 (aItem.AlignSelfFlags() & StyleAlignFlags::SAFE)) { 4049 alignSelf = StyleAlignFlags::FLEX_START; 4050 } 4051 4052 if (alignSelf == StyleAlignFlags::FLEX_START) { 4053 // No space to skip over -- we're done. 4054 } else if (alignSelf == StyleAlignFlags::FLEX_END) { 4055 mPosition += aLine.LineCrossSize() - aItem.OuterCrossSize(); 4056 } else if (alignSelf == StyleAlignFlags::CENTER || 4057 alignSelf == StyleAlignFlags::ANCHOR_CENTER) { 4058 // TODO(dshin, Bug 1909339): For now, treat `anchor-center` as `center`. 4059 // Note: If cross-size is odd, the "after" space will get the extra unit. 4060 mPosition += (aLine.LineCrossSize() - aItem.OuterCrossSize()) / 2; 4061 } else if (alignSelf == StyleAlignFlags::BASELINE || 4062 alignSelf == StyleAlignFlags::LAST_BASELINE) { 4063 const bool usingItemFirstBaseline = 4064 (alignSelf == StyleAlignFlags::BASELINE); 4065 4066 // The first-baseline sharing group gets (collectively) aligned to the 4067 // FlexLine's cross-start side, and similarly the last-baseline sharing 4068 // group gets snapped to the cross-end side. 4069 const bool isFirstBaselineSharingGroup = 4070 aItem.ItemBaselineSharingGroup() == BaselineSharingGroup::First; 4071 const mozilla::Side alignSide = 4072 isFirstBaselineSharingGroup ? aAxisTracker.CrossAxisPhysicalStartSide() 4073 : aAxisTracker.CrossAxisPhysicalEndSide(); 4074 4075 // To compute the aligned position for our flex item, we determine: 4076 // (1) The distance from the item's alignSide edge to the item's relevant 4077 // baseline. 4078 nscoord itemBaselineOffset = aItem.BaselineOffsetFromOuterCrossEdge( 4079 alignSide, usingItemFirstBaseline); 4080 4081 // (2) The distance between the FlexLine's alignSide edge and the relevant 4082 // baseline-sharing-group's baseline position. 4083 nscoord lineBaselineOffset = isFirstBaselineSharingGroup 4084 ? aLine.FirstBaselineOffset() 4085 : aLine.LastBaselineOffset(); 4086 4087 NS_ASSERTION(lineBaselineOffset >= itemBaselineOffset, 4088 "failed at finding largest baseline offset"); 4089 4090 // (3) The difference between the above offsets, which tells us how far we 4091 // need to shift the item away from the FlexLine's alignSide edge so 4092 // that its baseline is at the proper position for its group. 4093 nscoord itemOffsetFromLineEdge = lineBaselineOffset - itemBaselineOffset; 4094 4095 if (isFirstBaselineSharingGroup) { 4096 // alignSide is the line's cross-start edge. mPosition is already there. 4097 // From there, we step *forward* by the baseline adjustment: 4098 mPosition += itemOffsetFromLineEdge; 4099 } else { 4100 // alignSide is the line's cross-end edge. Advance mPosition to align 4101 // item with that edge (as in FLEX_END case)... 4102 mPosition += aLine.LineCrossSize() - aItem.OuterCrossSize(); 4103 // ...and step *back* by the baseline adjustment: 4104 mPosition -= itemOffsetFromLineEdge; 4105 } 4106 } else { 4107 MOZ_ASSERT_UNREACHABLE("Unexpected align-self value"); 4108 } 4109 } 4110 4111 FlexboxAxisInfo::FlexboxAxisInfo(const nsIFrame* aFlexContainer) { 4112 MOZ_ASSERT(aFlexContainer && aFlexContainer->IsFlexContainerFrame(), 4113 "Only flex containers may be passed to this constructor!"); 4114 if (aFlexContainer->IsLegacyWebkitBox()) { 4115 InitAxesFromLegacyProps(aFlexContainer); 4116 } else { 4117 InitAxesFromModernProps(aFlexContainer); 4118 } 4119 } 4120 4121 void FlexboxAxisInfo::InitAxesFromLegacyProps(const nsIFrame* aFlexContainer) { 4122 const nsStyleXUL* styleXUL = aFlexContainer->StyleXUL(); 4123 4124 const bool boxOrientIsVertical = 4125 styleXUL->mBoxOrient == StyleBoxOrient::Vertical; 4126 const bool wmIsVertical = aFlexContainer->GetWritingMode().IsVertical(); 4127 4128 // If box-orient agrees with our writing-mode, then we're "row-oriented" 4129 // (i.e. the flexbox main axis is the same as our writing mode's inline 4130 // direction). Otherwise, we're column-oriented (i.e. the flexbox's main 4131 // axis is perpendicular to the writing-mode's inline direction). 4132 mIsRowOriented = (boxOrientIsVertical == wmIsVertical); 4133 4134 // Legacy flexbox can use "-webkit-box-direction: reverse" to reverse the 4135 // main axis (so it runs in the reverse direction of the inline axis): 4136 mIsMainAxisReversed = styleXUL->mBoxDirection == StyleBoxDirection::Reverse; 4137 4138 // Legacy flexbox does not support reversing the cross axis -- it has no 4139 // equivalent of modern flexbox's "flex-wrap: wrap-reverse". 4140 mIsCrossAxisReversed = false; 4141 } 4142 4143 void FlexboxAxisInfo::InitAxesFromModernProps(const nsIFrame* aFlexContainer) { 4144 const nsStylePosition* stylePos = aFlexContainer->StylePosition(); 4145 StyleFlexDirection flexDirection = stylePos->mFlexDirection; 4146 4147 // Determine main axis: 4148 switch (flexDirection) { 4149 case StyleFlexDirection::Row: 4150 mIsRowOriented = true; 4151 mIsMainAxisReversed = false; 4152 break; 4153 case StyleFlexDirection::RowReverse: 4154 mIsRowOriented = true; 4155 mIsMainAxisReversed = true; 4156 break; 4157 case StyleFlexDirection::Column: 4158 mIsRowOriented = false; 4159 mIsMainAxisReversed = false; 4160 break; 4161 case StyleFlexDirection::ColumnReverse: 4162 mIsRowOriented = false; 4163 mIsMainAxisReversed = true; 4164 break; 4165 } 4166 4167 // "flex-wrap: wrap-reverse" reverses our cross axis. 4168 mIsCrossAxisReversed = stylePos->mFlexWrap == StyleFlexWrap::WrapReverse; 4169 } 4170 4171 FlexboxAxisTracker::FlexboxAxisTracker( 4172 const nsFlexContainerFrame* aFlexContainer) 4173 : mWM(aFlexContainer->GetWritingMode()), mAxisInfo(aFlexContainer) {} 4174 4175 LogicalSide FlexboxAxisTracker::MainAxisStartSide() const { 4176 return MakeLogicalSide( 4177 MainAxis(), IsMainAxisReversed() ? LogicalEdge::End : LogicalEdge::Start); 4178 } 4179 4180 LogicalSide FlexboxAxisTracker::CrossAxisStartSide() const { 4181 return MakeLogicalSide(CrossAxis(), IsCrossAxisReversed() 4182 ? LogicalEdge::End 4183 : LogicalEdge::Start); 4184 } 4185 4186 void nsFlexContainerFrame::GenerateFlexLines( 4187 const ReflowInput& aReflowInput, const nscoord aTentativeContentBoxMainSize, 4188 const nscoord aTentativeContentBoxCrossSize, 4189 const nsTArray<StrutInfo>& aStruts, const FlexboxAxisTracker& aAxisTracker, 4190 nscoord aMainGapSize, nsTArray<nsIFrame*>& aPlaceholders, 4191 nsTArray<FlexLine>& aLines, bool& aHasCollapsedItems) { 4192 MOZ_ASSERT(aLines.IsEmpty(), "Expecting outparam to start out empty"); 4193 4194 auto ConstructNewFlexLine = [&aLines, aMainGapSize]() { 4195 return aLines.EmplaceBack(aMainGapSize); 4196 }; 4197 4198 // We have at least one FlexLine. Even an empty flex container has a single 4199 // (empty) flex line. 4200 FlexLine* curLine = ConstructNewFlexLine(); 4201 4202 nscoord wrapThreshold; 4203 if (IsSingleLine(aReflowInput.mFrame, aReflowInput.mStylePosition)) { 4204 // Not wrapping. Set threshold to sentinel value that tells us not to wrap. 4205 wrapThreshold = NS_UNCONSTRAINEDSIZE; 4206 } else { 4207 // Wrapping! Set wrap threshold to flex container's content-box main-size. 4208 wrapThreshold = aTentativeContentBoxMainSize; 4209 4210 // If the flex container doesn't have a definite content-box main-size 4211 // (e.g. if main axis is vertical & 'height' is 'auto'), make sure we at 4212 // least wrap when we hit its max main-size. 4213 if (wrapThreshold == NS_UNCONSTRAINEDSIZE) { 4214 const nscoord flexContainerMaxMainSize = 4215 aAxisTracker.MainComponent(aReflowInput.ComputedMaxSize()); 4216 wrapThreshold = flexContainerMaxMainSize; 4217 } 4218 } 4219 4220 // Tracks the index of the next strut, in aStruts (and when this hits 4221 // aStruts.Length(), that means there are no more struts): 4222 uint32_t nextStrutIdx = 0; 4223 4224 // Overall index of the current flex item in the flex container. (This gets 4225 // checked against entries in aStruts.) 4226 uint32_t itemIdxInContainer = 0; 4227 4228 CSSOrderAwareFrameIterator iter( 4229 this, FrameChildListID::Principal, 4230 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll, 4231 CSSOrderAwareFrameIterator::OrderState::Unknown, 4232 OrderingPropertyForIter(this)); 4233 4234 AddOrRemoveStateBits(NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER, 4235 iter.ItemsAreAlreadyInOrder()); 4236 4237 const bool useMozBoxCollapseBehavior = 4238 StyleVisibility()->UseLegacyCollapseBehavior(); 4239 4240 for (; !iter.AtEnd(); iter.Next()) { 4241 nsIFrame* childFrame = *iter; 4242 // Don't create flex items / lines for placeholder frames: 4243 if (childFrame->IsPlaceholderFrame()) { 4244 aPlaceholders.AppendElement(childFrame); 4245 continue; 4246 } 4247 4248 const bool collapsed = childFrame->StyleVisibility()->IsCollapse(); 4249 aHasCollapsedItems = aHasCollapsedItems || collapsed; 4250 4251 if (useMozBoxCollapseBehavior && collapsed) { 4252 // Legacy visibility:collapse behavior: make a 0-sized strut. (No need to 4253 // bother with aStruts and remembering cross size.) 4254 curLine->Items().EmplaceBack(childFrame, 0, aReflowInput.GetWritingMode(), 4255 aAxisTracker); 4256 } else if (nextStrutIdx < aStruts.Length() && 4257 aStruts[nextStrutIdx].mItemIdx == itemIdxInContainer) { 4258 // Use the simplified "strut" FlexItem constructor: 4259 curLine->Items().EmplaceBack(childFrame, 4260 aStruts[nextStrutIdx].mStrutCrossSize, 4261 aReflowInput.GetWritingMode(), aAxisTracker); 4262 nextStrutIdx++; 4263 } else { 4264 GenerateFlexItemForChild(*curLine, childFrame, aReflowInput, aAxisTracker, 4265 aTentativeContentBoxCrossSize); 4266 } 4267 4268 // Check if we need to wrap the newly appended item to a new line, i.e. if 4269 // its outer hypothetical main size pushes our line over the threshold. 4270 // But we don't wrap if the line-length is unconstrained, nor do we wrap if 4271 // this was the first item on the line. 4272 if (wrapThreshold != NS_UNCONSTRAINEDSIZE && 4273 curLine->Items().Length() > 1) { 4274 // If the line will be longer than wrapThreshold or at least as long as 4275 // nscoord_MAX because of the newly appended item, then wrap and move the 4276 // item to a new line. 4277 auto newOuterSize = curLine->TotalOuterHypotheticalMainSize(); 4278 newOuterSize += curLine->Items().LastElement().OuterMainSize(); 4279 4280 // Account for gap between this line's previous item and this item. 4281 newOuterSize += aMainGapSize; 4282 4283 if (newOuterSize >= nscoord_MAX || newOuterSize > wrapThreshold) { 4284 curLine = ConstructNewFlexLine(); 4285 4286 // Get the previous line after adding a new line because the address can 4287 // change if nsTArray needs to reallocate a new space for the new line. 4288 FlexLine& prevLine = aLines[aLines.Length() - 2]; 4289 4290 // Move the item from the end of prevLine to the end of curLine. 4291 curLine->Items().AppendElement(prevLine.Items().PopLastElement()); 4292 } 4293 } 4294 4295 // Update the line's bookkeeping about how large its items collectively are. 4296 curLine->AddLastItemToMainSizeTotals(); 4297 itemIdxInContainer++; 4298 } 4299 } 4300 4301 nsFlexContainerFrame::FlexLayoutResult 4302 nsFlexContainerFrame::GenerateFlexLayoutResult() { 4303 MOZ_ASSERT(GetPrevInFlow(), "This should be called by non-first-in-flows!"); 4304 4305 auto* data = FirstInFlow()->GetProperty(SharedFlexData::Prop()); 4306 MOZ_ASSERT(data, "SharedFlexData should be set by our first-in-flow!"); 4307 4308 FlexLayoutResult flr; 4309 4310 // The order state of the children is consistent across entire continuation 4311 // chain due to calling nsContainerFrame::NormalizeChildLists() at the 4312 // beginning of Reflow(), so we can align our state bit with our 4313 // prev-in-flow's state. Setup here before calling OrderStateForIter() below. 4314 AddOrRemoveStateBits(NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER, 4315 GetPrevInFlow()->HasAnyStateBits( 4316 NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER)); 4317 4318 // Construct flex items for this flex container fragment from existing flex 4319 // items in SharedFlexData. 4320 CSSOrderAwareFrameIterator iter( 4321 this, FrameChildListID::Principal, 4322 CSSOrderAwareFrameIterator::ChildFilter::SkipPlaceholders, 4323 OrderStateForIter(this), OrderingPropertyForIter(this)); 4324 4325 auto ConstructNewFlexLine = [&flr]() { 4326 // Use zero main gap size since it doesn't matter in flex container's 4327 // next-in-flows. We've computed flex items' positions in first-in-flow. 4328 return flr.mLines.EmplaceBack(0); 4329 }; 4330 4331 // We have at least one FlexLine. Even an empty flex container has a single 4332 // (empty) flex line. 4333 FlexLine* currentLine = ConstructNewFlexLine(); 4334 4335 if (!iter.AtEnd()) { 4336 nsIFrame* child = *iter; 4337 nsIFrame* childFirstInFlow = child->FirstInFlow(); 4338 4339 // We are iterating nested for-loops over the FlexLines and FlexItems 4340 // generated by GenerateFlexLines() and cached in flex container's 4341 // first-in-flow. For each flex item, check if its frame (must be a 4342 // first-in-flow) is the first-in-flow of the first child frame in this flex 4343 // container continuation. If so, clone the data from that FlexItem into a 4344 // FlexLine. When we find a match for the item, we know that the next child 4345 // frame might have its first-in-flow as the next item in the same original 4346 // line. In this case, we'll put the cloned data in the same line here as 4347 // well. 4348 for (const FlexLine& line : data->mLines) { 4349 // If currentLine is empty, either it is the first line, or all the items 4350 // in the previous line have been placed in our prev-in-flows. No need to 4351 // construct a new line. 4352 if (!currentLine->IsEmpty()) { 4353 currentLine = ConstructNewFlexLine(); 4354 } 4355 for (const FlexItem& item : line.Items()) { 4356 if (item.Frame() == childFirstInFlow) { 4357 currentLine->Items().AppendElement(item.CloneFor(child)); 4358 iter.Next(); 4359 if (iter.AtEnd()) { 4360 // We've constructed flex items for all children. No need to check 4361 // rest of the items. 4362 child = childFirstInFlow = nullptr; 4363 break; 4364 } 4365 child = *iter; 4366 childFirstInFlow = child->FirstInFlow(); 4367 } 4368 } 4369 if (iter.AtEnd()) { 4370 // We've constructed flex items for all children. No need to check 4371 // rest of the lines. 4372 break; 4373 } 4374 } 4375 } 4376 4377 flr.mContentBoxMainSize = data->mContentBoxMainSize; 4378 flr.mContentBoxCrossSize = data->mContentBoxCrossSize; 4379 4380 return flr; 4381 } 4382 4383 // Returns the largest outer hypothetical main-size of any line in |aLines|. 4384 // (i.e. the hypothetical main-size of the largest line) 4385 static AuCoord64 GetLargestLineMainSize(nsTArray<FlexLine>& aLines) { 4386 AuCoord64 largestLineOuterSize = 0; 4387 for (const FlexLine& line : aLines) { 4388 largestLineOuterSize = 4389 std::max(largestLineOuterSize, line.TotalOuterHypotheticalMainSize()); 4390 } 4391 return largestLineOuterSize; 4392 } 4393 4394 nscoord nsFlexContainerFrame::ComputeMainSize( 4395 const ReflowInput& aReflowInput, const FlexboxAxisTracker& aAxisTracker, 4396 const nscoord aTentativeContentBoxMainSize, 4397 nsTArray<FlexLine>& aLines) const { 4398 if (aAxisTracker.IsRowOriented()) { 4399 // Row-oriented --> our main axis is the inline axis, so our main size 4400 // is our inline size (which should already be resolved). 4401 return aTentativeContentBoxMainSize; 4402 } 4403 4404 const bool shouldApplyAutomaticMinimumOnBlockAxis = 4405 aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis(); 4406 if (aTentativeContentBoxMainSize != NS_UNCONSTRAINEDSIZE && 4407 !shouldApplyAutomaticMinimumOnBlockAxis) { 4408 // Column-oriented case, with fixed BSize: 4409 // Just use our fixed block-size because we always assume the available 4410 // block-size is unconstrained, and the reflow input has already done the 4411 // appropriate min/max-BSize clamping. 4412 return aTentativeContentBoxMainSize; 4413 } 4414 4415 // Column-oriented case, with size-containment in block axis: 4416 // Behave as if we had no content and just use our MinBSize. 4417 if (Maybe<nscoord> containBSize = 4418 aReflowInput.mFrame->ContainIntrinsicBSize()) { 4419 return aReflowInput.ApplyMinMaxBSize(*containBSize); 4420 } 4421 4422 const AuCoord64 largestLineMainSize = GetLargestLineMainSize(aLines); 4423 const nscoord contentBSize = aReflowInput.ApplyMinMaxBSize( 4424 nscoord(largestLineMainSize.ToMinMaxClamped())); 4425 4426 // If the clamped largest FlexLine length is larger than the tentative main 4427 // size (which is resolved by aspect-ratio), we extend it to contain the 4428 // entire FlexLine. 4429 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum 4430 if (shouldApplyAutomaticMinimumOnBlockAxis) { 4431 // Column-oriented case, with auto BSize which is resolved by 4432 // aspect-ratio. 4433 return std::max(contentBSize, aTentativeContentBoxMainSize); 4434 } 4435 4436 // Column-oriented case, with auto BSize: 4437 // Resolve auto BSize to the largest FlexLine length, clamped to our 4438 // computed min/max main-size properties. 4439 return contentBSize; 4440 } 4441 4442 nscoord nsFlexContainerFrame::ComputeCrossSize( 4443 const ReflowInput& aReflowInput, const FlexboxAxisTracker& aAxisTracker, 4444 const nscoord aTentativeContentBoxCrossSize, nscoord aSumLineCrossSizes, 4445 bool* aIsDefinite) const { 4446 MOZ_ASSERT(aIsDefinite, "outparam pointer must be non-null"); 4447 4448 if (aAxisTracker.IsColumnOriented()) { 4449 // Column-oriented --> our cross axis is the inline axis, so our cross size 4450 // is our inline size (which should already be resolved). 4451 *aIsDefinite = true; 4452 // FIXME: Bug 1661847 - there are cases where aTentativeContentBoxCrossSize 4453 // (i.e. aReflowInput.ComputedISize()) might not be the right thing to 4454 // return here. Specifically: if our cross size is an intrinsic size, and we 4455 // have flex items that are flexible and have aspect ratios, then we may 4456 // need to take their post-flexing main sizes into account (multiplied 4457 // through their aspect ratios to get their cross sizes), in order to 4458 // determine their flex line's size & the flex container's cross size (e.g. 4459 // as `aSumLineCrossSizes`). 4460 return aTentativeContentBoxCrossSize; 4461 } 4462 4463 const bool shouldApplyAutomaticMinimumOnBlockAxis = 4464 aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis(); 4465 const nscoord computedBSize = aReflowInput.ComputedBSize(); 4466 if (computedBSize != NS_UNCONSTRAINEDSIZE && 4467 !shouldApplyAutomaticMinimumOnBlockAxis) { 4468 // Row-oriented case (cross axis is block-axis), with fixed BSize: 4469 *aIsDefinite = true; 4470 4471 // Just use our fixed block-size because we always assume the available 4472 // block-size is unconstrained, and the reflow input has already done the 4473 // appropriate min/max-BSize clamping. 4474 return computedBSize; 4475 } 4476 4477 // Row-oriented case, with size-containment in block axis: 4478 // Behave as if we had no content and just use our MinBSize. 4479 if (Maybe<nscoord> containBSize = 4480 aReflowInput.mFrame->ContainIntrinsicBSize()) { 4481 *aIsDefinite = true; 4482 return aReflowInput.ApplyMinMaxBSize(*containBSize); 4483 } 4484 4485 // The cross size must not be definite in the following cases. 4486 *aIsDefinite = false; 4487 4488 const nscoord contentBSize = 4489 aReflowInput.ApplyMinMaxBSize(aSumLineCrossSizes); 4490 // If the content block-size is larger than the effective computed 4491 // block-size, we extend the block-size to contain all the content. 4492 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum 4493 if (shouldApplyAutomaticMinimumOnBlockAxis) { 4494 // Row-oriented case (cross axis is block-axis), with auto BSize which is 4495 // resolved by aspect-ratio or content size. 4496 return std::max(contentBSize, computedBSize); 4497 } 4498 4499 // Row-oriented case (cross axis is block axis), with auto BSize: 4500 // Shrink-wrap our line(s), subject to our min-size / max-size 4501 // constraints in that (block) axis. 4502 return contentBSize; 4503 } 4504 4505 LogicalSize nsFlexContainerFrame::ComputeAvailableSizeForItems( 4506 const ReflowInput& aReflowInput, 4507 const mozilla::LogicalMargin& aBorderPadding) const { 4508 const WritingMode wm = GetWritingMode(); 4509 nscoord availableBSize = aReflowInput.AvailableBSize(); 4510 4511 if (availableBSize != NS_UNCONSTRAINEDSIZE) { 4512 // Available block-size is constrained. Subtract block-start border and 4513 // padding from it. 4514 availableBSize -= aBorderPadding.BStart(wm); 4515 4516 if (aReflowInput.mStyleBorder->mBoxDecorationBreak == 4517 StyleBoxDecorationBreak::Clone) { 4518 // We have box-decoration-break:clone. Subtract block-end border and 4519 // padding from the available block-size as well. 4520 availableBSize -= aBorderPadding.BEnd(wm); 4521 } 4522 4523 // Available block-size can became negative after subtracting block-axis 4524 // border and padding. Per spec, to guarantee progress, fragmentainers are 4525 // assumed to have a minimum block size of 1px regardless of their used 4526 // size. https://drafts.csswg.org/css-break/#breaking-rules 4527 availableBSize = 4528 std::max(nsPresContext::CSSPixelsToAppUnits(1), availableBSize); 4529 } 4530 4531 return LogicalSize(wm, aReflowInput.ComputedISize(), availableBSize); 4532 } 4533 4534 void FlexLine::PositionItemsInMainAxis( 4535 const StyleContentDistribution& aJustifyContent, 4536 nscoord aContentBoxMainSize, const FlexboxAxisTracker& aAxisTracker) { 4537 MainAxisPositionTracker mainAxisPosnTracker( 4538 aAxisTracker, this, aJustifyContent, aContentBoxMainSize); 4539 for (FlexItem& item : Items()) { 4540 nscoord itemMainBorderBoxSize = 4541 item.MainSize() + item.BorderPaddingSizeInMainAxis(); 4542 4543 // Resolve any main-axis 'auto' margins on aChild to an actual value. 4544 mainAxisPosnTracker.ResolveAutoMarginsInMainAxis(item); 4545 4546 // Advance our position tracker to child's upper-left content-box corner, 4547 // and use that as its position in the main axis. 4548 mainAxisPosnTracker.EnterMargin(item.Margin()); 4549 mainAxisPosnTracker.EnterChildFrame(itemMainBorderBoxSize); 4550 4551 item.SetMainPosition(mainAxisPosnTracker.Position()); 4552 4553 mainAxisPosnTracker.ExitChildFrame(itemMainBorderBoxSize); 4554 mainAxisPosnTracker.ExitMargin(item.Margin()); 4555 mainAxisPosnTracker.TraversePackingSpace(); 4556 if (&item != &Items().LastElement()) { 4557 mainAxisPosnTracker.TraverseGap(mMainGapSize); 4558 } 4559 } 4560 } 4561 4562 void nsFlexContainerFrame::SizeItemInCrossAxis(ReflowInput& aChildReflowInput, 4563 FlexItem& aItem) { 4564 // If cross axis is the item's inline axis, just use ISize from reflow input, 4565 // and don't bother with a full reflow. 4566 if (aItem.IsInlineAxisCrossAxis()) { 4567 aItem.SetCrossSize(aChildReflowInput.ComputedISize()); 4568 return; 4569 } 4570 4571 MOZ_ASSERT(!aItem.HadMeasuringReflow(), 4572 "We shouldn't need more than one measuring reflow"); 4573 4574 if (aItem.AlignSelf() == StyleAlignFlags::STRETCH) { 4575 // This item's got "align-self: stretch", so we probably imposed a 4576 // stretched computed cross-size on it during its previous 4577 // reflow. We're not imposing that BSize for *this* "measuring" reflow, so 4578 // we need to tell it to treat this reflow as a resize in its block axis 4579 // (regardless of whether any of its ancestors are actually being resized). 4580 // (Note: we know that the cross axis is the item's *block* axis -- if it 4581 // weren't, then we would've taken the early-return above.) 4582 aChildReflowInput.SetBResize(true); 4583 // Not 100% sure this is needed, but be conservative for now: 4584 aChildReflowInput.SetBResizeForPercentages(true); 4585 } 4586 4587 // Potentially reflow the item, and get the sizing info. 4588 const CachedBAxisMeasurement& measurement = 4589 MeasureBSizeForFlexItem(aItem, aChildReflowInput); 4590 4591 // Save the sizing info that we learned from this reflow 4592 // ----------------------------------------------------- 4593 4594 // Tentatively store the child's desired content-box cross-size. 4595 aItem.SetCrossSize(measurement.BSize()); 4596 } 4597 4598 void FlexLine::PositionItemsInCrossAxis( 4599 nscoord aLineStartPosition, const FlexboxAxisTracker& aAxisTracker) { 4600 SingleLineCrossAxisPositionTracker lineCrossAxisPosnTracker(aAxisTracker); 4601 4602 for (FlexItem& item : Items()) { 4603 // First, stretch the item's cross size (if appropriate), and resolve any 4604 // auto margins in this axis. 4605 item.ResolveStretchedCrossSize(mLineCrossSize); 4606 lineCrossAxisPosnTracker.ResolveAutoMarginsInCrossAxis(*this, item); 4607 4608 // Compute the cross-axis position of this item 4609 nscoord itemCrossBorderBoxSize = 4610 item.CrossSize() + item.BorderPaddingSizeInCrossAxis(); 4611 lineCrossAxisPosnTracker.EnterAlignPackingSpace(*this, item, aAxisTracker); 4612 lineCrossAxisPosnTracker.EnterMargin(item.Margin()); 4613 lineCrossAxisPosnTracker.EnterChildFrame(itemCrossBorderBoxSize); 4614 4615 item.SetCrossPosition(aLineStartPosition + 4616 lineCrossAxisPosnTracker.Position()); 4617 4618 // Back out to cross-axis edge of the line. 4619 lineCrossAxisPosnTracker.ResetPosition(); 4620 } 4621 } 4622 4623 void nsFlexContainerFrame::Reflow(nsPresContext* aPresContext, 4624 ReflowOutput& aReflowOutput, 4625 const ReflowInput& aReflowInput, 4626 nsReflowStatus& aStatus) { 4627 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) { 4628 return; 4629 } 4630 4631 MarkInReflow(); 4632 DO_GLOBAL_REFLOW_COUNT("nsFlexContainerFrame"); 4633 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 4634 MOZ_ASSERT(aPresContext == PresContext()); 4635 NS_WARNING_ASSERTION( 4636 aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE, 4637 "Unconstrained inline size; this should only result from huge sizes " 4638 "(not intrinsic sizing w/ orthogonal flows)"); 4639 4640 FLEX_LOG("Reflow flex container frame %p", this); 4641 4642 if (IsFrameTreeTooDeep(aReflowInput, aReflowOutput, aStatus)) { 4643 return; 4644 } 4645 4646 NormalizeChildLists(); 4647 4648 #ifdef DEBUG 4649 mDidPushItemsBitMayLie = false; 4650 SanityCheckChildListsBeforeReflow(); 4651 #endif // DEBUG 4652 4653 // We (and our children) can only depend on our ancestor's bsize if we have 4654 // a percent-bsize, or if we're positioned and we have "block-start" and 4655 // "block-end" set and have block-size:auto. (There are actually other cases, 4656 // too -- e.g. if our parent is itself a block-dir flex container and we're 4657 // flexible -- but we'll let our ancestors handle those sorts of cases.) 4658 // 4659 // TODO(emilio): the !bsize.IsLengthPercentage() preserves behavior, but it's 4660 // too conservative. min/max-content don't really depend on the container. 4661 WritingMode wm = aReflowInput.GetWritingMode(); 4662 const nsStylePosition* stylePos = StylePosition(); 4663 const auto anchorResolutionParams = 4664 AnchorPosOffsetResolutionParams::UseCBFrameSize( 4665 AnchorPosResolutionParams::From(this)); 4666 const auto bsize = stylePos->BSize(wm, anchorResolutionParams.mBaseParams); 4667 if (bsize->HasPercent() || 4668 (StyleDisplay()->IsAbsolutelyPositionedStyle() && 4669 (bsize->IsAuto() || !bsize->IsLengthPercentage()) && 4670 !stylePos 4671 ->GetAnchorResolvedInset(LogicalSide::BStart, wm, 4672 anchorResolutionParams) 4673 ->IsAuto() && 4674 !stylePos 4675 ->GetAnchorResolvedInset(LogicalSide::BEnd, wm, 4676 anchorResolutionParams) 4677 ->IsAuto())) { 4678 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); 4679 } 4680 4681 const FlexboxAxisTracker axisTracker(this); 4682 4683 // Check to see if we need to create a computed info structure, to 4684 // be filled out for use by devtools. 4685 ComputedFlexContainerInfo* containerInfo = CreateOrClearFlexContainerInfo(); 4686 4687 FlexLayoutResult flr; 4688 PerFragmentFlexData fragmentData; 4689 const nsIFrame* prevInFlow = GetPrevInFlow(); 4690 if (!prevInFlow) { 4691 const LogicalSize tentativeContentBoxSize = aReflowInput.ComputedSize(); 4692 const nscoord tentativeContentBoxMainSize = 4693 axisTracker.MainComponent(tentativeContentBoxSize); 4694 const nscoord tentativeContentBoxCrossSize = 4695 axisTracker.CrossComponent(tentativeContentBoxSize); 4696 4697 // Calculate gap sizes for main and cross axis. We only need them in 4698 // DoFlexLayout in the first-in-flow, so no need to worry about consumed 4699 // block-size. 4700 const auto& mainGapStyle = 4701 axisTracker.IsRowOriented() ? stylePos->mColumnGap : stylePos->mRowGap; 4702 const auto& crossGapStyle = 4703 axisTracker.IsRowOriented() ? stylePos->mRowGap : stylePos->mColumnGap; 4704 const nscoord mainGapSize = nsLayoutUtils::ResolveGapToLength( 4705 mainGapStyle, tentativeContentBoxMainSize); 4706 const nscoord crossGapSize = nsLayoutUtils::ResolveGapToLength( 4707 crossGapStyle, tentativeContentBoxCrossSize); 4708 4709 // When fragmenting a flex container, we run the flex algorithm without 4710 // regards to pagination in order to compute the flex container's desired 4711 // content-box size. https://drafts.csswg.org/css-flexbox-1/#pagination-algo 4712 // 4713 // Note: For a multi-line column-oriented flex container, the sample 4714 // algorithm suggests we wrap the flex line at the block-end edge of a 4715 // column/page, but we do not implement it intentionally. This brings the 4716 // layout result closer to the one as if there's no fragmentation. 4717 AutoTArray<StrutInfo, 1> struts; 4718 flr = DoFlexLayout(aReflowInput, tentativeContentBoxMainSize, 4719 tentativeContentBoxCrossSize, axisTracker, mainGapSize, 4720 crossGapSize, struts, containerInfo); 4721 4722 if (!struts.IsEmpty()) { 4723 // We're restarting flex layout, with new knowledge of collapsed items. 4724 flr.mLines.Clear(); 4725 flr.mPlaceholders.Clear(); 4726 flr = DoFlexLayout(aReflowInput, tentativeContentBoxMainSize, 4727 tentativeContentBoxCrossSize, axisTracker, mainGapSize, 4728 crossGapSize, struts, containerInfo); 4729 } 4730 } else { 4731 flr = GenerateFlexLayoutResult(); 4732 auto* fragmentDataProp = 4733 prevInFlow->GetProperty(PerFragmentFlexData::Prop()); 4734 MOZ_ASSERT(fragmentDataProp, 4735 "PerFragmentFlexData should be set in our prev-in-flow!"); 4736 fragmentData = *fragmentDataProp; 4737 } 4738 4739 LogicalSize contentBoxSize = axisTracker.LogicalSizeFromFlexRelativeSizes( 4740 flr.mContentBoxMainSize, flr.mContentBoxCrossSize); 4741 4742 const nscoord consumedBSize = CalcAndCacheConsumedBSize(); 4743 const nscoord effectiveContentBSize = 4744 contentBoxSize.BSize(wm) - consumedBSize; 4745 LogicalMargin borderPadding = aReflowInput.ComputedLogicalBorderPadding(wm); 4746 if (MOZ_UNLIKELY(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE)) { 4747 // We assume we are the last fragment by using 4748 // PreReflowBlockLevelLogicalSkipSides(), and skip block-end border and 4749 // padding if needed. 4750 borderPadding.ApplySkipSides(PreReflowBlockLevelLogicalSkipSides()); 4751 } 4752 4753 // Determine this frame's tentative border-box size. This is used for logical 4754 // to physical coordinate conversion when positioning children. 4755 // 4756 // Note that vertical-rl writing-mode is the only case where the block flow 4757 // direction progresses in a negative physical direction, and therefore block 4758 // direction coordinate conversion depends on knowing the width of the 4759 // coordinate space in order to translate between the logical and physical 4760 // origins. As a result, if our final border-box block-size is different from 4761 // this tentative one, and we are in vertical-rl writing mode, we need to 4762 // adjust our children's position after reflowing them. 4763 const LogicalSize tentativeBorderBoxSize( 4764 wm, contentBoxSize.ISize(wm) + borderPadding.IStartEnd(wm), 4765 std::min(effectiveContentBSize + borderPadding.BStartEnd(wm), 4766 aReflowInput.AvailableBSize())); 4767 const nsSize containerSize = tentativeBorderBoxSize.GetPhysicalSize(wm); 4768 4769 OverflowAreas ocBounds; 4770 nsReflowStatus ocStatus; 4771 if (prevInFlow) { 4772 ReflowOverflowContainerChildren( 4773 aPresContext, aReflowInput, ocBounds, ReflowChildFlags::Default, 4774 ocStatus, MergeSortedFrameListsFor, Some(containerSize)); 4775 } 4776 4777 const LogicalSize availableSizeForItems = 4778 ComputeAvailableSizeForItems(aReflowInput, borderPadding); 4779 const auto [childrenBEndEdge, childrenStatus] = 4780 ReflowChildren(aReflowInput, containerSize, availableSizeForItems, 4781 borderPadding, axisTracker, flr, fragmentData); 4782 4783 bool mayNeedNextInFlow = false; 4784 if (aReflowInput.IsInFragmentedContext()) { 4785 // This fragment's contribution to the flex container's cumulative 4786 // content-box block-size, if it turns out that this is the final vs. 4787 // non-final fragment: 4788 // 4789 // * If it turns out we *are* the final fragment, then this fragment's 4790 // content-box contribution is the distance from the start of our content 4791 // box to the block-end edge of our children (note the borderPadding 4792 // subtraction is just to get us to a content-box-relative offset here): 4793 const nscoord bSizeContributionIfFinalFragment = 4794 childrenBEndEdge - borderPadding.BStart(wm); 4795 4796 // * If it turns out we're *not* the final fragment, then this fragment's 4797 // content-box extends to the edge of the availableSizeForItems (at least), 4798 // regardless of whether we actually have items at that location: 4799 const nscoord bSizeContributionIfNotFinalFragment = std::max( 4800 bSizeContributionIfFinalFragment, availableSizeForItems.BSize(wm)); 4801 4802 // mCumulativeBEndEdgeShift was updated in ReflowChildren(), and our 4803 // children's block-size may grow in fragmented context. If our block-size 4804 // and max-block-size are unconstrained, then we allow the flex container to 4805 // grow to accommodate any children whose sizes grew as a result of 4806 // fragmentation. 4807 if (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE) { 4808 contentBoxSize.BSize(wm) = aReflowInput.ApplyMinMaxBSize( 4809 contentBoxSize.BSize(wm) + fragmentData.mCumulativeBEndEdgeShift); 4810 4811 if (childrenStatus.IsComplete()) { 4812 // All of the children fit! We know that we're using a content-based 4813 // block-size, and we know our children's block-size may have grown due 4814 // to fragmentation. So we allow ourselves to grow our block-size here 4815 // to contain the block-end edge of our last child (subject to our 4816 // min/max constraints). 4817 contentBoxSize.BSize(wm) = aReflowInput.ApplyMinMaxBSize(std::max( 4818 contentBoxSize.BSize(wm), fragmentData.mCumulativeContentBoxBSize + 4819 bSizeContributionIfFinalFragment)); 4820 } else { 4821 // As in the if-branch above, we extend our block-size, but in this case 4822 // we know that a child didn't fit and might overshot our available 4823 // size, so we assume this fragment won't be the final fragment, and 4824 // hence it should contribute bSizeContributionIfNotFinalFragment 4825 // (subject to our min/max constraints). 4826 contentBoxSize.BSize(wm) = aReflowInput.ApplyMinMaxBSize(std::max( 4827 contentBoxSize.BSize(wm), fragmentData.mCumulativeContentBoxBSize + 4828 bSizeContributionIfNotFinalFragment)); 4829 4830 if (aReflowInput.ComputedMaxBSize() == NS_UNCONSTRAINEDSIZE) { 4831 mayNeedNextInFlow = true; 4832 } else { 4833 // The definite max-block-size can be the upper bound of our 4834 // content-box block-size. We should check whether we need a 4835 // next-in-flow. 4836 mayNeedNextInFlow = contentBoxSize.BSize(wm) - consumedBSize > 4837 availableSizeForItems.BSize(wm); 4838 } 4839 } 4840 } else { 4841 mayNeedNextInFlow = contentBoxSize.BSize(wm) - consumedBSize > 4842 availableSizeForItems.BSize(wm); 4843 } 4844 fragmentData.mCumulativeContentBoxBSize += 4845 bSizeContributionIfNotFinalFragment; 4846 4847 // If we may need a next-in-flow, we'll need to skip block-end border and 4848 // padding. 4849 if (mayNeedNextInFlow && aReflowInput.mStyleBorder->mBoxDecorationBreak == 4850 StyleBoxDecorationBreak::Slice) { 4851 borderPadding.BEnd(wm) = 0; 4852 } 4853 } 4854 4855 PopulateReflowOutput(aReflowOutput, aReflowInput, aStatus, contentBoxSize, 4856 borderPadding, consumedBSize, mayNeedNextInFlow, 4857 childrenBEndEdge, childrenStatus, axisTracker, flr); 4858 4859 if (wm.IsVerticalRL()) { 4860 // If the final border-box block-size is different from the tentative one, 4861 // adjust our children's position. 4862 const nscoord deltaBCoord = 4863 tentativeBorderBoxSize.BSize(wm) - aReflowOutput.Size(wm).BSize(wm); 4864 if (deltaBCoord != 0) { 4865 const LogicalPoint delta(wm, 0, deltaBCoord); 4866 for (const FlexLine& line : flr.mLines) { 4867 for (const FlexItem& item : line.Items()) { 4868 item.Frame()->MovePositionBy(wm, delta); 4869 } 4870 } 4871 } 4872 } 4873 4874 // Overflow area = union(my overflow area, children's overflow areas) 4875 aReflowOutput.SetOverflowAreasToDesiredBounds(); 4876 UnionInFlowChildOverflow(aReflowOutput.mOverflowAreas); 4877 4878 // Merge overflow container bounds and status. 4879 aReflowOutput.mOverflowAreas.UnionWith(ocBounds); 4880 aStatus.MergeCompletionStatusFrom(ocStatus); 4881 4882 FinishReflowWithAbsoluteFrames(PresContext(), aReflowOutput, aReflowInput, 4883 aStatus); 4884 4885 // Finally update our line and item measurements in our containerInfo. 4886 if (MOZ_UNLIKELY(containerInfo)) { 4887 UpdateFlexLineAndItemInfo(*containerInfo, flr.mLines); 4888 } 4889 4890 // If we are the first-in-flow, we want to store data for our next-in-flows, 4891 // or clear the existing data if it is not needed. 4892 if (!prevInFlow) { 4893 SharedFlexData* sharedData = GetProperty(SharedFlexData::Prop()); 4894 if (!aStatus.IsFullyComplete()) { 4895 if (!sharedData) { 4896 sharedData = new SharedFlexData; 4897 SetProperty(SharedFlexData::Prop(), sharedData); 4898 } 4899 sharedData->Update(std::move(flr)); 4900 } else if (sharedData && !GetNextInFlow()) { 4901 // We are fully-complete, so no next-in-flow is needed. However, if we 4902 // report SetInlineLineBreakBeforeAndReset() in an incremental reflow, our 4903 // next-in-flow might still exist. It can be reflowed again before us if 4904 // it is an overflow container. Delete the existing data only if we don't 4905 // have a next-in-flow. 4906 RemoveProperty(SharedFlexData::Prop()); 4907 } 4908 } 4909 4910 PerFragmentFlexData* fragmentDataProp = 4911 GetProperty(PerFragmentFlexData::Prop()); 4912 if (!aStatus.IsFullyComplete()) { 4913 if (!fragmentDataProp) { 4914 fragmentDataProp = new PerFragmentFlexData; 4915 SetProperty(PerFragmentFlexData::Prop(), fragmentDataProp); 4916 } 4917 *fragmentDataProp = fragmentData; 4918 } else if (fragmentDataProp && !GetNextInFlow()) { 4919 // Similar to the condition to remove SharedFlexData, delete the 4920 // existing data only if we don't have a next-in-flow. 4921 RemoveProperty(PerFragmentFlexData::Prop()); 4922 } 4923 } 4924 4925 Maybe<nscoord> nsFlexContainerFrame::GetNaturalBaselineBOffset( 4926 WritingMode aWM, BaselineSharingGroup aBaselineGroup, 4927 BaselineExportContext) const { 4928 if (StyleDisplay()->IsContainLayout() || 4929 HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE)) { 4930 return Nothing{}; 4931 } 4932 return Some(aBaselineGroup == BaselineSharingGroup::First ? mFirstBaseline 4933 : mLastBaseline); 4934 } 4935 4936 void nsFlexContainerFrame::UnionInFlowChildOverflow( 4937 OverflowAreas& aOverflowAreas, bool aAsIfScrolled) { 4938 // The CSS Overflow spec [1] requires that a scrollable container's 4939 // scrollable overflow should include the following areas. 4940 // 4941 // a) "the box's own content and padding areas": we treat the *content* as 4942 // the scrolled inner frame's theoretical content-box that's intrinsically 4943 // sized to the union of all the flex items' margin boxes, _without_ 4944 // relative positioning applied. The *padding areas* is just inflation on 4945 // top of the theoretical content-box by the flex container's padding. 4946 // 4947 // b) "the margin areas of grid item and flex item boxes for which the box 4948 // establishes a containing block": a) already includes the flex items' 4949 // normal-positioned margin boxes into the scrollable overflow, but their 4950 // relative-positioned margin boxes should also be included because relpos 4951 // children are still flex items. 4952 // 4953 // [1] https://drafts.csswg.org/css-overflow-3/#scrollable. 4954 const bool isScrolledContent = 4955 aAsIfScrolled || 4956 Style()->GetPseudoType() == PseudoStyleType::scrolledContent; 4957 bool anyScrolledContentItem = false; 4958 // Union of normal-positioned margin boxes for all the items. 4959 nsRect itemMarginBoxes; 4960 // Overflow areas containing the union of relative-positioned and 4961 // stick-positioned margin boxes of relpos items. 4962 // 4963 // Note for sticky-positioned margin boxes, we only union it with the ink 4964 // overflow to avoid circular dependencies with the scroll container. (The 4965 // scroll position and the scroll container's size impact the sticky position, 4966 // so we don't want the sticky position to impact them.) 4967 OverflowAreas relPosItemMarginBoxes; 4968 const bool useMozBoxCollapseBehavior = 4969 StyleVisibility()->UseLegacyCollapseBehavior(); 4970 for (nsIFrame* f : mFrames) { 4971 if (useMozBoxCollapseBehavior && f->StyleVisibility()->IsCollapse()) { 4972 continue; 4973 } 4974 ConsiderChildOverflow(aOverflowAreas, f, aAsIfScrolled); 4975 if (!isScrolledContent) { 4976 continue; 4977 } 4978 if (f->IsPlaceholderFrame()) { 4979 continue; 4980 } 4981 anyScrolledContentItem = true; 4982 if (MOZ_UNLIKELY(f->IsRelativelyOrStickyPositioned())) { 4983 const nsRect marginRect = f->GetMarginRectRelativeToSelf(); 4984 itemMarginBoxes = 4985 itemMarginBoxes.Union(marginRect + f->GetNormalPosition()); 4986 if (f->IsRelativelyPositioned()) { 4987 relPosItemMarginBoxes.UnionAllWith(marginRect + f->GetPosition()); 4988 } else { 4989 MOZ_ASSERT(f->IsStickyPositioned()); 4990 relPosItemMarginBoxes.UnionWith( 4991 OverflowAreas(marginRect + f->GetPosition(), nsRect())); 4992 } 4993 } else { 4994 itemMarginBoxes = itemMarginBoxes.Union(f->GetMarginRect()); 4995 } 4996 } 4997 4998 if (anyScrolledContentItem) { 4999 itemMarginBoxes.Inflate(GetUsedPadding()); 5000 aOverflowAreas.UnionAllWith(itemMarginBoxes); 5001 aOverflowAreas.UnionWith(relPosItemMarginBoxes); 5002 } 5003 } 5004 5005 void nsFlexContainerFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas, 5006 bool aAsIfScrolled) { 5007 UnionInFlowChildOverflow(aOverflowAreas, aAsIfScrolled); 5008 // Union with child frames, skipping the principal list since we already 5009 // handled those above. 5010 nsLayoutUtils::UnionChildOverflow(this, aOverflowAreas, 5011 {FrameChildListID::Principal}); 5012 } 5013 5014 void nsFlexContainerFrame::CalculatePackingSpace( 5015 uint32_t aNumThingsToPack, const StyleContentDistribution& aAlignVal, 5016 nscoord* aFirstSubjectOffset, uint32_t* aNumPackingSpacesRemaining, 5017 nscoord* aPackingSpaceRemaining) { 5018 StyleAlignFlags val = aAlignVal.primary; 5019 MOZ_ASSERT(val == StyleAlignFlags::SPACE_BETWEEN || 5020 val == StyleAlignFlags::SPACE_AROUND || 5021 val == StyleAlignFlags::SPACE_EVENLY, 5022 "Unexpected alignment value"); 5023 5024 MOZ_ASSERT(*aPackingSpaceRemaining >= 0, 5025 "Should not be called with negative packing space"); 5026 5027 // Note: In the aNumThingsToPack==1 case, the fallback behavior for 5028 // 'space-between' depends on precise information about the axes that we 5029 // don't have here. So, for that case, we just depend on the caller to 5030 // explicitly convert 'space-{between,around,evenly}' keywords to the 5031 // appropriate fallback alignment and skip this function. 5032 MOZ_ASSERT(aNumThingsToPack > 1, 5033 "Should not be called unless there's more than 1 thing to pack"); 5034 5035 // Packing spaces between items: 5036 *aNumPackingSpacesRemaining = aNumThingsToPack - 1; 5037 5038 if (val == StyleAlignFlags::SPACE_BETWEEN) { 5039 // No need to reserve space at beginning/end, so we're done. 5040 return; 5041 } 5042 5043 // We need to add 1 or 2 packing spaces, split between beginning/end, for 5044 // space-around / space-evenly: 5045 size_t numPackingSpacesForEdges = 5046 val == StyleAlignFlags::SPACE_AROUND ? 1 : 2; 5047 5048 // How big will each "full" packing space be: 5049 nscoord packingSpaceSize = 5050 *aPackingSpaceRemaining / 5051 (*aNumPackingSpacesRemaining + numPackingSpacesForEdges); 5052 // How much packing-space are we allocating to the edges: 5053 nscoord totalEdgePackingSpace = numPackingSpacesForEdges * packingSpaceSize; 5054 5055 // Use half of that edge packing space right now: 5056 *aFirstSubjectOffset += totalEdgePackingSpace / 2; 5057 // ...but we need to subtract all of it right away, so that we won't 5058 // hand out any of it to intermediate packing spaces. 5059 *aPackingSpaceRemaining -= totalEdgePackingSpace; 5060 } 5061 5062 ComputedFlexContainerInfo* 5063 nsFlexContainerFrame::CreateOrClearFlexContainerInfo() { 5064 if (!HasAnyStateBits(NS_STATE_FLEX_COMPUTED_INFO)) { 5065 return nullptr; 5066 } 5067 5068 // The flag that sets ShouldGenerateComputedInfo() will never be cleared. 5069 // That's acceptable because it's only set in a Chrome API invoked by 5070 // devtools, and won't impact normal browsing. 5071 5072 // Re-use the ComputedFlexContainerInfo, if it exists. 5073 ComputedFlexContainerInfo* info = GetProperty(FlexContainerInfo()); 5074 if (info) { 5075 // We can reuse, as long as we clear out old data. 5076 info->mLines.Clear(); 5077 } else { 5078 info = new ComputedFlexContainerInfo(); 5079 SetProperty(FlexContainerInfo(), info); 5080 } 5081 5082 return info; 5083 } 5084 5085 nscoord nsFlexContainerFrame::FlexItemConsumedBSize(const FlexItem& aItem) { 5086 nsSplittableFrame* f = do_QueryFrame(aItem.Frame()); 5087 return f ? ConsumedBSize(f) : 0; 5088 } 5089 5090 void nsFlexContainerFrame::CreateFlexLineAndFlexItemInfo( 5091 ComputedFlexContainerInfo& aContainerInfo, 5092 const nsTArray<FlexLine>& aLines) { 5093 for (const FlexLine& line : aLines) { 5094 ComputedFlexLineInfo* lineInfo = aContainerInfo.mLines.AppendElement(); 5095 // Most of the remaining lineInfo properties will be filled out in 5096 // UpdateFlexLineAndItemInfo (some will be provided by other functions), 5097 // when we have real values. But we still add all the items here, so 5098 // we can capture computed data for each item as we proceed. 5099 for (const FlexItem& item : line.Items()) { 5100 nsIFrame* frame = item.Frame(); 5101 5102 // The frame may be for an element, or it may be for an 5103 // anonymous flex item, e.g. wrapping one or more text nodes. 5104 // DevTools wants the content node for the actual child in 5105 // the DOM tree, so we descend through anonymous boxes. 5106 nsIContent* content = nullptr; 5107 nsIFrame* targetFrame = GetFirstNonAnonBoxInSubtree(frame); 5108 if (targetFrame) { 5109 content = targetFrame->GetContent(); 5110 } 5111 5112 // Skip over content that is only whitespace, which might 5113 // have been broken off from a text node which is our real 5114 // target. 5115 while (content && content->TextIsOnlyWhitespace()) { 5116 // If content is only whitespace, try the frame sibling. 5117 targetFrame = targetFrame->GetNextSibling(); 5118 if (targetFrame) { 5119 content = targetFrame->GetContent(); 5120 } else { 5121 content = nullptr; 5122 } 5123 } 5124 5125 ComputedFlexItemInfo* itemInfo = lineInfo->mItems.AppendElement(); 5126 5127 itemInfo->mNode = content; 5128 5129 // itemInfo->mMainBaseSize and mMainDeltaSize will be filled out 5130 // in ResolveFlexibleLengths(). Other measurements will be captured in 5131 // UpdateFlexLineAndItemInfo. 5132 } 5133 } 5134 } 5135 5136 void nsFlexContainerFrame::ComputeFlexDirections( 5137 ComputedFlexContainerInfo& aContainerInfo, 5138 const FlexboxAxisTracker& aAxisTracker) { 5139 auto ConvertPhysicalStartSideToFlexPhysicalDirection = 5140 [](mozilla::Side aStartSide) { 5141 switch (aStartSide) { 5142 case eSideLeft: 5143 return dom::FlexPhysicalDirection::Horizontal_lr; 5144 case eSideRight: 5145 return dom::FlexPhysicalDirection::Horizontal_rl; 5146 case eSideTop: 5147 return dom::FlexPhysicalDirection::Vertical_tb; 5148 case eSideBottom: 5149 return dom::FlexPhysicalDirection::Vertical_bt; 5150 } 5151 5152 MOZ_ASSERT_UNREACHABLE("We should handle all sides!"); 5153 return dom::FlexPhysicalDirection::Horizontal_lr; 5154 }; 5155 5156 aContainerInfo.mMainAxisDirection = 5157 ConvertPhysicalStartSideToFlexPhysicalDirection( 5158 aAxisTracker.MainAxisPhysicalStartSide()); 5159 aContainerInfo.mCrossAxisDirection = 5160 ConvertPhysicalStartSideToFlexPhysicalDirection( 5161 aAxisTracker.CrossAxisPhysicalStartSide()); 5162 } 5163 5164 void nsFlexContainerFrame::UpdateFlexLineAndItemInfo( 5165 ComputedFlexContainerInfo& aContainerInfo, 5166 const nsTArray<FlexLine>& aLines) { 5167 uint32_t lineIndex = 0; 5168 for (const FlexLine& line : aLines) { 5169 ComputedFlexLineInfo& lineInfo = aContainerInfo.mLines[lineIndex]; 5170 5171 lineInfo.mCrossSize = line.LineCrossSize(); 5172 lineInfo.mFirstBaselineOffset = line.FirstBaselineOffset(); 5173 lineInfo.mLastBaselineOffset = line.LastBaselineOffset(); 5174 5175 uint32_t itemIndex = 0; 5176 for (const FlexItem& item : line.Items()) { 5177 ComputedFlexItemInfo& itemInfo = lineInfo.mItems[itemIndex]; 5178 itemInfo.mFrameRect = item.Frame()->GetRect(); 5179 itemInfo.mMainMinSize = item.MainMinSize(); 5180 itemInfo.mMainMaxSize = item.MainMaxSize(); 5181 itemInfo.mCrossMinSize = item.CrossMinSize(); 5182 itemInfo.mCrossMaxSize = item.CrossMaxSize(); 5183 itemInfo.mClampState = 5184 item.WasMinClamped() 5185 ? mozilla::dom::FlexItemClampState::Clamped_to_min 5186 : (item.WasMaxClamped() 5187 ? mozilla::dom::FlexItemClampState::Clamped_to_max 5188 : mozilla::dom::FlexItemClampState::Unclamped); 5189 ++itemIndex; 5190 } 5191 ++lineIndex; 5192 } 5193 } 5194 5195 nsFlexContainerFrame* nsFlexContainerFrame::GetFlexFrameWithComputedInfo( 5196 nsIFrame* aFrame) { 5197 // Prepare a lambda function that we may need to call multiple times. 5198 auto GetFlexContainerFrame = [](nsIFrame* aFrame) -> nsFlexContainerFrame* { 5199 // Return the aFrame's content insertion frame, iff it is a flex container. 5200 if (!aFrame) { 5201 return nullptr; 5202 } 5203 return do_QueryFrame(aFrame->GetContentInsertionFrame()); 5204 }; 5205 5206 nsFlexContainerFrame* flexFrame = GetFlexContainerFrame(aFrame); 5207 if (!flexFrame) { 5208 return nullptr; 5209 } 5210 // Generate the FlexContainerInfo data, if it's not already there. 5211 if (flexFrame->HasProperty(FlexContainerInfo())) { 5212 return flexFrame; 5213 } 5214 // Trigger a reflow that generates additional flex property data. 5215 // Hold onto aFrame while we do this, in case reflow destroys it. 5216 AutoWeakFrame weakFrameRef(aFrame); 5217 5218 RefPtr<mozilla::PresShell> presShell = flexFrame->PresShell(); 5219 flexFrame->AddStateBits(NS_STATE_FLEX_COMPUTED_INFO); 5220 presShell->FrameNeedsReflow(flexFrame, IntrinsicDirty::None, 5221 NS_FRAME_IS_DIRTY); 5222 presShell->FlushPendingNotifications(FlushType::Layout); 5223 5224 // Since the reflow may have side effects, get the flex frame 5225 // again. But if the weakFrameRef is no longer valid, then we 5226 // must bail out. 5227 if (!weakFrameRef.IsAlive()) { 5228 return nullptr; 5229 } 5230 5231 flexFrame = GetFlexContainerFrame(weakFrameRef.GetFrame()); 5232 5233 NS_WARNING_ASSERTION( 5234 !flexFrame || flexFrame->HasProperty(FlexContainerInfo()), 5235 "The state bit should've made our forced-reflow " 5236 "generate a FlexContainerInfo object"); 5237 return flexFrame; 5238 } 5239 5240 /* static */ 5241 bool nsFlexContainerFrame::IsItemInlineAxisMainAxis(nsIFrame* aFrame) { 5242 MOZ_ASSERT(aFrame && aFrame->IsFlexItem(), "expecting arg to be a flex item"); 5243 const WritingMode flexItemWM = aFrame->GetWritingMode(); 5244 const nsIFrame* flexContainer = aFrame->GetParent(); 5245 5246 if (flexContainer->IsLegacyWebkitBox()) { 5247 // For legacy boxes, the main axis is determined by "box-orient", and we can 5248 // just directly check if that's vertical, and compare that to whether the 5249 // item's WM is also vertical: 5250 bool boxOrientIsVertical = 5251 flexContainer->StyleXUL()->mBoxOrient == StyleBoxOrient::Vertical; 5252 return flexItemWM.IsVertical() == boxOrientIsVertical; 5253 } 5254 5255 // For modern CSS flexbox, we get our return value by asking two questions 5256 // and comparing their answers. 5257 // Question 1: does aFrame have the same inline axis as its flex container? 5258 bool itemInlineAxisIsParallelToParent = 5259 !flexItemWM.IsOrthogonalTo(flexContainer->GetWritingMode()); 5260 5261 // Question 2: is aFrame's flex container row-oriented? (This tells us 5262 // whether the flex container's main axis is its inline axis.) 5263 auto flexDirection = flexContainer->StylePosition()->mFlexDirection; 5264 bool flexContainerIsRowOriented = 5265 flexDirection == StyleFlexDirection::Row || 5266 flexDirection == StyleFlexDirection::RowReverse; 5267 5268 // aFrame's inline axis is its flex container's main axis IFF the above 5269 // questions have the same answer. 5270 return flexContainerIsRowOriented == itemInlineAxisIsParallelToParent; 5271 } 5272 5273 /* static */ 5274 bool nsFlexContainerFrame::IsUsedFlexBasisContent( 5275 const StyleFlexBasis& aFlexBasis, const StyleSize& aMainSize) { 5276 // We have a used flex-basis of 'content' if flex-basis explicitly has that 5277 // value, OR if flex-basis is 'auto' (deferring to the main-size property) 5278 // and the main-size property is also 'auto'. 5279 // See https://drafts.csswg.org/css-flexbox-1/#valdef-flex-basis-auto 5280 if (aFlexBasis.IsContent()) { 5281 return true; 5282 } 5283 return aFlexBasis.IsAuto() && aMainSize.IsAuto(); 5284 } 5285 5286 nsFlexContainerFrame::FlexLayoutResult nsFlexContainerFrame::DoFlexLayout( 5287 const ReflowInput& aReflowInput, const nscoord aTentativeContentBoxMainSize, 5288 const nscoord aTentativeContentBoxCrossSize, 5289 const FlexboxAxisTracker& aAxisTracker, nscoord aMainGapSize, 5290 nscoord aCrossGapSize, nsTArray<StrutInfo>& aStruts, 5291 ComputedFlexContainerInfo* const aContainerInfo) { 5292 FlexLayoutResult flr; 5293 5294 GenerateFlexLines(aReflowInput, aTentativeContentBoxMainSize, 5295 aTentativeContentBoxCrossSize, aStruts, aAxisTracker, 5296 aMainGapSize, flr.mPlaceholders, flr.mLines, 5297 flr.mHasCollapsedItems); 5298 5299 if ((flr.mLines.Length() == 1 && flr.mLines[0].IsEmpty()) || 5300 aReflowInput.mStyleDisplay->IsContainLayout()) { 5301 // We have no flex items, or we're layout-contained. So, we have no 5302 // baseline, and our parent should synthesize a baseline if needed. 5303 AddStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE); 5304 } else { 5305 RemoveStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE); 5306 } 5307 5308 // Construct our computed info if we've been asked to do so. This is 5309 // necessary to do now so we can capture some computed values for 5310 // FlexItems during layout that would not otherwise be saved (like 5311 // size adjustments). We'll later fix up the line properties, 5312 // because the correct values aren't available yet. 5313 if (aContainerInfo) { 5314 MOZ_ASSERT(HasAnyStateBits(NS_STATE_FLEX_COMPUTED_INFO), 5315 "We should only have the info struct if we should generate it"); 5316 5317 if (!aStruts.IsEmpty()) { 5318 // We restarted DoFlexLayout, and may have stale mLines to clear: 5319 aContainerInfo->mLines.Clear(); 5320 } else { 5321 MOZ_ASSERT(aContainerInfo->mLines.IsEmpty(), "Shouldn't have lines yet."); 5322 } 5323 5324 CreateFlexLineAndFlexItemInfo(*aContainerInfo, flr.mLines); 5325 ComputeFlexDirections(*aContainerInfo, aAxisTracker); 5326 } 5327 5328 flr.mContentBoxMainSize = ComputeMainSize( 5329 aReflowInput, aAxisTracker, aTentativeContentBoxMainSize, flr.mLines); 5330 5331 uint32_t lineIndex = 0; 5332 for (FlexLine& line : flr.mLines) { 5333 ComputedFlexLineInfo* lineInfo = 5334 aContainerInfo ? &aContainerInfo->mLines[lineIndex] : nullptr; 5335 line.ResolveFlexibleLengths(flr.mContentBoxMainSize, lineInfo); 5336 ++lineIndex; 5337 } 5338 5339 // Cross Size Determination - Flexbox spec section 9.4 5340 // https://drafts.csswg.org/css-flexbox-1/#cross-sizing 5341 // =================================================== 5342 // Calculate the hypothetical cross size of each item: 5343 5344 // 'sumLineCrossSizes' includes the size of all gaps between lines. We 5345 // initialize it with the sum of all the gaps, and add each line's cross size 5346 // at the end of the following for-loop. 5347 nscoord sumLineCrossSizes = aCrossGapSize * (flr.mLines.Length() - 1); 5348 for (FlexLine& line : flr.mLines) { 5349 for (FlexItem& item : line.Items()) { 5350 // The item may already have the correct cross-size; only recalculate 5351 // if the item's main size resolution (flexing) could have influenced it: 5352 if (item.CanMainSizeInfluenceCrossSize()) { 5353 StyleSizeOverrides sizeOverrides; 5354 if (item.IsInlineAxisMainAxis()) { 5355 sizeOverrides.mStyleISize.emplace(item.StyleMainSize()); 5356 } else { 5357 sizeOverrides.mStyleBSize.emplace(item.StyleMainSize()); 5358 } 5359 FLEX_ITEM_LOG(item.Frame(), "Sizing item in cross axis"); 5360 FLEX_LOGV("Main size override: %d", item.MainSize()); 5361 5362 const WritingMode wm = item.GetWritingMode(); 5363 LogicalSize availSize = aReflowInput.ComputedSize(wm); 5364 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; 5365 ReflowInput childReflowInput(PresContext(), aReflowInput, item.Frame(), 5366 availSize, Nothing(), {}, sizeOverrides, 5367 {ComputeSizeFlag::ShrinkWrap}); 5368 if (item.IsBlockAxisMainAxis() && item.TreatBSizeAsIndefinite()) { 5369 childReflowInput.mFlags.mTreatBSizeAsIndefinite = true; 5370 } 5371 5372 SizeItemInCrossAxis(childReflowInput, item); 5373 } 5374 } 5375 // Now that we've finished with this line's items, size the line itself: 5376 line.ComputeCrossSizeAndBaseline(aAxisTracker); 5377 sumLineCrossSizes += line.LineCrossSize(); 5378 } 5379 5380 bool isCrossSizeDefinite; 5381 flr.mContentBoxCrossSize = ComputeCrossSize( 5382 aReflowInput, aAxisTracker, aTentativeContentBoxCrossSize, 5383 sumLineCrossSizes, &isCrossSizeDefinite); 5384 5385 // Set up state for cross-axis alignment, at a high level (outside the 5386 // scope of a particular flex line) 5387 CrossAxisPositionTracker crossAxisPosnTracker( 5388 flr.mLines, aReflowInput, flr.mContentBoxCrossSize, isCrossSizeDefinite, 5389 aAxisTracker, aCrossGapSize); 5390 5391 // Now that we know the cross size of each line (including 5392 // "align-content:stretch" adjustments, from the CrossAxisPositionTracker 5393 // constructor), we can create struts for any flex items with 5394 // "visibility: collapse" (and restart flex layout). 5395 // Make sure to only do this if we had no struts. 5396 if (aStruts.IsEmpty() && flr.mHasCollapsedItems && 5397 !StyleVisibility()->UseLegacyCollapseBehavior()) { 5398 BuildStrutInfoFromCollapsedItems(flr.mLines, aStruts); 5399 if (!aStruts.IsEmpty()) { 5400 // Restart flex layout, using our struts. 5401 return flr; 5402 } 5403 } 5404 5405 // If the flex container is row-oriented, it should derive its first/last 5406 // baseline from the WM-relative startmost/endmost FlexLine if any items in 5407 // the line participate in baseline alignment. 5408 // https://drafts.csswg.org/css-flexbox-1/#flex-baselines 5409 // 5410 // Initialize the relevant variables here so that we can establish baselines 5411 // while iterating FlexLine later (while crossAxisPosnTracker is conveniently 5412 // pointing at the cross-start edge of that line, which the line's baseline 5413 // offset is measured from). 5414 const FlexLine* lineForFirstBaseline = nullptr; 5415 const FlexLine* lineForLastBaseline = nullptr; 5416 if (aAxisTracker.IsRowOriented()) { 5417 lineForFirstBaseline = &StartmostLine(flr.mLines, aAxisTracker); 5418 lineForLastBaseline = &EndmostLine(flr.mLines, aAxisTracker); 5419 } else { 5420 // For column-oriented flex container, use sentinel value to prompt us to 5421 // get baselines from the startmost/endmost items. 5422 flr.mAscent = nscoord_MIN; 5423 flr.mAscentForLast = nscoord_MIN; 5424 } 5425 5426 const auto justifyContent = 5427 aReflowInput.mFrame->IsLegacyWebkitBox() 5428 ? ConvertLegacyStyleToJustifyContent(StyleXUL()) 5429 : aReflowInput.mStylePosition->mJustifyContent; 5430 5431 lineIndex = 0; 5432 for (FlexLine& line : flr.mLines) { 5433 // Main-Axis Alignment - Flexbox spec section 9.5 5434 // https://drafts.csswg.org/css-flexbox-1/#main-alignment 5435 // ============================================== 5436 line.PositionItemsInMainAxis(justifyContent, flr.mContentBoxMainSize, 5437 aAxisTracker); 5438 5439 // See if we need to extract some computed info for this line. 5440 if (MOZ_UNLIKELY(aContainerInfo)) { 5441 ComputedFlexLineInfo& lineInfo = aContainerInfo->mLines[lineIndex]; 5442 lineInfo.mCrossStart = crossAxisPosnTracker.Position(); 5443 } 5444 5445 // Cross-Axis Alignment - Flexbox spec section 9.6 5446 // https://drafts.csswg.org/css-flexbox-1/#cross-alignment 5447 // =============================================== 5448 line.PositionItemsInCrossAxis(crossAxisPosnTracker.Position(), 5449 aAxisTracker); 5450 5451 // Flex Container Baselines - Flexbox spec section 8.5 5452 // https://drafts.csswg.org/css-flexbox-1/#flex-baselines 5453 auto ComputeAscentFromLine = [&](const FlexLine& aLine, 5454 BaselineSharingGroup aBaselineGroup) { 5455 MOZ_ASSERT(aAxisTracker.IsRowOriented(), 5456 "This makes sense only if we are row-oriented!"); 5457 5458 // baselineOffsetInLine is a distance from the line's cross-start edge. 5459 const nscoord baselineOffsetInLine = 5460 aLine.ExtractBaselineOffset(aBaselineGroup); 5461 5462 if (baselineOffsetInLine == nscoord_MIN) { 5463 // No "first baseline"-aligned or "last baseline"-aligned items in 5464 // aLine. Return a sentinel value to prompt us to get baseline from the 5465 // startmost or endmost FlexItem after we've reflowed it. 5466 return nscoord_MIN; 5467 } 5468 5469 // This "ascent" variable is a distance from the flex container's 5470 // content-box block-start edge. 5471 const nscoord ascent = aAxisTracker.LogicalAscentFromFlexRelativeAscent( 5472 crossAxisPosnTracker.Position() + baselineOffsetInLine, 5473 flr.mContentBoxCrossSize); 5474 5475 // Convert "ascent" variable to a distance from border-box start or end 5476 // edge, per documentation for FlexLayoutResult ascent members. 5477 const auto wm = aAxisTracker.GetWritingMode(); 5478 if (aBaselineGroup == BaselineSharingGroup::First) { 5479 return ascent + 5480 aReflowInput.ComputedLogicalBorderPadding(wm).BStart(wm); 5481 } 5482 return flr.mContentBoxCrossSize - ascent + 5483 aReflowInput.ComputedLogicalBorderPadding(wm).BEnd(wm); 5484 }; 5485 5486 if (lineForFirstBaseline && lineForFirstBaseline == &line) { 5487 flr.mAscent = ComputeAscentFromLine(line, BaselineSharingGroup::First); 5488 } 5489 if (lineForLastBaseline && lineForLastBaseline == &line) { 5490 flr.mAscentForLast = 5491 ComputeAscentFromLine(line, BaselineSharingGroup::Last); 5492 } 5493 5494 crossAxisPosnTracker.TraverseLine(line); 5495 crossAxisPosnTracker.TraversePackingSpace(); 5496 5497 if (&line != &flr.mLines.LastElement()) { 5498 crossAxisPosnTracker.TraverseGap(); 5499 } 5500 ++lineIndex; 5501 } 5502 5503 return flr; 5504 } 5505 5506 // This data structure is used in fragmentation, storing the block coordinate 5507 // metrics when reflowing 1) the BStart-most line in each fragment of a 5508 // row-oriented flex container or, 2) the BStart-most item in each fragment of a 5509 // single-line column-oriented flex container. 5510 // 5511 // When we lay out a row-oriented flex container fragment, its first line might 5512 // contain one or more monolithic items that were pushed from the previous 5513 // fragment specifically to avoid having those monolithic items overlap the 5514 // page/column break. The situation is similar for single-row column-oriented 5515 // flex container fragments, but a bit simpler; only their first item might have 5516 // been pushed to avoid overlapping a page/column break. 5517 // 5518 // We'll have to place any such pushed items at the block-start edge of the 5519 // current fragment's content-box, which is as close as we can get them to their 5520 // theoretical/unfragmented position (without slicing them); but it does 5521 // represent a shift away from their theoretical/unfragmented position (which 5522 // was somewhere in the previous fragment). 5523 // 5524 // When that happens, we need to record the maximum such shift that we had to 5525 // perform so that we can apply the same block-endwards shift to "downstream" 5526 // items (items towards the block-end edge) that we could otherwise collide 5527 // with. We also potentially apply the same shift when computing the block-end 5528 // edge of this flex container fragment's content-box so that we don't 5529 // inadvertently shift the last item (or line-of-items) to overlap the flex 5530 // container's border, or content beyond the flex container. 5531 // 5532 // We use this structure to keep track of several metrics, in service of this 5533 // goal. This structure is also necessary to adjust PerFragmentFlexData at the 5534 // end of ReflowChildren(). 5535 // 5536 // Note: "First" in the struct name means "BStart-most", not the order in the 5537 // flex line array or flex item array. 5538 struct FirstLineOrFirstItemBAxisMetrics final { 5539 // This value stores the block-end edge shift for 1) the BStart-most line in 5540 // the current fragment of a row-oriented flex container, or 2) the 5541 // BStart-most item in the current fragment of a single-line column-oriented 5542 // flex container. This number is non-negative. 5543 // 5544 // This value may become positive when any item is a first-in-flow and also 5545 // satisfies either the above condition 1) or 2), since that's a hint that it 5546 // could be monolithic or have a monolithic first descendant, and therefore an 5547 // item that might incur a page/column-break-dodging position-shift that this 5548 // variable needs to track. 5549 // 5550 // This value also stores the fragmentation-imposed growth in the block-size 5551 // of a) the BStart-most line in the current fragment of a row-oriented flex 5552 // container, or b) the BStart-most item in the current fragment of a 5553 // single-line column-oriented flex container. This number is non-negative. 5554 nscoord mBEndEdgeShift = 0; 5555 5556 // The first and second value in the pair store the max block-end edges for 5557 // items before and after applying the per-item position-shift in the block 5558 // axis. We only record the block-end edges for items with first-in-flow 5559 // frames placed in the current flex container fragment. This is used only by 5560 // row-oriented flex containers. 5561 Maybe<std::pair<nscoord, nscoord>> mMaxBEndEdge; 5562 }; 5563 5564 std::tuple<nscoord, nsReflowStatus> nsFlexContainerFrame::ReflowChildren( 5565 const ReflowInput& aReflowInput, const nsSize& aContainerSize, 5566 const LogicalSize& aAvailableSizeForItems, 5567 const LogicalMargin& aBorderPadding, const FlexboxAxisTracker& aAxisTracker, 5568 FlexLayoutResult& aFlr, PerFragmentFlexData& aFragmentData) { 5569 if (HidesContentForLayout()) { 5570 return {0, nsReflowStatus()}; 5571 } 5572 5573 // Before giving each child a final reflow, calculate the origin of the 5574 // flex container's content box (with respect to its border-box), so that 5575 // we can compute our flex item's final positions. 5576 WritingMode flexWM = aReflowInput.GetWritingMode(); 5577 const LogicalPoint containerContentBoxOrigin = 5578 aBorderPadding.StartOffset(flexWM); 5579 5580 // The block-end of children is relative to the flex container's border-box. 5581 nscoord maxBlockEndEdgeOfChildren = containerContentBoxOrigin.B(flexWM); 5582 5583 FirstLineOrFirstItemBAxisMetrics bAxisMetrics; 5584 FrameHashtable pushedItems; 5585 FrameHashtable incompleteItems; 5586 FrameHashtable overflowIncompleteItems; 5587 5588 const bool isSingleLine = 5589 IsSingleLine(aReflowInput.mFrame, aReflowInput.mStylePosition); 5590 const FlexLine& startmostLine = StartmostLine(aFlr.mLines, aAxisTracker); 5591 const FlexLine& endmostLine = EndmostLine(aFlr.mLines, aAxisTracker); 5592 const FlexItem* startmostItem = 5593 startmostLine.IsEmpty() ? nullptr 5594 : &startmostLine.StartmostItem(aAxisTracker); 5595 const FlexItem* endmostItem = 5596 endmostLine.IsEmpty() ? nullptr : &endmostLine.EndmostItem(aAxisTracker); 5597 5598 bool endmostItemOrLineHasBreakAfter = false; 5599 // If true, push all remaining flex items to the container's next-in-flow. 5600 bool shouldPushRemainingItems = false; 5601 5602 // FINAL REFLOW: Give each child frame another chance to reflow. 5603 const size_t numLines = aFlr.mLines.Length(); 5604 for (size_t lineIdx = 0; lineIdx < numLines; ++lineIdx) { 5605 // Iterate flex lines from the startmost to endmost (relative to flex 5606 // container's writing-mode). 5607 const auto& line = 5608 aFlr.mLines[aAxisTracker.IsCrossAxisReversed() ? numLines - lineIdx - 1 5609 : lineIdx]; 5610 MOZ_ASSERT(lineIdx != 0 || &line == &startmostLine, 5611 "Logic for finding startmost line should be consistent!"); 5612 5613 // These two variables can be set when we are a row-oriented flex container 5614 // during fragmentation. 5615 bool lineHasBreakBefore = false; 5616 bool lineHasBreakAfter = false; 5617 5618 const size_t numItems = line.Items().Length(); 5619 for (size_t itemIdx = 0; itemIdx < numItems; ++itemIdx) { 5620 // Iterate flex items from the startmost to endmost (relative to flex 5621 // container's writing-mode). 5622 const FlexItem& item = line.Items()[aAxisTracker.IsMainAxisReversed() 5623 ? numItems - itemIdx - 1 5624 : itemIdx]; 5625 MOZ_ASSERT(lineIdx != 0 || itemIdx != 0 || &item == startmostItem, 5626 "Logic for finding startmost item should be consistent!"); 5627 5628 LogicalPoint framePos = aAxisTracker.LogicalPointFromFlexRelativePoint( 5629 item.MainPosition(), item.CrossPosition(), aFlr.mContentBoxMainSize, 5630 aFlr.mContentBoxCrossSize); 5631 // This variable records the item's block-end edge before we give it a 5632 // per-item-position-shift, if the item is a first-in-flow in the 5633 // startmost line of a row-oriented flex container fragment. It is used to 5634 // determine the block-end edge shift for the startmost line at the end of 5635 // the outer loop. 5636 Maybe<nscoord> frameBPosBeforePerItemShift; 5637 5638 if (item.Frame()->GetPrevInFlow()) { 5639 // The item is a continuation. Lay it out at the beginning of the 5640 // available space. 5641 framePos.B(flexWM) = 0; 5642 } else if (GetPrevInFlow()) { 5643 // The item we're placing is not a continuation; though we're placing it 5644 // into a flex container fragment which *is* a continuation. To compute 5645 // the item's correct position in this fragment, we adjust the item's 5646 // theoretical/unfragmented block-direction position by subtracting the 5647 // cumulative content-box block-size for all the previous fragments and 5648 // adding the cumulative block-end edge shift. 5649 // 5650 // Note that the item's position in this fragment has not been finalized 5651 // yet. At this point, we've adjusted the item's 5652 // theoretical/unfragmented position to be relative to the block-end 5653 // edge of the previous container fragment's content-box. Later, we'll 5654 // compute per-item position-shift to finalize its position. 5655 framePos.B(flexWM) -= aFragmentData.mCumulativeContentBoxBSize; 5656 framePos.B(flexWM) += aFragmentData.mCumulativeBEndEdgeShift; 5657 5658 // This helper gets the per-item position-shift in the block-axis. 5659 auto GetPerItemPositionShiftToBEnd = [&]() { 5660 if (framePos.B(flexWM) >= 0) { 5661 // The item final position might be in current flex container 5662 // fragment or in any of the later fragments. No adjustment needed. 5663 return 0; 5664 } 5665 5666 // The item's block position is negative, but we want to place it at 5667 // the content-box block-start edge of this container fragment. To 5668 // achieve this, return a negated (positive) value to make the final 5669 // block position zero. 5670 // 5671 // This scenario occurs when fragmenting a row-oriented flex container 5672 // where this item is pushed to this container fragment. 5673 return -framePos.B(flexWM); 5674 }; 5675 5676 if (aAxisTracker.IsRowOriented()) { 5677 if (&line == &startmostLine) { 5678 frameBPosBeforePerItemShift.emplace(framePos.B(flexWM)); 5679 framePos.B(flexWM) += GetPerItemPositionShiftToBEnd(); 5680 } else { 5681 // We've computed two things for the startmost line during the outer 5682 // loop's first iteration: 1) how far the block-end edge had to 5683 // shift and 2) how large the block-size needed to grow. Here, we 5684 // just shift all items in the rest of the lines the same amount. 5685 framePos.B(flexWM) += bAxisMetrics.mBEndEdgeShift; 5686 } 5687 } else { 5688 MOZ_ASSERT(aAxisTracker.IsColumnOriented()); 5689 if (isSingleLine) { 5690 if (&item == startmostItem) { 5691 bAxisMetrics.mBEndEdgeShift = GetPerItemPositionShiftToBEnd(); 5692 } 5693 framePos.B(flexWM) += bAxisMetrics.mBEndEdgeShift; 5694 } else { 5695 // Bug 1806717: We need a more sophisticated solution for multi-line 5696 // column-oriented flex container when each line has a different 5697 // position-shift value. For now, we don't shift them. 5698 } 5699 } 5700 } 5701 5702 // Adjust available block-size for the item. (We compute it here because 5703 // framePos is still relative to the container's content-box.) 5704 // 5705 // Note: The available block-size can become negative if item's 5706 // block-direction position is below available space's block-end. 5707 const nscoord availableBSizeForItem = 5708 aAvailableSizeForItems.BSize(flexWM) == NS_UNCONSTRAINEDSIZE 5709 ? NS_UNCONSTRAINEDSIZE 5710 : aAvailableSizeForItems.BSize(flexWM) - framePos.B(flexWM); 5711 5712 // Adjust framePos to be relative to the container's border-box 5713 // (i.e. its frame rect), instead of the container's content-box: 5714 framePos += containerContentBoxOrigin; 5715 5716 // Check if we can skip reflowing the item because it will be pushed to 5717 // our next-in-flow -- i.e. if there was a forced break before it, or its 5718 // position is beyond the available space's block-end. 5719 bool itemInPushedItems = false; 5720 if (shouldPushRemainingItems) { 5721 FLEX_ITEM_LOG( 5722 item.Frame(), 5723 "[frag] Item needed to be pushed to container's next-in-flow due " 5724 "to a forced break before it"); 5725 pushedItems.Insert(item.Frame()); 5726 itemInPushedItems = true; 5727 } else if (availableBSizeForItem != NS_UNCONSTRAINEDSIZE && 5728 availableBSizeForItem <= 0) { 5729 // The item's position is beyond the available space, so we have to push 5730 // it. 5731 // 5732 // Note: Even if all of our items are beyond the available space & get 5733 // pushed here, we'll be guaranteed to place at least one of them (and 5734 // make progress) in one of the flex container's *next* fragment. It's 5735 // because ComputeAvailableSizeForItems() always reserves at least 1px 5736 // available block-size for its children, and we consume all available 5737 // block-size and add it to 5738 // PerFragmentFlexData::mCumulativeContentBoxBSize even if we are not 5739 // laying out any child. 5740 FLEX_ITEM_LOG( 5741 item.Frame(), 5742 "[frag] Item needed to be pushed to container's next-in-flow due " 5743 "to being positioned beyond block-end edge of available space"); 5744 pushedItems.Insert(item.Frame()); 5745 itemInPushedItems = true; 5746 } else if (item.NeedsFinalReflow(aReflowInput)) { 5747 // The available size must be in item's writing-mode. 5748 const WritingMode itemWM = item.GetWritingMode(); 5749 const auto availableSize = 5750 LogicalSize(flexWM, aAvailableSizeForItems.ISize(flexWM), 5751 availableBSizeForItem) 5752 .ConvertTo(itemWM, flexWM); 5753 5754 const bool isAdjacentWithBStart = 5755 framePos.B(flexWM) == containerContentBoxOrigin.B(flexWM); 5756 const nsReflowStatus childStatus = 5757 ReflowFlexItem(aAxisTracker, aReflowInput, item, framePos, 5758 isAdjacentWithBStart, availableSize, aContainerSize); 5759 5760 if (aReflowInput.IsInFragmentedContext()) { 5761 const bool itemHasBreakBefore = 5762 item.Frame()->ShouldBreakBefore(aReflowInput.mBreakType) || 5763 childStatus.IsInlineBreakBefore(); 5764 if (itemHasBreakBefore) { 5765 if (aAxisTracker.IsRowOriented()) { 5766 lineHasBreakBefore = true; 5767 } else if (isSingleLine) { 5768 if (&item == startmostItem) { 5769 if (!GetPrevInFlow() && !aReflowInput.mFlags.mIsTopOfPage) { 5770 // If we are first-in-flow and not at top-of-page, early 5771 // return here to propagate forced break-before from the 5772 // startmost item to the flex container. 5773 nsReflowStatus childrenStatus; 5774 childrenStatus.SetInlineLineBreakBeforeAndReset(); 5775 return {0, childrenStatus}; 5776 } 5777 } else { 5778 shouldPushRemainingItems = true; 5779 } 5780 } else { 5781 // Bug 1806717: We haven't implemented fragmentation for 5782 // multi-line column-oriented flex container, so we just ignore 5783 // forced breaks for now. 5784 } 5785 } 5786 } 5787 5788 const bool shouldPushItem = [&]() { 5789 if (shouldPushRemainingItems) { 5790 return true; 5791 } 5792 if (availableBSizeForItem == NS_UNCONSTRAINEDSIZE) { 5793 // If the available block-size is unconstrained, then we're not 5794 // fragmenting and we don't want to push the item. 5795 return false; 5796 } 5797 if (isAdjacentWithBStart) { 5798 // The flex item is adjacent with block-start of the container's 5799 // content-box. Don't push it, or we'll trap in an infinite loop. 5800 return false; 5801 } 5802 if (item.Frame()->BSize() <= availableBSizeForItem) { 5803 return false; 5804 } 5805 if (aAxisTracker.IsColumnOriented() && 5806 item.Frame()->StyleDisplay()->mBreakBefore == 5807 StyleBreakBetween::Avoid) { 5808 return false; 5809 } 5810 return true; 5811 }(); 5812 if (shouldPushItem) { 5813 FLEX_ITEM_LOG( 5814 item.Frame(), 5815 "[frag] Item needed to be pushed to container's next-in-flow " 5816 "because it encounters a forced break before it, or its " 5817 "block-size is larger than the available space"); 5818 pushedItems.Insert(item.Frame()); 5819 itemInPushedItems = true; 5820 } else if (childStatus.IsIncomplete()) { 5821 incompleteItems.Insert(item.Frame()); 5822 } else if (childStatus.IsOverflowIncomplete()) { 5823 overflowIncompleteItems.Insert(item.Frame()); 5824 } 5825 5826 if (aReflowInput.IsInFragmentedContext()) { 5827 const bool itemHasBreakAfter = 5828 item.Frame()->ShouldBreakAfter(aReflowInput.mBreakType) || 5829 childStatus.IsInlineBreakAfter(); 5830 if (itemHasBreakAfter) { 5831 if (aAxisTracker.IsRowOriented()) { 5832 lineHasBreakAfter = true; 5833 } else if (isSingleLine) { 5834 shouldPushRemainingItems = true; 5835 if (&item == endmostItem) { 5836 endmostItemOrLineHasBreakAfter = true; 5837 } 5838 } else { 5839 // Bug 1806717: We haven't implemented fragmentation for 5840 // multi-line column-oriented flex container, so we just ignore 5841 // forced breaks for now. 5842 } 5843 } 5844 } 5845 } else { 5846 // We already reflowed the item with the right content-box size, so we 5847 // can simply move it into place. 5848 MoveFlexItemToFinalPosition(item, framePos, aContainerSize); 5849 } 5850 5851 if (!itemInPushedItems) { 5852 const nscoord borderBoxBSize = item.Frame()->BSize(flexWM); 5853 const nscoord bEndEdgeAfterPerItemShift = 5854 framePos.B(flexWM) + borderBoxBSize; 5855 5856 // The item (or a fragment thereof) was placed in this flex container 5857 // fragment. Update the max block-end edge with the item's block-end 5858 // edge. 5859 maxBlockEndEdgeOfChildren = 5860 std::max(maxBlockEndEdgeOfChildren, bEndEdgeAfterPerItemShift); 5861 5862 if (frameBPosBeforePerItemShift) { 5863 // Make the block-end edge relative to flex container's border-box 5864 // because bEndEdgeAfterPerItemShift is relative to the border-box. 5865 const nscoord bEndEdgeBeforePerItemShift = 5866 containerContentBoxOrigin.B(flexWM) + 5867 *frameBPosBeforePerItemShift + borderBoxBSize; 5868 5869 if (bAxisMetrics.mMaxBEndEdge) { 5870 auto& [before, after] = *bAxisMetrics.mMaxBEndEdge; 5871 before = std::max(before, bEndEdgeBeforePerItemShift); 5872 after = std::max(after, bEndEdgeAfterPerItemShift); 5873 } else { 5874 bAxisMetrics.mMaxBEndEdge.emplace(bEndEdgeBeforePerItemShift, 5875 bEndEdgeAfterPerItemShift); 5876 } 5877 } 5878 5879 if (item.Frame()->GetPrevInFlow()) { 5880 // Items with a previous-continuation may experience some 5881 // fragmentation-imposed growth in their block-size; we compute that 5882 // here. 5883 const nscoord bSizeOfThisFragment = 5884 item.Frame()->ContentSize(flexWM).BSize(flexWM); 5885 const nscoord consumedBSize = FlexItemConsumedBSize(item); 5886 const nscoord unfragmentedBSize = item.BSize(); 5887 nscoord bSizeGrowthOfThisFragment = 0; 5888 5889 if (consumedBSize >= unfragmentedBSize) { 5890 // The item's block-size has been grown to exceed the unfragmented 5891 // block-size in the previous fragments. 5892 bSizeGrowthOfThisFragment = bSizeOfThisFragment; 5893 } else if (consumedBSize + bSizeOfThisFragment >= unfragmentedBSize) { 5894 // The item's block-size just grows in the current fragment to 5895 // exceed the unfragmented block-size. 5896 bSizeGrowthOfThisFragment = 5897 consumedBSize + bSizeOfThisFragment - unfragmentedBSize; 5898 } 5899 5900 if (aAxisTracker.IsRowOriented()) { 5901 if (&line == &startmostLine) { 5902 bAxisMetrics.mBEndEdgeShift = std::max( 5903 bAxisMetrics.mBEndEdgeShift, bSizeGrowthOfThisFragment); 5904 } 5905 } else { 5906 MOZ_ASSERT(aAxisTracker.IsColumnOriented()); 5907 if (isSingleLine) { 5908 if (&item == startmostItem) { 5909 MOZ_ASSERT(bAxisMetrics.mBEndEdgeShift == 0, 5910 "The item's frame is a continuation, so it " 5911 "shouldn't shift!"); 5912 bAxisMetrics.mBEndEdgeShift = bSizeGrowthOfThisFragment; 5913 } 5914 } else { 5915 // Bug 1806717: We need a more sophisticated solution for 5916 // multi-line column-oriented flex container when each line has a 5917 // different block-size growth value. For now, we don't deal with 5918 // them. 5919 } 5920 } 5921 } 5922 } 5923 5924 // If the item has auto margins, and we were tracking the UsedMargin 5925 // property, set the property to the computed margin values. 5926 if (item.HasAnyAutoMargin()) { 5927 nsMargin* propValue = 5928 item.Frame()->GetProperty(nsIFrame::UsedMarginProperty()); 5929 if (propValue) { 5930 *propValue = item.PhysicalMargin(); 5931 } 5932 } 5933 } 5934 5935 if (aReflowInput.IsInFragmentedContext() && aAxisTracker.IsRowOriented()) { 5936 // Propagate forced break values from the flex items to its flex line. 5937 if (lineHasBreakBefore) { 5938 if (&line == &startmostLine) { 5939 if (!GetPrevInFlow() && !aReflowInput.mFlags.mIsTopOfPage) { 5940 // If we are first-in-flow and not at top-of-page, early return here 5941 // to propagate forced break-before from the startmost line to the 5942 // flex container. 5943 nsReflowStatus childrenStatus; 5944 childrenStatus.SetInlineLineBreakBeforeAndReset(); 5945 return {0, childrenStatus}; 5946 } 5947 } else { 5948 // Current non-startmost line has forced break-before, so push all the 5949 // items in this line. 5950 for (const FlexItem& item : line.Items()) { 5951 pushedItems.Insert(item.Frame()); 5952 incompleteItems.Remove(item.Frame()); 5953 overflowIncompleteItems.Remove(item.Frame()); 5954 } 5955 shouldPushRemainingItems = true; 5956 } 5957 } 5958 if (lineHasBreakAfter) { 5959 shouldPushRemainingItems = true; 5960 if (&line == &endmostLine) { 5961 endmostItemOrLineHasBreakAfter = true; 5962 } 5963 } 5964 } 5965 5966 // Now we've finished processing all the items in the startmost line. 5967 // Determine the amount by which the startmost line's block-end edge has 5968 // shifted, so we can apply the same shift for the remaining lines. 5969 if (GetPrevInFlow() && aAxisTracker.IsRowOriented() && 5970 &line == &startmostLine && bAxisMetrics.mMaxBEndEdge) { 5971 auto& [before, after] = *bAxisMetrics.mMaxBEndEdge; 5972 bAxisMetrics.mBEndEdgeShift = 5973 std::max(bAxisMetrics.mBEndEdgeShift, after - before); 5974 } 5975 } 5976 5977 if (!aFlr.mPlaceholders.IsEmpty()) { 5978 ReflowPlaceholders(aReflowInput, aFlr.mPlaceholders, 5979 containerContentBoxOrigin, aContainerSize); 5980 } 5981 5982 nsReflowStatus childrenStatus; 5983 if (!pushedItems.IsEmpty() || !incompleteItems.IsEmpty()) { 5984 childrenStatus.SetIncomplete(); 5985 } else if (!overflowIncompleteItems.IsEmpty()) { 5986 childrenStatus.SetOverflowIncomplete(); 5987 } else if (endmostItemOrLineHasBreakAfter) { 5988 childrenStatus.SetInlineLineBreakAfter(); 5989 } 5990 PushIncompleteChildren(pushedItems, incompleteItems, overflowIncompleteItems); 5991 5992 // TODO: Try making this a fatal assertion after we fix bug 1751260. 5993 NS_ASSERTION(childrenStatus.IsFullyComplete() || 5994 aAvailableSizeForItems.BSize(flexWM) != NS_UNCONSTRAINEDSIZE, 5995 "We shouldn't have any incomplete children if the available " 5996 "block-size is unconstrained!"); 5997 5998 if (!pushedItems.IsEmpty()) { 5999 AddStateBits(NS_STATE_FLEX_DID_PUSH_ITEMS); 6000 } 6001 6002 if (GetPrevInFlow()) { 6003 aFragmentData.mCumulativeBEndEdgeShift += bAxisMetrics.mBEndEdgeShift; 6004 } 6005 6006 return {maxBlockEndEdgeOfChildren, childrenStatus}; 6007 } 6008 6009 void nsFlexContainerFrame::PopulateReflowOutput( 6010 ReflowOutput& aReflowOutput, const ReflowInput& aReflowInput, 6011 nsReflowStatus& aStatus, const LogicalSize& aContentBoxSize, 6012 const LogicalMargin& aBorderPadding, const nscoord aConsumedBSize, 6013 const bool aMayNeedNextInFlow, const nscoord aMaxBlockEndEdgeOfChildren, 6014 const nsReflowStatus& aChildrenStatus, 6015 const FlexboxAxisTracker& aAxisTracker, FlexLayoutResult& aFlr) { 6016 const WritingMode flexWM = aReflowInput.GetWritingMode(); 6017 6018 // Compute flex container's desired size (in its own writing-mode). 6019 LogicalSize desiredSizeInFlexWM(flexWM); 6020 desiredSizeInFlexWM.ISize(flexWM) = 6021 aContentBoxSize.ISize(flexWM) + aBorderPadding.IStartEnd(flexWM); 6022 6023 // Unconditionally skip adding block-end border and padding for now. We add it 6024 // lower down, after we've established baseline and decided whether bottom 6025 // border-padding fits (if we're fragmented). 6026 const nscoord effectiveContentBSizeWithBStartBP = 6027 aContentBoxSize.BSize(flexWM) - aConsumedBSize + 6028 aBorderPadding.BStart(flexWM); 6029 nscoord blockEndContainerBP = aBorderPadding.BEnd(flexWM); 6030 6031 if (aMayNeedNextInFlow) { 6032 // We assume our status should be reported as incomplete because we may need 6033 // a next-in-flow. 6034 bool isStatusIncomplete = true; 6035 6036 const nscoord availableBSizeMinusBEndBP = 6037 aReflowInput.AvailableBSize() - aBorderPadding.BEnd(flexWM); 6038 6039 if (aMaxBlockEndEdgeOfChildren <= availableBSizeMinusBEndBP) { 6040 // Consume all the available block-size. 6041 desiredSizeInFlexWM.BSize(flexWM) = availableBSizeMinusBEndBP; 6042 } else { 6043 // This case happens if we have some tall unbreakable children exceeding 6044 // the available block-size. 6045 desiredSizeInFlexWM.BSize(flexWM) = std::min( 6046 effectiveContentBSizeWithBStartBP, aMaxBlockEndEdgeOfChildren); 6047 6048 if ((aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE || 6049 aChildrenStatus.IsFullyComplete()) && 6050 aMaxBlockEndEdgeOfChildren >= effectiveContentBSizeWithBStartBP) { 6051 // We have some tall unbreakable child that's sticking off the end of 6052 // our fragment, *and* forcing us to consume all of our remaining 6053 // content block-size and call ourselves complete. 6054 // 6055 // - If we have a definite block-size: we get here if the tall child 6056 // makes us reach that block-size. 6057 // - If we have a content-based block-size: we get here if the tall 6058 // child makes us reach the content-based block-size from a 6059 // theoretical unfragmented layout, *and* all our children are 6060 // complete. (Note that if we have some incomplete child, then we 6061 // instead prefer to return an incomplete status, so we can get a 6062 // next-in-flow to include that child's requested next-in-flow, in the 6063 // spirit of having a block-size that fits the content.) 6064 // 6065 // TODO: the auto-height case might need more subtlety; see bug 1828977. 6066 isStatusIncomplete = false; 6067 6068 // We also potentially need to get the unskipped block-end border and 6069 // padding (if we assumed it'd be skipped as part of our tentative 6070 // assumption that we'd be incomplete). 6071 if (aReflowInput.mStyleBorder->mBoxDecorationBreak == 6072 StyleBoxDecorationBreak::Slice) { 6073 blockEndContainerBP = 6074 aReflowInput.ComputedLogicalBorderPadding(flexWM).BEnd(flexWM); 6075 } 6076 } 6077 } 6078 6079 if (isStatusIncomplete) { 6080 aStatus.SetIncomplete(); 6081 } 6082 } else { 6083 // Our own effective content-box block-size can fit within the available 6084 // block-size. 6085 desiredSizeInFlexWM.BSize(flexWM) = effectiveContentBSizeWithBStartBP; 6086 } 6087 6088 // Now, we account for how the block-end border and padding (if any) impacts 6089 // our desired size. If adding it pushes us over the available block-size, 6090 // then we become incomplete (unless we already weren't asking for any 6091 // block-size, in which case we stay complete to avoid looping forever). 6092 // 6093 // NOTE: If we have auto block-size, we allow our block-end border and padding 6094 // to push us over the available block-size without requesting a continuation, 6095 // for consistency with the behavior of "display:block" elements. 6096 const nscoord effectiveContentBSizeWithBStartEndBP = 6097 desiredSizeInFlexWM.BSize(flexWM) + blockEndContainerBP; 6098 6099 if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && 6100 effectiveContentBSizeWithBStartEndBP > aReflowInput.AvailableBSize() && 6101 desiredSizeInFlexWM.BSize(flexWM) != 0 && 6102 aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) { 6103 // We couldn't fit with the block-end border and padding included, so we'll 6104 // need a continuation. 6105 aStatus.SetIncomplete(); 6106 6107 if (aReflowInput.mStyleBorder->mBoxDecorationBreak == 6108 StyleBoxDecorationBreak::Slice) { 6109 blockEndContainerBP = 0; 6110 } 6111 } 6112 6113 // The variable "blockEndContainerBP" now accurately reflects how much (if 6114 // any) block-end border and padding we want for this frame, so we can proceed 6115 // to add it in. 6116 desiredSizeInFlexWM.BSize(flexWM) += blockEndContainerBP; 6117 6118 if (aStatus.IsComplete() && !aChildrenStatus.IsFullyComplete()) { 6119 aStatus.SetOverflowIncomplete(); 6120 aStatus.SetNextInFlowNeedsReflow(); 6121 } 6122 6123 // If we are the first-in-flow and not fully complete (either our block-size 6124 // or any of our flex items cannot fit in the available block-size), and the 6125 // style requires us to avoid breaking inside, set the status to prompt our 6126 // parent to push us to the next page/column. 6127 if (!GetPrevInFlow() && !aStatus.IsFullyComplete() && 6128 ShouldAvoidBreakInside(aReflowInput)) { 6129 aStatus.SetInlineLineBreakBeforeAndReset(); 6130 return; 6131 } 6132 6133 // Propagate forced break values from flex items or flex lines. 6134 if (aChildrenStatus.IsInlineBreakBefore()) { 6135 aStatus.SetInlineLineBreakBeforeAndReset(); 6136 } 6137 if (aChildrenStatus.IsInlineBreakAfter()) { 6138 aStatus.SetInlineLineBreakAfter(); 6139 } 6140 6141 // If we haven't established a baseline for the container yet, i.e. if we 6142 // don't have any flex item in the startmost flex line that participates in 6143 // baseline alignment, then use the startmost flex item to derive the 6144 // container's baseline. 6145 if (const FlexLine& line = StartmostLine(aFlr.mLines, aAxisTracker); 6146 aFlr.mAscent == nscoord_MIN && !line.IsEmpty()) { 6147 const FlexItem& item = line.StartmostItem(aAxisTracker); 6148 aFlr.mAscent = item.Frame() 6149 ->GetLogicalPosition( 6150 flexWM, desiredSizeInFlexWM.GetPhysicalSize(flexWM)) 6151 .B(flexWM) + 6152 item.ResolvedAscent(true); 6153 } 6154 6155 // Likewise, if we don't have any flex item in the endmost flex line that 6156 // participates in last baseline alignment, then use the endmost flex item to 6157 // derived the container's last baseline. 6158 if (const FlexLine& line = EndmostLine(aFlr.mLines, aAxisTracker); 6159 aFlr.mAscentForLast == nscoord_MIN && !line.IsEmpty()) { 6160 const FlexItem& item = line.EndmostItem(aAxisTracker); 6161 const nscoord lastAscent = 6162 item.Frame() 6163 ->GetLogicalPosition(flexWM, 6164 desiredSizeInFlexWM.GetPhysicalSize(flexWM)) 6165 .B(flexWM) + 6166 item.ResolvedAscent(false); 6167 6168 aFlr.mAscentForLast = desiredSizeInFlexWM.BSize(flexWM) - lastAscent; 6169 } 6170 6171 if (aFlr.mAscent == nscoord_MIN) { 6172 // Still don't have our baseline set -- this happens if we have no 6173 // children, if our children are huge enough that they have nscoord_MIN 6174 // as their baseline, or our content is hidden in which case, we'll use the 6175 // wrong baseline (but no big deal). 6176 NS_WARNING_ASSERTION( 6177 HidesContentForLayout() || aFlr.mLines[0].IsEmpty(), 6178 "Have flex items but didn't get an ascent - that's odd (or there are " 6179 "just gigantic sizes involved)"); 6180 // Per spec, synthesize baseline from the flex container's content box 6181 // (i.e. use block-end side of content-box) 6182 // XXXdholbert This only makes sense if parent's writing mode is 6183 // horizontal (& even then, really we should be using the BSize in terms 6184 // of the parent's writing mode, not ours). Clean up in bug 1155322. 6185 aFlr.mAscent = effectiveContentBSizeWithBStartBP; 6186 } 6187 6188 if (aFlr.mAscentForLast == nscoord_MIN) { 6189 // Still don't have our last baseline set -- this happens if we have no 6190 // children, if our children are huge enough that they have nscoord_MIN 6191 // as their baseline, or our content is hidden in which case, we'll use the 6192 // wrong baseline (but no big deal). 6193 NS_WARNING_ASSERTION( 6194 HidesContentForLayout() || aFlr.mLines[0].IsEmpty(), 6195 "Have flex items but didn't get an ascent - that's odd (or there are " 6196 "just gigantic sizes involved)"); 6197 // Per spec, synthesize baseline from the flex container's content box 6198 // (i.e. use block-end side of content-box) 6199 // XXXdholbert This only makes sense if parent's writing mode is 6200 // horizontal (& even then, really we should be using the BSize in terms 6201 // of the parent's writing mode, not ours). Clean up in bug 1155322. 6202 aFlr.mAscentForLast = blockEndContainerBP; 6203 } 6204 6205 if (HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE)) { 6206 // This will force our parent to call GetLogicalBaseline, which will 6207 // synthesize a margin-box baseline. 6208 aReflowOutput.SetBlockStartAscent(ReflowOutput::ASK_FOR_BASELINE); 6209 } else { 6210 // XXXdholbert aFlr.mAscent needs to be in terms of our parent's 6211 // writing-mode here. See bug 1155322. 6212 aReflowOutput.SetBlockStartAscent(aFlr.mAscent); 6213 } 6214 6215 // Cache the container baselines so that our parent can baseline-align us. 6216 mFirstBaseline = aFlr.mAscent; 6217 mLastBaseline = aFlr.mAscentForLast; 6218 6219 // Convert flex container's final desired size to parent's WM, for outparam. 6220 aReflowOutput.SetSize(flexWM, desiredSizeInFlexWM); 6221 } 6222 6223 void nsFlexContainerFrame::MoveFlexItemToFinalPosition( 6224 const FlexItem& aItem, const LogicalPoint& aFramePos, 6225 const nsSize& aContainerSize) { 6226 const WritingMode outerWM = aItem.ContainingBlockWM(); 6227 const nsStyleDisplay* display = aItem.Frame()->StyleDisplay(); 6228 LogicalPoint pos(aFramePos); 6229 if (display->IsRelativelyOrStickyPositionedStyle()) { 6230 // If the item is relatively positioned, look up its offsets (cached from 6231 // previous reflow). A sticky positioned item can pass a dummy 6232 // logicalOffsets into ApplyRelativePositioning(). 6233 LogicalMargin logicalOffsets(outerWM); 6234 if (display->IsRelativelyPositionedStyle()) { 6235 nsMargin* cachedOffsets = 6236 aItem.Frame()->GetProperty(nsIFrame::ComputedOffsetProperty()); 6237 MOZ_ASSERT( 6238 cachedOffsets, 6239 "relpos previously-reflowed frame should've cached its offsets"); 6240 logicalOffsets = LogicalMargin(outerWM, *cachedOffsets); 6241 } 6242 ReflowInput::ApplyRelativePositioning(aItem.Frame(), outerWM, 6243 logicalOffsets, &pos, aContainerSize); 6244 } 6245 6246 FLEX_ITEM_LOG(aItem.Frame(), "Moving item to its desired position %s", 6247 ToString(pos).c_str()); 6248 aItem.Frame()->SetPosition(outerWM, pos, aContainerSize); 6249 } 6250 6251 nsReflowStatus nsFlexContainerFrame::ReflowFlexItem( 6252 const FlexboxAxisTracker& aAxisTracker, const ReflowInput& aReflowInput, 6253 const FlexItem& aItem, const LogicalPoint& aFramePos, 6254 const bool aIsAdjacentWithBStart, const LogicalSize& aAvailableSize, 6255 const nsSize& aContainerSize) { 6256 FLEX_ITEM_LOG(aItem.Frame(), "Doing final reflow"); 6257 6258 // Returns true if we should use 'auto' in block axis's StyleSizeOverrides to 6259 // allow fragmentation-imposed block-size growth. 6260 auto ComputeBSizeOverrideWithAuto = [&]() { 6261 if (!aReflowInput.IsInFragmentedContext()) { 6262 return false; 6263 } 6264 if (aItem.Frame()->IsReplaced()) { 6265 // Disallow fragmentation-imposed block-size growth for replaced elements 6266 // since they are monolithic, and cannot be fragmented. 6267 return false; 6268 } 6269 if (aItem.HasAspectRatio()) { 6270 // Aspect-ratio's automatic content-based minimum size doesn't work 6271 // properly in a fragmented context (Bug 1868284) when we use 'auto' 6272 // block-size to apply the fragmentation-imposed block-size growth. 6273 // Disable it for now so that items with aspect-ratios can still use their 6274 // known block-sizes (from flex layout algorithm) in final reflow. 6275 return false; 6276 } 6277 if (aItem.IsBlockAxisMainAxis()) { 6278 if (aItem.IsFlexBaseSizeContentBSize()) { 6279 // The flex item resolved its indefinite flex-basis to the content 6280 // block-size. 6281 if (aItem.IsMainMinSizeContentBSize()) { 6282 // The item's flex base size and main min-size are both content 6283 // block-size. We interpret this content-based block-size as 6284 // permission to apply fragmentation-imposed block-size growth. 6285 return true; 6286 } 6287 if (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE) { 6288 // The flex container has an indefinite block-size. We allow the 6289 // item's to apply fragmentation-imposed block-size growth. 6290 return true; 6291 } 6292 } 6293 return false; 6294 } 6295 6296 MOZ_ASSERT(aItem.IsBlockAxisCrossAxis()); 6297 MOZ_ASSERT(aItem.IsStretched(), 6298 "No need to override block-size with 'auto' if the item is not " 6299 "stretched in the cross axis!"); 6300 6301 Maybe<nscoord> measuredBSize = aItem.MeasuredBSize(); 6302 if (measuredBSize && aItem.CrossSize() == *measuredBSize) { 6303 // The item has a measured content-based block-size due to having an 6304 // indefinite cross-size. If its cross-size is equal to the content-based 6305 // block-size, then it is the tallest item that established the cross-size 6306 // of the flex line. We allow it apply fragmentation-imposed block-size 6307 // growth. 6308 // 6309 // Note: We only allow the tallest item to grow because it is likely to 6310 // have the most impact on the overall flex container block-size growth. 6311 // This is not a perfect solution since other shorter items in the same 6312 // line might also have fragmentation-imposed block-size growth, but 6313 // currently there is no reliable way to detect whether they will outgrow 6314 // the tallest item. 6315 return true; 6316 } 6317 return false; 6318 }; 6319 6320 StyleSizeOverrides sizeOverrides; 6321 bool overrideBSizeWithAuto = false; 6322 6323 // Override flex item's main size. 6324 if (aItem.IsInlineAxisMainAxis()) { 6325 sizeOverrides.mStyleISize.emplace(aItem.StyleMainSize()); 6326 FLEX_LOGV("Main size (inline-size) override: %d", aItem.MainSize()); 6327 } else { 6328 overrideBSizeWithAuto = ComputeBSizeOverrideWithAuto(); 6329 if (overrideBSizeWithAuto) { 6330 sizeOverrides.mStyleBSize.emplace(StyleSize::Auto()); 6331 FLEX_LOGV("Main size (block-size) override: Auto"); 6332 } else { 6333 sizeOverrides.mStyleBSize.emplace(aItem.StyleMainSize()); 6334 FLEX_LOGV("Main size (block-size) override: %d", aItem.MainSize()); 6335 } 6336 } 6337 6338 // Override flex item's cross size if it was stretched in the cross axis (in 6339 // which case we're imposing a cross size). 6340 if (aItem.IsStretched()) { 6341 if (aItem.IsInlineAxisCrossAxis()) { 6342 sizeOverrides.mStyleISize.emplace(aItem.StyleCrossSize()); 6343 FLEX_LOGV("Cross size (inline-size) override: %d", aItem.CrossSize()); 6344 } else { 6345 overrideBSizeWithAuto = ComputeBSizeOverrideWithAuto(); 6346 if (overrideBSizeWithAuto) { 6347 sizeOverrides.mStyleBSize.emplace(StyleSize::Auto()); 6348 FLEX_LOGV("Cross size (block-size) override: Auto"); 6349 } else { 6350 sizeOverrides.mStyleBSize.emplace(aItem.StyleCrossSize()); 6351 FLEX_LOGV("Cross size (block-size) override: %d", aItem.CrossSize()); 6352 } 6353 } 6354 } 6355 if (sizeOverrides.mStyleBSize) { 6356 // We are overriding the block-size. For robustness, we always assume that 6357 // this represents a block-axis resize for the frame. This may be 6358 // conservative, but we do capture all the conditions in the block-axis 6359 // (checked in NeedsFinalReflow()) that make this item require a final 6360 // reflow. This sets relevant flags in ReflowInput::InitResizeFlags(). 6361 aItem.Frame()->SetHasBSizeChange(true); 6362 } 6363 6364 ReflowInput childReflowInput(PresContext(), aReflowInput, aItem.Frame(), 6365 aAvailableSize, Nothing(), {}, sizeOverrides, 6366 {ComputeSizeFlag::ShrinkWrap}); 6367 if (overrideBSizeWithAuto) { 6368 // If we use 'auto' to override the item's block-size, set the item's 6369 // original block-size to min-size as a lower bound. 6370 childReflowInput.SetComputedMinBSize(aItem.BSize()); 6371 6372 // Set the item's block-size as the percentage basis so that its children 6373 // can resolve percentage sizes correctly. 6374 childReflowInput.SetPercentageBasisInBlockAxis(aItem.BSize()); 6375 } 6376 6377 if (aItem.TreatBSizeAsIndefinite() && aItem.IsBlockAxisMainAxis()) { 6378 childReflowInput.mFlags.mTreatBSizeAsIndefinite = true; 6379 } 6380 6381 if (aItem.IsStretched() && aItem.IsBlockAxisCrossAxis()) { 6382 // This item is stretched (in the cross axis), and that axis is its block 6383 // axis. That stretching effectively gives it a relative BSize. 6384 // XXXdholbert This flag only makes a difference if we use the flex items' 6385 // frame-state when deciding whether to reflow them -- and we don't, as of 6386 // the changes in bug 851607. So this has no effect right now, but it might 6387 // make a difference if we optimize to use dirty bits in the 6388 // future. (Reftests flexbox-resizeviewport-1.xhtml and -2.xhtml are 6389 // intended to catch any regressions here, if we end up relying on this bit 6390 // & neglecting to set it.) 6391 aItem.Frame()->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); 6392 } 6393 6394 if (!aIsAdjacentWithBStart) { 6395 // mIsTopOfPage bit in childReflowInput is carried over from aReflowInput. 6396 // However, if this item's position is not adjacent with the flex 6397 // container's content-box block-start edge, we should clear it. 6398 childReflowInput.mFlags.mIsTopOfPage = false; 6399 } 6400 6401 // NOTE: Be very careful about doing anything else with childReflowInput 6402 // after this point, because some of its methods (e.g. SetComputedWidth) 6403 // internally call InitResizeFlags and stomp on mVResize & mHResize. 6404 6405 FLEX_ITEM_LOG(aItem.Frame(), "Reflowing item at its desired position %s", 6406 ToString(aFramePos).c_str()); 6407 6408 // CachedFlexItemData is stored in item's writing mode, so we pass 6409 // aChildReflowInput into ReflowOutput's constructor. 6410 ReflowOutput childReflowOutput(childReflowInput); 6411 nsReflowStatus childStatus; 6412 WritingMode outerWM = aReflowInput.GetWritingMode(); 6413 ReflowChild(aItem.Frame(), PresContext(), childReflowOutput, childReflowInput, 6414 outerWM, aFramePos, aContainerSize, ReflowChildFlags::Default, 6415 childStatus); 6416 6417 // XXXdholbert Perhaps we should call CheckForInterrupt here; see bug 1495532. 6418 6419 FinishReflowChild(aItem.Frame(), PresContext(), childReflowOutput, 6420 &childReflowInput, outerWM, aFramePos, aContainerSize, 6421 ReflowChildFlags::ApplyRelativePositioning); 6422 6423 aItem.SetAscent(childReflowOutput.BlockStartAscent()); 6424 6425 // Update our cached flex item info: 6426 if (auto* cached = aItem.Frame()->GetProperty(CachedFlexItemData::Prop())) { 6427 cached->Update(childReflowInput, childReflowOutput, 6428 FlexItemReflowType::Final); 6429 } else { 6430 cached = new CachedFlexItemData(childReflowInput, childReflowOutput, 6431 FlexItemReflowType::Final); 6432 aItem.Frame()->SetProperty(CachedFlexItemData::Prop(), cached); 6433 } 6434 6435 return childStatus; 6436 } 6437 6438 void nsFlexContainerFrame::ReflowPlaceholders( 6439 const ReflowInput& aReflowInput, nsTArray<nsIFrame*>& aPlaceholders, 6440 const LogicalPoint& aContentBoxOrigin, const nsSize& aContainerSize) { 6441 WritingMode outerWM = aReflowInput.GetWritingMode(); 6442 6443 // As noted in this method's documentation, we'll reflow every entry in 6444 // |aPlaceholders| at the container's content-box origin. 6445 for (nsIFrame* placeholder : aPlaceholders) { 6446 MOZ_ASSERT(placeholder->IsPlaceholderFrame(), 6447 "placeholders array should only contain placeholder frames"); 6448 WritingMode wm = placeholder->GetWritingMode(); 6449 LogicalSize availSize = aReflowInput.ComputedSize(wm); 6450 ReflowInput childReflowInput(PresContext(), aReflowInput, placeholder, 6451 availSize); 6452 // No need to set the -webkit-line-clamp related flags when reflowing 6453 // a placeholder. 6454 ReflowOutput childReflowOutput(outerWM); 6455 nsReflowStatus childStatus; 6456 ReflowChild(placeholder, PresContext(), childReflowOutput, childReflowInput, 6457 outerWM, aContentBoxOrigin, aContainerSize, 6458 ReflowChildFlags::Default, childStatus); 6459 6460 FinishReflowChild(placeholder, PresContext(), childReflowOutput, 6461 &childReflowInput, outerWM, aContentBoxOrigin, 6462 aContainerSize, ReflowChildFlags::Default); 6463 6464 // Mark the placeholder frame to indicate that it's not actually at the 6465 // element's static position, because we need to apply CSS Alignment after 6466 // we determine the OOF's size: 6467 placeholder->AddStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN); 6468 } 6469 } 6470 6471 nscoord nsFlexContainerFrame::ComputeIntrinsicISize( 6472 const IntrinsicSizeInput& aInput, IntrinsicISizeType aType) { 6473 FLEX_LOG("Compute %s isize for flex container frame %p", 6474 aType == IntrinsicISizeType::MinISize ? "min" : "pref", this); 6475 6476 if (Maybe<nscoord> containISize = ContainIntrinsicISize()) { 6477 return *containISize; 6478 } 6479 6480 nscoord containerISize = 0; 6481 const nsStylePosition* stylePos = StylePosition(); 6482 const FlexboxAxisTracker axisTracker(this); 6483 6484 nscoord mainGapSize; 6485 if (axisTracker.IsRowOriented()) { 6486 mainGapSize = nsLayoutUtils::ResolveGapToLength(stylePos->mColumnGap, 6487 NS_UNCONSTRAINEDSIZE); 6488 } else { 6489 mainGapSize = nsLayoutUtils::ResolveGapToLength(stylePos->mRowGap, 6490 NS_UNCONSTRAINEDSIZE); 6491 } 6492 6493 const bool useMozBoxCollapseBehavior = 6494 StyleVisibility()->UseLegacyCollapseBehavior(); 6495 const bool isSingleLine = IsSingleLine(this, stylePos); 6496 const auto flexWM = GetWritingMode(); 6497 6498 // The loop below sets aside space for a gap before each item besides the 6499 // first. This bool helps us handle that special-case. 6500 bool onFirstChild = true; 6501 6502 for (nsIFrame* childFrame : mFrames) { 6503 // Skip out-of-flow children because they don't participate in flex layout. 6504 if (childFrame->IsPlaceholderFrame()) { 6505 continue; 6506 } 6507 6508 if (useMozBoxCollapseBehavior && 6509 childFrame->StyleVisibility()->IsCollapse()) { 6510 // If we're using legacy "visibility:collapse" behavior, then we don't 6511 // care about the sizes of any collapsed children. 6512 continue; 6513 } 6514 6515 const auto childWM = childFrame->GetWritingMode(); 6516 const IntrinsicSizeInput childInput(aInput, childWM, flexWM); 6517 const auto* styleFrame = nsLayoutUtils::GetStyleFrame(childFrame); 6518 const auto childAnchorResolutionParams = 6519 AnchorPosResolutionParams::From(styleFrame); 6520 const auto* childStylePos = styleFrame->StylePosition(); 6521 6522 // A flex item with a definite block size can transfer its block size to the 6523 // inline-axis via its own aspect-ratio or serve as a percentage basis for 6524 // its children with aspect-ratios. Both can influence the item's intrinsic 6525 // inline size contribution to the flex container's intrinsic inline size. 6526 // 6527 // This helper function determines whether we should "pre-stretch" a flex 6528 // item's cross size (with that size considered to be definite) based on the 6529 // flex container's definite cross size. 6530 // 6531 // Note: The logic here is similar to the "pre-stretch" in 6532 // GenerateFlexItemForChild(). 6533 const bool childShouldStretchCrossSize = [&]() { 6534 if (!isSingleLine || axisTracker.IsColumnOriented()) { 6535 // We only perform "pre-stretch" for the item's cross size if the flex 6536 // container is single-line and row-oriented. 6537 return false; 6538 } 6539 if (!aInput.mPercentageBasisForChildren || 6540 aInput.mPercentageBasisForChildren->BSize(flexWM) == 6541 NS_UNCONSTRAINEDSIZE) { 6542 // The flex container does not have a definite cross size to stretch the 6543 // items. 6544 // 6545 // Note: if the flex container has a definite cross size (for items to 6546 // pre-stretch to fill), it should be passed down in 6547 // mPercentageBasisForChildren -- specifically in the BSize component, 6548 // given that we know the flex container is row-oriented at this point. 6549 return false; 6550 } 6551 [[maybe_unused]] auto [alignSelf, flags] = 6552 UsedAlignSelfAndFlagsForItem(childFrame); 6553 if (alignSelf != StyleAlignFlags::STRETCH || 6554 !childStylePos->BSize(flexWM, childAnchorResolutionParams) 6555 ->IsAuto() || 6556 childFrame->StyleMargin()->HasBlockAxisAuto( 6557 flexWM, childAnchorResolutionParams)) { 6558 // Similar to FlexItem::ResolveStretchedCrossSize(), we only stretch 6559 // the item if it satisfies all the following conditions: 6560 // - used align-self value is 'stretch' (CSSAlignmentForFlexItem() has 6561 // converted 'normal' to 'stretch') 6562 // - a cross-axis size property of value "auto" 6563 // - no auto margins in the cross-axis 6564 // https://drafts.csswg.org/css-flexbox-1/#valdef-align-items-stretch 6565 return false; 6566 } 6567 // Let's stretch the item's cross size. 6568 return true; 6569 }(); 6570 6571 StyleSizeOverrides sizeOverrides; 6572 if (childShouldStretchCrossSize) { 6573 const auto offsetData = childFrame->IntrinsicBSizeOffsets(); 6574 const nscoord boxSizingToMarginEdgeSize = 6575 childStylePos->mBoxSizing == StyleBoxSizing::Content 6576 ? offsetData.MarginBorderPadding() 6577 : offsetData.margin; 6578 const nscoord stretchedCrossSize = 6579 std::max(0, aInput.mPercentageBasisForChildren->BSize(flexWM) - 6580 boxSizingToMarginEdgeSize); 6581 const auto stretchedStyleCrossSize = StyleSize::LengthPercentage( 6582 LengthPercentage::FromAppUnits(stretchedCrossSize)); 6583 // The size override is in the child's own writing mode. 6584 if (flexWM.IsOrthogonalTo(childWM)) { 6585 sizeOverrides.mStyleISize.emplace(stretchedStyleCrossSize); 6586 } else { 6587 sizeOverrides.mStyleBSize.emplace(stretchedStyleCrossSize); 6588 } 6589 } 6590 nscoord childISize = nsLayoutUtils::IntrinsicForContainer( 6591 childInput.mContext, childFrame, aType, 6592 childInput.mPercentageBasisForChildren, 0, sizeOverrides); 6593 6594 // * For a row-oriented single-line flex container, the intrinsic 6595 // {min/pref}-isize is the sum of its items' {min/pref}-isizes and 6596 // (n-1) column gaps. 6597 // * For a column-oriented flex container, the intrinsic min isize 6598 // is the max of its items' min isizes. 6599 // * For a row-oriented multi-line flex container, the intrinsic 6600 // pref isize is former (sum), and its min isize is the latter (max). 6601 if (axisTracker.IsRowOriented() && 6602 (isSingleLine || aType == IntrinsicISizeType::PrefISize)) { 6603 containerISize += childISize; 6604 if (!onFirstChild) { 6605 containerISize += mainGapSize; 6606 } 6607 onFirstChild = false; 6608 } else { // (col-oriented, or MinISize for multi-line row flex container) 6609 containerISize = std::max(containerISize, childISize); 6610 } 6611 } 6612 6613 return containerISize; 6614 } 6615 6616 nscoord nsFlexContainerFrame::IntrinsicISize(const IntrinsicSizeInput& aInput, 6617 IntrinsicISizeType aType) { 6618 return mCachedIntrinsicSizes.GetOrSet(*this, aType, aInput, [&] { 6619 return ComputeIntrinsicISize(aInput, aType); 6620 }); 6621 } 6622 6623 int32_t nsFlexContainerFrame::GetNumLines() const { 6624 // TODO(emilio, bug 1793251): Treating all row oriented frames as single-lines 6625 // might not be great for flex-wrap'd containers, consider trying to do 6626 // better? We probably would need to persist more stuff than we do after 6627 // layout. 6628 return FlexboxAxisInfo(this).mIsRowOriented ? 1 : mFrames.GetLength(); 6629 } 6630 6631 bool nsFlexContainerFrame::IsLineIteratorFlowRTL() { 6632 FlexboxAxisInfo info(this); 6633 if (info.mIsRowOriented) { 6634 const bool isRtl = StyleVisibility()->mDirection == StyleDirection::Rtl; 6635 return info.mIsMainAxisReversed != isRtl; 6636 } 6637 return false; 6638 } 6639 6640 Result<nsILineIterator::LineInfo, nsresult> nsFlexContainerFrame::GetLine( 6641 int32_t aLineNumber) { 6642 if (aLineNumber < 0 || aLineNumber >= GetNumLines()) { 6643 return Err(NS_ERROR_FAILURE); 6644 } 6645 FlexboxAxisInfo info(this); 6646 LineInfo lineInfo; 6647 if (info.mIsRowOriented) { 6648 lineInfo.mLineBounds = GetRect(); 6649 lineInfo.mFirstFrameOnLine = mFrames.FirstChild(); 6650 // This isn't quite ideal for multi-line row flexbox, see bug 1793251. 6651 lineInfo.mNumFramesOnLine = mFrames.GetLength(); 6652 } else { 6653 // TODO(emilio, bug 1793322): Deal with column-reverse (mIsMainAxisReversed) 6654 nsIFrame* f = mFrames.FrameAt(aLineNumber); 6655 lineInfo.mLineBounds = f->GetRect(); 6656 lineInfo.mFirstFrameOnLine = f; 6657 lineInfo.mNumFramesOnLine = 1; 6658 } 6659 return lineInfo; 6660 } 6661 6662 int32_t nsFlexContainerFrame::FindLineContaining(const nsIFrame* aFrame, 6663 int32_t aStartLine) { 6664 const int32_t index = mFrames.IndexOf(aFrame); 6665 if (index < 0) { 6666 return -1; 6667 } 6668 const FlexboxAxisInfo info(this); 6669 if (info.mIsRowOriented) { 6670 return 0; 6671 } 6672 if (index < aStartLine) { 6673 return -1; 6674 } 6675 return index; 6676 } 6677 6678 NS_IMETHODIMP 6679 nsFlexContainerFrame::CheckLineOrder(int32_t aLine, bool* aIsReordered, 6680 nsIFrame** aFirstVisual, 6681 nsIFrame** aLastVisual) { 6682 *aIsReordered = false; 6683 *aFirstVisual = nullptr; 6684 *aLastVisual = nullptr; 6685 return NS_OK; 6686 } 6687 6688 NS_IMETHODIMP 6689 nsFlexContainerFrame::FindFrameAt(int32_t aLineNumber, nsPoint aPos, 6690 nsIFrame** aFrameFound, 6691 bool* aPosIsBeforeFirstFrame, 6692 bool* aPosIsAfterLastFrame) { 6693 const auto wm = GetWritingMode(); 6694 const LogicalPoint pos(wm, aPos, GetSize()); 6695 const FlexboxAxisInfo info(this); 6696 6697 *aFrameFound = nullptr; 6698 *aPosIsBeforeFirstFrame = true; 6699 *aPosIsAfterLastFrame = false; 6700 6701 if (!info.mIsRowOriented) { 6702 nsIFrame* f = mFrames.FrameAt(aLineNumber); 6703 if (!f) { 6704 return NS_OK; 6705 } 6706 6707 auto rect = f->GetLogicalRect(wm, GetSize()); 6708 *aFrameFound = f; 6709 *aPosIsBeforeFirstFrame = pos.I(wm) < rect.IStart(wm); 6710 *aPosIsAfterLastFrame = pos.I(wm) > rect.IEnd(wm); 6711 return NS_OK; 6712 } 6713 6714 LineFrameFinder finder(aPos, GetSize(), GetWritingMode(), 6715 IsLineIteratorFlowRTL()); 6716 for (nsIFrame* f : mFrames) { 6717 finder.Scan(f); 6718 if (finder.IsDone()) { 6719 break; 6720 } 6721 } 6722 finder.Finish(aFrameFound, aPosIsBeforeFirstFrame, aPosIsAfterLastFrame); 6723 return NS_OK; 6724 }