Backed out changeset 496886cb30a5 (bug 1867152) for bc failures on browser_user_input...
[gecko.git] / layout / generic / nsFlexContainerFrame.cpp
blob0512af7b40afa8f2fe42573bed9c1cbb4647bb71
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* rendering object for CSS "display: flex" */
9 #include "nsFlexContainerFrame.h"
11 #include <algorithm>
13 #include "gfxContext.h"
14 #include "mozilla/Baseline.h"
15 #include "mozilla/ComputedStyle.h"
16 #include "mozilla/CSSOrderAwareFrameIterator.h"
17 #include "mozilla/FloatingPoint.h"
18 #include "mozilla/Logging.h"
19 #include "mozilla/PresShell.h"
20 #include "mozilla/StaticPrefs_layout.h"
21 #include "mozilla/WritingModes.h"
22 #include "nsBlockFrame.h"
23 #include "nsContentUtils.h"
24 #include "nsCSSAnonBoxes.h"
25 #include "nsDebug.h"
26 #include "nsDisplayList.h"
27 #include "nsFieldSetFrame.h"
28 #include "nsIFrameInlines.h"
29 #include "nsLayoutUtils.h"
30 #include "nsPlaceholderFrame.h"
31 #include "nsPresContext.h"
33 using namespace mozilla;
34 using namespace mozilla::layout;
36 // Convenience typedefs for helper classes that we forward-declare in .h file
37 // (so that nsFlexContainerFrame methods can use them as parameters):
38 using FlexItem = nsFlexContainerFrame::FlexItem;
39 using FlexLine = nsFlexContainerFrame::FlexLine;
40 using FlexboxAxisTracker = nsFlexContainerFrame::FlexboxAxisTracker;
41 using StrutInfo = nsFlexContainerFrame::StrutInfo;
42 using CachedBAxisMeasurement = nsFlexContainerFrame::CachedBAxisMeasurement;
43 using CachedFlexItemData = nsFlexContainerFrame::CachedFlexItemData;
45 static mozilla::LazyLogModule gFlexContainerLog("FlexContainer");
46 #define FLEX_LOG(...) \
47 MOZ_LOG(gFlexContainerLog, LogLevel::Debug, (__VA_ARGS__));
48 #define FLEX_LOGV(...) \
49 MOZ_LOG(gFlexContainerLog, LogLevel::Verbose, (__VA_ARGS__));
51 // Returns true if aFlexContainer is a frame for some element that has
52 // display:-webkit-{inline-}box (or -moz-{inline-}box). aFlexContainer is
53 // expected to be an instance of nsFlexContainerFrame (enforced with an assert);
54 // otherwise, this function's state-bit-check here is bogus.
55 static bool IsLegacyBox(const nsIFrame* aFlexContainer) {
56 MOZ_ASSERT(aFlexContainer->IsFlexContainerFrame(),
57 "only flex containers may be passed to this function");
58 return aFlexContainer->HasAnyStateBits(
59 NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX);
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;
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 IsLegacyBox(aFlexContainer)
78 ? CSSOrderAwareFrameIterator::OrderingProperty::BoxOrdinalGroup
79 : CSSOrderAwareFrameIterator::OrderingProperty::Order;
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;
100 MOZ_ASSERT_UNREACHABLE("Unrecognized mBoxAlign enum value");
101 // Fall back to default value of "align-items" property:
102 return StyleAlignFlags::STRETCH;
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};
121 MOZ_ASSERT_UNREACHABLE("Unrecognized mBoxPack enum value");
122 // Fall back to default value of "justify-content" property:
123 return {StyleAlignFlags::FLEX_START};
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.
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());
137 // Encapsulates our flex container's main & cross axes. This class is backed by
138 // a FlexboxAxisInfo helper member variable, and it adds some convenience APIs
139 // on top of what that struct offers.
140 class MOZ_STACK_CLASS nsFlexContainerFrame::FlexboxAxisTracker {
141 public:
142 explicit FlexboxAxisTracker(const nsFlexContainerFrame* aFlexContainer);
144 // Accessors:
145 LogicalAxis MainAxis() const {
146 return IsRowOriented() ? eLogicalAxisInline : eLogicalAxisBlock;
148 LogicalAxis CrossAxis() const {
149 return IsRowOriented() ? eLogicalAxisBlock : eLogicalAxisInline;
152 LogicalSide MainAxisStartSide() const;
153 LogicalSide MainAxisEndSide() const {
154 return GetOppositeSide(MainAxisStartSide());
157 LogicalSide CrossAxisStartSide() const;
158 LogicalSide CrossAxisEndSide() const {
159 return GetOppositeSide(CrossAxisStartSide());
162 mozilla::Side MainAxisPhysicalStartSide() const {
163 return mWM.PhysicalSide(MainAxisStartSide());
165 mozilla::Side MainAxisPhysicalEndSide() const {
166 return mWM.PhysicalSide(MainAxisEndSide());
169 mozilla::Side CrossAxisPhysicalStartSide() const {
170 return mWM.PhysicalSide(CrossAxisStartSide());
172 mozilla::Side CrossAxisPhysicalEndSide() const {
173 return mWM.PhysicalSide(CrossAxisEndSide());
176 // Returns the flex container's writing mode.
177 WritingMode GetWritingMode() const { return mWM; }
179 // Returns true if our main axis is in the reverse direction of our
180 // writing mode's corresponding axis. (From 'flex-direction: *-reverse')
181 bool IsMainAxisReversed() const { return mAxisInfo.mIsMainAxisReversed; }
182 // Returns true if our cross axis is in the reverse direction of our
183 // writing mode's corresponding axis. (From 'flex-wrap: *-reverse')
184 bool IsCrossAxisReversed() const { return mAxisInfo.mIsCrossAxisReversed; }
186 bool IsRowOriented() const { return mAxisInfo.mIsRowOriented; }
187 bool IsColumnOriented() const { return !IsRowOriented(); }
189 // aSize is expected to match the flex container's WritingMode.
190 nscoord MainComponent(const LogicalSize& aSize) const {
191 return IsRowOriented() ? aSize.ISize(mWM) : aSize.BSize(mWM);
193 int32_t MainComponent(const LayoutDeviceIntSize& aIntSize) const {
194 return IsMainAxisHorizontal() ? aIntSize.width : aIntSize.height;
197 // aSize is expected to match the flex container's WritingMode.
198 nscoord CrossComponent(const LogicalSize& aSize) const {
199 return IsRowOriented() ? aSize.BSize(mWM) : aSize.ISize(mWM);
201 int32_t CrossComponent(const LayoutDeviceIntSize& aIntSize) const {
202 return IsMainAxisHorizontal() ? aIntSize.height : aIntSize.width;
205 // NOTE: aMargin is expected to use the flex container's WritingMode.
206 nscoord MarginSizeInMainAxis(const LogicalMargin& aMargin) const {
207 // If we're row-oriented, our main axis is the inline axis.
208 return IsRowOriented() ? aMargin.IStartEnd(mWM) : aMargin.BStartEnd(mWM);
210 nscoord MarginSizeInCrossAxis(const LogicalMargin& aMargin) const {
211 // If we're row-oriented, our cross axis is the block axis.
212 return IsRowOriented() ? aMargin.BStartEnd(mWM) : aMargin.IStartEnd(mWM);
216 * Converts a "flex-relative" point (a main-axis & cross-axis coordinate)
217 * into a LogicalPoint, using the flex container's writing mode.
219 * @arg aMainCoord The main-axis coordinate -- i.e an offset from the
220 * main-start edge of the flex container's content box.
221 * @arg aCrossCoord The cross-axis coordinate -- i.e an offset from the
222 * cross-start edge of the flex container's content box.
223 * @arg aContainerMainSize The main size of flex container's content box.
224 * @arg aContainerCrossSize The cross size of flex container's content box.
225 * @return A LogicalPoint, with the flex container's writing mode, that
226 * represents the same position. The logical coordinates are
227 * relative to the flex container's content box.
229 LogicalPoint LogicalPointFromFlexRelativePoint(
230 nscoord aMainCoord, nscoord aCrossCoord, nscoord aContainerMainSize,
231 nscoord aContainerCrossSize) const {
232 nscoord logicalCoordInMainAxis =
233 IsMainAxisReversed() ? aContainerMainSize - aMainCoord : aMainCoord;
234 nscoord logicalCoordInCrossAxis =
235 IsCrossAxisReversed() ? aContainerCrossSize - aCrossCoord : aCrossCoord;
237 return IsRowOriented() ? LogicalPoint(mWM, logicalCoordInMainAxis,
238 logicalCoordInCrossAxis)
239 : LogicalPoint(mWM, logicalCoordInCrossAxis,
240 logicalCoordInMainAxis);
244 * Converts a "flex-relative" size (a main-axis & cross-axis size)
245 * into a LogicalSize, using the flex container's writing mode.
247 * @arg aMainSize The main-axis size.
248 * @arg aCrossSize The cross-axis size.
249 * @return A LogicalSize, with the flex container's writing mode, that
250 * represents the same size.
252 LogicalSize LogicalSizeFromFlexRelativeSizes(nscoord aMainSize,
253 nscoord aCrossSize) const {
254 return IsRowOriented() ? LogicalSize(mWM, aMainSize, aCrossSize)
255 : LogicalSize(mWM, aCrossSize, aMainSize);
259 * Converts a "flex-relative" ascent (the distance from the flex container's
260 * content-box cross-start edge to its baseline) into a logical ascent (the
261 * distance from the flex container's content-box block-start edge to its
262 * baseline).
264 nscoord LogicalAscentFromFlexRelativeAscent(
265 nscoord aFlexRelativeAscent, nscoord aContentBoxCrossSize) const {
266 return (IsCrossAxisReversed() ? aContentBoxCrossSize - aFlexRelativeAscent
267 : aFlexRelativeAscent);
270 bool IsMainAxisHorizontal() const {
271 // If we're row-oriented, and our writing mode is NOT vertical,
272 // or we're column-oriented and our writing mode IS vertical,
273 // then our main axis is horizontal. This handles all cases:
274 return IsRowOriented() != mWM.IsVertical();
277 // Returns true if this flex item's inline axis in aItemWM is parallel (or
278 // antiparallel) to the container's main axis. Returns false, otherwise.
280 // Note: this is a helper used before constructing FlexItem. Inside of flex
281 // reflow code, FlexItem::IsInlineAxisMainAxis() is equivalent & more optimal.
282 bool IsInlineAxisMainAxis(WritingMode aItemWM) const {
283 return IsRowOriented() != GetWritingMode().IsOrthogonalTo(aItemWM);
286 // Maps justify-*: 'left' or 'right' to 'start' or 'end'.
287 StyleAlignFlags ResolveJustifyLeftRight(const StyleAlignFlags& aFlags) const {
288 MOZ_ASSERT(
289 aFlags == StyleAlignFlags::LEFT || aFlags == StyleAlignFlags::RIGHT,
290 "This helper accepts only 'LEFT' or 'RIGHT' flags!");
292 const auto wm = GetWritingMode();
293 const bool isJustifyLeft = aFlags == StyleAlignFlags::LEFT;
294 if (IsColumnOriented()) {
295 if (!wm.IsVertical()) {
296 // Container's alignment axis (main axis) is *not* parallel to the
297 // line-left <-> line-right axis or the physical left <-> physical right
298 // axis, so we map both 'left' and 'right' to 'start'.
299 return StyleAlignFlags::START;
302 MOZ_ASSERT(wm.PhysicalAxis(MainAxis()) == eAxisHorizontal,
303 "Vertical column-oriented flex container's main axis should "
304 "be parallel to physical left <-> right axis!");
305 // Map 'left' or 'right' to 'start' or 'end', depending on its block flow
306 // direction.
307 return isJustifyLeft == wm.IsVerticalLR() ? StyleAlignFlags::START
308 : StyleAlignFlags::END;
311 MOZ_ASSERT(MainAxis() == eLogicalAxisInline,
312 "Row-oriented flex container's main axis should be parallel to "
313 "line-left <-> line-right axis!");
315 // If we get here, we're operating on the flex container's inline axis,
316 // so we map 'left' to whichever of 'start' or 'end' corresponds to the
317 // *line-relative* left side; and similar for 'right'.
318 return isJustifyLeft == wm.IsBidiLTR() ? StyleAlignFlags::START
319 : StyleAlignFlags::END;
322 // Delete copy-constructor & reassignment operator, to prevent accidental
323 // (unnecessary) copying.
324 FlexboxAxisTracker(const FlexboxAxisTracker&) = delete;
325 FlexboxAxisTracker& operator=(const FlexboxAxisTracker&) = delete;
327 private:
328 const WritingMode mWM; // The flex container's writing mode.
329 const FlexboxAxisInfo mAxisInfo;
333 * Represents a flex item.
334 * Includes the various pieces of input that the Flexbox Layout Algorithm uses
335 * to resolve a flexible width.
337 class nsFlexContainerFrame::FlexItem final {
338 public:
339 // Normal constructor:
340 FlexItem(ReflowInput& aFlexItemReflowInput, float aFlexGrow,
341 float aFlexShrink, nscoord aFlexBaseSize, nscoord aMainMinSize,
342 nscoord aMainMaxSize, nscoord aTentativeCrossSize,
343 nscoord aCrossMinSize, nscoord aCrossMaxSize,
344 const FlexboxAxisTracker& aAxisTracker);
346 // Simplified constructor, to be used only for generating "struts":
347 // (NOTE: This "strut" constructor uses the *container's* writing mode, which
348 // we'll use on this FlexItem instead of the child frame's real writing mode.
349 // This is fine - it doesn't matter what writing mode we use for a
350 // strut, since it won't render any content and we already know its size.)
351 FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize, WritingMode aContainerWM,
352 const FlexboxAxisTracker& aAxisTracker);
354 // Clone existing FlexItem for its underlying frame's continuation.
355 // @param aContinuation a continuation in our next-in-flow chain.
356 FlexItem CloneFor(nsIFrame* const aContinuation) const {
357 MOZ_ASSERT(Frame() == aContinuation->FirstInFlow(),
358 "aContinuation should be in aItem's continuation chain!");
359 FlexItem item(*this);
360 item.mFrame = aContinuation;
361 item.mHadMeasuringReflow = false;
362 return item;
365 // Accessors
366 nsIFrame* Frame() const { return mFrame; }
367 nscoord FlexBaseSize() const { return mFlexBaseSize; }
369 nscoord MainMinSize() const {
370 MOZ_ASSERT(!mNeedsMinSizeAutoResolution,
371 "Someone's using an unresolved 'auto' main min-size");
372 return mMainMinSize;
374 nscoord MainMaxSize() const { return mMainMaxSize; }
376 // Note: These return the main-axis position and size of our *content box*.
377 nscoord MainSize() const { return mMainSize; }
378 nscoord MainPosition() const { return mMainPosn; }
380 nscoord CrossMinSize() const { return mCrossMinSize; }
381 nscoord CrossMaxSize() const { return mCrossMaxSize; }
383 // Note: These return the cross-axis position and size of our *content box*.
384 nscoord CrossSize() const { return mCrossSize; }
385 nscoord CrossPosition() const { return mCrossPosn; }
387 // Lazy getter for mAscent or mAscentForLast.
388 nscoord ResolvedAscent(bool aUseFirstBaseline) const {
389 // XXX We should be using the *container's* writing-mode (mCBWM) here,
390 // instead of the item's (mWM). This is essentially bug 1155322.
391 nscoord& ascent = aUseFirstBaseline ? mAscent : mAscentForLast;
392 if (ascent != ReflowOutput::ASK_FOR_BASELINE) {
393 return ascent;
396 // Use GetFirstLineBaseline() or GetLastLineBaseline() as appropriate:
397 bool found = aUseFirstBaseline
398 ? nsLayoutUtils::GetFirstLineBaseline(mWM, mFrame, &ascent)
399 : nsLayoutUtils::GetLastLineBaseline(mWM, mFrame, &ascent);
400 if (found) {
401 return ascent;
404 // If the nsLayoutUtils getter fails, then ask the frame directly:
405 auto baselineGroup = aUseFirstBaseline ? BaselineSharingGroup::First
406 : BaselineSharingGroup::Last;
407 if (auto baseline = mFrame->GetNaturalBaselineBOffset(
408 mWM, baselineGroup, BaselineExportContext::Other)) {
409 // Offset for last baseline from `GetNaturalBaselineBOffset` originates
410 // from the frame's block end, so convert it back.
411 ascent = baselineGroup == BaselineSharingGroup::First
412 ? *baseline
413 : mFrame->BSize(mWM) - *baseline;
414 return ascent;
417 // We couldn't determine a baseline, so we synthesize one from border box:
418 ascent = Baseline::SynthesizeBOffsetFromBorderBox(
419 mFrame, mWM, BaselineSharingGroup::First);
420 return ascent;
423 // Convenience methods to compute the main & cross size of our *margin-box*.
424 nscoord OuterMainSize() const {
425 return mMainSize + MarginBorderPaddingSizeInMainAxis();
428 nscoord OuterCrossSize() const {
429 return mCrossSize + MarginBorderPaddingSizeInCrossAxis();
432 // Convenience method to return the content-box block-size.
433 nscoord BSize() const {
434 return IsBlockAxisMainAxis() ? MainSize() : CrossSize();
437 // Convenience method to return the measured content-box block-size computed
438 // in nsFlexContainerFrame::MeasureBSizeForFlexItem().
439 Maybe<nscoord> MeasuredBSize() const;
441 // Convenience methods to synthesize a style main size or a style cross size
442 // with box-size considered, to provide the size overrides when constructing
443 // ReflowInput for flex items.
444 StyleSize StyleMainSize() const {
445 nscoord mainSize = MainSize();
446 if (Frame()->StylePosition()->mBoxSizing == StyleBoxSizing::Border) {
447 mainSize += BorderPaddingSizeInMainAxis();
449 return StyleSize::LengthPercentage(
450 LengthPercentage::FromAppUnits(mainSize));
453 StyleSize StyleCrossSize() const {
454 nscoord crossSize = CrossSize();
455 if (Frame()->StylePosition()->mBoxSizing == StyleBoxSizing::Border) {
456 crossSize += BorderPaddingSizeInCrossAxis();
458 return StyleSize::LengthPercentage(
459 LengthPercentage::FromAppUnits(crossSize));
462 // Returns the distance between this FlexItem's baseline and the cross-start
463 // edge of its margin-box. Used in baseline alignment.
465 // (This function needs to be told which physical start side we're measuring
466 // the baseline from, so that it can look up the appropriate components from
467 // margin.)
468 nscoord BaselineOffsetFromOuterCrossEdge(mozilla::Side aStartSide,
469 bool aUseFirstLineBaseline) const;
471 double ShareOfWeightSoFar() const { return mShareOfWeightSoFar; }
473 bool IsFrozen() const { return mIsFrozen; }
475 bool HadMinViolation() const {
476 MOZ_ASSERT(!mIsFrozen, "min violation has no meaning for frozen items.");
477 return mHadMinViolation;
480 bool HadMaxViolation() const {
481 MOZ_ASSERT(!mIsFrozen, "max violation has no meaning for frozen items.");
482 return mHadMaxViolation;
485 bool WasMinClamped() const {
486 MOZ_ASSERT(mIsFrozen, "min clamping has no meaning for unfrozen items.");
487 return mHadMinViolation;
490 bool WasMaxClamped() const {
491 MOZ_ASSERT(mIsFrozen, "max clamping has no meaning for unfrozen items.");
492 return mHadMaxViolation;
495 // Indicates whether this item received a preliminary "measuring" reflow
496 // before its actual reflow.
497 bool HadMeasuringReflow() const { return mHadMeasuringReflow; }
499 // Indicates whether this item's computed cross-size property is 'auto'.
500 bool IsCrossSizeAuto() const;
502 // Indicates whether the cross-size property is set to something definite,
503 // for the purpose of preferred aspect ratio calculations.
504 bool IsCrossSizeDefinite(const ReflowInput& aItemReflowInput) const;
506 // Indicates whether this item's cross-size has been stretched (from having
507 // "align-self: stretch" with an auto cross-size and no auto margins in the
508 // cross axis).
509 bool IsStretched() const { return mIsStretched; }
511 bool IsFlexBaseSizeContentBSize() const {
512 return mIsFlexBaseSizeContentBSize;
515 bool IsMainMinSizeContentBSize() const { return mIsMainMinSizeContentBSize; }
517 // Indicates whether we need to resolve an 'auto' value for the main-axis
518 // min-[width|height] property.
519 bool NeedsMinSizeAutoResolution() const {
520 return mNeedsMinSizeAutoResolution;
523 bool HasAnyAutoMargin() const { return mHasAnyAutoMargin; }
525 BaselineSharingGroup ItemBaselineSharingGroup() const {
526 MOZ_ASSERT(mAlignSelf._0 == StyleAlignFlags::BASELINE ||
527 mAlignSelf._0 == StyleAlignFlags::LAST_BASELINE,
528 "mBaselineSharingGroup only gets a meaningful value "
529 "for baseline-aligned items");
530 return mBaselineSharingGroup;
533 // Indicates whether this item is a "strut" left behind by an element with
534 // visibility:collapse.
535 bool IsStrut() const { return mIsStrut; }
537 // The main axis and cross axis are relative to mCBWM.
538 LogicalAxis MainAxis() const { return mMainAxis; }
539 LogicalAxis CrossAxis() const { return GetOrthogonalAxis(mMainAxis); }
541 // IsInlineAxisMainAxis() returns true if this item's inline axis is parallel
542 // (or antiparallel) to the container's main axis. Otherwise (i.e. if this
543 // item's inline axis is orthogonal to the container's main axis), this
544 // function returns false. The next 3 methods are all other ways of asking
545 // the same question, and only exist for readability at callsites (depending
546 // on which axes those callsites are reasoning about).
547 bool IsInlineAxisMainAxis() const { return mIsInlineAxisMainAxis; }
548 bool IsInlineAxisCrossAxis() const { return !mIsInlineAxisMainAxis; }
549 bool IsBlockAxisMainAxis() const { return !mIsInlineAxisMainAxis; }
550 bool IsBlockAxisCrossAxis() const { return mIsInlineAxisMainAxis; }
552 WritingMode GetWritingMode() const { return mWM; }
553 WritingMode ContainingBlockWM() const { return mCBWM; }
554 StyleAlignSelf AlignSelf() const { return mAlignSelf; }
555 StyleAlignFlags AlignSelfFlags() const { return mAlignSelfFlags; }
557 // Returns the flex factor (flex-grow or flex-shrink), depending on
558 // 'aIsUsingFlexGrow'.
560 // Asserts fatally if called on a frozen item (since frozen items are not
561 // flexible).
562 float GetFlexFactor(bool aIsUsingFlexGrow) {
563 MOZ_ASSERT(!IsFrozen(), "shouldn't need flex factor after item is frozen");
565 return aIsUsingFlexGrow ? mFlexGrow : mFlexShrink;
568 // Returns the weight that we should use in the "resolving flexible lengths"
569 // algorithm. If we're using the flex grow factor, we just return that;
570 // otherwise, we return the "scaled flex shrink factor" (scaled by our flex
571 // base size, so that when both large and small items are shrinking, the large
572 // items shrink more).
574 // I'm calling this a "weight" instead of a "[scaled] flex-[grow|shrink]
575 // factor", to more clearly distinguish it from the actual flex-grow &
576 // flex-shrink factors.
578 // Asserts fatally if called on a frozen item (since frozen items are not
579 // flexible).
580 float GetWeight(bool aIsUsingFlexGrow) {
581 MOZ_ASSERT(!IsFrozen(), "shouldn't need weight after item is frozen");
583 if (aIsUsingFlexGrow) {
584 return mFlexGrow;
587 // We're using flex-shrink --> return mFlexShrink * mFlexBaseSize
588 if (mFlexBaseSize == 0) {
589 // Special-case for mFlexBaseSize == 0 -- we have no room to shrink, so
590 // regardless of mFlexShrink, we should just return 0.
591 // (This is really a special-case for when mFlexShrink is infinity, to
592 // avoid performing mFlexShrink * mFlexBaseSize = inf * 0 = undefined.)
593 return 0.0f;
595 return mFlexShrink * mFlexBaseSize;
598 bool TreatBSizeAsIndefinite() const { return mTreatBSizeAsIndefinite; }
600 const AspectRatio& GetAspectRatio() const { return mAspectRatio; }
601 bool HasAspectRatio() const { return !!mAspectRatio; }
603 // Getters for margin:
604 // ===================
605 LogicalMargin Margin() const { return mMargin; }
606 nsMargin PhysicalMargin() const { return mMargin.GetPhysicalMargin(mCBWM); }
608 // Returns the margin component for a given LogicalSide in flex container's
609 // writing-mode.
610 nscoord GetMarginComponentForSide(LogicalSide aSide) const {
611 return mMargin.Side(aSide, mCBWM);
614 // Returns the total space occupied by this item's margins in the given axis
615 nscoord MarginSizeInMainAxis() const {
616 return mMargin.StartEnd(MainAxis(), mCBWM);
618 nscoord MarginSizeInCrossAxis() const {
619 return mMargin.StartEnd(CrossAxis(), mCBWM);
622 // Getters for border/padding
623 // ==========================
624 // Returns the total space occupied by this item's borders and padding in
625 // the given axis
626 LogicalMargin BorderPadding() const { return mBorderPadding; }
627 nscoord BorderPaddingSizeInMainAxis() const {
628 return mBorderPadding.StartEnd(MainAxis(), mCBWM);
630 nscoord BorderPaddingSizeInCrossAxis() const {
631 return mBorderPadding.StartEnd(CrossAxis(), mCBWM);
634 // Getter for combined margin/border/padding
635 // =========================================
636 // Returns the total space occupied by this item's margins, borders and
637 // padding in the given axis
638 nscoord MarginBorderPaddingSizeInMainAxis() const {
639 return MarginSizeInMainAxis() + BorderPaddingSizeInMainAxis();
641 nscoord MarginBorderPaddingSizeInCrossAxis() const {
642 return MarginSizeInCrossAxis() + BorderPaddingSizeInCrossAxis();
645 // Setters
646 // =======
647 // Helper to set the resolved value of min-[width|height]:auto for the main
648 // axis. (Should only be used if NeedsMinSizeAutoResolution() returns true.)
649 void UpdateMainMinSize(nscoord aNewMinSize) {
650 NS_ASSERTION(aNewMinSize >= 0,
651 "How did we end up with a negative min-size?");
652 MOZ_ASSERT(
653 mMainMaxSize == NS_UNCONSTRAINEDSIZE || mMainMaxSize >= aNewMinSize,
654 "Should only use this function for resolving min-size:auto, "
655 "and main max-size should be an upper-bound for resolved val");
656 MOZ_ASSERT(
657 mNeedsMinSizeAutoResolution &&
658 (mMainMinSize == 0 || mFrame->IsThemed(mFrame->StyleDisplay())),
659 "Should only use this function for resolving min-size:auto, "
660 "so we shouldn't already have a nonzero min-size established "
661 "(unless it's a themed-widget-imposed minimum size)");
663 if (aNewMinSize > mMainMinSize) {
664 mMainMinSize = aNewMinSize;
665 // Also clamp main-size to be >= new min-size:
666 mMainSize = std::max(mMainSize, aNewMinSize);
668 mNeedsMinSizeAutoResolution = false;
671 // This sets our flex base size, and then sets our main size to the
672 // resulting "hypothetical main size" (the base size clamped to our
673 // main-axis [min,max] sizing constraints).
674 void SetFlexBaseSizeAndMainSize(nscoord aNewFlexBaseSize) {
675 MOZ_ASSERT(!mIsFrozen || mFlexBaseSize == NS_UNCONSTRAINEDSIZE,
676 "flex base size shouldn't change after we're frozen "
677 "(unless we're just resolving an intrinsic size)");
678 mFlexBaseSize = aNewFlexBaseSize;
680 // Before we've resolved flexible lengths, we keep mMainSize set to
681 // the 'hypothetical main size', which is the flex base size, clamped
682 // to the [min,max] range:
683 mMainSize = NS_CSS_MINMAX(mFlexBaseSize, mMainMinSize, mMainMaxSize);
685 FLEX_LOGV(
686 "Set flex base size: %d, hypothetical main size: %d for flex item %p",
687 mFlexBaseSize, mMainSize, mFrame);
690 // Setters used while we're resolving flexible lengths
691 // ---------------------------------------------------
693 // Sets the main-size of our flex item's content-box.
694 void SetMainSize(nscoord aNewMainSize) {
695 MOZ_ASSERT(!mIsFrozen, "main size shouldn't change after we're frozen");
696 mMainSize = aNewMainSize;
699 void SetShareOfWeightSoFar(double aNewShare) {
700 MOZ_ASSERT(!mIsFrozen || aNewShare == 0.0,
701 "shouldn't be giving this item any share of the weight "
702 "after it's frozen");
703 mShareOfWeightSoFar = aNewShare;
706 void Freeze() {
707 mIsFrozen = true;
708 // Now that we are frozen, the meaning of mHadMinViolation and
709 // mHadMaxViolation changes to indicate min and max clamping. Clear
710 // both of the member variables so that they are ready to be set
711 // as clamping state later, if necessary.
712 mHadMinViolation = false;
713 mHadMaxViolation = false;
716 void SetHadMinViolation() {
717 MOZ_ASSERT(!mIsFrozen,
718 "shouldn't be changing main size & having violations "
719 "after we're frozen");
720 mHadMinViolation = true;
722 void SetHadMaxViolation() {
723 MOZ_ASSERT(!mIsFrozen,
724 "shouldn't be changing main size & having violations "
725 "after we're frozen");
726 mHadMaxViolation = true;
728 void ClearViolationFlags() {
729 MOZ_ASSERT(!mIsFrozen,
730 "shouldn't be altering violation flags after we're "
731 "frozen");
732 mHadMinViolation = mHadMaxViolation = false;
735 void SetWasMinClamped() {
736 MOZ_ASSERT(!mHadMinViolation && !mHadMaxViolation, "only clamp once");
737 // This reuses the mHadMinViolation member variable to track clamping
738 // events. This is allowable because mHadMinViolation only reflects
739 // a violation up until the item is frozen.
740 MOZ_ASSERT(mIsFrozen, "shouldn't set clamping state when we are unfrozen");
741 mHadMinViolation = true;
743 void SetWasMaxClamped() {
744 MOZ_ASSERT(!mHadMinViolation && !mHadMaxViolation, "only clamp once");
745 // This reuses the mHadMaxViolation member variable to track clamping
746 // events. This is allowable because mHadMaxViolation only reflects
747 // a violation up until the item is frozen.
748 MOZ_ASSERT(mIsFrozen, "shouldn't set clamping state when we are unfrozen");
749 mHadMaxViolation = true;
752 // Setters for values that are determined after we've resolved our main size
753 // -------------------------------------------------------------------------
755 // Sets the main-axis position of our flex item's content-box.
756 // (This is the distance between the main-start edge of the flex container
757 // and the main-start edge of the flex item's content-box.)
758 void SetMainPosition(nscoord aPosn) {
759 MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
760 mMainPosn = aPosn;
763 // Sets the cross-size of our flex item's content-box.
764 void SetCrossSize(nscoord aCrossSize) {
765 MOZ_ASSERT(!mIsStretched,
766 "Cross size shouldn't be modified after it's been stretched");
767 mCrossSize = aCrossSize;
770 // Sets the cross-axis position of our flex item's content-box.
771 // (This is the distance between the cross-start edge of the flex container
772 // and the cross-start edge of the flex item.)
773 void SetCrossPosition(nscoord aPosn) {
774 MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
775 mCrossPosn = aPosn;
778 // After a FlexItem has had a reflow, this method can be used to cache its
779 // (possibly-unresolved) ascent, in case it's needed later for
780 // baseline-alignment or to establish the container's baseline.
781 // (NOTE: This can be marked 'const' even though it's modifying mAscent,
782 // because mAscent is mutable. It's nice for this to be 'const', because it
783 // means our final reflow can iterate over const FlexItem pointers, and we
784 // can be sure it's not modifying those FlexItems, except via this method.)
785 void SetAscent(nscoord aAscent) const {
786 mAscent = aAscent; // NOTE: this may be ASK_FOR_BASELINE
789 void SetHadMeasuringReflow() { mHadMeasuringReflow = true; }
791 void SetIsStretched() {
792 MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
793 mIsStretched = true;
796 void SetIsFlexBaseSizeContentBSize() { mIsFlexBaseSizeContentBSize = true; }
798 void SetIsMainMinSizeContentBSize() { mIsMainMinSizeContentBSize = true; }
800 // Setter for margin components (for resolving "auto" margins)
801 void SetMarginComponentForSide(LogicalSide aSide, nscoord aLength) {
802 MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
803 mMargin.Side(aSide, mCBWM) = aLength;
806 void ResolveStretchedCrossSize(nscoord aLineCrossSize);
808 // Resolves flex base size if flex-basis' used value is 'content', using this
809 // item's preferred aspect ratio and cross size.
810 void ResolveFlexBaseSizeFromAspectRatio(const ReflowInput& aItemReflowInput);
812 uint32_t NumAutoMarginsInMainAxis() const {
813 return NumAutoMarginsInAxis(MainAxis());
816 uint32_t NumAutoMarginsInCrossAxis() const {
817 return NumAutoMarginsInAxis(CrossAxis());
820 // Once the main size has been resolved, should we bother doing layout to
821 // establish the cross size?
822 bool CanMainSizeInfluenceCrossSize() const;
824 // Returns a main size, clamped by any definite min and max cross size
825 // converted through the preferred aspect ratio. The caller is responsible for
826 // ensuring that the flex item's preferred aspect ratio is not zero.
827 nscoord ClampMainSizeViaCrossAxisConstraints(
828 nscoord aMainSize, const ReflowInput& aItemReflowInput) const;
830 // Indicates whether we think this flex item needs a "final" reflow
831 // (after its final flexed size & final position have been determined).
833 // @param aParentReflowInput the flex container's reflow input.
834 // @return true if such a reflow is needed, or false if we believe it can
835 // simply be moved to its final position and skip the reflow.
836 bool NeedsFinalReflow(const ReflowInput& aParentReflowInput) const;
838 // Gets the block frame that contains the flex item's content. This is
839 // Frame() itself or one of its descendants.
840 nsBlockFrame* BlockFrame() const;
842 protected:
843 bool IsMinSizeAutoResolutionNeeded() const;
845 uint32_t NumAutoMarginsInAxis(LogicalAxis aAxis) const;
847 // Values that we already know in constructor, and remain unchanged:
848 // The flex item's frame.
849 nsIFrame* mFrame = nullptr;
850 float mFlexGrow = 0.0f;
851 float mFlexShrink = 0.0f;
852 AspectRatio mAspectRatio;
854 // The flex item's writing mode.
855 WritingMode mWM;
857 // The flex container's writing mode.
858 WritingMode mCBWM;
860 // The flex container's main axis in flex container's writing mode.
861 LogicalAxis mMainAxis;
863 // Stored in flex container's writing mode.
864 LogicalMargin mBorderPadding;
866 // Stored in flex container's writing mode. Its value can change when we
867 // resolve "auto" marigns.
868 LogicalMargin mMargin;
870 // These are non-const so that we can lazily update them with the item's
871 // intrinsic size (obtained via a "measuring" reflow), when necessary.
872 // (e.g. for "flex-basis:auto;height:auto" & "min-height:auto")
873 nscoord mFlexBaseSize = 0;
874 nscoord mMainMinSize = 0;
875 nscoord mMainMaxSize = 0;
877 // mCrossMinSize and mCrossMaxSize are not changed after constructor.
878 nscoord mCrossMinSize = 0;
879 nscoord mCrossMaxSize = 0;
881 // Values that we compute after constructor:
882 nscoord mMainSize = 0;
883 nscoord mMainPosn = 0;
884 nscoord mCrossSize = 0;
885 nscoord mCrossPosn = 0;
887 // Mutable b/c it's set & resolved lazily, sometimes via const pointer. See
888 // comment above SetAscent().
889 // We initialize this to ASK_FOR_BASELINE, and opportunistically fill it in
890 // with a real value if we end up reflowing this flex item. (But if we don't
891 // reflow this flex item, then this sentinel tells us that we don't know it
892 // yet & anyone who cares will need to explicitly request it.)
894 // Both mAscent and mAscentForLast are distance from the frame's border-box
895 // block-start edge.
896 mutable nscoord mAscent = ReflowOutput::ASK_FOR_BASELINE;
897 mutable nscoord mAscentForLast = ReflowOutput::ASK_FOR_BASELINE;
899 // Temporary state, while we're resolving flexible widths (for our main size)
900 // XXXdholbert To save space, we could use a union to make these variables
901 // overlay the same memory as some other member vars that aren't touched
902 // until after main-size has been resolved. In particular, these could share
903 // memory with mMainPosn through mAscent, and mIsStretched.
904 double mShareOfWeightSoFar = 0.0;
906 bool mIsFrozen = false;
907 bool mHadMinViolation = false;
908 bool mHadMaxViolation = false;
910 // Did this item get a preliminary reflow, to measure its desired height?
911 bool mHadMeasuringReflow = false;
913 // See IsStretched() documentation.
914 bool mIsStretched = false;
916 // Is this item a "strut" left behind by an element with visibility:collapse?
917 bool mIsStrut = false;
919 // See IsInlineAxisMainAxis() documentation. This is not changed after
920 // constructor.
921 bool mIsInlineAxisMainAxis = true;
923 // Does this item need to resolve a min-[width|height]:auto (in main-axis)?
925 // Note: mNeedsMinSizeAutoResolution needs to be declared towards the end of
926 // the member variables since it's initialized in a method that depends on
927 // other members declared above such as mCBWM, mMainAxis, and
928 // mIsInlineAxisMainAxis.
929 bool mNeedsMinSizeAutoResolution = false;
931 // Should we take care to treat this item's resolved BSize as indefinite?
932 bool mTreatBSizeAsIndefinite = false;
934 // Does this item have an auto margin in either main or cross axis?
935 bool mHasAnyAutoMargin = false;
937 // Does this item have a content-based flex base size (and is that a size in
938 // its block-axis)?
939 bool mIsFlexBaseSizeContentBSize = false;
941 // Does this item have a content-based resolved auto min size (and is that a
942 // size in its block-axis)?
943 bool mIsMainMinSizeContentBSize = false;
945 // If this item is {first,last}-baseline-aligned using 'align-self', which of
946 // its FlexLine's baseline sharing groups does it participate in?
947 BaselineSharingGroup mBaselineSharingGroup = BaselineSharingGroup::First;
949 // My "align-self" computed value (with "auto" swapped out for parent"s
950 // "align-items" value, in our constructor).
951 StyleAlignSelf mAlignSelf{StyleAlignFlags::AUTO};
953 // Flags for 'align-self' (safe/unsafe/legacy).
954 StyleAlignFlags mAlignSelfFlags{0};
958 * Represents a single flex line in a flex container.
959 * Manages an array of the FlexItems that are in the line.
961 class nsFlexContainerFrame::FlexLine final {
962 public:
963 explicit FlexLine(nscoord aMainGapSize) : mMainGapSize(aMainGapSize) {}
965 nscoord SumOfGaps() const {
966 return NumItems() > 0 ? (NumItems() - 1) * mMainGapSize : 0;
969 // Returns the sum of our FlexItems' outer hypothetical main sizes plus the
970 // sum of main axis {row,column}-gaps between items.
971 // ("outer" = margin-box, and "hypothetical" = before flexing)
972 AuCoord64 TotalOuterHypotheticalMainSize() const {
973 return mTotalOuterHypotheticalMainSize;
976 // Accessors for our FlexItems & information about them:
978 // Note: Callers must use IsEmpty() to ensure that the FlexLine is non-empty
979 // before calling accessors that return FlexItem.
980 FlexItem& FirstItem() { return mItems[0]; }
981 const FlexItem& FirstItem() const { return mItems[0]; }
983 FlexItem& LastItem() { return mItems.LastElement(); }
984 const FlexItem& LastItem() const { return mItems.LastElement(); }
986 // The "startmost"/"endmost" is from the perspective of the flex container's
987 // writing-mode, not from the perspective of the flex-relative main axis.
988 const FlexItem& StartmostItem(const FlexboxAxisTracker& aAxisTracker) const {
989 return aAxisTracker.IsMainAxisReversed() ? LastItem() : FirstItem();
991 const FlexItem& EndmostItem(const FlexboxAxisTracker& aAxisTracker) const {
992 return aAxisTracker.IsMainAxisReversed() ? FirstItem() : LastItem();
995 bool IsEmpty() const { return mItems.IsEmpty(); }
997 uint32_t NumItems() const { return mItems.Length(); }
999 nsTArray<FlexItem>& Items() { return mItems; }
1000 const nsTArray<FlexItem>& Items() const { return mItems; }
1002 // Adds the last flex item's hypothetical outer main-size and
1003 // margin/border/padding to our totals. This should be called exactly once for
1004 // each flex item, after we've determined that this line is the correct home
1005 // for that item.
1006 void AddLastItemToMainSizeTotals() {
1007 const FlexItem& lastItem = Items().LastElement();
1009 // Update our various bookkeeping member-vars:
1010 if (lastItem.IsFrozen()) {
1011 mNumFrozenItems++;
1014 mTotalItemMBP += lastItem.MarginBorderPaddingSizeInMainAxis();
1015 mTotalOuterHypotheticalMainSize += lastItem.OuterMainSize();
1017 // If the item added was not the first item in the line, we add in any gap
1018 // space as needed.
1019 if (NumItems() >= 2) {
1020 mTotalOuterHypotheticalMainSize += mMainGapSize;
1024 // Computes the cross-size and baseline position of this FlexLine, based on
1025 // its FlexItems.
1026 void ComputeCrossSizeAndBaseline(const FlexboxAxisTracker& aAxisTracker);
1028 // Returns the cross-size of this line.
1029 nscoord LineCrossSize() const { return mLineCrossSize; }
1031 // Setter for line cross-size -- needed for cases where the flex container
1032 // imposes a cross-size on the line. (e.g. for single-line flexbox, or for
1033 // multi-line flexbox with 'align-content: stretch')
1034 void SetLineCrossSize(nscoord aLineCrossSize) {
1035 mLineCrossSize = aLineCrossSize;
1039 * Returns the offset within this line where any baseline-aligned FlexItems
1040 * should place their baseline. The return value represents a distance from
1041 * the line's cross-start edge.
1043 * If there are no baseline-aligned FlexItems, returns nscoord_MIN.
1045 nscoord FirstBaselineOffset() const { return mFirstBaselineOffset; }
1048 * Returns the offset within this line where any last baseline-aligned
1049 * FlexItems should place their baseline. Opposite the case of the first
1050 * baseline offset, this represents a distance from the line's cross-end
1051 * edge (since last baseline-aligned items are flush to the cross-end edge).
1053 * If there are no last baseline-aligned FlexItems, returns nscoord_MIN.
1055 nscoord LastBaselineOffset() const { return mLastBaselineOffset; }
1057 // Extract a baseline from this line, which would be suitable for use as the
1058 // flex container's 'aBaselineGroup' (i.e. first/last) baseline.
1059 // https://drafts.csswg.org/css-flexbox-1/#flex-baselines
1061 // The return value always represents a distance from the line's cross-start
1062 // edge, even if we are querying last baseline. If this line has no flex items
1063 // in its aBaselineGroup group, this method falls back to trying the opposite
1064 // group. If this line has no baseline-aligned items at all, this returns
1065 // nscoord_MIN.
1066 nscoord ExtractBaselineOffset(BaselineSharingGroup aBaselineGroup) const;
1069 * Returns the gap size in the main axis for this line. Used for gap
1070 * calculations.
1072 nscoord MainGapSize() const { return mMainGapSize; }
1074 // Runs the "Resolving Flexible Lengths" algorithm from section 9.7 of the
1075 // CSS flexbox spec to distribute aFlexContainerMainSize among our flex items.
1076 // https://drafts.csswg.org/css-flexbox-1/#resolve-flexible-lengths
1077 void ResolveFlexibleLengths(nscoord aFlexContainerMainSize,
1078 ComputedFlexLineInfo* aLineInfo);
1080 void PositionItemsInMainAxis(const StyleContentDistribution& aJustifyContent,
1081 nscoord aContentBoxMainSize,
1082 const FlexboxAxisTracker& aAxisTracker);
1084 void PositionItemsInCrossAxis(nscoord aLineStartPosition,
1085 const FlexboxAxisTracker& aAxisTracker);
1087 private:
1088 // Helpers for ResolveFlexibleLengths():
1089 void FreezeItemsEarly(bool aIsUsingFlexGrow, ComputedFlexLineInfo* aLineInfo);
1091 void FreezeOrRestoreEachFlexibleSize(const nscoord aTotalViolation,
1092 bool aIsFinalIteration);
1094 // Stores this line's flex items.
1095 nsTArray<FlexItem> mItems;
1097 // Number of *frozen* FlexItems in this line, based on FlexItem::IsFrozen().
1098 // Mostly used for optimization purposes, e.g. to bail out early from loops
1099 // when we can tell they have nothing left to do.
1100 uint32_t mNumFrozenItems = 0;
1102 // Sum of margin/border/padding for the FlexItems in this FlexLine.
1103 nscoord mTotalItemMBP = 0;
1105 // Sum of FlexItems' outer hypothetical main sizes and all main-axis
1106 // {row,columnm}-gaps between items.
1107 // (i.e. their flex base sizes, clamped via their min/max-size properties,
1108 // plus their main-axis margin/border/padding, plus the sum of the gaps.)
1110 // This variable uses a 64-bit coord type to avoid integer overflow in case
1111 // several of the individual items have huge hypothetical main sizes, which
1112 // can happen with percent-width table-layout:fixed descendants. We have to
1113 // avoid integer overflow in order to shrink items properly in that scenario.
1114 AuCoord64 mTotalOuterHypotheticalMainSize = 0;
1116 nscoord mLineCrossSize = 0;
1117 nscoord mFirstBaselineOffset = nscoord_MIN;
1118 nscoord mLastBaselineOffset = nscoord_MIN;
1120 // Maintain size of each {row,column}-gap in the main axis
1121 const nscoord mMainGapSize;
1124 // The "startmost"/"endmost" is from the perspective of the flex container's
1125 // writing-mode, not from the perspective of the flex-relative cross axis.
1126 const FlexLine& StartmostLine(const nsTArray<FlexLine>& aLines,
1127 const FlexboxAxisTracker& aAxisTracker) {
1128 return aAxisTracker.IsCrossAxisReversed() ? aLines.LastElement() : aLines[0];
1130 const FlexLine& EndmostLine(const nsTArray<FlexLine>& aLines,
1131 const FlexboxAxisTracker& aAxisTracker) {
1132 return aAxisTracker.IsCrossAxisReversed() ? aLines[0] : aLines.LastElement();
1135 // Information about a strut left behind by a FlexItem that's been collapsed
1136 // using "visibility:collapse".
1137 struct nsFlexContainerFrame::StrutInfo {
1138 StrutInfo(uint32_t aItemIdx, nscoord aStrutCrossSize)
1139 : mItemIdx(aItemIdx), mStrutCrossSize(aStrutCrossSize) {}
1141 uint32_t mItemIdx; // Index in the child list.
1142 nscoord mStrutCrossSize; // The cross-size of this strut.
1145 // Flex data shared by the flex container frames in a continuation chain, owned
1146 // by the first-in-flow. The data is initialized at the end of the
1147 // first-in-flow's Reflow().
1148 struct nsFlexContainerFrame::SharedFlexData final {
1149 // The flex lines generated in DoFlexLayout() by our first-in-flow.
1150 nsTArray<FlexLine> mLines;
1152 // The final content main/cross size computed by DoFlexLayout.
1153 nscoord mContentBoxMainSize = NS_UNCONSTRAINEDSIZE;
1154 nscoord mContentBoxCrossSize = NS_UNCONSTRAINEDSIZE;
1156 // Update this struct. Called by the first-in-flow.
1157 void Update(FlexLayoutResult&& aFlr) {
1158 mLines = std::move(aFlr.mLines);
1159 mContentBoxMainSize = aFlr.mContentBoxMainSize;
1160 mContentBoxCrossSize = aFlr.mContentBoxCrossSize;
1163 // The frame property under which this struct is stored. Set only on the
1164 // first-in-flow.
1165 NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, SharedFlexData)
1168 // Flex data stored in every flex container's in-flow fragment (continuation).
1170 // It's intended to prevent quadratic operations resulting from each fragment
1171 // having to walk its full prev-in-flow chain, and also serves as an argument to
1172 // the flex container next-in-flow's ReflowChildren(), to compute the position
1173 // offset for each flex item.
1174 struct nsFlexContainerFrame::PerFragmentFlexData final {
1175 // Suppose D is the distance from a flex container fragment's content-box
1176 // block-start edge to whichever is larger of either (a) the block-end edge of
1177 // its children, or (b) the available space's block-end edge. (Note: in case
1178 // (b), D is conceptually the sum of the block-size of the children, the
1179 // packing space before & in between them, and part of the packing space after
1180 // them.)
1182 // This variable stores the sum of the D values for the current flex container
1183 // fragments and for all its previous fragments
1184 nscoord mCumulativeContentBoxBSize = 0;
1186 // This variable accumulates FirstLineOrFirstItemBAxisMetrics::mBEndEdgeShift,
1187 // for the current flex container fragment and for all its previous fragments.
1188 // See the comment of mBEndEdgeShift for its computation details. In short,
1189 // this value is the net block-end edge shift, accumulated for the children in
1190 // all the previous fragments. This number is non-negative.
1192 // This value is also used to grow a flex container's block-size if the
1193 // container's computed block-size is unconstrained. For example: a tall item
1194 // may be pushed to the next page/column, which leaves some wasted area at the
1195 // bottom of the current flex container fragment, and causes the flex
1196 // container fragments to be (collectively) larger than the hypothetical
1197 // unfragmented size. Another example: a tall flex item may be broken into
1198 // multiple fragments, and those fragments may have a larger collective
1199 // block-size as compared to the item's original unfragmented size; the
1200 // container would need to increase its block-size to account for this.
1201 nscoord mCumulativeBEndEdgeShift = 0;
1203 // The frame property under which this struct is stored. Cached on every
1204 // in-flow fragment (continuation) at the end of the flex container's
1205 // Reflow().
1206 NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, PerFragmentFlexData)
1209 static void BuildStrutInfoFromCollapsedItems(const nsTArray<FlexLine>& aLines,
1210 nsTArray<StrutInfo>& aStruts) {
1211 MOZ_ASSERT(aStruts.IsEmpty(),
1212 "We should only build up StrutInfo once per reflow, so "
1213 "aStruts should be empty when this is called");
1215 uint32_t itemIdxInContainer = 0;
1216 for (const FlexLine& line : aLines) {
1217 for (const FlexItem& item : line.Items()) {
1218 if (item.Frame()->StyleVisibility()->IsCollapse()) {
1219 // Note the cross size of the line as the item's strut size.
1220 aStruts.AppendElement(
1221 StrutInfo(itemIdxInContainer, line.LineCrossSize()));
1223 itemIdxInContainer++;
1228 static mozilla::StyleAlignFlags SimplifyAlignOrJustifyContentForOneItem(
1229 const StyleContentDistribution& aAlignmentVal, bool aIsAlign) {
1230 // Mask away any explicit fallback, to get the main (non-fallback) part of
1231 // the specified value:
1232 StyleAlignFlags specified = aAlignmentVal.primary;
1234 // XXX strip off <overflow-position> bits until we implement it (bug 1311892)
1235 specified &= ~StyleAlignFlags::FLAG_BITS;
1237 // FIRST: handle a special-case for "justify-content:stretch" (or equivalent),
1238 // which requires that we ignore any author-provided explicit fallback value.
1239 if (specified == StyleAlignFlags::NORMAL) {
1240 // In a flex container, *-content: "'normal' behaves as 'stretch'".
1241 // Do that conversion early, so it benefits from our 'stretch' special-case.
1242 // https://drafts.csswg.org/css-align-3/#distribution-flex
1243 specified = StyleAlignFlags::STRETCH;
1245 if (!aIsAlign && specified == StyleAlignFlags::STRETCH) {
1246 // In a flex container, in "justify-content Axis: [...] 'stretch' behaves
1247 // as 'flex-start' (ignoring the specified fallback alignment, if any)."
1248 // https://drafts.csswg.org/css-align-3/#distribution-flex
1249 // So, we just directly return 'flex-start', & ignore explicit fallback..
1250 return StyleAlignFlags::FLEX_START;
1253 // TODO: Check for an explicit fallback value (and if it's present, use it)
1254 // here once we parse it, see https://github.com/w3c/csswg-drafts/issues/1002.
1256 // If there's no explicit fallback, use the implied fallback values for
1257 // space-{between,around,evenly} (since those values only make sense with
1258 // multiple alignment subjects), and otherwise just use the specified value:
1259 if (specified == StyleAlignFlags::SPACE_BETWEEN) {
1260 return StyleAlignFlags::FLEX_START;
1262 if (specified == StyleAlignFlags::SPACE_AROUND ||
1263 specified == StyleAlignFlags::SPACE_EVENLY) {
1264 return StyleAlignFlags::CENTER;
1266 return specified;
1269 bool nsFlexContainerFrame::DrainSelfOverflowList() {
1270 return DrainAndMergeSelfOverflowList();
1273 void nsFlexContainerFrame::AppendFrames(ChildListID aListID,
1274 nsFrameList&& aFrameList) {
1275 NoteNewChildren(aListID, aFrameList);
1276 nsContainerFrame::AppendFrames(aListID, std::move(aFrameList));
1279 void nsFlexContainerFrame::InsertFrames(
1280 ChildListID aListID, nsIFrame* aPrevFrame,
1281 const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) {
1282 NoteNewChildren(aListID, aFrameList);
1283 nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
1284 std::move(aFrameList));
1287 void nsFlexContainerFrame::RemoveFrame(DestroyContext& aContext,
1288 ChildListID aListID,
1289 nsIFrame* aOldFrame) {
1290 MOZ_ASSERT(aListID == FrameChildListID::Principal, "unexpected child list");
1292 #ifdef DEBUG
1293 SetDidPushItemsBitIfNeeded(aListID, aOldFrame);
1294 #endif
1296 nsContainerFrame::RemoveFrame(aContext, aListID, aOldFrame);
1299 StyleAlignFlags nsFlexContainerFrame::CSSAlignmentForAbsPosChild(
1300 const ReflowInput& aChildRI, LogicalAxis aLogicalAxis) const {
1301 const FlexboxAxisTracker axisTracker(this);
1303 // If we're row-oriented and the caller is asking about our inline axis (or
1304 // alternately, if we're column-oriented and the caller is asking about our
1305 // block axis), then the caller is really asking about our *main* axis.
1306 // Otherwise, the caller is asking about our cross axis.
1307 const bool isMainAxis =
1308 (axisTracker.IsRowOriented() == (aLogicalAxis == eLogicalAxisInline));
1309 const nsStylePosition* containerStylePos = StylePosition();
1310 const bool isAxisReversed = isMainAxis ? axisTracker.IsMainAxisReversed()
1311 : axisTracker.IsCrossAxisReversed();
1313 StyleAlignFlags alignment{0};
1314 StyleAlignFlags alignmentFlags{0};
1315 if (isMainAxis) {
1316 // We're aligning in the main axis: align according to 'justify-content'.
1317 // (We don't care about justify-self; it has no effect on children of flex
1318 // containers, unless https://github.com/w3c/csswg-drafts/issues/7644
1319 // changes that.)
1320 alignment = SimplifyAlignOrJustifyContentForOneItem(
1321 containerStylePos->mJustifyContent,
1322 /*aIsAlign = */ false);
1323 } else {
1324 // We're aligning in the cross axis: align according to 'align-self'.
1325 // (We don't care about align-content; it has no effect on abspos flex
1326 // children, per https://github.com/w3c/csswg-drafts/issues/7596 )
1327 alignment = aChildRI.mStylePosition->UsedAlignSelf(Style())._0;
1328 // Extract and strip align flag bits
1329 alignmentFlags = alignment & StyleAlignFlags::FLAG_BITS;
1330 alignment &= ~StyleAlignFlags::FLAG_BITS;
1332 if (alignment == StyleAlignFlags::NORMAL) {
1333 // "the 'normal' keyword behaves as 'start' on replaced
1334 // absolutely-positioned boxes, and behaves as 'stretch' on all other
1335 // absolutely-positioned boxes."
1336 // https://drafts.csswg.org/css-align/#align-abspos
1337 alignment = aChildRI.mFrame->IsReplaced() ? StyleAlignFlags::START
1338 : StyleAlignFlags::STRETCH;
1342 if (alignment == StyleAlignFlags::STRETCH) {
1343 // The default fallback alignment for 'stretch' is 'flex-start'.
1344 alignment = StyleAlignFlags::FLEX_START;
1347 // Resolve flex-start, flex-end, auto, left, right, baseline, last baseline;
1348 if (alignment == StyleAlignFlags::FLEX_START) {
1349 alignment = isAxisReversed ? StyleAlignFlags::END : StyleAlignFlags::START;
1350 } else if (alignment == StyleAlignFlags::FLEX_END) {
1351 alignment = isAxisReversed ? StyleAlignFlags::START : StyleAlignFlags::END;
1352 } else if (alignment == StyleAlignFlags::LEFT ||
1353 alignment == StyleAlignFlags::RIGHT) {
1354 MOZ_ASSERT(isMainAxis, "Only justify-* can have 'left' and 'right'!");
1355 alignment = axisTracker.ResolveJustifyLeftRight(alignment);
1356 } else if (alignment == StyleAlignFlags::BASELINE) {
1357 alignment = StyleAlignFlags::START;
1358 } else if (alignment == StyleAlignFlags::LAST_BASELINE) {
1359 alignment = StyleAlignFlags::END;
1362 MOZ_ASSERT(alignment != StyleAlignFlags::STRETCH,
1363 "We should've converted 'stretch' to the fallback alignment!");
1364 MOZ_ASSERT(alignment != StyleAlignFlags::FLEX_START &&
1365 alignment != StyleAlignFlags::FLEX_END,
1366 "nsAbsoluteContainingBlock doesn't know how to handle "
1367 "flex-relative axis for flex containers!");
1369 return (alignment | alignmentFlags);
1372 void nsFlexContainerFrame::GenerateFlexItemForChild(
1373 FlexLine& aLine, nsIFrame* aChildFrame,
1374 const ReflowInput& aParentReflowInput,
1375 const FlexboxAxisTracker& aAxisTracker,
1376 const nscoord aTentativeContentBoxCrossSize) {
1377 const auto flexWM = aAxisTracker.GetWritingMode();
1378 const auto childWM = aChildFrame->GetWritingMode();
1380 // Note: we use GetStyleFrame() to access the sizing & flex properties here.
1381 // This lets us correctly handle table wrapper frames as flex items since
1382 // their inline-size and block-size properties are always 'auto'. In order for
1383 // 'flex-basis:auto' to actually resolve to the author's specified inline-size
1384 // or block-size, we need to dig through to the inner table.
1385 const auto* stylePos =
1386 nsLayoutUtils::GetStyleFrame(aChildFrame)->StylePosition();
1388 // Construct a StyleSizeOverrides for this flex item so that its ReflowInput
1389 // below will use and resolve its flex base size rather than its corresponding
1390 // preferred main size property (only for modern CSS flexbox).
1391 StyleSizeOverrides sizeOverrides;
1392 if (!IsLegacyBox(this)) {
1393 Maybe<StyleSize> styleFlexBaseSize;
1395 // When resolving flex base size, flex items use their 'flex-basis' property
1396 // in place of their preferred main size (e.g. 'width') for sizing purposes,
1397 // *unless* they have 'flex-basis:auto' in which case they use their
1398 // preferred main size after all.
1399 const auto& flexBasis = stylePos->mFlexBasis;
1400 const auto& styleMainSize = stylePos->Size(aAxisTracker.MainAxis(), flexWM);
1401 if (IsUsedFlexBasisContent(flexBasis, styleMainSize)) {
1402 // If we get here, we're resolving the flex base size for a flex item, and
1403 // we fall into the flexbox spec section 9.2 step 3, substep C (if we have
1404 // a definite cross size) or E (if not).
1405 styleFlexBaseSize.emplace(StyleSize::MaxContent());
1406 } else if (flexBasis.IsSize() && !flexBasis.IsAuto()) {
1407 // For all other non-'auto' flex-basis values, we just swap in the
1408 // flex-basis itself for the preferred main-size property.
1409 styleFlexBaseSize.emplace(flexBasis.AsSize());
1410 } else {
1411 // else: flex-basis is 'auto', which is deferring to some explicit value
1412 // in the preferred main size.
1413 MOZ_ASSERT(flexBasis.IsAuto());
1414 styleFlexBaseSize.emplace(styleMainSize);
1417 MOZ_ASSERT(styleFlexBaseSize, "We should've emplace styleFlexBaseSize!");
1419 // Provide the size override for the preferred main size property.
1420 if (aAxisTracker.IsInlineAxisMainAxis(childWM)) {
1421 sizeOverrides.mStyleISize = std::move(styleFlexBaseSize);
1422 } else {
1423 sizeOverrides.mStyleBSize = std::move(styleFlexBaseSize);
1426 // 'flex-basis' should works on the inner table frame for a table flex item,
1427 // just like how 'height' works on a table element.
1428 sizeOverrides.mApplyOverridesVerbatim = true;
1431 // Create temporary reflow input just for sizing -- to get hypothetical
1432 // main-size and the computed values of min / max main-size property.
1433 // (This reflow input will _not_ be used for reflow.)
1434 ReflowInput childRI(PresContext(), aParentReflowInput, aChildFrame,
1435 aParentReflowInput.ComputedSize(childWM), Nothing(), {},
1436 sizeOverrides);
1438 // FLEX GROW & SHRINK WEIGHTS
1439 // --------------------------
1440 float flexGrow, flexShrink;
1441 if (IsLegacyBox(this)) {
1442 flexGrow = flexShrink = aChildFrame->StyleXUL()->mBoxFlex;
1443 } else {
1444 flexGrow = stylePos->mFlexGrow;
1445 flexShrink = stylePos->mFlexShrink;
1448 // MAIN SIZES (flex base size, min/max size)
1449 // -----------------------------------------
1450 const LogicalSize computedSizeInFlexWM = childRI.ComputedSize(flexWM);
1451 const LogicalSize computedMinSizeInFlexWM = childRI.ComputedMinSize(flexWM);
1452 const LogicalSize computedMaxSizeInFlexWM = childRI.ComputedMaxSize(flexWM);
1454 const nscoord flexBaseSize = aAxisTracker.MainComponent(computedSizeInFlexWM);
1455 const nscoord mainMinSize =
1456 aAxisTracker.MainComponent(computedMinSizeInFlexWM);
1457 const nscoord mainMaxSize =
1458 aAxisTracker.MainComponent(computedMaxSizeInFlexWM);
1460 // This is enforced by the ReflowInput where these values come from:
1461 MOZ_ASSERT(mainMinSize <= mainMaxSize, "min size is larger than max size");
1463 // CROSS SIZES (tentative cross size, min/max cross size)
1464 // ------------------------------------------------------
1465 // Grab the cross size from the reflow input. This might be the right value,
1466 // or we might resolve it to something else in SizeItemInCrossAxis(); hence,
1467 // it's tentative. See comment under "Cross Size Determination" for more.
1468 const nscoord tentativeCrossSize =
1469 aAxisTracker.CrossComponent(computedSizeInFlexWM);
1470 const nscoord crossMinSize =
1471 aAxisTracker.CrossComponent(computedMinSizeInFlexWM);
1472 const nscoord crossMaxSize =
1473 aAxisTracker.CrossComponent(computedMaxSizeInFlexWM);
1475 // Construct the flex item!
1476 FlexItem& item = *aLine.Items().EmplaceBack(
1477 childRI, flexGrow, flexShrink, flexBaseSize, mainMinSize, mainMaxSize,
1478 tentativeCrossSize, crossMinSize, crossMaxSize, aAxisTracker);
1480 // We may be about to do computations based on our item's cross-size
1481 // (e.g. using it as a constraint when measuring our content in the
1482 // main axis, or using it with the preferred aspect ratio to obtain a main
1483 // size). BEFORE WE DO THAT, we need let the item "pre-stretch" its cross size
1484 // (if it's got 'align-self:stretch'), for a certain case where the spec says
1485 // the stretched cross size is considered "definite". That case is if we
1486 // have a single-line (nowrap) flex container which itself has a definite
1487 // cross-size. Otherwise, we'll wait to do stretching, since (in other
1488 // cases) we don't know how much the item should stretch yet.
1489 const bool isSingleLine =
1490 StyleFlexWrap::Nowrap == aParentReflowInput.mStylePosition->mFlexWrap;
1491 if (isSingleLine) {
1492 // Is container's cross size "definite"?
1493 // - If it's column-oriented, then "yes", because its cross size is its
1494 // inline-size which is always definite from its descendants' perspective.
1495 // - Otherwise (if it's row-oriented), then we check the actual size
1496 // and call it definite if it's not NS_UNCONSTRAINEDSIZE.
1497 if (aAxisTracker.IsColumnOriented() ||
1498 aTentativeContentBoxCrossSize != NS_UNCONSTRAINEDSIZE) {
1499 // Container's cross size is "definite", so we can resolve the item's
1500 // stretched cross size using that.
1501 item.ResolveStretchedCrossSize(aTentativeContentBoxCrossSize);
1505 // Before thinking about freezing the item at its base size, we need to give
1506 // it a chance to recalculate the base size from its cross size and aspect
1507 // ratio (since its cross size might've *just* now become definite due to
1508 // 'stretch' above)
1509 item.ResolveFlexBaseSizeFromAspectRatio(childRI);
1511 // If we're inflexible, we can just freeze to our hypothetical main-size
1512 // up-front.
1513 if (flexGrow == 0.0f && flexShrink == 0.0f) {
1514 item.Freeze();
1515 if (flexBaseSize < mainMinSize) {
1516 item.SetWasMinClamped();
1517 } else if (flexBaseSize > mainMaxSize) {
1518 item.SetWasMaxClamped();
1522 // Resolve "flex-basis:auto" and/or "min-[width|height]:auto" (which might
1523 // require us to reflow the item to measure content height)
1524 ResolveAutoFlexBasisAndMinSize(item, childRI, aAxisTracker);
1527 // Static helper-functions for ResolveAutoFlexBasisAndMinSize():
1528 // -------------------------------------------------------------
1529 // Partially resolves "min-[width|height]:auto" and returns the resulting value.
1530 // By "partially", I mean we don't consider the min-content size (but we do
1531 // consider the main-size and main max-size properties, and the preferred aspect
1532 // ratio). The caller is responsible for computing & considering the min-content
1533 // size in combination with the partially-resolved value that this function
1534 // returns.
1536 // Basically, this function gets the specified size suggestion; if not, the
1537 // transferred size suggestion; if both sizes do not exist, return nscoord_MAX.
1539 // Spec reference: https://drafts.csswg.org/css-flexbox-1/#min-size-auto
1540 static nscoord PartiallyResolveAutoMinSize(
1541 const FlexItem& aFlexItem, const ReflowInput& aItemReflowInput,
1542 const FlexboxAxisTracker& aAxisTracker) {
1543 MOZ_ASSERT(aFlexItem.NeedsMinSizeAutoResolution(),
1544 "only call for FlexItems that need min-size auto resolution");
1546 const auto itemWM = aFlexItem.GetWritingMode();
1547 const auto cbWM = aAxisTracker.GetWritingMode();
1548 const auto& mainStyleSize =
1549 aItemReflowInput.mStylePosition->Size(aAxisTracker.MainAxis(), cbWM);
1550 const auto& maxMainStyleSize =
1551 aItemReflowInput.mStylePosition->MaxSize(aAxisTracker.MainAxis(), cbWM);
1552 const auto boxSizingAdjust =
1553 aItemReflowInput.mStylePosition->mBoxSizing == StyleBoxSizing::Border
1554 ? aFlexItem.BorderPadding().Size(cbWM)
1555 : LogicalSize(cbWM);
1557 // If this flex item is a compressible replaced element list in CSS Sizing 3
1558 // §5.2.2, CSS Sizing 3 §5.2.1c requires us to resolve the percentage part of
1559 // the preferred main size property against zero, yielding a definite
1560 // specified size suggestion. Here we can use a zero percentage basis to
1561 // fulfill this requirement.
1562 const auto percentBasis =
1563 aFlexItem.Frame()->IsPercentageResolvedAgainstZero(mainStyleSize,
1564 maxMainStyleSize)
1565 ? LogicalSize(cbWM, 0, 0)
1566 : aItemReflowInput.mContainingBlockSize.ConvertTo(cbWM, itemWM);
1568 // Compute the specified size suggestion, which is the main-size property if
1569 // it's definite.
1570 nscoord specifiedSizeSuggestion = nscoord_MAX;
1572 if (aAxisTracker.IsRowOriented()) {
1573 if (mainStyleSize.IsLengthPercentage()) {
1574 // NOTE: We ignore extremum inline-size. This is OK because the caller is
1575 // responsible for computing the min-content inline-size and min()'ing it
1576 // with the value we return.
1577 specifiedSizeSuggestion = aFlexItem.Frame()->ComputeISizeValue(
1578 cbWM, percentBasis, boxSizingAdjust,
1579 mainStyleSize.AsLengthPercentage());
1581 } else {
1582 if (!nsLayoutUtils::IsAutoBSize(mainStyleSize, percentBasis.BSize(cbWM))) {
1583 // NOTE: We ignore auto and extremum block-size. This is OK because the
1584 // caller is responsible for computing the min-content block-size and
1585 // min()'ing it with the value we return.
1586 specifiedSizeSuggestion = nsLayoutUtils::ComputeBSizeValue(
1587 percentBasis.BSize(cbWM), boxSizingAdjust.BSize(cbWM),
1588 mainStyleSize.AsLengthPercentage());
1592 if (specifiedSizeSuggestion != nscoord_MAX) {
1593 // We have the specified size suggestion. Return it now since we don't need
1594 // to consider transferred size suggestion.
1595 FLEX_LOGV(" Specified size suggestion: %d", specifiedSizeSuggestion);
1596 return specifiedSizeSuggestion;
1599 // Compute the transferred size suggestion, which is the cross size converted
1600 // through the aspect ratio (if the item is replaced, and it has an aspect
1601 // ratio and a definite cross size).
1602 if (const auto& aspectRatio = aFlexItem.GetAspectRatio();
1603 aFlexItem.Frame()->IsReplaced() && aspectRatio &&
1604 aFlexItem.IsCrossSizeDefinite(aItemReflowInput)) {
1605 // We have a usable aspect ratio. (not going to divide by 0)
1606 nscoord transferredSizeSuggestion = aspectRatio.ComputeRatioDependentSize(
1607 aFlexItem.MainAxis(), cbWM, aFlexItem.CrossSize(), boxSizingAdjust);
1609 // Clamp the transferred size suggestion by any definite min and max
1610 // cross size converted through the aspect ratio.
1611 transferredSizeSuggestion = aFlexItem.ClampMainSizeViaCrossAxisConstraints(
1612 transferredSizeSuggestion, aItemReflowInput);
1614 FLEX_LOGV(" Transferred size suggestion: %d", transferredSizeSuggestion);
1615 return transferredSizeSuggestion;
1618 return nscoord_MAX;
1621 // Note: If & when we handle "min-height: min-content" for flex items,
1622 // we may want to resolve that in this function, too.
1623 void nsFlexContainerFrame::ResolveAutoFlexBasisAndMinSize(
1624 FlexItem& aFlexItem, const ReflowInput& aItemReflowInput,
1625 const FlexboxAxisTracker& aAxisTracker) {
1626 // (Note: We can guarantee that the flex-basis will have already been
1627 // resolved if the main axis is the same as the item's inline
1628 // axis. Inline-axis values should always be resolvable without reflow.)
1629 const bool isMainSizeAuto =
1630 (!aFlexItem.IsInlineAxisMainAxis() &&
1631 NS_UNCONSTRAINEDSIZE == aFlexItem.FlexBaseSize());
1633 const bool isMainMinSizeAuto = aFlexItem.NeedsMinSizeAutoResolution();
1635 if (!isMainSizeAuto && !isMainMinSizeAuto) {
1636 // Nothing to do; this function is only needed for flex items
1637 // with a used flex-basis of "auto" or a min-main-size of "auto".
1638 return;
1641 FLEX_LOGV("Resolving auto main size or auto min main size for flex item %p",
1642 aFlexItem.Frame());
1644 nscoord resolvedMinSize; // (only set/used if isMainMinSizeAuto==true)
1645 bool minSizeNeedsToMeasureContent = false; // assume the best
1646 if (isMainMinSizeAuto) {
1647 // Resolve the min-size, except for considering the min-content size.
1648 // (We'll consider that later, if we need to.)
1649 resolvedMinSize =
1650 PartiallyResolveAutoMinSize(aFlexItem, aItemReflowInput, aAxisTracker);
1651 if (resolvedMinSize > 0) {
1652 // If resolvedMinSize were already at 0, we could skip calculating content
1653 // size suggestion because it can't go any lower.
1654 minSizeNeedsToMeasureContent = true;
1658 const bool flexBasisNeedsToMeasureContent = isMainSizeAuto;
1660 // Measure content, if needed (w/ intrinsic-width method or a reflow)
1661 if (minSizeNeedsToMeasureContent || flexBasisNeedsToMeasureContent) {
1662 // Compute the content size suggestion, which is the min-content size in the
1663 // main axis.
1664 nscoord contentSizeSuggestion = nscoord_MAX;
1666 if (aFlexItem.IsInlineAxisMainAxis()) {
1667 if (minSizeNeedsToMeasureContent) {
1668 // Compute the flex item's content size suggestion, which is the
1669 // 'min-content' size on the main axis.
1670 // https://drafts.csswg.org/css-flexbox-1/#content-size-suggestion
1671 const auto cbWM = aAxisTracker.GetWritingMode();
1672 const auto itemWM = aFlexItem.GetWritingMode();
1673 const nscoord availISize = 0; // for min-content size
1674 StyleSizeOverrides sizeOverrides;
1675 sizeOverrides.mStyleISize.emplace(StyleSize::Auto());
1676 const auto sizeInItemWM = aFlexItem.Frame()->ComputeSize(
1677 aItemReflowInput.mRenderingContext, itemWM,
1678 aItemReflowInput.mContainingBlockSize, availISize,
1679 aItemReflowInput.ComputedLogicalMargin(itemWM).Size(itemWM),
1680 aItemReflowInput.ComputedLogicalBorderPadding(itemWM).Size(itemWM),
1681 sizeOverrides, {ComputeSizeFlag::ShrinkWrap});
1683 contentSizeSuggestion = aAxisTracker.MainComponent(
1684 sizeInItemWM.mLogicalSize.ConvertTo(cbWM, itemWM));
1686 NS_ASSERTION(!flexBasisNeedsToMeasureContent,
1687 "flex-basis:auto should have been resolved in the "
1688 "reflow input, for horizontal flexbox. It shouldn't need "
1689 "special handling here");
1690 } else {
1691 // If this item is flexible (in its block axis)...
1692 // OR if we're measuring its 'auto' min-BSize, with its main-size (in its
1693 // block axis) being something non-"auto"...
1694 // THEN: we assume that the computed BSize that we're reflowing with now
1695 // could be different from the one we'll use for this flex item's
1696 // "actual" reflow later on. In that case, we need to be sure the flex
1697 // item treats this as a block-axis resize (regardless of whether there
1698 // are actually any ancestors being resized in that axis).
1699 // (Note: We don't have to do this for the inline axis, because
1700 // InitResizeFlags will always turn on mIsIResize on when it sees that
1701 // the computed ISize is different from current ISize, and that's all we
1702 // need.)
1703 bool forceBResizeForMeasuringReflow =
1704 !aFlexItem.IsFrozen() || // Is the item flexible?
1705 !flexBasisNeedsToMeasureContent; // Are we *only* measuring it for
1706 // 'min-block-size:auto'?
1708 const ReflowInput& flexContainerRI = *aItemReflowInput.mParentReflowInput;
1709 nscoord contentBSize = MeasureFlexItemContentBSize(
1710 aFlexItem, forceBResizeForMeasuringReflow, flexContainerRI);
1711 if (minSizeNeedsToMeasureContent) {
1712 contentSizeSuggestion = contentBSize;
1714 if (flexBasisNeedsToMeasureContent) {
1715 aFlexItem.SetFlexBaseSizeAndMainSize(contentBSize);
1716 aFlexItem.SetIsFlexBaseSizeContentBSize();
1720 if (minSizeNeedsToMeasureContent) {
1721 // Clamp the content size suggestion by any definite min and max cross
1722 // size converted through the aspect ratio.
1723 if (aFlexItem.HasAspectRatio()) {
1724 contentSizeSuggestion = aFlexItem.ClampMainSizeViaCrossAxisConstraints(
1725 contentSizeSuggestion, aItemReflowInput);
1728 FLEX_LOGV(" Content size suggestion: %d", contentSizeSuggestion);
1729 resolvedMinSize = std::min(resolvedMinSize, contentSizeSuggestion);
1731 // Clamp the resolved min main size by the max main size if it's definite.
1732 if (aFlexItem.MainMaxSize() != NS_UNCONSTRAINEDSIZE) {
1733 resolvedMinSize = std::min(resolvedMinSize, aFlexItem.MainMaxSize());
1734 } else if (MOZ_UNLIKELY(resolvedMinSize > nscoord_MAX)) {
1735 NS_WARNING("Bogus resolved auto min main size!");
1736 // Our resolved min-size is bogus, probably due to some huge sizes in
1737 // the content. Clamp it to the valid nscoord range, so that we can at
1738 // least depend on it being <= the max-size (which is also the
1739 // nscoord_MAX sentinel value if we reach this point).
1740 resolvedMinSize = nscoord_MAX;
1742 FLEX_LOGV(" Resolved auto min main size: %d", resolvedMinSize);
1744 if (resolvedMinSize == contentSizeSuggestion) {
1745 // When we are here, we've measured the item's content-based size, and
1746 // we used it as the resolved auto min main size. Record the fact so
1747 // that we can use it to determine whether we allow a flex item to grow
1748 // its block-size in ReflowFlexItem().
1749 aFlexItem.SetIsMainMinSizeContentBSize();
1754 if (isMainMinSizeAuto) {
1755 aFlexItem.UpdateMainMinSize(resolvedMinSize);
1760 * A cached result for a flex item's block-axis measuring reflow. This cache
1761 * prevents us from doing exponential reflows in cases of deeply nested flex
1762 * and scroll frames.
1764 * We store the cached value in the flex item's frame property table, for
1765 * simplicity.
1767 * Right now, we cache the following as a "key", from the item's ReflowInput:
1768 * - its ComputedSize
1769 * - its min/max block size (in case its ComputedBSize is unconstrained)
1770 * - its AvailableBSize
1771 * ...and we cache the following as the "value", from the item's ReflowOutput:
1772 * - its final content-box BSize
1774 * The assumption here is that a given flex item measurement from our "value"
1775 * won't change unless one of the pieces of the "key" change, or the flex
1776 * item's intrinsic size is marked as dirty (due to a style or DOM change).
1777 * (The latter will cause the cached value to be discarded, in
1778 * nsIFrame::MarkIntrinsicISizesDirty.)
1780 * Note that the components of "Key" (mComputed{MinB,MaxB,}Size and
1781 * mAvailableBSize) are sufficient to catch any changes to the flex container's
1782 * size that the item may care about for its measuring reflow. Specifically:
1783 * - If the item cares about the container's size (e.g. if it has a percent
1784 * height and the container's height changes, in a horizontal-WM container)
1785 * then that'll be detectable via the item's ReflowInput's "ComputedSize()"
1786 * differing from the value in our Key. And the same applies for the
1787 * inline axis.
1788 * - If the item is fragmentable (pending bug 939897) and its measured BSize
1789 * depends on where it gets fragmented, then that sort of change can be
1790 * detected due to the item's ReflowInput's "AvailableBSize()" differing
1791 * from the value in our Key.
1793 * One particular case to consider (& need to be sure not to break when
1794 * changing this class): the flex item's computed BSize may change between
1795 * measuring reflows due to how the mIsFlexContainerMeasuringBSize flag affects
1796 * size computation (see bug 1336708). This is one reason we need to use the
1797 * computed BSize as part of the key.
1799 class nsFlexContainerFrame::CachedBAxisMeasurement {
1800 struct Key {
1801 const LogicalSize mComputedSize;
1802 const nscoord mComputedMinBSize;
1803 const nscoord mComputedMaxBSize;
1804 const nscoord mAvailableBSize;
1806 explicit Key(const ReflowInput& aRI)
1807 : mComputedSize(aRI.ComputedSize()),
1808 mComputedMinBSize(aRI.ComputedMinBSize()),
1809 mComputedMaxBSize(aRI.ComputedMaxBSize()),
1810 mAvailableBSize(aRI.AvailableBSize()) {}
1812 bool operator==(const Key& aOther) const {
1813 return mComputedSize == aOther.mComputedSize &&
1814 mComputedMinBSize == aOther.mComputedMinBSize &&
1815 mComputedMaxBSize == aOther.mComputedMaxBSize &&
1816 mAvailableBSize == aOther.mAvailableBSize;
1820 const Key mKey;
1822 // This could/should be const, but it's non-const for now just because it's
1823 // assigned via a series of steps in the constructor body:
1824 nscoord mBSize;
1826 public:
1827 CachedBAxisMeasurement(const ReflowInput& aReflowInput,
1828 const ReflowOutput& aReflowOutput)
1829 : mKey(aReflowInput) {
1830 // To get content-box bsize, we have to subtract off border & padding
1831 // (and floor at 0 in case the border/padding are too large):
1832 WritingMode itemWM = aReflowInput.GetWritingMode();
1833 nscoord borderBoxBSize = aReflowOutput.BSize(itemWM);
1834 mBSize =
1835 borderBoxBSize -
1836 aReflowInput.ComputedLogicalBorderPadding(itemWM).BStartEnd(itemWM);
1837 mBSize = std::max(0, mBSize);
1841 * Returns true if this cached flex item measurement is valid for (i.e. can
1842 * be expected to match the output of) a measuring reflow whose input
1843 * parameters are given via aReflowInput.
1845 bool IsValidFor(const ReflowInput& aReflowInput) const {
1846 return mKey == Key(aReflowInput);
1849 nscoord BSize() const { return mBSize; }
1853 * A cached copy of various metrics from a flex item's most recent final reflow.
1854 * It can be used to determine whether we can optimize away the flex item's
1855 * final reflow, when we perform an incremental reflow of its flex container.
1857 class CachedFinalReflowMetrics final {
1858 public:
1859 CachedFinalReflowMetrics(const ReflowInput& aReflowInput,
1860 const ReflowOutput& aReflowOutput)
1861 : CachedFinalReflowMetrics(aReflowInput.GetWritingMode(), aReflowInput,
1862 aReflowOutput) {}
1864 CachedFinalReflowMetrics(const FlexItem& aItem, const LogicalSize& aSize)
1865 : mBorderPadding(aItem.BorderPadding().ConvertTo(
1866 aItem.GetWritingMode(), aItem.ContainingBlockWM())),
1867 mSize(aSize),
1868 mTreatBSizeAsIndefinite(aItem.TreatBSizeAsIndefinite()) {}
1870 const LogicalSize& Size() const { return mSize; }
1871 const LogicalMargin& BorderPadding() const { return mBorderPadding; }
1872 bool TreatBSizeAsIndefinite() const { return mTreatBSizeAsIndefinite; }
1874 private:
1875 // A convenience constructor with a WritingMode argument.
1876 CachedFinalReflowMetrics(WritingMode aWM, const ReflowInput& aReflowInput,
1877 const ReflowOutput& aReflowOutput)
1878 : mBorderPadding(aReflowInput.ComputedLogicalBorderPadding(aWM)),
1879 mSize(aReflowOutput.Size(aWM) - mBorderPadding.Size(aWM)),
1880 mTreatBSizeAsIndefinite(aReflowInput.mFlags.mTreatBSizeAsIndefinite) {}
1882 // The flex item's border and padding, in its own writing-mode, that it used
1883 // used during its most recent "final reflow".
1884 LogicalMargin mBorderPadding;
1886 // The flex item's content-box size, in its own writing-mode, that it used
1887 // during its most recent "final reflow".
1888 LogicalSize mSize;
1890 // True if the flex item's BSize was considered "indefinite" in its most
1891 // recent "final reflow". (For a flex item "final reflow", this is fully
1892 // determined by the mTreatBSizeAsIndefinite flag in ReflowInput. See the
1893 // flag's documentation for more information.)
1894 bool mTreatBSizeAsIndefinite;
1898 * When we instantiate/update a CachedFlexItemData, this enum must be used to
1899 * indicate the sort of reflow whose results we're capturing. This impacts
1900 * what we cache & how we use the cached information.
1902 enum class FlexItemReflowType {
1903 // A reflow to measure the block-axis size of a flex item (as an input to the
1904 // flex layout algorithm).
1905 Measuring,
1907 // A reflow with the flex item's "final" size at the end of the flex layout
1908 // algorithm.
1909 Final,
1913 * This class stores information about the conditions and results for the most
1914 * recent ReflowChild call that we made on a given flex item. This information
1915 * helps us reason about whether we can assume that a subsequent ReflowChild()
1916 * invocation is unnecessary & skippable.
1918 class nsFlexContainerFrame::CachedFlexItemData {
1919 public:
1920 CachedFlexItemData(const ReflowInput& aReflowInput,
1921 const ReflowOutput& aReflowOutput,
1922 FlexItemReflowType aType) {
1923 Update(aReflowInput, aReflowOutput, aType);
1926 // This method is intended to be called after we perform either a "measuring
1927 // reflow" or a "final reflow" for a given flex item.
1928 void Update(const ReflowInput& aReflowInput,
1929 const ReflowOutput& aReflowOutput, FlexItemReflowType aType) {
1930 if (aType == FlexItemReflowType::Measuring) {
1931 mBAxisMeasurement.reset();
1932 mBAxisMeasurement.emplace(aReflowInput, aReflowOutput);
1933 // Clear any cached "last final reflow metrics", too, because now the most
1934 // recent reflow was *not* a "final reflow".
1935 mFinalReflowMetrics.reset();
1936 return;
1939 MOZ_ASSERT(aType == FlexItemReflowType::Final);
1940 mFinalReflowMetrics.reset();
1941 mFinalReflowMetrics.emplace(aReflowInput, aReflowOutput);
1944 // This method is intended to be called for situations where we decide to
1945 // skip a final reflow because we've just done a measuring reflow which left
1946 // us (and our descendants) with the correct sizes. In this scenario, we
1947 // still want to cache the size as if we did a final reflow (because we've
1948 // determined that the recent measuring reflow was sufficient). That way,
1949 // our flex container can still skip a final reflow for this item in the
1950 // future as long as conditions are right.
1951 void Update(const FlexItem& aItem, const LogicalSize& aSize) {
1952 MOZ_ASSERT(!mFinalReflowMetrics,
1953 "This version of the method is only intended to be called when "
1954 "the most recent reflow was a 'measuring reflow'; and that "
1955 "should have cleared out mFinalReflowMetrics");
1957 mFinalReflowMetrics.reset(); // Just in case this assert^ fails.
1958 mFinalReflowMetrics.emplace(aItem, aSize);
1961 // If the flex container needs a measuring reflow for the flex item, then the
1962 // resulting block-axis measurements can be cached here. If no measurement
1963 // has been needed so far, then this member will be Nothing().
1964 Maybe<CachedBAxisMeasurement> mBAxisMeasurement;
1966 // The metrics that the corresponding flex item used in its most recent
1967 // "final reflow". (Note: the assumption here is that this reflow was this
1968 // item's most recent reflow of any type. If the item ends up undergoing a
1969 // subsequent measuring reflow, then this value needs to be cleared, because
1970 // at that point it's no longer an accurate way of reasoning about the
1971 // current state of the frame tree.)
1972 Maybe<CachedFinalReflowMetrics> mFinalReflowMetrics;
1974 // Instances of this class are stored under this frame property, on
1975 // frames that are flex items:
1976 NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, CachedFlexItemData)
1979 void nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(
1980 nsIFrame* aItemFrame) {
1981 MOZ_ASSERT(aItemFrame->IsFlexItem());
1982 if (auto* cache = aItemFrame->GetProperty(CachedFlexItemData::Prop())) {
1983 cache->mBAxisMeasurement.reset();
1984 cache->mFinalReflowMetrics.reset();
1988 const CachedBAxisMeasurement& nsFlexContainerFrame::MeasureBSizeForFlexItem(
1989 FlexItem& aItem, ReflowInput& aChildReflowInput) {
1990 auto* cachedData = aItem.Frame()->GetProperty(CachedFlexItemData::Prop());
1992 if (cachedData && cachedData->mBAxisMeasurement) {
1993 if (!aItem.Frame()->IsSubtreeDirty() &&
1994 cachedData->mBAxisMeasurement->IsValidFor(aChildReflowInput)) {
1995 FLEX_LOG("[perf] MeasureBSizeForFlexItem accepted cached value");
1996 return *(cachedData->mBAxisMeasurement);
1998 FLEX_LOG("[perf] MeasureBSizeForFlexItem rejected cached value");
1999 } else {
2000 FLEX_LOG("[perf] MeasureBSizeForFlexItem didn't have a cached value");
2003 // CachedFlexItemData is stored in item's writing mode, so we pass
2004 // aChildReflowInput into ReflowOutput's constructor.
2005 ReflowOutput childReflowOutput(aChildReflowInput);
2006 nsReflowStatus childReflowStatus;
2008 const ReflowChildFlags flags = ReflowChildFlags::NoMoveFrame;
2009 const WritingMode outerWM = GetWritingMode();
2010 const LogicalPoint dummyPosition(outerWM);
2011 const nsSize dummyContainerSize;
2013 // We use NoMoveFrame, so the position and container size used here are
2014 // unimportant.
2015 ReflowChild(aItem.Frame(), PresContext(), childReflowOutput,
2016 aChildReflowInput, outerWM, dummyPosition, dummyContainerSize,
2017 flags, childReflowStatus);
2018 aItem.SetHadMeasuringReflow();
2020 // We always use unconstrained available block-size to measure flex items,
2021 // which means they should always complete.
2022 MOZ_ASSERT(childReflowStatus.IsComplete(),
2023 "We gave flex item unconstrained available block-size, so it "
2024 "should be complete");
2026 // Tell the child we're done with its initial reflow.
2027 // (Necessary for e.g. GetBaseline() to work below w/out asserting)
2028 FinishReflowChild(aItem.Frame(), PresContext(), childReflowOutput,
2029 &aChildReflowInput, outerWM, dummyPosition,
2030 dummyContainerSize, flags);
2032 aItem.SetAscent(childReflowOutput.BlockStartAscent());
2034 // Update (or add) our cached measurement, so that we can hopefully skip this
2035 // measuring reflow the next time around:
2036 if (cachedData) {
2037 cachedData->Update(aChildReflowInput, childReflowOutput,
2038 FlexItemReflowType::Measuring);
2039 } else {
2040 cachedData = new CachedFlexItemData(aChildReflowInput, childReflowOutput,
2041 FlexItemReflowType::Measuring);
2042 aItem.Frame()->SetProperty(CachedFlexItemData::Prop(), cachedData);
2044 return *(cachedData->mBAxisMeasurement);
2047 /* virtual */
2048 void nsFlexContainerFrame::MarkIntrinsicISizesDirty() {
2049 mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
2050 mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
2052 nsContainerFrame::MarkIntrinsicISizesDirty();
2055 nscoord nsFlexContainerFrame::MeasureFlexItemContentBSize(
2056 FlexItem& aFlexItem, bool aForceBResizeForMeasuringReflow,
2057 const ReflowInput& aParentReflowInput) {
2058 FLEX_LOG("Measuring flex item's content block-size");
2060 // Set up a reflow input for measuring the flex item's content block-size:
2061 WritingMode wm = aFlexItem.Frame()->GetWritingMode();
2062 LogicalSize availSize = aParentReflowInput.ComputedSize(wm);
2063 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
2065 StyleSizeOverrides sizeOverrides;
2066 if (aFlexItem.IsStretched()) {
2067 sizeOverrides.mStyleISize.emplace(aFlexItem.StyleCrossSize());
2068 // Suppress any AspectRatio that we might have to prevent ComputeSize() from
2069 // transferring our inline-size override through the aspect-ratio to set the
2070 // block-size, because that would prevent us from measuring the content
2071 // block-size.
2072 sizeOverrides.mAspectRatio.emplace(AspectRatio());
2073 FLEX_LOGV(" Cross size override: %d", aFlexItem.CrossSize());
2075 sizeOverrides.mStyleBSize.emplace(StyleSize::Auto());
2077 ReflowInput childRIForMeasuringBSize(
2078 PresContext(), aParentReflowInput, aFlexItem.Frame(), availSize,
2079 Nothing(), ReflowInput::InitFlag::CallerWillInit, sizeOverrides);
2080 childRIForMeasuringBSize.Init(PresContext());
2082 // When measuring flex item's content block-size, disregard the item's
2083 // min-block-size and max-block-size by resetting both to to their
2084 // unconstraining (extreme) values. The flexbox layout algorithm does still
2085 // explicitly clamp both sizes when resolving the target main size.
2086 childRIForMeasuringBSize.SetComputedMinBSize(0);
2087 childRIForMeasuringBSize.SetComputedMaxBSize(NS_UNCONSTRAINEDSIZE);
2089 if (aForceBResizeForMeasuringReflow) {
2090 childRIForMeasuringBSize.SetBResize(true);
2091 // Not 100% sure this is needed, but be conservative for now:
2092 childRIForMeasuringBSize.mFlags.mIsBResizeForPercentages = true;
2095 const CachedBAxisMeasurement& measurement =
2096 MeasureBSizeForFlexItem(aFlexItem, childRIForMeasuringBSize);
2098 return measurement.BSize();
2101 FlexItem::FlexItem(ReflowInput& aFlexItemReflowInput, float aFlexGrow,
2102 float aFlexShrink, nscoord aFlexBaseSize,
2103 nscoord aMainMinSize, nscoord aMainMaxSize,
2104 nscoord aTentativeCrossSize, nscoord aCrossMinSize,
2105 nscoord aCrossMaxSize,
2106 const FlexboxAxisTracker& aAxisTracker)
2107 : mFrame(aFlexItemReflowInput.mFrame),
2108 mFlexGrow(aFlexGrow),
2109 mFlexShrink(aFlexShrink),
2110 mAspectRatio(mFrame->GetAspectRatio()),
2111 mWM(aFlexItemReflowInput.GetWritingMode()),
2112 mCBWM(aAxisTracker.GetWritingMode()),
2113 mMainAxis(aAxisTracker.MainAxis()),
2114 mBorderPadding(aFlexItemReflowInput.ComputedLogicalBorderPadding(mCBWM)),
2115 mMargin(aFlexItemReflowInput.ComputedLogicalMargin(mCBWM)),
2116 mMainMinSize(aMainMinSize),
2117 mMainMaxSize(aMainMaxSize),
2118 mCrossMinSize(aCrossMinSize),
2119 mCrossMaxSize(aCrossMaxSize),
2120 mCrossSize(aTentativeCrossSize),
2121 mIsInlineAxisMainAxis(aAxisTracker.IsInlineAxisMainAxis(mWM)),
2122 mNeedsMinSizeAutoResolution(IsMinSizeAutoResolutionNeeded())
2123 // mAlignSelf, mHasAnyAutoMargin see below
2125 MOZ_ASSERT(mFrame, "expecting a non-null child frame");
2126 MOZ_ASSERT(!mFrame->IsPlaceholderFrame(),
2127 "placeholder frames should not be treated as flex items");
2128 MOZ_ASSERT(!mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
2129 "out-of-flow frames should not be treated as flex items");
2130 MOZ_ASSERT(mIsInlineAxisMainAxis ==
2131 nsFlexContainerFrame::IsItemInlineAxisMainAxis(mFrame),
2132 "public API should be consistent with internal state (about "
2133 "whether flex item's inline axis is flex container's main axis)");
2135 const ReflowInput* containerRS = aFlexItemReflowInput.mParentReflowInput;
2136 if (IsLegacyBox(containerRS->mFrame)) {
2137 // For -webkit-{inline-}box and -moz-{inline-}box, we need to:
2138 // (1) Use prefixed "box-align" instead of "align-items" to determine the
2139 // container's cross-axis alignment behavior.
2140 // (2) Suppress the ability for flex items to override that with their own
2141 // cross-axis alignment. (The legacy box model doesn't support this.)
2142 // So, each FlexItem simply copies the container's converted "align-items"
2143 // value and disregards their own "align-self" property.
2144 const nsStyleXUL* containerStyleXUL = containerRS->mFrame->StyleXUL();
2145 mAlignSelf = {ConvertLegacyStyleToAlignItems(containerStyleXUL)};
2146 mAlignSelfFlags = {0};
2147 } else {
2148 mAlignSelf = aFlexItemReflowInput.mStylePosition->UsedAlignSelf(
2149 containerRS->mFrame->Style());
2150 if (MOZ_LIKELY(mAlignSelf._0 == StyleAlignFlags::NORMAL)) {
2151 mAlignSelf = {StyleAlignFlags::STRETCH};
2154 // Store and strip off the <overflow-position> bits
2155 mAlignSelfFlags = mAlignSelf._0 & StyleAlignFlags::FLAG_BITS;
2156 mAlignSelf._0 &= ~StyleAlignFlags::FLAG_BITS;
2159 // Our main-size is considered definite if any of these are true:
2160 // (a) main axis is the item's inline axis.
2161 // (b) flex container has definite main size.
2162 // (c) flex item has a definite flex basis.
2164 // Hence, we need to take care to treat the final main-size as *indefinite*
2165 // if none of these conditions are satisfied.
2166 if (mIsInlineAxisMainAxis) {
2167 // The item's block-axis is the flex container's cross axis. We don't need
2168 // any special handling to treat cross sizes as indefinite, because the
2169 // cases where we stomp on the cross size with a definite value are all...
2170 // - situations where the spec requires us to treat the cross size as
2171 // definite; specifically, `align-self:stretch` whose cross size is
2172 // definite.
2173 // - situations where definiteness doesn't matter (e.g. for an element with
2174 // an aspect ratio, which for now are all leaf nodes and hence
2175 // can't have any percent-height descendants that would care about the
2176 // definiteness of its size. (Once bug 1528375 is fixed, we might need to
2177 // be more careful about definite vs. indefinite sizing on flex items with
2178 // aspect ratios.)
2179 mTreatBSizeAsIndefinite = false;
2180 } else {
2181 // The item's block-axis is the flex container's main axis. So, the flex
2182 // item's main size is its BSize, and is considered definite under certain
2183 // conditions laid out for definite flex-item main-sizes in the spec.
2184 if (aAxisTracker.IsRowOriented() ||
2185 (containerRS->ComputedBSize() != NS_UNCONSTRAINEDSIZE &&
2186 !containerRS->mFlags.mTreatBSizeAsIndefinite)) {
2187 // The flex *container* has a definite main-size (either by being
2188 // row-oriented [and using its own inline size which is by definition
2189 // definite, or by being column-oriented and having a definite
2190 // block-size). The spec says this means all of the flex items'
2191 // post-flexing main sizes should *also* be treated as definite.
2192 mTreatBSizeAsIndefinite = false;
2193 } else if (aFlexBaseSize != NS_UNCONSTRAINEDSIZE) {
2194 // The flex item has a definite flex basis, which we'll treat as making
2195 // its main-size definite.
2196 mTreatBSizeAsIndefinite = false;
2197 } else {
2198 // Otherwise, we have to treat the item's BSize as indefinite.
2199 mTreatBSizeAsIndefinite = true;
2203 SetFlexBaseSizeAndMainSize(aFlexBaseSize);
2205 const nsStyleMargin* styleMargin = aFlexItemReflowInput.mStyleMargin;
2206 mHasAnyAutoMargin = styleMargin->HasInlineAxisAuto(mCBWM) ||
2207 styleMargin->HasBlockAxisAuto(mCBWM);
2209 // Assert that any "auto" margin components are set to 0.
2210 // (We'll resolve them later; until then, we want to treat them as 0-sized.)
2211 #ifdef DEBUG
2213 for (const auto side : AllLogicalSides()) {
2214 if (styleMargin->mMargin.Get(mCBWM, side).IsAuto()) {
2215 MOZ_ASSERT(GetMarginComponentForSide(side) == 0,
2216 "Someone else tried to resolve our auto margin");
2220 #endif // DEBUG
2222 if (mAlignSelf._0 == StyleAlignFlags::BASELINE ||
2223 mAlignSelf._0 == StyleAlignFlags::LAST_BASELINE) {
2224 // Check which of the item's baselines we're meant to use (first vs. last)
2225 const bool usingItemFirstBaseline =
2226 (mAlignSelf._0 == StyleAlignFlags::BASELINE);
2227 if (IsBlockAxisCrossAxis()) {
2228 // The flex item wants to be aligned in the cross axis using one of its
2229 // baselines; and the cross axis is the item's block axis, so
2230 // baseline-alignment in that axis makes sense.
2232 // To determine the item's baseline sharing group, we check whether the
2233 // item's block axis has the same vs. opposite flow direction as the
2234 // corresponding LogicalAxis on the flex container. We do this by
2235 // getting the physical side that corresponds to these axes' "logical
2236 // start" sides, and we compare those physical sides to find out if
2237 // they're the same vs. opposite.
2238 mozilla::Side itemBlockStartSide = mWM.PhysicalSide(eLogicalSideBStart);
2240 // (Note: this is *not* the "flex-start" side; rather, it's the *logical*
2241 // i.e. WM-relative block-start or inline-start side.)
2242 mozilla::Side containerStartSideInCrossAxis = mCBWM.PhysicalSide(
2243 MakeLogicalSide(aAxisTracker.CrossAxis(), eLogicalEdgeStart));
2245 // We already know these two Sides (the item's block-start and the
2246 // container's 'logical start' side for its cross axis) are in the same
2247 // physical axis, since we're inside of a check for
2248 // FlexItem::IsBlockAxisCrossAxis(). So these two Sides must be either
2249 // the same physical side or opposite from each other. If the Sides are
2250 // the same, then the flow direction is the same, which means the item's
2251 // {first,last} baseline participates in the {first,last}
2252 // baseline-sharing group in its FlexLine. Otherwise, the flow direction
2253 // is opposite, and so the item's {first,last} baseline participates in
2254 // the opposite i.e. {last,first} baseline-sharing group. This is
2255 // roughly per css-align-3 section 9.2, specifically the definition of
2256 // what makes baseline alignment preferences "compatible".
2257 bool itemBlockAxisFlowDirMatchesContainer =
2258 (itemBlockStartSide == containerStartSideInCrossAxis);
2259 mBaselineSharingGroup =
2260 (itemBlockAxisFlowDirMatchesContainer == usingItemFirstBaseline)
2261 ? BaselineSharingGroup::First
2262 : BaselineSharingGroup::Last;
2263 } else {
2264 // The flex item wants to be aligned in the cross axis using one of its
2265 // baselines, but we cannot get its baseline because the FlexItem's block
2266 // axis is *orthogonal* to the container's cross axis. To handle this, we
2267 // are supposed to synthesize a baseline from the item's border box and
2268 // using that for baseline alignment.
2269 mBaselineSharingGroup = usingItemFirstBaseline
2270 ? BaselineSharingGroup::First
2271 : BaselineSharingGroup::Last;
2276 // Simplified constructor for creating a special "strut" FlexItem, for a child
2277 // with visibility:collapse. The strut has 0 main-size, and it only exists to
2278 // impose a minimum cross size on whichever FlexLine it ends up in.
2279 FlexItem::FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize,
2280 WritingMode aContainerWM,
2281 const FlexboxAxisTracker& aAxisTracker)
2282 : mFrame(aChildFrame),
2283 mWM(aChildFrame->GetWritingMode()),
2284 mCBWM(aContainerWM),
2285 mMainAxis(aAxisTracker.MainAxis()),
2286 mBorderPadding(mCBWM),
2287 mMargin(mCBWM),
2288 mCrossSize(aCrossSize),
2289 // Struts don't do layout, so its WM doesn't matter at this point. So, we
2290 // just share container's WM for simplicity:
2291 mIsFrozen(true),
2292 mIsStrut(true), // (this is the constructor for making struts, after all)
2293 mAlignSelf({StyleAlignFlags::FLEX_START}) {
2294 MOZ_ASSERT(mFrame, "expecting a non-null child frame");
2295 MOZ_ASSERT(mFrame->StyleVisibility()->IsCollapse(),
2296 "Should only make struts for children with 'visibility:collapse'");
2297 MOZ_ASSERT(!mFrame->IsPlaceholderFrame(),
2298 "placeholder frames should not be treated as flex items");
2299 MOZ_ASSERT(!mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
2300 "out-of-flow frames should not be treated as flex items");
2303 bool FlexItem::IsMinSizeAutoResolutionNeeded() const {
2304 // We'll need special behavior for "min-[width|height]:auto" (whichever is in
2305 // the flex container's main axis) iff:
2306 // (a) its computed value is "auto", and
2307 // (b) the item is *not* a scroll container. (A scroll container's automatic
2308 // minimum size is zero.)
2309 // https://drafts.csswg.org/css-flexbox-1/#min-size-auto
2310 const auto& mainMinSize =
2311 Frame()->StylePosition()->MinSize(MainAxis(), ContainingBlockWM());
2313 return IsAutoOrEnumOnBSize(mainMinSize, IsInlineAxisMainAxis()) &&
2314 !Frame()->StyleDisplay()->IsScrollableOverflow();
2317 Maybe<nscoord> FlexItem::MeasuredBSize() const {
2318 auto* cachedData =
2319 Frame()->FirstInFlow()->GetProperty(CachedFlexItemData::Prop());
2320 if (!cachedData || !cachedData->mBAxisMeasurement) {
2321 return Nothing();
2323 return Some(cachedData->mBAxisMeasurement->BSize());
2326 nscoord FlexItem::BaselineOffsetFromOuterCrossEdge(
2327 mozilla::Side aStartSide, bool aUseFirstLineBaseline) const {
2328 // NOTE:
2329 // * We only use baselines for aligning in the flex container's cross axis.
2330 // * Baselines are a measurement in the item's block axis.
2331 if (IsBlockAxisMainAxis()) {
2332 // We get here if the item's block axis is *orthogonal* the container's
2333 // cross axis. For example, a flex item with writing-mode:horizontal-tb in a
2334 // column-oriented flex container. We need to synthesize the item's baseline
2335 // from its border-box edge.
2336 const bool isMainAxisHorizontal =
2337 mCBWM.PhysicalAxis(MainAxis()) == mozilla::eAxisHorizontal;
2339 // When the main axis is horizontal, the synthesized baseline is the bottom
2340 // edge of the item's border-box. Otherwise, when the main axis is vertical,
2341 // the left edge. This is for compatibility with Google Chrome.
2342 nscoord marginTopOrLeftToBaseline =
2343 isMainAxisHorizontal ? PhysicalMargin().top : PhysicalMargin().left;
2344 if (mCBWM.IsAlphabeticalBaseline()) {
2345 marginTopOrLeftToBaseline += (isMainAxisHorizontal ? CrossSize() : 0);
2346 } else {
2347 MOZ_ASSERT(mCBWM.IsCentralBaseline());
2348 marginTopOrLeftToBaseline += CrossSize() / 2;
2351 return aStartSide == mozilla::eSideTop || aStartSide == mozilla::eSideLeft
2352 ? marginTopOrLeftToBaseline
2353 : OuterCrossSize() - marginTopOrLeftToBaseline;
2356 // We get here if the item's block axis is parallel (or antiparallel) to the
2357 // container's cross axis. We call ResolvedAscent() to get the item's
2358 // baseline. If the item has no baseline, the method will synthesize one from
2359 // the border-box edge.
2360 MOZ_ASSERT(IsBlockAxisCrossAxis(),
2361 "Only expecting to be doing baseline computations when the "
2362 "cross axis is the block axis");
2364 mozilla::Side itemBlockStartSide = mWM.PhysicalSide(eLogicalSideBStart);
2366 nscoord marginBStartToBaseline = ResolvedAscent(aUseFirstLineBaseline) +
2367 PhysicalMargin().Side(itemBlockStartSide);
2369 return (aStartSide == itemBlockStartSide)
2370 ? marginBStartToBaseline
2371 : OuterCrossSize() - marginBStartToBaseline;
2374 bool FlexItem::IsCrossSizeAuto() const {
2375 const nsStylePosition* stylePos =
2376 nsLayoutUtils::GetStyleFrame(mFrame)->StylePosition();
2377 // Check whichever component is in the flex container's cross axis.
2378 // (IsInlineAxisCrossAxis() tells us whether that's our ISize or BSize, in
2379 // terms of our own WritingMode, mWM.)
2380 return IsInlineAxisCrossAxis() ? stylePos->ISize(mWM).IsAuto()
2381 : stylePos->BSize(mWM).IsAuto();
2384 bool FlexItem::IsCrossSizeDefinite(const ReflowInput& aItemReflowInput) const {
2385 if (IsStretched()) {
2386 // Definite cross-size, imposed via 'align-self:stretch' & flex container.
2387 return true;
2390 const nsStylePosition* pos = aItemReflowInput.mStylePosition;
2391 const auto itemWM = GetWritingMode();
2393 // The logic here should be similar to the logic for isAutoISize/isAutoBSize
2394 // in nsContainerFrame::ComputeSizeWithIntrinsicDimensions().
2395 if (IsInlineAxisCrossAxis()) {
2396 return !pos->ISize(itemWM).IsAuto();
2399 nscoord cbBSize = aItemReflowInput.mContainingBlockSize.BSize(itemWM);
2400 return !nsLayoutUtils::IsAutoBSize(pos->BSize(itemWM), cbBSize);
2403 void FlexItem::ResolveFlexBaseSizeFromAspectRatio(
2404 const ReflowInput& aItemReflowInput) {
2405 // This implements the Flex Layout Algorithm Step 3B:
2406 // https://drafts.csswg.org/css-flexbox-1/#algo-main-item
2407 // If the flex item has ...
2408 // - an aspect ratio,
2409 // - a [used] flex-basis of 'content', and
2410 // - a definite cross size
2411 // then the flex base size is calculated from its inner cross size and the
2412 // flex item's preferred aspect ratio.
2413 if (HasAspectRatio() &&
2414 nsFlexContainerFrame::IsUsedFlexBasisContent(
2415 aItemReflowInput.mStylePosition->mFlexBasis,
2416 aItemReflowInput.mStylePosition->Size(MainAxis(), mCBWM)) &&
2417 IsCrossSizeDefinite(aItemReflowInput)) {
2418 const LogicalSize contentBoxSizeToBoxSizingAdjust =
2419 aItemReflowInput.mStylePosition->mBoxSizing == StyleBoxSizing::Border
2420 ? BorderPadding().Size(mCBWM)
2421 : LogicalSize(mCBWM);
2422 const nscoord mainSizeFromRatio = mAspectRatio.ComputeRatioDependentSize(
2423 MainAxis(), mCBWM, CrossSize(), contentBoxSizeToBoxSizingAdjust);
2424 SetFlexBaseSizeAndMainSize(mainSizeFromRatio);
2428 uint32_t FlexItem::NumAutoMarginsInAxis(LogicalAxis aAxis) const {
2429 uint32_t numAutoMargins = 0;
2430 const auto& styleMargin = mFrame->StyleMargin()->mMargin;
2431 for (const auto edge : {eLogicalEdgeStart, eLogicalEdgeEnd}) {
2432 const auto side = MakeLogicalSide(aAxis, edge);
2433 if (styleMargin.Get(mCBWM, side).IsAuto()) {
2434 numAutoMargins++;
2438 // Mostly for clarity:
2439 MOZ_ASSERT(numAutoMargins <= 2,
2440 "We're just looking at one item along one dimension, so we "
2441 "should only have examined 2 margins");
2443 return numAutoMargins;
2446 bool FlexItem::CanMainSizeInfluenceCrossSize() const {
2447 if (mIsStretched) {
2448 // We've already had our cross-size stretched for "align-self:stretch").
2449 // The container is imposing its cross size on us.
2450 return false;
2453 if (mIsStrut) {
2454 // Struts (for visibility:collapse items) have a predetermined size;
2455 // no need to measure anything.
2456 return false;
2459 if (HasAspectRatio()) {
2460 // For flex items that have an aspect ratio (and maintain it, i.e. are
2461 // not stretched, which we already checked above): changes to main-size
2462 // *do* influence the cross size.
2463 return true;
2466 if (IsInlineAxisCrossAxis()) {
2467 // If we get here, this function is really asking: "can changes to this
2468 // item's block size have an influence on its inline size"? For blocks and
2469 // tables, the answer is "no".
2470 if (mFrame->IsBlockFrame() || mFrame->IsTableWrapperFrame()) {
2471 // XXXdholbert (Maybe use an IsFrameOfType query or something more
2472 // general to test this across all frame types? For now, I'm just
2473 // optimizing for block and table, since those are common containers that
2474 // can contain arbitrarily-large subtrees (and that reliably have ISize
2475 // being unaffected by BSize, per CSS2). So optimizing away needless
2476 // relayout is possible & especially valuable for these containers.)
2477 return false;
2479 // Other opt-outs can go here, as they're identified as being useful
2480 // (particularly for containers where an extra reflow is expensive). But in
2481 // general, we have to assume that a flexed BSize *could* influence the
2482 // ISize. Some examples where this can definitely happen:
2483 // * Intrinsically-sized multicol with fixed-ISize columns, which adds
2484 // columns (i.e. grows in inline axis) depending on its block size.
2485 // * Intrinsically-sized multi-line column-oriented flex container, which
2486 // adds flex lines (i.e. grows in inline axis) depending on its block size.
2489 // Default assumption, if we haven't proven otherwise: the resolved main size
2490 // *can* change the cross size.
2491 return true;
2494 nscoord FlexItem::ClampMainSizeViaCrossAxisConstraints(
2495 nscoord aMainSize, const ReflowInput& aItemReflowInput) const {
2496 MOZ_ASSERT(HasAspectRatio(), "Caller should've checked the ratio is valid!");
2498 const LogicalSize contentBoxSizeToBoxSizingAdjust =
2499 aItemReflowInput.mStylePosition->mBoxSizing == StyleBoxSizing::Border
2500 ? BorderPadding().Size(mCBWM)
2501 : LogicalSize(mCBWM);
2503 const nscoord mainMinSizeFromRatio = mAspectRatio.ComputeRatioDependentSize(
2504 MainAxis(), mCBWM, CrossMinSize(), contentBoxSizeToBoxSizingAdjust);
2505 nscoord clampedMainSize = std::max(aMainSize, mainMinSizeFromRatio);
2507 if (CrossMaxSize() != NS_UNCONSTRAINEDSIZE) {
2508 const nscoord mainMaxSizeFromRatio = mAspectRatio.ComputeRatioDependentSize(
2509 MainAxis(), mCBWM, CrossMaxSize(), contentBoxSizeToBoxSizingAdjust);
2510 clampedMainSize = std::min(clampedMainSize, mainMaxSizeFromRatio);
2513 return clampedMainSize;
2517 * Returns true if aFrame or any of its children have the
2518 * NS_FRAME_CONTAINS_RELATIVE_BSIZE flag set -- i.e. if any of these frames (or
2519 * their descendants) might have a relative-BSize dependency on aFrame (or its
2520 * ancestors).
2522 static bool FrameHasRelativeBSizeDependency(nsIFrame* aFrame) {
2523 if (aFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
2524 return true;
2526 for (const auto& childList : aFrame->ChildLists()) {
2527 for (nsIFrame* childFrame : childList.mList) {
2528 if (childFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
2529 return true;
2533 return false;
2536 bool FlexItem::NeedsFinalReflow(const ReflowInput& aParentReflowInput) const {
2537 if (!StaticPrefs::layout_flexbox_item_final_reflow_optimization_enabled()) {
2538 FLEX_LOG(
2539 "[perf] Flex item %p needed a final reflow due to optimization being "
2540 "disabled via the preference",
2541 mFrame);
2542 return true;
2545 // NOTE: We can have continuations from an earlier constrained reflow.
2546 if (mFrame->GetPrevInFlow() || mFrame->GetNextInFlow()) {
2547 // This is an item has continuation(s). Reflow it.
2548 FLEX_LOG("[frag] Flex item %p needed a final reflow due to continuation(s)",
2549 mFrame);
2550 return true;
2553 // A flex item can grow its block-size in a fragmented context if there's any
2554 // force break within it (bug 1663079), or if it has a repeated table header
2555 // or footer (bug 1744363). We currently always reflow it.
2557 // Bug 1815294: investigate if we can design a more specific condition to
2558 // prevent triggering O(n^2) behavior when printing a deeply-nested flex
2559 // container.
2560 if (aParentReflowInput.IsInFragmentedContext()) {
2561 FLEX_LOG(
2562 "[frag] Flex item %p needed both a measuring reflow and a final "
2563 "reflow due to being in a fragmented context.",
2564 mFrame);
2565 return true;
2568 // Flex item's final content-box size (in terms of its own writing-mode):
2569 const LogicalSize finalSize = mIsInlineAxisMainAxis
2570 ? LogicalSize(mWM, mMainSize, mCrossSize)
2571 : LogicalSize(mWM, mCrossSize, mMainSize);
2573 if (HadMeasuringReflow()) {
2574 // We've already reflowed this flex item once, to measure it. In that
2575 // reflow, did its frame happen to end up with the correct final size
2576 // that the flex container would like it to have?
2577 if (finalSize != mFrame->ContentSize(mWM)) {
2578 // The measuring reflow left the item with a different size than its
2579 // final flexed size. So, we need to reflow to give it the correct size.
2580 FLEX_LOG(
2581 "[perf] Flex item %p needed both a measuring reflow and a final "
2582 "reflow due to measured size disagreeing with final size",
2583 mFrame);
2584 return true;
2587 if (FrameHasRelativeBSizeDependency(mFrame)) {
2588 // This item has descendants with relative BSizes who may care that its
2589 // size may now be considered "definite" in the final reflow (whereas it
2590 // was indefinite during the measuring reflow).
2591 FLEX_LOG(
2592 "[perf] Flex item %p needed both a measuring reflow and a final "
2593 "reflow due to BSize potentially becoming definite",
2594 mFrame);
2595 return true;
2598 // If we get here, then this flex item had a measuring reflow, it left us
2599 // with the correct size, none of its descendants care that its BSize may
2600 // now be considered definite, and it can fit into the available block-size.
2601 // So it doesn't need a final reflow.
2603 // We now cache this size as if we had done a final reflow (because we've
2604 // determined that the measuring reflow was effectively equivalent). This
2605 // way, in our next time through flex layout, we may be able to skip both
2606 // the measuring reflow *and* the final reflow (if conditions are the same
2607 // as they are now).
2608 if (auto* cache = mFrame->GetProperty(CachedFlexItemData::Prop())) {
2609 cache->Update(*this, finalSize);
2612 return false;
2615 // This item didn't receive a measuring reflow (at least, not during this
2616 // reflow of our flex container). We may still be able to skip reflowing it
2617 // (i.e. return false from this function), if its subtree is clean & its most
2618 // recent "final reflow" had it at the correct content-box size &
2619 // definiteness.
2620 // Let's check for each condition that would still require us to reflow:
2621 if (mFrame->IsSubtreeDirty()) {
2622 FLEX_LOG(
2623 "[perf] Flex item %p needed a final reflow due to its subtree "
2624 "being dirty",
2625 mFrame);
2626 return true;
2629 // Cool; this item & its subtree haven't experienced any style/content
2630 // changes that would automatically require a reflow.
2632 // Did we cache the metrics from its most recent "final reflow"?
2633 auto* cache = mFrame->GetProperty(CachedFlexItemData::Prop());
2634 if (!cache || !cache->mFinalReflowMetrics) {
2635 FLEX_LOG(
2636 "[perf] Flex item %p needed a final reflow due to lacking a "
2637 "cached mFinalReflowMetrics (maybe cache was cleared)",
2638 mFrame);
2639 return true;
2642 // Does the cached size match our current size?
2643 if (cache->mFinalReflowMetrics->Size() != finalSize) {
2644 FLEX_LOG(
2645 "[perf] Flex item %p needed a final reflow due to having a "
2646 "different content box size vs. its most recent final reflow",
2647 mFrame);
2648 return true;
2651 // Does the cached border and padding match our current ones?
2653 // Note: this is just to detect cases where we have a percent padding whose
2654 // basis has changed. Any other sort of change to BorderPadding() (e.g. a new
2655 // specified value) should result in the frame being marked dirty via proper
2656 // change hint (see nsStylePadding::CalcDifference()), which will force it to
2657 // reflow.
2658 if (cache->mFinalReflowMetrics->BorderPadding() !=
2659 BorderPadding().ConvertTo(mWM, mCBWM)) {
2660 FLEX_LOG(
2661 "[perf] Flex item %p needed a final reflow due to having a "
2662 "different border and padding vs. its most recent final reflow",
2663 mFrame);
2664 return true;
2667 // The flex container is giving this flex item the same size that the item
2668 // had on its most recent "final reflow". But if its definiteness changed and
2669 // one of the descendants cares, then it would still need a reflow.
2670 if (cache->mFinalReflowMetrics->TreatBSizeAsIndefinite() !=
2671 mTreatBSizeAsIndefinite &&
2672 FrameHasRelativeBSizeDependency(mFrame)) {
2673 FLEX_LOG(
2674 "[perf] Flex item %p needed a final reflow due to having "
2675 "its BSize change definiteness & having a rel-BSize child",
2676 mFrame);
2677 return true;
2680 // If we get here, we can skip the final reflow! (The item's subtree isn't
2681 // dirty, and our current conditions are sufficiently similar to the most
2682 // recent "final reflow" that it should have left our subtree in the correct
2683 // state.)
2684 FLEX_LOG("[perf] Flex item %p didn't need a final reflow", mFrame);
2685 return false;
2688 // Keeps track of our position along a particular axis (where a '0' position
2689 // corresponds to the 'start' edge of that axis).
2690 // This class shouldn't be instantiated directly -- rather, it should only be
2691 // instantiated via its subclasses defined below.
2692 class MOZ_STACK_CLASS PositionTracker {
2693 public:
2694 // Accessor for the current value of the position that we're tracking.
2695 inline nscoord Position() const { return mPosition; }
2696 inline LogicalAxis Axis() const { return mAxis; }
2698 inline LogicalSide StartSide() {
2699 return MakeLogicalSide(
2700 mAxis, mIsAxisReversed ? eLogicalEdgeEnd : eLogicalEdgeStart);
2703 inline LogicalSide EndSide() {
2704 return MakeLogicalSide(
2705 mAxis, mIsAxisReversed ? eLogicalEdgeStart : eLogicalEdgeEnd);
2708 // Advances our position across the start edge of the given margin, in the
2709 // axis we're tracking.
2710 void EnterMargin(const LogicalMargin& aMargin) {
2711 mPosition += aMargin.Side(StartSide(), mWM);
2714 // Advances our position across the end edge of the given margin, in the axis
2715 // we're tracking.
2716 void ExitMargin(const LogicalMargin& aMargin) {
2717 mPosition += aMargin.Side(EndSide(), mWM);
2720 // Advances our current position from the start side of a child frame's
2721 // border-box to the frame's upper or left edge (depending on our axis).
2722 // (Note that this is a no-op if our axis grows in the same direction as
2723 // the corresponding logical axis.)
2724 void EnterChildFrame(nscoord aChildFrameSize) {
2725 if (mIsAxisReversed) {
2726 mPosition += aChildFrameSize;
2730 // Advances our current position from a frame's upper or left border-box edge
2731 // (whichever is in the axis we're tracking) to the 'end' side of the frame
2732 // in the axis that we're tracking. (Note that this is a no-op if our axis
2733 // is reversed with respect to the corresponding logical axis.)
2734 void ExitChildFrame(nscoord aChildFrameSize) {
2735 if (!mIsAxisReversed) {
2736 mPosition += aChildFrameSize;
2740 // Delete copy-constructor & reassignment operator, to prevent accidental
2741 // (unnecessary) copying.
2742 PositionTracker(const PositionTracker&) = delete;
2743 PositionTracker& operator=(const PositionTracker&) = delete;
2745 protected:
2746 // Protected constructor, to be sure we're only instantiated via a subclass.
2747 PositionTracker(WritingMode aWM, LogicalAxis aAxis, bool aIsAxisReversed)
2748 : mWM(aWM), mAxis(aAxis), mIsAxisReversed(aIsAxisReversed) {}
2750 // Member data:
2751 // The position we're tracking.
2752 nscoord mPosition = 0;
2754 // The flex container's writing mode.
2755 const WritingMode mWM;
2757 // The axis along which we're moving.
2758 const LogicalAxis mAxis = eLogicalAxisInline;
2760 // Is the axis along which we're moving reversed (e.g. LTR vs RTL) with
2761 // respect to the corresponding axis on the flex container's WM?
2762 const bool mIsAxisReversed = false;
2765 // Tracks our position in the main axis, when we're laying out flex items.
2766 // The "0" position represents the main-start edge of the flex container's
2767 // content-box.
2768 class MOZ_STACK_CLASS MainAxisPositionTracker : public PositionTracker {
2769 public:
2770 MainAxisPositionTracker(const FlexboxAxisTracker& aAxisTracker,
2771 const FlexLine* aLine,
2772 const StyleContentDistribution& aJustifyContent,
2773 nscoord aContentBoxMainSize);
2775 ~MainAxisPositionTracker() {
2776 MOZ_ASSERT(mNumPackingSpacesRemaining == 0,
2777 "miscounted the number of packing spaces");
2778 MOZ_ASSERT(mNumAutoMarginsInMainAxis == 0,
2779 "miscounted the number of auto margins");
2782 // Advances past the gap space (if any) between two flex items
2783 void TraverseGap(nscoord aGapSize) { mPosition += aGapSize; }
2785 // Advances past the packing space (if any) between two flex items
2786 void TraversePackingSpace();
2788 // If aItem has any 'auto' margins in the main axis, this method updates the
2789 // corresponding values in its margin.
2790 void ResolveAutoMarginsInMainAxis(FlexItem& aItem);
2792 private:
2793 nscoord mPackingSpaceRemaining = 0;
2794 uint32_t mNumAutoMarginsInMainAxis = 0;
2795 uint32_t mNumPackingSpacesRemaining = 0;
2796 StyleContentDistribution mJustifyContent = {StyleAlignFlags::AUTO};
2799 // Utility class for managing our position along the cross axis along
2800 // the whole flex container (at a higher level than a single line).
2801 // The "0" position represents the cross-start edge of the flex container's
2802 // content-box.
2803 class MOZ_STACK_CLASS CrossAxisPositionTracker : public PositionTracker {
2804 public:
2805 CrossAxisPositionTracker(nsTArray<FlexLine>& aLines,
2806 const ReflowInput& aReflowInput,
2807 nscoord aContentBoxCrossSize,
2808 bool aIsCrossSizeDefinite,
2809 const FlexboxAxisTracker& aAxisTracker,
2810 const nscoord aCrossGapSize);
2812 // Advances past the gap (if any) between two flex lines
2813 void TraverseGap() { mPosition += mCrossGapSize; }
2815 // Advances past the packing space (if any) between two flex lines
2816 void TraversePackingSpace();
2818 // Advances past the given FlexLine
2819 void TraverseLine(FlexLine& aLine) { mPosition += aLine.LineCrossSize(); }
2821 // Redeclare the frame-related methods from PositionTracker with
2822 // = delete, to be sure (at compile time) that no client code can invoke
2823 // them. (Unlike the other PositionTracker derived classes, this class here
2824 // deals with FlexLines, not with individual FlexItems or frames.)
2825 void EnterMargin(const LogicalMargin& aMargin) = delete;
2826 void ExitMargin(const LogicalMargin& aMargin) = delete;
2827 void EnterChildFrame(nscoord aChildFrameSize) = delete;
2828 void ExitChildFrame(nscoord aChildFrameSize) = delete;
2830 private:
2831 nscoord mPackingSpaceRemaining = 0;
2832 uint32_t mNumPackingSpacesRemaining = 0;
2833 StyleContentDistribution mAlignContent = {StyleAlignFlags::AUTO};
2835 const nscoord mCrossGapSize;
2838 // Utility class for managing our position along the cross axis, *within* a
2839 // single flex line.
2840 class MOZ_STACK_CLASS SingleLineCrossAxisPositionTracker
2841 : public PositionTracker {
2842 public:
2843 explicit SingleLineCrossAxisPositionTracker(
2844 const FlexboxAxisTracker& aAxisTracker);
2846 void ResolveAutoMarginsInCrossAxis(const FlexLine& aLine, FlexItem& aItem);
2848 void EnterAlignPackingSpace(const FlexLine& aLine, const FlexItem& aItem,
2849 const FlexboxAxisTracker& aAxisTracker);
2851 // Resets our position to the cross-start edge of this line.
2852 inline void ResetPosition() { mPosition = 0; }
2855 //----------------------------------------------------------------------
2857 // Frame class boilerplate
2858 // =======================
2860 NS_QUERYFRAME_HEAD(nsFlexContainerFrame)
2861 NS_QUERYFRAME_ENTRY(nsFlexContainerFrame)
2862 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
2864 NS_IMPL_FRAMEARENA_HELPERS(nsFlexContainerFrame)
2866 nsContainerFrame* NS_NewFlexContainerFrame(PresShell* aPresShell,
2867 ComputedStyle* aStyle) {
2868 return new (aPresShell)
2869 nsFlexContainerFrame(aStyle, aPresShell->GetPresContext());
2872 //----------------------------------------------------------------------
2874 // nsFlexContainerFrame Method Implementations
2875 // ===========================================
2877 /* virtual */
2878 nsFlexContainerFrame::~nsFlexContainerFrame() = default;
2880 /* virtual */
2881 void nsFlexContainerFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
2882 nsIFrame* aPrevInFlow) {
2883 nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
2885 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER)) {
2886 AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
2889 auto displayInside = StyleDisplay()->DisplayInside();
2890 // If this frame is for a scrollable element, then it will actually have
2891 // "display:block", and its *parent frame* will have the real
2892 // flex-flavored display value. So in that case, check the parent frame to
2893 // find out if we're legacy.
2895 // TODO(emilio): Maybe ::-moz-scrolled-content and co should inherit `display`
2896 // (or a blockified version thereof, to not hit bug 456484).
2897 if (displayInside == StyleDisplayInside::Flow) {
2898 MOZ_ASSERT(StyleDisplay()->mDisplay == StyleDisplay::Block);
2899 MOZ_ASSERT(Style()->GetPseudoType() == PseudoStyleType::buttonContent ||
2900 Style()->GetPseudoType() == PseudoStyleType::scrolledContent,
2901 "The only way a nsFlexContainerFrame can have 'display:block' "
2902 "should be if it's the inner part of a scrollable or button "
2903 "element");
2904 displayInside = GetParent()->StyleDisplay()->DisplayInside();
2907 // Figure out if we should set a frame state bit to indicate that this frame
2908 // represents a legacy -moz-{inline-}box or -webkit-{inline-}box container.
2909 if (displayInside == StyleDisplayInside::WebkitBox) {
2910 AddStateBits(NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX);
2914 #ifdef DEBUG_FRAME_DUMP
2915 nsresult nsFlexContainerFrame::GetFrameName(nsAString& aResult) const {
2916 return MakeFrameName(u"FlexContainer"_ns, aResult);
2918 #endif
2920 void nsFlexContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
2921 const nsDisplayListSet& aLists) {
2922 nsDisplayListCollection tempLists(aBuilder);
2924 DisplayBorderBackgroundOutline(aBuilder, tempLists);
2925 if (GetPrevInFlow()) {
2926 DisplayOverflowContainers(aBuilder, tempLists);
2929 // Our children are all block-level, so their borders/backgrounds all go on
2930 // the BlockBorderBackgrounds list.
2931 nsDisplayListSet childLists(tempLists, tempLists.BlockBorderBackgrounds());
2933 CSSOrderAwareFrameIterator iter(
2934 this, FrameChildListID::Principal,
2935 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
2936 OrderStateForIter(this), OrderingPropertyForIter(this));
2938 const auto flags = DisplayFlagsForFlexOrGridItem();
2939 for (; !iter.AtEnd(); iter.Next()) {
2940 nsIFrame* childFrame = *iter;
2941 BuildDisplayListForChild(aBuilder, childFrame, childLists, flags);
2944 tempLists.MoveTo(aLists);
2947 void FlexLine::FreezeItemsEarly(bool aIsUsingFlexGrow,
2948 ComputedFlexLineInfo* aLineInfo) {
2949 // After we've established the type of flexing we're doing (growing vs.
2950 // shrinking), and before we try to flex any items, we freeze items that
2951 // obviously *can't* flex.
2953 // Quoting the spec:
2954 // # Freeze, setting its target main size to its hypothetical main size...
2955 // # - any item that has a flex factor of zero
2956 // # - if using the flex grow factor: any item that has a flex base size
2957 // # greater than its hypothetical main size
2958 // # - if using the flex shrink factor: any item that has a flex base size
2959 // # smaller than its hypothetical main size
2960 // https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths
2962 // (NOTE: At this point, item->MainSize() *is* the item's hypothetical
2963 // main size, since SetFlexBaseSizeAndMainSize() sets it up that way, and the
2964 // item hasn't had a chance to flex away from that yet.)
2966 // Since this loop only operates on unfrozen flex items, we can break as
2967 // soon as we have seen all of them.
2968 uint32_t numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems;
2969 for (FlexItem& item : Items()) {
2970 if (numUnfrozenItemsToBeSeen == 0) {
2971 break;
2974 if (!item.IsFrozen()) {
2975 numUnfrozenItemsToBeSeen--;
2976 bool shouldFreeze = (0.0f == item.GetFlexFactor(aIsUsingFlexGrow));
2977 if (!shouldFreeze) {
2978 if (aIsUsingFlexGrow) {
2979 if (item.FlexBaseSize() > item.MainSize()) {
2980 shouldFreeze = true;
2982 } else { // using flex-shrink
2983 if (item.FlexBaseSize() < item.MainSize()) {
2984 shouldFreeze = true;
2988 if (shouldFreeze) {
2989 // Freeze item! (at its hypothetical main size)
2990 item.Freeze();
2991 if (item.FlexBaseSize() < item.MainSize()) {
2992 item.SetWasMinClamped();
2993 } else if (item.FlexBaseSize() > item.MainSize()) {
2994 item.SetWasMaxClamped();
2996 mNumFrozenItems++;
3001 MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");
3004 // Based on the sign of aTotalViolation, this function freezes a subset of our
3005 // flexible sizes, and restores the remaining ones to their initial pref sizes.
3006 void FlexLine::FreezeOrRestoreEachFlexibleSize(const nscoord aTotalViolation,
3007 bool aIsFinalIteration) {
3008 enum FreezeType {
3009 eFreezeEverything,
3010 eFreezeMinViolations,
3011 eFreezeMaxViolations
3014 FreezeType freezeType;
3015 if (aTotalViolation == 0) {
3016 freezeType = eFreezeEverything;
3017 } else if (aTotalViolation > 0) {
3018 freezeType = eFreezeMinViolations;
3019 } else { // aTotalViolation < 0
3020 freezeType = eFreezeMaxViolations;
3023 // Since this loop only operates on unfrozen flex items, we can break as
3024 // soon as we have seen all of them.
3025 uint32_t numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems;
3026 for (FlexItem& item : Items()) {
3027 if (numUnfrozenItemsToBeSeen == 0) {
3028 break;
3031 if (!item.IsFrozen()) {
3032 numUnfrozenItemsToBeSeen--;
3034 MOZ_ASSERT(!item.HadMinViolation() || !item.HadMaxViolation(),
3035 "Can have either min or max violation, but not both");
3037 bool hadMinViolation = item.HadMinViolation();
3038 bool hadMaxViolation = item.HadMaxViolation();
3039 if (eFreezeEverything == freezeType ||
3040 (eFreezeMinViolations == freezeType && hadMinViolation) ||
3041 (eFreezeMaxViolations == freezeType && hadMaxViolation)) {
3042 MOZ_ASSERT(item.MainSize() >= item.MainMinSize(),
3043 "Freezing item at a size below its minimum");
3044 MOZ_ASSERT(item.MainSize() <= item.MainMaxSize(),
3045 "Freezing item at a size above its maximum");
3047 item.Freeze();
3048 if (hadMinViolation) {
3049 item.SetWasMinClamped();
3050 } else if (hadMaxViolation) {
3051 item.SetWasMaxClamped();
3053 mNumFrozenItems++;
3054 } else if (MOZ_UNLIKELY(aIsFinalIteration)) {
3055 // XXXdholbert If & when bug 765861 is fixed, we should upgrade this
3056 // assertion to be fatal except in documents with enormous lengths.
3057 NS_ERROR(
3058 "Final iteration still has unfrozen items, this shouldn't"
3059 " happen unless there was nscoord under/overflow.");
3060 item.Freeze();
3061 mNumFrozenItems++;
3062 } // else, we'll reset this item's main size to its flex base size on the
3063 // next iteration of this algorithm.
3065 if (!item.IsFrozen()) {
3066 // Clear this item's violation(s), now that we've dealt with them
3067 item.ClearViolationFlags();
3072 MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");
3075 void FlexLine::ResolveFlexibleLengths(nscoord aFlexContainerMainSize,
3076 ComputedFlexLineInfo* aLineInfo) {
3077 // In this function, we use 64-bit coord type to avoid integer overflow in
3078 // case several of the individual items have huge hypothetical main sizes,
3079 // which can happen with percent-width table-layout:fixed descendants. Here we
3080 // promote the container's main size to 64-bit to make the arithmetic
3081 // convenient.
3082 AuCoord64 flexContainerMainSize(aFlexContainerMainSize);
3084 // Before we start resolving sizes: if we have an aLineInfo structure to fill
3085 // out, we inform it of each item's base size, and we initialize the "delta"
3086 // for each item to 0. (And if the flex algorithm wants to grow or shrink the
3087 // item, we'll update this delta further down.)
3088 if (aLineInfo) {
3089 uint32_t itemIndex = 0;
3090 for (FlexItem& item : Items()) {
3091 aLineInfo->mItems[itemIndex].mMainBaseSize = item.FlexBaseSize();
3092 aLineInfo->mItems[itemIndex].mMainDeltaSize = 0;
3093 ++itemIndex;
3097 // Determine whether we're going to be growing or shrinking items.
3098 const bool isUsingFlexGrow =
3099 (mTotalOuterHypotheticalMainSize < flexContainerMainSize);
3101 if (aLineInfo) {
3102 aLineInfo->mGrowthState =
3103 isUsingFlexGrow ? mozilla::dom::FlexLineGrowthState::Growing
3104 : mozilla::dom::FlexLineGrowthState::Shrinking;
3107 // Do an "early freeze" for flex items that obviously can't flex in the
3108 // direction we've chosen:
3109 FreezeItemsEarly(isUsingFlexGrow, aLineInfo);
3111 if ((mNumFrozenItems == NumItems()) && !aLineInfo) {
3112 // All our items are frozen, so we have no flexible lengths to resolve,
3113 // and we aren't being asked to generate computed line info.
3114 FLEX_LOG("No flexible length to resolve");
3115 return;
3117 MOZ_ASSERT(!IsEmpty() || aLineInfo,
3118 "empty lines should take the early-return above");
3120 FLEX_LOG("Resolving flexible lengths for items");
3122 // Subtract space occupied by our items' margins/borders/padding/gaps, so
3123 // we can just be dealing with the space available for our flex items' content
3124 // boxes.
3125 const AuCoord64 totalItemMBPAndGaps = mTotalItemMBP + SumOfGaps();
3126 const AuCoord64 spaceAvailableForFlexItemsContentBoxes =
3127 flexContainerMainSize - totalItemMBPAndGaps;
3129 Maybe<AuCoord64> origAvailableFreeSpace;
3131 // NOTE: I claim that this chunk of the algorithm (the looping part) needs to
3132 // run the loop at MOST NumItems() times. This claim should hold up
3133 // because we'll freeze at least one item on each loop iteration, and once
3134 // we've run out of items to freeze, there's nothing left to do. However,
3135 // in most cases, we'll break out of this loop long before we hit that many
3136 // iterations.
3137 for (uint32_t iterationCounter = 0; iterationCounter < NumItems();
3138 iterationCounter++) {
3139 // Set every not-yet-frozen item's used main size to its
3140 // flex base size, and subtract all the used main sizes from our
3141 // total amount of space to determine the 'available free space'
3142 // (positive or negative) to be distributed among our flexible items.
3143 AuCoord64 availableFreeSpace = spaceAvailableForFlexItemsContentBoxes;
3144 for (FlexItem& item : Items()) {
3145 if (!item.IsFrozen()) {
3146 item.SetMainSize(item.FlexBaseSize());
3148 availableFreeSpace -= item.MainSize();
3151 FLEX_LOG(" available free space: %" PRId64 "; flex items should \"%s\"",
3152 availableFreeSpace.value, isUsingFlexGrow ? "grow" : "shrink");
3154 // The sign of our free space should agree with the type of flexing
3155 // (grow/shrink) that we're doing. Any disagreement should've made us use
3156 // the other type of flexing, or should've been resolved in
3157 // FreezeItemsEarly.
3159 // Note: it's possible that an individual flex item has huge
3160 // margin/border/padding that makes either its
3161 // MarginBorderPaddingSizeInMainAxis() or OuterMainSize() negative due to
3162 // integer overflow. If that happens, the accumulated
3163 // mTotalOuterHypotheticalMainSize or mTotalItemMBP could be negative due to
3164 // that one item's negative (overflowed) size. Likewise, a huge main gap
3165 // size between flex items can also make our accumulated SumOfGaps()
3166 // negative. In these case, we throw up our hands and don't require
3167 // isUsingFlexGrow to agree with availableFreeSpace. Luckily, we won't get
3168 // stuck in the algorithm below, and just distribute the wrong
3169 // availableFreeSpace with the wrong grow/shrink factors.
3170 MOZ_ASSERT(!(mTotalOuterHypotheticalMainSize >= 0 && mTotalItemMBP >= 0 &&
3171 totalItemMBPAndGaps >= 0) ||
3172 (isUsingFlexGrow && availableFreeSpace >= 0) ||
3173 (!isUsingFlexGrow && availableFreeSpace <= 0),
3174 "availableFreeSpace's sign should match isUsingFlexGrow");
3176 // If we have any free space available, give each flexible item a portion
3177 // of availableFreeSpace.
3178 if (availableFreeSpace != AuCoord64(0)) {
3179 // The first time we do this, we initialize origAvailableFreeSpace.
3180 if (!origAvailableFreeSpace) {
3181 origAvailableFreeSpace.emplace(availableFreeSpace);
3184 // STRATEGY: On each item, we compute & store its "share" of the total
3185 // weight that we've seen so far:
3186 // curWeight / weightSum
3188 // Then, when we go to actually distribute the space (in the next loop),
3189 // we can simply walk backwards through the elements and give each item
3190 // its "share" multiplied by the remaining available space.
3192 // SPECIAL CASE: If the sum of the weights is larger than the
3193 // maximum representable double (overflowing to infinity), then we can't
3194 // sensibly divide out proportional shares anymore. In that case, we
3195 // simply treat the flex item(s) with the largest weights as if
3196 // their weights were infinite (dwarfing all the others), and we
3197 // distribute all of the available space among them.
3198 double weightSum = 0.0;
3199 double flexFactorSum = 0.0;
3200 double largestWeight = 0.0;
3201 uint32_t numItemsWithLargestWeight = 0;
3203 // Since this loop only operates on unfrozen flex items, we can break as
3204 // soon as we have seen all of them.
3205 uint32_t numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems;
3206 for (FlexItem& item : Items()) {
3207 if (numUnfrozenItemsToBeSeen == 0) {
3208 break;
3211 if (!item.IsFrozen()) {
3212 numUnfrozenItemsToBeSeen--;
3214 const double curWeight = item.GetWeight(isUsingFlexGrow);
3215 const double curFlexFactor = item.GetFlexFactor(isUsingFlexGrow);
3216 MOZ_ASSERT(curWeight >= 0.0, "weights are non-negative");
3217 MOZ_ASSERT(curFlexFactor >= 0.0, "flex factors are non-negative");
3219 weightSum += curWeight;
3220 flexFactorSum += curFlexFactor;
3222 if (std::isfinite(weightSum)) {
3223 if (curWeight == 0.0) {
3224 item.SetShareOfWeightSoFar(0.0);
3225 } else {
3226 item.SetShareOfWeightSoFar(curWeight / weightSum);
3228 } // else, the sum of weights overflows to infinity, in which
3229 // case we don't bother with "SetShareOfWeightSoFar" since
3230 // we know we won't use it. (instead, we'll just give every
3231 // item with the largest weight an equal share of space.)
3233 // Update our largest-weight tracking vars
3234 if (curWeight > largestWeight) {
3235 largestWeight = curWeight;
3236 numItemsWithLargestWeight = 1;
3237 } else if (curWeight == largestWeight) {
3238 numItemsWithLargestWeight++;
3243 MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");
3245 if (weightSum != 0.0) {
3246 MOZ_ASSERT(flexFactorSum != 0.0,
3247 "flex factor sum can't be 0, if a weighted sum "
3248 "of its components (weightSum) is nonzero");
3249 if (flexFactorSum < 1.0) {
3250 // Our unfrozen flex items don't want all of the original free space!
3251 // (Their flex factors add up to something less than 1.)
3252 // Hence, make sure we don't distribute any more than the portion of
3253 // our original free space that these items actually want.
3254 auto totalDesiredPortionOfOrigFreeSpace =
3255 AuCoord64::FromRound(*origAvailableFreeSpace * flexFactorSum);
3257 // Clamp availableFreeSpace to be no larger than that ^^.
3258 // (using min or max, depending on sign).
3259 // This should not change the sign of availableFreeSpace (except
3260 // possibly by setting it to 0), as enforced by this assertion:
3261 NS_ASSERTION(totalDesiredPortionOfOrigFreeSpace == AuCoord64(0) ||
3262 ((totalDesiredPortionOfOrigFreeSpace > 0) ==
3263 (availableFreeSpace > 0)),
3264 "When we reduce available free space for flex "
3265 "factors < 1, we shouldn't change the sign of the "
3266 "free space...");
3268 if (availableFreeSpace > 0) {
3269 availableFreeSpace = std::min(availableFreeSpace,
3270 totalDesiredPortionOfOrigFreeSpace);
3271 } else {
3272 availableFreeSpace = std::max(availableFreeSpace,
3273 totalDesiredPortionOfOrigFreeSpace);
3277 FLEX_LOG(" Distributing available space:");
3278 // Since this loop only operates on unfrozen flex items, we can break as
3279 // soon as we have seen all of them.
3280 numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems;
3282 // NOTE: It's important that we traverse our items in *reverse* order
3283 // here, for correct width distribution according to the items'
3284 // "ShareOfWeightSoFar" progressively-calculated values.
3285 for (FlexItem& item : Reversed(Items())) {
3286 if (numUnfrozenItemsToBeSeen == 0) {
3287 break;
3290 if (!item.IsFrozen()) {
3291 numUnfrozenItemsToBeSeen--;
3293 // To avoid rounding issues, we compute the change in size for this
3294 // item, and then subtract it from the remaining available space.
3295 AuCoord64 sizeDelta = 0;
3296 if (std::isfinite(weightSum)) {
3297 double myShareOfRemainingSpace = item.ShareOfWeightSoFar();
3299 MOZ_ASSERT(myShareOfRemainingSpace >= 0.0 &&
3300 myShareOfRemainingSpace <= 1.0,
3301 "my share should be nonnegative fractional amount");
3303 if (myShareOfRemainingSpace == 1.0) {
3304 // (We special-case 1.0 to avoid float error from converting
3305 // availableFreeSpace from integer*1.0 --> double --> integer)
3306 sizeDelta = availableFreeSpace;
3307 } else if (myShareOfRemainingSpace > 0.0) {
3308 sizeDelta = AuCoord64::FromRound(availableFreeSpace *
3309 myShareOfRemainingSpace);
3311 } else if (item.GetWeight(isUsingFlexGrow) == largestWeight) {
3312 // Total flexibility is infinite, so we're just distributing
3313 // the available space equally among the items that are tied for
3314 // having the largest weight (and this is one of those items).
3315 sizeDelta = AuCoord64::FromRound(
3316 availableFreeSpace / double(numItemsWithLargestWeight));
3317 numItemsWithLargestWeight--;
3320 availableFreeSpace -= sizeDelta;
3322 item.SetMainSize(item.MainSize() +
3323 nscoord(sizeDelta.ToMinMaxClamped()));
3324 FLEX_LOG(" flex item %p receives %" PRId64 ", for a total of %d",
3325 item.Frame(), sizeDelta.value, item.MainSize());
3329 MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");
3331 // If we have an aLineInfo structure to fill out, capture any
3332 // size changes that may have occurred in the previous loop.
3333 // We don't do this inside the previous loop, because we don't
3334 // want to burden layout when aLineInfo is null.
3335 if (aLineInfo) {
3336 uint32_t itemIndex = 0;
3337 for (FlexItem& item : Items()) {
3338 if (!item.IsFrozen()) {
3339 // Calculate a deltaSize that represents how much the flex sizing
3340 // algorithm "wants" to stretch or shrink this item during this
3341 // pass through the algorithm. Later passes through the algorithm
3342 // may overwrite this, until this item is frozen. Note that this
3343 // value may not reflect how much the size of the item is
3344 // actually changed, since the size of the item will be clamped
3345 // to min and max values later in this pass. That's intentional,
3346 // since we want to report the value that the sizing algorithm
3347 // tried to stretch or shrink the item.
3348 nscoord deltaSize =
3349 item.MainSize() - aLineInfo->mItems[itemIndex].mMainBaseSize;
3351 aLineInfo->mItems[itemIndex].mMainDeltaSize = deltaSize;
3353 ++itemIndex;
3359 // Fix min/max violations:
3360 nscoord totalViolation = 0; // keeps track of adjustments for min/max
3361 FLEX_LOG(" Checking for violations:");
3363 // Since this loop only operates on unfrozen flex items, we can break as
3364 // soon as we have seen all of them.
3365 uint32_t numUnfrozenItemsToBeSeen = NumItems() - mNumFrozenItems;
3366 for (FlexItem& item : Items()) {
3367 if (numUnfrozenItemsToBeSeen == 0) {
3368 break;
3371 if (!item.IsFrozen()) {
3372 numUnfrozenItemsToBeSeen--;
3374 if (item.MainSize() < item.MainMinSize()) {
3375 // min violation
3376 totalViolation += item.MainMinSize() - item.MainSize();
3377 item.SetMainSize(item.MainMinSize());
3378 item.SetHadMinViolation();
3379 } else if (item.MainSize() > item.MainMaxSize()) {
3380 // max violation
3381 totalViolation += item.MainMaxSize() - item.MainSize();
3382 item.SetMainSize(item.MainMaxSize());
3383 item.SetHadMaxViolation();
3388 MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");
3390 FreezeOrRestoreEachFlexibleSize(totalViolation,
3391 iterationCounter + 1 == NumItems());
3393 FLEX_LOG(" Total violation: %d", totalViolation);
3395 if (mNumFrozenItems == NumItems()) {
3396 break;
3399 MOZ_ASSERT(totalViolation != 0,
3400 "Zero violation should've made us freeze all items & break");
3403 #ifdef DEBUG
3404 // Post-condition: all items should've been frozen.
3405 // Make sure the counts match:
3406 MOZ_ASSERT(mNumFrozenItems == NumItems(), "All items should be frozen");
3408 // For good measure, check each item directly, in case our counts are busted:
3409 for (const FlexItem& item : Items()) {
3410 MOZ_ASSERT(item.IsFrozen(), "All items should be frozen");
3412 #endif // DEBUG
3415 MainAxisPositionTracker::MainAxisPositionTracker(
3416 const FlexboxAxisTracker& aAxisTracker, const FlexLine* aLine,
3417 const StyleContentDistribution& aJustifyContent,
3418 nscoord aContentBoxMainSize)
3419 : PositionTracker(aAxisTracker.GetWritingMode(), aAxisTracker.MainAxis(),
3420 aAxisTracker.IsMainAxisReversed()),
3421 // we chip away at this below
3422 mPackingSpaceRemaining(aContentBoxMainSize),
3423 mJustifyContent(aJustifyContent) {
3424 // Extract the flag portion of mJustifyContent and strip off the flag bits
3425 // NOTE: This must happen before any assignment to mJustifyContent to
3426 // avoid overwriting the flag bits.
3427 StyleAlignFlags justifyContentFlags =
3428 mJustifyContent.primary & StyleAlignFlags::FLAG_BITS;
3429 mJustifyContent.primary &= ~StyleAlignFlags::FLAG_BITS;
3431 // 'normal' behaves as 'stretch', and 'stretch' behaves as 'flex-start',
3432 // in the main axis
3433 // https://drafts.csswg.org/css-align-3/#propdef-justify-content
3434 if (mJustifyContent.primary == StyleAlignFlags::NORMAL ||
3435 mJustifyContent.primary == StyleAlignFlags::STRETCH) {
3436 mJustifyContent.primary = StyleAlignFlags::FLEX_START;
3439 // mPackingSpaceRemaining is initialized to the container's main size. Now
3440 // we'll subtract out the main sizes of our flex items, so that it ends up
3441 // with the *actual* amount of packing space.
3442 for (const FlexItem& item : aLine->Items()) {
3443 mPackingSpaceRemaining -= item.OuterMainSize();
3444 mNumAutoMarginsInMainAxis += item.NumAutoMarginsInMainAxis();
3447 // Subtract space required for row/col gap from the remaining packing space
3448 mPackingSpaceRemaining -= aLine->SumOfGaps();
3450 if (mPackingSpaceRemaining <= 0) {
3451 // No available packing space to use for resolving auto margins.
3452 mNumAutoMarginsInMainAxis = 0;
3453 // If packing space is negative and <overflow-position> is set to 'safe'
3454 // all justify options fall back to 'start'
3455 if (justifyContentFlags & StyleAlignFlags::SAFE) {
3456 mJustifyContent.primary = StyleAlignFlags::START;
3460 // If packing space is negative or we only have one item, 'space-between'
3461 // falls back to 'flex-start', and 'space-around' & 'space-evenly' fall back
3462 // to 'center'. In those cases, it's simplest to just pretend we have a
3463 // different 'justify-content' value and share code.
3464 if (mPackingSpaceRemaining < 0 || aLine->NumItems() == 1) {
3465 if (mJustifyContent.primary == StyleAlignFlags::SPACE_BETWEEN) {
3466 mJustifyContent.primary = StyleAlignFlags::FLEX_START;
3467 } else if (mJustifyContent.primary == StyleAlignFlags::SPACE_AROUND ||
3468 mJustifyContent.primary == StyleAlignFlags::SPACE_EVENLY) {
3469 mJustifyContent.primary = StyleAlignFlags::CENTER;
3473 // Map 'left'/'right' to 'start'/'end'
3474 if (mJustifyContent.primary == StyleAlignFlags::LEFT ||
3475 mJustifyContent.primary == StyleAlignFlags::RIGHT) {
3476 mJustifyContent.primary =
3477 aAxisTracker.ResolveJustifyLeftRight(mJustifyContent.primary);
3480 // Map 'start'/'end' to 'flex-start'/'flex-end'.
3481 if (mJustifyContent.primary == StyleAlignFlags::START) {
3482 mJustifyContent.primary = aAxisTracker.IsMainAxisReversed()
3483 ? StyleAlignFlags::FLEX_END
3484 : StyleAlignFlags::FLEX_START;
3485 } else if (mJustifyContent.primary == StyleAlignFlags::END) {
3486 mJustifyContent.primary = aAxisTracker.IsMainAxisReversed()
3487 ? StyleAlignFlags::FLEX_START
3488 : StyleAlignFlags::FLEX_END;
3491 // Figure out how much space we'll set aside for auto margins or
3492 // packing spaces, and advance past any leading packing-space.
3493 if (mNumAutoMarginsInMainAxis == 0 && mPackingSpaceRemaining != 0 &&
3494 !aLine->IsEmpty()) {
3495 if (mJustifyContent.primary == StyleAlignFlags::FLEX_START) {
3496 // All packing space should go at the end --> nothing to do here.
3497 } else if (mJustifyContent.primary == StyleAlignFlags::FLEX_END) {
3498 // All packing space goes at the beginning
3499 mPosition += mPackingSpaceRemaining;
3500 } else if (mJustifyContent.primary == StyleAlignFlags::CENTER) {
3501 // Half the packing space goes at the beginning
3502 mPosition += mPackingSpaceRemaining / 2;
3503 } else if (mJustifyContent.primary == StyleAlignFlags::SPACE_BETWEEN ||
3504 mJustifyContent.primary == StyleAlignFlags::SPACE_AROUND ||
3505 mJustifyContent.primary == StyleAlignFlags::SPACE_EVENLY) {
3506 nsFlexContainerFrame::CalculatePackingSpace(
3507 aLine->NumItems(), mJustifyContent, &mPosition,
3508 &mNumPackingSpacesRemaining, &mPackingSpaceRemaining);
3509 } else {
3510 MOZ_ASSERT_UNREACHABLE("Unexpected justify-content value");
3514 MOZ_ASSERT(mNumPackingSpacesRemaining == 0 || mNumAutoMarginsInMainAxis == 0,
3515 "extra space should either go to packing space or to "
3516 "auto margins, but not to both");
3519 void MainAxisPositionTracker::ResolveAutoMarginsInMainAxis(FlexItem& aItem) {
3520 if (mNumAutoMarginsInMainAxis) {
3521 const auto& styleMargin = aItem.Frame()->StyleMargin()->mMargin;
3522 for (const auto side : {StartSide(), EndSide()}) {
3523 if (styleMargin.Get(mWM, side).IsAuto()) {
3524 // NOTE: This integer math will skew the distribution of remainder
3525 // app-units towards the end, which is fine.
3526 nscoord curAutoMarginSize =
3527 mPackingSpaceRemaining / mNumAutoMarginsInMainAxis;
3529 MOZ_ASSERT(aItem.GetMarginComponentForSide(side) == 0,
3530 "Expecting auto margins to have value '0' before we "
3531 "resolve them");
3532 aItem.SetMarginComponentForSide(side, curAutoMarginSize);
3534 mNumAutoMarginsInMainAxis--;
3535 mPackingSpaceRemaining -= curAutoMarginSize;
3541 void MainAxisPositionTracker::TraversePackingSpace() {
3542 if (mNumPackingSpacesRemaining) {
3543 MOZ_ASSERT(mJustifyContent.primary == StyleAlignFlags::SPACE_BETWEEN ||
3544 mJustifyContent.primary == StyleAlignFlags::SPACE_AROUND ||
3545 mJustifyContent.primary == StyleAlignFlags::SPACE_EVENLY,
3546 "mNumPackingSpacesRemaining only applies for "
3547 "space-between/space-around/space-evenly");
3549 MOZ_ASSERT(mPackingSpaceRemaining >= 0,
3550 "ran out of packing space earlier than we expected");
3552 // NOTE: This integer math will skew the distribution of remainder
3553 // app-units towards the end, which is fine.
3554 nscoord curPackingSpace =
3555 mPackingSpaceRemaining / mNumPackingSpacesRemaining;
3557 mPosition += curPackingSpace;
3558 mNumPackingSpacesRemaining--;
3559 mPackingSpaceRemaining -= curPackingSpace;
3563 CrossAxisPositionTracker::CrossAxisPositionTracker(
3564 nsTArray<FlexLine>& aLines, const ReflowInput& aReflowInput,
3565 nscoord aContentBoxCrossSize, bool aIsCrossSizeDefinite,
3566 const FlexboxAxisTracker& aAxisTracker, const nscoord aCrossGapSize)
3567 : PositionTracker(aAxisTracker.GetWritingMode(), aAxisTracker.CrossAxis(),
3568 aAxisTracker.IsCrossAxisReversed()),
3569 mAlignContent(aReflowInput.mStylePosition->mAlignContent),
3570 mCrossGapSize(aCrossGapSize) {
3571 // Extract and strip the flag bits from alignContent
3572 StyleAlignFlags alignContentFlags =
3573 mAlignContent.primary & StyleAlignFlags::FLAG_BITS;
3574 mAlignContent.primary &= ~StyleAlignFlags::FLAG_BITS;
3576 // 'normal' behaves as 'stretch'
3577 if (mAlignContent.primary == StyleAlignFlags::NORMAL) {
3578 mAlignContent.primary = StyleAlignFlags::STRETCH;
3581 const bool isSingleLine =
3582 StyleFlexWrap::Nowrap == aReflowInput.mStylePosition->mFlexWrap;
3583 if (isSingleLine) {
3584 MOZ_ASSERT(aLines.Length() == 1,
3585 "If we're styled as single-line, we should only have 1 line");
3586 // "If the flex container is single-line and has a definite cross size, the
3587 // cross size of the flex line is the flex container's inner cross size."
3589 // SOURCE: https://drafts.csswg.org/css-flexbox/#algo-cross-line
3590 // NOTE: This means (by definition) that there's no packing space, which
3591 // means we don't need to be concerned with "align-content" at all and we
3592 // can return early. This is handy, because this is the usual case (for
3593 // single-line flexbox).
3594 if (aIsCrossSizeDefinite) {
3595 aLines[0].SetLineCrossSize(aContentBoxCrossSize);
3596 return;
3599 // "If the flex container is single-line, then clamp the line's
3600 // cross-size to be within the container's computed min and max cross-size
3601 // properties."
3602 aLines[0].SetLineCrossSize(NS_CSS_MINMAX(aLines[0].LineCrossSize(),
3603 aReflowInput.ComputedMinBSize(),
3604 aReflowInput.ComputedMaxBSize()));
3607 // NOTE: The rest of this function should essentially match
3608 // MainAxisPositionTracker's constructor, though with FlexLines instead of
3609 // FlexItems, and with the additional value "stretch" (and of course with
3610 // cross sizes instead of main sizes.)
3612 // Figure out how much packing space we have (container's cross size minus
3613 // all the lines' cross sizes). Also, share this loop to count how many
3614 // lines we have. (We need that count in some cases below.)
3615 mPackingSpaceRemaining = aContentBoxCrossSize;
3616 uint32_t numLines = 0;
3617 for (FlexLine& line : aLines) {
3618 mPackingSpaceRemaining -= line.LineCrossSize();
3619 numLines++;
3622 // Subtract space required for row/col gap from the remaining packing space
3623 MOZ_ASSERT(numLines >= 1,
3624 "GenerateFlexLines should've produced at least 1 line");
3625 mPackingSpaceRemaining -= aCrossGapSize * (numLines - 1);
3627 // If <overflow-position> is 'safe' and packing space is negative
3628 // all align options fall back to 'start'
3629 if ((alignContentFlags & StyleAlignFlags::SAFE) &&
3630 mPackingSpaceRemaining < 0) {
3631 mAlignContent.primary = StyleAlignFlags::START;
3634 // If packing space is negative, 'space-between' and 'stretch' behave like
3635 // 'flex-start', and 'space-around' and 'space-evenly' behave like 'center'.
3636 // In those cases, it's simplest to just pretend we have a different
3637 // 'align-content' value and share code. (If we only have one line, all of
3638 // the 'space-*' keywords fall back as well, but 'stretch' doesn't because
3639 // even a single line can still stretch.)
3640 if (mPackingSpaceRemaining < 0 &&
3641 mAlignContent.primary == StyleAlignFlags::STRETCH) {
3642 mAlignContent.primary = StyleAlignFlags::FLEX_START;
3643 } else if (mPackingSpaceRemaining < 0 || numLines == 1) {
3644 if (mAlignContent.primary == StyleAlignFlags::SPACE_BETWEEN) {
3645 mAlignContent.primary = StyleAlignFlags::FLEX_START;
3646 } else if (mAlignContent.primary == StyleAlignFlags::SPACE_AROUND ||
3647 mAlignContent.primary == StyleAlignFlags::SPACE_EVENLY) {
3648 mAlignContent.primary = StyleAlignFlags::CENTER;
3652 // Map 'start'/'end' to 'flex-start'/'flex-end'.
3653 if (mAlignContent.primary == StyleAlignFlags::START) {
3654 mAlignContent.primary = aAxisTracker.IsCrossAxisReversed()
3655 ? StyleAlignFlags::FLEX_END
3656 : StyleAlignFlags::FLEX_START;
3657 } else if (mAlignContent.primary == StyleAlignFlags::END) {
3658 mAlignContent.primary = aAxisTracker.IsCrossAxisReversed()
3659 ? StyleAlignFlags::FLEX_START
3660 : StyleAlignFlags::FLEX_END;
3663 // Figure out how much space we'll set aside for packing spaces, and advance
3664 // past any leading packing-space.
3665 if (mPackingSpaceRemaining != 0) {
3666 if (mAlignContent.primary == StyleAlignFlags::BASELINE ||
3667 mAlignContent.primary == StyleAlignFlags::LAST_BASELINE) {
3668 // TODO: Bug 1480850 will implement 'align-content: [first/last] baseline'
3669 // for flexbox. Until then, behaves as if align-content is 'flex-start' by
3670 // doing nothing.
3671 } else if (mAlignContent.primary == StyleAlignFlags::FLEX_START) {
3672 // All packing space should go at the end --> nothing to do here.
3673 } else if (mAlignContent.primary == StyleAlignFlags::FLEX_END) {
3674 // All packing space goes at the beginning
3675 mPosition += mPackingSpaceRemaining;
3676 } else if (mAlignContent.primary == StyleAlignFlags::CENTER) {
3677 // Half the packing space goes at the beginning
3678 mPosition += mPackingSpaceRemaining / 2;
3679 } else if (mAlignContent.primary == StyleAlignFlags::SPACE_BETWEEN ||
3680 mAlignContent.primary == StyleAlignFlags::SPACE_AROUND ||
3681 mAlignContent.primary == StyleAlignFlags::SPACE_EVENLY) {
3682 nsFlexContainerFrame::CalculatePackingSpace(
3683 numLines, mAlignContent, &mPosition, &mNumPackingSpacesRemaining,
3684 &mPackingSpaceRemaining);
3685 } else if (mAlignContent.primary == StyleAlignFlags::STRETCH) {
3686 // Split space equally between the lines:
3687 MOZ_ASSERT(mPackingSpaceRemaining > 0,
3688 "negative packing space should make us use 'flex-start' "
3689 "instead of 'stretch' (and we shouldn't bother with this "
3690 "code if we have 0 packing space)");
3692 uint32_t numLinesLeft = numLines;
3693 for (FlexLine& line : aLines) {
3694 // Our share is the amount of space remaining, divided by the number
3695 // of lines remainig.
3696 MOZ_ASSERT(numLinesLeft > 0, "miscalculated num lines");
3697 nscoord shareOfExtraSpace = mPackingSpaceRemaining / numLinesLeft;
3698 nscoord newSize = line.LineCrossSize() + shareOfExtraSpace;
3699 line.SetLineCrossSize(newSize);
3701 mPackingSpaceRemaining -= shareOfExtraSpace;
3702 numLinesLeft--;
3704 MOZ_ASSERT(numLinesLeft == 0, "miscalculated num lines");
3705 } else {
3706 MOZ_ASSERT_UNREACHABLE("Unexpected align-content value");
3711 void CrossAxisPositionTracker::TraversePackingSpace() {
3712 if (mNumPackingSpacesRemaining) {
3713 MOZ_ASSERT(mAlignContent.primary == StyleAlignFlags::SPACE_BETWEEN ||
3714 mAlignContent.primary == StyleAlignFlags::SPACE_AROUND ||
3715 mAlignContent.primary == StyleAlignFlags::SPACE_EVENLY,
3716 "mNumPackingSpacesRemaining only applies for "
3717 "space-between/space-around/space-evenly");
3719 MOZ_ASSERT(mPackingSpaceRemaining >= 0,
3720 "ran out of packing space earlier than we expected");
3722 // NOTE: This integer math will skew the distribution of remainder
3723 // app-units towards the end, which is fine.
3724 nscoord curPackingSpace =
3725 mPackingSpaceRemaining / mNumPackingSpacesRemaining;
3727 mPosition += curPackingSpace;
3728 mNumPackingSpacesRemaining--;
3729 mPackingSpaceRemaining -= curPackingSpace;
3733 SingleLineCrossAxisPositionTracker::SingleLineCrossAxisPositionTracker(
3734 const FlexboxAxisTracker& aAxisTracker)
3735 : PositionTracker(aAxisTracker.GetWritingMode(), aAxisTracker.CrossAxis(),
3736 aAxisTracker.IsCrossAxisReversed()) {}
3738 void FlexLine::ComputeCrossSizeAndBaseline(
3739 const FlexboxAxisTracker& aAxisTracker) {
3740 // NOTE: in these "cross{Start,End}ToFurthest{First,Last}Baseline" variables,
3741 // the "first/last" term is referring to the flex *line's* baseline-sharing
3742 // groups, which may or may not match any flex *item's* exact align-self
3743 // value. See the code that sets FlexItem::mBaselineSharingGroup for more
3744 // details.
3745 nscoord crossStartToFurthestFirstBaseline = nscoord_MIN;
3746 nscoord crossEndToFurthestFirstBaseline = nscoord_MIN;
3747 nscoord crossStartToFurthestLastBaseline = nscoord_MIN;
3748 nscoord crossEndToFurthestLastBaseline = nscoord_MIN;
3750 nscoord largestOuterCrossSize = 0;
3751 for (const FlexItem& item : Items()) {
3752 nscoord curOuterCrossSize = item.OuterCrossSize();
3754 if ((item.AlignSelf()._0 == StyleAlignFlags::BASELINE ||
3755 item.AlignSelf()._0 == StyleAlignFlags::LAST_BASELINE) &&
3756 item.NumAutoMarginsInCrossAxis() == 0) {
3757 const bool usingItemFirstBaseline =
3758 (item.AlignSelf()._0 == StyleAlignFlags::BASELINE);
3760 // Find distance from our item's cross-start and cross-end margin-box
3761 // edges to its baseline.
3763 // Here's a diagram of a flex-item that we might be doing this on.
3764 // "mmm" is the margin-box, "bbb" is the border-box. The bottom of
3765 // the text "BASE" is the baseline.
3767 // ---(cross-start)---
3768 // ___ ___ ___
3769 // mmmmmmmmmmmm | |margin-start |
3770 // m m | _|_ ___ |
3771 // m bbbbbbbb m |curOuterCrossSize | |crossStartToBaseline
3772 // m b b m | |ascent |
3773 // m b BASE b m | _|_ _|_
3774 // m b b m | |
3775 // m bbbbbbbb m | |crossEndToBaseline
3776 // m m | |
3777 // mmmmmmmmmmmm _|_ _|_
3779 // ---(cross-end)---
3781 // We already have the curOuterCrossSize, margin-start, and the ascent.
3782 // * We can get crossStartToBaseline by adding margin-start + ascent.
3783 // * If we subtract that from the curOuterCrossSize, we get
3784 // crossEndToBaseline.
3786 nscoord crossStartToBaseline = item.BaselineOffsetFromOuterCrossEdge(
3787 aAxisTracker.CrossAxisPhysicalStartSide(), usingItemFirstBaseline);
3788 nscoord crossEndToBaseline = curOuterCrossSize - crossStartToBaseline;
3790 // Now, update our "largest" values for these (across all the flex items
3791 // in this flex line), so we can use them in computing the line's cross
3792 // size below:
3793 if (item.ItemBaselineSharingGroup() == BaselineSharingGroup::First) {
3794 crossStartToFurthestFirstBaseline =
3795 std::max(crossStartToFurthestFirstBaseline, crossStartToBaseline);
3796 crossEndToFurthestFirstBaseline =
3797 std::max(crossEndToFurthestFirstBaseline, crossEndToBaseline);
3798 } else {
3799 crossStartToFurthestLastBaseline =
3800 std::max(crossStartToFurthestLastBaseline, crossStartToBaseline);
3801 crossEndToFurthestLastBaseline =
3802 std::max(crossEndToFurthestLastBaseline, crossEndToBaseline);
3804 } else {
3805 largestOuterCrossSize =
3806 std::max(largestOuterCrossSize, curOuterCrossSize);
3810 // The line's baseline offset is the distance from the line's edge to the
3811 // furthest item-baseline. The item(s) with that baseline will be exactly
3812 // aligned with the line's edge.
3813 mFirstBaselineOffset = crossStartToFurthestFirstBaseline;
3814 mLastBaselineOffset = crossEndToFurthestLastBaseline;
3816 // The line's cross-size is the larger of:
3817 // (a) [largest cross-start-to-baseline + largest baseline-to-cross-end] of
3818 // all baseline-aligned items with no cross-axis auto margins...
3819 // and
3820 // (b) [largest cross-start-to-baseline + largest baseline-to-cross-end] of
3821 // all last baseline-aligned items with no cross-axis auto margins...
3822 // and
3823 // (c) largest cross-size of all other children.
3824 mLineCrossSize = std::max(
3825 std::max(
3826 crossStartToFurthestFirstBaseline + crossEndToFurthestFirstBaseline,
3827 crossStartToFurthestLastBaseline + crossEndToFurthestLastBaseline),
3828 largestOuterCrossSize);
3831 nscoord FlexLine::ExtractBaselineOffset(
3832 BaselineSharingGroup aBaselineGroup) const {
3833 auto LastBaselineOffsetFromStartEdge = [this]() {
3834 // Convert the distance to be relative from the line's cross-start edge.
3835 const nscoord offset = LastBaselineOffset();
3836 return offset != nscoord_MIN ? LineCrossSize() - offset : offset;
3839 auto PrimaryBaseline = [=]() {
3840 return aBaselineGroup == BaselineSharingGroup::First
3841 ? FirstBaselineOffset()
3842 : LastBaselineOffsetFromStartEdge();
3844 auto SecondaryBaseline = [=]() {
3845 return aBaselineGroup == BaselineSharingGroup::First
3846 ? LastBaselineOffsetFromStartEdge()
3847 : FirstBaselineOffset();
3850 const nscoord primaryBaseline = PrimaryBaseline();
3851 if (primaryBaseline != nscoord_MIN) {
3852 return primaryBaseline;
3854 return SecondaryBaseline();
3857 void FlexItem::ResolveStretchedCrossSize(nscoord aLineCrossSize) {
3858 // We stretch IFF we are align-self:stretch, have no auto margins in
3859 // cross axis, and have cross-axis size property == "auto". If any of those
3860 // conditions don't hold up, we won't stretch.
3861 if (mAlignSelf._0 != StyleAlignFlags::STRETCH ||
3862 NumAutoMarginsInCrossAxis() != 0 || !IsCrossSizeAuto()) {
3863 return;
3866 // If we've already been stretched, we can bail out early, too.
3867 // No need to redo the calculation.
3868 if (mIsStretched) {
3869 return;
3872 // Reserve space for margins & border & padding, and then use whatever
3873 // remains as our item's cross-size (clamped to its min/max range).
3874 nscoord stretchedSize = aLineCrossSize - MarginBorderPaddingSizeInCrossAxis();
3876 stretchedSize = NS_CSS_MINMAX(stretchedSize, mCrossMinSize, mCrossMaxSize);
3878 // Update the cross-size & make a note that it's stretched, so we know to
3879 // override the reflow input's computed cross-size in our final reflow.
3880 SetCrossSize(stretchedSize);
3881 mIsStretched = true;
3884 static nsBlockFrame* FindFlexItemBlockFrame(nsIFrame* aFrame) {
3885 if (nsBlockFrame* block = do_QueryFrame(aFrame)) {
3886 return block;
3888 for (nsIFrame* f : aFrame->PrincipalChildList()) {
3889 if (nsBlockFrame* block = FindFlexItemBlockFrame(f)) {
3890 return block;
3893 return nullptr;
3896 nsBlockFrame* FlexItem::BlockFrame() const {
3897 return FindFlexItemBlockFrame(Frame());
3900 void SingleLineCrossAxisPositionTracker::ResolveAutoMarginsInCrossAxis(
3901 const FlexLine& aLine, FlexItem& aItem) {
3902 // Subtract the space that our item is already occupying, to see how much
3903 // space (if any) is available for its auto margins.
3904 nscoord spaceForAutoMargins = aLine.LineCrossSize() - aItem.OuterCrossSize();
3906 if (spaceForAutoMargins <= 0) {
3907 return; // No available space --> nothing to do
3910 uint32_t numAutoMargins = aItem.NumAutoMarginsInCrossAxis();
3911 if (numAutoMargins == 0) {
3912 return; // No auto margins --> nothing to do.
3915 // OK, we have at least one auto margin and we have some available space.
3916 // Give each auto margin a share of the space.
3917 const auto& styleMargin = aItem.Frame()->StyleMargin()->mMargin;
3918 for (const auto side : {StartSide(), EndSide()}) {
3919 if (styleMargin.Get(mWM, side).IsAuto()) {
3920 MOZ_ASSERT(aItem.GetMarginComponentForSide(side) == 0,
3921 "Expecting auto margins to have value '0' before we "
3922 "update them");
3924 // NOTE: integer divison is fine here; numAutoMargins is either 1 or 2.
3925 // If it's 2 & spaceForAutoMargins is odd, 1st margin gets smaller half.
3926 nscoord curAutoMarginSize = spaceForAutoMargins / numAutoMargins;
3927 aItem.SetMarginComponentForSide(side, curAutoMarginSize);
3928 numAutoMargins--;
3929 spaceForAutoMargins -= curAutoMarginSize;
3934 void SingleLineCrossAxisPositionTracker::EnterAlignPackingSpace(
3935 const FlexLine& aLine, const FlexItem& aItem,
3936 const FlexboxAxisTracker& aAxisTracker) {
3937 // We don't do align-self alignment on items that have auto margins
3938 // in the cross axis.
3939 if (aItem.NumAutoMarginsInCrossAxis()) {
3940 return;
3943 StyleAlignFlags alignSelf = aItem.AlignSelf()._0;
3944 // NOTE: 'stretch' behaves like 'flex-start' once we've stretched any
3945 // auto-sized items (which we've already done).
3946 if (alignSelf == StyleAlignFlags::STRETCH) {
3947 alignSelf = StyleAlignFlags::FLEX_START;
3950 // Map 'self-start'/'self-end' to 'start'/'end'
3951 if (alignSelf == StyleAlignFlags::SELF_START ||
3952 alignSelf == StyleAlignFlags::SELF_END) {
3953 const LogicalAxis logCrossAxis =
3954 aAxisTracker.IsRowOriented() ? eLogicalAxisBlock : eLogicalAxisInline;
3955 const WritingMode cWM = aAxisTracker.GetWritingMode();
3956 const bool sameStart =
3957 cWM.ParallelAxisStartsOnSameSide(logCrossAxis, aItem.GetWritingMode());
3958 alignSelf = sameStart == (alignSelf == StyleAlignFlags::SELF_START)
3959 ? StyleAlignFlags::START
3960 : StyleAlignFlags::END;
3963 // Map 'start'/'end' to 'flex-start'/'flex-end'.
3964 if (alignSelf == StyleAlignFlags::START) {
3965 alignSelf = aAxisTracker.IsCrossAxisReversed()
3966 ? StyleAlignFlags::FLEX_END
3967 : StyleAlignFlags::FLEX_START;
3968 } else if (alignSelf == StyleAlignFlags::END) {
3969 alignSelf = aAxisTracker.IsCrossAxisReversed() ? StyleAlignFlags::FLEX_START
3970 : StyleAlignFlags::FLEX_END;
3973 // 'align-self' falls back to 'flex-start' if it is 'center'/'flex-end' and we
3974 // have cross axis overflow
3975 // XXX we should really be falling back to 'start' as of bug 1472843
3976 if (aLine.LineCrossSize() < aItem.OuterCrossSize() &&
3977 (aItem.AlignSelfFlags() & StyleAlignFlags::SAFE)) {
3978 alignSelf = StyleAlignFlags::FLEX_START;
3981 if (alignSelf == StyleAlignFlags::FLEX_START) {
3982 // No space to skip over -- we're done.
3983 } else if (alignSelf == StyleAlignFlags::FLEX_END) {
3984 mPosition += aLine.LineCrossSize() - aItem.OuterCrossSize();
3985 } else if (alignSelf == StyleAlignFlags::CENTER) {
3986 // Note: If cross-size is odd, the "after" space will get the extra unit.
3987 mPosition += (aLine.LineCrossSize() - aItem.OuterCrossSize()) / 2;
3988 } else if (alignSelf == StyleAlignFlags::BASELINE ||
3989 alignSelf == StyleAlignFlags::LAST_BASELINE) {
3990 const bool usingItemFirstBaseline =
3991 (alignSelf == StyleAlignFlags::BASELINE);
3993 // The first-baseline sharing group gets (collectively) aligned to the
3994 // FlexLine's cross-start side, and similarly the last-baseline sharing
3995 // group gets snapped to the cross-end side.
3996 const bool isFirstBaselineSharingGroup =
3997 aItem.ItemBaselineSharingGroup() == BaselineSharingGroup::First;
3998 const mozilla::Side alignSide =
3999 isFirstBaselineSharingGroup ? aAxisTracker.CrossAxisPhysicalStartSide()
4000 : aAxisTracker.CrossAxisPhysicalEndSide();
4002 // To compute the aligned position for our flex item, we determine:
4003 // (1) The distance from the item's alignSide edge to the item's relevant
4004 // baseline.
4005 nscoord itemBaselineOffset = aItem.BaselineOffsetFromOuterCrossEdge(
4006 alignSide, usingItemFirstBaseline);
4008 // (2) The distance between the FlexLine's alignSide edge and the relevant
4009 // baseline-sharing-group's baseline position.
4010 nscoord lineBaselineOffset = isFirstBaselineSharingGroup
4011 ? aLine.FirstBaselineOffset()
4012 : aLine.LastBaselineOffset();
4014 NS_ASSERTION(lineBaselineOffset >= itemBaselineOffset,
4015 "failed at finding largest baseline offset");
4017 // (3) The difference between the above offsets, which tells us how far we
4018 // need to shift the item away from the FlexLine's alignSide edge so
4019 // that its baseline is at the proper position for its group.
4020 nscoord itemOffsetFromLineEdge = lineBaselineOffset - itemBaselineOffset;
4022 if (isFirstBaselineSharingGroup) {
4023 // alignSide is the line's cross-start edge. mPosition is already there.
4024 // From there, we step *forward* by the baseline adjustment:
4025 mPosition += itemOffsetFromLineEdge;
4026 } else {
4027 // alignSide is the line's cross-end edge. Advance mPosition to align
4028 // item with that edge (as in FLEX_END case)...
4029 mPosition += aLine.LineCrossSize() - aItem.OuterCrossSize();
4030 // ...and step *back* by the baseline adjustment:
4031 mPosition -= itemOffsetFromLineEdge;
4033 } else {
4034 MOZ_ASSERT_UNREACHABLE("Unexpected align-self value");
4038 FlexboxAxisInfo::FlexboxAxisInfo(const nsIFrame* aFlexContainer) {
4039 MOZ_ASSERT(aFlexContainer && aFlexContainer->IsFlexContainerFrame(),
4040 "Only flex containers may be passed to this constructor!");
4041 if (IsLegacyBox(aFlexContainer)) {
4042 InitAxesFromLegacyProps(aFlexContainer);
4043 } else {
4044 InitAxesFromModernProps(aFlexContainer);
4048 void FlexboxAxisInfo::InitAxesFromLegacyProps(const nsIFrame* aFlexContainer) {
4049 const nsStyleXUL* styleXUL = aFlexContainer->StyleXUL();
4051 const bool boxOrientIsVertical =
4052 styleXUL->mBoxOrient == StyleBoxOrient::Vertical;
4053 const bool wmIsVertical = aFlexContainer->GetWritingMode().IsVertical();
4055 // If box-orient agrees with our writing-mode, then we're "row-oriented"
4056 // (i.e. the flexbox main axis is the same as our writing mode's inline
4057 // direction). Otherwise, we're column-oriented (i.e. the flexbox's main
4058 // axis is perpendicular to the writing-mode's inline direction).
4059 mIsRowOriented = (boxOrientIsVertical == wmIsVertical);
4061 // Legacy flexbox can use "-webkit-box-direction: reverse" to reverse the
4062 // main axis (so it runs in the reverse direction of the inline axis):
4063 mIsMainAxisReversed = styleXUL->mBoxDirection == StyleBoxDirection::Reverse;
4065 // Legacy flexbox does not support reversing the cross axis -- it has no
4066 // equivalent of modern flexbox's "flex-wrap: wrap-reverse".
4067 mIsCrossAxisReversed = false;
4070 void FlexboxAxisInfo::InitAxesFromModernProps(const nsIFrame* aFlexContainer) {
4071 const nsStylePosition* stylePos = aFlexContainer->StylePosition();
4072 StyleFlexDirection flexDirection = stylePos->mFlexDirection;
4074 // Determine main axis:
4075 switch (flexDirection) {
4076 case StyleFlexDirection::Row:
4077 mIsRowOriented = true;
4078 mIsMainAxisReversed = false;
4079 break;
4080 case StyleFlexDirection::RowReverse:
4081 mIsRowOriented = true;
4082 mIsMainAxisReversed = true;
4083 break;
4084 case StyleFlexDirection::Column:
4085 mIsRowOriented = false;
4086 mIsMainAxisReversed = false;
4087 break;
4088 case StyleFlexDirection::ColumnReverse:
4089 mIsRowOriented = false;
4090 mIsMainAxisReversed = true;
4091 break;
4094 // "flex-wrap: wrap-reverse" reverses our cross axis.
4095 mIsCrossAxisReversed = stylePos->mFlexWrap == StyleFlexWrap::WrapReverse;
4098 FlexboxAxisTracker::FlexboxAxisTracker(
4099 const nsFlexContainerFrame* aFlexContainer)
4100 : mWM(aFlexContainer->GetWritingMode()), mAxisInfo(aFlexContainer) {}
4102 LogicalSide FlexboxAxisTracker::MainAxisStartSide() const {
4103 return MakeLogicalSide(
4104 MainAxis(), IsMainAxisReversed() ? eLogicalEdgeEnd : eLogicalEdgeStart);
4107 LogicalSide FlexboxAxisTracker::CrossAxisStartSide() const {
4108 return MakeLogicalSide(
4109 CrossAxis(), IsCrossAxisReversed() ? eLogicalEdgeEnd : eLogicalEdgeStart);
4112 void nsFlexContainerFrame::GenerateFlexLines(
4113 const ReflowInput& aReflowInput, const nscoord aTentativeContentBoxMainSize,
4114 const nscoord aTentativeContentBoxCrossSize,
4115 const nsTArray<StrutInfo>& aStruts, const FlexboxAxisTracker& aAxisTracker,
4116 nscoord aMainGapSize, nsTArray<nsIFrame*>& aPlaceholders,
4117 nsTArray<FlexLine>& aLines, bool& aHasCollapsedItems) {
4118 MOZ_ASSERT(aLines.IsEmpty(), "Expecting outparam to start out empty");
4120 auto ConstructNewFlexLine = [&aLines, aMainGapSize]() {
4121 return aLines.EmplaceBack(aMainGapSize);
4124 const bool isSingleLine =
4125 StyleFlexWrap::Nowrap == aReflowInput.mStylePosition->mFlexWrap;
4127 // We have at least one FlexLine. Even an empty flex container has a single
4128 // (empty) flex line.
4129 FlexLine* curLine = ConstructNewFlexLine();
4131 nscoord wrapThreshold;
4132 if (isSingleLine) {
4133 // Not wrapping. Set threshold to sentinel value that tells us not to wrap.
4134 wrapThreshold = NS_UNCONSTRAINEDSIZE;
4135 } else {
4136 // Wrapping! Set wrap threshold to flex container's content-box main-size.
4137 wrapThreshold = aTentativeContentBoxMainSize;
4139 // If the flex container doesn't have a definite content-box main-size
4140 // (e.g. if main axis is vertical & 'height' is 'auto'), make sure we at
4141 // least wrap when we hit its max main-size.
4142 if (wrapThreshold == NS_UNCONSTRAINEDSIZE) {
4143 const nscoord flexContainerMaxMainSize =
4144 aAxisTracker.MainComponent(aReflowInput.ComputedMaxSize());
4145 wrapThreshold = flexContainerMaxMainSize;
4149 // Tracks the index of the next strut, in aStruts (and when this hits
4150 // aStruts.Length(), that means there are no more struts):
4151 uint32_t nextStrutIdx = 0;
4153 // Overall index of the current flex item in the flex container. (This gets
4154 // checked against entries in aStruts.)
4155 uint32_t itemIdxInContainer = 0;
4157 CSSOrderAwareFrameIterator iter(
4158 this, FrameChildListID::Principal,
4159 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
4160 CSSOrderAwareFrameIterator::OrderState::Unknown,
4161 OrderingPropertyForIter(this));
4163 AddOrRemoveStateBits(NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER,
4164 iter.ItemsAreAlreadyInOrder());
4166 const bool useMozBoxCollapseBehavior =
4167 StyleVisibility()->UseLegacyCollapseBehavior();
4169 for (; !iter.AtEnd(); iter.Next()) {
4170 nsIFrame* childFrame = *iter;
4171 // Don't create flex items / lines for placeholder frames:
4172 if (childFrame->IsPlaceholderFrame()) {
4173 aPlaceholders.AppendElement(childFrame);
4174 continue;
4177 const bool collapsed = childFrame->StyleVisibility()->IsCollapse();
4178 aHasCollapsedItems = aHasCollapsedItems || collapsed;
4180 if (useMozBoxCollapseBehavior && collapsed) {
4181 // Legacy visibility:collapse behavior: make a 0-sized strut. (No need to
4182 // bother with aStruts and remembering cross size.)
4183 curLine->Items().EmplaceBack(childFrame, 0, aReflowInput.GetWritingMode(),
4184 aAxisTracker);
4185 } else if (nextStrutIdx < aStruts.Length() &&
4186 aStruts[nextStrutIdx].mItemIdx == itemIdxInContainer) {
4187 // Use the simplified "strut" FlexItem constructor:
4188 curLine->Items().EmplaceBack(childFrame,
4189 aStruts[nextStrutIdx].mStrutCrossSize,
4190 aReflowInput.GetWritingMode(), aAxisTracker);
4191 nextStrutIdx++;
4192 } else {
4193 GenerateFlexItemForChild(*curLine, childFrame, aReflowInput, aAxisTracker,
4194 aTentativeContentBoxCrossSize);
4197 // Check if we need to wrap the newly appended item to a new line, i.e. if
4198 // its outer hypothetical main size pushes our line over the threshold.
4199 // But we don't wrap if the line-length is unconstrained, nor do we wrap if
4200 // this was the first item on the line.
4201 if (wrapThreshold != NS_UNCONSTRAINEDSIZE &&
4202 curLine->Items().Length() > 1) {
4203 // If the line will be longer than wrapThreshold or at least as long as
4204 // nscoord_MAX because of the newly appended item, then wrap and move the
4205 // item to a new line.
4206 auto newOuterSize = curLine->TotalOuterHypotheticalMainSize();
4207 newOuterSize += curLine->Items().LastElement().OuterMainSize();
4209 // Account for gap between this line's previous item and this item.
4210 newOuterSize += aMainGapSize;
4212 if (newOuterSize >= nscoord_MAX || newOuterSize > wrapThreshold) {
4213 curLine = ConstructNewFlexLine();
4215 // Get the previous line after adding a new line because the address can
4216 // change if nsTArray needs to reallocate a new space for the new line.
4217 FlexLine& prevLine = aLines[aLines.Length() - 2];
4219 // Move the item from the end of prevLine to the end of curLine.
4220 curLine->Items().AppendElement(prevLine.Items().PopLastElement());
4224 // Update the line's bookkeeping about how large its items collectively are.
4225 curLine->AddLastItemToMainSizeTotals();
4226 itemIdxInContainer++;
4230 nsFlexContainerFrame::FlexLayoutResult
4231 nsFlexContainerFrame::GenerateFlexLayoutResult() {
4232 MOZ_ASSERT(GetPrevInFlow(), "This should be called by non-first-in-flows!");
4234 auto* data = FirstInFlow()->GetProperty(SharedFlexData::Prop());
4235 MOZ_ASSERT(data, "SharedFlexData should be set by our first-in-flow!");
4237 FlexLayoutResult flr;
4239 // The order state of the children is consistent across entire continuation
4240 // chain due to calling nsContainerFrame::NormalizeChildLists() at the
4241 // beginning of Reflow(), so we can align our state bit with our
4242 // prev-in-flow's state. Setup here before calling OrderStateForIter() below.
4243 AddOrRemoveStateBits(NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER,
4244 GetPrevInFlow()->HasAnyStateBits(
4245 NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER));
4247 // Construct flex items for this flex container fragment from existing flex
4248 // items in SharedFlexData.
4249 CSSOrderAwareFrameIterator iter(
4250 this, FrameChildListID::Principal,
4251 CSSOrderAwareFrameIterator::ChildFilter::SkipPlaceholders,
4252 OrderStateForIter(this), OrderingPropertyForIter(this));
4254 auto ConstructNewFlexLine = [&flr]() {
4255 // Use zero main gap size since it doesn't matter in flex container's
4256 // next-in-flows. We've computed flex items' positions in first-in-flow.
4257 return flr.mLines.EmplaceBack(0);
4260 // We have at least one FlexLine. Even an empty flex container has a single
4261 // (empty) flex line.
4262 FlexLine* currentLine = ConstructNewFlexLine();
4264 if (!iter.AtEnd()) {
4265 nsIFrame* child = *iter;
4266 nsIFrame* childFirstInFlow = child->FirstInFlow();
4268 // We are iterating nested for-loops over the FlexLines and FlexItems
4269 // generated by GenerateFlexLines() and cached in flex container's
4270 // first-in-flow. For each flex item, check if its frame (must be a
4271 // first-in-flow) is the first-in-flow of the first child frame in this flex
4272 // container continuation. If so, clone the data from that FlexItem into a
4273 // FlexLine. When we find a match for the item, we know that the next child
4274 // frame might have its first-in-flow as the next item in the same original
4275 // line. In this case, we'll put the cloned data in the same line here as
4276 // well.
4277 for (const FlexLine& line : data->mLines) {
4278 // If currentLine is empty, either it is the first line, or all the items
4279 // in the previous line have been placed in our prev-in-flows. No need to
4280 // construct a new line.
4281 if (!currentLine->IsEmpty()) {
4282 currentLine = ConstructNewFlexLine();
4284 for (const FlexItem& item : line.Items()) {
4285 if (item.Frame() == childFirstInFlow) {
4286 currentLine->Items().AppendElement(item.CloneFor(child));
4287 iter.Next();
4288 if (iter.AtEnd()) {
4289 // We've constructed flex items for all children. No need to check
4290 // rest of the items.
4291 child = childFirstInFlow = nullptr;
4292 break;
4294 child = *iter;
4295 childFirstInFlow = child->FirstInFlow();
4298 if (iter.AtEnd()) {
4299 // We've constructed flex items for all children. No need to check
4300 // rest of the lines.
4301 break;
4306 flr.mContentBoxMainSize = data->mContentBoxMainSize;
4307 flr.mContentBoxCrossSize = data->mContentBoxCrossSize;
4309 return flr;
4312 // Returns the largest outer hypothetical main-size of any line in |aLines|.
4313 // (i.e. the hypothetical main-size of the largest line)
4314 static AuCoord64 GetLargestLineMainSize(nsTArray<FlexLine>& aLines) {
4315 AuCoord64 largestLineOuterSize = 0;
4316 for (const FlexLine& line : aLines) {
4317 largestLineOuterSize =
4318 std::max(largestLineOuterSize, line.TotalOuterHypotheticalMainSize());
4320 return largestLineOuterSize;
4323 nscoord nsFlexContainerFrame::ComputeMainSize(
4324 const ReflowInput& aReflowInput, const FlexboxAxisTracker& aAxisTracker,
4325 const nscoord aTentativeContentBoxMainSize,
4326 nsTArray<FlexLine>& aLines) const {
4327 if (aAxisTracker.IsRowOriented()) {
4328 // Row-oriented --> our main axis is the inline axis, so our main size
4329 // is our inline size (which should already be resolved).
4330 return aTentativeContentBoxMainSize;
4333 const bool shouldApplyAutomaticMinimumOnBlockAxis =
4334 aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis();
4335 if (aTentativeContentBoxMainSize != NS_UNCONSTRAINEDSIZE &&
4336 !shouldApplyAutomaticMinimumOnBlockAxis) {
4337 // Column-oriented case, with fixed BSize:
4338 // Just use our fixed block-size because we always assume the available
4339 // block-size is unconstrained, and the reflow input has already done the
4340 // appropriate min/max-BSize clamping.
4341 return aTentativeContentBoxMainSize;
4344 // Column-oriented case, with size-containment in block axis:
4345 // Behave as if we had no content and just use our MinBSize.
4346 if (Maybe<nscoord> containBSize =
4347 aReflowInput.mFrame->ContainIntrinsicBSize()) {
4348 return NS_CSS_MINMAX(*containBSize, aReflowInput.ComputedMinBSize(),
4349 aReflowInput.ComputedMaxBSize());
4352 const AuCoord64 largestLineMainSize = GetLargestLineMainSize(aLines);
4353 const nscoord contentBSize = NS_CSS_MINMAX(
4354 nscoord(largestLineMainSize.ToMinMaxClamped()),
4355 aReflowInput.ComputedMinBSize(), aReflowInput.ComputedMaxBSize());
4356 // If the clamped largest FlexLine length is larger than the tentative main
4357 // size (which is resolved by aspect-ratio), we extend it to contain the
4358 // entire FlexLine.
4359 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
4360 if (shouldApplyAutomaticMinimumOnBlockAxis) {
4361 // Column-oriented case, with auto BSize which is resolved by
4362 // aspect-ratio.
4363 return std::max(contentBSize, aTentativeContentBoxMainSize);
4366 // Column-oriented case, with auto BSize:
4367 // Resolve auto BSize to the largest FlexLine length, clamped to our
4368 // computed min/max main-size properties.
4369 return contentBSize;
4372 nscoord nsFlexContainerFrame::ComputeCrossSize(
4373 const ReflowInput& aReflowInput, const FlexboxAxisTracker& aAxisTracker,
4374 const nscoord aTentativeContentBoxCrossSize, nscoord aSumLineCrossSizes,
4375 bool* aIsDefinite) const {
4376 MOZ_ASSERT(aIsDefinite, "outparam pointer must be non-null");
4378 if (aAxisTracker.IsColumnOriented()) {
4379 // Column-oriented --> our cross axis is the inline axis, so our cross size
4380 // is our inline size (which should already be resolved).
4381 *aIsDefinite = true;
4382 // FIXME: Bug 1661847 - there are cases where aTentativeContentBoxCrossSize
4383 // (i.e. aReflowInput.ComputedISize()) might not be the right thing to
4384 // return here. Specifically: if our cross size is an intrinsic size, and we
4385 // have flex items that are flexible and have aspect ratios, then we may
4386 // need to take their post-flexing main sizes into account (multiplied
4387 // through their aspect ratios to get their cross sizes), in order to
4388 // determine their flex line's size & the flex container's cross size (e.g.
4389 // as `aSumLineCrossSizes`).
4390 return aTentativeContentBoxCrossSize;
4393 const bool shouldApplyAutomaticMinimumOnBlockAxis =
4394 aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis();
4395 const nscoord computedBSize = aReflowInput.ComputedBSize();
4396 if (computedBSize != NS_UNCONSTRAINEDSIZE &&
4397 !shouldApplyAutomaticMinimumOnBlockAxis) {
4398 // Row-oriented case (cross axis is block-axis), with fixed BSize:
4399 *aIsDefinite = true;
4401 // Just use our fixed block-size because we always assume the available
4402 // block-size is unconstrained, and the reflow input has already done the
4403 // appropriate min/max-BSize clamping.
4404 return computedBSize;
4407 // Row-oriented case, with size-containment in block axis:
4408 // Behave as if we had no content and just use our MinBSize.
4409 if (Maybe<nscoord> containBSize =
4410 aReflowInput.mFrame->ContainIntrinsicBSize()) {
4411 *aIsDefinite = true;
4412 return NS_CSS_MINMAX(*containBSize, aReflowInput.ComputedMinBSize(),
4413 aReflowInput.ComputedMaxBSize());
4416 // The cross size must not be definite in the following cases.
4417 *aIsDefinite = false;
4419 const nscoord contentBSize =
4420 NS_CSS_MINMAX(aSumLineCrossSizes, aReflowInput.ComputedMinBSize(),
4421 aReflowInput.ComputedMaxBSize());
4422 // If the content block-size is larger than the effective computed
4423 // block-size, we extend the block-size to contain all the content.
4424 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
4425 if (shouldApplyAutomaticMinimumOnBlockAxis) {
4426 // Row-oriented case (cross axis is block-axis), with auto BSize which is
4427 // resolved by aspect-ratio or content size.
4428 return std::max(contentBSize, computedBSize);
4431 // Row-oriented case (cross axis is block axis), with auto BSize:
4432 // Shrink-wrap our line(s), subject to our min-size / max-size
4433 // constraints in that (block) axis.
4434 return contentBSize;
4437 LogicalSize nsFlexContainerFrame::ComputeAvailableSizeForItems(
4438 const ReflowInput& aReflowInput,
4439 const mozilla::LogicalMargin& aBorderPadding) const {
4440 const WritingMode wm = GetWritingMode();
4441 nscoord availableBSize = aReflowInput.AvailableBSize();
4443 if (availableBSize != NS_UNCONSTRAINEDSIZE) {
4444 // Available block-size is constrained. Subtract block-start border and
4445 // padding from it.
4446 availableBSize -= aBorderPadding.BStart(wm);
4448 if (aReflowInput.mStyleBorder->mBoxDecorationBreak ==
4449 StyleBoxDecorationBreak::Clone) {
4450 // We have box-decoration-break:clone. Subtract block-end border and
4451 // padding from the available block-size as well.
4452 availableBSize -= aBorderPadding.BEnd(wm);
4455 // Available block-size can became negative after subtracting block-axis
4456 // border and padding. Per spec, to guarantee progress, fragmentainers are
4457 // assumed to have a minimum block size of 1px regardless of their used
4458 // size. https://drafts.csswg.org/css-break/#breaking-rules
4459 availableBSize =
4460 std::max(nsPresContext::CSSPixelsToAppUnits(1), availableBSize);
4463 return LogicalSize(wm, aReflowInput.ComputedISize(), availableBSize);
4466 void FlexLine::PositionItemsInMainAxis(
4467 const StyleContentDistribution& aJustifyContent,
4468 nscoord aContentBoxMainSize, const FlexboxAxisTracker& aAxisTracker) {
4469 MainAxisPositionTracker mainAxisPosnTracker(
4470 aAxisTracker, this, aJustifyContent, aContentBoxMainSize);
4471 for (FlexItem& item : Items()) {
4472 nscoord itemMainBorderBoxSize =
4473 item.MainSize() + item.BorderPaddingSizeInMainAxis();
4475 // Resolve any main-axis 'auto' margins on aChild to an actual value.
4476 mainAxisPosnTracker.ResolveAutoMarginsInMainAxis(item);
4478 // Advance our position tracker to child's upper-left content-box corner,
4479 // and use that as its position in the main axis.
4480 mainAxisPosnTracker.EnterMargin(item.Margin());
4481 mainAxisPosnTracker.EnterChildFrame(itemMainBorderBoxSize);
4483 item.SetMainPosition(mainAxisPosnTracker.Position());
4485 mainAxisPosnTracker.ExitChildFrame(itemMainBorderBoxSize);
4486 mainAxisPosnTracker.ExitMargin(item.Margin());
4487 mainAxisPosnTracker.TraversePackingSpace();
4488 if (&item != &Items().LastElement()) {
4489 mainAxisPosnTracker.TraverseGap(mMainGapSize);
4494 void nsFlexContainerFrame::SizeItemInCrossAxis(ReflowInput& aChildReflowInput,
4495 FlexItem& aItem) {
4496 // If cross axis is the item's inline axis, just use ISize from reflow input,
4497 // and don't bother with a full reflow.
4498 if (aItem.IsInlineAxisCrossAxis()) {
4499 aItem.SetCrossSize(aChildReflowInput.ComputedISize());
4500 return;
4503 MOZ_ASSERT(!aItem.HadMeasuringReflow(),
4504 "We shouldn't need more than one measuring reflow");
4506 if (aItem.AlignSelf()._0 == StyleAlignFlags::STRETCH) {
4507 // This item's got "align-self: stretch", so we probably imposed a
4508 // stretched computed cross-size on it during its previous
4509 // reflow. We're not imposing that BSize for *this* "measuring" reflow, so
4510 // we need to tell it to treat this reflow as a resize in its block axis
4511 // (regardless of whether any of its ancestors are actually being resized).
4512 // (Note: we know that the cross axis is the item's *block* axis -- if it
4513 // weren't, then we would've taken the early-return above.)
4514 aChildReflowInput.SetBResize(true);
4515 // Not 100% sure this is needed, but be conservative for now:
4516 aChildReflowInput.mFlags.mIsBResizeForPercentages = true;
4519 // Potentially reflow the item, and get the sizing info.
4520 const CachedBAxisMeasurement& measurement =
4521 MeasureBSizeForFlexItem(aItem, aChildReflowInput);
4523 // Save the sizing info that we learned from this reflow
4524 // -----------------------------------------------------
4526 // Tentatively store the child's desired content-box cross-size.
4527 aItem.SetCrossSize(measurement.BSize());
4530 void FlexLine::PositionItemsInCrossAxis(
4531 nscoord aLineStartPosition, const FlexboxAxisTracker& aAxisTracker) {
4532 SingleLineCrossAxisPositionTracker lineCrossAxisPosnTracker(aAxisTracker);
4534 for (FlexItem& item : Items()) {
4535 // First, stretch the item's cross size (if appropriate), and resolve any
4536 // auto margins in this axis.
4537 item.ResolveStretchedCrossSize(mLineCrossSize);
4538 lineCrossAxisPosnTracker.ResolveAutoMarginsInCrossAxis(*this, item);
4540 // Compute the cross-axis position of this item
4541 nscoord itemCrossBorderBoxSize =
4542 item.CrossSize() + item.BorderPaddingSizeInCrossAxis();
4543 lineCrossAxisPosnTracker.EnterAlignPackingSpace(*this, item, aAxisTracker);
4544 lineCrossAxisPosnTracker.EnterMargin(item.Margin());
4545 lineCrossAxisPosnTracker.EnterChildFrame(itemCrossBorderBoxSize);
4547 item.SetCrossPosition(aLineStartPosition +
4548 lineCrossAxisPosnTracker.Position());
4550 // Back out to cross-axis edge of the line.
4551 lineCrossAxisPosnTracker.ResetPosition();
4555 void nsFlexContainerFrame::Reflow(nsPresContext* aPresContext,
4556 ReflowOutput& aReflowOutput,
4557 const ReflowInput& aReflowInput,
4558 nsReflowStatus& aStatus) {
4559 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
4560 return;
4563 MarkInReflow();
4564 DO_GLOBAL_REFLOW_COUNT("nsFlexContainerFrame");
4565 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aReflowOutput, aStatus);
4566 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
4567 MOZ_ASSERT(aPresContext == PresContext());
4568 NS_WARNING_ASSERTION(
4569 aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE,
4570 "Unconstrained inline size; this should only result from huge sizes "
4571 "(not intrinsic sizing w/ orthogonal flows)");
4573 FLEX_LOG("Reflow() for nsFlexContainerFrame %p", this);
4575 if (IsFrameTreeTooDeep(aReflowInput, aReflowOutput, aStatus)) {
4576 return;
4579 NormalizeChildLists();
4581 #ifdef DEBUG
4582 mDidPushItemsBitMayLie = false;
4583 SanityCheckChildListsBeforeReflow();
4584 #endif // DEBUG
4586 // We (and our children) can only depend on our ancestor's bsize if we have
4587 // a percent-bsize, or if we're positioned and we have "block-start" and
4588 // "block-end" set and have block-size:auto. (There are actually other cases,
4589 // too -- e.g. if our parent is itself a block-dir flex container and we're
4590 // flexible -- but we'll let our ancestors handle those sorts of cases.)
4592 // TODO(emilio): the !bsize.IsLengthPercentage() preserves behavior, but it's
4593 // too conservative. min/max-content don't really depend on the container.
4594 WritingMode wm = aReflowInput.GetWritingMode();
4595 const nsStylePosition* stylePos = StylePosition();
4596 const auto& bsize = stylePos->BSize(wm);
4597 if (bsize.HasPercent() || (StyleDisplay()->IsAbsolutelyPositionedStyle() &&
4598 (bsize.IsAuto() || !bsize.IsLengthPercentage()) &&
4599 !stylePos->mOffset.GetBStart(wm).IsAuto() &&
4600 !stylePos->mOffset.GetBEnd(wm).IsAuto())) {
4601 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
4604 const FlexboxAxisTracker axisTracker(this);
4606 // Check to see if we need to create a computed info structure, to
4607 // be filled out for use by devtools.
4608 ComputedFlexContainerInfo* containerInfo = CreateOrClearFlexContainerInfo();
4610 FlexLayoutResult flr;
4611 PerFragmentFlexData fragmentData;
4612 const nsIFrame* prevInFlow = GetPrevInFlow();
4613 if (!prevInFlow) {
4614 const LogicalSize tentativeContentBoxSize = aReflowInput.ComputedSize();
4615 const nscoord tentativeContentBoxMainSize =
4616 axisTracker.MainComponent(tentativeContentBoxSize);
4617 const nscoord tentativeContentBoxCrossSize =
4618 axisTracker.CrossComponent(tentativeContentBoxSize);
4620 // Calculate gap sizes for main and cross axis. We only need them in
4621 // DoFlexLayout in the first-in-flow, so no need to worry about consumed
4622 // block-size.
4623 const auto& mainGapStyle =
4624 axisTracker.IsRowOriented() ? stylePos->mColumnGap : stylePos->mRowGap;
4625 const auto& crossGapStyle =
4626 axisTracker.IsRowOriented() ? stylePos->mRowGap : stylePos->mColumnGap;
4627 const nscoord mainGapSize = nsLayoutUtils::ResolveGapToLength(
4628 mainGapStyle, tentativeContentBoxMainSize);
4629 const nscoord crossGapSize = nsLayoutUtils::ResolveGapToLength(
4630 crossGapStyle, tentativeContentBoxCrossSize);
4632 // When fragmenting a flex container, we run the flex algorithm without
4633 // regards to pagination in order to compute the flex container's desired
4634 // content-box size. https://drafts.csswg.org/css-flexbox-1/#pagination-algo
4636 // Note: For a multi-line column-oriented flex container, the sample
4637 // algorithm suggests we wrap the flex line at the block-end edge of a
4638 // column/page, but we do not implement it intentionally. This brings the
4639 // layout result closer to the one as if there's no fragmentation.
4640 AutoTArray<StrutInfo, 1> struts;
4641 flr = DoFlexLayout(aReflowInput, tentativeContentBoxMainSize,
4642 tentativeContentBoxCrossSize, axisTracker, mainGapSize,
4643 crossGapSize, struts, containerInfo);
4645 if (!struts.IsEmpty()) {
4646 // We're restarting flex layout, with new knowledge of collapsed items.
4647 flr.mLines.Clear();
4648 flr.mPlaceholders.Clear();
4649 flr = DoFlexLayout(aReflowInput, tentativeContentBoxMainSize,
4650 tentativeContentBoxCrossSize, axisTracker, mainGapSize,
4651 crossGapSize, struts, containerInfo);
4653 } else {
4654 flr = GenerateFlexLayoutResult();
4655 auto* fragmentDataProp =
4656 prevInFlow->GetProperty(PerFragmentFlexData::Prop());
4657 MOZ_ASSERT(fragmentDataProp,
4658 "PerFragmentFlexData should be set in our prev-in-flow!");
4659 fragmentData = *fragmentDataProp;
4662 LogicalSize contentBoxSize = axisTracker.LogicalSizeFromFlexRelativeSizes(
4663 flr.mContentBoxMainSize, flr.mContentBoxCrossSize);
4665 const nscoord consumedBSize = CalcAndCacheConsumedBSize();
4666 const nscoord effectiveContentBSize =
4667 contentBoxSize.BSize(wm) - consumedBSize;
4668 LogicalMargin borderPadding = aReflowInput.ComputedLogicalBorderPadding(wm);
4669 if (MOZ_UNLIKELY(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE)) {
4670 // We assume we are the last fragment by using
4671 // PreReflowBlockLevelLogicalSkipSides(), and skip block-end border and
4672 // padding if needed.
4673 borderPadding.ApplySkipSides(PreReflowBlockLevelLogicalSkipSides());
4676 // Determine this frame's tentative border-box size. This is used for logical
4677 // to physical coordinate conversion when positioning children.
4679 // Note that vertical-rl writing-mode is the only case where the block flow
4680 // direction progresses in a negative physical direction, and therefore block
4681 // direction coordinate conversion depends on knowing the width of the
4682 // coordinate space in order to translate between the logical and physical
4683 // origins. As a result, if our final border-box block-size is different from
4684 // this tentative one, and we are in vertical-rl writing mode, we need to
4685 // adjust our children's position after reflowing them.
4686 const LogicalSize tentativeBorderBoxSize(
4687 wm, contentBoxSize.ISize(wm) + borderPadding.IStartEnd(wm),
4688 std::min(effectiveContentBSize + borderPadding.BStartEnd(wm),
4689 aReflowInput.AvailableBSize()));
4690 const nsSize containerSize = tentativeBorderBoxSize.GetPhysicalSize(wm);
4692 OverflowAreas ocBounds;
4693 nsReflowStatus ocStatus;
4694 if (prevInFlow) {
4695 ReflowOverflowContainerChildren(
4696 aPresContext, aReflowInput, ocBounds, ReflowChildFlags::Default,
4697 ocStatus, MergeSortedFrameListsFor, Some(containerSize));
4700 const LogicalSize availableSizeForItems =
4701 ComputeAvailableSizeForItems(aReflowInput, borderPadding);
4702 const auto [childrenBEndEdge, childrenStatus] =
4703 ReflowChildren(aReflowInput, containerSize, availableSizeForItems,
4704 borderPadding, axisTracker, flr, fragmentData);
4706 bool mayNeedNextInFlow = false;
4707 if (aReflowInput.IsInFragmentedContext()) {
4708 // This fragment's contribution to the flex container's cumulative
4709 // content-box block-size, if it turns out that this is the final vs.
4710 // non-final fragment:
4712 // * If it turns out we *are* the final fragment, then this fragment's
4713 // content-box contribution is the distance from the start of our content
4714 // box to the block-end edge of our children (note the borderPadding
4715 // subtraction is just to get us to a content-box-relative offset here):
4716 const nscoord bSizeContributionIfFinalFragment =
4717 childrenBEndEdge - borderPadding.BStart(wm);
4719 // * If it turns out we're *not* the final fragment, then this fragment's
4720 // content-box extends to the edge of the availableSizeForItems (at least),
4721 // regardless of whether we actually have items at that location:
4722 const nscoord bSizeContributionIfNotFinalFragment = std::max(
4723 bSizeContributionIfFinalFragment, availableSizeForItems.BSize(wm));
4725 // mCumulativeBEndEdgeShift was updated in ReflowChildren(), and our
4726 // children's block-size may grow in fragmented context. If our block-size
4727 // and max-block-size are unconstrained, then we allow the flex container to
4728 // grow to accommodate any children whose sizes grew as a result of
4729 // fragmentation.
4730 if (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE) {
4731 contentBoxSize.BSize(wm) = aReflowInput.ApplyMinMaxBSize(
4732 contentBoxSize.BSize(wm) + fragmentData.mCumulativeBEndEdgeShift);
4734 if (childrenStatus.IsComplete()) {
4735 // All of the children fit! We know that we're using a content-based
4736 // block-size, and we know our children's block-size may have grown due
4737 // to fragmentation. So we allow ourselves to grow our block-size here
4738 // to contain the block-end edge of our last child (subject to our
4739 // min/max constraints).
4740 contentBoxSize.BSize(wm) = aReflowInput.ApplyMinMaxBSize(std::max(
4741 contentBoxSize.BSize(wm), fragmentData.mCumulativeContentBoxBSize +
4742 bSizeContributionIfFinalFragment));
4743 } else {
4744 // As in the if-branch above, we extend our block-size, but in this case
4745 // we know that a child didn't fit and might overshot our available
4746 // size, so we assume this fragment won't be the final fragment, and
4747 // hence it should contribute bSizeContributionIfNotFinalFragment
4748 // (subject to our min/max constraints).
4749 contentBoxSize.BSize(wm) = aReflowInput.ApplyMinMaxBSize(std::max(
4750 contentBoxSize.BSize(wm), fragmentData.mCumulativeContentBoxBSize +
4751 bSizeContributionIfNotFinalFragment));
4753 if (aReflowInput.ComputedMaxBSize() == NS_UNCONSTRAINEDSIZE) {
4754 mayNeedNextInFlow = true;
4755 } else {
4756 // The definite max-block-size can be the upper bound of our
4757 // content-box block-size. We should check whether we need a
4758 // next-in-flow.
4759 mayNeedNextInFlow = contentBoxSize.BSize(wm) - consumedBSize >
4760 availableSizeForItems.BSize(wm);
4763 } else {
4764 mayNeedNextInFlow = contentBoxSize.BSize(wm) - consumedBSize >
4765 availableSizeForItems.BSize(wm);
4767 fragmentData.mCumulativeContentBoxBSize +=
4768 bSizeContributionIfNotFinalFragment;
4770 // If we may need a next-in-flow, we'll need to skip block-end border and
4771 // padding.
4772 if (mayNeedNextInFlow && aReflowInput.mStyleBorder->mBoxDecorationBreak ==
4773 StyleBoxDecorationBreak::Slice) {
4774 borderPadding.BEnd(wm) = 0;
4778 PopulateReflowOutput(aReflowOutput, aReflowInput, aStatus, contentBoxSize,
4779 borderPadding, consumedBSize, mayNeedNextInFlow,
4780 childrenBEndEdge, childrenStatus, axisTracker, flr);
4782 if (wm.IsVerticalRL()) {
4783 // If the final border-box block-size is different from the tentative one,
4784 // adjust our children's position.
4785 const nscoord deltaBCoord =
4786 tentativeBorderBoxSize.BSize(wm) - aReflowOutput.Size(wm).BSize(wm);
4787 if (deltaBCoord != 0) {
4788 const LogicalPoint delta(wm, 0, deltaBCoord);
4789 for (const FlexLine& line : flr.mLines) {
4790 for (const FlexItem& item : line.Items()) {
4791 item.Frame()->MovePositionBy(wm, delta);
4797 // Overflow area = union(my overflow area, children's overflow areas)
4798 aReflowOutput.SetOverflowAreasToDesiredBounds();
4799 UnionInFlowChildOverflow(aReflowOutput.mOverflowAreas);
4801 // Merge overflow container bounds and status.
4802 aReflowOutput.mOverflowAreas.UnionWith(ocBounds);
4803 aStatus.MergeCompletionStatusFrom(ocStatus);
4805 FinishReflowWithAbsoluteFrames(PresContext(), aReflowOutput, aReflowInput,
4806 aStatus);
4808 // Finally update our line and item measurements in our containerInfo.
4809 if (MOZ_UNLIKELY(containerInfo)) {
4810 UpdateFlexLineAndItemInfo(*containerInfo, flr.mLines);
4813 // If we are the first-in-flow, we want to store data for our next-in-flows,
4814 // or clear the existing data if it is not needed.
4815 if (!prevInFlow) {
4816 SharedFlexData* sharedData = GetProperty(SharedFlexData::Prop());
4817 if (!aStatus.IsFullyComplete()) {
4818 if (!sharedData) {
4819 sharedData = new SharedFlexData;
4820 SetProperty(SharedFlexData::Prop(), sharedData);
4822 sharedData->Update(std::move(flr));
4823 } else if (sharedData && !GetNextInFlow()) {
4824 // We are fully-complete, so no next-in-flow is needed. However, if we
4825 // report SetInlineLineBreakBeforeAndReset() in an incremental reflow, our
4826 // next-in-flow might still exist. It can be reflowed again before us if
4827 // it is an overflow container. Delete the existing data only if we don't
4828 // have a next-in-flow.
4829 RemoveProperty(SharedFlexData::Prop());
4833 PerFragmentFlexData* fragmentDataProp =
4834 GetProperty(PerFragmentFlexData::Prop());
4835 if (!aStatus.IsFullyComplete()) {
4836 if (!fragmentDataProp) {
4837 fragmentDataProp = new PerFragmentFlexData;
4838 SetProperty(PerFragmentFlexData::Prop(), fragmentDataProp);
4840 *fragmentDataProp = fragmentData;
4841 } else if (fragmentDataProp && !GetNextInFlow()) {
4842 // Similar to the condition to remove SharedFlexData, delete the
4843 // existing data only if we don't have a next-in-flow.
4844 RemoveProperty(PerFragmentFlexData::Prop());
4848 Maybe<nscoord> nsFlexContainerFrame::GetNaturalBaselineBOffset(
4849 WritingMode aWM, BaselineSharingGroup aBaselineGroup,
4850 BaselineExportContext) const {
4851 if (StyleDisplay()->IsContainLayout() ||
4852 HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE)) {
4853 return Nothing{};
4855 return Some(aBaselineGroup == BaselineSharingGroup::First ? mFirstBaseline
4856 : mLastBaseline);
4859 void nsFlexContainerFrame::UnionInFlowChildOverflow(
4860 OverflowAreas& aOverflowAreas) {
4861 // The CSS Overflow spec [1] requires that a scrollable container's
4862 // scrollable overflow should include the following areas.
4864 // a) "the box's own content and padding areas": we treat the *content* as
4865 // the scrolled inner frame's theoretical content-box that's intrinsically
4866 // sized to the union of all the flex items' margin boxes, _without_
4867 // relative positioning applied. The *padding areas* is just inflation on
4868 // top of the theoretical content-box by the flex container's padding.
4870 // b) "the margin areas of grid item and flex item boxes for which the box
4871 // establishes a containing block": a) already includes the flex items'
4872 // normal-positioned margin boxes into the scrollable overflow, but their
4873 // relative-positioned margin boxes should also be included because relpos
4874 // children are still flex items.
4876 // [1] https://drafts.csswg.org/css-overflow-3/#scrollable.
4877 const bool isScrolledContent =
4878 Style()->GetPseudoType() == PseudoStyleType::scrolledContent;
4879 bool anyScrolledContentItem = false;
4880 // Union of normal-positioned margin boxes for all the items.
4881 nsRect itemMarginBoxes;
4882 // Union of relative-positioned margin boxes for the relpos items only.
4883 nsRect relPosItemMarginBoxes;
4884 const bool useMozBoxCollapseBehavior =
4885 StyleVisibility()->UseLegacyCollapseBehavior();
4886 for (nsIFrame* f : mFrames) {
4887 if (useMozBoxCollapseBehavior && f->StyleVisibility()->IsCollapse()) {
4888 continue;
4890 ConsiderChildOverflow(aOverflowAreas, f);
4891 if (!isScrolledContent) {
4892 continue;
4894 if (f->IsPlaceholderFrame()) {
4895 continue;
4897 anyScrolledContentItem = true;
4898 if (MOZ_UNLIKELY(f->IsRelativelyOrStickyPositioned())) {
4899 const nsRect marginRect = f->GetMarginRectRelativeToSelf();
4900 itemMarginBoxes =
4901 itemMarginBoxes.Union(marginRect + f->GetNormalPosition());
4902 relPosItemMarginBoxes =
4903 relPosItemMarginBoxes.Union(marginRect + f->GetPosition());
4904 } else {
4905 itemMarginBoxes = itemMarginBoxes.Union(f->GetMarginRect());
4909 if (anyScrolledContentItem) {
4910 itemMarginBoxes.Inflate(GetUsedPadding());
4911 aOverflowAreas.UnionAllWith(itemMarginBoxes);
4912 aOverflowAreas.UnionAllWith(relPosItemMarginBoxes);
4916 void nsFlexContainerFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) {
4917 UnionInFlowChildOverflow(aOverflowAreas);
4918 // Union with child frames, skipping the principal list since we already
4919 // handled those above.
4920 nsLayoutUtils::UnionChildOverflow(this, aOverflowAreas,
4921 {FrameChildListID::Principal});
4924 void nsFlexContainerFrame::CalculatePackingSpace(
4925 uint32_t aNumThingsToPack, const StyleContentDistribution& aAlignVal,
4926 nscoord* aFirstSubjectOffset, uint32_t* aNumPackingSpacesRemaining,
4927 nscoord* aPackingSpaceRemaining) {
4928 StyleAlignFlags val = aAlignVal.primary;
4929 MOZ_ASSERT(val == StyleAlignFlags::SPACE_BETWEEN ||
4930 val == StyleAlignFlags::SPACE_AROUND ||
4931 val == StyleAlignFlags::SPACE_EVENLY,
4932 "Unexpected alignment value");
4934 MOZ_ASSERT(*aPackingSpaceRemaining >= 0,
4935 "Should not be called with negative packing space");
4937 // Note: In the aNumThingsToPack==1 case, the fallback behavior for
4938 // 'space-between' depends on precise information about the axes that we
4939 // don't have here. So, for that case, we just depend on the caller to
4940 // explicitly convert 'space-{between,around,evenly}' keywords to the
4941 // appropriate fallback alignment and skip this function.
4942 MOZ_ASSERT(aNumThingsToPack > 1,
4943 "Should not be called unless there's more than 1 thing to pack");
4945 // Packing spaces between items:
4946 *aNumPackingSpacesRemaining = aNumThingsToPack - 1;
4948 if (val == StyleAlignFlags::SPACE_BETWEEN) {
4949 // No need to reserve space at beginning/end, so we're done.
4950 return;
4953 // We need to add 1 or 2 packing spaces, split between beginning/end, for
4954 // space-around / space-evenly:
4955 size_t numPackingSpacesForEdges =
4956 val == StyleAlignFlags::SPACE_AROUND ? 1 : 2;
4958 // How big will each "full" packing space be:
4959 nscoord packingSpaceSize =
4960 *aPackingSpaceRemaining /
4961 (*aNumPackingSpacesRemaining + numPackingSpacesForEdges);
4962 // How much packing-space are we allocating to the edges:
4963 nscoord totalEdgePackingSpace = numPackingSpacesForEdges * packingSpaceSize;
4965 // Use half of that edge packing space right now:
4966 *aFirstSubjectOffset += totalEdgePackingSpace / 2;
4967 // ...but we need to subtract all of it right away, so that we won't
4968 // hand out any of it to intermediate packing spaces.
4969 *aPackingSpaceRemaining -= totalEdgePackingSpace;
4972 ComputedFlexContainerInfo*
4973 nsFlexContainerFrame::CreateOrClearFlexContainerInfo() {
4974 if (!ShouldGenerateComputedInfo()) {
4975 return nullptr;
4978 // The flag that sets ShouldGenerateComputedInfo() will never be cleared.
4979 // That's acceptable because it's only set in a Chrome API invoked by
4980 // devtools, and won't impact normal browsing.
4982 // Re-use the ComputedFlexContainerInfo, if it exists.
4983 ComputedFlexContainerInfo* info = GetProperty(FlexContainerInfo());
4984 if (info) {
4985 // We can reuse, as long as we clear out old data.
4986 info->mLines.Clear();
4987 } else {
4988 info = new ComputedFlexContainerInfo();
4989 SetProperty(FlexContainerInfo(), info);
4992 return info;
4995 nscoord nsFlexContainerFrame::FlexItemConsumedBSize(const FlexItem& aItem) {
4996 nsSplittableFrame* f = do_QueryFrame(aItem.Frame());
4997 return f ? ConsumedBSize(f) : 0;
5000 void nsFlexContainerFrame::CreateFlexLineAndFlexItemInfo(
5001 ComputedFlexContainerInfo& aContainerInfo,
5002 const nsTArray<FlexLine>& aLines) {
5003 for (const FlexLine& line : aLines) {
5004 ComputedFlexLineInfo* lineInfo = aContainerInfo.mLines.AppendElement();
5005 // Most of the remaining lineInfo properties will be filled out in
5006 // UpdateFlexLineAndItemInfo (some will be provided by other functions),
5007 // when we have real values. But we still add all the items here, so
5008 // we can capture computed data for each item as we proceed.
5009 for (const FlexItem& item : line.Items()) {
5010 nsIFrame* frame = item.Frame();
5012 // The frame may be for an element, or it may be for an
5013 // anonymous flex item, e.g. wrapping one or more text nodes.
5014 // DevTools wants the content node for the actual child in
5015 // the DOM tree, so we descend through anonymous boxes.
5016 nsIFrame* targetFrame = GetFirstNonAnonBoxInSubtree(frame);
5017 nsIContent* content = targetFrame->GetContent();
5019 // Skip over content that is only whitespace, which might
5020 // have been broken off from a text node which is our real
5021 // target.
5022 while (content && content->TextIsOnlyWhitespace()) {
5023 // If content is only whitespace, try the frame sibling.
5024 targetFrame = targetFrame->GetNextSibling();
5025 if (targetFrame) {
5026 content = targetFrame->GetContent();
5027 } else {
5028 content = nullptr;
5032 ComputedFlexItemInfo* itemInfo = lineInfo->mItems.AppendElement();
5034 itemInfo->mNode = content;
5036 // itemInfo->mMainBaseSize and mMainDeltaSize will be filled out
5037 // in ResolveFlexibleLengths(). Other measurements will be captured in
5038 // UpdateFlexLineAndItemInfo.
5043 void nsFlexContainerFrame::ComputeFlexDirections(
5044 ComputedFlexContainerInfo& aContainerInfo,
5045 const FlexboxAxisTracker& aAxisTracker) {
5046 auto ConvertPhysicalStartSideToFlexPhysicalDirection =
5047 [](mozilla::Side aStartSide) {
5048 switch (aStartSide) {
5049 case eSideLeft:
5050 return dom::FlexPhysicalDirection::Horizontal_lr;
5051 case eSideRight:
5052 return dom::FlexPhysicalDirection::Horizontal_rl;
5053 case eSideTop:
5054 return dom::FlexPhysicalDirection::Vertical_tb;
5055 case eSideBottom:
5056 return dom::FlexPhysicalDirection::Vertical_bt;
5059 MOZ_ASSERT_UNREACHABLE("We should handle all sides!");
5060 return dom::FlexPhysicalDirection::Horizontal_lr;
5063 aContainerInfo.mMainAxisDirection =
5064 ConvertPhysicalStartSideToFlexPhysicalDirection(
5065 aAxisTracker.MainAxisPhysicalStartSide());
5066 aContainerInfo.mCrossAxisDirection =
5067 ConvertPhysicalStartSideToFlexPhysicalDirection(
5068 aAxisTracker.CrossAxisPhysicalStartSide());
5071 void nsFlexContainerFrame::UpdateFlexLineAndItemInfo(
5072 ComputedFlexContainerInfo& aContainerInfo,
5073 const nsTArray<FlexLine>& aLines) {
5074 uint32_t lineIndex = 0;
5075 for (const FlexLine& line : aLines) {
5076 ComputedFlexLineInfo& lineInfo = aContainerInfo.mLines[lineIndex];
5078 lineInfo.mCrossSize = line.LineCrossSize();
5079 lineInfo.mFirstBaselineOffset = line.FirstBaselineOffset();
5080 lineInfo.mLastBaselineOffset = line.LastBaselineOffset();
5082 uint32_t itemIndex = 0;
5083 for (const FlexItem& item : line.Items()) {
5084 ComputedFlexItemInfo& itemInfo = lineInfo.mItems[itemIndex];
5085 itemInfo.mFrameRect = item.Frame()->GetRect();
5086 itemInfo.mMainMinSize = item.MainMinSize();
5087 itemInfo.mMainMaxSize = item.MainMaxSize();
5088 itemInfo.mCrossMinSize = item.CrossMinSize();
5089 itemInfo.mCrossMaxSize = item.CrossMaxSize();
5090 itemInfo.mClampState =
5091 item.WasMinClamped()
5092 ? mozilla::dom::FlexItemClampState::Clamped_to_min
5093 : (item.WasMaxClamped()
5094 ? mozilla::dom::FlexItemClampState::Clamped_to_max
5095 : mozilla::dom::FlexItemClampState::Unclamped);
5096 ++itemIndex;
5098 ++lineIndex;
5102 nsFlexContainerFrame* nsFlexContainerFrame::GetFlexFrameWithComputedInfo(
5103 nsIFrame* aFrame) {
5104 // Prepare a lambda function that we may need to call multiple times.
5105 auto GetFlexContainerFrame = [](nsIFrame* aFrame) {
5106 // Return the aFrame's content insertion frame, iff it is
5107 // a flex container frame.
5108 nsFlexContainerFrame* flexFrame = nullptr;
5110 if (aFrame) {
5111 nsIFrame* inner = aFrame;
5112 if (MOZ_UNLIKELY(aFrame->IsFieldSetFrame())) {
5113 inner = static_cast<nsFieldSetFrame*>(aFrame)->GetInner();
5115 // Since "Get" methods like GetInner and GetContentInsertionFrame can
5116 // return null, we check the return values before dereferencing. Our
5117 // calling pattern makes this unlikely, but we're being careful.
5118 nsIFrame* insertionFrame =
5119 inner ? inner->GetContentInsertionFrame() : nullptr;
5120 nsIFrame* possibleFlexFrame = insertionFrame ? insertionFrame : aFrame;
5121 flexFrame = possibleFlexFrame->IsFlexContainerFrame()
5122 ? static_cast<nsFlexContainerFrame*>(possibleFlexFrame)
5123 : nullptr;
5125 return flexFrame;
5128 nsFlexContainerFrame* flexFrame = GetFlexContainerFrame(aFrame);
5129 if (flexFrame) {
5130 // Generate the FlexContainerInfo data, if it's not already there.
5131 bool reflowNeeded = !flexFrame->HasProperty(FlexContainerInfo());
5133 if (reflowNeeded) {
5134 // Trigger a reflow that generates additional flex property data.
5135 // Hold onto aFrame while we do this, in case reflow destroys it.
5136 AutoWeakFrame weakFrameRef(aFrame);
5138 RefPtr<mozilla::PresShell> presShell = flexFrame->PresShell();
5139 flexFrame->SetShouldGenerateComputedInfo(true);
5140 presShell->FrameNeedsReflow(flexFrame, IntrinsicDirty::None,
5141 NS_FRAME_IS_DIRTY);
5142 presShell->FlushPendingNotifications(FlushType::Layout);
5144 // Since the reflow may have side effects, get the flex frame
5145 // again. But if the weakFrameRef is no longer valid, then we
5146 // must bail out.
5147 if (!weakFrameRef.IsAlive()) {
5148 return nullptr;
5151 flexFrame = GetFlexContainerFrame(weakFrameRef.GetFrame());
5153 NS_WARNING_ASSERTION(
5154 !flexFrame || flexFrame->HasProperty(FlexContainerInfo()),
5155 "The state bit should've made our forced-reflow "
5156 "generate a FlexContainerInfo object");
5159 return flexFrame;
5162 /* static */
5163 bool nsFlexContainerFrame::IsItemInlineAxisMainAxis(nsIFrame* aFrame) {
5164 MOZ_ASSERT(aFrame && aFrame->IsFlexItem(), "expecting arg to be a flex item");
5165 const WritingMode flexItemWM = aFrame->GetWritingMode();
5166 const nsIFrame* flexContainer = aFrame->GetParent();
5168 if (IsLegacyBox(flexContainer)) {
5169 // For legacy boxes, the main axis is determined by "box-orient", and we can
5170 // just directly check if that's vertical, and compare that to whether the
5171 // item's WM is also vertical:
5172 bool boxOrientIsVertical =
5173 flexContainer->StyleXUL()->mBoxOrient == StyleBoxOrient::Vertical;
5174 return flexItemWM.IsVertical() == boxOrientIsVertical;
5177 // For modern CSS flexbox, we get our return value by asking two questions
5178 // and comparing their answers.
5179 // Question 1: does aFrame have the same inline axis as its flex container?
5180 bool itemInlineAxisIsParallelToParent =
5181 !flexItemWM.IsOrthogonalTo(flexContainer->GetWritingMode());
5183 // Question 2: is aFrame's flex container row-oriented? (This tells us
5184 // whether the flex container's main axis is its inline axis.)
5185 auto flexDirection = flexContainer->StylePosition()->mFlexDirection;
5186 bool flexContainerIsRowOriented =
5187 flexDirection == StyleFlexDirection::Row ||
5188 flexDirection == StyleFlexDirection::RowReverse;
5190 // aFrame's inline axis is its flex container's main axis IFF the above
5191 // questions have the same answer.
5192 return flexContainerIsRowOriented == itemInlineAxisIsParallelToParent;
5195 /* static */
5196 bool nsFlexContainerFrame::IsUsedFlexBasisContent(
5197 const StyleFlexBasis& aFlexBasis, const StyleSize& aMainSize) {
5198 // We have a used flex-basis of 'content' if flex-basis explicitly has that
5199 // value, OR if flex-basis is 'auto' (deferring to the main-size property)
5200 // and the main-size property is also 'auto'.
5201 // See https://drafts.csswg.org/css-flexbox-1/#valdef-flex-basis-auto
5202 if (aFlexBasis.IsContent()) {
5203 return true;
5205 return aFlexBasis.IsAuto() && aMainSize.IsAuto();
5208 nsFlexContainerFrame::FlexLayoutResult nsFlexContainerFrame::DoFlexLayout(
5209 const ReflowInput& aReflowInput, const nscoord aTentativeContentBoxMainSize,
5210 const nscoord aTentativeContentBoxCrossSize,
5211 const FlexboxAxisTracker& aAxisTracker, nscoord aMainGapSize,
5212 nscoord aCrossGapSize, nsTArray<StrutInfo>& aStruts,
5213 ComputedFlexContainerInfo* const aContainerInfo) {
5214 FlexLayoutResult flr;
5216 GenerateFlexLines(aReflowInput, aTentativeContentBoxMainSize,
5217 aTentativeContentBoxCrossSize, aStruts, aAxisTracker,
5218 aMainGapSize, flr.mPlaceholders, flr.mLines,
5219 flr.mHasCollapsedItems);
5221 if ((flr.mLines.Length() == 1 && flr.mLines[0].IsEmpty()) ||
5222 aReflowInput.mStyleDisplay->IsContainLayout()) {
5223 // We have no flex items, or we're layout-contained. So, we have no
5224 // baseline, and our parent should synthesize a baseline if needed.
5225 AddStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE);
5226 } else {
5227 RemoveStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE);
5230 // Construct our computed info if we've been asked to do so. This is
5231 // necessary to do now so we can capture some computed values for
5232 // FlexItems during layout that would not otherwise be saved (like
5233 // size adjustments). We'll later fix up the line properties,
5234 // because the correct values aren't available yet.
5235 if (aContainerInfo) {
5236 MOZ_ASSERT(ShouldGenerateComputedInfo(),
5237 "We should only have the info struct if "
5238 "ShouldGenerateComputedInfo() is true!");
5240 if (!aStruts.IsEmpty()) {
5241 // We restarted DoFlexLayout, and may have stale mLines to clear:
5242 aContainerInfo->mLines.Clear();
5243 } else {
5244 MOZ_ASSERT(aContainerInfo->mLines.IsEmpty(), "Shouldn't have lines yet.");
5247 CreateFlexLineAndFlexItemInfo(*aContainerInfo, flr.mLines);
5248 ComputeFlexDirections(*aContainerInfo, aAxisTracker);
5251 flr.mContentBoxMainSize = ComputeMainSize(
5252 aReflowInput, aAxisTracker, aTentativeContentBoxMainSize, flr.mLines);
5254 uint32_t lineIndex = 0;
5255 for (FlexLine& line : flr.mLines) {
5256 ComputedFlexLineInfo* lineInfo =
5257 aContainerInfo ? &aContainerInfo->mLines[lineIndex] : nullptr;
5258 line.ResolveFlexibleLengths(flr.mContentBoxMainSize, lineInfo);
5259 ++lineIndex;
5262 // Cross Size Determination - Flexbox spec section 9.4
5263 // https://drafts.csswg.org/css-flexbox-1/#cross-sizing
5264 // ===================================================
5265 // Calculate the hypothetical cross size of each item:
5267 // 'sumLineCrossSizes' includes the size of all gaps between lines. We
5268 // initialize it with the sum of all the gaps, and add each line's cross size
5269 // at the end of the following for-loop.
5270 nscoord sumLineCrossSizes = aCrossGapSize * (flr.mLines.Length() - 1);
5271 for (FlexLine& line : flr.mLines) {
5272 for (FlexItem& item : line.Items()) {
5273 // The item may already have the correct cross-size; only recalculate
5274 // if the item's main size resolution (flexing) could have influenced it:
5275 if (item.CanMainSizeInfluenceCrossSize()) {
5276 StyleSizeOverrides sizeOverrides;
5277 if (item.IsInlineAxisMainAxis()) {
5278 sizeOverrides.mStyleISize.emplace(item.StyleMainSize());
5279 } else {
5280 sizeOverrides.mStyleBSize.emplace(item.StyleMainSize());
5282 FLEX_LOG("Sizing flex item %p in cross axis", item.Frame());
5283 FLEX_LOGV(" Main size override: %d", item.MainSize());
5285 const WritingMode wm = item.GetWritingMode();
5286 LogicalSize availSize = aReflowInput.ComputedSize(wm);
5287 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
5288 ReflowInput childReflowInput(PresContext(), aReflowInput, item.Frame(),
5289 availSize, Nothing(), {}, sizeOverrides);
5290 if (item.IsBlockAxisMainAxis() && item.TreatBSizeAsIndefinite()) {
5291 childReflowInput.mFlags.mTreatBSizeAsIndefinite = true;
5294 SizeItemInCrossAxis(childReflowInput, item);
5297 // Now that we've finished with this line's items, size the line itself:
5298 line.ComputeCrossSizeAndBaseline(aAxisTracker);
5299 sumLineCrossSizes += line.LineCrossSize();
5302 bool isCrossSizeDefinite;
5303 flr.mContentBoxCrossSize = ComputeCrossSize(
5304 aReflowInput, aAxisTracker, aTentativeContentBoxCrossSize,
5305 sumLineCrossSizes, &isCrossSizeDefinite);
5307 // Set up state for cross-axis alignment, at a high level (outside the
5308 // scope of a particular flex line)
5309 CrossAxisPositionTracker crossAxisPosnTracker(
5310 flr.mLines, aReflowInput, flr.mContentBoxCrossSize, isCrossSizeDefinite,
5311 aAxisTracker, aCrossGapSize);
5313 // Now that we know the cross size of each line (including
5314 // "align-content:stretch" adjustments, from the CrossAxisPositionTracker
5315 // constructor), we can create struts for any flex items with
5316 // "visibility: collapse" (and restart flex layout).
5317 // Make sure to only do this if we had no struts.
5318 if (aStruts.IsEmpty() && flr.mHasCollapsedItems &&
5319 !StyleVisibility()->UseLegacyCollapseBehavior()) {
5320 BuildStrutInfoFromCollapsedItems(flr.mLines, aStruts);
5321 if (!aStruts.IsEmpty()) {
5322 // Restart flex layout, using our struts.
5323 return flr;
5327 // If the flex container is row-oriented, it should derive its first/last
5328 // baseline from the WM-relative startmost/endmost FlexLine if any items in
5329 // the line participate in baseline alignment.
5330 // https://drafts.csswg.org/css-flexbox-1/#flex-baselines
5332 // Initialize the relevant variables here so that we can establish baselines
5333 // while iterating FlexLine later (while crossAxisPosnTracker is conveniently
5334 // pointing at the cross-start edge of that line, which the line's baseline
5335 // offset is measured from).
5336 const FlexLine* lineForFirstBaseline = nullptr;
5337 const FlexLine* lineForLastBaseline = nullptr;
5338 if (aAxisTracker.IsRowOriented()) {
5339 lineForFirstBaseline = &StartmostLine(flr.mLines, aAxisTracker);
5340 lineForLastBaseline = &EndmostLine(flr.mLines, aAxisTracker);
5341 } else {
5342 // For column-oriented flex container, use sentinel value to prompt us to
5343 // get baselines from the startmost/endmost items.
5344 flr.mAscent = nscoord_MIN;
5345 flr.mAscentForLast = nscoord_MIN;
5348 const auto justifyContent =
5349 IsLegacyBox(aReflowInput.mFrame)
5350 ? ConvertLegacyStyleToJustifyContent(StyleXUL())
5351 : aReflowInput.mStylePosition->mJustifyContent;
5353 lineIndex = 0;
5354 for (FlexLine& line : flr.mLines) {
5355 // Main-Axis Alignment - Flexbox spec section 9.5
5356 // https://drafts.csswg.org/css-flexbox-1/#main-alignment
5357 // ==============================================
5358 line.PositionItemsInMainAxis(justifyContent, flr.mContentBoxMainSize,
5359 aAxisTracker);
5361 // See if we need to extract some computed info for this line.
5362 if (MOZ_UNLIKELY(aContainerInfo)) {
5363 ComputedFlexLineInfo& lineInfo = aContainerInfo->mLines[lineIndex];
5364 lineInfo.mCrossStart = crossAxisPosnTracker.Position();
5367 // Cross-Axis Alignment - Flexbox spec section 9.6
5368 // https://drafts.csswg.org/css-flexbox-1/#cross-alignment
5369 // ===============================================
5370 line.PositionItemsInCrossAxis(crossAxisPosnTracker.Position(),
5371 aAxisTracker);
5373 // Flex Container Baselines - Flexbox spec section 8.5
5374 // https://drafts.csswg.org/css-flexbox-1/#flex-baselines
5375 auto ComputeAscentFromLine = [&](const FlexLine& aLine,
5376 BaselineSharingGroup aBaselineGroup) {
5377 MOZ_ASSERT(aAxisTracker.IsRowOriented(),
5378 "This makes sense only if we are row-oriented!");
5380 // baselineOffsetInLine is a distance from the line's cross-start edge.
5381 const nscoord baselineOffsetInLine =
5382 aLine.ExtractBaselineOffset(aBaselineGroup);
5384 if (baselineOffsetInLine == nscoord_MIN) {
5385 // No "first baseline"-aligned or "last baseline"-aligned items in
5386 // aLine. Return a sentinel value to prompt us to get baseline from the
5387 // startmost or endmost FlexItem after we've reflowed it.
5388 return nscoord_MIN;
5391 // This "ascent" variable is a distance from the flex container's
5392 // content-box block-start edge.
5393 const nscoord ascent = aAxisTracker.LogicalAscentFromFlexRelativeAscent(
5394 crossAxisPosnTracker.Position() + baselineOffsetInLine,
5395 flr.mContentBoxCrossSize);
5397 // Convert "ascent" variable to a distance from border-box start or end
5398 // edge, per documentation for FlexLayoutResult ascent members.
5399 const auto wm = aAxisTracker.GetWritingMode();
5400 if (aBaselineGroup == BaselineSharingGroup::First) {
5401 return ascent +
5402 aReflowInput.ComputedLogicalBorderPadding(wm).BStart(wm);
5404 return flr.mContentBoxCrossSize - ascent +
5405 aReflowInput.ComputedLogicalBorderPadding(wm).BEnd(wm);
5408 if (lineForFirstBaseline && lineForFirstBaseline == &line) {
5409 flr.mAscent = ComputeAscentFromLine(line, BaselineSharingGroup::First);
5411 if (lineForLastBaseline && lineForLastBaseline == &line) {
5412 flr.mAscentForLast =
5413 ComputeAscentFromLine(line, BaselineSharingGroup::Last);
5416 crossAxisPosnTracker.TraverseLine(line);
5417 crossAxisPosnTracker.TraversePackingSpace();
5419 if (&line != &flr.mLines.LastElement()) {
5420 crossAxisPosnTracker.TraverseGap();
5422 ++lineIndex;
5425 return flr;
5428 // This data structure is used in fragmentation, storing the block coordinate
5429 // metrics when reflowing 1) the BStart-most line in each fragment of a
5430 // row-oriented flex container or, 2) the BStart-most item in each fragment of a
5431 // single-line column-oriented flex container.
5433 // When we lay out a row-oriented flex container fragment, its first line might
5434 // contain one or more monolithic items that were pushed from the previous
5435 // fragment specifically to avoid having those monolithic items overlap the
5436 // page/column break. The situation is similar for single-row column-oriented
5437 // flex container fragments, but a bit simpler; only their first item might have
5438 // been pushed to avoid overlapping a page/column break.
5440 // We'll have to place any such pushed items at the block-start edge of the
5441 // current fragment's content-box, which is as close as we can get them to their
5442 // theoretical/unfragmented position (without slicing them); but it does
5443 // represent a shift away from their theoretical/unfragmented position (which
5444 // was somewhere in the previous fragment).
5446 // When that happens, we need to record the maximum such shift that we had to
5447 // perform so that we can apply the same block-endwards shift to "downstream"
5448 // items (items towards the block-end edge) that we could otherwise collide
5449 // with. We also potentially apply the same shift when computing the block-end
5450 // edge of this flex container fragment's content-box so that we don't
5451 // inadvertently shift the last item (or line-of-items) to overlap the flex
5452 // container's border, or content beyond the flex container.
5454 // We use this structure to keep track of several metrics, in service of this
5455 // goal. This structure is also necessary to adjust PerFragmentFlexData at the
5456 // end of ReflowChildren().
5458 // Note: "First" in the struct name means "BStart-most", not the order in the
5459 // flex line array or flex item array.
5460 struct FirstLineOrFirstItemBAxisMetrics final {
5461 // This value stores the block-end edge shift for 1) the BStart-most line in
5462 // the current fragment of a row-oriented flex container, or 2) the
5463 // BStart-most item in the current fragment of a single-line column-oriented
5464 // flex container. This number is non-negative.
5466 // This value may become positive when any item is a first-in-flow and also
5467 // satisfies either the above condition 1) or 2), since that's a hint that it
5468 // could be monolithic or have a monolithic first descendant, and therefore an
5469 // item that might incur a page/column-break-dodging position-shift that this
5470 // variable needs to track.
5472 // This value also stores the fragmentation-imposed growth in the block-size
5473 // of a) the BStart-most line in the current fragment of a row-oriented flex
5474 // container, or b) the BStart-most item in the current fragment of a
5475 // single-line column-oriented flex container. This number is non-negative.
5476 nscoord mBEndEdgeShift = 0;
5478 // The first and second value in the pair store the max block-end edges for
5479 // items before and after applying the per-item position-shift in the block
5480 // axis. We only record the block-end edges for items with first-in-flow
5481 // frames placed in the current flex container fragment. This is used only by
5482 // row-oriented flex containers.
5483 Maybe<std::pair<nscoord, nscoord>> mMaxBEndEdge;
5486 std::tuple<nscoord, nsReflowStatus> nsFlexContainerFrame::ReflowChildren(
5487 const ReflowInput& aReflowInput, const nsSize& aContainerSize,
5488 const LogicalSize& aAvailableSizeForItems,
5489 const LogicalMargin& aBorderPadding, const FlexboxAxisTracker& aAxisTracker,
5490 FlexLayoutResult& aFlr, PerFragmentFlexData& aFragmentData) {
5491 if (HidesContentForLayout()) {
5492 return {0, nsReflowStatus()};
5495 // Before giving each child a final reflow, calculate the origin of the
5496 // flex container's content box (with respect to its border-box), so that
5497 // we can compute our flex item's final positions.
5498 WritingMode flexWM = aReflowInput.GetWritingMode();
5499 const LogicalPoint containerContentBoxOrigin =
5500 aBorderPadding.StartOffset(flexWM);
5502 // The block-end of children is relative to the flex container's border-box.
5503 nscoord maxBlockEndEdgeOfChildren = containerContentBoxOrigin.B(flexWM);
5505 FirstLineOrFirstItemBAxisMetrics bAxisMetrics;
5506 FrameHashtable pushedItems;
5507 FrameHashtable incompleteItems;
5508 FrameHashtable overflowIncompleteItems;
5510 const bool isSingleLine =
5511 StyleFlexWrap::Nowrap == aReflowInput.mStylePosition->mFlexWrap;
5513 // FINAL REFLOW: Give each child frame another chance to reflow, now that
5514 // we know its final size and position.
5515 const FlexLine& startmostLine = StartmostLine(aFlr.mLines, aAxisTracker);
5516 const FlexItem* startmostItem =
5517 startmostLine.IsEmpty() ? nullptr
5518 : &startmostLine.StartmostItem(aAxisTracker);
5520 const size_t numLines = aFlr.mLines.Length();
5521 for (size_t lineIdx = 0; lineIdx < numLines; ++lineIdx) {
5522 // Iterate flex lines from the startmost to endmost (relative to flex
5523 // container's writing-mode).
5524 const auto& line =
5525 aFlr.mLines[aAxisTracker.IsCrossAxisReversed() ? numLines - lineIdx - 1
5526 : lineIdx];
5527 MOZ_ASSERT(lineIdx != 0 || &line == &startmostLine,
5528 "Logic for finding startmost line should be consistent!");
5530 const size_t numItems = line.Items().Length();
5531 for (size_t itemIdx = 0; itemIdx < numItems; ++itemIdx) {
5532 // Iterate flex items from the startmost to endmost (relative to flex
5533 // container's writing-mode).
5534 const FlexItem& item = line.Items()[aAxisTracker.IsMainAxisReversed()
5535 ? numItems - itemIdx - 1
5536 : itemIdx];
5537 MOZ_ASSERT(lineIdx != 0 || itemIdx != 0 || &item == startmostItem,
5538 "Logic for finding startmost item should be consistent!");
5540 LogicalPoint framePos = aAxisTracker.LogicalPointFromFlexRelativePoint(
5541 item.MainPosition(), item.CrossPosition(), aFlr.mContentBoxMainSize,
5542 aFlr.mContentBoxCrossSize);
5543 // This variable records the item's block-end edge before we give it a
5544 // per-item-position-shift, if the item is a first-in-flow in the
5545 // startmost line of a row-oriented flex container fragment. It is used to
5546 // determine the block-end edge shift for the startmost line at the end of
5547 // the outer loop.
5548 Maybe<nscoord> frameBPosBeforePerItemShift;
5550 if (item.Frame()->GetPrevInFlow()) {
5551 // The item is a continuation. Lay it out at the beginning of the
5552 // available space.
5553 framePos.B(flexWM) = 0;
5554 } else if (GetPrevInFlow()) {
5555 // The item we're placing is not a continuation; though we're placing it
5556 // into a flex container fragment which *is* a continuation. To compute
5557 // the item's correct position in this fragment, we adjust the item's
5558 // theoretical/unfragmented block-direction position by subtracting the
5559 // cumulative content-box block-size for all the previous fragments and
5560 // adding the cumulative block-end edge shift.
5562 // Note that the item's position in this fragment has not been finalized
5563 // yet. At this point, we've adjusted the item's
5564 // theoretical/unfragmented position to be relative to the block-end
5565 // edge of the previous container fragment's content-box. Later, we'll
5566 // compute per-item position-shift to finalize its position.
5567 framePos.B(flexWM) -= aFragmentData.mCumulativeContentBoxBSize;
5568 framePos.B(flexWM) += aFragmentData.mCumulativeBEndEdgeShift;
5570 // This helper gets the per-item position-shift in the block-axis.
5571 auto GetPerItemPositionShiftToBEnd = [&]() {
5572 if (framePos.B(flexWM) >= 0) {
5573 // The item final position might be in current flex container
5574 // fragment or in any of the later fragments. No adjustment needed.
5575 return 0;
5578 // The item's block position is negative, but we want to place it at
5579 // the content-box block-start edge of this container fragment. To
5580 // achieve this, return a negated (positive) value to make the final
5581 // block position zero.
5583 // This scenario occurs when fragmenting a row-oriented flex container
5584 // where this item is pushed to this container fragment.
5585 return -framePos.B(flexWM);
5588 if (aAxisTracker.IsRowOriented()) {
5589 if (&line == &startmostLine) {
5590 frameBPosBeforePerItemShift.emplace(framePos.B(flexWM));
5591 framePos.B(flexWM) += GetPerItemPositionShiftToBEnd();
5592 } else {
5593 // We've computed two things for the startmost line during the outer
5594 // loop's first iteration: 1) how far the block-end edge had to
5595 // shift and 2) how large the block-size needed to grow. Here, we
5596 // just shift all items in the rest of the lines the same amount.
5597 framePos.B(flexWM) += bAxisMetrics.mBEndEdgeShift;
5599 } else {
5600 MOZ_ASSERT(aAxisTracker.IsColumnOriented());
5601 if (isSingleLine) {
5602 if (&item == startmostItem) {
5603 bAxisMetrics.mBEndEdgeShift = GetPerItemPositionShiftToBEnd();
5605 framePos.B(flexWM) += bAxisMetrics.mBEndEdgeShift;
5606 } else {
5607 // Bug 1806717: We need a more sophisticated solution for multi-line
5608 // column-oriented flex container when each line has a different
5609 // position-shift value. For now, we don't shift them.
5614 // Adjust available block-size for the item. (We compute it here because
5615 // framePos is still relative to the container's content-box.)
5617 // Note: The available block-size can become negative if item's
5618 // block-direction position is below available space's block-end.
5619 const nscoord availableBSizeForItem =
5620 aAvailableSizeForItems.BSize(flexWM) == NS_UNCONSTRAINEDSIZE
5621 ? NS_UNCONSTRAINEDSIZE
5622 : aAvailableSizeForItems.BSize(flexWM) - framePos.B(flexWM);
5624 // Adjust framePos to be relative to the container's border-box
5625 // (i.e. its frame rect), instead of the container's content-box:
5626 framePos += containerContentBoxOrigin;
5628 // Check if we actually need to reflow the item -- if the item's position
5629 // is below the available space's block-end, push it to our next-in-flow;
5630 // if it does need a reflow, and we already reflowed it with the right
5631 // content-box size.
5632 const bool childBPosExceedAvailableSpaceBEnd =
5633 availableBSizeForItem != NS_UNCONSTRAINEDSIZE &&
5634 availableBSizeForItem <= 0;
5635 bool itemInPushedItems = false;
5636 if (childBPosExceedAvailableSpaceBEnd) {
5637 // Note: Even if all of our items are beyond the available space & get
5638 // pushed here, we'll be guaranteed to place at least one of them (and
5639 // make progress) in one of the flex container's *next* fragment. It's
5640 // because ComputeAvailableSizeForItems() always reserves at least 1px
5641 // available block-size for its children, and we consume all available
5642 // block-size and add it to
5643 // PerFragmentFlexData::mCumulativeContentBoxBSize even if we are not
5644 // laying out any child.
5645 FLEX_LOG(
5646 "[frag] Flex item %p needed to be pushed to container's "
5647 "next-in-flow due to position below available space's block-end",
5648 item.Frame());
5649 pushedItems.Insert(item.Frame());
5650 itemInPushedItems = true;
5651 } else if (item.NeedsFinalReflow(aReflowInput)) {
5652 // The available size must be in item's writing-mode.
5653 const WritingMode itemWM = item.GetWritingMode();
5654 const auto availableSize =
5655 LogicalSize(flexWM, aAvailableSizeForItems.ISize(flexWM),
5656 availableBSizeForItem)
5657 .ConvertTo(itemWM, flexWM);
5659 const nsReflowStatus childReflowStatus =
5660 ReflowFlexItem(aAxisTracker, aReflowInput, item, framePos,
5661 availableSize, aContainerSize);
5663 const bool shouldPushItem = [&]() {
5664 if (availableBSizeForItem == NS_UNCONSTRAINEDSIZE) {
5665 // If the available block-size is unconstrained, then we're not
5666 // fragmenting and we don't want to push the item.
5667 return false;
5669 if (framePos.B(flexWM) == containerContentBoxOrigin.B(flexWM)) {
5670 // The flex item is adjacent with block-start of the container's
5671 // content-box. Don't push it, or we'll trap in an infinite loop.
5672 return false;
5674 if (item.Frame()->BSize() <= availableBSizeForItem) {
5675 return false;
5677 if (aAxisTracker.IsColumnOriented() &&
5678 item.Frame()->StyleDisplay()->mBreakBefore ==
5679 StyleBreakBetween::Avoid) {
5680 return false;
5682 return true;
5683 }();
5684 if (shouldPushItem) {
5685 FLEX_LOG(
5686 "[frag] Flex item %p needed to be pushed to container's "
5687 "next-in-flow because its block-size is larger than the "
5688 "available space",
5689 item.Frame());
5690 pushedItems.Insert(item.Frame());
5691 itemInPushedItems = true;
5692 } else if (childReflowStatus.IsIncomplete()) {
5693 incompleteItems.Insert(item.Frame());
5694 } else if (childReflowStatus.IsOverflowIncomplete()) {
5695 overflowIncompleteItems.Insert(item.Frame());
5697 } else {
5698 MoveFlexItemToFinalPosition(item, framePos, aContainerSize);
5701 if (!itemInPushedItems) {
5702 const nscoord borderBoxBSize = item.Frame()->BSize(flexWM);
5703 const nscoord bEndEdgeAfterPerItemShift =
5704 framePos.B(flexWM) + borderBoxBSize;
5706 // The item (or a fragment thereof) was placed in this flex container
5707 // fragment. Update the max block-end edge with the item's block-end
5708 // edge.
5709 maxBlockEndEdgeOfChildren =
5710 std::max(maxBlockEndEdgeOfChildren, bEndEdgeAfterPerItemShift);
5712 if (frameBPosBeforePerItemShift) {
5713 // Make the block-end edge relative to flex container's border-box
5714 // because bEndEdgeAfterPerItemShift is relative to the border-box.
5715 const nscoord bEndEdgeBeforePerItemShift =
5716 containerContentBoxOrigin.B(flexWM) +
5717 *frameBPosBeforePerItemShift + borderBoxBSize;
5719 if (bAxisMetrics.mMaxBEndEdge) {
5720 auto& [before, after] = *bAxisMetrics.mMaxBEndEdge;
5721 before = std::max(before, bEndEdgeBeforePerItemShift);
5722 after = std::max(after, bEndEdgeAfterPerItemShift);
5723 } else {
5724 bAxisMetrics.mMaxBEndEdge.emplace(bEndEdgeBeforePerItemShift,
5725 bEndEdgeAfterPerItemShift);
5729 if (item.Frame()->GetPrevInFlow()) {
5730 // Items with a previous-continuation may experience some
5731 // fragmentation-imposed growth in their block-size; we compute that
5732 // here.
5733 const nscoord bSizeOfThisFragment =
5734 item.Frame()->ContentSize(flexWM).BSize(flexWM);
5735 const nscoord consumedBSize = FlexItemConsumedBSize(item);
5736 const nscoord unfragmentedBSize = item.BSize();
5737 nscoord bSizeGrowthOfThisFragment = 0;
5739 if (consumedBSize >= unfragmentedBSize) {
5740 // The item's block-size has been grown to exceed the unfragmented
5741 // block-size in the previous fragments.
5742 bSizeGrowthOfThisFragment = bSizeOfThisFragment;
5743 } else if (consumedBSize + bSizeOfThisFragment >= unfragmentedBSize) {
5744 // The item's block-size just grows in the current fragment to
5745 // exceed the unfragmented block-size.
5746 bSizeGrowthOfThisFragment =
5747 consumedBSize + bSizeOfThisFragment - unfragmentedBSize;
5750 if (aAxisTracker.IsRowOriented()) {
5751 if (&line == &startmostLine) {
5752 bAxisMetrics.mBEndEdgeShift = std::max(
5753 bAxisMetrics.mBEndEdgeShift, bSizeGrowthOfThisFragment);
5755 } else {
5756 MOZ_ASSERT(aAxisTracker.IsColumnOriented());
5757 if (isSingleLine) {
5758 if (&item == startmostItem) {
5759 MOZ_ASSERT(bAxisMetrics.mBEndEdgeShift == 0,
5760 "The item's frame is a continuation, so it "
5761 "shouldn't shift!");
5762 bAxisMetrics.mBEndEdgeShift = bSizeGrowthOfThisFragment;
5764 } else {
5765 // Bug 1806717: We need a more sophisticated solution for
5766 // multi-line column-oriented flex container when each line has a
5767 // different block-size growth value. For now, we don't deal with
5768 // them.
5774 // If the item has auto margins, and we were tracking the UsedMargin
5775 // property, set the property to the computed margin values.
5776 if (item.HasAnyAutoMargin()) {
5777 nsMargin* propValue =
5778 item.Frame()->GetProperty(nsIFrame::UsedMarginProperty());
5779 if (propValue) {
5780 *propValue = item.PhysicalMargin();
5785 // Now we've finished processing all the items in the startmost line.
5786 // Determine the amount by which the startmost line's block-end edge has
5787 // shifted, so we can apply the same shift for the remaining lines.
5788 if (GetPrevInFlow() && aAxisTracker.IsRowOriented() &&
5789 &line == &startmostLine && bAxisMetrics.mMaxBEndEdge) {
5790 auto& [before, after] = *bAxisMetrics.mMaxBEndEdge;
5791 bAxisMetrics.mBEndEdgeShift =
5792 std::max(bAxisMetrics.mBEndEdgeShift, after - before);
5796 if (!aFlr.mPlaceholders.IsEmpty()) {
5797 ReflowPlaceholders(aReflowInput, aFlr.mPlaceholders,
5798 containerContentBoxOrigin, aContainerSize);
5801 nsReflowStatus childrenStatus;
5802 if (!pushedItems.IsEmpty() || !incompleteItems.IsEmpty()) {
5803 childrenStatus.SetIncomplete();
5804 } else if (!overflowIncompleteItems.IsEmpty()) {
5805 childrenStatus.SetOverflowIncomplete();
5807 PushIncompleteChildren(pushedItems, incompleteItems, overflowIncompleteItems);
5809 // TODO: Try making this a fatal assertion after we fix bug 1751260.
5810 NS_ASSERTION(childrenStatus.IsFullyComplete() ||
5811 aAvailableSizeForItems.BSize(flexWM) != NS_UNCONSTRAINEDSIZE,
5812 "We shouldn't have any incomplete children if the available "
5813 "block-size is unconstrained!");
5815 if (!pushedItems.IsEmpty()) {
5816 AddStateBits(NS_STATE_FLEX_DID_PUSH_ITEMS);
5819 if (GetPrevInFlow()) {
5820 aFragmentData.mCumulativeBEndEdgeShift += bAxisMetrics.mBEndEdgeShift;
5823 return {maxBlockEndEdgeOfChildren, childrenStatus};
5826 void nsFlexContainerFrame::PopulateReflowOutput(
5827 ReflowOutput& aReflowOutput, const ReflowInput& aReflowInput,
5828 nsReflowStatus& aStatus, const LogicalSize& aContentBoxSize,
5829 const LogicalMargin& aBorderPadding, const nscoord aConsumedBSize,
5830 const bool aMayNeedNextInFlow, const nscoord aMaxBlockEndEdgeOfChildren,
5831 const nsReflowStatus& aChildrenStatus,
5832 const FlexboxAxisTracker& aAxisTracker, FlexLayoutResult& aFlr) {
5833 const WritingMode flexWM = aReflowInput.GetWritingMode();
5835 // Compute flex container's desired size (in its own writing-mode).
5836 LogicalSize desiredSizeInFlexWM(flexWM);
5837 desiredSizeInFlexWM.ISize(flexWM) =
5838 aContentBoxSize.ISize(flexWM) + aBorderPadding.IStartEnd(flexWM);
5840 // Unconditionally skip adding block-end border and padding for now. We add it
5841 // lower down, after we've established baseline and decided whether bottom
5842 // border-padding fits (if we're fragmented).
5843 const nscoord effectiveContentBSizeWithBStartBP =
5844 aContentBoxSize.BSize(flexWM) - aConsumedBSize +
5845 aBorderPadding.BStart(flexWM);
5846 nscoord blockEndContainerBP = aBorderPadding.BEnd(flexWM);
5848 if (aMayNeedNextInFlow) {
5849 // We assume our status should be reported as incomplete because we may need
5850 // a next-in-flow.
5851 bool isStatusIncomplete = true;
5853 const nscoord availableBSizeMinusBEndBP =
5854 aReflowInput.AvailableBSize() - aBorderPadding.BEnd(flexWM);
5856 if (aMaxBlockEndEdgeOfChildren <= availableBSizeMinusBEndBP) {
5857 // Consume all the available block-size.
5858 desiredSizeInFlexWM.BSize(flexWM) = availableBSizeMinusBEndBP;
5859 } else {
5860 // This case happens if we have some tall unbreakable children exceeding
5861 // the available block-size.
5862 desiredSizeInFlexWM.BSize(flexWM) = std::min(
5863 effectiveContentBSizeWithBStartBP, aMaxBlockEndEdgeOfChildren);
5865 if ((aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE ||
5866 aChildrenStatus.IsFullyComplete()) &&
5867 aMaxBlockEndEdgeOfChildren >= effectiveContentBSizeWithBStartBP) {
5868 // We have some tall unbreakable child that's sticking off the end of
5869 // our fragment, *and* forcing us to consume all of our remaining
5870 // content block-size and call ourselves complete.
5872 // - If we have a definite block-size: we get here if the tall child
5873 // makes us reach that block-size.
5874 // - If we have a content-based block-size: we get here if the tall
5875 // child makes us reach the content-based block-size from a
5876 // theoretical unfragmented layout, *and* all our children are
5877 // complete. (Note that if we have some incomplete child, then we
5878 // instead prefer to return an incomplete status, so we can get a
5879 // next-in-flow to include that child's requested next-in-flow, in the
5880 // spirit of having a block-size that fits the content.)
5882 // TODO: the auto-height case might need more subtlety; see bug 1828977.
5883 isStatusIncomplete = false;
5885 // We also potentially need to get the unskipped block-end border and
5886 // padding (if we assumed it'd be skipped as part of our tentative
5887 // assumption that we'd be incomplete).
5888 if (aReflowInput.mStyleBorder->mBoxDecorationBreak ==
5889 StyleBoxDecorationBreak::Slice) {
5890 blockEndContainerBP =
5891 aReflowInput.ComputedLogicalBorderPadding(flexWM).BEnd(flexWM);
5896 if (isStatusIncomplete) {
5897 aStatus.SetIncomplete();
5899 } else {
5900 // Our own effective content-box block-size can fit within the available
5901 // block-size.
5902 desiredSizeInFlexWM.BSize(flexWM) = effectiveContentBSizeWithBStartBP;
5905 // Now, we account for how the block-end border and padding (if any) impacts
5906 // our desired size. If adding it pushes us over the available block-size,
5907 // then we become incomplete (unless we already weren't asking for any
5908 // block-size, in which case we stay complete to avoid looping forever).
5910 // NOTE: If we have auto block-size, we allow our block-end border and padding
5911 // to push us over the available block-size without requesting a continuation,
5912 // for consistency with the behavior of "display:block" elements.
5913 const nscoord effectiveContentBSizeWithBStartEndBP =
5914 desiredSizeInFlexWM.BSize(flexWM) + blockEndContainerBP;
5916 if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
5917 effectiveContentBSizeWithBStartEndBP > aReflowInput.AvailableBSize() &&
5918 desiredSizeInFlexWM.BSize(flexWM) != 0 &&
5919 aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) {
5920 // We couldn't fit with the block-end border and padding included, so we'll
5921 // need a continuation.
5922 aStatus.SetIncomplete();
5924 if (aReflowInput.mStyleBorder->mBoxDecorationBreak ==
5925 StyleBoxDecorationBreak::Slice) {
5926 blockEndContainerBP = 0;
5930 // The variable "blockEndContainerBP" now accurately reflects how much (if
5931 // any) block-end border and padding we want for this frame, so we can proceed
5932 // to add it in.
5933 desiredSizeInFlexWM.BSize(flexWM) += blockEndContainerBP;
5935 if (aStatus.IsComplete() && !aChildrenStatus.IsFullyComplete()) {
5936 aStatus.SetOverflowIncomplete();
5937 aStatus.SetNextInFlowNeedsReflow();
5940 // If we are the first-in-flow and not fully complete (either our block-size
5941 // or any of our flex items cannot fit in the available block-size), and the
5942 // style requires us to avoid breaking inside, set the status to prompt our
5943 // parent to push us to the next page/column.
5944 if (!GetPrevInFlow() && !aStatus.IsFullyComplete() &&
5945 ShouldAvoidBreakInside(aReflowInput)) {
5946 aStatus.SetInlineLineBreakBeforeAndReset();
5947 return;
5950 // If we haven't established a baseline for the container yet, i.e. if we
5951 // don't have any flex item in the startmost flex line that participates in
5952 // baseline alignment, then use the startmost flex item to derive the
5953 // container's baseline.
5954 if (const FlexLine& line = StartmostLine(aFlr.mLines, aAxisTracker);
5955 aFlr.mAscent == nscoord_MIN && !line.IsEmpty()) {
5956 const FlexItem& item = line.StartmostItem(aAxisTracker);
5957 aFlr.mAscent = item.Frame()
5958 ->GetLogicalPosition(
5959 flexWM, desiredSizeInFlexWM.GetPhysicalSize(flexWM))
5960 .B(flexWM) +
5961 item.ResolvedAscent(true);
5964 // Likewise, if we don't have any flex item in the endmost flex line that
5965 // participates in last baseline alignment, then use the endmost flex item to
5966 // derived the container's last baseline.
5967 if (const FlexLine& line = EndmostLine(aFlr.mLines, aAxisTracker);
5968 aFlr.mAscentForLast == nscoord_MIN && !line.IsEmpty()) {
5969 const FlexItem& item = line.EndmostItem(aAxisTracker);
5970 const nscoord lastAscent =
5971 item.Frame()
5972 ->GetLogicalPosition(flexWM,
5973 desiredSizeInFlexWM.GetPhysicalSize(flexWM))
5974 .B(flexWM) +
5975 item.ResolvedAscent(false);
5977 aFlr.mAscentForLast = desiredSizeInFlexWM.BSize(flexWM) - lastAscent;
5980 if (aFlr.mAscent == nscoord_MIN) {
5981 // Still don't have our baseline set -- this happens if we have no
5982 // children, if our children are huge enough that they have nscoord_MIN
5983 // as their baseline, or our content is hidden in which case, we'll use the
5984 // wrong baseline (but no big deal).
5985 NS_WARNING_ASSERTION(
5986 HidesContentForLayout() || aFlr.mLines[0].IsEmpty(),
5987 "Have flex items but didn't get an ascent - that's odd (or there are "
5988 "just gigantic sizes involved)");
5989 // Per spec, synthesize baseline from the flex container's content box
5990 // (i.e. use block-end side of content-box)
5991 // XXXdholbert This only makes sense if parent's writing mode is
5992 // horizontal (& even then, really we should be using the BSize in terms
5993 // of the parent's writing mode, not ours). Clean up in bug 1155322.
5994 aFlr.mAscent = effectiveContentBSizeWithBStartBP;
5997 if (aFlr.mAscentForLast == nscoord_MIN) {
5998 // Still don't have our last baseline set -- this happens if we have no
5999 // children, if our children are huge enough that they have nscoord_MIN
6000 // as their baseline, or our content is hidden in which case, we'll use the
6001 // wrong baseline (but no big deal).
6002 NS_WARNING_ASSERTION(
6003 HidesContentForLayout() || aFlr.mLines[0].IsEmpty(),
6004 "Have flex items but didn't get an ascent - that's odd (or there are "
6005 "just gigantic sizes involved)");
6006 // Per spec, synthesize baseline from the flex container's content box
6007 // (i.e. use block-end side of content-box)
6008 // XXXdholbert This only makes sense if parent's writing mode is
6009 // horizontal (& even then, really we should be using the BSize in terms
6010 // of the parent's writing mode, not ours). Clean up in bug 1155322.
6011 aFlr.mAscentForLast = blockEndContainerBP;
6014 if (HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE)) {
6015 // This will force our parent to call GetLogicalBaseline, which will
6016 // synthesize a margin-box baseline.
6017 aReflowOutput.SetBlockStartAscent(ReflowOutput::ASK_FOR_BASELINE);
6018 } else {
6019 // XXXdholbert aFlr.mAscent needs to be in terms of our parent's
6020 // writing-mode here. See bug 1155322.
6021 aReflowOutput.SetBlockStartAscent(aFlr.mAscent);
6024 // Cache the container baselines so that our parent can baseline-align us.
6025 mFirstBaseline = aFlr.mAscent;
6026 mLastBaseline = aFlr.mAscentForLast;
6028 // Convert flex container's final desired size to parent's WM, for outparam.
6029 aReflowOutput.SetSize(flexWM, desiredSizeInFlexWM);
6032 void nsFlexContainerFrame::MoveFlexItemToFinalPosition(
6033 const FlexItem& aItem, const LogicalPoint& aFramePos,
6034 const nsSize& aContainerSize) {
6035 const WritingMode outerWM = aItem.ContainingBlockWM();
6036 const nsStyleDisplay* display = aItem.Frame()->StyleDisplay();
6037 LogicalPoint pos(aFramePos);
6038 if (display->IsRelativelyOrStickyPositionedStyle()) {
6039 // If the item is relatively positioned, look up its offsets (cached from
6040 // previous reflow). A sticky positioned item can pass a dummy
6041 // logicalOffsets into ApplyRelativePositioning().
6042 LogicalMargin logicalOffsets(outerWM);
6043 if (display->IsRelativelyPositionedStyle()) {
6044 nsMargin* cachedOffsets =
6045 aItem.Frame()->GetProperty(nsIFrame::ComputedOffsetProperty());
6046 MOZ_ASSERT(
6047 cachedOffsets,
6048 "relpos previously-reflowed frame should've cached its offsets");
6049 logicalOffsets = LogicalMargin(outerWM, *cachedOffsets);
6051 ReflowInput::ApplyRelativePositioning(aItem.Frame(), outerWM,
6052 logicalOffsets, &pos, aContainerSize);
6055 FLEX_LOG("Moving flex item %p to its desired position %s", aItem.Frame(),
6056 ToString(pos).c_str());
6057 aItem.Frame()->SetPosition(outerWM, pos, aContainerSize);
6058 PositionFrameView(aItem.Frame());
6059 PositionChildViews(aItem.Frame());
6062 nsReflowStatus nsFlexContainerFrame::ReflowFlexItem(
6063 const FlexboxAxisTracker& aAxisTracker, const ReflowInput& aReflowInput,
6064 const FlexItem& aItem, const LogicalPoint& aFramePos,
6065 const LogicalSize& aAvailableSize, const nsSize& aContainerSize) {
6066 FLEX_LOG("Doing final reflow for flex item %p", aItem.Frame());
6068 // Returns true if we should use 'auto' in block axis's StyleSizeOverrides to
6069 // allow fragmentation-imposed block-size growth.
6070 auto ComputeBSizeOverrideWithAuto = [&]() {
6071 if (!aReflowInput.IsInFragmentedContext()) {
6072 return false;
6074 if (aItem.HasAspectRatio()) {
6075 // Aspect-ratio's automatic content-based minimum size doesn't work
6076 // properly in a fragmented context (Bug 1868284) when we use 'auto'
6077 // block-size to apply the fragmentation-imposed block-size growth.
6078 // Disable it for now so that items with aspect-ratios can still use their
6079 // known block-sizes (from flex layout algorithm) in final reflow.
6080 return false;
6082 if (aItem.IsBlockAxisMainAxis()) {
6083 if (aItem.IsFlexBaseSizeContentBSize()) {
6084 // The flex item resolved its indefinite flex-basis to the content
6085 // block-size.
6086 if (aItem.IsMainMinSizeContentBSize()) {
6087 // The item's flex base size and main min-size are both content
6088 // block-size. We interpret this content-based block-size as
6089 // permission to apply fragmentation-imposed block-size growth.
6090 return true;
6092 if (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE) {
6093 // The flex container has an indefinite block-size. We allow the
6094 // item's to apply fragmentation-imposed block-size growth.
6095 return true;
6098 return false;
6101 MOZ_ASSERT(aItem.IsBlockAxisCrossAxis());
6102 MOZ_ASSERT(aItem.IsStretched(),
6103 "No need to override block-size with 'auto' if the item is not "
6104 "stretched in the cross axis!");
6106 Maybe<nscoord> measuredBSize = aItem.MeasuredBSize();
6107 if (measuredBSize && aItem.CrossSize() == *measuredBSize) {
6108 // The item has a measured content-based block-size due to having an
6109 // indefinite cross-size. If its cross-size is equal to the content-based
6110 // block-size, then it is the tallest item that established the cross-size
6111 // of the flex line. We allow it apply fragmentation-imposed block-size
6112 // growth.
6114 // Note: We only allow the tallest item to grow because it is likely to
6115 // have the most impact on the overall flex container block-size growth.
6116 // This is not a perfect solution since other shorter items in the same
6117 // line might also have fragmentation-imposed block-size growth, but
6118 // currently there is no reliable way to detect whether they will outgrow
6119 // the tallest item.
6120 return true;
6122 return false;
6125 StyleSizeOverrides sizeOverrides;
6126 bool overrideBSizeWithAuto = false;
6128 // Override flex item's main size.
6129 if (aItem.IsInlineAxisMainAxis()) {
6130 sizeOverrides.mStyleISize.emplace(aItem.StyleMainSize());
6131 FLEX_LOGV(" Main size (inline-size) override: %d", aItem.MainSize());
6132 } else {
6133 overrideBSizeWithAuto = ComputeBSizeOverrideWithAuto();
6134 if (overrideBSizeWithAuto) {
6135 sizeOverrides.mStyleBSize.emplace(StyleSize::Auto());
6136 FLEX_LOGV(" Main size (block-size) override: Auto");
6137 } else {
6138 sizeOverrides.mStyleBSize.emplace(aItem.StyleMainSize());
6139 FLEX_LOGV(" Main size (block-size) override: %d", aItem.MainSize());
6143 // Override flex item's cross size if it was stretched in the cross axis (in
6144 // which case we're imposing a cross size).
6145 if (aItem.IsStretched()) {
6146 if (aItem.IsInlineAxisCrossAxis()) {
6147 sizeOverrides.mStyleISize.emplace(aItem.StyleCrossSize());
6148 FLEX_LOGV(" Cross size (inline-size) override: %d", aItem.CrossSize());
6149 } else {
6150 overrideBSizeWithAuto = ComputeBSizeOverrideWithAuto();
6151 if (overrideBSizeWithAuto) {
6152 sizeOverrides.mStyleBSize.emplace(StyleSize::Auto());
6153 FLEX_LOGV(" Cross size (block-size) override: Auto");
6154 } else {
6155 sizeOverrides.mStyleBSize.emplace(aItem.StyleCrossSize());
6156 FLEX_LOGV(" Cross size (block-size) override: %d", aItem.CrossSize());
6160 if (sizeOverrides.mStyleBSize) {
6161 // We are overriding the block-size. For robustness, we always assume that
6162 // this represents a block-axis resize for the frame. This may be
6163 // conservative, but we do capture all the conditions in the block-axis
6164 // (checked in NeedsFinalReflow()) that make this item require a final
6165 // reflow. This sets relevant flags in ReflowInput::InitResizeFlags().
6166 aItem.Frame()->SetHasBSizeChange(true);
6169 ReflowInput childReflowInput(PresContext(), aReflowInput, aItem.Frame(),
6170 aAvailableSize, Nothing(), {}, sizeOverrides);
6171 if (overrideBSizeWithAuto) {
6172 // If we use 'auto' to override the item's block-size, set the item's
6173 // original block-size to min-size as a lower bound.
6174 childReflowInput.SetComputedMinBSize(aItem.BSize());
6176 // Set the item's block-size as the percentage basis so that its children
6177 // can resolve percentage sizes correctly.
6178 childReflowInput.SetPercentageBasisInBlockAxis(aItem.BSize());
6181 if (aItem.TreatBSizeAsIndefinite() && aItem.IsBlockAxisMainAxis()) {
6182 childReflowInput.mFlags.mTreatBSizeAsIndefinite = true;
6185 if (aItem.IsStretched() && aItem.IsBlockAxisCrossAxis()) {
6186 // This item is stretched (in the cross axis), and that axis is its block
6187 // axis. That stretching effectively gives it a relative BSize.
6188 // XXXdholbert This flag only makes a difference if we use the flex items'
6189 // frame-state when deciding whether to reflow them -- and we don't, as of
6190 // the changes in bug 851607. So this has no effect right now, but it might
6191 // make a difference if we optimize to use dirty bits in the
6192 // future. (Reftests flexbox-resizeviewport-1.xhtml and -2.xhtml are
6193 // intended to catch any regressions here, if we end up relying on this bit
6194 // & neglecting to set it.)
6195 aItem.Frame()->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
6198 // NOTE: Be very careful about doing anything else with childReflowInput
6199 // after this point, because some of its methods (e.g. SetComputedWidth)
6200 // internally call InitResizeFlags and stomp on mVResize & mHResize.
6202 FLEX_LOG("Reflowing flex item %p at its desired position %s", aItem.Frame(),
6203 ToString(aFramePos).c_str());
6205 // CachedFlexItemData is stored in item's writing mode, so we pass
6206 // aChildReflowInput into ReflowOutput's constructor.
6207 ReflowOutput childReflowOutput(childReflowInput);
6208 nsReflowStatus childReflowStatus;
6209 WritingMode outerWM = aReflowInput.GetWritingMode();
6210 ReflowChild(aItem.Frame(), PresContext(), childReflowOutput, childReflowInput,
6211 outerWM, aFramePos, aContainerSize, ReflowChildFlags::Default,
6212 childReflowStatus);
6214 // XXXdholbert Perhaps we should call CheckForInterrupt here; see bug 1495532.
6216 FinishReflowChild(aItem.Frame(), PresContext(), childReflowOutput,
6217 &childReflowInput, outerWM, aFramePos, aContainerSize,
6218 ReflowChildFlags::ApplyRelativePositioning);
6220 aItem.SetAscent(childReflowOutput.BlockStartAscent());
6222 // Update our cached flex item info:
6223 if (auto* cached = aItem.Frame()->GetProperty(CachedFlexItemData::Prop())) {
6224 cached->Update(childReflowInput, childReflowOutput,
6225 FlexItemReflowType::Final);
6226 } else {
6227 cached = new CachedFlexItemData(childReflowInput, childReflowOutput,
6228 FlexItemReflowType::Final);
6229 aItem.Frame()->SetProperty(CachedFlexItemData::Prop(), cached);
6232 return childReflowStatus;
6235 void nsFlexContainerFrame::ReflowPlaceholders(
6236 const ReflowInput& aReflowInput, nsTArray<nsIFrame*>& aPlaceholders,
6237 const LogicalPoint& aContentBoxOrigin, const nsSize& aContainerSize) {
6238 WritingMode outerWM = aReflowInput.GetWritingMode();
6240 // As noted in this method's documentation, we'll reflow every entry in
6241 // |aPlaceholders| at the container's content-box origin.
6242 for (nsIFrame* placeholder : aPlaceholders) {
6243 MOZ_ASSERT(placeholder->IsPlaceholderFrame(),
6244 "placeholders array should only contain placeholder frames");
6245 WritingMode wm = placeholder->GetWritingMode();
6246 LogicalSize availSize = aReflowInput.ComputedSize(wm);
6247 ReflowInput childReflowInput(PresContext(), aReflowInput, placeholder,
6248 availSize);
6249 // No need to set the -webkit-line-clamp related flags when reflowing
6250 // a placeholder.
6251 ReflowOutput childReflowOutput(outerWM);
6252 nsReflowStatus childReflowStatus;
6253 ReflowChild(placeholder, PresContext(), childReflowOutput, childReflowInput,
6254 outerWM, aContentBoxOrigin, aContainerSize,
6255 ReflowChildFlags::Default, childReflowStatus);
6257 FinishReflowChild(placeholder, PresContext(), childReflowOutput,
6258 &childReflowInput, outerWM, aContentBoxOrigin,
6259 aContainerSize, ReflowChildFlags::Default);
6261 // Mark the placeholder frame to indicate that it's not actually at the
6262 // element's static position, because we need to apply CSS Alignment after
6263 // we determine the OOF's size:
6264 placeholder->AddStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN);
6268 nscoord nsFlexContainerFrame::IntrinsicISize(gfxContext* aRenderingContext,
6269 IntrinsicISizeType aType) {
6270 nscoord containerISize = 0;
6271 const nsStylePosition* stylePos = StylePosition();
6272 const FlexboxAxisTracker axisTracker(this);
6274 nscoord mainGapSize;
6275 if (axisTracker.IsRowOriented()) {
6276 mainGapSize = nsLayoutUtils::ResolveGapToLength(stylePos->mColumnGap,
6277 NS_UNCONSTRAINEDSIZE);
6278 } else {
6279 mainGapSize = nsLayoutUtils::ResolveGapToLength(stylePos->mRowGap,
6280 NS_UNCONSTRAINEDSIZE);
6283 const bool useMozBoxCollapseBehavior =
6284 StyleVisibility()->UseLegacyCollapseBehavior();
6286 // The loop below sets aside space for a gap before each item besides the
6287 // first. This bool helps us handle that special-case.
6288 bool onFirstChild = true;
6290 for (nsIFrame* childFrame : mFrames) {
6291 // Skip out-of-flow children because they don't participate in flex layout.
6292 if (childFrame->IsPlaceholderFrame()) {
6293 continue;
6296 if (useMozBoxCollapseBehavior &&
6297 childFrame->StyleVisibility()->IsCollapse()) {
6298 // If we're using legacy "visibility:collapse" behavior, then we don't
6299 // care about the sizes of any collapsed children.
6300 continue;
6303 nscoord childISize = nsLayoutUtils::IntrinsicForContainer(
6304 aRenderingContext, childFrame, aType);
6306 // * For a row-oriented single-line flex container, the intrinsic
6307 // {min/pref}-isize is the sum of its items' {min/pref}-isizes and
6308 // (n-1) column gaps.
6309 // * For a column-oriented flex container, the intrinsic min isize
6310 // is the max of its items' min isizes.
6311 // * For a row-oriented multi-line flex container, the intrinsic
6312 // pref isize is former (sum), and its min isize is the latter (max).
6313 bool isSingleLine = (StyleFlexWrap::Nowrap == stylePos->mFlexWrap);
6314 if (axisTracker.IsRowOriented() &&
6315 (isSingleLine || aType == IntrinsicISizeType::PrefISize)) {
6316 containerISize += childISize;
6317 if (!onFirstChild) {
6318 containerISize += mainGapSize;
6320 onFirstChild = false;
6321 } else { // (col-oriented, or MinISize for multi-line row flex container)
6322 containerISize = std::max(containerISize, childISize);
6326 return containerISize;
6329 /* virtual */
6330 nscoord nsFlexContainerFrame::GetMinISize(gfxContext* aRenderingContext) {
6331 DISPLAY_MIN_INLINE_SIZE(this, mCachedMinISize);
6332 if (mCachedMinISize == NS_INTRINSIC_ISIZE_UNKNOWN) {
6333 if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
6334 mCachedMinISize = *containISize;
6335 } else {
6336 mCachedMinISize =
6337 IntrinsicISize(aRenderingContext, IntrinsicISizeType::MinISize);
6341 return mCachedMinISize;
6344 /* virtual */
6345 nscoord nsFlexContainerFrame::GetPrefISize(gfxContext* aRenderingContext) {
6346 DISPLAY_PREF_INLINE_SIZE(this, mCachedPrefISize);
6347 if (mCachedPrefISize == NS_INTRINSIC_ISIZE_UNKNOWN) {
6348 if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
6349 mCachedPrefISize = *containISize;
6350 } else {
6351 mCachedPrefISize =
6352 IntrinsicISize(aRenderingContext, IntrinsicISizeType::PrefISize);
6356 return mCachedPrefISize;
6359 int32_t nsFlexContainerFrame::GetNumLines() const {
6360 // TODO(emilio, bug 1793251): Treating all row oriented frames as single-lines
6361 // might not be great for flex-wrap'd containers, consider trying to do
6362 // better? We probably would need to persist more stuff than we do after
6363 // layout.
6364 return FlexboxAxisInfo(this).mIsRowOriented ? 1 : mFrames.GetLength();
6367 bool nsFlexContainerFrame::IsLineIteratorFlowRTL() {
6368 FlexboxAxisInfo info(this);
6369 if (info.mIsRowOriented) {
6370 const bool isRtl = StyleVisibility()->mDirection == StyleDirection::Rtl;
6371 return info.mIsMainAxisReversed != isRtl;
6373 return false;
6376 Result<nsILineIterator::LineInfo, nsresult> nsFlexContainerFrame::GetLine(
6377 int32_t aLineNumber) {
6378 if (aLineNumber < 0 || aLineNumber >= GetNumLines()) {
6379 return Err(NS_ERROR_FAILURE);
6381 FlexboxAxisInfo info(this);
6382 LineInfo lineInfo;
6383 if (info.mIsRowOriented) {
6384 lineInfo.mLineBounds = GetRect();
6385 lineInfo.mFirstFrameOnLine = mFrames.FirstChild();
6386 // This isn't quite ideal for multi-line row flexbox, see bug 1793251.
6387 lineInfo.mNumFramesOnLine = mFrames.GetLength();
6388 } else {
6389 // TODO(emilio, bug 1793322): Deal with column-reverse (mIsMainAxisReversed)
6390 nsIFrame* f = mFrames.FrameAt(aLineNumber);
6391 lineInfo.mLineBounds = f->GetRect();
6392 lineInfo.mFirstFrameOnLine = f;
6393 lineInfo.mNumFramesOnLine = 1;
6395 return lineInfo;
6398 int32_t nsFlexContainerFrame::FindLineContaining(nsIFrame* aFrame,
6399 int32_t aStartLine) {
6400 const int32_t index = mFrames.IndexOf(aFrame);
6401 if (index < 0) {
6402 return -1;
6404 const FlexboxAxisInfo info(this);
6405 if (info.mIsRowOriented) {
6406 return 0;
6408 if (index < aStartLine) {
6409 return -1;
6411 return index;
6414 NS_IMETHODIMP
6415 nsFlexContainerFrame::CheckLineOrder(int32_t aLine, bool* aIsReordered,
6416 nsIFrame** aFirstVisual,
6417 nsIFrame** aLastVisual) {
6418 *aIsReordered = false;
6419 *aFirstVisual = nullptr;
6420 *aLastVisual = nullptr;
6421 return NS_OK;
6424 NS_IMETHODIMP
6425 nsFlexContainerFrame::FindFrameAt(int32_t aLineNumber, nsPoint aPos,
6426 nsIFrame** aFrameFound,
6427 bool* aPosIsBeforeFirstFrame,
6428 bool* aPosIsAfterLastFrame) {
6429 const auto wm = GetWritingMode();
6430 const LogicalPoint pos(wm, aPos, GetSize());
6431 const FlexboxAxisInfo info(this);
6433 *aFrameFound = nullptr;
6434 *aPosIsBeforeFirstFrame = true;
6435 *aPosIsAfterLastFrame = false;
6437 if (!info.mIsRowOriented) {
6438 nsIFrame* f = mFrames.FrameAt(aLineNumber);
6439 if (!f) {
6440 return NS_OK;
6443 auto rect = f->GetLogicalRect(wm, GetSize());
6444 *aFrameFound = f;
6445 *aPosIsBeforeFirstFrame = pos.I(wm) < rect.IStart(wm);
6446 *aPosIsAfterLastFrame = pos.I(wm) > rect.IEnd(wm);
6447 return NS_OK;
6450 LineFrameFinder finder(aPos, GetSize(), GetWritingMode(),
6451 IsLineIteratorFlowRTL());
6452 for (nsIFrame* f : mFrames) {
6453 finder.Scan(f);
6454 if (finder.IsDone()) {
6455 break;
6458 finder.Finish(aFrameFound, aPosIsBeforeFirstFrame, aPosIsAfterLastFrame);
6459 return NS_OK;