1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /* rendering object for CSS "display: flex" */
8 
9 #include "nsFlexContainerFrame.h"
10 
11 #include <algorithm>
12 
13 #include "gfxContext.h"
14 #include "mozilla/ComputedStyle.h"
15 #include "mozilla/CSSOrderAwareFrameIterator.h"
16 #include "mozilla/FloatingPoint.h"
17 #include "mozilla/Logging.h"
18 #include "mozilla/PresShell.h"
19 #include "mozilla/WritingModes.h"
20 #include "nsBlockFrame.h"
21 #include "nsContentUtils.h"
22 #include "nsCSSAnonBoxes.h"
23 #include "nsDisplayList.h"
24 #include "nsFieldSetFrame.h"
25 #include "nsIFrameInlines.h"
26 #include "nsLayoutUtils.h"
27 #include "nsPlaceholderFrame.h"
28 #include "nsPresContext.h"
29 
30 using namespace mozilla;
31 using namespace mozilla::layout;
32 
33 // Convenience typedefs for helper classes that we forward-declare in .h file
34 // (so that nsFlexContainerFrame methods can use them as parameters):
35 using FlexItem = nsFlexContainerFrame::FlexItem;
36 using FlexLine = nsFlexContainerFrame::FlexLine;
37 using FlexboxAxisTracker = nsFlexContainerFrame::FlexboxAxisTracker;
38 using StrutInfo = nsFlexContainerFrame::StrutInfo;
39 using CachedBAxisMeasurement = nsFlexContainerFrame::CachedBAxisMeasurement;
40 
41 static mozilla::LazyLogModule gFlexContainerLog("FlexContainer");
42 #define FLEX_LOG(...) \
43   MOZ_LOG(gFlexContainerLog, LogLevel::Debug, (__VA_ARGS__));
44 #define FLEX_LOGV(...) \
45   MOZ_LOG(gFlexContainerLog, LogLevel::Verbose, (__VA_ARGS__));
46 
47 // Returns true iff the given nsStyleDisplay has display:-webkit-{inline-}box
48 // or display:-moz-{inline-}box.
IsDisplayValueLegacyBox(const nsStyleDisplay * aStyleDisp)49 static inline bool IsDisplayValueLegacyBox(const nsStyleDisplay* aStyleDisp) {
50   return aStyleDisp->mDisplay == mozilla::StyleDisplay::WebkitBox ||
51          aStyleDisp->mDisplay == mozilla::StyleDisplay::WebkitInlineBox ||
52          aStyleDisp->mDisplay == mozilla::StyleDisplay::MozBox ||
53          aStyleDisp->mDisplay == mozilla::StyleDisplay::MozInlineBox;
54 }
55 
56 // Returns true if aFlexContainer is a frame for some element that has
57 // display:-webkit-{inline-}box (or -moz-{inline-}box). aFlexContainer is
58 // expected to be an instance of nsFlexContainerFrame (enforced with an assert);
59 // otherwise, this function's state-bit-check here is bogus.
IsLegacyBox(const nsIFrame * aFlexContainer)60 static bool IsLegacyBox(const nsIFrame* aFlexContainer) {
61   MOZ_ASSERT(aFlexContainer->IsFlexContainerFrame(),
62              "only flex containers may be passed to this function");
63   return aFlexContainer->HasAnyStateBits(NS_STATE_FLEX_IS_EMULATING_LEGACY_BOX);
64 }
65 
66 // Returns the OrderState enum we should pass to CSSOrderAwareFrameIterator
67 // (depending on whether aFlexContainer has
68 // NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER state bit).
OrderStateForIter(const nsFlexContainerFrame * aFlexContainer)69 static CSSOrderAwareFrameIterator::OrderState OrderStateForIter(
70     const nsFlexContainerFrame* aFlexContainer) {
71   return aFlexContainer->HasAnyStateBits(
72              NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER)
73              ? CSSOrderAwareFrameIterator::OrderState::Ordered
74              : CSSOrderAwareFrameIterator::OrderState::Unordered;
75 }
76 
77 // Returns the OrderingProperty enum that we should pass to
78 // CSSOrderAwareFrameIterator (depending on whether it's a legacy box).
OrderingPropertyForIter(const nsFlexContainerFrame * aFlexContainer)79 static CSSOrderAwareFrameIterator::OrderingProperty OrderingPropertyForIter(
80     const nsFlexContainerFrame* aFlexContainer) {
81   return IsLegacyBox(aFlexContainer)
82              ? CSSOrderAwareFrameIterator::OrderingProperty::BoxOrdinalGroup
83              : CSSOrderAwareFrameIterator::OrderingProperty::Order;
84 }
85 
86 // Returns the "align-items" value that's equivalent to the legacy "box-align"
87 // value in the given style struct.
ConvertLegacyStyleToAlignItems(const nsStyleXUL * aStyleXUL)88 static StyleAlignFlags ConvertLegacyStyleToAlignItems(
89     const nsStyleXUL* aStyleXUL) {
90   // -[moz|webkit]-box-align corresponds to modern "align-items"
91   switch (aStyleXUL->mBoxAlign) {
92     case StyleBoxAlign::Stretch:
93       return StyleAlignFlags::STRETCH;
94     case StyleBoxAlign::Start:
95       return StyleAlignFlags::FLEX_START;
96     case StyleBoxAlign::Center:
97       return StyleAlignFlags::CENTER;
98     case StyleBoxAlign::Baseline:
99       return StyleAlignFlags::BASELINE;
100     case StyleBoxAlign::End:
101       return StyleAlignFlags::FLEX_END;
102   }
103 
104   MOZ_ASSERT_UNREACHABLE("Unrecognized mBoxAlign enum value");
105   // Fall back to default value of "align-items" property:
106   return StyleAlignFlags::STRETCH;
107 }
108 
109 // Returns the "justify-content" value that's equivalent to the legacy
110 // "box-pack" value in the given style struct.
ConvertLegacyStyleToJustifyContent(const nsStyleXUL * aStyleXUL)111 static StyleContentDistribution ConvertLegacyStyleToJustifyContent(
112     const nsStyleXUL* aStyleXUL) {
113   // -[moz|webkit]-box-pack corresponds to modern "justify-content"
114   switch (aStyleXUL->mBoxPack) {
115     case StyleBoxPack::Start:
116       return {StyleAlignFlags::FLEX_START};
117     case StyleBoxPack::Center:
118       return {StyleAlignFlags::CENTER};
119     case StyleBoxPack::End:
120       return {StyleAlignFlags::FLEX_END};
121     case StyleBoxPack::Justify:
122       return {StyleAlignFlags::SPACE_BETWEEN};
123   }
124 
125   MOZ_ASSERT_UNREACHABLE("Unrecognized mBoxPack enum value");
126   // Fall back to default value of "justify-content" property:
127   return {StyleAlignFlags::FLEX_START};
128 }
129 
130 /**
131  * Converts a "flex-relative" coordinate in a single axis (a main- or cross-axis
132  * coordinate) into a coordinate in the corresponding physical (x or y) axis. If
133  * the flex-relative axis in question already maps *directly* to a physical
134  * axis (i.e. if it's LTR or TTB), then the physical coordinate has the same
135  * numeric value as the provided flex-relative coordinate. Otherwise, we have to
136  * subtract the flex-relative coordinate from the flex container's size in that
137  * axis, to flip the polarity. (So e.g. a main-axis position of 2px in a RTL
138  * 20px-wide container would correspond to a physical coordinate (x-value) of
139  * 18px.)
140  */
PhysicalCoordFromFlexRelativeCoord(nscoord aFlexRelativeCoord,nscoord aContainerSize,mozilla::Side aStartSide)141 static nscoord PhysicalCoordFromFlexRelativeCoord(nscoord aFlexRelativeCoord,
142                                                   nscoord aContainerSize,
143                                                   mozilla::Side aStartSide) {
144   if (aStartSide == eSideLeft || aStartSide == eSideTop) {
145     return aFlexRelativeCoord;
146   }
147   return aContainerSize - aFlexRelativeCoord;
148 }
149 
150 // Check if the size is auto or it is a keyword in the block axis.
151 // |aIsInline| should represent whether aSize is in the inline axis, from the
152 // perspective of the writing mode of the flex item that the size comes from.
153 //
154 // max-content and min-content should behave as property's initial value.
155 // Bug 567039: We treat -moz-fit-content and -moz-available as property's
156 // initial value for now.
IsAutoOrEnumOnBSize(const StyleSize & aSize,bool aIsInline)157 static inline bool IsAutoOrEnumOnBSize(const StyleSize& aSize, bool aIsInline) {
158   return aSize.IsAuto() || (!aIsInline && !aSize.IsLengthPercentage());
159 }
160 
161 // Helper-macros to let us pick one of two expressions to evaluate
162 // (an inline-axis expression vs. a block-axis expression), to get a
163 // main-axis or cross-axis component.
164 // For code that has e.g. a LogicalSize object, the methods
165 // FlexboxAxisTracker::MainComponent and CrossComponent are cleaner
166 // than these macros. But in cases where we simply have two separate
167 // expressions for ISize and BSize (which may be expensive to evaluate),
168 // these macros can be used to ensure that only the needed expression is
169 // evaluated.
170 #define GET_MAIN_COMPONENT_LOGICAL(axisTracker_, wm_, isize_, bsize_) \
171   (axisTracker_).IsInlineAxisMainAxis((wm_)) ? (isize_) : (bsize_)
172 
173 #define GET_CROSS_COMPONENT_LOGICAL(axisTracker_, wm_, isize_, bsize_) \
174   (axisTracker_).IsInlineAxisMainAxis((wm_)) ? (bsize_) : (isize_)
175 
176 // Encapsulates our flex container's main & cross axes. This class is backed by
177 // a FlexboxAxisInfo helper member variable, and it adds some convenience APIs
178 // on top of what that struct offers.
179 class MOZ_STACK_CLASS nsFlexContainerFrame::FlexboxAxisTracker {
180  public:
181   explicit FlexboxAxisTracker(const nsFlexContainerFrame* aFlexContainer);
182 
183   // Accessors:
MainAxis() const184   LogicalAxis MainAxis() const {
185     return IsRowOriented() ? eLogicalAxisInline : eLogicalAxisBlock;
186   }
CrossAxis() const187   LogicalAxis CrossAxis() const {
188     return IsRowOriented() ? eLogicalAxisBlock : eLogicalAxisInline;
189   }
190 
191   LogicalSide MainAxisStartSide() const;
MainAxisEndSide() const192   LogicalSide MainAxisEndSide() const {
193     return GetOppositeSide(MainAxisStartSide());
194   }
195 
196   LogicalSide CrossAxisStartSide() const;
CrossAxisEndSide() const197   LogicalSide CrossAxisEndSide() const {
198     return GetOppositeSide(CrossAxisStartSide());
199   }
200 
MainAxisPhysicalStartSide() const201   mozilla::Side MainAxisPhysicalStartSide() const {
202     return mWM.PhysicalSide(MainAxisStartSide());
203   }
MainAxisPhysicalEndSide() const204   mozilla::Side MainAxisPhysicalEndSide() const {
205     return mWM.PhysicalSide(MainAxisEndSide());
206   }
207 
CrossAxisPhysicalStartSide() const208   mozilla::Side CrossAxisPhysicalStartSide() const {
209     return mWM.PhysicalSide(CrossAxisStartSide());
210   }
CrossAxisPhysicalEndSide() const211   mozilla::Side CrossAxisPhysicalEndSide() const {
212     return mWM.PhysicalSide(CrossAxisEndSide());
213   }
214 
215   // Returns the flex container's writing mode.
GetWritingMode() const216   WritingMode GetWritingMode() const { return mWM; }
217 
218   // Returns true if our main axis is in the reverse direction of our
219   // writing mode's corresponding axis. (From 'flex-direction: *-reverse')
IsMainAxisReversed() const220   bool IsMainAxisReversed() const { return mAxisInfo.mIsMainAxisReversed; }
221   // Returns true if our cross axis is in the reverse direction of our
222   // writing mode's corresponding axis. (From 'flex-wrap: *-reverse')
IsCrossAxisReversed() const223   bool IsCrossAxisReversed() const { return mAxisInfo.mIsCrossAxisReversed; }
224 
IsRowOriented() const225   bool IsRowOriented() const { return mAxisInfo.mIsRowOriented; }
IsColumnOriented() const226   bool IsColumnOriented() const { return !IsRowOriented(); }
227 
228   // aSize is expected to match the flex container's WritingMode.
MainComponent(const LogicalSize & aSize) const229   nscoord MainComponent(const LogicalSize& aSize) const {
230     return IsRowOriented() ? aSize.ISize(mWM) : aSize.BSize(mWM);
231   }
MainComponent(const LayoutDeviceIntSize & aIntSize) const232   int32_t MainComponent(const LayoutDeviceIntSize& aIntSize) const {
233     return IsMainAxisHorizontal() ? aIntSize.width : aIntSize.height;
234   }
235 
236   // aSize is expected to match the flex container's WritingMode.
CrossComponent(const LogicalSize & aSize) const237   nscoord CrossComponent(const LogicalSize& aSize) const {
238     return IsRowOriented() ? aSize.BSize(mWM) : aSize.ISize(mWM);
239   }
CrossComponent(const LayoutDeviceIntSize & aIntSize) const240   int32_t CrossComponent(const LayoutDeviceIntSize& aIntSize) const {
241     return IsMainAxisHorizontal() ? aIntSize.height : aIntSize.width;
242   }
243 
244   // NOTE: aMargin is expected to use the flex container's WritingMode.
MarginSizeInMainAxis(const LogicalMargin & aMargin) const245   nscoord MarginSizeInMainAxis(const LogicalMargin& aMargin) const {
246     // If we're row-oriented, our main axis is the inline axis.
247     return IsRowOriented() ? aMargin.IStartEnd(mWM) : aMargin.BStartEnd(mWM);
248   }
MarginSizeInCrossAxis(const LogicalMargin & aMargin) const249   nscoord MarginSizeInCrossAxis(const LogicalMargin& aMargin) const {
250     // If we're row-oriented, our cross axis is the block axis.
251     return IsRowOriented() ? aMargin.BStartEnd(mWM) : aMargin.IStartEnd(mWM);
252   }
253 
254   /**
255    * Converts a "flex-relative" point (a main-axis & cross-axis coordinate)
256    * into a LogicalPoint, using the flex container's writing mode.
257    *
258    *  @arg aMainCoord  The main-axis coordinate -- i.e an offset from the
259    *                   main-start edge of the flex container's content box.
260    *  @arg aCrossCoord The cross-axis coordinate -- i.e an offset from the
261    *                   cross-start edge of the flex container's content box.
262    *  @arg aContainerMainSize  The main size of flex container's content box.
263    *  @arg aContainerCrossSize The cross size of flex container's content box.
264    *  @return A LogicalPoint, with the flex container's writing mode, that
265    *          represents the same position. The logical coordinates are
266    *          relative to the flex container's content box.
267    */
LogicalPointFromFlexRelativePoint(nscoord aMainCoord,nscoord aCrossCoord,nscoord aContainerMainSize,nscoord aContainerCrossSize) const268   LogicalPoint LogicalPointFromFlexRelativePoint(
269       nscoord aMainCoord, nscoord aCrossCoord, nscoord aContainerMainSize,
270       nscoord aContainerCrossSize) const {
271     nscoord logicalCoordInMainAxis =
272         IsMainAxisReversed() ? aContainerMainSize - aMainCoord : aMainCoord;
273     nscoord logicalCoordInCrossAxis =
274         IsCrossAxisReversed() ? aContainerCrossSize - aCrossCoord : aCrossCoord;
275 
276     return IsRowOriented() ? LogicalPoint(mWM, logicalCoordInMainAxis,
277                                           logicalCoordInCrossAxis)
278                            : LogicalPoint(mWM, logicalCoordInCrossAxis,
279                                           logicalCoordInMainAxis);
280   }
281 
282   /**
283    * Converts a "flex-relative" size (a main-axis & cross-axis size)
284    * into a LogicalSize, using the flex container's writing mode.
285    *
286    *  @arg aMainSize  The main-axis size.
287    *  @arg aCrossSize The cross-axis size.
288    *  @return A LogicalSize, with the flex container's writing mode, that
289    *          represents the same size.
290    */
LogicalSizeFromFlexRelativeSizes(nscoord aMainSize,nscoord aCrossSize) const291   LogicalSize LogicalSizeFromFlexRelativeSizes(nscoord aMainSize,
292                                                nscoord aCrossSize) const {
293     return IsRowOriented() ? LogicalSize(mWM, aMainSize, aCrossSize)
294                            : LogicalSize(mWM, aCrossSize, aMainSize);
295   }
296 
IsMainAxisHorizontal() const297   bool IsMainAxisHorizontal() const {
298     // If we're row-oriented, and our writing mode is NOT vertical,
299     // or we're column-oriented and our writing mode IS vertical,
300     // then our main axis is horizontal. This handles all cases:
301     return IsRowOriented() != mWM.IsVertical();
302   }
303 
304   // Returns true if this flex item's inline axis in aItemWM is parallel (or
305   // antiparallel) to the container's main axis. Returns false, otherwise.
306   //
307   // Note: this is a helper for implementing macros and can also be used before
308   // constructing FlexItem. Inside of flex reflow code,
309   // FlexItem::IsInlineAxisMainAxis() is equivalent & more optimal.
IsInlineAxisMainAxis(WritingMode aItemWM) const310   bool IsInlineAxisMainAxis(WritingMode aItemWM) const {
311     return IsRowOriented() != GetWritingMode().IsOrthogonalTo(aItemWM);
312   }
313 
314   // Maps justify-*: 'left' or 'right' to 'start' or 'end'.
ResolveJustifyLeftRight(const StyleAlignFlags & aFlags) const315   StyleAlignFlags ResolveJustifyLeftRight(const StyleAlignFlags& aFlags) const {
316     MOZ_ASSERT(
317         aFlags == StyleAlignFlags::LEFT || aFlags == StyleAlignFlags::RIGHT,
318         "This helper accepts only 'LEFT' or 'RIGHT' flags!");
319 
320     const auto wm = GetWritingMode();
321     const bool isJustifyLeft = aFlags == StyleAlignFlags::LEFT;
322     if (IsColumnOriented()) {
323       if (!wm.IsVertical()) {
324         // Container's alignment axis (main axis) is *not* parallel to the
325         // line-left <-> line-right axis or the physical left <-> physical right
326         // axis, so we map both 'left' and 'right' to 'start'.
327         return StyleAlignFlags::START;
328       }
329 
330       MOZ_ASSERT(wm.PhysicalAxis(MainAxis()) == eAxisHorizontal,
331                  "Vertical column-oriented flex container's main axis should "
332                  "be parallel to physical left <-> right axis!");
333       // Map 'left' or 'right' to 'start' or 'end', depending on its block flow
334       // direction.
335       return isJustifyLeft == wm.IsVerticalLR() ? StyleAlignFlags::START
336                                                 : StyleAlignFlags::END;
337     }
338 
339     MOZ_ASSERT(MainAxis() == eLogicalAxisInline,
340                "Row-oriented flex container's main axis should be parallel to "
341                "line-left <-> line-right axis!");
342 
343     // If we get here, we're operating on the flex container's inline axis,
344     // so we map 'left' to whichever of 'start' or 'end' corresponds to the
345     // *line-relative* left side; and similar for 'right'.
346     return isJustifyLeft == wm.IsBidiLTR() ? StyleAlignFlags::START
347                                            : StyleAlignFlags::END;
348   }
349 
350   // Delete copy-constructor & reassignment operator, to prevent accidental
351   // (unnecessary) copying.
352   FlexboxAxisTracker(const FlexboxAxisTracker&) = delete;
353   FlexboxAxisTracker& operator=(const FlexboxAxisTracker&) = delete;
354 
355  private:
356   const WritingMode mWM;  // The flex container's writing mode.
357   const FlexboxAxisInfo mAxisInfo;
358 };
359 
360 /**
361  * Represents a flex item.
362  * Includes the various pieces of input that the Flexbox Layout Algorithm uses
363  * to resolve a flexible width.
364  */
365 class nsFlexContainerFrame::FlexItem final {
366  public:
367   // Normal constructor:
368   FlexItem(ReflowInput& aFlexItemReflowInput, float aFlexGrow,
369            float aFlexShrink, nscoord aFlexBaseSize, nscoord aMainMinSize,
370            nscoord aMainMaxSize, nscoord aTentativeCrossSize,
371            nscoord aCrossMinSize, nscoord aCrossMaxSize,
372            const FlexboxAxisTracker& aAxisTracker);
373 
374   // Simplified constructor, to be used only for generating "struts":
375   // (NOTE: This "strut" constructor uses the *container's* writing mode, which
376   // we'll use on this FlexItem instead of the child frame's real writing mode.
377   // This is fine - it doesn't matter what writing mode we use for a
378   // strut, since it won't render any content and we already know its size.)
379   FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize, WritingMode aContainerWM,
380            const FlexboxAxisTracker& aAxisTracker);
381 
382   // Clone existing FlexItem for its underlying frame's continuation.
383   // @param aContinuation a continuation in our next-in-flow chain.
CloneFor(nsIFrame * const aContinuation) const384   FlexItem CloneFor(nsIFrame* const aContinuation) const {
385     MOZ_ASSERT(Frame() == aContinuation->FirstInFlow(),
386                "aContinuation should be in aItem's continuation chain!");
387     FlexItem item(*this);
388     item.mFrame = aContinuation;
389     item.mHadMeasuringReflow = false;
390     return item;
391   }
392 
393   // Accessors
Frame() const394   nsIFrame* Frame() const { return mFrame; }
FlexBaseSize() const395   nscoord FlexBaseSize() const { return mFlexBaseSize; }
396 
MainMinSize() const397   nscoord MainMinSize() const {
398     MOZ_ASSERT(!mNeedsMinSizeAutoResolution,
399                "Someone's using an unresolved 'auto' main min-size");
400     return mMainMinSize;
401   }
MainMaxSize() const402   nscoord MainMaxSize() const { return mMainMaxSize; }
403 
404   // Note: These return the main-axis position and size of our *content box*.
MainSize() const405   nscoord MainSize() const { return mMainSize; }
MainPosition() const406   nscoord MainPosition() const { return mMainPosn; }
407 
CrossMinSize() const408   nscoord CrossMinSize() const { return mCrossMinSize; }
CrossMaxSize() const409   nscoord CrossMaxSize() const { return mCrossMaxSize; }
410 
411   // Note: These return the cross-axis position and size of our *content box*.
CrossSize() const412   nscoord CrossSize() const { return mCrossSize; }
CrossPosition() const413   nscoord CrossPosition() const { return mCrossPosn; }
414 
415   // Lazy getter for mAscent.
ResolvedAscent(bool aUseFirstBaseline) const416   nscoord ResolvedAscent(bool aUseFirstBaseline) const {
417     // XXXdholbert Two concerns to follow up on here:
418     // (1) We probably should be checking and reacting to aUseFirstBaseline
419     // for all of the cases here (e.g. this first one). Maybe we need to store
420     // two versions of mAscent and choose the appropriate one based on
421     // aUseFirstBaseline? This is roughly bug 1480850, I think.
422     // (2) We should be using the *container's* writing-mode (mCBWM) here,
423     // instead of the item's (mWM). This is essentially bug 1155322.
424     if (mAscent != ReflowOutput::ASK_FOR_BASELINE) {
425       return mAscent;
426     }
427 
428     // Use GetFirstLineBaseline() or GetLastLineBaseline() as appropriate:
429     bool found =
430         aUseFirstBaseline
431             ? nsLayoutUtils::GetFirstLineBaseline(mWM, mFrame, &mAscent)
432             : nsLayoutUtils::GetLastLineBaseline(mWM, mFrame, &mAscent);
433     if (found) {
434       return mAscent;
435     }
436 
437     // If the nsLayoutUtils getter fails, then ask the frame directly:
438     auto baselineGroup = aUseFirstBaseline ? BaselineSharingGroup::First
439                                            : BaselineSharingGroup::Last;
440     if (mFrame->GetNaturalBaselineBOffset(mWM, baselineGroup, &mAscent)) {
441       return mAscent;
442     }
443 
444     // We couldn't determine a baseline, so we synthesize one from border box:
445     mAscent = mFrame->SynthesizeBaselineBOffsetFromBorderBox(
446         mWM, BaselineSharingGroup::First);
447     return mAscent;
448   }
449 
450   // Convenience methods to compute the main & cross size of our *margin-box*.
OuterMainSize() const451   nscoord OuterMainSize() const {
452     return mMainSize + MarginBorderPaddingSizeInMainAxis();
453   }
454 
OuterCrossSize() const455   nscoord OuterCrossSize() const {
456     return mCrossSize + MarginBorderPaddingSizeInCrossAxis();
457   }
458 
459   // Convenience methods to synthesize a style main size or a style cross size
460   // with box-size considered, to provide the size overrides when constructing
461   // ReflowInput for flex items.
StyleMainSize() const462   StyleSize StyleMainSize() const {
463     nscoord mainSize = MainSize();
464     if (Frame()->StylePosition()->mBoxSizing == StyleBoxSizing::Border) {
465       mainSize += BorderPaddingSizeInMainAxis();
466     }
467     return StyleSize::LengthPercentage(
468         LengthPercentage::FromAppUnits(mainSize));
469   }
470 
StyleCrossSize() const471   StyleSize StyleCrossSize() const {
472     nscoord crossSize = CrossSize();
473     if (Frame()->StylePosition()->mBoxSizing == StyleBoxSizing::Border) {
474       crossSize += BorderPaddingSizeInCrossAxis();
475     }
476     return StyleSize::LengthPercentage(
477         LengthPercentage::FromAppUnits(crossSize));
478   }
479 
480   // Returns the distance between this FlexItem's baseline and the cross-start
481   // edge of its margin-box. Used in baseline alignment.
482   //
483   // (This function needs to be told which physical start side we're measuring
484   // the baseline from, so that it can look up the appropriate components from
485   // margin.)
486   nscoord BaselineOffsetFromOuterCrossEdge(mozilla::Side aStartSide,
487                                            bool aUseFirstLineBaseline) const;
488 
ShareOfWeightSoFar() const489   double ShareOfWeightSoFar() const { return mShareOfWeightSoFar; }
490 
IsFrozen() const491   bool IsFrozen() const { return mIsFrozen; }
492 
HadMinViolation() const493   bool HadMinViolation() const {
494     MOZ_ASSERT(!mIsFrozen, "min violation has no meaning for frozen items.");
495     return mHadMinViolation;
496   }
497 
HadMaxViolation() const498   bool HadMaxViolation() const {
499     MOZ_ASSERT(!mIsFrozen, "max violation has no meaning for frozen items.");
500     return mHadMaxViolation;
501   }
502 
WasMinClamped() const503   bool WasMinClamped() const {
504     MOZ_ASSERT(mIsFrozen, "min clamping has no meaning for unfrozen items.");
505     return mHadMinViolation;
506   }
507 
WasMaxClamped() const508   bool WasMaxClamped() const {
509     MOZ_ASSERT(mIsFrozen, "max clamping has no meaning for unfrozen items.");
510     return mHadMaxViolation;
511   }
512 
513   // Indicates whether this item received a preliminary "measuring" reflow
514   // before its actual reflow.
HadMeasuringReflow() const515   bool HadMeasuringReflow() const { return mHadMeasuringReflow; }
516 
517   // Indicates whether this item's computed cross-size property is 'auto'.
518   bool IsCrossSizeAuto() const;
519 
520   // Indicates whether the cross-size property is set to something definite,
521   // for the purpose of preferred aspect ratio calculations.
522   bool IsCrossSizeDefinite(const ReflowInput& aItemReflowInput) const;
523 
524   // Indicates whether this item's cross-size has been stretched (from having
525   // "align-self: stretch" with an auto cross-size and no auto margins in the
526   // cross axis).
IsStretched() const527   bool IsStretched() const { return mIsStretched; }
528 
529   // Indicates whether we need to resolve an 'auto' value for the main-axis
530   // min-[width|height] property.
NeedsMinSizeAutoResolution() const531   bool NeedsMinSizeAutoResolution() const {
532     return mNeedsMinSizeAutoResolution;
533   }
534 
HasAnyAutoMargin() const535   bool HasAnyAutoMargin() const { return mHasAnyAutoMargin; }
536 
537   // Indicates whether this item is a "strut" left behind by an element with
538   // visibility:collapse.
IsStrut() const539   bool IsStrut() const { return mIsStrut; }
540 
541   // The main axis and cross axis are relative to mCBWM.
MainAxis() const542   LogicalAxis MainAxis() const { return mMainAxis; }
CrossAxis() const543   LogicalAxis CrossAxis() const { return GetOrthogonalAxis(mMainAxis); }
544 
545   // IsInlineAxisMainAxis() returns true if this item's inline axis is parallel
546   // (or antiparallel) to the container's main axis. Otherwise (i.e. if this
547   // item's inline axis is orthogonal to the container's main axis), this
548   // function returns false. The next 3 methods are all other ways of asking
549   // the same question, and only exist for readability at callsites (depending
550   // on which axes those callsites are reasoning about).
IsInlineAxisMainAxis() const551   bool IsInlineAxisMainAxis() const { return mIsInlineAxisMainAxis; }
IsInlineAxisCrossAxis() const552   bool IsInlineAxisCrossAxis() const { return !mIsInlineAxisMainAxis; }
IsBlockAxisMainAxis() const553   bool IsBlockAxisMainAxis() const { return !mIsInlineAxisMainAxis; }
IsBlockAxisCrossAxis() const554   bool IsBlockAxisCrossAxis() const { return mIsInlineAxisMainAxis; }
555 
GetWritingMode() const556   WritingMode GetWritingMode() const { return mWM; }
ContainingBlockWM() const557   WritingMode ContainingBlockWM() const { return mCBWM; }
AlignSelf() const558   StyleAlignSelf AlignSelf() const { return mAlignSelf; }
AlignSelfFlags() const559   StyleAlignFlags AlignSelfFlags() const { return mAlignSelfFlags; }
560 
561   // Returns the flex factor (flex-grow or flex-shrink), depending on
562   // 'aIsUsingFlexGrow'.
563   //
564   // Asserts fatally if called on a frozen item (since frozen items are not
565   // flexible).
GetFlexFactor(bool aIsUsingFlexGrow)566   float GetFlexFactor(bool aIsUsingFlexGrow) {
567     MOZ_ASSERT(!IsFrozen(), "shouldn't need flex factor after item is frozen");
568 
569     return aIsUsingFlexGrow ? mFlexGrow : mFlexShrink;
570   }
571 
572   // Returns the weight that we should use in the "resolving flexible lengths"
573   // algorithm.  If we're using the flex grow factor, we just return that;
574   // otherwise, we return the "scaled flex shrink factor" (scaled by our flex
575   // base size, so that when both large and small items are shrinking, the large
576   // items shrink more).
577   //
578   // I'm calling this a "weight" instead of a "[scaled] flex-[grow|shrink]
579   // factor", to more clearly distinguish it from the actual flex-grow &
580   // flex-shrink factors.
581   //
582   // Asserts fatally if called on a frozen item (since frozen items are not
583   // flexible).
GetWeight(bool aIsUsingFlexGrow)584   float GetWeight(bool aIsUsingFlexGrow) {
585     MOZ_ASSERT(!IsFrozen(), "shouldn't need weight after item is frozen");
586 
587     if (aIsUsingFlexGrow) {
588       return mFlexGrow;
589     }
590 
591     // We're using flex-shrink --> return mFlexShrink * mFlexBaseSize
592     if (mFlexBaseSize == 0) {
593       // Special-case for mFlexBaseSize == 0 -- we have no room to shrink, so
594       // regardless of mFlexShrink, we should just return 0.
595       // (This is really a special-case for when mFlexShrink is infinity, to
596       // avoid performing mFlexShrink * mFlexBaseSize = inf * 0 = undefined.)
597       return 0.0f;
598     }
599     return mFlexShrink * mFlexBaseSize;
600   }
601 
TreatBSizeAsIndefinite() const602   bool TreatBSizeAsIndefinite() const { return mTreatBSizeAsIndefinite; }
603 
GetAspectRatio() const604   const AspectRatio& GetAspectRatio() const { return mAspectRatio; }
HasAspectRatio() const605   bool HasAspectRatio() const { return !!mAspectRatio; }
606 
607   // Getters for margin:
608   // ===================
Margin() const609   LogicalMargin Margin() const { return mMargin; }
PhysicalMargin() const610   nsMargin PhysicalMargin() const { return mMargin.GetPhysicalMargin(mCBWM); }
611 
612   // Returns the margin component for a given LogicalSide in flex container's
613   // writing-mode.
GetMarginComponentForSide(LogicalSide aSide) const614   nscoord GetMarginComponentForSide(LogicalSide aSide) const {
615     return mMargin.Side(aSide, mCBWM);
616   }
617 
618   // Returns the total space occupied by this item's margins in the given axis
MarginSizeInMainAxis() const619   nscoord MarginSizeInMainAxis() const {
620     return mMargin.StartEnd(MainAxis(), mCBWM);
621   }
MarginSizeInCrossAxis() const622   nscoord MarginSizeInCrossAxis() const {
623     return mMargin.StartEnd(CrossAxis(), mCBWM);
624   }
625 
626   // Getters for border/padding
627   // ==========================
628   // Returns the total space occupied by this item's borders and padding in
629   // the given axis
BorderPadding() const630   LogicalMargin BorderPadding() const { return mBorderPadding; }
BorderPaddingSizeInMainAxis() const631   nscoord BorderPaddingSizeInMainAxis() const {
632     return mBorderPadding.StartEnd(MainAxis(), mCBWM);
633   }
BorderPaddingSizeInCrossAxis() const634   nscoord BorderPaddingSizeInCrossAxis() const {
635     return mBorderPadding.StartEnd(CrossAxis(), mCBWM);
636   }
637 
638   // Getter for combined margin/border/padding
639   // =========================================
640   // Returns the total space occupied by this item's margins, borders and
641   // padding in the given axis
MarginBorderPaddingSizeInMainAxis() const642   nscoord MarginBorderPaddingSizeInMainAxis() const {
643     return MarginSizeInMainAxis() + BorderPaddingSizeInMainAxis();
644   }
MarginBorderPaddingSizeInCrossAxis() const645   nscoord MarginBorderPaddingSizeInCrossAxis() const {
646     return MarginSizeInCrossAxis() + BorderPaddingSizeInCrossAxis();
647   }
648 
649   // Setters
650   // =======
651   // Helper to set the resolved value of min-[width|height]:auto for the main
652   // axis. (Should only be used if NeedsMinSizeAutoResolution() returns true.)
UpdateMainMinSize(nscoord aNewMinSize)653   void UpdateMainMinSize(nscoord aNewMinSize) {
654     NS_ASSERTION(aNewMinSize >= 0,
655                  "How did we end up with a negative min-size?");
656     MOZ_ASSERT(
657         mMainMaxSize == NS_UNCONSTRAINEDSIZE || mMainMaxSize >= aNewMinSize,
658         "Should only use this function for resolving min-size:auto, "
659         "and main max-size should be an upper-bound for resolved val");
660     MOZ_ASSERT(
661         mNeedsMinSizeAutoResolution &&
662             (mMainMinSize == 0 || mFrame->IsThemed(mFrame->StyleDisplay())),
663         "Should only use this function for resolving min-size:auto, "
664         "so we shouldn't already have a nonzero min-size established "
665         "(unless it's a themed-widget-imposed minimum size)");
666 
667     if (aNewMinSize > mMainMinSize) {
668       mMainMinSize = aNewMinSize;
669       // Also clamp main-size to be >= new min-size:
670       mMainSize = std::max(mMainSize, aNewMinSize);
671     }
672     mNeedsMinSizeAutoResolution = false;
673   }
674 
675   // This sets our flex base size, and then sets our main size to the
676   // resulting "hypothetical main size" (the base size clamped to our
677   // main-axis [min,max] sizing constraints).
SetFlexBaseSizeAndMainSize(nscoord aNewFlexBaseSize)678   void SetFlexBaseSizeAndMainSize(nscoord aNewFlexBaseSize) {
679     MOZ_ASSERT(!mIsFrozen || mFlexBaseSize == NS_UNCONSTRAINEDSIZE,
680                "flex base size shouldn't change after we're frozen "
681                "(unless we're just resolving an intrinsic size)");
682     mFlexBaseSize = aNewFlexBaseSize;
683 
684     // Before we've resolved flexible lengths, we keep mMainSize set to
685     // the 'hypothetical main size', which is the flex base size, clamped
686     // to the [min,max] range:
687     mMainSize = NS_CSS_MINMAX(mFlexBaseSize, mMainMinSize, mMainMaxSize);
688 
689     FLEX_LOGV(
690         "Set flex base size: %d, hypothetical main size: %d for flex item %p",
691         mFlexBaseSize, mMainSize, mFrame);
692   }
693 
694   // Setters used while we're resolving flexible lengths
695   // ---------------------------------------------------
696 
697   // Sets the main-size of our flex item's content-box.
SetMainSize(nscoord aNewMainSize)698   void SetMainSize(nscoord aNewMainSize) {
699     MOZ_ASSERT(!mIsFrozen, "main size shouldn't change after we're frozen");
700     mMainSize = aNewMainSize;
701   }
702 
SetShareOfWeightSoFar(double aNewShare)703   void SetShareOfWeightSoFar(double aNewShare) {
704     MOZ_ASSERT(!mIsFrozen || aNewShare == 0.0,
705                "shouldn't be giving this item any share of the weight "
706                "after it's frozen");
707     mShareOfWeightSoFar = aNewShare;
708   }
709 
Freeze()710   void Freeze() {
711     mIsFrozen = true;
712     // Now that we are frozen, the meaning of mHadMinViolation and
713     // mHadMaxViolation changes to indicate min and max clamping. Clear
714     // both of the member variables so that they are ready to be set
715     // as clamping state later, if necessary.
716     mHadMinViolation = false;
717     mHadMaxViolation = false;
718   }
719 
SetHadMinViolation()720   void SetHadMinViolation() {
721     MOZ_ASSERT(!mIsFrozen,
722                "shouldn't be changing main size & having violations "
723                "after we're frozen");
724     mHadMinViolation = true;
725   }
SetHadMaxViolation()726   void SetHadMaxViolation() {
727     MOZ_ASSERT(!mIsFrozen,
728                "shouldn't be changing main size & having violations "
729                "after we're frozen");
730     mHadMaxViolation = true;
731   }
ClearViolationFlags()732   void ClearViolationFlags() {
733     MOZ_ASSERT(!mIsFrozen,
734                "shouldn't be altering violation flags after we're "
735                "frozen");
736     mHadMinViolation = mHadMaxViolation = false;
737   }
738 
SetWasMinClamped()739   void SetWasMinClamped() {
740     MOZ_ASSERT(!mHadMinViolation && !mHadMaxViolation, "only clamp once");
741     // This reuses the mHadMinViolation member variable to track clamping
742     // events. This is allowable because mHadMinViolation only reflects
743     // a violation up until the item is frozen.
744     MOZ_ASSERT(mIsFrozen, "shouldn't set clamping state when we are unfrozen");
745     mHadMinViolation = true;
746   }
SetWasMaxClamped()747   void SetWasMaxClamped() {
748     MOZ_ASSERT(!mHadMinViolation && !mHadMaxViolation, "only clamp once");
749     // This reuses the mHadMaxViolation member variable to track clamping
750     // events. This is allowable because mHadMaxViolation only reflects
751     // a violation up until the item is frozen.
752     MOZ_ASSERT(mIsFrozen, "shouldn't set clamping state when we are unfrozen");
753     mHadMaxViolation = true;
754   }
755 
756   // Setters for values that are determined after we've resolved our main size
757   // -------------------------------------------------------------------------
758 
759   // Sets the main-axis position of our flex item's content-box.
760   // (This is the distance between the main-start edge of the flex container
761   // and the main-start edge of the flex item's content-box.)
SetMainPosition(nscoord aPosn)762   void SetMainPosition(nscoord aPosn) {
763     MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
764     mMainPosn = aPosn;
765   }
766 
767   // Sets the cross-size of our flex item's content-box.
SetCrossSize(nscoord aCrossSize)768   void SetCrossSize(nscoord aCrossSize) {
769     MOZ_ASSERT(!mIsStretched,
770                "Cross size shouldn't be modified after it's been stretched");
771     mCrossSize = aCrossSize;
772   }
773 
774   // Sets the cross-axis position of our flex item's content-box.
775   // (This is the distance between the cross-start edge of the flex container
776   // and the cross-start edge of the flex item.)
SetCrossPosition(nscoord aPosn)777   void SetCrossPosition(nscoord aPosn) {
778     MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
779     mCrossPosn = aPosn;
780   }
781 
782   // After a FlexItem has had a reflow, this method can be used to cache its
783   // (possibly-unresolved) ascent, in case it's needed later for
784   // baseline-alignment or to establish the container's baseline.
785   // (NOTE: This can be marked 'const' even though it's modifying mAscent,
786   // because mAscent is mutable. It's nice for this to be 'const', because it
787   // means our final reflow can iterate over const FlexItem pointers, and we
788   // can be sure it's not modifying those FlexItems, except via this method.)
SetAscent(nscoord aAscent) const789   void SetAscent(nscoord aAscent) const {
790     mAscent = aAscent;  // NOTE: this may be ASK_FOR_BASELINE
791   }
792 
SetHadMeasuringReflow()793   void SetHadMeasuringReflow() { mHadMeasuringReflow = true; }
794 
SetIsStretched()795   void SetIsStretched() {
796     MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
797     mIsStretched = true;
798   }
799 
800   // Setter for margin components (for resolving "auto" margins)
SetMarginComponentForSide(LogicalSide aSide,nscoord aLength)801   void SetMarginComponentForSide(LogicalSide aSide, nscoord aLength) {
802     MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
803     mMargin.Side(aSide, mCBWM) = aLength;
804   }
805 
806   void ResolveStretchedCrossSize(nscoord aLineCrossSize);
807 
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);
811 
NumAutoMarginsInMainAxis() const812   uint32_t NumAutoMarginsInMainAxis() const {
813     return NumAutoMarginsInAxis(MainAxis());
814   };
815 
NumAutoMarginsInCrossAxis() const816   uint32_t NumAutoMarginsInCrossAxis() const {
817     return NumAutoMarginsInAxis(CrossAxis());
818   };
819 
820   // Once the main size has been resolved, should we bother doing layout to
821   // establish the cross size?
822   bool CanMainSizeInfluenceCrossSize() const;
823 
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;
829 
830   // Indicates whether we think this flex item needs a "final" reflow
831   // (after its final flexed size & final position have been determined).
832   //
833   // @param aAvailableBSizeForItem the available block-size for this item (in
834   //                               flex container's writing-mode)
835   // @return true if such a reflow is needed, or false if we believe it can
836   // simply be moved to its final position and skip the reflow.
837   bool NeedsFinalReflow(const nscoord aAvailableBSizeForItem) const;
838 
839   // Gets the block frame that contains the flex item's content.  This is
840   // Frame() itself or one of its descendants.
841   nsBlockFrame* BlockFrame() const;
842 
843  protected:
844   // Helper called by the constructor, to set mNeedsMinSizeAutoResolution:
845   void CheckForMinSizeAuto(const ReflowInput& aFlexItemReflowInput,
846                            const FlexboxAxisTracker& aAxisTracker);
847 
848   uint32_t NumAutoMarginsInAxis(LogicalAxis aAxis) const;
849 
850   // Values that we already know in constructor, and remain unchanged:
851   // The flex item's frame.
852   nsIFrame* mFrame = nullptr;
853   float mFlexGrow = 0.0f;
854   float mFlexShrink = 0.0f;
855   AspectRatio mAspectRatio;
856 
857   // The flex item's writing mode.
858   WritingMode mWM;
859 
860   // The flex container's writing mode.
861   WritingMode mCBWM;
862 
863   // The flex container's main axis in flex container's writing mode.
864   LogicalAxis mMainAxis;
865 
866   // Stored in flex container's writing mode.
867   LogicalMargin mBorderPadding;
868 
869   // Stored in flex container's writing mode. Its value can change when we
870   // resolve "auto" marigns.
871   LogicalMargin mMargin;
872 
873   // These are non-const so that we can lazily update them with the item's
874   // intrinsic size (obtained via a "measuring" reflow), when necessary.
875   // (e.g. for "flex-basis:auto;height:auto" & "min-height:auto")
876   nscoord mFlexBaseSize = 0;
877   nscoord mMainMinSize = 0;
878   nscoord mMainMaxSize = 0;
879 
880   // mCrossMinSize and mCrossMaxSize are not changed after constructor.
881   nscoord mCrossMinSize = 0;
882   nscoord mCrossMaxSize = 0;
883 
884   // Values that we compute after constructor:
885   nscoord mMainSize = 0;
886   nscoord mMainPosn = 0;
887   nscoord mCrossSize = 0;
888   nscoord mCrossPosn = 0;
889 
890   // Mutable b/c it's set & resolved lazily, sometimes via const pointer. See
891   // comment above SetAscent().
892   // We initialize this to ASK_FOR_BASELINE, and opportunistically fill it in
893   // with a real value if we end up reflowing this flex item. (But if we don't
894   // reflow this flex item, then this sentinel tells us that we don't know it
895   // yet & anyone who cares will need to explicitly request it.)
896   mutable nscoord mAscent = ReflowOutput::ASK_FOR_BASELINE;
897 
898   // Temporary state, while we're resolving flexible widths (for our main size)
899   // XXXdholbert To save space, we could use a union to make these variables
900   // overlay the same memory as some other member vars that aren't touched
901   // until after main-size has been resolved. In particular, these could share
902   // memory with mMainPosn through mAscent, and mIsStretched.
903   double mShareOfWeightSoFar = 0.0;
904 
905   bool mIsFrozen = false;
906   bool mHadMinViolation = false;
907   bool mHadMaxViolation = false;
908 
909   // Did this item get a preliminary reflow, to measure its desired height?
910   bool mHadMeasuringReflow = false;
911 
912   // See IsStretched() documentation.
913   bool mIsStretched = false;
914 
915   // Is this item a "strut" left behind by an element with visibility:collapse?
916   bool mIsStrut = false;
917 
918   // See IsInlineAxisMainAxis() documentation. This is not changed after
919   // constructor.
920   bool mIsInlineAxisMainAxis = true;
921 
922   // Does this item need to resolve a min-[width|height]:auto (in main-axis).
923   bool mNeedsMinSizeAutoResolution = false;
924 
925   // Should we take care to treat this item's resolved BSize as indefinite?
926   bool mTreatBSizeAsIndefinite = false;
927 
928   // Does this item have an auto margin in either main or cross axis?
929   bool mHasAnyAutoMargin = false;
930 
931   // My "align-self" computed value (with "auto" swapped out for parent"s
932   // "align-items" value, in our constructor).
933   StyleAlignSelf mAlignSelf{StyleAlignFlags::AUTO};
934 
935   // Flags for 'align-self' (safe/unsafe/legacy).
936   StyleAlignFlags mAlignSelfFlags{0};
937 };
938 
939 /**
940  * Represents a single flex line in a flex container.
941  * Manages an array of the FlexItems that are in the line.
942  */
943 class nsFlexContainerFrame::FlexLine final {
944  public:
FlexLine(nscoord aMainGapSize)945   explicit FlexLine(nscoord aMainGapSize) : mMainGapSize(aMainGapSize) {}
946 
SumOfGaps() const947   nscoord SumOfGaps() const {
948     return NumItems() > 0 ? (NumItems() - 1) * mMainGapSize : 0;
949   }
950 
951   // Returns the sum of our FlexItems' outer hypothetical main sizes plus the
952   // sum of main axis {row,column}-gaps between items.
953   // ("outer" = margin-box, and "hypothetical" = before flexing)
TotalOuterHypotheticalMainSize() const954   AuCoord64 TotalOuterHypotheticalMainSize() const {
955     return mTotalOuterHypotheticalMainSize;
956   }
957 
958   // Accessors for our FlexItems & information about them:
959   //
960   // Note: Using IsEmpty() to ensure that the FlexLine is non-empty before
961   // calling FirstItem() or LastItem().
FirstItem()962   FlexItem& FirstItem() { return mItems[0]; }
FirstItem() const963   const FlexItem& FirstItem() const { return mItems[0]; }
964 
LastItem()965   FlexItem& LastItem() { return mItems.LastElement(); }
LastItem() const966   const FlexItem& LastItem() const { return mItems.LastElement(); }
967 
IsEmpty() const968   bool IsEmpty() const { return mItems.IsEmpty(); }
969 
NumItems() const970   uint32_t NumItems() const { return mItems.Length(); }
971 
Items()972   nsTArray<FlexItem>& Items() { return mItems; }
Items() const973   const nsTArray<FlexItem>& Items() const { return mItems; }
974 
975   // Adds the last flex item's hypothetical outer main-size and
976   // margin/border/padding to our totals. This should be called exactly once for
977   // each flex item, after we've determined that this line is the correct home
978   // for that item.
AddLastItemToMainSizeTotals()979   void AddLastItemToMainSizeTotals() {
980     const FlexItem& lastItem = Items().LastElement();
981 
982     // Update our various bookkeeping member-vars:
983     if (lastItem.IsFrozen()) {
984       mNumFrozenItems++;
985     }
986 
987     mTotalItemMBP += lastItem.MarginBorderPaddingSizeInMainAxis();
988     mTotalOuterHypotheticalMainSize += lastItem.OuterMainSize();
989 
990     // If the item added was not the first item in the line, we add in any gap
991     // space as needed.
992     if (NumItems() >= 2) {
993       mTotalOuterHypotheticalMainSize += mMainGapSize;
994     }
995   }
996 
997   // Computes the cross-size and baseline position of this FlexLine, based on
998   // its FlexItems.
999   void ComputeCrossSizeAndBaseline(const FlexboxAxisTracker& aAxisTracker);
1000 
1001   // Returns the cross-size of this line.
LineCrossSize() const1002   nscoord LineCrossSize() const { return mLineCrossSize; }
1003 
1004   // Setter for line cross-size -- needed for cases where the flex container
1005   // imposes a cross-size on the line. (e.g. for single-line flexbox, or for
1006   // multi-line flexbox with 'align-content: stretch')
SetLineCrossSize(nscoord aLineCrossSize)1007   void SetLineCrossSize(nscoord aLineCrossSize) {
1008     mLineCrossSize = aLineCrossSize;
1009   }
1010 
1011   /**
1012    * Returns the offset within this line where any baseline-aligned FlexItems
1013    * should place their baseline. The return value represents a distance from
1014    * the line's cross-start edge.
1015    *
1016    * If there are no baseline-aligned FlexItems, returns nscoord_MIN.
1017    */
FirstBaselineOffset() const1018   nscoord FirstBaselineOffset() const { return mFirstBaselineOffset; }
1019 
1020   /**
1021    * Returns the offset within this line where any last baseline-aligned
1022    * FlexItems should place their baseline. Opposite the case of the first
1023    * baseline offset, this represents a distance from the line's cross-end
1024    * edge (since last baseline-aligned items are flush to the cross-end edge).
1025    * If we're internally reversing the axes, this instead represents the
1026    * distance from the line's cross-start edge.
1027    *
1028    * If there are no last baseline-aligned FlexItems, returns nscoord_MIN.
1029    */
LastBaselineOffset() const1030   nscoord LastBaselineOffset() const { return mLastBaselineOffset; }
1031 
1032   /**
1033    * Returns the gap size in the main axis for this line. Used for gap
1034    * calculations.
1035    */
MainGapSize() const1036   nscoord MainGapSize() const { return mMainGapSize; }
1037 
1038   // Runs the "Resolving Flexible Lengths" algorithm from section 9.7 of the
1039   // CSS flexbox spec to distribute aFlexContainerMainSize among our flex items.
1040   // https://drafts.csswg.org/css-flexbox-1/#resolve-flexible-lengths
1041   void ResolveFlexibleLengths(nscoord aFlexContainerMainSize,
1042                               ComputedFlexLineInfo* aLineInfo);
1043 
1044   void PositionItemsInMainAxis(const StyleContentDistribution& aJustifyContent,
1045                                nscoord aContentBoxMainSize,
1046                                const FlexboxAxisTracker& aAxisTracker);
1047 
1048   void PositionItemsInCrossAxis(nscoord aLineStartPosition,
1049                                 const FlexboxAxisTracker& aAxisTracker);
1050 
1051  private:
1052   // Helpers for ResolveFlexibleLengths():
1053   void FreezeItemsEarly(bool aIsUsingFlexGrow, ComputedFlexLineInfo* aLineInfo);
1054 
1055   void FreezeOrRestoreEachFlexibleSize(const nscoord aTotalViolation,
1056                                        bool aIsFinalIteration);
1057 
1058   // Stores this line's flex items.
1059   nsTArray<FlexItem> mItems;
1060 
1061   // Number of *frozen* FlexItems in this line, based on FlexItem::IsFrozen().
1062   // Mostly used for optimization purposes, e.g. to bail out early from loops
1063   // when we can tell they have nothing left to do.
1064   uint32_t mNumFrozenItems = 0;
1065 
1066   // Sum of margin/border/padding for the FlexItems in this FlexLine.
1067   nscoord mTotalItemMBP = 0;
1068 
1069   // Sum of FlexItems' outer hypothetical main sizes and all main-axis
1070   // {row,columnm}-gaps between items.
1071   // (i.e. their flex base sizes, clamped via their min/max-size properties,
1072   // plus their main-axis margin/border/padding, plus the sum of the gaps.)
1073   //
1074   // This variable uses a 64-bit coord type to avoid integer overflow in case
1075   // several of the individual items have huge hypothetical main sizes, which
1076   // can happen with percent-width table-layout:fixed descendants. We have to
1077   // avoid integer overflow in order to shrink items properly in that scenario.
1078   AuCoord64 mTotalOuterHypotheticalMainSize = 0;
1079 
1080   nscoord mLineCrossSize = 0;
1081   nscoord mFirstBaselineOffset = nscoord_MIN;
1082   nscoord mLastBaselineOffset = nscoord_MIN;
1083 
1084   // Maintain size of each {row,column}-gap in the main axis
1085   const nscoord mMainGapSize;
1086 };
1087 
1088 // Information about a strut left behind by a FlexItem that's been collapsed
1089 // using "visibility:collapse".
1090 struct nsFlexContainerFrame::StrutInfo {
StrutInfonsFlexContainerFrame::StrutInfo1091   StrutInfo(uint32_t aItemIdx, nscoord aStrutCrossSize)
1092       : mItemIdx(aItemIdx), mStrutCrossSize(aStrutCrossSize) {}
1093 
1094   uint32_t mItemIdx;        // Index in the child list.
1095   nscoord mStrutCrossSize;  // The cross-size of this strut.
1096 };
1097 
1098 // Flex data shared by the flex container frames in a continuation chain, owned
1099 // by the first-in-flow. The data is initialized at the end of the
1100 // first-in-flow's Reflow().
1101 struct nsFlexContainerFrame::SharedFlexData {
1102   nsTArray<FlexLine> mLines;
1103 
1104   // The final content main/cross size computed by DoFlexLayout.
1105   nscoord mContentBoxMainSize = NS_UNCONSTRAINEDSIZE;
1106   nscoord mContentBoxCrossSize = NS_UNCONSTRAINEDSIZE;
1107 
1108   // The frame property under which this struct is stored. Set only on the
1109   // first-in-flow.
1110   NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, SharedFlexData)
1111 };
1112 
1113 // Forward iterate all the FlexItems in aLines.
1114 class nsFlexContainerFrame::FlexItemIterator final {
1115  public:
FlexItemIterator(const nsTArray<FlexLine> & aLines)1116   explicit FlexItemIterator(const nsTArray<FlexLine>& aLines)
1117       : mLineIter(aLines.begin()),
1118         mLineIterEnd(aLines.end()),
1119         mItemIter(mLineIter->Items().begin()),
1120         mItemIterEnd(mLineIter->Items().end()) {
1121     MOZ_ASSERT(mLineIter != mLineIterEnd,
1122                "Flex container should have at least one FlexLine!");
1123 
1124     if (mItemIter == mItemIterEnd) {
1125       // The flex container is empty, so advance to mLineIterEnd.
1126       ++mLineIter;
1127       MOZ_ASSERT(AtEnd());
1128     }
1129   }
1130 
Next()1131   void Next() {
1132     MOZ_ASSERT(!AtEnd());
1133     ++mItemIter;
1134 
1135     if (mItemIter == mItemIterEnd) {
1136       // We are pointing to the end of the flex items, so advance to the next
1137       // line.
1138       ++mLineIter;
1139 
1140       if (mLineIter != mLineIterEnd) {
1141         mItemIter = mLineIter->Items().begin();
1142         mItemIterEnd = mLineIter->Items().end();
1143         MOZ_ASSERT(mItemIter != mItemIterEnd,
1144                    "Why do we have a FlexLine with no FlexItem?");
1145       }
1146     }
1147   }
1148 
AtEnd() const1149   bool AtEnd() const {
1150     MOZ_ASSERT(
1151         (mLineIter == mLineIterEnd && mItemIter == mItemIterEnd) ||
1152             (mLineIter != mLineIterEnd && mItemIter != mItemIterEnd),
1153         "Line & item iterators should agree on whether we're at the end!");
1154     return mLineIter == mLineIterEnd;
1155   }
1156 
operator *() const1157   const FlexItem& operator*() const {
1158     MOZ_ASSERT(!AtEnd());
1159     return mItemIter.operator*();
1160   }
1161 
operator ->() const1162   const FlexItem* operator->() const {
1163     MOZ_ASSERT(!AtEnd());
1164     return mItemIter.operator->();
1165   }
1166 
1167  private:
1168   nsTArray<FlexLine>::const_iterator mLineIter;
1169   nsTArray<FlexLine>::const_iterator mLineIterEnd;
1170   nsTArray<FlexItem>::const_iterator mItemIter;
1171   nsTArray<FlexItem>::const_iterator mItemIterEnd;
1172 };
1173 
BuildStrutInfoFromCollapsedItems(const nsTArray<FlexLine> & aLines,nsTArray<StrutInfo> & aStruts)1174 static void BuildStrutInfoFromCollapsedItems(const nsTArray<FlexLine>& aLines,
1175                                              nsTArray<StrutInfo>& aStruts) {
1176   MOZ_ASSERT(aStruts.IsEmpty(),
1177              "We should only build up StrutInfo once per reflow, so "
1178              "aStruts should be empty when this is called");
1179 
1180   uint32_t itemIdxInContainer = 0;
1181   for (const FlexLine& line : aLines) {
1182     for (const FlexItem& item : line.Items()) {
1183       if (StyleVisibility::Collapse ==
1184           item.Frame()->StyleVisibility()->mVisible) {
1185         // Note the cross size of the line as the item's strut size.
1186         aStruts.AppendElement(
1187             StrutInfo(itemIdxInContainer, line.LineCrossSize()));
1188       }
1189       itemIdxInContainer++;
1190     }
1191   }
1192 }
1193 
SimplifyAlignOrJustifyContentForOneItem(const StyleContentDistribution & aAlignmentVal,bool aIsAlign)1194 static mozilla::StyleAlignFlags SimplifyAlignOrJustifyContentForOneItem(
1195     const StyleContentDistribution& aAlignmentVal, bool aIsAlign) {
1196   // Mask away any explicit fallback, to get the main (non-fallback) part of
1197   // the specified value:
1198   StyleAlignFlags specified = aAlignmentVal.primary;
1199 
1200   // XXX strip off <overflow-position> bits until we implement it (bug 1311892)
1201   specified &= ~StyleAlignFlags::FLAG_BITS;
1202 
1203   // FIRST: handle a special-case for "justify-content:stretch" (or equivalent),
1204   // which requires that we ignore any author-provided explicit fallback value.
1205   if (specified == StyleAlignFlags::NORMAL) {
1206     // In a flex container, *-content: "'normal' behaves as 'stretch'".
1207     // Do that conversion early, so it benefits from our 'stretch' special-case.
1208     // https://drafts.csswg.org/css-align-3/#distribution-flex
1209     specified = StyleAlignFlags::STRETCH;
1210   }
1211   if (!aIsAlign && specified == StyleAlignFlags::STRETCH) {
1212     // In a flex container, in "justify-content Axis: [...] 'stretch' behaves
1213     // as 'flex-start' (ignoring the specified fallback alignment, if any)."
1214     // https://drafts.csswg.org/css-align-3/#distribution-flex
1215     // So, we just directly return 'flex-start', & ignore explicit fallback..
1216     return StyleAlignFlags::FLEX_START;
1217   }
1218 
1219   // TODO: Check for an explicit fallback value (and if it's present, use it)
1220   // here once we parse it, see https://github.com/w3c/csswg-drafts/issues/1002.
1221 
1222   // If there's no explicit fallback, use the implied fallback values for
1223   // space-{between,around,evenly} (since those values only make sense with
1224   // multiple alignment subjects), and otherwise just use the specified value:
1225   if (specified == StyleAlignFlags::SPACE_BETWEEN) {
1226     return StyleAlignFlags::FLEX_START;
1227   }
1228   if (specified == StyleAlignFlags::SPACE_AROUND ||
1229       specified == StyleAlignFlags::SPACE_EVENLY) {
1230     return StyleAlignFlags::CENTER;
1231   }
1232   return specified;
1233 }
1234 
DrainSelfOverflowList()1235 bool nsFlexContainerFrame::DrainSelfOverflowList() {
1236   return DrainAndMergeSelfOverflowList();
1237 }
1238 
AppendFrames(ChildListID aListID,nsFrameList & aFrameList)1239 void nsFlexContainerFrame::AppendFrames(ChildListID aListID,
1240                                         nsFrameList& aFrameList) {
1241   NoteNewChildren(aListID, aFrameList);
1242   nsContainerFrame::AppendFrames(aListID, aFrameList);
1243 }
1244 
InsertFrames(ChildListID aListID,nsIFrame * aPrevFrame,const nsLineList::iterator * aPrevFrameLine,nsFrameList & aFrameList)1245 void nsFlexContainerFrame::InsertFrames(
1246     ChildListID aListID, nsIFrame* aPrevFrame,
1247     const nsLineList::iterator* aPrevFrameLine, nsFrameList& aFrameList) {
1248   NoteNewChildren(aListID, aFrameList);
1249   nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
1250                                  aFrameList);
1251 }
1252 
RemoveFrame(ChildListID aListID,nsIFrame * aOldFrame)1253 void nsFlexContainerFrame::RemoveFrame(ChildListID aListID,
1254                                        nsIFrame* aOldFrame) {
1255   MOZ_ASSERT(aListID == kPrincipalList, "unexpected child list");
1256 
1257 #ifdef DEBUG
1258   SetDidPushItemsBitIfNeeded(aListID, aOldFrame);
1259 #endif
1260 
1261   nsContainerFrame::RemoveFrame(aListID, aOldFrame);
1262 }
1263 
CSSAlignmentForAbsPosChild(const ReflowInput & aChildRI,LogicalAxis aLogicalAxis) const1264 StyleAlignFlags nsFlexContainerFrame::CSSAlignmentForAbsPosChild(
1265     const ReflowInput& aChildRI, LogicalAxis aLogicalAxis) const {
1266   const FlexboxAxisTracker axisTracker(this);
1267 
1268   // If we're row-oriented and the caller is asking about our inline axis (or
1269   // alternately, if we're column-oriented and the caller is asking about our
1270   // block axis), then the caller is really asking about our *main* axis.
1271   // Otherwise, the caller is asking about our cross axis.
1272   const bool isMainAxis =
1273       (axisTracker.IsRowOriented() == (aLogicalAxis == eLogicalAxisInline));
1274   const nsStylePosition* containerStylePos = StylePosition();
1275   const bool isAxisReversed = isMainAxis ? axisTracker.IsMainAxisReversed()
1276                                          : axisTracker.IsCrossAxisReversed();
1277 
1278   StyleAlignFlags alignment{0};
1279   StyleAlignFlags alignmentFlags{0};
1280   if (isMainAxis) {
1281     alignment = SimplifyAlignOrJustifyContentForOneItem(
1282         containerStylePos->mJustifyContent,
1283         /*aIsAlign = */ false);
1284   } else {
1285     const StyleAlignFlags alignContent =
1286         SimplifyAlignOrJustifyContentForOneItem(
1287             containerStylePos->mAlignContent,
1288             /*aIsAlign = */ true);
1289     if (StyleFlexWrap::Nowrap != containerStylePos->mFlexWrap &&
1290         alignContent != StyleAlignFlags::STRETCH) {
1291       // Multi-line, align-content isn't stretch --> align-content determines
1292       // this child's alignment in the cross axis.
1293       alignment = alignContent;
1294     } else {
1295       // Single-line, or multi-line but the (one) line stretches to fill
1296       // container. Respect align-self.
1297       alignment = aChildRI.mStylePosition->UsedAlignSelf(Style())._0;
1298       // Extract and strip align flag bits
1299       alignmentFlags = alignment & StyleAlignFlags::FLAG_BITS;
1300       alignment &= ~StyleAlignFlags::FLAG_BITS;
1301 
1302       if (alignment == StyleAlignFlags::NORMAL) {
1303         // "the 'normal' keyword behaves as 'start' on replaced
1304         // absolutely-positioned boxes, and behaves as 'stretch' on all other
1305         // absolutely-positioned boxes."
1306         // https://drafts.csswg.org/css-align/#align-abspos
1307         alignment = aChildRI.mFrame->IsFrameOfType(nsIFrame::eReplaced)
1308                         ? StyleAlignFlags::START
1309                         : StyleAlignFlags::STRETCH;
1310       }
1311     }
1312   }
1313 
1314   if (alignment == StyleAlignFlags::STRETCH) {
1315     // The default fallback alignment for 'stretch' is 'flex-start'.
1316     alignment = StyleAlignFlags::FLEX_START;
1317   }
1318 
1319   // Resolve flex-start, flex-end, auto, left, right, baseline, last baseline;
1320   if (alignment == StyleAlignFlags::FLEX_START) {
1321     alignment = isAxisReversed ? StyleAlignFlags::END : StyleAlignFlags::START;
1322   } else if (alignment == StyleAlignFlags::FLEX_END) {
1323     alignment = isAxisReversed ? StyleAlignFlags::START : StyleAlignFlags::END;
1324   } else if (alignment == StyleAlignFlags::LEFT ||
1325              alignment == StyleAlignFlags::RIGHT) {
1326     MOZ_ASSERT(isMainAxis, "Only justify-* can have 'left' and 'right'!");
1327     alignment = axisTracker.ResolveJustifyLeftRight(alignment);
1328   } else if (alignment == StyleAlignFlags::BASELINE) {
1329     alignment = StyleAlignFlags::START;
1330   } else if (alignment == StyleAlignFlags::LAST_BASELINE) {
1331     alignment = StyleAlignFlags::END;
1332   }
1333 
1334   MOZ_ASSERT(alignment != StyleAlignFlags::STRETCH,
1335              "We should've converted 'stretch' to the fallback alignment!");
1336   MOZ_ASSERT(alignment != StyleAlignFlags::FLEX_START &&
1337                  alignment != StyleAlignFlags::FLEX_END,
1338              "nsAbsoluteContainingBlock doesn't know how to handle "
1339              "flex-relative axis for flex containers!");
1340 
1341   return (alignment | alignmentFlags);
1342 }
1343 
GenerateFlexItemForChild(FlexLine & aLine,nsIFrame * aChildFrame,const ReflowInput & aParentReflowInput,const FlexboxAxisTracker & aAxisTracker,const nscoord aTentativeContentBoxCrossSize,bool aHasLineClampEllipsis)1344 FlexItem* nsFlexContainerFrame::GenerateFlexItemForChild(
1345     FlexLine& aLine, nsIFrame* aChildFrame,
1346     const ReflowInput& aParentReflowInput,
1347     const FlexboxAxisTracker& aAxisTracker,
1348     const nscoord aTentativeContentBoxCrossSize, bool aHasLineClampEllipsis) {
1349   const auto flexWM = aAxisTracker.GetWritingMode();
1350   const auto childWM = aChildFrame->GetWritingMode();
1351 
1352   // Note: we use GetStyleFrame() to access the sizing & flex properties here.
1353   // This lets us correctly handle table wrapper frames as flex items since
1354   // their inline-size and block-size properties are always 'auto'. In order for
1355   // 'flex-basis:auto' to actually resolve to the author's specified inline-size
1356   // or block-size, we need to dig through to the inner table.
1357   const auto* stylePos =
1358       nsLayoutUtils::GetStyleFrame(aChildFrame)->StylePosition();
1359 
1360   // Construct a StyleSizeOverrides for this flex item so that its ReflowInput
1361   // below will use and resolve its flex base size rather than its corresponding
1362   // preferred main size property (only for modern CSS flexbox).
1363   StyleSizeOverrides sizeOverrides;
1364   if (!IsLegacyBox(this)) {
1365     Maybe<StyleSize> styleFlexBaseSize;
1366 
1367     // When resolving flex base size, flex items use their 'flex-basis' property
1368     // in place of their preferred main size (e.g. 'width') for sizing purposes,
1369     // *unless* they have 'flex-basis:auto' in which case they use their
1370     // preferred main size after all.
1371     const auto& flexBasis = stylePos->mFlexBasis;
1372     const auto& styleMainSize = stylePos->Size(aAxisTracker.MainAxis(), flexWM);
1373     if (IsUsedFlexBasisContent(flexBasis, styleMainSize)) {
1374       // If we get here, we're resolving the flex base size for a flex item, and
1375       // we fall into the flexbox spec section 9.2 step 3, substep C (if we have
1376       // a definite cross size) or E (if not).
1377       if (aChildFrame->GetAspectRatio()) {
1378         // FIXME: This is a workaround. Once bug 1670151 is fixed, aspect-ratio
1379         // will be considered when resolving flex item's flex base size with the
1380         // value 'max-content'.
1381         styleFlexBaseSize.emplace(StyleSize::Auto());
1382       } else {
1383         styleFlexBaseSize.emplace(StyleSize::MaxContent());
1384       }
1385     } else if (flexBasis.IsSize() && !flexBasis.IsAuto()) {
1386       // For all other non-'auto' flex-basis values, we just swap in the
1387       // flex-basis itself for the preferred main-size property.
1388       styleFlexBaseSize.emplace(flexBasis.AsSize());
1389     } else {
1390       // else: flex-basis is 'auto', which is deferring to some explicit value
1391       // in the preferred main size.
1392       MOZ_ASSERT(flexBasis.IsAuto());
1393       styleFlexBaseSize.emplace(styleMainSize);
1394     }
1395 
1396     MOZ_ASSERT(styleFlexBaseSize, "We should've emplace styleFlexBaseSize!");
1397 
1398     // Provide the size override for the preferred main size property.
1399     if (aAxisTracker.IsInlineAxisMainAxis(childWM)) {
1400       sizeOverrides.mStyleISize = std::move(styleFlexBaseSize);
1401     } else {
1402       sizeOverrides.mStyleBSize = std::move(styleFlexBaseSize);
1403     }
1404 
1405     // 'flex-basis' should works on the inner table frame for a table flex item,
1406     // just like how 'height' works on a table element.
1407     sizeOverrides.mApplyOverridesVerbatim = true;
1408   }
1409 
1410   // Create temporary reflow input just for sizing -- to get hypothetical
1411   // main-size and the computed values of min / max main-size property.
1412   // (This reflow input will _not_ be used for reflow.)
1413   ReflowInput childRI(PresContext(), aParentReflowInput, aChildFrame,
1414                       aParentReflowInput.ComputedSize(childWM), Nothing(), {},
1415                       sizeOverrides);
1416   childRI.mFlags.mInsideLineClamp = GetLineClampValue() != 0;
1417 
1418   // FLEX GROW & SHRINK WEIGHTS
1419   // --------------------------
1420   float flexGrow, flexShrink;
1421   if (IsLegacyBox(this)) {
1422     if (GetLineClampValue() != 0) {
1423       // Items affected by -webkit-line-clamp are always inflexible.
1424       flexGrow = flexShrink = 0;
1425     } else {
1426       flexGrow = flexShrink = aChildFrame->StyleXUL()->mBoxFlex;
1427     }
1428   } else {
1429     flexGrow = stylePos->mFlexGrow;
1430     flexShrink = stylePos->mFlexShrink;
1431   }
1432 
1433   // MAIN SIZES (flex base size, min/max size)
1434   // -----------------------------------------
1435   nscoord flexBaseSize = GET_MAIN_COMPONENT_LOGICAL(
1436       aAxisTracker, childWM, childRI.ComputedISize(), childRI.ComputedBSize());
1437   nscoord mainMinSize = GET_MAIN_COMPONENT_LOGICAL(aAxisTracker, childWM,
1438                                                    childRI.ComputedMinISize(),
1439                                                    childRI.ComputedMinBSize());
1440   nscoord mainMaxSize = GET_MAIN_COMPONENT_LOGICAL(aAxisTracker, childWM,
1441                                                    childRI.ComputedMaxISize(),
1442                                                    childRI.ComputedMaxBSize());
1443   // This is enforced by the ReflowInput where these values come from:
1444   MOZ_ASSERT(mainMinSize <= mainMaxSize, "min size is larger than max size");
1445 
1446   // CROSS SIZES (tentative cross size, min/max cross size)
1447   // ------------------------------------------------------
1448   // Grab the cross size from the reflow input. This might be the right value,
1449   // or we might resolve it to something else in SizeItemInCrossAxis(); hence,
1450   // it's tentative. See comment under "Cross Size Determination" for more.
1451   nscoord tentativeCrossSize = GET_CROSS_COMPONENT_LOGICAL(
1452       aAxisTracker, childWM, childRI.ComputedISize(), childRI.ComputedBSize());
1453   nscoord crossMinSize = GET_CROSS_COMPONENT_LOGICAL(
1454       aAxisTracker, childWM, childRI.ComputedMinISize(),
1455       childRI.ComputedMinBSize());
1456   nscoord crossMaxSize = GET_CROSS_COMPONENT_LOGICAL(
1457       aAxisTracker, childWM, childRI.ComputedMaxISize(),
1458       childRI.ComputedMaxBSize());
1459 
1460   // SPECIAL-CASE FOR WIDGET-IMPOSED SIZES
1461   // Check if we're a themed widget, in which case we might have a minimum
1462   // main & cross size imposed by our widget (which we can't go below), or
1463   // (more severe) our widget might have only a single valid size.
1464   bool isFixedSizeWidget = false;
1465   const nsStyleDisplay* disp = aChildFrame->StyleDisplay();
1466   if (aChildFrame->IsThemed(disp)) {
1467     LayoutDeviceIntSize widgetMinSize;
1468     bool canOverride = true;
1469     PresContext()->Theme()->GetMinimumWidgetSize(PresContext(), aChildFrame,
1470                                                  disp->EffectiveAppearance(),
1471                                                  &widgetMinSize, &canOverride);
1472 
1473     nscoord widgetMainMinSize = PresContext()->DevPixelsToAppUnits(
1474         aAxisTracker.MainComponent(widgetMinSize));
1475     nscoord widgetCrossMinSize = PresContext()->DevPixelsToAppUnits(
1476         aAxisTracker.CrossComponent(widgetMinSize));
1477 
1478     // GetMinimumWidgetSize() returns border-box. We need content-box, so
1479     // subtract borderPadding.
1480     const LogicalMargin bpInFlexWM =
1481         childRI.ComputedLogicalBorderPadding(flexWM);
1482     widgetMainMinSize -= aAxisTracker.MarginSizeInMainAxis(bpInFlexWM);
1483     widgetCrossMinSize -= aAxisTracker.MarginSizeInCrossAxis(bpInFlexWM);
1484     // ... (but don't let that push these min sizes below 0).
1485     widgetMainMinSize = std::max(0, widgetMainMinSize);
1486     widgetCrossMinSize = std::max(0, widgetCrossMinSize);
1487 
1488     if (!canOverride) {
1489       // Fixed-size widget: freeze our main-size at the widget's mandated size.
1490       // (Set min and max main-sizes to that size, too, to keep us from
1491       // clamping to any other size later on.)
1492       flexBaseSize = mainMinSize = mainMaxSize = widgetMainMinSize;
1493       tentativeCrossSize = crossMinSize = crossMaxSize = widgetCrossMinSize;
1494       isFixedSizeWidget = true;
1495     } else {
1496       // Variable-size widget: ensure our min/max sizes are at least as large
1497       // as the widget's mandated minimum size, so we don't flex below that.
1498       mainMinSize = std::max(mainMinSize, widgetMainMinSize);
1499       mainMaxSize = std::max(mainMaxSize, widgetMainMinSize);
1500 
1501       if (tentativeCrossSize != NS_UNCONSTRAINEDSIZE) {
1502         tentativeCrossSize = std::max(tentativeCrossSize, widgetCrossMinSize);
1503       }
1504       crossMinSize = std::max(crossMinSize, widgetCrossMinSize);
1505       crossMaxSize = std::max(crossMaxSize, widgetCrossMinSize);
1506     }
1507   }
1508 
1509   // Construct the flex item!
1510   FlexItem* item = aLine.Items().EmplaceBack(
1511       childRI, flexGrow, flexShrink, flexBaseSize, mainMinSize, mainMaxSize,
1512       tentativeCrossSize, crossMinSize, crossMaxSize, aAxisTracker);
1513 
1514   // We may be about to do computations based on our item's cross-size
1515   // (e.g. using it as a constraint when measuring our content in the
1516   // main axis, or using it with the preferred aspect ratio to obtain a main
1517   // size). BEFORE WE DO THAT, we need let the item "pre-stretch" its cross size
1518   // (if it's got 'align-self:stretch'), for a certain case where the spec says
1519   // the stretched cross size is considered "definite". That case is if we
1520   // have a single-line (nowrap) flex container which itself has a definite
1521   // cross-size.  Otherwise, we'll wait to do stretching, since (in other
1522   // cases) we don't know how much the item should stretch yet.
1523   const bool isSingleLine =
1524       StyleFlexWrap::Nowrap == aParentReflowInput.mStylePosition->mFlexWrap;
1525   if (isSingleLine) {
1526     // Is container's cross size "definite"?
1527     // - If it's column-oriented, then "yes", because its cross size is its
1528     // inline-size which is always definite from its descendants' perspective.
1529     // - Otherwise (if it's row-oriented), then we check the actual size
1530     // and call it definite if it's not NS_UNCONSTRAINEDSIZE.
1531     if (aAxisTracker.IsColumnOriented() ||
1532         aTentativeContentBoxCrossSize != NS_UNCONSTRAINEDSIZE) {
1533       // Container's cross size is "definite", so we can resolve the item's
1534       // stretched cross size using that.
1535       item->ResolveStretchedCrossSize(aTentativeContentBoxCrossSize);
1536     }
1537   }
1538 
1539   // Before thinking about freezing the item at its base size, we need to give
1540   // it a chance to recalculate the base size from its cross size and aspect
1541   // ratio (since its cross size might've *just* now become definite due to
1542   // 'stretch' above)
1543   item->ResolveFlexBaseSizeFromAspectRatio(childRI);
1544 
1545   // If we're inflexible, we can just freeze to our hypothetical main-size
1546   // up-front. Similarly, if we're a fixed-size widget, we only have one
1547   // valid size, so we freeze to keep ourselves from flexing.
1548   if (isFixedSizeWidget || (flexGrow == 0.0f && flexShrink == 0.0f)) {
1549     item->Freeze();
1550     if (flexBaseSize < mainMinSize) {
1551       item->SetWasMinClamped();
1552     } else if (flexBaseSize > mainMaxSize) {
1553       item->SetWasMaxClamped();
1554     }
1555   }
1556 
1557   // Resolve "flex-basis:auto" and/or "min-[width|height]:auto" (which might
1558   // require us to reflow the item to measure content height)
1559   ResolveAutoFlexBasisAndMinSize(*item, childRI, aAxisTracker,
1560                                  aHasLineClampEllipsis);
1561   return item;
1562 }
1563 
1564 // Static helper-functions for ResolveAutoFlexBasisAndMinSize():
1565 // -------------------------------------------------------------
1566 // Partially resolves "min-[width|height]:auto" and returns the resulting value.
1567 // By "partially", I mean we don't consider the min-content size (but we do
1568 // consider the main-size and main max-size properties, and the preferred aspect
1569 // ratio). The caller is responsible for computing & considering the min-content
1570 // size in combination with the partially-resolved value that this function
1571 // returns.
1572 //
1573 // Basically, this function gets the specified size suggestion; if not, the
1574 // transferred size suggestion; if both sizes do not exist, return nscoord_MAX.
1575 //
1576 // Spec reference: https://drafts.csswg.org/css-flexbox-1/#min-size-auto
PartiallyResolveAutoMinSize(const FlexItem & aFlexItem,const ReflowInput & aItemReflowInput,const FlexboxAxisTracker & aAxisTracker)1577 static nscoord PartiallyResolveAutoMinSize(
1578     const FlexItem& aFlexItem, const ReflowInput& aItemReflowInput,
1579     const FlexboxAxisTracker& aAxisTracker) {
1580   MOZ_ASSERT(aFlexItem.NeedsMinSizeAutoResolution(),
1581              "only call for FlexItems that need min-size auto resolution");
1582 
1583   const auto itemWM = aFlexItem.GetWritingMode();
1584   const auto cbWM = aAxisTracker.GetWritingMode();
1585   const auto& mainStyleSize =
1586       aItemReflowInput.mStylePosition->Size(aAxisTracker.MainAxis(), cbWM);
1587   const auto& maxMainStyleSize =
1588       aItemReflowInput.mStylePosition->MaxSize(aAxisTracker.MainAxis(), cbWM);
1589   const auto boxSizingAdjust =
1590       aItemReflowInput.mStylePosition->mBoxSizing == StyleBoxSizing::Border
1591           ? aFlexItem.BorderPadding().Size(cbWM)
1592           : LogicalSize(cbWM);
1593 
1594   // If this flex item is a compressible replaced element list in CSS Sizing 3
1595   // §5.2.2, CSS Sizing 3 §5.2.1c requires us to resolve the percentage part of
1596   // the preferred main size property against zero, yielding a definite
1597   // specified size suggestion. Here we can use a zero percentage basis to
1598   // fulfill this requirement.
1599   const auto percentBasis =
1600       aFlexItem.Frame()->IsPercentageResolvedAgainstZero(mainStyleSize,
1601                                                          maxMainStyleSize)
1602           ? LogicalSize(cbWM, 0, 0)
1603           : aItemReflowInput.mContainingBlockSize.ConvertTo(cbWM, itemWM);
1604 
1605   // Compute the specified size suggestion, which is the main-size property if
1606   // it's definite.
1607   nscoord specifiedSizeSuggestion = nscoord_MAX;
1608 
1609   if (aAxisTracker.IsRowOriented()) {
1610     if (mainStyleSize.IsLengthPercentage()) {
1611       // NOTE: We ignore extremum inline-size. This is OK because the caller is
1612       // responsible for computing the min-content inline-size and min()'ing it
1613       // with the value we return.
1614       specifiedSizeSuggestion = aFlexItem.Frame()->ComputeISizeValue(
1615           cbWM, percentBasis, boxSizingAdjust,
1616           mainStyleSize.AsLengthPercentage());
1617     }
1618   } else {
1619     if (!nsLayoutUtils::IsAutoBSize(mainStyleSize, percentBasis.BSize(cbWM))) {
1620       // NOTE: We ignore auto and extremum block-size. This is OK because the
1621       // caller is responsible for computing the min-content block-size and
1622       // min()'ing it with the value we return.
1623       specifiedSizeSuggestion = nsLayoutUtils::ComputeBSizeValue(
1624           percentBasis.BSize(cbWM), boxSizingAdjust.BSize(cbWM),
1625           mainStyleSize.AsLengthPercentage());
1626     }
1627   }
1628 
1629   if (specifiedSizeSuggestion != nscoord_MAX) {
1630     // We have the specified size suggestion. Return it now since we don't need
1631     // to consider transferred size suggestion.
1632     FLEX_LOGV(" Specified size suggestion: %d", specifiedSizeSuggestion);
1633     return specifiedSizeSuggestion;
1634   }
1635 
1636   // Compute the transferred size suggestion, which is the cross size converted
1637   // through the aspect ratio (if the item is replaced, and it has an aspect
1638   // ratio and a definite cross size).
1639   if (const auto& aspectRatio = aFlexItem.GetAspectRatio();
1640       aFlexItem.Frame()->IsFrameOfType(nsIFrame::eReplaced) && aspectRatio &&
1641       aFlexItem.IsCrossSizeDefinite(aItemReflowInput)) {
1642     // We have a usable aspect ratio. (not going to divide by 0)
1643     nscoord transferredSizeSuggestion = aspectRatio.ComputeRatioDependentSize(
1644         aFlexItem.MainAxis(), cbWM, aFlexItem.CrossSize(), boxSizingAdjust);
1645 
1646     // Clamp the transferred size suggestion by any definite min and max
1647     // cross size converted through the aspect ratio.
1648     transferredSizeSuggestion = aFlexItem.ClampMainSizeViaCrossAxisConstraints(
1649         transferredSizeSuggestion, aItemReflowInput);
1650 
1651     FLEX_LOGV(" Transferred size suggestion: %d", transferredSizeSuggestion);
1652     return transferredSizeSuggestion;
1653   }
1654 
1655   return nscoord_MAX;
1656 }
1657 
1658 // Note: If & when we handle "min-height: min-content" for flex items,
1659 // we may want to resolve that in this function, too.
ResolveAutoFlexBasisAndMinSize(FlexItem & aFlexItem,const ReflowInput & aItemReflowInput,const FlexboxAxisTracker & aAxisTracker,bool aHasLineClampEllipsis)1660 void nsFlexContainerFrame::ResolveAutoFlexBasisAndMinSize(
1661     FlexItem& aFlexItem, const ReflowInput& aItemReflowInput,
1662     const FlexboxAxisTracker& aAxisTracker, bool aHasLineClampEllipsis) {
1663   // (Note: We can guarantee that the flex-basis will have already been
1664   // resolved if the main axis is the same as the item's inline
1665   // axis. Inline-axis values should always be resolvable without reflow.)
1666   const bool isMainSizeAuto =
1667       (!aFlexItem.IsInlineAxisMainAxis() &&
1668        NS_UNCONSTRAINEDSIZE == aFlexItem.FlexBaseSize());
1669 
1670   const bool isMainMinSizeAuto = aFlexItem.NeedsMinSizeAutoResolution();
1671 
1672   if (!isMainSizeAuto && !isMainMinSizeAuto) {
1673     // Nothing to do; this function is only needed for flex items
1674     // with a used flex-basis of "auto" or a min-main-size of "auto".
1675     return;
1676   }
1677 
1678   FLEX_LOGV("Resolving auto main size or auto min main size for flex item %p",
1679             aFlexItem.Frame());
1680 
1681   nscoord resolvedMinSize;  // (only set/used if isMainMinSizeAuto==true)
1682   bool minSizeNeedsToMeasureContent = false;  // assume the best
1683   if (isMainMinSizeAuto) {
1684     // Resolve the min-size, except for considering the min-content size.
1685     // (We'll consider that later, if we need to.)
1686     resolvedMinSize =
1687         PartiallyResolveAutoMinSize(aFlexItem, aItemReflowInput, aAxisTracker);
1688     if (resolvedMinSize > 0) {
1689       // If resolvedMinSize were already at 0, we could skip calculating content
1690       // size suggestion because it can't go any lower.
1691       minSizeNeedsToMeasureContent = true;
1692     }
1693   }
1694 
1695   const bool flexBasisNeedsToMeasureContent = isMainSizeAuto;
1696 
1697   // Measure content, if needed (w/ intrinsic-width method or a reflow)
1698   if (minSizeNeedsToMeasureContent || flexBasisNeedsToMeasureContent) {
1699     // Compute the content size suggestion, which is the min-content size in the
1700     // main axis.
1701     nscoord contentSizeSuggestion = nscoord_MAX;
1702 
1703     if (aFlexItem.IsInlineAxisMainAxis()) {
1704       if (minSizeNeedsToMeasureContent) {
1705         // Compute the flex item's content size suggestion, which is the
1706         // 'min-content' size on the main axis.
1707         // https://drafts.csswg.org/css-flexbox-1/#content-size-suggestion
1708         const auto cbWM = aAxisTracker.GetWritingMode();
1709         const auto itemWM = aFlexItem.GetWritingMode();
1710         const nscoord availISize = 0;  // for min-content size
1711         StyleSizeOverrides sizeOverrides;
1712         sizeOverrides.mStyleISize.emplace(StyleSize::Auto());
1713         const auto sizeInItemWM = aFlexItem.Frame()->ComputeSize(
1714             aItemReflowInput.mRenderingContext, itemWM,
1715             aItemReflowInput.mContainingBlockSize, availISize,
1716             aItemReflowInput.ComputedLogicalMargin(itemWM).Size(itemWM),
1717             aItemReflowInput.ComputedLogicalBorderPadding(itemWM).Size(itemWM),
1718             sizeOverrides, {ComputeSizeFlag::ShrinkWrap});
1719 
1720         contentSizeSuggestion = aAxisTracker.MainComponent(
1721             sizeInItemWM.mLogicalSize.ConvertTo(cbWM, itemWM));
1722       }
1723       NS_ASSERTION(!flexBasisNeedsToMeasureContent,
1724                    "flex-basis:auto should have been resolved in the "
1725                    "reflow input, for horizontal flexbox. It shouldn't need "
1726                    "special handling here");
1727     } else {
1728       // If this item is flexible (in its block axis)...
1729       // OR if we're measuring its 'auto' min-BSize, with its main-size (in its
1730       // block axis) being something non-"auto"...
1731       // THEN: we assume that the computed BSize that we're reflowing with now
1732       // could be different from the one we'll use for this flex item's
1733       // "actual" reflow later on.  In that case, we need to be sure the flex
1734       // item treats this as a block-axis resize (regardless of whether there
1735       // are actually any ancestors being resized in that axis).
1736       // (Note: We don't have to do this for the inline axis, because
1737       // InitResizeFlags will always turn on mIsIResize on when it sees that
1738       // the computed ISize is different from current ISize, and that's all we
1739       // need.)
1740       bool forceBResizeForMeasuringReflow =
1741           !aFlexItem.IsFrozen() ||          // Is the item flexible?
1742           !flexBasisNeedsToMeasureContent;  // Are we *only* measuring it for
1743                                             // 'min-block-size:auto'?
1744 
1745       const ReflowInput& flexContainerRI = *aItemReflowInput.mParentReflowInput;
1746       nscoord contentBSize =
1747           MeasureFlexItemContentBSize(aFlexItem, forceBResizeForMeasuringReflow,
1748                                       aHasLineClampEllipsis, flexContainerRI);
1749       if (minSizeNeedsToMeasureContent) {
1750         contentSizeSuggestion = contentBSize;
1751       }
1752       if (flexBasisNeedsToMeasureContent) {
1753         aFlexItem.SetFlexBaseSizeAndMainSize(contentBSize);
1754       }
1755     }
1756 
1757     if (minSizeNeedsToMeasureContent) {
1758       // Clamp the content size suggestion by any definite min and max cross
1759       // size converted through the aspect ratio.
1760       if (aFlexItem.HasAspectRatio()) {
1761         contentSizeSuggestion = aFlexItem.ClampMainSizeViaCrossAxisConstraints(
1762             contentSizeSuggestion, aItemReflowInput);
1763       }
1764 
1765       FLEX_LOGV(" Content size suggestion: %d", contentSizeSuggestion);
1766       resolvedMinSize = std::min(resolvedMinSize, contentSizeSuggestion);
1767 
1768       // Clamp the resolved min main size by the max main size if it's definite.
1769       if (aFlexItem.MainMaxSize() != NS_UNCONSTRAINEDSIZE) {
1770         resolvedMinSize = std::min(resolvedMinSize, aFlexItem.MainMaxSize());
1771       } else if (MOZ_UNLIKELY(resolvedMinSize > nscoord_MAX)) {
1772         NS_WARNING("Bogus resolved auto min main size!");
1773         // Our resolved min-size is bogus, probably due to some huge sizes in
1774         // the content. Clamp it to the valid nscoord range, so that we can at
1775         // least depend on it being <= the max-size (which is also the
1776         // nscoord_MAX sentinel value if we reach this point).
1777         resolvedMinSize = nscoord_MAX;
1778       }
1779       FLEX_LOGV(" Resolved auto min main size: %d", resolvedMinSize);
1780     }
1781   }
1782 
1783   if (isMainMinSizeAuto) {
1784     aFlexItem.UpdateMainMinSize(resolvedMinSize);
1785   }
1786 }
1787 
1788 /**
1789  * A cached result for a flex item's block-axis measuring reflow. This cache
1790  * prevents us from doing exponential reflows in cases of deeply nested flex
1791  * and scroll frames.
1792  *
1793  * We store the cached value in the flex item's frame property table, for
1794  * simplicity.
1795  *
1796  * Right now, we cache the following as a "key", from the item's ReflowInput:
1797  *   - its ComputedSize
1798  *   - its min/max block size (in case its ComputedBSize is unconstrained)
1799  *   - its AvailableBSize
1800  * ...and we cache the following as the "value", from the item's ReflowOutput:
1801  *   - its final content-box BSize
1802  *
1803  * The assumption here is that a given flex item measurement from our "value"
1804  * won't change unless one of the pieces of the "key" change, or the flex
1805  * item's intrinsic size is marked as dirty (due to a style or DOM change).
1806  * (The latter will cause the cached value to be discarded, in
1807  * nsIFrame::MarkIntrinsicISizesDirty.)
1808  *
1809  * Note that the components of "Key" (mComputed{MinB,MaxB,}Size and
1810  * mAvailableBSize) are sufficient to catch any changes to the flex container's
1811  * size that the item may care about for its measuring reflow. Specifically:
1812  *  - If the item cares about the container's size (e.g. if it has a percent
1813  *    height and the container's height changes, in a horizontal-WM container)
1814  *    then that'll be detectable via the item's ReflowInput's "ComputedSize()"
1815  *    differing from the value in our Key.  And the same applies for the
1816  *    inline axis.
1817  *  - If the item is fragmentable (pending bug 939897) and its measured BSize
1818  *    depends on where it gets fragmented, then that sort of change can be
1819  *    detected due to the item's ReflowInput's "AvailableBSize()" differing
1820  *    from the value in our Key.
1821  *
1822  * One particular case to consider (& need to be sure not to break when
1823  * changing this class): the flex item's computed BSize may change between
1824  * measuring reflows due to how the mIsFlexContainerMeasuringBSize flag affects
1825  * size computation (see bug 1336708). This is one reason we need to use the
1826  * computed BSize as part of the key.
1827  */
1828 class nsFlexContainerFrame::CachedBAxisMeasurement {
1829   struct Key {
1830     const LogicalSize mComputedSize;
1831     const nscoord mComputedMinBSize;
1832     const nscoord mComputedMaxBSize;
1833     const nscoord mAvailableBSize;
1834 
KeynsFlexContainerFrame::CachedBAxisMeasurement::Key1835     explicit Key(const ReflowInput& aRI)
1836         : mComputedSize(aRI.ComputedSize()),
1837           mComputedMinBSize(aRI.ComputedMinBSize()),
1838           mComputedMaxBSize(aRI.ComputedMaxBSize()),
1839           mAvailableBSize(aRI.AvailableBSize()) {}
1840 
operator ==nsFlexContainerFrame::CachedBAxisMeasurement::Key1841     bool operator==(const Key& aOther) const {
1842       return mComputedSize == aOther.mComputedSize &&
1843              mComputedMinBSize == aOther.mComputedMinBSize &&
1844              mComputedMaxBSize == aOther.mComputedMaxBSize &&
1845              mAvailableBSize == aOther.mAvailableBSize;
1846     }
1847   };
1848 
1849   const Key mKey;
1850 
1851   // This could/should be const, but it's non-const for now just because it's
1852   // assigned via a series of steps in the constructor body:
1853   nscoord mBSize;
1854 
1855  public:
CachedBAxisMeasurement(const ReflowInput & aReflowInput,const ReflowOutput & aReflowOutput)1856   CachedBAxisMeasurement(const ReflowInput& aReflowInput,
1857                          const ReflowOutput& aReflowOutput)
1858       : mKey(aReflowInput) {
1859     // To get content-box bsize, we have to subtract off border & padding
1860     // (and floor at 0 in case the border/padding are too large):
1861     WritingMode itemWM = aReflowInput.GetWritingMode();
1862     nscoord borderBoxBSize = aReflowOutput.BSize(itemWM);
1863     mBSize =
1864         borderBoxBSize -
1865         aReflowInput.ComputedLogicalBorderPadding(itemWM).BStartEnd(itemWM);
1866     mBSize = std::max(0, mBSize);
1867   }
1868 
1869   /**
1870    * Returns true if this cached flex item measurement is valid for (i.e. can
1871    * be expected to match the output of) a measuring reflow whose input
1872    * parameters are given via aReflowInput.
1873    */
IsValidFor(const ReflowInput & aReflowInput) const1874   bool IsValidFor(const ReflowInput& aReflowInput) const {
1875     return mKey == Key(aReflowInput);
1876   }
1877 
BSize() const1878   nscoord BSize() const { return mBSize; }
1879 };
1880 
1881 /**
1882  * A cached copy of various metrics from a flex item's most recent final reflow.
1883  * It can be used to determine whether we can optimize away the flex item's
1884  * final reflow, when we perform an incremental reflow of its flex container.
1885  */
1886 class CachedFinalReflowMetrics final {
1887  public:
CachedFinalReflowMetrics(const ReflowInput & aReflowInput,const ReflowOutput & aReflowOutput)1888   CachedFinalReflowMetrics(const ReflowInput& aReflowInput,
1889                            const ReflowOutput& aReflowOutput)
1890       : CachedFinalReflowMetrics(aReflowInput.GetWritingMode(), aReflowInput,
1891                                  aReflowOutput) {}
1892 
CachedFinalReflowMetrics(const FlexItem & aItem,const LogicalSize & aSize)1893   CachedFinalReflowMetrics(const FlexItem& aItem, const LogicalSize& aSize)
1894       : mBorderPadding(aItem.BorderPadding().ConvertTo(
1895             aItem.GetWritingMode(), aItem.ContainingBlockWM())),
1896         mSize(aSize),
1897         mTreatBSizeAsIndefinite(aItem.TreatBSizeAsIndefinite()) {}
1898 
Size() const1899   const LogicalSize& Size() const { return mSize; }
BorderPadding() const1900   const LogicalMargin& BorderPadding() const { return mBorderPadding; }
TreatBSizeAsIndefinite() const1901   bool TreatBSizeAsIndefinite() const { return mTreatBSizeAsIndefinite; }
1902 
1903  private:
1904   // A convenience constructor with a WritingMode argument.
CachedFinalReflowMetrics(WritingMode aWM,const ReflowInput & aReflowInput,const ReflowOutput & aReflowOutput)1905   CachedFinalReflowMetrics(WritingMode aWM, const ReflowInput& aReflowInput,
1906                            const ReflowOutput& aReflowOutput)
1907       : mBorderPadding(aReflowInput.ComputedLogicalBorderPadding(aWM)),
1908         mSize(aReflowOutput.Size(aWM) - mBorderPadding.Size(aWM)),
1909         mTreatBSizeAsIndefinite(aReflowInput.mFlags.mTreatBSizeAsIndefinite) {}
1910 
1911   // The flex item's border and padding, in its own writing-mode, that it used
1912   // used during its most recent "final reflow".
1913   LogicalMargin mBorderPadding;
1914 
1915   // The flex item's content-box size, in its own writing-mode, that it used
1916   // during its most recent "final reflow".
1917   LogicalSize mSize;
1918 
1919   // True if the flex item's BSize was considered "indefinite" in its most
1920   // recent "final reflow". (For a flex item "final reflow", this is fully
1921   // determined by the mTreatBSizeAsIndefinite flag in ReflowInput. See the
1922   // flag's documentation for more information.)
1923   bool mTreatBSizeAsIndefinite;
1924 };
1925 
1926 /**
1927  * When we instantiate/update a CachedFlexItemData, this enum must be used to
1928  * indicate the sort of reflow whose results we're capturing. This impacts
1929  * what we cache & how we use the cached information.
1930  */
1931 enum class FlexItemReflowType {
1932   // A reflow to measure the block-axis size of a flex item (as an input to the
1933   // flex layout algorithm).
1934   Measuring,
1935 
1936   // A reflow with the flex item's "final" size at the end of the flex layout
1937   // algorithm.
1938   Final,
1939 };
1940 
1941 /**
1942  * This class stores information about the conditions and results for the most
1943  * recent ReflowChild call that we made on a given flex item.  This information
1944  * helps us reason about whether we can assume that a subsequent ReflowChild()
1945  * invocation is unnecessary & skippable.
1946  */
1947 class nsFlexContainerFrame::CachedFlexItemData {
1948  public:
CachedFlexItemData(const ReflowInput & aReflowInput,const ReflowOutput & aReflowOutput,FlexItemReflowType aType)1949   CachedFlexItemData(const ReflowInput& aReflowInput,
1950                      const ReflowOutput& aReflowOutput,
1951                      FlexItemReflowType aType) {
1952     Update(aReflowInput, aReflowOutput, aType);
1953   }
1954 
1955   // This method is intended to be called after we perform either a "measuring
1956   // reflow" or a "final reflow" for a given flex item.
Update(const ReflowInput & aReflowInput,const ReflowOutput & aReflowOutput,FlexItemReflowType aType)1957   void Update(const ReflowInput& aReflowInput,
1958               const ReflowOutput& aReflowOutput, FlexItemReflowType aType) {
1959     if (aType == FlexItemReflowType::Measuring) {
1960       mBAxisMeasurement.reset();
1961       mBAxisMeasurement.emplace(aReflowInput, aReflowOutput);
1962       // Clear any cached "last final reflow metrics", too, because now the most
1963       // recent reflow was *not* a "final reflow".
1964       mFinalReflowMetrics.reset();
1965       return;
1966     }
1967 
1968     MOZ_ASSERT(aType == FlexItemReflowType::Final);
1969     mFinalReflowMetrics.reset();
1970     mFinalReflowMetrics.emplace(aReflowInput, aReflowOutput);
1971   }
1972 
1973   // This method is intended to be called for situations where we decide to
1974   // skip a final reflow because we've just done a measuring reflow which left
1975   // us (and our descendants) with the correct sizes. In this scenario, we
1976   // still want to cache the size as if we did a final reflow (because we've
1977   // determined that the recent measuring reflow was sufficient).  That way,
1978   // our flex container can still skip a final reflow for this item in the
1979   // future as long as conditions are right.
Update(const FlexItem & aItem,const LogicalSize & aSize)1980   void Update(const FlexItem& aItem, const LogicalSize& aSize) {
1981     MOZ_ASSERT(!mFinalReflowMetrics,
1982                "This version of the method is only intended to be called when "
1983                "the most recent reflow was a 'measuring reflow'; and that "
1984                "should have cleared out mFinalReflowMetrics");
1985 
1986     mFinalReflowMetrics.reset();  // Just in case this assert^ fails.
1987     mFinalReflowMetrics.emplace(aItem, aSize);
1988   }
1989 
1990   // If the flex container needs a measuring reflow for the flex item, then the
1991   // resulting block-axis measurements can be cached here.  If no measurement
1992   // has been needed so far, then this member will be Nothing().
1993   Maybe<CachedBAxisMeasurement> mBAxisMeasurement;
1994 
1995   // The metrics that the corresponding flex item used in its most recent
1996   // "final reflow". (Note: the assumption here is that this reflow was this
1997   // item's most recent reflow of any type.  If the item ends up undergoing a
1998   // subsequent measuring reflow, then this value needs to be cleared, because
1999   // at that point it's no longer an accurate way of reasoning about the
2000   // current state of the frame tree.)
2001   Maybe<CachedFinalReflowMetrics> mFinalReflowMetrics;
2002 
2003   // Instances of this class are stored under this frame property, on
2004   // frames that are flex items:
2005   NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, CachedFlexItemData)
2006 };
2007 
MarkCachedFlexMeasurementsDirty(nsIFrame * aItemFrame)2008 void nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(
2009     nsIFrame* aItemFrame) {
2010   MOZ_ASSERT(aItemFrame->IsFlexItem());
2011   if (auto* cache = aItemFrame->GetProperty(CachedFlexItemData::Prop())) {
2012     cache->mBAxisMeasurement.reset();
2013     cache->mFinalReflowMetrics.reset();
2014   }
2015 }
2016 
MeasureBSizeForFlexItem(FlexItem & aItem,ReflowInput & aChildReflowInput)2017 const CachedBAxisMeasurement& nsFlexContainerFrame::MeasureBSizeForFlexItem(
2018     FlexItem& aItem, ReflowInput& aChildReflowInput) {
2019   auto* cachedData = aItem.Frame()->GetProperty(CachedFlexItemData::Prop());
2020 
2021   if (cachedData && cachedData->mBAxisMeasurement) {
2022     if (!aItem.Frame()->IsSubtreeDirty() &&
2023         cachedData->mBAxisMeasurement->IsValidFor(aChildReflowInput)) {
2024       FLEX_LOG("[perf] MeasureBSizeForFlexItem accepted cached value");
2025       return *(cachedData->mBAxisMeasurement);
2026     }
2027     FLEX_LOG("[perf] MeasureBSizeForFlexItem rejected cached value");
2028   } else {
2029     FLEX_LOG("[perf] MeasureBSizeForFlexItem didn't have a cached value");
2030   }
2031 
2032   // CachedFlexItemData is stored in item's writing mode, so we pass
2033   // aChildReflowInput into ReflowOutput's constructor.
2034   ReflowOutput childReflowOutput(aChildReflowInput);
2035   nsReflowStatus childReflowStatus;
2036 
2037   const ReflowChildFlags flags = ReflowChildFlags::NoMoveFrame;
2038   const WritingMode outerWM = GetWritingMode();
2039   const LogicalPoint dummyPosition(outerWM);
2040   const nsSize dummyContainerSize;
2041 
2042   // We use NoMoveFrame, so the position and container size used here are
2043   // unimportant.
2044   ReflowChild(aItem.Frame(), PresContext(), childReflowOutput,
2045               aChildReflowInput, outerWM, dummyPosition, dummyContainerSize,
2046               flags, childReflowStatus);
2047   aItem.SetHadMeasuringReflow();
2048 
2049   // We always use unconstrained available block-size to measure flex items,
2050   // which means they should always complete.
2051   MOZ_ASSERT(childReflowStatus.IsComplete(),
2052              "We gave flex item unconstrained available block-size, so it "
2053              "should be complete");
2054 
2055   // Tell the child we're done with its initial reflow.
2056   // (Necessary for e.g. GetBaseline() to work below w/out asserting)
2057   FinishReflowChild(aItem.Frame(), PresContext(), childReflowOutput,
2058                     &aChildReflowInput, outerWM, dummyPosition,
2059                     dummyContainerSize, flags);
2060 
2061   aItem.SetAscent(childReflowOutput.BlockStartAscent());
2062 
2063   // Update (or add) our cached measurement, so that we can hopefully skip this
2064   // measuring reflow the next time around:
2065   if (cachedData) {
2066     cachedData->Update(aChildReflowInput, childReflowOutput,
2067                        FlexItemReflowType::Measuring);
2068   } else {
2069     cachedData = new CachedFlexItemData(aChildReflowInput, childReflowOutput,
2070                                         FlexItemReflowType::Measuring);
2071     aItem.Frame()->SetProperty(CachedFlexItemData::Prop(), cachedData);
2072   }
2073   return *(cachedData->mBAxisMeasurement);
2074 }
2075 
2076 /* virtual */
MarkIntrinsicISizesDirty()2077 void nsFlexContainerFrame::MarkIntrinsicISizesDirty() {
2078   mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
2079   mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
2080 
2081   nsContainerFrame::MarkIntrinsicISizesDirty();
2082 }
2083 
MeasureFlexItemContentBSize(FlexItem & aFlexItem,bool aForceBResizeForMeasuringReflow,bool aHasLineClampEllipsis,const ReflowInput & aParentReflowInput)2084 nscoord nsFlexContainerFrame::MeasureFlexItemContentBSize(
2085     FlexItem& aFlexItem, bool aForceBResizeForMeasuringReflow,
2086     bool aHasLineClampEllipsis, const ReflowInput& aParentReflowInput) {
2087   FLEX_LOG("Measuring flex item's content block-size");
2088 
2089   // Set up a reflow input for measuring the flex item's content block-size:
2090   WritingMode wm = aFlexItem.Frame()->GetWritingMode();
2091   LogicalSize availSize = aParentReflowInput.ComputedSize(wm);
2092   availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
2093 
2094   StyleSizeOverrides sizeOverrides;
2095   if (aFlexItem.IsStretched()) {
2096     sizeOverrides.mStyleISize.emplace(aFlexItem.StyleCrossSize());
2097     // Suppress any AspectRatio that we might have to prevent ComputeSize() from
2098     // transferring our inline-size override through the aspect-ratio to set the
2099     // block-size, because that would prevent us from measuring the content
2100     // block-size.
2101     sizeOverrides.mAspectRatio.emplace(AspectRatio());
2102     FLEX_LOGV(" Cross size override: %d", aFlexItem.CrossSize());
2103   }
2104   sizeOverrides.mStyleBSize.emplace(StyleSize::Auto());
2105 
2106   ReflowInput childRIForMeasuringBSize(
2107       PresContext(), aParentReflowInput, aFlexItem.Frame(), availSize,
2108       Nothing(), ReflowInput::InitFlag::CallerWillInit, sizeOverrides);
2109   childRIForMeasuringBSize.mFlags.mInsideLineClamp = GetLineClampValue() != 0;
2110   childRIForMeasuringBSize.mFlags.mApplyLineClamp =
2111       childRIForMeasuringBSize.mFlags.mInsideLineClamp || aHasLineClampEllipsis;
2112   childRIForMeasuringBSize.Init(PresContext());
2113 
2114   // When measuring flex item's content block-size, disregard the item's
2115   // min-block-size and max-block-size by resetting both to to their
2116   // unconstraining (extreme) values. The flexbox layout algorithm does still
2117   // explicitly clamp both sizes when resolving the target main size.
2118   childRIForMeasuringBSize.ComputedMinBSize() = 0;
2119   childRIForMeasuringBSize.ComputedMaxBSize() = NS_UNCONSTRAINEDSIZE;
2120 
2121   if (aForceBResizeForMeasuringReflow) {
2122     childRIForMeasuringBSize.SetBResize(true);
2123     // Not 100% sure this is needed, but be conservative for now:
2124     childRIForMeasuringBSize.mFlags.mIsBResizeForPercentages = true;
2125   }
2126 
2127   const CachedBAxisMeasurement& measurement =
2128       MeasureBSizeForFlexItem(aFlexItem, childRIForMeasuringBSize);
2129 
2130   return measurement.BSize();
2131 }
2132 
FlexItem(ReflowInput & aFlexItemReflowInput,float aFlexGrow,float aFlexShrink,nscoord aFlexBaseSize,nscoord aMainMinSize,nscoord aMainMaxSize,nscoord aTentativeCrossSize,nscoord aCrossMinSize,nscoord aCrossMaxSize,const FlexboxAxisTracker & aAxisTracker)2133 FlexItem::FlexItem(ReflowInput& aFlexItemReflowInput, float aFlexGrow,
2134                    float aFlexShrink, nscoord aFlexBaseSize,
2135                    nscoord aMainMinSize, nscoord aMainMaxSize,
2136                    nscoord aTentativeCrossSize, nscoord aCrossMinSize,
2137                    nscoord aCrossMaxSize,
2138                    const FlexboxAxisTracker& aAxisTracker)
2139     : mFrame(aFlexItemReflowInput.mFrame),
2140       mFlexGrow(aFlexGrow),
2141       mFlexShrink(aFlexShrink),
2142       mAspectRatio(mFrame->GetAspectRatio()),
2143       mWM(aFlexItemReflowInput.GetWritingMode()),
2144       mCBWM(aAxisTracker.GetWritingMode()),
2145       mMainAxis(aAxisTracker.MainAxis()),
2146       mBorderPadding(aFlexItemReflowInput.ComputedLogicalBorderPadding(mCBWM)),
2147       mMargin(aFlexItemReflowInput.ComputedLogicalMargin(mCBWM)),
2148       mMainMinSize(aMainMinSize),
2149       mMainMaxSize(aMainMaxSize),
2150       mCrossMinSize(aCrossMinSize),
2151       mCrossMaxSize(aCrossMaxSize),
2152       mCrossSize(aTentativeCrossSize),
2153       mIsInlineAxisMainAxis(aAxisTracker.IsInlineAxisMainAxis(mWM))
2154 // mNeedsMinSizeAutoResolution is initialized in CheckForMinSizeAuto()
2155 // mAlignSelf, mHasAnyAutoMargin see below
2156 {
2157   MOZ_ASSERT(mFrame, "expecting a non-null child frame");
2158   MOZ_ASSERT(!mFrame->IsPlaceholderFrame(),
2159              "placeholder frames should not be treated as flex items");
2160   MOZ_ASSERT(!mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
2161              "out-of-flow frames should not be treated as flex items");
2162   MOZ_ASSERT(mIsInlineAxisMainAxis ==
2163                  nsFlexContainerFrame::IsItemInlineAxisMainAxis(mFrame),
2164              "public API should be consistent with internal state (about "
2165              "whether flex item's inline axis is flex container's main axis)");
2166 
2167   const ReflowInput* containerRS = aFlexItemReflowInput.mParentReflowInput;
2168   if (IsLegacyBox(containerRS->mFrame)) {
2169     // For -webkit-{inline-}box and -moz-{inline-}box, we need to:
2170     // (1) Use prefixed "box-align" instead of "align-items" to determine the
2171     //     container's cross-axis alignment behavior.
2172     // (2) Suppress the ability for flex items to override that with their own
2173     //     cross-axis alignment. (The legacy box model doesn't support this.)
2174     // So, each FlexItem simply copies the container's converted "align-items"
2175     // value and disregards their own "align-self" property.
2176     const nsStyleXUL* containerStyleXUL = containerRS->mFrame->StyleXUL();
2177     mAlignSelf = {ConvertLegacyStyleToAlignItems(containerStyleXUL)};
2178     mAlignSelfFlags = {0};
2179   } else {
2180     mAlignSelf = aFlexItemReflowInput.mStylePosition->UsedAlignSelf(
2181         containerRS->mFrame->Style());
2182     if (MOZ_LIKELY(mAlignSelf._0 == StyleAlignFlags::NORMAL)) {
2183       mAlignSelf = {StyleAlignFlags::STRETCH};
2184     }
2185 
2186     // Store and strip off the <overflow-position> bits
2187     mAlignSelfFlags = mAlignSelf._0 & StyleAlignFlags::FLAG_BITS;
2188     mAlignSelf._0 &= ~StyleAlignFlags::FLAG_BITS;
2189   }
2190 
2191   // Our main-size is considered definite if any of these are true:
2192   // (a) main axis is the item's inline axis.
2193   // (b) flex container has definite main size.
2194   // (c) flex item has a definite flex basis.
2195   //
2196   // Hence, we need to take care to treat the final main-size as *indefinite*
2197   // if none of these conditions are satisfied.
2198   if (mIsInlineAxisMainAxis) {
2199     // The item's block-axis is the flex container's cross axis. We don't need
2200     // any special handling to treat cross sizes as indefinite, because the
2201     // cases where we stomp on the cross size with a definite value are all...
2202     // - situations where the spec requires us to treat the cross size as
2203     // definite; specifically, `align-self:stretch` whose cross size is
2204     // definite.
2205     // - situations where definiteness doesn't matter (e.g. for an element with
2206     // an aspect ratio, which for now are all leaf nodes and hence
2207     // can't have any percent-height descendants that would care about the
2208     // definiteness of its size. (Once bug 1528375 is fixed, we might need to
2209     // be more careful about definite vs. indefinite sizing on flex items with
2210     // aspect ratios.)
2211     mTreatBSizeAsIndefinite = false;
2212   } else {
2213     // The item's block-axis is the flex container's main axis. So, the flex
2214     // item's main size is its BSize, and is considered definite under certain
2215     // conditions laid out for definite flex-item main-sizes in the spec.
2216     if (aAxisTracker.IsRowOriented() ||
2217         (containerRS->ComputedBSize() != NS_UNCONSTRAINEDSIZE &&
2218          !containerRS->mFlags.mTreatBSizeAsIndefinite)) {
2219       // The flex *container* has a definite main-size (either by being
2220       // row-oriented [and using its own inline size which is by definition
2221       // definite, or by being column-oriented and having a definite
2222       // block-size).  The spec says this means all of the flex items'
2223       // post-flexing main sizes should *also* be treated as definite.
2224       mTreatBSizeAsIndefinite = false;
2225     } else if (aFlexBaseSize != NS_UNCONSTRAINEDSIZE) {
2226       // The flex item has a definite flex basis, which we'll treat as making
2227       // its main-size definite.
2228       mTreatBSizeAsIndefinite = false;
2229     } else {
2230       // Otherwise, we have to treat the item's BSize as indefinite.
2231       mTreatBSizeAsIndefinite = true;
2232     }
2233   }
2234 
2235   SetFlexBaseSizeAndMainSize(aFlexBaseSize);
2236   CheckForMinSizeAuto(aFlexItemReflowInput, aAxisTracker);
2237 
2238   const nsStyleMargin* styleMargin = aFlexItemReflowInput.mStyleMargin;
2239   mHasAnyAutoMargin = styleMargin->HasInlineAxisAuto(mCBWM) ||
2240                       styleMargin->HasBlockAxisAuto(mCBWM);
2241 
2242   // Assert that any "auto" margin components are set to 0.
2243   // (We'll resolve them later; until then, we want to treat them as 0-sized.)
2244 #ifdef DEBUG
2245   {
2246     for (const auto side : AllLogicalSides()) {
2247       if (styleMargin->mMargin.Get(mCBWM, side).IsAuto()) {
2248         MOZ_ASSERT(GetMarginComponentForSide(side) == 0,
2249                    "Someone else tried to resolve our auto margin");
2250       }
2251     }
2252   }
2253 #endif  // DEBUG
2254 
2255   // Map align-self 'baseline' value to 'start' when baseline alignment
2256   // is not possible because the FlexItem's block axis is orthogonal to
2257   // the cross axis of the container. If that's the case, we just directly
2258   // convert our align-self value here, so that we don't have to handle this
2259   // with special cases elsewhere.
2260   // We are treating this case as one where it is appropriate to use the
2261   // fallback values defined at https://www.w3.org/TR/css-align/#baseline-values
2262   if (!IsBlockAxisCrossAxis()) {
2263     if (mAlignSelf._0 == StyleAlignFlags::BASELINE) {
2264       mAlignSelf = {StyleAlignFlags::FLEX_START};
2265     } else if (mAlignSelf._0 == StyleAlignFlags::LAST_BASELINE) {
2266       mAlignSelf = {StyleAlignFlags::FLEX_END};
2267     }
2268   }
2269 }
2270 
2271 // Simplified constructor for creating a special "strut" FlexItem, for a child
2272 // with visibility:collapse. The strut has 0 main-size, and it only exists to
2273 // impose a minimum cross size on whichever FlexLine it ends up in.
FlexItem(nsIFrame * aChildFrame,nscoord aCrossSize,WritingMode aContainerWM,const FlexboxAxisTracker & aAxisTracker)2274 FlexItem::FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize,
2275                    WritingMode aContainerWM,
2276                    const FlexboxAxisTracker& aAxisTracker)
2277     : mFrame(aChildFrame),
2278       mWM(aContainerWM),
2279       mCBWM(aContainerWM),
2280       mMainAxis(aAxisTracker.MainAxis()),
2281       mBorderPadding(mCBWM),
2282       mMargin(mCBWM),
2283       mCrossSize(aCrossSize),
2284       // Struts don't do layout, so its WM doesn't matter at this point. So, we
2285       // just share container's WM for simplicity:
2286       mIsFrozen(true),
2287       mIsStrut(true),  // (this is the constructor for making struts, after all)
2288       mAlignSelf({StyleAlignFlags::FLEX_START}) {
2289   MOZ_ASSERT(mFrame, "expecting a non-null child frame");
2290   MOZ_ASSERT(StyleVisibility::Collapse == mFrame->StyleVisibility()->mVisible,
2291              "Should only make struts for children with 'visibility:collapse'");
2292   MOZ_ASSERT(!mFrame->IsPlaceholderFrame(),
2293              "placeholder frames should not be treated as flex items");
2294   MOZ_ASSERT(!mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
2295              "out-of-flow frames should not be treated as flex items");
2296 }
2297 
CheckForMinSizeAuto(const ReflowInput & aFlexItemReflowInput,const FlexboxAxisTracker & aAxisTracker)2298 void FlexItem::CheckForMinSizeAuto(const ReflowInput& aFlexItemReflowInput,
2299                                    const FlexboxAxisTracker& aAxisTracker) {
2300   const nsStylePosition* pos = aFlexItemReflowInput.mStylePosition;
2301   const nsStyleDisplay* disp = aFlexItemReflowInput.mStyleDisplay;
2302 
2303   // We'll need special behavior for "min-[width|height]:auto" (whichever is in
2304   // the flex container's main axis) iff:
2305   // (a) its computed value is "auto"
2306   // (b) the "overflow" sub-property in the same axis (the main axis) has a
2307   //     computed value of "visible" and the item does not create a scroll
2308   //     container.
2309   const auto& mainMinSize = aAxisTracker.IsRowOriented()
2310                                 ? pos->MinISize(aAxisTracker.GetWritingMode())
2311                                 : pos->MinBSize(aAxisTracker.GetWritingMode());
2312 
2313   // If the scrollable overflow makes us create a scroll container, then we
2314   // don't need to do any extra resolution for our `min-size:auto` value.
2315   // We don't need to check for scrollable overflow in a particular axis
2316   // because this will be true for both or neither axis.
2317   mNeedsMinSizeAutoResolution =
2318       IsAutoOrEnumOnBSize(mainMinSize, IsInlineAxisMainAxis()) &&
2319       !disp->IsScrollableOverflow();
2320 }
2321 
BaselineOffsetFromOuterCrossEdge(mozilla::Side aStartSide,bool aUseFirstLineBaseline) const2322 nscoord FlexItem::BaselineOffsetFromOuterCrossEdge(
2323     mozilla::Side aStartSide, bool aUseFirstLineBaseline) const {
2324   // NOTE:
2325   //  * We only use baselines for aligning in the flex container's cross axis.
2326   //  * Baselines are a measurement in the item's block axis.
2327   // ...so we only expect to get here if the item's block axis is parallel (or
2328   // antiparallel) to the container's cross axis.  (Otherwise, the FlexItem
2329   // constructor should've resolved mAlignSelf with a fallback value, which
2330   // would prevent this function from being called.)
2331   MOZ_ASSERT(IsBlockAxisCrossAxis(),
2332              "Only expecting to be doing baseline computations when the "
2333              "cross axis is the block axis");
2334 
2335   mozilla::Side itemBlockStartSide = mWM.PhysicalSide(eLogicalSideBStart);
2336 
2337   nscoord marginBStartToBaseline = ResolvedAscent(aUseFirstLineBaseline) +
2338                                    PhysicalMargin().Side(itemBlockStartSide);
2339 
2340   return (aStartSide == itemBlockStartSide)
2341              ? marginBStartToBaseline
2342              : OuterCrossSize() - marginBStartToBaseline;
2343 }
2344 
IsCrossSizeAuto() const2345 bool FlexItem::IsCrossSizeAuto() const {
2346   const nsStylePosition* stylePos =
2347       nsLayoutUtils::GetStyleFrame(mFrame)->StylePosition();
2348   // Check whichever component is in the flex container's cross axis.
2349   // (IsInlineAxisCrossAxis() tells us whether that's our ISize or BSize, in
2350   // terms of our own WritingMode, mWM.)
2351   return IsInlineAxisCrossAxis() ? stylePos->ISize(mWM).IsAuto()
2352                                  : stylePos->BSize(mWM).IsAuto();
2353 }
2354 
IsCrossSizeDefinite(const ReflowInput & aItemReflowInput) const2355 bool FlexItem::IsCrossSizeDefinite(const ReflowInput& aItemReflowInput) const {
2356   if (IsStretched()) {
2357     // Definite cross-size, imposed via 'align-self:stretch' & flex container.
2358     return true;
2359   }
2360 
2361   const nsStylePosition* pos = aItemReflowInput.mStylePosition;
2362   const auto itemWM = GetWritingMode();
2363 
2364   // The logic here should be similar to the logic for isAutoISize/isAutoBSize
2365   // in nsContainerFrame::ComputeSizeWithIntrinsicDimensions().
2366   if (IsInlineAxisCrossAxis()) {
2367     return !pos->ISize(itemWM).IsAuto();
2368   }
2369 
2370   nscoord cbBSize = aItemReflowInput.mContainingBlockSize.BSize(itemWM);
2371   return !nsLayoutUtils::IsAutoBSize(pos->BSize(itemWM), cbBSize);
2372 }
2373 
ResolveFlexBaseSizeFromAspectRatio(const ReflowInput & aItemReflowInput)2374 void FlexItem::ResolveFlexBaseSizeFromAspectRatio(
2375     const ReflowInput& aItemReflowInput) {
2376   // This implements the Flex Layout Algorithm Step 3B:
2377   // https://drafts.csswg.org/css-flexbox-1/#algo-main-item
2378   // If the flex item has ...
2379   //  - an aspect ratio,
2380   //  - a [used] flex-basis of 'content', and
2381   //  - a definite cross size
2382   // then the flex base size is calculated from its inner cross size and the
2383   // flex item's preferred aspect ratio.
2384   if (HasAspectRatio() &&
2385       nsFlexContainerFrame::IsUsedFlexBasisContent(
2386           aItemReflowInput.mStylePosition->mFlexBasis,
2387           aItemReflowInput.mStylePosition->Size(MainAxis(), mCBWM)) &&
2388       IsCrossSizeDefinite(aItemReflowInput)) {
2389     const LogicalSize contentBoxSizeToBoxSizingAdjust =
2390         aItemReflowInput.mStylePosition->mBoxSizing == StyleBoxSizing::Border
2391             ? BorderPadding().Size(mCBWM)
2392             : LogicalSize(mCBWM);
2393     const nscoord mainSizeFromRatio = mAspectRatio.ComputeRatioDependentSize(
2394         MainAxis(), mCBWM, CrossSize(), contentBoxSizeToBoxSizingAdjust);
2395     SetFlexBaseSizeAndMainSize(mainSizeFromRatio);
2396   }
2397 }
2398 
NumAutoMarginsInAxis(LogicalAxis aAxis) const2399 uint32_t FlexItem::NumAutoMarginsInAxis(LogicalAxis aAxis) const {
2400   uint32_t numAutoMargins = 0;
2401   const auto& styleMargin = mFrame->StyleMargin()->mMargin;
2402   for (const auto edge : {eLogicalEdgeStart, eLogicalEdgeEnd}) {
2403     const auto side = MakeLogicalSide(aAxis, edge);
2404     if (styleMargin.Get(mCBWM, side).IsAuto()) {
2405       numAutoMargins++;
2406     }
2407   }
2408 
2409   // Mostly for clarity:
2410   MOZ_ASSERT(numAutoMargins <= 2,
2411              "We're just looking at one item along one dimension, so we "
2412              "should only have examined 2 margins");
2413 
2414   return numAutoMargins;
2415 }
2416 
CanMainSizeInfluenceCrossSize() const2417 bool FlexItem::CanMainSizeInfluenceCrossSize() const {
2418   if (mIsStretched) {
2419     // We've already had our cross-size stretched for "align-self:stretch").
2420     // The container is imposing its cross size on us.
2421     return false;
2422   }
2423 
2424   if (mIsStrut) {
2425     // Struts (for visibility:collapse items) have a predetermined size;
2426     // no need to measure anything.
2427     return false;
2428   }
2429 
2430   if (HasAspectRatio()) {
2431     // For flex items that have an aspect ratio (and maintain it, i.e. are
2432     // not stretched, which we already checked above): changes to main-size
2433     // *do* influence the cross size.
2434     return true;
2435   }
2436 
2437   if (IsInlineAxisCrossAxis()) {
2438     // If we get here, this function is really asking: "can changes to this
2439     // item's block size have an influence on its inline size"?  For blocks and
2440     // tables, the answer is "no".
2441     if (mFrame->IsBlockFrame() || mFrame->IsTableWrapperFrame()) {
2442       // XXXdholbert (Maybe use an IsFrameOfType query or something more
2443       // general to test this across all frame types? For now, I'm just
2444       // optimizing for block and table, since those are common containers that
2445       // can contain arbitrarily-large subtrees (and that reliably have ISize
2446       // being unaffected by BSize, per CSS2).  So optimizing away needless
2447       // relayout is possible & especially valuable for these containers.)
2448       return false;
2449     }
2450     // Other opt-outs can go here, as they're identified as being useful
2451     // (particularly for containers where an extra reflow is expensive). But in
2452     // general, we have to assume that a flexed BSize *could* influence the
2453     // ISize. Some examples where this can definitely happen:
2454     // * Intrinsically-sized multicol with fixed-ISize columns, which adds
2455     // columns (i.e. grows in inline axis) depending on its block size.
2456     // * Intrinsically-sized multi-line column-oriented flex container, which
2457     // adds flex lines (i.e. grows in inline axis) depending on its block size.
2458   }
2459 
2460   // Default assumption, if we haven't proven otherwise: the resolved main size
2461   // *can* change the cross size.
2462   return true;
2463 }
2464 
ClampMainSizeViaCrossAxisConstraints(nscoord aMainSize,const ReflowInput & aItemReflowInput) const2465 nscoord FlexItem::ClampMainSizeViaCrossAxisConstraints(
2466     nscoord aMainSize, const ReflowInput& aItemReflowInput) const {
2467   MOZ_ASSERT(HasAspectRatio(), "Caller should've checked the ratio is valid!");
2468 
2469   const LogicalSize contentBoxSizeToBoxSizingAdjust =
2470       aItemReflowInput.mStylePosition->mBoxSizing == StyleBoxSizing::Border
2471           ? BorderPadding().Size(mCBWM)
2472           : LogicalSize(mCBWM);
2473 
2474   const nscoord mainMinSizeFromRatio = mAspectRatio.ComputeRatioDependentSize(
2475       MainAxis(), mCBWM, CrossMinSize(), contentBoxSizeToBoxSizingAdjust);
2476   nscoord clampedMainSize = std::max(aMainSize, mainMinSizeFromRatio);
2477 
2478   if (CrossMaxSize() != NS_UNCONSTRAINEDSIZE) {
2479     const nscoord mainMaxSizeFromRatio = mAspectRatio.ComputeRatioDependentSize(
2480         MainAxis(), mCBWM, CrossMaxSize(), contentBoxSizeToBoxSizingAdjust);
2481     clampedMainSize = std::min(clampedMainSize, mainMaxSizeFromRatio);
2482   }
2483 
2484   return clampedMainSize;
2485 }
2486 
2487 /**
2488  * Returns true if aFrame or any of its children have the
2489  * NS_FRAME_CONTAINS_RELATIVE_BSIZE flag set -- i.e. if any of these frames (or
2490  * their descendants) might have a relative-BSize dependency on aFrame (or its
2491  * ancestors).
2492  */
FrameHasRelativeBSizeDependency(nsIFrame * aFrame)2493 static bool FrameHasRelativeBSizeDependency(nsIFrame* aFrame) {
2494   if (aFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
2495     return true;
2496   }
2497   for (const auto& childList : aFrame->ChildLists()) {
2498     for (nsIFrame* childFrame : childList.mList) {
2499       if (childFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
2500         return true;
2501       }
2502     }
2503   }
2504   return false;
2505 }
2506 
NeedsFinalReflow(const nscoord aAvailableBSizeForItem) const2507 bool FlexItem::NeedsFinalReflow(const nscoord aAvailableBSizeForItem) const {
2508   MOZ_ASSERT(
2509       aAvailableBSizeForItem == NS_UNCONSTRAINEDSIZE ||
2510           aAvailableBSizeForItem > 0,
2511       "We can only handle unconstrained or positive available block-size.");
2512 
2513   if (!StaticPrefs::layout_flexbox_item_final_reflow_optimization_enabled()) {
2514     FLEX_LOG(
2515         "[perf] Flex item %p needed a final reflow due to optimization being "
2516         "disabled via the preference",
2517         mFrame);
2518     return true;
2519   }
2520 
2521   // NOTE: even if aAvailableBSizeForItem == NS_UNCONSTRAINEDSIZE we can still
2522   // have continuations from an earlier constrained reflow.
2523   if (mFrame->GetPrevInFlow() || mFrame->GetNextInFlow()) {
2524     // This is an item has continuation(s). Reflow it.
2525     FLEX_LOG("[frag] Flex item %p needed a final reflow due to continuation(s)",
2526              mFrame);
2527     return true;
2528   }
2529 
2530   // Bug 1637091: We can do better and skip this flex item's final reflow if
2531   // both this flex item's block-size and overflow areas can fit the
2532   // aAvailableBSizeForItem.
2533   if (aAvailableBSizeForItem != NS_UNCONSTRAINEDSIZE) {
2534     FLEX_LOG(
2535         "[frag] Flex item %p needed both a measuring reflow and a final "
2536         "reflow due to constrained available block-size",
2537         mFrame);
2538     return true;
2539   }
2540 
2541   // Flex item's final content-box size (in terms of its own writing-mode):
2542   const LogicalSize finalSize = mIsInlineAxisMainAxis
2543                                     ? LogicalSize(mWM, mMainSize, mCrossSize)
2544                                     : LogicalSize(mWM, mCrossSize, mMainSize);
2545 
2546   if (HadMeasuringReflow()) {
2547     // We've already reflowed this flex item once, to measure it. In that
2548     // reflow, did its frame happen to end up with the correct final size
2549     // that the flex container would like it to have?
2550     if (finalSize != mFrame->ContentSize(mWM)) {
2551       // The measuring reflow left the item with a different size than its
2552       // final flexed size. So, we need to reflow to give it the correct size.
2553       FLEX_LOG(
2554           "[perf] Flex item %p needed both a measuring reflow and a final "
2555           "reflow due to measured size disagreeing with final size",
2556           mFrame);
2557       return true;
2558     }
2559 
2560     if (FrameHasRelativeBSizeDependency(mFrame)) {
2561       // This item has descendants with relative BSizes who may care that its
2562       // size may now be considered "definite" in the final reflow (whereas it
2563       // was indefinite during the measuring reflow).
2564       FLEX_LOG(
2565           "[perf] Flex item %p needed both a measuring reflow and a final "
2566           "reflow due to BSize potentially becoming definite",
2567           mFrame);
2568       return true;
2569     }
2570 
2571     // If we get here, then this flex item had a measuring reflow, it left us
2572     // with the correct size, none of its descendants care that its BSize may
2573     // now be considered definite, and it can fit into the available block-size.
2574     // So it doesn't need a final reflow.
2575     //
2576     // We now cache this size as if we had done a final reflow (because we've
2577     // determined that the measuring reflow was effectively equivalent).  This
2578     // way, in our next time through flex layout, we may be able to skip both
2579     // the measuring reflow *and* the final reflow (if conditions are the same
2580     // as they are now).
2581     if (auto* cache = mFrame->GetProperty(CachedFlexItemData::Prop())) {
2582       cache->Update(*this, finalSize);
2583     }
2584 
2585     return false;
2586   }
2587 
2588   // This item didn't receive a measuring reflow (at least, not during this
2589   // reflow of our flex container).  We may still be able to skip reflowing it
2590   // (i.e. return false from this function), if its subtree is clean & its most
2591   // recent "final reflow" had it at the correct content-box size &
2592   // definiteness.
2593   // Let's check for each condition that would still require us to reflow:
2594   if (mFrame->IsSubtreeDirty()) {
2595     FLEX_LOG(
2596         "[perf] Flex item %p needed a final reflow due to its subtree "
2597         "being dirty",
2598         mFrame);
2599     return true;
2600   }
2601 
2602   // Cool; this item & its subtree haven't experienced any style/content
2603   // changes that would automatically require a reflow.
2604 
2605   // Did we cache the metrics from its most recent "final reflow"?
2606   auto* cache = mFrame->GetProperty(CachedFlexItemData::Prop());
2607   if (!cache || !cache->mFinalReflowMetrics) {
2608     FLEX_LOG(
2609         "[perf] Flex item %p needed a final reflow due to lacking a "
2610         "cached mFinalReflowMetrics (maybe cache was cleared)",
2611         mFrame);
2612     return true;
2613   }
2614 
2615   // Does the cached size match our current size?
2616   if (cache->mFinalReflowMetrics->Size() != finalSize) {
2617     FLEX_LOG(
2618         "[perf] Flex item %p needed a final reflow due to having a "
2619         "different content box size vs. its most recent final reflow",
2620         mFrame);
2621     return true;
2622   }
2623 
2624   // Does the cached border and padding match our current ones?
2625   //
2626   // Note: this is just to detect cases where we have a percent padding whose
2627   // basis has changed. Any other sort of change to BorderPadding() (e.g. a new
2628   // specified value) should result in the frame being marked dirty via proper
2629   // change hint (see nsStylePadding::CalcDifference()), which will force it to
2630   // reflow.
2631   if (cache->mFinalReflowMetrics->BorderPadding() !=
2632       BorderPadding().ConvertTo(mWM, mCBWM)) {
2633     FLEX_LOG(
2634         "[perf] Flex item %p needed a final reflow due to having a "
2635         "different border and padding vs. its most recent final reflow",
2636         mFrame);
2637     return true;
2638   }
2639 
2640   // The flex container is giving this flex item the same size that the item
2641   // had on its most recent "final reflow". But if its definiteness changed and
2642   // one of the descendants cares, then it would still need a reflow.
2643   if (cache->mFinalReflowMetrics->TreatBSizeAsIndefinite() !=
2644           mTreatBSizeAsIndefinite &&
2645       FrameHasRelativeBSizeDependency(mFrame)) {
2646     FLEX_LOG(
2647         "[perf] Flex item %p needed a final reflow due to having "
2648         "its BSize change definiteness & having a rel-BSize child",
2649         mFrame);
2650     return true;
2651   }
2652 
2653   // If we get here, we can skip the final reflow! (The item's subtree isn't
2654   // dirty, and our current conditions are sufficiently similar to the most
2655   // recent "final reflow" that it should have left our subtree in the correct
2656   // state.)
2657   FLEX_LOG("[perf] Flex item %p didn't need a final reflow", mFrame);
2658   return false;
2659 }
2660 
2661 // Keeps track of our position along a particular axis (where a '0' position
2662 // corresponds to the 'start' edge of that axis).
2663 // This class shouldn't be instantiated directly -- rather, it should only be
2664 // instantiated via its subclasses defined below.
2665 class MOZ_STACK_CLASS PositionTracker {
2666  public:
2667   // Accessor for the current value of the position that we're tracking.
Position() const2668   inline nscoord Position() const { return mPosition; }
Axis() const2669   inline LogicalAxis Axis() const { return mAxis; }
2670 
StartSide()2671   inline LogicalSide StartSide() {
2672     return MakeLogicalSide(
2673         mAxis, mIsAxisReversed ? eLogicalEdgeEnd : eLogicalEdgeStart);
2674   }
2675 
EndSide()2676   inline LogicalSide EndSide() {
2677     return MakeLogicalSide(
2678         mAxis, mIsAxisReversed ? eLogicalEdgeStart : eLogicalEdgeEnd);
2679   }
2680 
2681   // Advances our position across the start edge of the given margin, in the
2682   // axis we're tracking.
EnterMargin(const LogicalMargin & aMargin)2683   void EnterMargin(const LogicalMargin& aMargin) {
2684     mPosition += aMargin.Side(StartSide(), mWM);
2685   }
2686 
2687   // Advances our position across the end edge of the given margin, in the axis
2688   // we're tracking.
ExitMargin(const LogicalMargin & aMargin)2689   void ExitMargin(const LogicalMargin& aMargin) {
2690     mPosition += aMargin.Side(EndSide(), mWM);
2691   }
2692 
2693   // Advances our current position from the start side of a child frame's
2694   // border-box to the frame's upper or left edge (depending on our axis).
2695   // (Note that this is a no-op if our axis grows in the same direction as
2696   // the corresponding logical axis.)
EnterChildFrame(nscoord aChildFrameSize)2697   void EnterChildFrame(nscoord aChildFrameSize) {
2698     if (mIsAxisReversed) {
2699       mPosition += aChildFrameSize;
2700     }
2701   }
2702 
2703   // Advances our current position from a frame's upper or left border-box edge
2704   // (whichever is in the axis we're tracking) to the 'end' side of the frame
2705   // in the axis that we're tracking. (Note that this is a no-op if our axis
2706   // is reversed with respect to the corresponding logical axis.)
ExitChildFrame(nscoord aChildFrameSize)2707   void ExitChildFrame(nscoord aChildFrameSize) {
2708     if (!mIsAxisReversed) {
2709       mPosition += aChildFrameSize;
2710     }
2711   }
2712 
2713   // Delete copy-constructor & reassignment operator, to prevent accidental
2714   // (unnecessary) copying.
2715   PositionTracker(const PositionTracker&) = delete;
2716   PositionTracker& operator=(const PositionTracker&) = delete;
2717 
2718  protected:
2719   // Protected constructor, to be sure we're only instantiated via a subclass.
PositionTracker(WritingMode aWM,LogicalAxis aAxis,bool aIsAxisReversed)2720   PositionTracker(WritingMode aWM, LogicalAxis aAxis, bool aIsAxisReversed)
2721       : mWM(aWM), mAxis(aAxis), mIsAxisReversed(aIsAxisReversed) {}
2722 
2723   // Member data:
2724   // The position we're tracking.
2725   nscoord mPosition = 0;
2726 
2727   // The flex container's writing mode.
2728   const WritingMode mWM;
2729 
2730   // The axis along which we're moving.
2731   const LogicalAxis mAxis = eLogicalAxisInline;
2732 
2733   // Is the axis along which we're moving reversed (e.g. LTR vs RTL) with
2734   // respect to the corresponding axis on the flex container's WM?
2735   const bool mIsAxisReversed = false;
2736 };
2737 
2738 // Tracks our position in the main axis, when we're laying out flex items.
2739 // The "0" position represents the main-start edge of the flex container's
2740 // content-box.
2741 class MOZ_STACK_CLASS MainAxisPositionTracker : public PositionTracker {
2742  public:
2743   MainAxisPositionTracker(const FlexboxAxisTracker& aAxisTracker,
2744                           const FlexLine* aLine,
2745                           const StyleContentDistribution& aJustifyContent,
2746                           nscoord aContentBoxMainSize);
2747 
~MainAxisPositionTracker()2748   ~MainAxisPositionTracker() {
2749     MOZ_ASSERT(mNumPackingSpacesRemaining == 0,
2750                "miscounted the number of packing spaces");
2751     MOZ_ASSERT(mNumAutoMarginsInMainAxis == 0,
2752                "miscounted the number of auto margins");
2753   }
2754 
2755   // Advances past the gap space (if any) between two flex items
TraverseGap(nscoord aGapSize)2756   void TraverseGap(nscoord aGapSize) { mPosition += aGapSize; }
2757 
2758   // Advances past the packing space (if any) between two flex items
2759   void TraversePackingSpace();
2760 
2761   // If aItem has any 'auto' margins in the main axis, this method updates the
2762   // corresponding values in its margin.
2763   void ResolveAutoMarginsInMainAxis(FlexItem& aItem);
2764 
2765  private:
2766   nscoord mPackingSpaceRemaining = 0;
2767   uint32_t mNumAutoMarginsInMainAxis = 0;
2768   uint32_t mNumPackingSpacesRemaining = 0;
2769   StyleContentDistribution mJustifyContent = {StyleAlignFlags::AUTO};
2770 };
2771 
2772 // Utility class for managing our position along the cross axis along
2773 // the whole flex container (at a higher level than a single line).
2774 // The "0" position represents the cross-start edge of the flex container's
2775 // content-box.
2776 class MOZ_STACK_CLASS CrossAxisPositionTracker : public PositionTracker {
2777  public:
2778   CrossAxisPositionTracker(nsTArray<FlexLine>& aLines,
2779                            const ReflowInput& aReflowInput,
2780                            nscoord aContentBoxCrossSize,
2781                            bool aIsCrossSizeDefinite,
2782                            const FlexboxAxisTracker& aAxisTracker,
2783                            const nscoord aCrossGapSize);
2784 
2785   // Advances past the gap (if any) between two flex lines
TraverseGap()2786   void TraverseGap() { mPosition += mCrossGapSize; }
2787 
2788   // Advances past the packing space (if any) between two flex lines
2789   void TraversePackingSpace();
2790 
2791   // Advances past the given FlexLine
TraverseLine(FlexLine & aLine)2792   void TraverseLine(FlexLine& aLine) { mPosition += aLine.LineCrossSize(); }
2793 
2794   // Redeclare the frame-related methods from PositionTracker with
2795   // = delete, to be sure (at compile time) that no client code can invoke
2796   // them. (Unlike the other PositionTracker derived classes, this class here
2797   // deals with FlexLines, not with individual FlexItems or frames.)
2798   void EnterMargin(const LogicalMargin& aMargin) = delete;
2799   void ExitMargin(const LogicalMargin& aMargin) = delete;
2800   void EnterChildFrame(nscoord aChildFrameSize) = delete;
2801   void ExitChildFrame(nscoord aChildFrameSize) = delete;
2802 
2803  private:
2804   nscoord mPackingSpaceRemaining = 0;
2805   uint32_t mNumPackingSpacesRemaining = 0;
2806   StyleContentDistribution mAlignContent = {StyleAlignFlags::AUTO};
2807 
2808   const nscoord mCrossGapSize;
2809 };
2810 
2811 // Utility class for managing our position along the cross axis, *within* a
2812 // single flex line.
2813 class MOZ_STACK_CLASS SingleLineCrossAxisPositionTracker
2814     : public PositionTracker {
2815  public:
2816   explicit SingleLineCrossAxisPositionTracker(
2817       const FlexboxAxisTracker& aAxisTracker);
2818 
2819   void ResolveAutoMarginsInCrossAxis(const FlexLine& aLine, FlexItem& aItem);
2820 
2821   void EnterAlignPackingSpace(const FlexLine& aLine, const FlexItem& aItem,
2822                               const FlexboxAxisTracker& aAxisTracker);
2823 
2824   // Resets our position to the cross-start edge of this line.
ResetPosition()2825   inline void ResetPosition() { mPosition = 0; }
2826 };
2827 
2828 //----------------------------------------------------------------------
2829 
2830 // Frame class boilerplate
2831 // =======================
2832 
2833 NS_QUERYFRAME_HEAD(nsFlexContainerFrame)
NS_QUERYFRAME_ENTRY(nsFlexContainerFrame)2834   NS_QUERYFRAME_ENTRY(nsFlexContainerFrame)
2835 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
2836 
2837 NS_IMPL_FRAMEARENA_HELPERS(nsFlexContainerFrame)
2838 
2839 // Suppose D is the distance from a flex container fragment's content-box
2840 // block-start edge to whichever is larger of either (a) the block-end edge of
2841 // its children, or (b) the available space's block-end edge. D is conceptually
2842 // the sum of the block-size of the children, the packing space before & in
2843 // between them, and part of the packing space after them if (b) happens.
2844 //
2845 // SumOfBlockEndEdgeOfChildrenProperty stores the sum of the D of the current
2846 // flex container fragment and the D's of its prev-in-flows from the last
2847 // reflow. It's intended to prevent quadratic operations resulting from each
2848 // fragment having to walk its full prev-in-flow chain, and also serves as an
2849 // argument to the flex fragmentainer next-in-flow's ReflowChildren(), to
2850 // compute the position offset for each flex item.
2851 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(SumOfChildrenBlockSizeProperty, nscoord)
2852 
2853 nsContainerFrame* NS_NewFlexContainerFrame(PresShell* aPresShell,
2854                                            ComputedStyle* aStyle) {
2855   return new (aPresShell)
2856       nsFlexContainerFrame(aStyle, aPresShell->GetPresContext());
2857 }
2858 
2859 //----------------------------------------------------------------------
2860 
2861 // nsFlexContainerFrame Method Implementations
2862 // ===========================================
2863 
2864 /* virtual */
2865 nsFlexContainerFrame::~nsFlexContainerFrame() = default;
2866 
2867 /* virtual */
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)2868 void nsFlexContainerFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
2869                                 nsIFrame* aPrevInFlow) {
2870   nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
2871 
2872   if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER)) {
2873     AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
2874   }
2875 
2876   const nsStyleDisplay* styleDisp = Style()->StyleDisplay();
2877 
2878   // Figure out if we should set a frame state bit to indicate that this frame
2879   // represents a legacy -webkit-{inline-}box or -moz-{inline-}box container.
2880   // First, the trivial case: just check "display" directly.
2881   bool isLegacyBox = IsDisplayValueLegacyBox(styleDisp);
2882 
2883   // If this frame is for a scrollable element, then it will actually have
2884   // "display:block", and its *parent frame* will have the real
2885   // flex-flavored display value. So in that case, check the parent frame to
2886   // find out if we're legacy.
2887   if (!isLegacyBox && styleDisp->mDisplay == mozilla::StyleDisplay::Block) {
2888     ComputedStyle* parentComputedStyle = GetParent()->Style();
2889     NS_ASSERTION(
2890         Style()->GetPseudoType() == PseudoStyleType::buttonContent ||
2891             Style()->GetPseudoType() == PseudoStyleType::scrolledContent,
2892         "The only way a nsFlexContainerFrame can have 'display:block' "
2893         "should be if it's the inner part of a scrollable or button "
2894         "element");
2895     isLegacyBox = IsDisplayValueLegacyBox(parentComputedStyle->StyleDisplay());
2896   }
2897 
2898   if (isLegacyBox) {
2899     AddStateBits(NS_STATE_FLEX_IS_EMULATING_LEGACY_BOX);
2900   }
2901 }
2902 
2903 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const2904 nsresult nsFlexContainerFrame::GetFrameName(nsAString& aResult) const {
2905   return MakeFrameName(u"FlexContainer"_ns, aResult);
2906 }
2907 #endif
2908 
GetLogicalBaseline(mozilla::WritingMode aWM) const2909 nscoord nsFlexContainerFrame::GetLogicalBaseline(
2910     mozilla::WritingMode aWM) const {
2911   NS_ASSERTION(mBaselineFromLastReflow != NS_INTRINSIC_ISIZE_UNKNOWN,
2912                "baseline has not been set");
2913 
2914   if (HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE)) {
2915     // Return a baseline synthesized from our margin-box.
2916     return nsContainerFrame::GetLogicalBaseline(aWM);
2917   }
2918   return mBaselineFromLastReflow;
2919 }
2920 
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)2921 void nsFlexContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
2922                                             const nsDisplayListSet& aLists) {
2923   nsDisplayListCollection tempLists(aBuilder);
2924 
2925   DisplayBorderBackgroundOutline(aBuilder, tempLists);
2926   if (GetPrevInFlow()) {
2927     DisplayOverflowContainers(aBuilder, tempLists);
2928   }
2929 
2930   // Our children are all block-level, so their borders/backgrounds all go on
2931   // the BlockBorderBackgrounds list.
2932   nsDisplayListSet childLists(tempLists, tempLists.BlockBorderBackgrounds());
2933 
2934   CSSOrderAwareFrameIterator iter(
2935       this, kPrincipalList, CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
2936       OrderStateForIter(this), OrderingPropertyForIter(this));
2937 
2938   for (; !iter.AtEnd(); iter.Next()) {
2939     nsIFrame* childFrame = *iter;
2940     BuildDisplayListForChild(aBuilder, childFrame, childLists,
2941                              childFrame->DisplayFlagForFlexOrGridItem());
2942   }
2943 
2944   tempLists.MoveTo(aLists);
2945 }
2946 
FreezeItemsEarly(bool aIsUsingFlexGrow,ComputedFlexLineInfo * aLineInfo)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.
2952   //
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
2961   //
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.)
2965 
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;
2972     }
2973 
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;
2981           }
2982         } else {  // using flex-shrink
2983           if (item.FlexBaseSize() < item.MainSize()) {
2984             shouldFreeze = true;
2985           }
2986         }
2987       }
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();
2995         }
2996         mNumFrozenItems++;
2997       }
2998     }
2999   }
3000 
3001   MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");
3002 }
3003 
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.
FreezeOrRestoreEachFlexibleSize(const nscoord aTotalViolation,bool aIsFinalIteration)3006 void FlexLine::FreezeOrRestoreEachFlexibleSize(const nscoord aTotalViolation,
3007                                                bool aIsFinalIteration) {
3008   enum FreezeType {
3009     eFreezeEverything,
3010     eFreezeMinViolations,
3011     eFreezeMaxViolations
3012   };
3013 
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;
3021   }
3022 
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;
3029     }
3030 
3031     if (!item.IsFrozen()) {
3032       numUnfrozenItemsToBeSeen--;
3033 
3034       MOZ_ASSERT(!item.HadMinViolation() || !item.HadMaxViolation(),
3035                  "Can have either min or max violation, but not both");
3036 
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");
3046 
3047         item.Freeze();
3048         if (hadMinViolation) {
3049           item.SetWasMinClamped();
3050         } else if (hadMaxViolation) {
3051           item.SetWasMaxClamped();
3052         }
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.
3064 
3065       if (!item.IsFrozen()) {
3066         // Clear this item's violation(s), now that we've dealt with them
3067         item.ClearViolationFlags();
3068       }
3069     }
3070   }
3071 
3072   MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");
3073 }
3074 
ResolveFlexibleLengths(nscoord aFlexContainerMainSize,ComputedFlexLineInfo * aLineInfo)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);
3083 
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;
3094     }
3095   }
3096 
3097   // Determine whether we're going to be growing or shrinking items.
3098   const bool isUsingFlexGrow =
3099       (mTotalOuterHypotheticalMainSize < flexContainerMainSize);
3100 
3101   if (aLineInfo) {
3102     aLineInfo->mGrowthState =
3103         isUsingFlexGrow ? mozilla::dom::FlexLineGrowthState::Growing
3104                         : mozilla::dom::FlexLineGrowthState::Shrinking;
3105   }
3106 
3107   // Do an "early freeze" for flex items that obviously can't flex in the
3108   // direction we've chosen:
3109   FreezeItemsEarly(isUsingFlexGrow, aLineInfo);
3110 
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;
3116   }
3117   MOZ_ASSERT(!IsEmpty() || aLineInfo,
3118              "empty lines should take the early-return above");
3119 
3120   FLEX_LOG("Resolving flexible lengths for items");
3121 
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;
3128 
3129   Maybe<AuCoord64> origAvailableFreeSpace;
3130 
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());
3147       }
3148       availableFreeSpace -= item.MainSize();
3149     }
3150 
3151     FLEX_LOG(" available free space: %" PRId64 "; flex items should \"%s\"",
3152              availableFreeSpace.value, isUsingFlexGrow ? "grow" : "shrink");
3153 
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.
3158     //
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");
3175 
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);
3182       }
3183 
3184       // STRATEGY: On each item, we compute & store its "share" of the total
3185       // weight that we've seen so far:
3186       //   curWeight / weightSum
3187       //
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.
3191       //
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;
3202 
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;
3209         }
3210 
3211         if (!item.IsFrozen()) {
3212           numUnfrozenItemsToBeSeen--;
3213 
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");
3218 
3219           weightSum += curWeight;
3220           flexFactorSum += curFlexFactor;
3221 
3222           if (IsFinite(weightSum)) {
3223             if (curWeight == 0.0) {
3224               item.SetShareOfWeightSoFar(0.0);
3225             } else {
3226               item.SetShareOfWeightSoFar(curWeight / weightSum);
3227             }
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.)
3232 
3233           // Update our largest-weight tracking vars
3234           if (curWeight > largestWeight) {
3235             largestWeight = curWeight;
3236             numItemsWithLargestWeight = 1;
3237           } else if (curWeight == largestWeight) {
3238             numItemsWithLargestWeight++;
3239           }
3240         }
3241       }
3242 
3243       MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");
3244 
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);
3256 
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...");
3267 
3268           if (availableFreeSpace > 0) {
3269             availableFreeSpace = std::min(availableFreeSpace,
3270                                           totalDesiredPortionOfOrigFreeSpace);
3271           } else {
3272             availableFreeSpace = std::max(availableFreeSpace,
3273                                           totalDesiredPortionOfOrigFreeSpace);
3274           }
3275         }
3276 
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;
3281 
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;
3288           }
3289 
3290           if (!item.IsFrozen()) {
3291             numUnfrozenItemsToBeSeen--;
3292 
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 (IsFinite(weightSum)) {
3297               double myShareOfRemainingSpace = item.ShareOfWeightSoFar();
3298 
3299               MOZ_ASSERT(myShareOfRemainingSpace >= 0.0 &&
3300                              myShareOfRemainingSpace <= 1.0,
3301                          "my share should be nonnegative fractional amount");
3302 
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);
3310               }
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--;
3318             }
3319 
3320             availableFreeSpace -= sizeDelta;
3321 
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());
3326           }
3327         }
3328 
3329         MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");
3330 
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;
3350 
3351               aLineInfo->mItems[itemIndex].mMainDeltaSize = deltaSize;
3352             }
3353             ++itemIndex;
3354           }
3355         }
3356       }
3357     }
3358 
3359     // Fix min/max violations:
3360     nscoord totalViolation = 0;  // keeps track of adjustments for min/max
3361     FLEX_LOG(" Checking for violations:");
3362 
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;
3369       }
3370 
3371       if (!item.IsFrozen()) {
3372         numUnfrozenItemsToBeSeen--;
3373 
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();
3384         }
3385       }
3386     }
3387 
3388     MOZ_ASSERT(numUnfrozenItemsToBeSeen == 0, "miscounted frozen items?");
3389 
3390     FreezeOrRestoreEachFlexibleSize(totalViolation,
3391                                     iterationCounter + 1 == NumItems());
3392 
3393     FLEX_LOG(" Total violation: %d", totalViolation);
3394 
3395     if (mNumFrozenItems == NumItems()) {
3396       break;
3397     }
3398 
3399     MOZ_ASSERT(totalViolation != 0,
3400                "Zero violation should've made us freeze all items & break");
3401   }
3402 
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");
3407 
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");
3411   }
3412 #endif  // DEBUG
3413 }
3414 
MainAxisPositionTracker(const FlexboxAxisTracker & aAxisTracker,const FlexLine * aLine,const StyleContentDistribution & aJustifyContent,nscoord aContentBoxMainSize)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;
3430 
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;
3437   }
3438 
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();
3445   }
3446 
3447   // Subtract space required for row/col gap from the remaining packing space
3448   mPackingSpaceRemaining -= aLine->SumOfGaps();
3449 
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;
3457     }
3458   }
3459 
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;
3470     }
3471   }
3472 
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);
3478   }
3479 
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;
3489   }
3490 
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");
3511     }
3512   }
3513 
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");
3517 }
3518 
ResolveAutoMarginsInMainAxis(FlexItem & aItem)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;
3528 
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);
3533 
3534         mNumAutoMarginsInMainAxis--;
3535         mPackingSpaceRemaining -= curAutoMarginSize;
3536       }
3537     }
3538   }
3539 }
3540 
TraversePackingSpace()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");
3548 
3549     MOZ_ASSERT(mPackingSpaceRemaining >= 0,
3550                "ran out of packing space earlier than we expected");
3551 
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;
3556 
3557     mPosition += curPackingSpace;
3558     mNumPackingSpacesRemaining--;
3559     mPackingSpaceRemaining -= curPackingSpace;
3560   }
3561 }
3562 
CrossAxisPositionTracker(nsTArray<FlexLine> & aLines,const ReflowInput & aReflowInput,nscoord aContentBoxCrossSize,bool aIsCrossSizeDefinite,const FlexboxAxisTracker & aAxisTracker,const nscoord aCrossGapSize)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;
3575 
3576   // 'normal' behaves as 'stretch'
3577   if (mAlignContent.primary == StyleAlignFlags::NORMAL) {
3578     mAlignContent.primary = StyleAlignFlags::STRETCH;
3579   }
3580 
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."
3588     //
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;
3597     }
3598 
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()));
3605   }
3606 
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.)
3611 
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++;
3620   }
3621 
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);
3626 
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;
3632   }
3633 
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;
3649     }
3650   }
3651 
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;
3661   }
3662 
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       NS_WARNING(
3669           "NYI: "
3670           "align-items/align-self:left/right/self-start/self-end/baseline/"
3671           "last baseline");
3672     } else if (mAlignContent.primary == StyleAlignFlags::FLEX_START) {
3673       // All packing space should go at the end --> nothing to do here.
3674     } else if (mAlignContent.primary == StyleAlignFlags::FLEX_END) {
3675       // All packing space goes at the beginning
3676       mPosition += mPackingSpaceRemaining;
3677     } else if (mAlignContent.primary == StyleAlignFlags::CENTER) {
3678       // Half the packing space goes at the beginning
3679       mPosition += mPackingSpaceRemaining / 2;
3680     } else if (mAlignContent.primary == StyleAlignFlags::SPACE_BETWEEN ||
3681                mAlignContent.primary == StyleAlignFlags::SPACE_AROUND ||
3682                mAlignContent.primary == StyleAlignFlags::SPACE_EVENLY) {
3683       nsFlexContainerFrame::CalculatePackingSpace(
3684           numLines, mAlignContent, &mPosition, &mNumPackingSpacesRemaining,
3685           &mPackingSpaceRemaining);
3686     } else if (mAlignContent.primary == StyleAlignFlags::STRETCH) {
3687       // Split space equally between the lines:
3688       MOZ_ASSERT(mPackingSpaceRemaining > 0,
3689                  "negative packing space should make us use 'flex-start' "
3690                  "instead of 'stretch' (and we shouldn't bother with this "
3691                  "code if we have 0 packing space)");
3692 
3693       uint32_t numLinesLeft = numLines;
3694       for (FlexLine& line : aLines) {
3695         // Our share is the amount of space remaining, divided by the number
3696         // of lines remainig.
3697         MOZ_ASSERT(numLinesLeft > 0, "miscalculated num lines");
3698         nscoord shareOfExtraSpace = mPackingSpaceRemaining / numLinesLeft;
3699         nscoord newSize = line.LineCrossSize() + shareOfExtraSpace;
3700         line.SetLineCrossSize(newSize);
3701 
3702         mPackingSpaceRemaining -= shareOfExtraSpace;
3703         numLinesLeft--;
3704       }
3705       MOZ_ASSERT(numLinesLeft == 0, "miscalculated num lines");
3706     } else {
3707       MOZ_ASSERT_UNREACHABLE("Unexpected align-content value");
3708     }
3709   }
3710 }
3711 
TraversePackingSpace()3712 void CrossAxisPositionTracker::TraversePackingSpace() {
3713   if (mNumPackingSpacesRemaining) {
3714     MOZ_ASSERT(mAlignContent.primary == StyleAlignFlags::SPACE_BETWEEN ||
3715                    mAlignContent.primary == StyleAlignFlags::SPACE_AROUND ||
3716                    mAlignContent.primary == StyleAlignFlags::SPACE_EVENLY,
3717                "mNumPackingSpacesRemaining only applies for "
3718                "space-between/space-around/space-evenly");
3719 
3720     MOZ_ASSERT(mPackingSpaceRemaining >= 0,
3721                "ran out of packing space earlier than we expected");
3722 
3723     // NOTE: This integer math will skew the distribution of remainder
3724     // app-units towards the end, which is fine.
3725     nscoord curPackingSpace =
3726         mPackingSpaceRemaining / mNumPackingSpacesRemaining;
3727 
3728     mPosition += curPackingSpace;
3729     mNumPackingSpacesRemaining--;
3730     mPackingSpaceRemaining -= curPackingSpace;
3731   }
3732 }
3733 
SingleLineCrossAxisPositionTracker(const FlexboxAxisTracker & aAxisTracker)3734 SingleLineCrossAxisPositionTracker::SingleLineCrossAxisPositionTracker(
3735     const FlexboxAxisTracker& aAxisTracker)
3736     : PositionTracker(aAxisTracker.GetWritingMode(), aAxisTracker.CrossAxis(),
3737                       aAxisTracker.IsCrossAxisReversed()) {}
3738 
ComputeCrossSizeAndBaseline(const FlexboxAxisTracker & aAxisTracker)3739 void FlexLine::ComputeCrossSizeAndBaseline(
3740     const FlexboxAxisTracker& aAxisTracker) {
3741   nscoord crossStartToFurthestFirstBaseline = nscoord_MIN;
3742   nscoord crossEndToFurthestFirstBaseline = nscoord_MIN;
3743   nscoord crossStartToFurthestLastBaseline = nscoord_MIN;
3744   nscoord crossEndToFurthestLastBaseline = nscoord_MIN;
3745   nscoord largestOuterCrossSize = 0;
3746   for (const FlexItem& item : Items()) {
3747     nscoord curOuterCrossSize = item.OuterCrossSize();
3748 
3749     if ((item.AlignSelf()._0 == StyleAlignFlags::BASELINE ||
3750          item.AlignSelf()._0 == StyleAlignFlags::LAST_BASELINE) &&
3751         item.NumAutoMarginsInCrossAxis() == 0) {
3752       const bool useFirst = (item.AlignSelf()._0 == StyleAlignFlags::BASELINE);
3753       // FIXME: Once we support "writing-mode", we'll have to do baseline
3754       // alignment in vertical flex containers here (w/ horizontal cross-axes).
3755 
3756       // Find distance from our item's cross-start and cross-end margin-box
3757       // edges to its baseline.
3758       //
3759       // Here's a diagram of a flex-item that we might be doing this on.
3760       // "mmm" is the margin-box, "bbb" is the border-box. The bottom of
3761       // the text "BASE" is the baseline.
3762       //
3763       // ---(cross-start)---
3764       //                ___              ___            ___
3765       //   mmmmmmmmmmmm  |                |margin-start  |
3766       //   m          m  |               _|_   ___       |
3767       //   m bbbbbbbb m  |curOuterCrossSize     |        |crossStartToBaseline
3768       //   m b      b m  |                      |ascent  |
3769       //   m b BASE b m  |                     _|_      _|_
3770       //   m b      b m  |                               |
3771       //   m bbbbbbbb m  |                               |crossEndToBaseline
3772       //   m          m  |                               |
3773       //   mmmmmmmmmmmm _|_                             _|_
3774       //
3775       // ---(cross-end)---
3776       //
3777       // We already have the curOuterCrossSize, margin-start, and the ascent.
3778       // * We can get crossStartToBaseline by adding margin-start + ascent.
3779       // * If we subtract that from the curOuterCrossSize, we get
3780       //   crossEndToBaseline.
3781 
3782       nscoord crossStartToBaseline = item.BaselineOffsetFromOuterCrossEdge(
3783           aAxisTracker.CrossAxisPhysicalStartSide(), useFirst);
3784       nscoord crossEndToBaseline = curOuterCrossSize - crossStartToBaseline;
3785 
3786       // Now, update our "largest" values for these (across all the flex items
3787       // in this flex line), so we can use them in computing the line's cross
3788       // size below:
3789       if (useFirst) {
3790         crossStartToFurthestFirstBaseline =
3791             std::max(crossStartToFurthestFirstBaseline, crossStartToBaseline);
3792         crossEndToFurthestFirstBaseline =
3793             std::max(crossEndToFurthestFirstBaseline, crossEndToBaseline);
3794       } else {
3795         crossStartToFurthestLastBaseline =
3796             std::max(crossStartToFurthestLastBaseline, crossStartToBaseline);
3797         crossEndToFurthestLastBaseline =
3798             std::max(crossEndToFurthestLastBaseline, crossEndToBaseline);
3799       }
3800     } else {
3801       largestOuterCrossSize =
3802           std::max(largestOuterCrossSize, curOuterCrossSize);
3803     }
3804   }
3805 
3806   // The line's baseline offset is the distance from the line's edge to the
3807   // furthest item-baseline. The item(s) with that baseline will be exactly
3808   // aligned with the line's edge.
3809   mFirstBaselineOffset = crossStartToFurthestFirstBaseline;
3810   mLastBaselineOffset = crossEndToFurthestLastBaseline;
3811 
3812   // The line's cross-size is the larger of:
3813   //  (a) [largest cross-start-to-baseline + largest baseline-to-cross-end] of
3814   //      all baseline-aligned items with no cross-axis auto margins...
3815   // and
3816   //  (b) [largest cross-start-to-baseline + largest baseline-to-cross-end] of
3817   //      all last baseline-aligned items with no cross-axis auto margins...
3818   // and
3819   //  (c) largest cross-size of all other children.
3820   mLineCrossSize = std::max(
3821       std::max(
3822           crossStartToFurthestFirstBaseline + crossEndToFurthestFirstBaseline,
3823           crossStartToFurthestLastBaseline + crossEndToFurthestLastBaseline),
3824       largestOuterCrossSize);
3825 }
3826 
ResolveStretchedCrossSize(nscoord aLineCrossSize)3827 void FlexItem::ResolveStretchedCrossSize(nscoord aLineCrossSize) {
3828   // We stretch IFF we are align-self:stretch, have no auto margins in
3829   // cross axis, and have cross-axis size property == "auto". If any of those
3830   // conditions don't hold up, we won't stretch.
3831   if (mAlignSelf._0 != StyleAlignFlags::STRETCH ||
3832       NumAutoMarginsInCrossAxis() != 0 || !IsCrossSizeAuto()) {
3833     return;
3834   }
3835 
3836   // If we've already been stretched, we can bail out early, too.
3837   // No need to redo the calculation.
3838   if (mIsStretched) {
3839     return;
3840   }
3841 
3842   // Reserve space for margins & border & padding, and then use whatever
3843   // remains as our item's cross-size (clamped to its min/max range).
3844   nscoord stretchedSize = aLineCrossSize - MarginBorderPaddingSizeInCrossAxis();
3845 
3846   stretchedSize = NS_CSS_MINMAX(stretchedSize, mCrossMinSize, mCrossMaxSize);
3847 
3848   // Update the cross-size & make a note that it's stretched, so we know to
3849   // override the reflow input's computed cross-size in our final reflow.
3850   SetCrossSize(stretchedSize);
3851   mIsStretched = true;
3852 }
3853 
FindFlexItemBlockFrame(nsIFrame * aFrame)3854 static nsBlockFrame* FindFlexItemBlockFrame(nsIFrame* aFrame) {
3855   if (nsBlockFrame* block = do_QueryFrame(aFrame)) {
3856     return block;
3857   }
3858   for (nsIFrame* f : aFrame->PrincipalChildList()) {
3859     if (nsBlockFrame* block = FindFlexItemBlockFrame(f)) {
3860       return block;
3861     }
3862   }
3863   return nullptr;
3864 }
3865 
BlockFrame() const3866 nsBlockFrame* FlexItem::BlockFrame() const {
3867   return FindFlexItemBlockFrame(Frame());
3868 }
3869 
ResolveAutoMarginsInCrossAxis(const FlexLine & aLine,FlexItem & aItem)3870 void SingleLineCrossAxisPositionTracker::ResolveAutoMarginsInCrossAxis(
3871     const FlexLine& aLine, FlexItem& aItem) {
3872   // Subtract the space that our item is already occupying, to see how much
3873   // space (if any) is available for its auto margins.
3874   nscoord spaceForAutoMargins = aLine.LineCrossSize() - aItem.OuterCrossSize();
3875 
3876   if (spaceForAutoMargins <= 0) {
3877     return;  // No available space  --> nothing to do
3878   }
3879 
3880   uint32_t numAutoMargins = aItem.NumAutoMarginsInCrossAxis();
3881   if (numAutoMargins == 0) {
3882     return;  // No auto margins --> nothing to do.
3883   }
3884 
3885   // OK, we have at least one auto margin and we have some available space.
3886   // Give each auto margin a share of the space.
3887   const auto& styleMargin = aItem.Frame()->StyleMargin()->mMargin;
3888   for (const auto side : {StartSide(), EndSide()}) {
3889     if (styleMargin.Get(mWM, side).IsAuto()) {
3890       MOZ_ASSERT(aItem.GetMarginComponentForSide(side) == 0,
3891                  "Expecting auto margins to have value '0' before we "
3892                  "update them");
3893 
3894       // NOTE: integer divison is fine here; numAutoMargins is either 1 or 2.
3895       // If it's 2 & spaceForAutoMargins is odd, 1st margin gets smaller half.
3896       nscoord curAutoMarginSize = spaceForAutoMargins / numAutoMargins;
3897       aItem.SetMarginComponentForSide(side, curAutoMarginSize);
3898       numAutoMargins--;
3899       spaceForAutoMargins -= curAutoMarginSize;
3900     }
3901   }
3902 }
3903 
EnterAlignPackingSpace(const FlexLine & aLine,const FlexItem & aItem,const FlexboxAxisTracker & aAxisTracker)3904 void SingleLineCrossAxisPositionTracker::EnterAlignPackingSpace(
3905     const FlexLine& aLine, const FlexItem& aItem,
3906     const FlexboxAxisTracker& aAxisTracker) {
3907   // We don't do align-self alignment on items that have auto margins
3908   // in the cross axis.
3909   if (aItem.NumAutoMarginsInCrossAxis()) {
3910     return;
3911   }
3912 
3913   StyleAlignFlags alignSelf = aItem.AlignSelf()._0;
3914   // NOTE: 'stretch' behaves like 'flex-start' once we've stretched any
3915   // auto-sized items (which we've already done).
3916   if (alignSelf == StyleAlignFlags::STRETCH) {
3917     alignSelf = StyleAlignFlags::FLEX_START;
3918   }
3919 
3920   // Map 'self-start'/'self-end' to 'start'/'end'
3921   if (alignSelf == StyleAlignFlags::SELF_START ||
3922       alignSelf == StyleAlignFlags::SELF_END) {
3923     const LogicalAxis logCrossAxis =
3924         aAxisTracker.IsRowOriented() ? eLogicalAxisBlock : eLogicalAxisInline;
3925     const WritingMode cWM = aAxisTracker.GetWritingMode();
3926     const bool sameStart =
3927         cWM.ParallelAxisStartsOnSameSide(logCrossAxis, aItem.GetWritingMode());
3928     alignSelf = sameStart == (alignSelf == StyleAlignFlags::SELF_START)
3929                     ? StyleAlignFlags::START
3930                     : StyleAlignFlags::END;
3931   }
3932 
3933   // Map 'start'/'end' to 'flex-start'/'flex-end'.
3934   if (alignSelf == StyleAlignFlags::START) {
3935     alignSelf = aAxisTracker.IsCrossAxisReversed()
3936                     ? StyleAlignFlags::FLEX_END
3937                     : StyleAlignFlags::FLEX_START;
3938   } else if (alignSelf == StyleAlignFlags::END) {
3939     alignSelf = aAxisTracker.IsCrossAxisReversed() ? StyleAlignFlags::FLEX_START
3940                                                    : StyleAlignFlags::FLEX_END;
3941   }
3942 
3943   // 'align-self' falls back to 'flex-start' if it is 'center'/'flex-end' and we
3944   // have cross axis overflow
3945   // XXX we should really be falling back to 'start' as of bug 1472843
3946   if (aLine.LineCrossSize() < aItem.OuterCrossSize() &&
3947       (aItem.AlignSelfFlags() & StyleAlignFlags::SAFE)) {
3948     alignSelf = StyleAlignFlags::FLEX_START;
3949   }
3950 
3951   if (alignSelf == StyleAlignFlags::FLEX_START) {
3952     // No space to skip over -- we're done.
3953   } else if (alignSelf == StyleAlignFlags::FLEX_END) {
3954     mPosition += aLine.LineCrossSize() - aItem.OuterCrossSize();
3955   } else if (alignSelf == StyleAlignFlags::CENTER) {
3956     // Note: If cross-size is odd, the "after" space will get the extra unit.
3957     mPosition += (aLine.LineCrossSize() - aItem.OuterCrossSize()) / 2;
3958   } else if (alignSelf == StyleAlignFlags::BASELINE ||
3959              alignSelf == StyleAlignFlags::LAST_BASELINE) {
3960     const bool useFirst = (alignSelf == StyleAlignFlags::BASELINE);
3961 
3962     // Baseline-aligned items are collectively aligned with the line's physical
3963     // cross-start or cross-end side, depending on whether we're doing
3964     // first-baseline or last-baseline alignment.
3965     const mozilla::Side baselineAlignStartSide =
3966         useFirst ? aAxisTracker.CrossAxisPhysicalStartSide()
3967                  : aAxisTracker.CrossAxisPhysicalEndSide();
3968 
3969     nscoord itemBaselineOffset = aItem.BaselineOffsetFromOuterCrossEdge(
3970         baselineAlignStartSide, useFirst);
3971 
3972     nscoord lineBaselineOffset =
3973         useFirst ? aLine.FirstBaselineOffset() : aLine.LastBaselineOffset();
3974 
3975     NS_ASSERTION(lineBaselineOffset >= itemBaselineOffset,
3976                  "failed at finding largest baseline offset");
3977 
3978     // How much do we need to adjust our position (from the line edge),
3979     // to get the item's baseline to hit the line's baseline offset:
3980     nscoord baselineDiff = lineBaselineOffset - itemBaselineOffset;
3981 
3982     if (useFirst) {
3983       // mPosition is already at line's flex-start edge.
3984       // From there, we step *forward* by the baseline adjustment:
3985       mPosition += baselineDiff;
3986     } else {
3987       // Advance to align item w/ line's flex-end edge (as in FLEX_END case):
3988       mPosition += aLine.LineCrossSize() - aItem.OuterCrossSize();
3989       // ...and step *back* by the baseline adjustment:
3990       mPosition -= baselineDiff;
3991     }
3992   } else {
3993     MOZ_ASSERT_UNREACHABLE("Unexpected align-self value");
3994   }
3995 }
3996 
FlexboxAxisInfo(const nsIFrame * aFlexContainer)3997 FlexboxAxisInfo::FlexboxAxisInfo(const nsIFrame* aFlexContainer) {
3998   MOZ_ASSERT(aFlexContainer && aFlexContainer->IsFlexContainerFrame(),
3999              "Only flex containers may be passed to this constructor!");
4000   if (IsLegacyBox(aFlexContainer)) {
4001     InitAxesFromLegacyProps(aFlexContainer);
4002   } else {
4003     InitAxesFromModernProps(aFlexContainer);
4004   }
4005 }
4006 
InitAxesFromLegacyProps(const nsIFrame * aFlexContainer)4007 void FlexboxAxisInfo::InitAxesFromLegacyProps(const nsIFrame* aFlexContainer) {
4008   const nsStyleXUL* styleXUL = aFlexContainer->StyleXUL();
4009 
4010   const bool boxOrientIsVertical =
4011       (styleXUL->mBoxOrient == StyleBoxOrient::Vertical);
4012   const bool wmIsVertical = aFlexContainer->GetWritingMode().IsVertical();
4013 
4014   // If box-orient agrees with our writing-mode, then we're "row-oriented"
4015   // (i.e. the flexbox main axis is the same as our writing mode's inline
4016   // direction).  Otherwise, we're column-oriented (i.e. the flexbox's main
4017   // axis is perpendicular to the writing-mode's inline direction).
4018   mIsRowOriented = (boxOrientIsVertical == wmIsVertical);
4019 
4020   // Legacy flexbox can use "-webkit-box-direction: reverse" to reverse the
4021   // main axis (so it runs in the reverse direction of the inline axis):
4022   mIsMainAxisReversed = styleXUL->mBoxDirection == StyleBoxDirection::Reverse;
4023 
4024   // Legacy flexbox does not support reversing the cross axis -- it has no
4025   // equivalent of modern flexbox's "flex-wrap: wrap-reverse".
4026   mIsCrossAxisReversed = false;
4027 }
4028 
InitAxesFromModernProps(const nsIFrame * aFlexContainer)4029 void FlexboxAxisInfo::InitAxesFromModernProps(const nsIFrame* aFlexContainer) {
4030   const nsStylePosition* stylePos = aFlexContainer->StylePosition();
4031   StyleFlexDirection flexDirection = stylePos->mFlexDirection;
4032 
4033   // Determine main axis:
4034   switch (flexDirection) {
4035     case StyleFlexDirection::Row:
4036       mIsRowOriented = true;
4037       mIsMainAxisReversed = false;
4038       break;
4039     case StyleFlexDirection::RowReverse:
4040       mIsRowOriented = true;
4041       mIsMainAxisReversed = true;
4042       break;
4043     case StyleFlexDirection::Column:
4044       mIsRowOriented = false;
4045       mIsMainAxisReversed = false;
4046       break;
4047     case StyleFlexDirection::ColumnReverse:
4048       mIsRowOriented = false;
4049       mIsMainAxisReversed = true;
4050       break;
4051   }
4052 
4053   // "flex-wrap: wrap-reverse" reverses our cross axis.
4054   mIsCrossAxisReversed = stylePos->mFlexWrap == StyleFlexWrap::WrapReverse;
4055 }
4056 
FlexboxAxisTracker(const nsFlexContainerFrame * aFlexContainer)4057 FlexboxAxisTracker::FlexboxAxisTracker(
4058     const nsFlexContainerFrame* aFlexContainer)
4059     : mWM(aFlexContainer->GetWritingMode()), mAxisInfo(aFlexContainer) {}
4060 
MainAxisStartSide() const4061 LogicalSide FlexboxAxisTracker::MainAxisStartSide() const {
4062   return MakeLogicalSide(
4063       MainAxis(), IsMainAxisReversed() ? eLogicalEdgeEnd : eLogicalEdgeStart);
4064 }
4065 
CrossAxisStartSide() const4066 LogicalSide FlexboxAxisTracker::CrossAxisStartSide() const {
4067   return MakeLogicalSide(
4068       CrossAxis(), IsCrossAxisReversed() ? eLogicalEdgeEnd : eLogicalEdgeStart);
4069 }
4070 
ShouldUseMozBoxCollapseBehavior(const nsStyleDisplay * aFlexStyleDisp)4071 bool nsFlexContainerFrame::ShouldUseMozBoxCollapseBehavior(
4072     const nsStyleDisplay* aFlexStyleDisp) {
4073   MOZ_ASSERT(StyleDisplay() == aFlexStyleDisp, "wrong StyleDisplay passed in");
4074 
4075   // Quick filter to screen out *actual* (not-coopted-for-emulation)
4076   // flex containers, using state bit:
4077   if (!IsLegacyBox(this)) {
4078     return false;
4079   }
4080 
4081   // Check our own display value:
4082   if (aFlexStyleDisp->mDisplay == mozilla::StyleDisplay::MozBox ||
4083       aFlexStyleDisp->mDisplay == mozilla::StyleDisplay::MozInlineBox) {
4084     return true;
4085   }
4086 
4087   // Check our parent's display value, if we're an anonymous box (with a
4088   // potentially-untrustworthy display value):
4089   auto pseudoType = Style()->GetPseudoType();
4090   if (pseudoType == PseudoStyleType::scrolledContent ||
4091       pseudoType == PseudoStyleType::buttonContent) {
4092     const nsStyleDisplay* disp = GetParent()->StyleDisplay();
4093     if (disp->mDisplay == mozilla::StyleDisplay::MozBox ||
4094         disp->mDisplay == mozilla::StyleDisplay::MozInlineBox) {
4095       return true;
4096     }
4097   }
4098 
4099   return false;
4100 }
4101 
GenerateFlexLines(const ReflowInput & aReflowInput,const nscoord aTentativeContentBoxMainSize,const nscoord aTentativeContentBoxCrossSize,const nsTArray<StrutInfo> & aStruts,const FlexboxAxisTracker & aAxisTracker,nscoord aMainGapSize,bool aHasLineClampEllipsis,nsTArray<nsIFrame * > & aPlaceholders,nsTArray<FlexLine> & aLines)4102 void nsFlexContainerFrame::GenerateFlexLines(
4103     const ReflowInput& aReflowInput, const nscoord aTentativeContentBoxMainSize,
4104     const nscoord aTentativeContentBoxCrossSize,
4105     const nsTArray<StrutInfo>& aStruts, const FlexboxAxisTracker& aAxisTracker,
4106     nscoord aMainGapSize, bool aHasLineClampEllipsis,
4107     nsTArray<nsIFrame*>& aPlaceholders, /* out */
4108     nsTArray<FlexLine>& aLines /* out */) {
4109   MOZ_ASSERT(aLines.IsEmpty(), "Expecting outparam to start out empty");
4110 
4111   auto ConstructNewFlexLine = [&aLines, aMainGapSize]() {
4112     return aLines.EmplaceBack(aMainGapSize);
4113   };
4114 
4115   const bool isSingleLine =
4116       StyleFlexWrap::Nowrap == aReflowInput.mStylePosition->mFlexWrap;
4117 
4118   // We have at least one FlexLine. Even an empty flex container has a single
4119   // (empty) flex line.
4120   FlexLine* curLine = ConstructNewFlexLine();
4121 
4122   nscoord wrapThreshold;
4123   if (isSingleLine) {
4124     // Not wrapping. Set threshold to sentinel value that tells us not to wrap.
4125     wrapThreshold = NS_UNCONSTRAINEDSIZE;
4126   } else {
4127     // Wrapping! Set wrap threshold to flex container's content-box main-size.
4128     wrapThreshold = aTentativeContentBoxMainSize;
4129 
4130     // If the flex container doesn't have a definite content-box main-size
4131     // (e.g. if main axis is vertical & 'height' is 'auto'), make sure we at
4132     // least wrap when we hit its max main-size.
4133     if (wrapThreshold == NS_UNCONSTRAINEDSIZE) {
4134       const nscoord flexContainerMaxMainSize =
4135           aAxisTracker.MainComponent(aReflowInput.ComputedMaxSize());
4136       wrapThreshold = flexContainerMaxMainSize;
4137     }
4138   }
4139 
4140   // Tracks the index of the next strut, in aStruts (and when this hits
4141   // aStruts.Length(), that means there are no more struts):
4142   uint32_t nextStrutIdx = 0;
4143 
4144   // Overall index of the current flex item in the flex container. (This gets
4145   // checked against entries in aStruts.)
4146   uint32_t itemIdxInContainer = 0;
4147 
4148   CSSOrderAwareFrameIterator iter(
4149       this, kPrincipalList, CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
4150       CSSOrderAwareFrameIterator::OrderState::Unknown,
4151       OrderingPropertyForIter(this));
4152 
4153   AddOrRemoveStateBits(NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER,
4154                        iter.ItemsAreAlreadyInOrder());
4155 
4156   bool prevItemRequestedBreakAfter = false;
4157   const bool useMozBoxCollapseBehavior =
4158       ShouldUseMozBoxCollapseBehavior(aReflowInput.mStyleDisplay);
4159 
4160   for (; !iter.AtEnd(); iter.Next()) {
4161     nsIFrame* childFrame = *iter;
4162     // Don't create flex items / lines for placeholder frames:
4163     if (childFrame->IsPlaceholderFrame()) {
4164       aPlaceholders.AppendElement(childFrame);
4165       continue;
4166     }
4167 
4168     // If we're multi-line and current line isn't empty, create a new flex line
4169     // to satisfy the previous flex item's request or to honor "break-before".
4170     if (!isSingleLine && !curLine->IsEmpty() &&
4171         (prevItemRequestedBreakAfter ||
4172          childFrame->StyleDisplay()->BreakBefore())) {
4173       curLine = ConstructNewFlexLine();
4174       prevItemRequestedBreakAfter = false;
4175     }
4176 
4177     if (useMozBoxCollapseBehavior &&
4178         (StyleVisibility::Collapse ==
4179          childFrame->StyleVisibility()->mVisible)) {
4180       // Legacy visibility:collapse behavior: make a 0-sized strut. (No need to
4181       // bother with aStruts and remembering cross size.)
4182       curLine->Items().EmplaceBack(childFrame, 0, aReflowInput.GetWritingMode(),
4183                                    aAxisTracker);
4184     } else if (nextStrutIdx < aStruts.Length() &&
4185                aStruts[nextStrutIdx].mItemIdx == itemIdxInContainer) {
4186       // Use the simplified "strut" FlexItem constructor:
4187       curLine->Items().EmplaceBack(childFrame,
4188                                    aStruts[nextStrutIdx].mStrutCrossSize,
4189                                    aReflowInput.GetWritingMode(), aAxisTracker);
4190       nextStrutIdx++;
4191     } else {
4192       GenerateFlexItemForChild(*curLine, childFrame, aReflowInput, aAxisTracker,
4193                                aTentativeContentBoxCrossSize,
4194                                aHasLineClampEllipsis);
4195     }
4196 
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();
4208 
4209       // Account for gap between this line's previous item and this item.
4210       newOuterSize += aMainGapSize;
4211 
4212       if (newOuterSize >= nscoord_MAX || newOuterSize > wrapThreshold) {
4213         curLine = ConstructNewFlexLine();
4214 
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];
4218 
4219         // Move the item from the end of prevLine to the end of curLine.
4220         curLine->Items().AppendElement(prevLine.Items().PopLastElement());
4221       }
4222     }
4223 
4224     // Update the line's bookkeeping about how large its items collectively are.
4225     curLine->AddLastItemToMainSizeTotals();
4226 
4227     // Honor "break-after" if we're multi-line. If we have more children, we
4228     // will create a new flex line in the next iteration.
4229     if (!isSingleLine && childFrame->StyleDisplay()->BreakAfter()) {
4230       prevItemRequestedBreakAfter = true;
4231     }
4232     itemIdxInContainer++;
4233   }
4234 }
4235 
4236 nsFlexContainerFrame::FlexLayoutResult
GenerateFlexLayoutResult()4237 nsFlexContainerFrame::GenerateFlexLayoutResult() {
4238   MOZ_ASSERT(GetPrevInFlow(), "This should be called by non-first-in-flows!");
4239 
4240   auto* data = FirstInFlow()->GetProperty(SharedFlexData::Prop());
4241   MOZ_ASSERT(data, "SharedFlexData should be set by our first-in-flow!");
4242 
4243   FlexLayoutResult flr;
4244 
4245   // Pretend we have only one line and zero main gap size.
4246   flr.mLines.AppendElement(FlexLine(0));
4247 
4248   // The order state of the children is consistent across entire continuation
4249   // chain due to calling nsContainerFrame::NormalizeChildLists() at the
4250   // beginning of Reflow(), so we can align our state bit with our
4251   // prev-in-flow's state. Setup here before calling OrderStateForIter() below.
4252   AddOrRemoveStateBits(NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER,
4253                        GetPrevInFlow()->HasAnyStateBits(
4254                            NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER));
4255 
4256   // Construct flex items for this flex container fragment from existing flex
4257   // items in SharedFlexData.
4258   CSSOrderAwareFrameIterator iter(
4259       this, kPrincipalList,
4260       CSSOrderAwareFrameIterator::ChildFilter::SkipPlaceholders,
4261       OrderStateForIter(this), OrderingPropertyForIter(this));
4262 
4263   FlexItemIterator itemIter(data->mLines);
4264 
4265   for (; !iter.AtEnd(); iter.Next()) {
4266     nsIFrame* const child = *iter;
4267     nsIFrame* const childFirstInFlow = child->FirstInFlow();
4268 
4269     MOZ_ASSERT(!itemIter.AtEnd(),
4270                "Why can't we find FlexItem for our child frame?");
4271 
4272     for (; !itemIter.AtEnd(); itemIter.Next()) {
4273       if (itemIter->Frame() == childFirstInFlow) {
4274         flr.mLines[0].Items().AppendElement(itemIter->CloneFor(child));
4275         itemIter.Next();
4276         break;
4277       }
4278     }
4279   }
4280 
4281   flr.mContentBoxMainSize = data->mContentBoxMainSize;
4282   flr.mContentBoxCrossSize = data->mContentBoxCrossSize;
4283 
4284   return flr;
4285 }
4286 
4287 // Returns the largest outer hypothetical main-size of any line in |aLines|.
4288 // (i.e. the hypothetical main-size of the largest line)
GetLargestLineMainSize(nsTArray<FlexLine> & aLines)4289 static AuCoord64 GetLargestLineMainSize(nsTArray<FlexLine>& aLines) {
4290   AuCoord64 largestLineOuterSize = 0;
4291   for (const FlexLine& line : aLines) {
4292     largestLineOuterSize =
4293         std::max(largestLineOuterSize, line.TotalOuterHypotheticalMainSize());
4294   }
4295   return largestLineOuterSize;
4296 }
4297 
ComputeMainSize(const ReflowInput & aReflowInput,const FlexboxAxisTracker & aAxisTracker,const nscoord aTentativeContentBoxMainSize,nsTArray<FlexLine> & aLines) const4298 nscoord nsFlexContainerFrame::ComputeMainSize(
4299     const ReflowInput& aReflowInput, const FlexboxAxisTracker& aAxisTracker,
4300     const nscoord aTentativeContentBoxMainSize,
4301     nsTArray<FlexLine>& aLines) const {
4302   if (aAxisTracker.IsRowOriented()) {
4303     // Row-oriented --> our main axis is the inline axis, so our main size
4304     // is our inline size (which should already be resolved).
4305     return aTentativeContentBoxMainSize;
4306   }
4307 
4308   if (aTentativeContentBoxMainSize != NS_UNCONSTRAINEDSIZE) {
4309     // Column-oriented case, with fixed BSize:
4310     // Just use our fixed block-size because we always assume the available
4311     // block-size is unconstrained, and the reflow input has already done the
4312     // appropriate min/max-BSize clamping.
4313     return aTentativeContentBoxMainSize;
4314   }
4315 
4316   // Column-oriented case, with size-containment:
4317   // Behave as if we had no content and just use our MinBSize.
4318   if (aReflowInput.mStyleDisplay->IsContainSize()) {
4319     return aReflowInput.ComputedMinBSize();
4320   }
4321 
4322   // Column-oriented case, with auto BSize:
4323   // Resolve auto BSize to the largest FlexLine length, clamped to our
4324   // computed min/max main-size properties.
4325   const AuCoord64 largestLineMainSize = GetLargestLineMainSize(aLines);
4326   return NS_CSS_MINMAX(nscoord(largestLineMainSize.ToMinMaxClamped()),
4327                        aReflowInput.ComputedMinBSize(),
4328                        aReflowInput.ComputedMaxBSize());
4329 }
4330 
ComputeCrossSize(const ReflowInput & aReflowInput,const FlexboxAxisTracker & aAxisTracker,const nscoord aTentativeContentBoxCrossSize,nscoord aSumLineCrossSizes,bool * aIsDefinite) const4331 nscoord nsFlexContainerFrame::ComputeCrossSize(
4332     const ReflowInput& aReflowInput, const FlexboxAxisTracker& aAxisTracker,
4333     const nscoord aTentativeContentBoxCrossSize, nscoord aSumLineCrossSizes,
4334     bool* aIsDefinite) const {
4335   MOZ_ASSERT(aIsDefinite, "outparam pointer must be non-null");
4336 
4337   if (aAxisTracker.IsColumnOriented()) {
4338     // Column-oriented --> our cross axis is the inline axis, so our cross size
4339     // is our inline size (which should already be resolved).
4340     *aIsDefinite = true;
4341     // FIXME: Bug 1661847 - there are cases where aTentativeContentBoxCrossSize
4342     // (i.e. aReflowInput.ComputedISize()) might not be the right thing to
4343     // return here. Specifically: if our cross size is an intrinsic size, and we
4344     // have flex items that are flexible and have aspect ratios, then we may
4345     // need to take their post-flexing main sizes into account (multiplied
4346     // through their aspect ratios to get their cross sizes), in order to
4347     // determine their flex line's size & the flex container's cross size (e.g.
4348     // as `aSumLineCrossSizes`).
4349     return aTentativeContentBoxCrossSize;
4350   }
4351 
4352   const nscoord computedBSize = aReflowInput.ComputedBSize();
4353   if (computedBSize != NS_UNCONSTRAINEDSIZE) {
4354     // Row-oriented case (cross axis is block-axis), with fixed BSize:
4355     *aIsDefinite = true;
4356 
4357     // Just use our fixed block-size because we always assume the available
4358     // block-size is unconstrained, and the reflow input has already done the
4359     // appropriate min/max-BSize clamping.
4360     return computedBSize;
4361   }
4362 
4363   // Row-oriented case, with size-containment:
4364   // Behave as if we had no content and just use our MinBSize.
4365   if (aReflowInput.mStyleDisplay->IsContainSize()) {
4366     *aIsDefinite = true;
4367     return aReflowInput.ComputedMinBSize();
4368   }
4369 
4370   // Row-oriented case (cross axis is block axis), with auto BSize:
4371   // Shrink-wrap our line(s), subject to our min-size / max-size
4372   // constraints in that (block) axis.
4373   *aIsDefinite = false;
4374   return NS_CSS_MINMAX(aSumLineCrossSizes, aReflowInput.ComputedMinBSize(),
4375                        aReflowInput.ComputedMaxBSize());
4376 }
4377 
ComputeAvailableSizeForItems(const ReflowInput & aReflowInput,const mozilla::LogicalMargin & aBorderPadding) const4378 LogicalSize nsFlexContainerFrame::ComputeAvailableSizeForItems(
4379     const ReflowInput& aReflowInput,
4380     const mozilla::LogicalMargin& aBorderPadding) const {
4381   const WritingMode wm = GetWritingMode();
4382   nscoord availableBSize = aReflowInput.AvailableBSize();
4383 
4384   if (availableBSize != NS_UNCONSTRAINEDSIZE) {
4385     // Available block-size is constrained. Subtract block-start border and
4386     // padding from it.
4387     availableBSize -= aBorderPadding.BStart(wm);
4388 
4389     if (aReflowInput.mStyleBorder->mBoxDecorationBreak ==
4390         StyleBoxDecorationBreak::Clone) {
4391       // We have box-decoration-break:clone. Subtract block-end border and
4392       // padding from the available block-size as well.
4393       availableBSize -= aBorderPadding.BEnd(wm);
4394     }
4395 
4396     // Available block-size can became negative after subtracting block-axis
4397     // border and padding. Per spec, to guarantee progress, fragmentainers are
4398     // assumed to have a minimum block size of 1px regardless of their used
4399     // size. https://drafts.csswg.org/css-break/#breaking-rules
4400     availableBSize =
4401         std::max(nsPresContext::CSSPixelsToAppUnits(1), availableBSize);
4402   }
4403 
4404   return LogicalSize(wm, aReflowInput.ComputedISize(), availableBSize);
4405 }
4406 
PositionItemsInMainAxis(const StyleContentDistribution & aJustifyContent,nscoord aContentBoxMainSize,const FlexboxAxisTracker & aAxisTracker)4407 void FlexLine::PositionItemsInMainAxis(
4408     const StyleContentDistribution& aJustifyContent,
4409     nscoord aContentBoxMainSize, const FlexboxAxisTracker& aAxisTracker) {
4410   MainAxisPositionTracker mainAxisPosnTracker(
4411       aAxisTracker, this, aJustifyContent, aContentBoxMainSize);
4412   for (FlexItem& item : Items()) {
4413     nscoord itemMainBorderBoxSize =
4414         item.MainSize() + item.BorderPaddingSizeInMainAxis();
4415 
4416     // Resolve any main-axis 'auto' margins on aChild to an actual value.
4417     mainAxisPosnTracker.ResolveAutoMarginsInMainAxis(item);
4418 
4419     // Advance our position tracker to child's upper-left content-box corner,
4420     // and use that as its position in the main axis.
4421     mainAxisPosnTracker.EnterMargin(item.Margin());
4422     mainAxisPosnTracker.EnterChildFrame(itemMainBorderBoxSize);
4423 
4424     item.SetMainPosition(mainAxisPosnTracker.Position());
4425 
4426     mainAxisPosnTracker.ExitChildFrame(itemMainBorderBoxSize);
4427     mainAxisPosnTracker.ExitMargin(item.Margin());
4428     mainAxisPosnTracker.TraversePackingSpace();
4429     if (&item != &Items().LastElement()) {
4430       mainAxisPosnTracker.TraverseGap(mMainGapSize);
4431     }
4432   }
4433 }
4434 
4435 /**
4436  * Given the flex container's "flex-relative ascent" (i.e. distance from the
4437  * flex container's content-box cross-start edge to its baseline), returns
4438  * its actual physical ascent value (the distance from the *border-box* top
4439  * edge to its baseline).
4440  */
ComputePhysicalAscentFromFlexRelativeAscent(nscoord aFlexRelativeAscent,nscoord aContentBoxCrossSize,const ReflowInput & aReflowInput,const FlexboxAxisTracker & aAxisTracker)4441 static nscoord ComputePhysicalAscentFromFlexRelativeAscent(
4442     nscoord aFlexRelativeAscent, nscoord aContentBoxCrossSize,
4443     const ReflowInput& aReflowInput, const FlexboxAxisTracker& aAxisTracker) {
4444   return aReflowInput.ComputedPhysicalBorderPadding().top +
4445          PhysicalCoordFromFlexRelativeCoord(
4446              aFlexRelativeAscent, aContentBoxCrossSize,
4447              aAxisTracker.CrossAxisPhysicalStartSide());
4448 }
4449 
SizeItemInCrossAxis(ReflowInput & aChildReflowInput,FlexItem & aItem)4450 void nsFlexContainerFrame::SizeItemInCrossAxis(ReflowInput& aChildReflowInput,
4451                                                FlexItem& aItem) {
4452   // If cross axis is the item's inline axis, just use ISize from reflow input,
4453   // and don't bother with a full reflow.
4454   if (aItem.IsInlineAxisCrossAxis()) {
4455     aItem.SetCrossSize(aChildReflowInput.ComputedISize());
4456     return;
4457   }
4458 
4459   MOZ_ASSERT(!aItem.HadMeasuringReflow(),
4460              "We shouldn't need more than one measuring reflow");
4461 
4462   if (aItem.AlignSelf()._0 == StyleAlignFlags::STRETCH) {
4463     // This item's got "align-self: stretch", so we probably imposed a
4464     // stretched computed cross-size on it during its previous
4465     // reflow. We're not imposing that BSize for *this* "measuring" reflow, so
4466     // we need to tell it to treat this reflow as a resize in its block axis
4467     // (regardless of whether any of its ancestors are actually being resized).
4468     // (Note: we know that the cross axis is the item's *block* axis -- if it
4469     // weren't, then we would've taken the early-return above.)
4470     aChildReflowInput.SetBResize(true);
4471     // Not 100% sure this is needed, but be conservative for now:
4472     aChildReflowInput.mFlags.mIsBResizeForPercentages = true;
4473   }
4474 
4475   // Potentially reflow the item, and get the sizing info.
4476   const CachedBAxisMeasurement& measurement =
4477       MeasureBSizeForFlexItem(aItem, aChildReflowInput);
4478 
4479   // Save the sizing info that we learned from this reflow
4480   // -----------------------------------------------------
4481 
4482   // Tentatively store the child's desired content-box cross-size.
4483   aItem.SetCrossSize(measurement.BSize());
4484 }
4485 
PositionItemsInCrossAxis(nscoord aLineStartPosition,const FlexboxAxisTracker & aAxisTracker)4486 void FlexLine::PositionItemsInCrossAxis(
4487     nscoord aLineStartPosition, const FlexboxAxisTracker& aAxisTracker) {
4488   SingleLineCrossAxisPositionTracker lineCrossAxisPosnTracker(aAxisTracker);
4489 
4490   for (FlexItem& item : Items()) {
4491     // First, stretch the item's cross size (if appropriate), and resolve any
4492     // auto margins in this axis.
4493     item.ResolveStretchedCrossSize(mLineCrossSize);
4494     lineCrossAxisPosnTracker.ResolveAutoMarginsInCrossAxis(*this, item);
4495 
4496     // Compute the cross-axis position of this item
4497     nscoord itemCrossBorderBoxSize =
4498         item.CrossSize() + item.BorderPaddingSizeInCrossAxis();
4499     lineCrossAxisPosnTracker.EnterAlignPackingSpace(*this, item, aAxisTracker);
4500     lineCrossAxisPosnTracker.EnterMargin(item.Margin());
4501     lineCrossAxisPosnTracker.EnterChildFrame(itemCrossBorderBoxSize);
4502 
4503     item.SetCrossPosition(aLineStartPosition +
4504                           lineCrossAxisPosnTracker.Position());
4505 
4506     // Back out to cross-axis edge of the line.
4507     lineCrossAxisPosnTracker.ResetPosition();
4508   }
4509 }
4510 
Reflow(nsPresContext * aPresContext,ReflowOutput & aReflowOutput,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)4511 void nsFlexContainerFrame::Reflow(nsPresContext* aPresContext,
4512                                   ReflowOutput& aReflowOutput,
4513                                   const ReflowInput& aReflowInput,
4514                                   nsReflowStatus& aStatus) {
4515   MarkInReflow();
4516   DO_GLOBAL_REFLOW_COUNT("nsFlexContainerFrame");
4517   DISPLAY_REFLOW(aPresContext, this, aReflowInput, aReflowOutput, aStatus);
4518   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
4519   MOZ_ASSERT(aPresContext == PresContext());
4520   NS_WARNING_ASSERTION(
4521       aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE,
4522       "Unconstrained inline size; this should only result from huge sizes "
4523       "(not intrinsic sizing w/ orthogonal flows)");
4524 
4525   FLEX_LOG("Reflow() for nsFlexContainerFrame %p", this);
4526 
4527   if (IsFrameTreeTooDeep(aReflowInput, aReflowOutput, aStatus)) {
4528     return;
4529   }
4530 
4531   NormalizeChildLists();
4532 
4533 #ifdef DEBUG
4534   mDidPushItemsBitMayLie = false;
4535   SanityCheckChildListsBeforeReflow();
4536 #endif  // DEBUG
4537 
4538   // We (and our children) can only depend on our ancestor's bsize if we have
4539   // a percent-bsize, or if we're positioned and we have "block-start" and
4540   // "block-end" set and have block-size:auto.  (There are actually other cases,
4541   // too -- e.g. if our parent is itself a block-dir flex container and we're
4542   // flexible -- but we'll let our ancestors handle those sorts of cases.)
4543   //
4544   // TODO(emilio): the !bsize.IsLengthPercentage() preserves behavior, but it's
4545   // too conservative. min/max-content don't really depend on the container.
4546   WritingMode wm = aReflowInput.GetWritingMode();
4547   const nsStylePosition* stylePos = StylePosition();
4548   const auto& bsize = stylePos->BSize(wm);
4549   if (bsize.HasPercent() || (StyleDisplay()->IsAbsolutelyPositionedStyle() &&
4550                              (bsize.IsAuto() || !bsize.IsLengthPercentage()) &&
4551                              !stylePos->mOffset.GetBStart(wm).IsAuto() &&
4552                              !stylePos->mOffset.GetBEnd(wm).IsAuto())) {
4553     AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
4554   }
4555 
4556   // Check if there is a -webkit-line-clamp ellipsis somewhere inside at least
4557   // one of the flex items, so we can clear the flag before the block frame
4558   // re-sets it on the appropriate line during its bsize measuring reflow.
4559   bool hasLineClampEllipsis =
4560       HasAnyStateBits(NS_STATE_FLEX_HAS_LINE_CLAMP_ELLIPSIS);
4561   RemoveStateBits(NS_STATE_FLEX_HAS_LINE_CLAMP_ELLIPSIS);
4562 
4563   const FlexboxAxisTracker axisTracker(this);
4564 
4565   // Check to see if we need to create a computed info structure, to
4566   // be filled out for use by devtools.
4567   ComputedFlexContainerInfo* containerInfo = CreateOrClearFlexContainerInfo();
4568 
4569   FlexLayoutResult flr;
4570   if (!GetPrevInFlow()) {
4571     const LogicalSize tentativeContentBoxSize = aReflowInput.ComputedSize();
4572     const nscoord tentativeContentBoxMainSize =
4573         axisTracker.MainComponent(tentativeContentBoxSize);
4574     const nscoord tentativeContentBoxCrossSize =
4575         axisTracker.CrossComponent(tentativeContentBoxSize);
4576 
4577     // Calculate gap sizes for main and cross axis. We only need them in
4578     // DoFlexLayout in the first-in-flow, so no need to worry about consumed
4579     // block-size.
4580     const auto& mainGapStyle =
4581         axisTracker.IsRowOriented() ? stylePos->mColumnGap : stylePos->mRowGap;
4582     const auto& crossGapStyle =
4583         axisTracker.IsRowOriented() ? stylePos->mRowGap : stylePos->mColumnGap;
4584     const nscoord mainGapSize = nsLayoutUtils::ResolveGapToLength(
4585         mainGapStyle, tentativeContentBoxMainSize);
4586     const nscoord crossGapSize = nsLayoutUtils::ResolveGapToLength(
4587         crossGapStyle, tentativeContentBoxCrossSize);
4588 
4589     // When fragmenting a flex container, we run the flex algorithm without
4590     // regards to pagination in order to compute the flex container's desired
4591     // content-box size. https://drafts.csswg.org/css-flexbox-1/#pagination-algo
4592     //
4593     // Note: For a multi-line column-oriented flex container, the sample
4594     // algorithm suggests we wrap the flex line at the block-end edge of a
4595     // column/page, but we do not implement it intentionally. This brings the
4596     // layout result closer to the one as if there's no fragmentation.
4597     AutoTArray<StrutInfo, 1> struts;
4598     flr =
4599         DoFlexLayout(aReflowInput, tentativeContentBoxMainSize,
4600                      tentativeContentBoxCrossSize, axisTracker, mainGapSize,
4601                      crossGapSize, hasLineClampEllipsis, struts, containerInfo);
4602 
4603     if (!struts.IsEmpty()) {
4604       // We're restarting flex layout, with new knowledge of collapsed items.
4605       flr.mLines.Clear();
4606       flr.mPlaceholders.Clear();
4607       flr = DoFlexLayout(aReflowInput, tentativeContentBoxMainSize,
4608                          tentativeContentBoxCrossSize, axisTracker, mainGapSize,
4609                          crossGapSize, hasLineClampEllipsis, struts,
4610                          containerInfo);
4611     }
4612   } else {
4613     flr = GenerateFlexLayoutResult();
4614   }
4615 
4616   const LogicalSize contentBoxSize =
4617       axisTracker.LogicalSizeFromFlexRelativeSizes(flr.mContentBoxMainSize,
4618                                                    flr.mContentBoxCrossSize);
4619   const nscoord consumedBSize = CalcAndCacheConsumedBSize();
4620   const nscoord effectiveContentBSize =
4621       contentBoxSize.BSize(wm) - consumedBSize;
4622   LogicalMargin borderPadding = aReflowInput.ComputedLogicalBorderPadding(wm);
4623   bool mayNeedNextInFlow = false;
4624   if (MOZ_UNLIKELY(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE)) {
4625     // We assume we are the last fragment by using
4626     // PreReflowBlockLevelLogicalSkipSides(), and skip block-end border and
4627     // padding if needed.
4628     borderPadding.ApplySkipSides(PreReflowBlockLevelLogicalSkipSides());
4629     // Check if we may need a next-in-flow. If so, we'll need to skip block-end
4630     // border and padding.
4631     const LogicalSize availableSizeForItems =
4632         ComputeAvailableSizeForItems(aReflowInput, borderPadding);
4633     mayNeedNextInFlow = effectiveContentBSize > availableSizeForItems.BSize(wm);
4634     if (mayNeedNextInFlow && aReflowInput.mStyleBorder->mBoxDecorationBreak ==
4635                                  StyleBoxDecorationBreak::Slice) {
4636       borderPadding.BEnd(wm) = 0;
4637     }
4638   }
4639 
4640   // Determine this frame's tentative border-box size. This is used for logical
4641   // to physical coordinate conversion when positioning children.
4642   //
4643   // Note that vertical-rl writing-mode is the only case where the block flow
4644   // direction progresses in a negative physical direction, and therefore block
4645   // direction coordinate conversion depends on knowing the width of the
4646   // coordinate space in order to translate between the logical and physical
4647   // origins. As a result, if our final border-box block-size is different from
4648   // this tentative one, and we are in vertical-rl writing mode, we need to
4649   // adjust our children's position after reflowing them.
4650   const LogicalSize tentativeBorderBoxSize(
4651       wm, contentBoxSize.ISize(wm) + borderPadding.IStartEnd(wm),
4652       std::min(effectiveContentBSize + borderPadding.BStartEnd(wm),
4653                aReflowInput.AvailableBSize()));
4654   const nsSize containerSize = tentativeBorderBoxSize.GetPhysicalSize(wm);
4655 
4656   const auto* prevInFlow = static_cast<nsFlexContainerFrame*>(GetPrevInFlow());
4657   OverflowAreas ocBounds;
4658   nsReflowStatus ocStatus;
4659   nscoord sumOfChildrenBlockSize;
4660   if (prevInFlow) {
4661     ReflowOverflowContainerChildren(
4662         aPresContext, aReflowInput, ocBounds, ReflowChildFlags::Default,
4663         ocStatus, MergeSortedFrameListsFor, Some(containerSize));
4664     sumOfChildrenBlockSize =
4665         prevInFlow->GetProperty(SumOfChildrenBlockSizeProperty());
4666   } else {
4667     sumOfChildrenBlockSize = 0;
4668   }
4669 
4670   const LogicalSize availableSizeForItems =
4671       ComputeAvailableSizeForItems(aReflowInput, borderPadding);
4672   const auto [maxBlockEndEdgeOfChildren, areChildrenComplete] = ReflowChildren(
4673       aReflowInput, containerSize, availableSizeForItems, borderPadding,
4674       sumOfChildrenBlockSize, axisTracker, hasLineClampEllipsis, flr);
4675 
4676   // maxBlockEndEdgeOfChildren is relative to border-box, so we need to subtract
4677   // block-start border and padding to make it relative to our content-box. Note
4678   // that if there is a packing space in between the last flex item's block-end
4679   // edge and the available space's block-end edge, we want to record the
4680   // available size of item to consume part of the packing space.
4681   sumOfChildrenBlockSize +=
4682       std::max(maxBlockEndEdgeOfChildren - borderPadding.BStart(wm),
4683                availableSizeForItems.BSize(wm));
4684 
4685   PopulateReflowOutput(aReflowOutput, aReflowInput, aStatus, contentBoxSize,
4686                        borderPadding, consumedBSize, mayNeedNextInFlow,
4687                        maxBlockEndEdgeOfChildren, areChildrenComplete,
4688                        flr.mAscent, flr.mLines, axisTracker);
4689 
4690   if (wm.IsVerticalRL()) {
4691     // If the final border-box block-size is different from the tentative one,
4692     // adjust our children's position.
4693     const nscoord deltaBCoord =
4694         tentativeBorderBoxSize.BSize(wm) - aReflowOutput.Size(wm).BSize(wm);
4695     if (deltaBCoord != 0) {
4696       const LogicalPoint delta(wm, 0, deltaBCoord);
4697       for (const FlexLine& line : flr.mLines) {
4698         for (const FlexItem& item : line.Items()) {
4699           item.Frame()->MovePositionBy(wm, delta);
4700         }
4701       }
4702     }
4703   }
4704 
4705   // Overflow area = union(my overflow area, children's overflow areas)
4706   aReflowOutput.SetOverflowAreasToDesiredBounds();
4707   for (nsIFrame* childFrame : mFrames) {
4708     ConsiderChildOverflow(aReflowOutput.mOverflowAreas, childFrame);
4709   }
4710 
4711   MOZ_ASSERT(!flr.mLines.IsEmpty(),
4712              "Flex container should have at least one FlexLine!");
4713   if (Style()->GetPseudoType() == PseudoStyleType::scrolledContent &&
4714       !flr.mLines.IsEmpty() && !flr.mLines[0].IsEmpty()) {
4715     MOZ_ASSERT(aReflowInput.ComputedLogicalBorderPadding(wm) ==
4716                    aReflowInput.ComputedLogicalPadding(wm),
4717                "A scrolled inner frame shouldn't have any border!");
4718     const LogicalMargin& padding = borderPadding;
4719 
4720     // The CSS Overflow spec [1] requires that a scrollable container's
4721     // scrollable overflow should include the following areas.
4722     //
4723     // a) "the box's own content and padding areas": we treat the *content* as
4724     // the scrolled inner frame's theoretical content-box that's intrinsically
4725     // sized to the union of all the flex items' margin boxes, _without_
4726     // relative positioning applied. The *padding areas* is just inflation on
4727     // top of the theoretical content-box by the flex container's padding.
4728     //
4729     // b) "the margin areas of grid item and flex item boxes for which the box
4730     // establishes a containing block": a) already includes the flex items'
4731     // normal-positioned margin boxes into the scrollable overflow, but their
4732     // relative-positioned margin boxes should also be included because relpos
4733     // children are still flex items.
4734     //
4735     // [1] https://drafts.csswg.org/css-overflow-3/#scrollable.
4736 
4737     // Union of normal-positioned margin boxes for all the items.
4738     nsRect itemMarginBoxes;
4739     // Union of relative-positioned margin boxes for the relpos items only.
4740     nsRect relPosItemMarginBoxes;
4741 
4742     for (const FlexLine& line : flr.mLines) {
4743       for (const FlexItem& item : line.Items()) {
4744         const nsIFrame* f = item.Frame();
4745         if (MOZ_UNLIKELY(f->IsRelativelyOrStickyPositioned())) {
4746           const nsRect marginRect = f->GetMarginRectRelativeToSelf();
4747           itemMarginBoxes =
4748               itemMarginBoxes.Union(marginRect + f->GetNormalPosition());
4749           relPosItemMarginBoxes =
4750               relPosItemMarginBoxes.Union(marginRect + f->GetPosition());
4751         } else {
4752           itemMarginBoxes = itemMarginBoxes.Union(f->GetMarginRect());
4753         }
4754       }
4755     }
4756 
4757     itemMarginBoxes.Inflate(padding.GetPhysicalMargin(wm));
4758     aReflowOutput.mOverflowAreas.UnionAllWith(itemMarginBoxes);
4759     aReflowOutput.mOverflowAreas.UnionAllWith(relPosItemMarginBoxes);
4760   }
4761 
4762   // Merge overflow container bounds and status.
4763   aReflowOutput.mOverflowAreas.UnionWith(ocBounds);
4764   aStatus.MergeCompletionStatusFrom(ocStatus);
4765 
4766   FinishReflowWithAbsoluteFrames(PresContext(), aReflowOutput, aReflowInput,
4767                                  aStatus);
4768 
4769   NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aReflowOutput)
4770 
4771   // Finally update our line and item measurements in our containerInfo.
4772   if (MOZ_UNLIKELY(containerInfo)) {
4773     UpdateFlexLineAndItemInfo(*containerInfo, flr.mLines);
4774   }
4775 
4776   // If we are the first-in-flow, we want to store data for our next-in-flows,
4777   // or clear the existing data if it is not needed.
4778   if (!GetPrevInFlow()) {
4779     SharedFlexData* data = GetProperty(SharedFlexData::Prop());
4780     if (!aStatus.IsFullyComplete()) {
4781       if (!data) {
4782         data = new SharedFlexData;
4783         SetProperty(SharedFlexData::Prop(), data);
4784       }
4785       data->mLines = std::move(flr.mLines);
4786       data->mContentBoxMainSize = flr.mContentBoxMainSize;
4787       data->mContentBoxCrossSize = flr.mContentBoxCrossSize;
4788 
4789       SetProperty(SumOfChildrenBlockSizeProperty(), sumOfChildrenBlockSize);
4790     } else if (data) {
4791       // We are fully-complete, so no next-in-flow is needed. Delete the
4792       // existing data.
4793       RemoveProperty(SharedFlexData::Prop());
4794       RemoveProperty(SumOfChildrenBlockSizeProperty());
4795     }
4796   } else {
4797     SetProperty(SumOfChildrenBlockSizeProperty(), sumOfChildrenBlockSize);
4798   }
4799 }
4800 
CalculatePackingSpace(uint32_t aNumThingsToPack,const StyleContentDistribution & aAlignVal,nscoord * aFirstSubjectOffset,uint32_t * aNumPackingSpacesRemaining,nscoord * aPackingSpaceRemaining)4801 void nsFlexContainerFrame::CalculatePackingSpace(
4802     uint32_t aNumThingsToPack, const StyleContentDistribution& aAlignVal,
4803     nscoord* aFirstSubjectOffset, uint32_t* aNumPackingSpacesRemaining,
4804     nscoord* aPackingSpaceRemaining) {
4805   StyleAlignFlags val = aAlignVal.primary;
4806   MOZ_ASSERT(val == StyleAlignFlags::SPACE_BETWEEN ||
4807                  val == StyleAlignFlags::SPACE_AROUND ||
4808                  val == StyleAlignFlags::SPACE_EVENLY,
4809              "Unexpected alignment value");
4810 
4811   MOZ_ASSERT(*aPackingSpaceRemaining >= 0,
4812              "Should not be called with negative packing space");
4813 
4814   // Note: In the aNumThingsToPack==1 case, the fallback behavior for
4815   // 'space-between' depends on precise information about the axes that we
4816   // don't have here. So, for that case, we just depend on the caller to
4817   // explicitly convert 'space-{between,around,evenly}' keywords to the
4818   // appropriate fallback alignment and skip this function.
4819   MOZ_ASSERT(aNumThingsToPack > 1,
4820              "Should not be called unless there's more than 1 thing to pack");
4821 
4822   // Packing spaces between items:
4823   *aNumPackingSpacesRemaining = aNumThingsToPack - 1;
4824 
4825   if (val == StyleAlignFlags::SPACE_BETWEEN) {
4826     // No need to reserve space at beginning/end, so we're done.
4827     return;
4828   }
4829 
4830   // We need to add 1 or 2 packing spaces, split between beginning/end, for
4831   // space-around / space-evenly:
4832   size_t numPackingSpacesForEdges =
4833       val == StyleAlignFlags::SPACE_AROUND ? 1 : 2;
4834 
4835   // How big will each "full" packing space be:
4836   nscoord packingSpaceSize =
4837       *aPackingSpaceRemaining /
4838       (*aNumPackingSpacesRemaining + numPackingSpacesForEdges);
4839   // How much packing-space are we allocating to the edges:
4840   nscoord totalEdgePackingSpace = numPackingSpacesForEdges * packingSpaceSize;
4841 
4842   // Use half of that edge packing space right now:
4843   *aFirstSubjectOffset += totalEdgePackingSpace / 2;
4844   // ...but we need to subtract all of it right away, so that we won't
4845   // hand out any of it to intermediate packing spaces.
4846   *aPackingSpaceRemaining -= totalEdgePackingSpace;
4847 }
4848 
4849 ComputedFlexContainerInfo*
CreateOrClearFlexContainerInfo()4850 nsFlexContainerFrame::CreateOrClearFlexContainerInfo() {
4851   if (!ShouldGenerateComputedInfo()) {
4852     return nullptr;
4853   }
4854 
4855   // The flag that sets ShouldGenerateComputedInfo() will never be cleared.
4856   // That's acceptable because it's only set in a Chrome API invoked by
4857   // devtools, and won't impact normal browsing.
4858 
4859   // Re-use the ComputedFlexContainerInfo, if it exists.
4860   ComputedFlexContainerInfo* info = GetProperty(FlexContainerInfo());
4861   if (info) {
4862     // We can reuse, as long as we clear out old data.
4863     info->mLines.Clear();
4864   } else {
4865     info = new ComputedFlexContainerInfo();
4866     SetProperty(FlexContainerInfo(), info);
4867   }
4868 
4869   return info;
4870 }
4871 
CreateFlexLineAndFlexItemInfo(ComputedFlexContainerInfo & aContainerInfo,const nsTArray<FlexLine> & aLines)4872 void nsFlexContainerFrame::CreateFlexLineAndFlexItemInfo(
4873     ComputedFlexContainerInfo& aContainerInfo,
4874     const nsTArray<FlexLine>& aLines) {
4875   for (const FlexLine& line : aLines) {
4876     ComputedFlexLineInfo* lineInfo = aContainerInfo.mLines.AppendElement();
4877     // Most of the remaining lineInfo properties will be filled out in
4878     // UpdateFlexLineAndItemInfo (some will be provided by other functions),
4879     // when we have real values. But we still add all the items here, so
4880     // we can capture computed data for each item as we proceed.
4881     for (const FlexItem& item : line.Items()) {
4882       nsIFrame* frame = item.Frame();
4883 
4884       // The frame may be for an element, or it may be for an
4885       // anonymous flex item, e.g. wrapping one or more text nodes.
4886       // DevTools wants the content node for the actual child in
4887       // the DOM tree, so we descend through anonymous boxes.
4888       nsIFrame* targetFrame = GetFirstNonAnonBoxInSubtree(frame);
4889       nsIContent* content = targetFrame->GetContent();
4890 
4891       // Skip over content that is only whitespace, which might
4892       // have been broken off from a text node which is our real
4893       // target.
4894       while (content && content->TextIsOnlyWhitespace()) {
4895         // If content is only whitespace, try the frame sibling.
4896         targetFrame = targetFrame->GetNextSibling();
4897         if (targetFrame) {
4898           content = targetFrame->GetContent();
4899         } else {
4900           content = nullptr;
4901         }
4902       }
4903 
4904       ComputedFlexItemInfo* itemInfo = lineInfo->mItems.AppendElement();
4905 
4906       itemInfo->mNode = content;
4907 
4908       // itemInfo->mMainBaseSize and mMainDeltaSize will be filled out
4909       // in ResolveFlexibleLengths(). Other measurements will be captured in
4910       // UpdateFlexLineAndItemInfo.
4911     }
4912   }
4913 }
4914 
ComputeFlexDirections(ComputedFlexContainerInfo & aContainerInfo,const FlexboxAxisTracker & aAxisTracker)4915 void nsFlexContainerFrame::ComputeFlexDirections(
4916     ComputedFlexContainerInfo& aContainerInfo,
4917     const FlexboxAxisTracker& aAxisTracker) {
4918   auto ConvertPhysicalStartSideToFlexPhysicalDirection =
4919       [](mozilla::Side aStartSide) {
4920         switch (aStartSide) {
4921           case eSideLeft:
4922             return dom::FlexPhysicalDirection::Horizontal_lr;
4923           case eSideRight:
4924             return dom::FlexPhysicalDirection::Horizontal_rl;
4925           case eSideTop:
4926             return dom::FlexPhysicalDirection::Vertical_tb;
4927           case eSideBottom:
4928             return dom::FlexPhysicalDirection::Vertical_bt;
4929         }
4930 
4931         MOZ_ASSERT_UNREACHABLE("We should handle all sides!");
4932         return dom::FlexPhysicalDirection::Horizontal_lr;
4933       };
4934 
4935   aContainerInfo.mMainAxisDirection =
4936       ConvertPhysicalStartSideToFlexPhysicalDirection(
4937           aAxisTracker.MainAxisPhysicalStartSide());
4938   aContainerInfo.mCrossAxisDirection =
4939       ConvertPhysicalStartSideToFlexPhysicalDirection(
4940           aAxisTracker.CrossAxisPhysicalStartSide());
4941 }
4942 
UpdateFlexLineAndItemInfo(ComputedFlexContainerInfo & aContainerInfo,const nsTArray<FlexLine> & aLines)4943 void nsFlexContainerFrame::UpdateFlexLineAndItemInfo(
4944     ComputedFlexContainerInfo& aContainerInfo,
4945     const nsTArray<FlexLine>& aLines) {
4946   uint32_t lineIndex = 0;
4947   for (const FlexLine& line : aLines) {
4948     ComputedFlexLineInfo& lineInfo = aContainerInfo.mLines[lineIndex];
4949 
4950     lineInfo.mCrossSize = line.LineCrossSize();
4951     lineInfo.mFirstBaselineOffset = line.FirstBaselineOffset();
4952     lineInfo.mLastBaselineOffset = line.LastBaselineOffset();
4953 
4954     uint32_t itemIndex = 0;
4955     for (const FlexItem& item : line.Items()) {
4956       ComputedFlexItemInfo& itemInfo = lineInfo.mItems[itemIndex];
4957       itemInfo.mFrameRect = item.Frame()->GetRect();
4958       itemInfo.mMainMinSize = item.MainMinSize();
4959       itemInfo.mMainMaxSize = item.MainMaxSize();
4960       itemInfo.mCrossMinSize = item.CrossMinSize();
4961       itemInfo.mCrossMaxSize = item.CrossMaxSize();
4962       itemInfo.mClampState =
4963           item.WasMinClamped()
4964               ? mozilla::dom::FlexItemClampState::Clamped_to_min
4965               : (item.WasMaxClamped()
4966                      ? mozilla::dom::FlexItemClampState::Clamped_to_max
4967                      : mozilla::dom::FlexItemClampState::Unclamped);
4968       ++itemIndex;
4969     }
4970     ++lineIndex;
4971   }
4972 }
4973 
GetFlexFrameWithComputedInfo(nsIFrame * aFrame)4974 nsFlexContainerFrame* nsFlexContainerFrame::GetFlexFrameWithComputedInfo(
4975     nsIFrame* aFrame) {
4976   // Prepare a lambda function that we may need to call multiple times.
4977   auto GetFlexContainerFrame = [](nsIFrame* aFrame) {
4978     // Return the aFrame's content insertion frame, iff it is
4979     // a flex container frame.
4980     nsFlexContainerFrame* flexFrame = nullptr;
4981 
4982     if (aFrame) {
4983       nsIFrame* inner = aFrame;
4984       if (MOZ_UNLIKELY(aFrame->IsFieldSetFrame())) {
4985         inner = static_cast<nsFieldSetFrame*>(aFrame)->GetInner();
4986       }
4987       // Since "Get" methods like GetInner and GetContentInsertionFrame can
4988       // return null, we check the return values before dereferencing. Our
4989       // calling pattern makes this unlikely, but we're being careful.
4990       nsIFrame* insertionFrame =
4991           inner ? inner->GetContentInsertionFrame() : nullptr;
4992       nsIFrame* possibleFlexFrame = insertionFrame ? insertionFrame : aFrame;
4993       flexFrame = possibleFlexFrame->IsFlexContainerFrame()
4994                       ? static_cast<nsFlexContainerFrame*>(possibleFlexFrame)
4995                       : nullptr;
4996     }
4997     return flexFrame;
4998   };
4999 
5000   nsFlexContainerFrame* flexFrame = GetFlexContainerFrame(aFrame);
5001   if (flexFrame) {
5002     // Generate the FlexContainerInfo data, if it's not already there.
5003     bool reflowNeeded = !flexFrame->HasProperty(FlexContainerInfo());
5004 
5005     if (reflowNeeded) {
5006       // Trigger a reflow that generates additional flex property data.
5007       // Hold onto aFrame while we do this, in case reflow destroys it.
5008       AutoWeakFrame weakFrameRef(aFrame);
5009 
5010       RefPtr<mozilla::PresShell> presShell = flexFrame->PresShell();
5011       flexFrame->SetShouldGenerateComputedInfo(true);
5012       presShell->FrameNeedsReflow(flexFrame, IntrinsicDirty::Resize,
5013                                   NS_FRAME_IS_DIRTY);
5014       presShell->FlushPendingNotifications(FlushType::Layout);
5015 
5016       // Since the reflow may have side effects, get the flex frame
5017       // again. But if the weakFrameRef is no longer valid, then we
5018       // must bail out.
5019       if (!weakFrameRef.IsAlive()) {
5020         return nullptr;
5021       }
5022 
5023       flexFrame = GetFlexContainerFrame(weakFrameRef.GetFrame());
5024 
5025       NS_WARNING_ASSERTION(
5026           !flexFrame || flexFrame->HasProperty(FlexContainerInfo()),
5027           "The state bit should've made our forced-reflow "
5028           "generate a FlexContainerInfo object");
5029     }
5030   }
5031   return flexFrame;
5032 }
5033 
5034 /* static */
IsItemInlineAxisMainAxis(nsIFrame * aFrame)5035 bool nsFlexContainerFrame::IsItemInlineAxisMainAxis(nsIFrame* aFrame) {
5036   MOZ_ASSERT(aFrame && aFrame->IsFlexItem(), "expecting arg to be a flex item");
5037   const WritingMode flexItemWM = aFrame->GetWritingMode();
5038   const nsIFrame* flexContainer = aFrame->GetParent();
5039 
5040   if (IsLegacyBox(flexContainer)) {
5041     // For legacy boxes, the main axis is determined by "box-orient", and we can
5042     // just directly check if that's vertical, and compare that to whether the
5043     // item's WM is also vertical:
5044     bool boxOrientIsVertical =
5045         (flexContainer->StyleXUL()->mBoxOrient == StyleBoxOrient::Vertical);
5046     return flexItemWM.IsVertical() == boxOrientIsVertical;
5047   }
5048 
5049   // For modern CSS flexbox, we get our return value by asking two questions
5050   // and comparing their answers.
5051   // Question 1: does aFrame have the same inline axis as its flex container?
5052   bool itemInlineAxisIsParallelToParent =
5053       !flexItemWM.IsOrthogonalTo(flexContainer->GetWritingMode());
5054 
5055   // Question 2: is aFrame's flex container row-oriented? (This tells us
5056   // whether the flex container's main axis is its inline axis.)
5057   auto flexDirection = flexContainer->StylePosition()->mFlexDirection;
5058   bool flexContainerIsRowOriented =
5059       flexDirection == StyleFlexDirection::Row ||
5060       flexDirection == StyleFlexDirection::RowReverse;
5061 
5062   // aFrame's inline axis is its flex container's main axis IFF the above
5063   // questions have the same answer.
5064   return flexContainerIsRowOriented == itemInlineAxisIsParallelToParent;
5065 }
5066 
5067 /* static */
IsUsedFlexBasisContent(const StyleFlexBasis & aFlexBasis,const StyleSize & aMainSize)5068 bool nsFlexContainerFrame::IsUsedFlexBasisContent(
5069     const StyleFlexBasis& aFlexBasis, const StyleSize& aMainSize) {
5070   // We have a used flex-basis of 'content' if flex-basis explicitly has that
5071   // value, OR if flex-basis is 'auto' (deferring to the main-size property)
5072   // and the main-size property is also 'auto'.
5073   // See https://drafts.csswg.org/css-flexbox-1/#valdef-flex-basis-auto
5074   if (aFlexBasis.IsContent()) {
5075     return true;
5076   }
5077   return aFlexBasis.IsAuto() && aMainSize.IsAuto();
5078 }
5079 
DoFlexLayout(const ReflowInput & aReflowInput,const nscoord aTentativeContentBoxMainSize,const nscoord aTentativeContentBoxCrossSize,const FlexboxAxisTracker & aAxisTracker,nscoord aMainGapSize,nscoord aCrossGapSize,bool aHasLineClampEllipsis,nsTArray<StrutInfo> & aStruts,ComputedFlexContainerInfo * const aContainerInfo)5080 nsFlexContainerFrame::FlexLayoutResult nsFlexContainerFrame::DoFlexLayout(
5081     const ReflowInput& aReflowInput, const nscoord aTentativeContentBoxMainSize,
5082     const nscoord aTentativeContentBoxCrossSize,
5083     const FlexboxAxisTracker& aAxisTracker, nscoord aMainGapSize,
5084     nscoord aCrossGapSize, bool aHasLineClampEllipsis,
5085     nsTArray<StrutInfo>& aStruts,
5086     ComputedFlexContainerInfo* const aContainerInfo) {
5087   FlexLayoutResult flr;
5088 
5089   GenerateFlexLines(aReflowInput, aTentativeContentBoxMainSize,
5090                     aTentativeContentBoxCrossSize, aStruts, aAxisTracker,
5091                     aMainGapSize, aHasLineClampEllipsis, flr.mPlaceholders,
5092                     flr.mLines);
5093 
5094   if ((flr.mLines.Length() == 1 && flr.mLines[0].IsEmpty()) ||
5095       aReflowInput.mStyleDisplay->IsContainLayout()) {
5096     // We have no flex items, or we're layout-contained. So, we have no
5097     // baseline, and our parent should synthesize a baseline if needed.
5098     AddStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE);
5099   } else {
5100     RemoveStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE);
5101   }
5102 
5103   // Construct our computed info if we've been asked to do so. This is
5104   // necessary to do now so we can capture some computed values for
5105   // FlexItems during layout that would not otherwise be saved (like
5106   // size adjustments). We'll later fix up the line properties,
5107   // because the correct values aren't available yet.
5108   if (aContainerInfo) {
5109     MOZ_ASSERT(ShouldGenerateComputedInfo(),
5110                "We should only have the info struct if "
5111                "ShouldGenerateComputedInfo() is true!");
5112 
5113     if (!aStruts.IsEmpty()) {
5114       // We restarted DoFlexLayout, and may have stale mLines to clear:
5115       aContainerInfo->mLines.Clear();
5116     } else {
5117       MOZ_ASSERT(aContainerInfo->mLines.IsEmpty(), "Shouldn't have lines yet.");
5118     }
5119 
5120     CreateFlexLineAndFlexItemInfo(*aContainerInfo, flr.mLines);
5121     ComputeFlexDirections(*aContainerInfo, aAxisTracker);
5122   }
5123 
5124   flr.mContentBoxMainSize = ComputeMainSize(
5125       aReflowInput, aAxisTracker, aTentativeContentBoxMainSize, flr.mLines);
5126 
5127   uint32_t lineIndex = 0;
5128   for (FlexLine& line : flr.mLines) {
5129     ComputedFlexLineInfo* lineInfo =
5130         aContainerInfo ? &aContainerInfo->mLines[lineIndex] : nullptr;
5131     line.ResolveFlexibleLengths(flr.mContentBoxMainSize, lineInfo);
5132     ++lineIndex;
5133   }
5134 
5135   // Cross Size Determination - Flexbox spec section 9.4
5136   // https://drafts.csswg.org/css-flexbox-1/#cross-sizing
5137   // ===================================================
5138   // Calculate the hypothetical cross size of each item:
5139 
5140   // 'sumLineCrossSizes' includes the size of all gaps between lines. We
5141   // initialize it with the sum of all the gaps, and add each line's cross size
5142   // at the end of the following for-loop.
5143   nscoord sumLineCrossSizes = aCrossGapSize * (flr.mLines.Length() - 1);
5144   for (FlexLine& line : flr.mLines) {
5145     for (FlexItem& item : line.Items()) {
5146       // The item may already have the correct cross-size; only recalculate
5147       // if the item's main size resolution (flexing) could have influenced it:
5148       if (item.CanMainSizeInfluenceCrossSize()) {
5149         StyleSizeOverrides sizeOverrides;
5150         if (item.IsInlineAxisMainAxis()) {
5151           sizeOverrides.mStyleISize.emplace(item.StyleMainSize());
5152         } else {
5153           sizeOverrides.mStyleBSize.emplace(item.StyleMainSize());
5154         }
5155         FLEX_LOG("Sizing flex item %p in cross axis", item.Frame());
5156         FLEX_LOGV(" Main size override: %d", item.MainSize());
5157 
5158         const WritingMode wm = item.GetWritingMode();
5159         LogicalSize availSize = aReflowInput.ComputedSize(wm);
5160         availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
5161         ReflowInput childReflowInput(PresContext(), aReflowInput, item.Frame(),
5162                                      availSize, Nothing(), {}, sizeOverrides);
5163         childReflowInput.mFlags.mInsideLineClamp = GetLineClampValue() != 0;
5164         if (item.IsBlockAxisMainAxis() && item.TreatBSizeAsIndefinite()) {
5165           childReflowInput.mFlags.mTreatBSizeAsIndefinite = true;
5166         }
5167 
5168         SizeItemInCrossAxis(childReflowInput, item);
5169       }
5170     }
5171     // Now that we've finished with this line's items, size the line itself:
5172     line.ComputeCrossSizeAndBaseline(aAxisTracker);
5173     sumLineCrossSizes += line.LineCrossSize();
5174   }
5175 
5176   bool isCrossSizeDefinite;
5177   flr.mContentBoxCrossSize = ComputeCrossSize(
5178       aReflowInput, aAxisTracker, aTentativeContentBoxCrossSize,
5179       sumLineCrossSizes, &isCrossSizeDefinite);
5180 
5181   // Set up state for cross-axis alignment, at a high level (outside the
5182   // scope of a particular flex line)
5183   CrossAxisPositionTracker crossAxisPosnTracker(
5184       flr.mLines, aReflowInput, flr.mContentBoxCrossSize, isCrossSizeDefinite,
5185       aAxisTracker, aCrossGapSize);
5186 
5187   // Now that we know the cross size of each line (including
5188   // "align-content:stretch" adjustments, from the CrossAxisPositionTracker
5189   // constructor), we can create struts for any flex items with
5190   // "visibility: collapse" (and restart flex layout).
5191   if (aStruts.IsEmpty() &&  // (Don't make struts if we already did)
5192       !ShouldUseMozBoxCollapseBehavior(aReflowInput.mStyleDisplay)) {
5193     BuildStrutInfoFromCollapsedItems(flr.mLines, aStruts);
5194     if (!aStruts.IsEmpty()) {
5195       // Restart flex layout, using our struts.
5196       return flr;
5197     }
5198   }
5199 
5200   // If the container should derive its baseline from the first FlexLine,
5201   // do that here (while crossAxisPosnTracker is conveniently pointing
5202   // at the cross-start edge of that line, which the line's baseline offset is
5203   // measured from):
5204   if (nscoord firstLineBaselineOffset = flr.mLines[0].FirstBaselineOffset();
5205       firstLineBaselineOffset == nscoord_MIN) {
5206     // No baseline-aligned items in line. Use sentinel value to prompt us to
5207     // get baseline from the first FlexItem after we've reflowed it.
5208     flr.mAscent = nscoord_MIN;
5209   } else {
5210     flr.mAscent = ComputePhysicalAscentFromFlexRelativeAscent(
5211         crossAxisPosnTracker.Position() + firstLineBaselineOffset,
5212         flr.mContentBoxCrossSize, aReflowInput, aAxisTracker);
5213   }
5214 
5215   const auto justifyContent =
5216       IsLegacyBox(aReflowInput.mFrame)
5217           ? ConvertLegacyStyleToJustifyContent(StyleXUL())
5218           : aReflowInput.mStylePosition->mJustifyContent;
5219 
5220   lineIndex = 0;
5221   for (FlexLine& line : flr.mLines) {
5222     // Main-Axis Alignment - Flexbox spec section 9.5
5223     // https://drafts.csswg.org/css-flexbox-1/#main-alignment
5224     // ==============================================
5225     line.PositionItemsInMainAxis(justifyContent, flr.mContentBoxMainSize,
5226                                  aAxisTracker);
5227 
5228     // See if we need to extract some computed info for this line.
5229     if (MOZ_UNLIKELY(aContainerInfo)) {
5230       ComputedFlexLineInfo& lineInfo = aContainerInfo->mLines[lineIndex];
5231       lineInfo.mCrossStart = crossAxisPosnTracker.Position();
5232     }
5233 
5234     // Cross-Axis Alignment - Flexbox spec section 9.6
5235     // https://drafts.csswg.org/css-flexbox-1/#cross-alignment
5236     // ===============================================
5237     line.PositionItemsInCrossAxis(crossAxisPosnTracker.Position(),
5238                                   aAxisTracker);
5239     crossAxisPosnTracker.TraverseLine(line);
5240     crossAxisPosnTracker.TraversePackingSpace();
5241 
5242     if (&line != &flr.mLines.LastElement()) {
5243       crossAxisPosnTracker.TraverseGap();
5244     }
5245     ++lineIndex;
5246   }
5247 
5248   return flr;
5249 }
5250 
ReflowChildren(const ReflowInput & aReflowInput,const nsSize & aContainerSize,const LogicalSize & aAvailableSizeForItems,const LogicalMargin & aBorderPadding,const nscoord aSumOfPrevInFlowsChildrenBlockSize,const FlexboxAxisTracker & aAxisTracker,bool aHasLineClampEllipsis,FlexLayoutResult & aFlr)5251 std::tuple<nscoord, bool> nsFlexContainerFrame::ReflowChildren(
5252     const ReflowInput& aReflowInput, const nsSize& aContainerSize,
5253     const LogicalSize& aAvailableSizeForItems,
5254     const LogicalMargin& aBorderPadding,
5255     const nscoord aSumOfPrevInFlowsChildrenBlockSize,
5256     const FlexboxAxisTracker& aAxisTracker, bool aHasLineClampEllipsis,
5257     FlexLayoutResult& aFlr) {
5258   // Before giving each child a final reflow, calculate the origin of the
5259   // flex container's content box (with respect to its border-box), so that
5260   // we can compute our flex item's final positions.
5261   WritingMode flexWM = aReflowInput.GetWritingMode();
5262   const LogicalPoint containerContentBoxOrigin(
5263       flexWM, aBorderPadding.IStart(flexWM), aBorderPadding.BStart(flexWM));
5264 
5265   // If the flex container has no baseline-aligned items, it will use the first
5266   // item to determine its baseline:
5267   const FlexItem* firstItem =
5268       aFlr.mLines[0].IsEmpty() ? nullptr : &aFlr.mLines[0].FirstItem();
5269 
5270   // The block-end of children is relative to the flex container's border-box.
5271   nscoord maxBlockEndEdgeOfChildren = containerContentBoxOrigin.B(flexWM);
5272 
5273   FrameHashtable pushedItems;
5274   FrameHashtable incompleteItems;
5275   FrameHashtable overflowIncompleteItems;
5276 
5277   // FINAL REFLOW: Give each child frame another chance to reflow, now that
5278   // we know its final size and position.
5279   for (const FlexLine& line : aFlr.mLines) {
5280     for (const FlexItem& item : line.Items()) {
5281       LogicalPoint framePos = aAxisTracker.LogicalPointFromFlexRelativePoint(
5282           item.MainPosition(), item.CrossPosition(), aFlr.mContentBoxMainSize,
5283           aFlr.mContentBoxCrossSize);
5284 
5285       if (item.Frame()->GetPrevInFlow()) {
5286         // The item is a continuation. Lay it out at the beginning of the
5287         // available space.
5288         framePos.B(flexWM) = 0;
5289       } else {
5290         // We haven't laid the item out. Subtract its block-direction position
5291         // by the sum of our prev-in-flows' content block-end.
5292         framePos.B(flexWM) -= aSumOfPrevInFlowsChildrenBlockSize;
5293       }
5294 
5295       // Adjust available block-size for the item. (We compute it here because
5296       // framePos is still relative to the container's content-box.)
5297       //
5298       // Note: The available block-size can become negative if item's
5299       // block-direction position is below available space's block-end.
5300       const nscoord availableBSizeForItem =
5301           aAvailableSizeForItems.BSize(flexWM) == NS_UNCONSTRAINEDSIZE
5302               ? NS_UNCONSTRAINEDSIZE
5303               : aAvailableSizeForItems.BSize(flexWM) - framePos.B(flexWM);
5304 
5305       // Adjust framePos to be relative to the container's border-box
5306       // (i.e. its frame rect), instead of the container's content-box:
5307       framePos += containerContentBoxOrigin;
5308 
5309       // (Intentionally snapshotting this before ApplyRelativePositioning, to
5310       // maybe use for setting the flex container's baseline.)
5311       const nscoord itemNormalBPos = framePos.B(flexWM);
5312 
5313       // Check if we actually need to reflow the item -- if the item's position
5314       // is below the available space's block-end, push it to our next-in-flow;
5315       // if it does need a reflow, and we already reflowed it with the right
5316       // content-box size, and there is no need to do a reflow to clear out a
5317       // -webkit-line-clamp ellipsis, we can just reposition it as-needed.
5318       const bool childBPosExceedAvailableSpaceBEnd =
5319           availableBSizeForItem != NS_UNCONSTRAINEDSIZE &&
5320           availableBSizeForItem <= 0;
5321       if (childBPosExceedAvailableSpaceBEnd) {
5322         // Note: Even if all of our items are beyond the available space & get
5323         // pushed here, we'll be guaranteed to place at least one of them (and
5324         // make progress) in one of the flex container's *next* fragment. It's
5325         // because ComputeAvailableSizeForItems() always reserves at least 1px
5326         // available block-size for its children, and we consume all available
5327         // block-size and add it to SumOfChildrenBlockSizeProperty even if we
5328         // are not laying out any child.
5329         FLEX_LOG(
5330             "[frag] Flex item %p needed to be pushed to container's "
5331             "next-in-flow due to position below available space's block-end",
5332             item.Frame());
5333         pushedItems.Insert(item.Frame());
5334       } else if (item.NeedsFinalReflow(availableBSizeForItem)) {
5335         // The available size must be in item's writing-mode.
5336         const WritingMode itemWM = item.GetWritingMode();
5337         const auto availableSize =
5338             LogicalSize(flexWM, aAvailableSizeForItems.ISize(flexWM),
5339                         availableBSizeForItem)
5340                 .ConvertTo(itemWM, flexWM);
5341 
5342         const nsReflowStatus childReflowStatus = ReflowFlexItem(
5343             aAxisTracker, aReflowInput, item, framePos, availableSize,
5344             aContainerSize, aHasLineClampEllipsis);
5345 
5346         if (childReflowStatus.IsIncomplete()) {
5347           incompleteItems.Insert(item.Frame());
5348         } else if (childReflowStatus.IsOverflowIncomplete()) {
5349           overflowIncompleteItems.Insert(item.Frame());
5350         }
5351       } else {
5352         MoveFlexItemToFinalPosition(aReflowInput, item, framePos,
5353                                     aContainerSize);
5354         // We didn't perform a final reflow of the item. If we still have a
5355         // -webkit-line-clamp ellipsis hanging around, but we shouldn't have
5356         // one any more, we need to clear that now.  Technically, we only need
5357         // to do this if we *didn't* do a bsize measuring reflow of the item
5358         // earlier (since that is normally when we deal with -webkit-line-clamp
5359         // ellipses) but not all flex items need such a reflow.
5360         // XXXdholbert This comment implies that we could skip this if
5361         // HadMeasuringReflow() is true.  Maybe we should try doing that?
5362         if (aHasLineClampEllipsis && GetLineClampValue() == 0) {
5363           item.BlockFrame()->ClearLineClampEllipsis();
5364         }
5365       }
5366 
5367       if (!childBPosExceedAvailableSpaceBEnd) {
5368         // The item (or a fragment thereof) was placed in this flex container
5369         // fragment. Update the max block-end edge with the item's block-end
5370         // edge.
5371         maxBlockEndEdgeOfChildren =
5372             std::max(maxBlockEndEdgeOfChildren,
5373                      itemNormalBPos + item.Frame()->BSize(flexWM));
5374       }
5375 
5376       // If the item has auto margins, and we were tracking the UsedMargin
5377       // property, set the property to the computed margin values.
5378       if (item.HasAnyAutoMargin()) {
5379         nsMargin* propValue =
5380             item.Frame()->GetProperty(nsIFrame::UsedMarginProperty());
5381         if (propValue) {
5382           *propValue = item.PhysicalMargin();
5383         }
5384       }
5385 
5386       // If this is our first item and we haven't established a baseline for
5387       // the container yet (i.e. if we don't have 'align-self: baseline' on any
5388       // children), then use this child's first baseline as the container's
5389       // baseline.
5390       if (&item == firstItem && aFlr.mAscent == nscoord_MIN) {
5391         aFlr.mAscent = itemNormalBPos + item.ResolvedAscent(true);
5392       }
5393     }
5394   }
5395 
5396   if (!aFlr.mPlaceholders.IsEmpty()) {
5397     ReflowPlaceholders(aReflowInput, aFlr.mPlaceholders,
5398                        containerContentBoxOrigin, aContainerSize);
5399   }
5400 
5401   const bool anyChildIncomplete = PushIncompleteChildren(
5402       pushedItems, incompleteItems, overflowIncompleteItems);
5403 
5404   if (!pushedItems.IsEmpty()) {
5405     AddStateBits(NS_STATE_FLEX_DID_PUSH_ITEMS);
5406   }
5407 
5408   return {maxBlockEndEdgeOfChildren, anyChildIncomplete};
5409 }
5410 
PopulateReflowOutput(ReflowOutput & aReflowOutput,const ReflowInput & aReflowInput,nsReflowStatus & aStatus,const LogicalSize & aContentBoxSize,const LogicalMargin & aBorderPadding,const nscoord aConsumedBSize,const bool aMayNeedNextInFlow,const nscoord aMaxBlockEndEdgeOfChildren,const bool aAnyChildIncomplete,nscoord aFlexContainerAscent,nsTArray<FlexLine> & aLines,const FlexboxAxisTracker & aAxisTracker)5411 void nsFlexContainerFrame::PopulateReflowOutput(
5412     ReflowOutput& aReflowOutput, const ReflowInput& aReflowInput,
5413     nsReflowStatus& aStatus, const LogicalSize& aContentBoxSize,
5414     const LogicalMargin& aBorderPadding, const nscoord aConsumedBSize,
5415     const bool aMayNeedNextInFlow, const nscoord aMaxBlockEndEdgeOfChildren,
5416     const bool aAnyChildIncomplete, nscoord aFlexContainerAscent,
5417     nsTArray<FlexLine>& aLines, const FlexboxAxisTracker& aAxisTracker) {
5418   const WritingMode flexWM = aReflowInput.GetWritingMode();
5419 
5420   // Compute flex container's desired size (in its own writing-mode).
5421   LogicalSize desiredSizeInFlexWM(flexWM);
5422   desiredSizeInFlexWM.ISize(flexWM) =
5423       aContentBoxSize.ISize(flexWM) + aBorderPadding.IStartEnd(flexWM);
5424 
5425   // Unconditionally skip adding block-end border and padding for now. We add it
5426   // lower down, after we've established baseline and decided whether bottom
5427   // border-padding fits (if we're fragmented).
5428   const nscoord effectiveContentBSizeWithBStartBP =
5429       aContentBoxSize.BSize(flexWM) - aConsumedBSize +
5430       aBorderPadding.BStart(flexWM);
5431   nscoord blockEndContainerBP = aBorderPadding.BEnd(flexWM);
5432 
5433   if (aMayNeedNextInFlow) {
5434     // We assume our status should be reported as incomplete because we may need
5435     // a next-in-flow.
5436     bool isStatusIncomplete = true;
5437 
5438     const nscoord availableBSizeMinusBEndBP =
5439         aReflowInput.AvailableBSize() - aBorderPadding.BEnd(flexWM);
5440 
5441     if (aMaxBlockEndEdgeOfChildren <= availableBSizeMinusBEndBP) {
5442       // Consume all the available block-size.
5443       desiredSizeInFlexWM.BSize(flexWM) = availableBSizeMinusBEndBP;
5444     } else {
5445       // This case happens if we have some tall unbreakable children exceeding
5446       // the available block-size.
5447       desiredSizeInFlexWM.BSize(flexWM) = std::min(
5448           effectiveContentBSizeWithBStartBP, aMaxBlockEndEdgeOfChildren);
5449 
5450       if (aMaxBlockEndEdgeOfChildren >= effectiveContentBSizeWithBStartBP) {
5451         // Some unbreakable children force us to consume all of our content
5452         // block-size, and make us complete.
5453         isStatusIncomplete = false;
5454 
5455         // We also potentially need to get the unskipped block-end border and
5456         // padding (if we assumed it'd be skipped as part of our tentative
5457         // assumption that we'd be complete).
5458         if (aReflowInput.mStyleBorder->mBoxDecorationBreak ==
5459             StyleBoxDecorationBreak::Slice) {
5460           blockEndContainerBP =
5461               aReflowInput.ComputedLogicalBorderPadding(flexWM).BEnd(flexWM);
5462         }
5463       }
5464     }
5465 
5466     if (isStatusIncomplete) {
5467       aStatus.SetIncomplete();
5468     }
5469   } else {
5470     // Our own effective content-box block-size can fit within the available
5471     // block-size.
5472     desiredSizeInFlexWM.BSize(flexWM) = effectiveContentBSizeWithBStartBP;
5473   }
5474 
5475   if (aFlexContainerAscent == nscoord_MIN) {
5476     // Still don't have our baseline set -- this happens if we have no
5477     // children (or if our children are huge enough that they have nscoord_MIN
5478     // as their baseline... in which case, we'll use the wrong baseline, but no
5479     // big deal)
5480     NS_WARNING_ASSERTION(
5481         aLines[0].IsEmpty(),
5482         "Have flex items but didn't get an ascent - that's odd (or there are "
5483         "just gigantic sizes involved)");
5484     // Per spec, synthesize baseline from the flex container's content box
5485     // (i.e. use block-end side of content-box)
5486     // XXXdholbert This only makes sense if parent's writing mode is
5487     // horizontal (& even then, really we should be using the BSize in terms
5488     // of the parent's writing mode, not ours). Clean up in bug 1155322.
5489     aFlexContainerAscent = desiredSizeInFlexWM.BSize(flexWM);
5490   }
5491 
5492   if (HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE)) {
5493     // This will force our parent to call GetLogicalBaseline, which will
5494     // synthesize a margin-box baseline.
5495     aReflowOutput.SetBlockStartAscent(ReflowOutput::ASK_FOR_BASELINE);
5496   } else {
5497     // XXXdholbert aFlexContainerAscent needs to be in terms of
5498     // our parent's writing-mode here. See bug 1155322.
5499     aReflowOutput.SetBlockStartAscent(aFlexContainerAscent);
5500   }
5501 
5502   // Now, we account for how the block-end border and padding (if any) impacts
5503   // our desired size. If adding it pushes us over the available block-size,
5504   // then we become incomplete (unless we already weren't asking for any
5505   // block-size, in which case we stay complete to avoid looping forever).
5506   //
5507   // NOTE: If we have auto block-size, we allow our block-end border and padding
5508   // to push us over the available block-size without requesting a continuation,
5509   // for consistency with the behavior of "display:block" elements.
5510   const nscoord effectiveContentBSizeWithBStartEndBP =
5511       desiredSizeInFlexWM.BSize(flexWM) + blockEndContainerBP;
5512 
5513   if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
5514       effectiveContentBSizeWithBStartEndBP > aReflowInput.AvailableBSize() &&
5515       desiredSizeInFlexWM.BSize(flexWM) != 0 &&
5516       aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) {
5517     // We couldn't fit with the block-end border and padding included, so we'll
5518     // need a continuation.
5519     aStatus.SetIncomplete();
5520 
5521     if (aReflowInput.mStyleBorder->mBoxDecorationBreak ==
5522         StyleBoxDecorationBreak::Slice) {
5523       blockEndContainerBP = 0;
5524     }
5525   }
5526 
5527   // The variable "blockEndContainerBP" now accurately reflects how much (if
5528   // any) block-end border and padding we want for this frame, so we can proceed
5529   // to add it in.
5530   desiredSizeInFlexWM.BSize(flexWM) += blockEndContainerBP;
5531 
5532   if (aStatus.IsComplete() && aAnyChildIncomplete) {
5533     aStatus.SetOverflowIncomplete();
5534     aStatus.SetNextInFlowNeedsReflow();
5535   }
5536 
5537   // Calculate the container baselines so that our parent can baseline-align us.
5538   mBaselineFromLastReflow = aFlexContainerAscent;
5539   mLastBaselineFromLastReflow = aLines.LastElement().LastBaselineOffset();
5540   if (mLastBaselineFromLastReflow == nscoord_MIN) {
5541     // XXX we fall back to a mirrored first baseline here for now, but this
5542     // should probably use the last baseline of the last item or something.
5543     mLastBaselineFromLastReflow =
5544         desiredSizeInFlexWM.BSize(flexWM) - aFlexContainerAscent;
5545   }
5546 
5547   // Convert flex container's final desired size to parent's WM, for outparam.
5548   aReflowOutput.SetSize(flexWM, desiredSizeInFlexWM);
5549 }
5550 
MoveFlexItemToFinalPosition(const ReflowInput & aReflowInput,const FlexItem & aItem,LogicalPoint & aFramePos,const nsSize & aContainerSize)5551 void nsFlexContainerFrame::MoveFlexItemToFinalPosition(
5552     const ReflowInput& aReflowInput, const FlexItem& aItem,
5553     LogicalPoint& aFramePos, const nsSize& aContainerSize) {
5554   FLEX_LOG("Moving flex item %p to its desired position %s", aItem.Frame(),
5555            ToString(aFramePos).c_str());
5556 
5557   WritingMode outerWM = aReflowInput.GetWritingMode();
5558 
5559   // If item is relpos, look up its offsets (cached from prev reflow)
5560   LogicalMargin logicalOffsets(outerWM);
5561   // Bug 1758020: Should we call IsRelativelyOrStickyPositionedStyle()?
5562   if (aItem.Frame()->StyleDisplay()->IsRelativelyPositionedStyle()) {
5563     nsMargin* cachedOffsets =
5564         aItem.Frame()->GetProperty(nsIFrame::ComputedOffsetProperty());
5565     MOZ_ASSERT(cachedOffsets,
5566                "relpos previously-reflowed frame should've cached its offsets");
5567     logicalOffsets = LogicalMargin(outerWM, *cachedOffsets);
5568   }
5569   ReflowInput::ApplyRelativePositioning(aItem.Frame(), outerWM, logicalOffsets,
5570                                         &aFramePos, aContainerSize);
5571   aItem.Frame()->SetPosition(outerWM, aFramePos, aContainerSize);
5572   PositionFrameView(aItem.Frame());
5573   PositionChildViews(aItem.Frame());
5574 }
5575 
ReflowFlexItem(const FlexboxAxisTracker & aAxisTracker,const ReflowInput & aReflowInput,const FlexItem & aItem,LogicalPoint & aFramePos,const LogicalSize & aAvailableSize,const nsSize & aContainerSize,bool aHasLineClampEllipsis)5576 nsReflowStatus nsFlexContainerFrame::ReflowFlexItem(
5577     const FlexboxAxisTracker& aAxisTracker, const ReflowInput& aReflowInput,
5578     const FlexItem& aItem, LogicalPoint& aFramePos,
5579     const LogicalSize& aAvailableSize, const nsSize& aContainerSize,
5580     bool aHasLineClampEllipsis) {
5581   FLEX_LOG("Doing final reflow for flex item %p", aItem.Frame());
5582 
5583   WritingMode outerWM = aReflowInput.GetWritingMode();
5584 
5585   StyleSizeOverrides sizeOverrides;
5586   // Override flex item's main size.
5587   if (aItem.IsInlineAxisMainAxis()) {
5588     sizeOverrides.mStyleISize.emplace(aItem.StyleMainSize());
5589   } else {
5590     sizeOverrides.mStyleBSize.emplace(aItem.StyleMainSize());
5591   }
5592   FLEX_LOGV(" Main size override: %d", aItem.MainSize());
5593 
5594   // Override flex item's cross size if it was stretched in the cross axis (in
5595   // which case we're imposing a cross size).
5596   if (aItem.IsStretched()) {
5597     if (aItem.IsInlineAxisCrossAxis()) {
5598       sizeOverrides.mStyleISize.emplace(aItem.StyleCrossSize());
5599     } else {
5600       sizeOverrides.mStyleBSize.emplace(aItem.StyleCrossSize());
5601     }
5602     FLEX_LOGV(" Cross size override: %d", aItem.CrossSize());
5603   }
5604   if (sizeOverrides.mStyleBSize) {
5605     // We are overriding the block-size. For robustness, we always assume that
5606     // this represents a block-axis resize for the frame. This may be
5607     // conservative, but we do capture all the conditions in the block-axis
5608     // (checked in NeedsFinalReflow()) that make this item require a final
5609     // reflow. This sets relevant flags in ReflowInput::InitResizeFlags().
5610     aItem.Frame()->SetHasBSizeChange(true);
5611   }
5612 
5613   ReflowInput childReflowInput(PresContext(), aReflowInput, aItem.Frame(),
5614                                aAvailableSize, Nothing(), {}, sizeOverrides);
5615   childReflowInput.mFlags.mInsideLineClamp = GetLineClampValue() != 0;
5616   // This is the final reflow of this flex item; if we previously had a
5617   // -webkit-line-clamp, and we missed our chance to clear the ellipsis
5618   // because we didn't need to call MeasureFlexItemContentBSize, we set
5619   // mApplyLineClamp to cause it to get cleared here.
5620   childReflowInput.mFlags.mApplyLineClamp =
5621       !childReflowInput.mFlags.mInsideLineClamp && aHasLineClampEllipsis;
5622 
5623   if (aItem.TreatBSizeAsIndefinite() && aItem.IsBlockAxisMainAxis()) {
5624     childReflowInput.mFlags.mTreatBSizeAsIndefinite = true;
5625   }
5626 
5627   if (aItem.IsStretched() && aItem.IsBlockAxisCrossAxis()) {
5628     // This item is stretched (in the cross axis), and that axis is its block
5629     // axis.  That stretching effectively gives it a relative BSize.
5630     // XXXdholbert This flag only makes a difference if we use the flex items'
5631     // frame-state when deciding whether to reflow them -- and we don't, as of
5632     // the changes in bug 851607. So this has no effect right now, but it might
5633     // make a difference if we optimize to use dirty bits in the
5634     // future. (Reftests flexbox-resizeviewport-1.xhtml and -2.xhtml are
5635     // intended to catch any regressions here, if we end up relying on this bit
5636     // & neglecting to set it.)
5637     aItem.Frame()->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
5638   }
5639 
5640   // NOTE: Be very careful about doing anything else with childReflowInput
5641   // after this point, because some of its methods (e.g. SetComputedWidth)
5642   // internally call InitResizeFlags and stomp on mVResize & mHResize.
5643 
5644   // CachedFlexItemData is stored in item's writing mode, so we pass
5645   // aChildReflowInput into ReflowOutput's constructor.
5646   ReflowOutput childReflowOutput(childReflowInput);
5647   nsReflowStatus childReflowStatus;
5648   ReflowChild(aItem.Frame(), PresContext(), childReflowOutput, childReflowInput,
5649               outerWM, aFramePos, aContainerSize, ReflowChildFlags::Default,
5650               childReflowStatus);
5651 
5652   // XXXdholbert Perhaps we should call CheckForInterrupt here; see bug 1495532.
5653 
5654   FinishReflowChild(aItem.Frame(), PresContext(), childReflowOutput,
5655                     &childReflowInput, outerWM, aFramePos, aContainerSize,
5656                     ReflowChildFlags::ApplyRelativePositioning);
5657 
5658   aItem.SetAscent(childReflowOutput.BlockStartAscent());
5659 
5660   // Update our cached flex item info:
5661   if (auto* cached = aItem.Frame()->GetProperty(CachedFlexItemData::Prop())) {
5662     cached->Update(childReflowInput, childReflowOutput,
5663                    FlexItemReflowType::Final);
5664   } else {
5665     cached = new CachedFlexItemData(childReflowInput, childReflowOutput,
5666                                     FlexItemReflowType::Final);
5667     aItem.Frame()->SetProperty(CachedFlexItemData::Prop(), cached);
5668   }
5669 
5670   return childReflowStatus;
5671 }
5672 
ReflowPlaceholders(const ReflowInput & aReflowInput,nsTArray<nsIFrame * > & aPlaceholders,const LogicalPoint & aContentBoxOrigin,const nsSize & aContainerSize)5673 void nsFlexContainerFrame::ReflowPlaceholders(
5674     const ReflowInput& aReflowInput, nsTArray<nsIFrame*>& aPlaceholders,
5675     const LogicalPoint& aContentBoxOrigin, const nsSize& aContainerSize) {
5676   WritingMode outerWM = aReflowInput.GetWritingMode();
5677 
5678   // As noted in this method's documentation, we'll reflow every entry in
5679   // |aPlaceholders| at the container's content-box origin.
5680   for (nsIFrame* placeholder : aPlaceholders) {
5681     MOZ_ASSERT(placeholder->IsPlaceholderFrame(),
5682                "placeholders array should only contain placeholder frames");
5683     WritingMode wm = placeholder->GetWritingMode();
5684     LogicalSize availSize = aReflowInput.ComputedSize(wm);
5685     ReflowInput childReflowInput(PresContext(), aReflowInput, placeholder,
5686                                  availSize);
5687     // No need to set the -webkit-line-clamp related flags when reflowing
5688     // a placeholder.
5689     ReflowOutput childReflowOutput(outerWM);
5690     nsReflowStatus childReflowStatus;
5691     ReflowChild(placeholder, PresContext(), childReflowOutput, childReflowInput,
5692                 outerWM, aContentBoxOrigin, aContainerSize,
5693                 ReflowChildFlags::Default, childReflowStatus);
5694 
5695     FinishReflowChild(placeholder, PresContext(), childReflowOutput,
5696                       &childReflowInput, outerWM, aContentBoxOrigin,
5697                       aContainerSize, ReflowChildFlags::Default);
5698 
5699     // Mark the placeholder frame to indicate that it's not actually at the
5700     // element's static position, because we need to apply CSS Alignment after
5701     // we determine the OOF's size:
5702     placeholder->AddStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN);
5703   }
5704 }
5705 
IntrinsicISize(gfxContext * aRenderingContext,IntrinsicISizeType aType)5706 nscoord nsFlexContainerFrame::IntrinsicISize(gfxContext* aRenderingContext,
5707                                              IntrinsicISizeType aType) {
5708   nscoord containerISize = 0;
5709   const nsStylePosition* stylePos = StylePosition();
5710   const FlexboxAxisTracker axisTracker(this);
5711 
5712   nscoord mainGapSize;
5713   if (axisTracker.IsRowOriented()) {
5714     mainGapSize = nsLayoutUtils::ResolveGapToLength(stylePos->mColumnGap,
5715                                                     NS_UNCONSTRAINEDSIZE);
5716   } else {
5717     mainGapSize = nsLayoutUtils::ResolveGapToLength(stylePos->mRowGap,
5718                                                     NS_UNCONSTRAINEDSIZE);
5719   }
5720 
5721   const bool useMozBoxCollapseBehavior =
5722       ShouldUseMozBoxCollapseBehavior(StyleDisplay());
5723 
5724   // The loop below sets aside space for a gap before each item besides the
5725   // first. This bool helps us handle that special-case.
5726   bool onFirstChild = true;
5727 
5728   for (nsIFrame* childFrame : mFrames) {
5729     // Skip out-of-flow children because they don't participate in flex layout.
5730     if (childFrame->IsPlaceholderFrame()) {
5731       continue;
5732     }
5733 
5734     // If we're using legacy "visibility:collapse" behavior, then we don't
5735     // care about the sizes of any collapsed children.
5736     if (!useMozBoxCollapseBehavior ||
5737         (StyleVisibility::Collapse !=
5738          childFrame->StyleVisibility()->mVisible)) {
5739       nscoord childISize = nsLayoutUtils::IntrinsicForContainer(
5740           aRenderingContext, childFrame, aType);
5741       // * For a row-oriented single-line flex container, the intrinsic
5742       // {min/pref}-isize is the sum of its items' {min/pref}-isizes and
5743       // (n-1) column gaps.
5744       // * For a column-oriented flex container, the intrinsic min isize
5745       // is the max of its items' min isizes.
5746       // * For a row-oriented multi-line flex container, the intrinsic
5747       // pref isize is former (sum), and its min isize is the latter (max).
5748       bool isSingleLine = (StyleFlexWrap::Nowrap == stylePos->mFlexWrap);
5749       if (axisTracker.IsRowOriented() &&
5750           (isSingleLine || aType == IntrinsicISizeType::PrefISize)) {
5751         containerISize += childISize;
5752         if (!onFirstChild) {
5753           containerISize += mainGapSize;
5754         }
5755         onFirstChild = false;
5756       } else {  // (col-oriented, or MinISize for multi-line row flex container)
5757         containerISize = std::max(containerISize, childISize);
5758       }
5759     }
5760   }
5761 
5762   return containerISize;
5763 }
5764 
5765 /* virtual */
GetMinISize(gfxContext * aRenderingContext)5766 nscoord nsFlexContainerFrame::GetMinISize(gfxContext* aRenderingContext) {
5767   DISPLAY_MIN_INLINE_SIZE(this, mCachedMinISize);
5768   if (mCachedMinISize == NS_INTRINSIC_ISIZE_UNKNOWN) {
5769     mCachedMinISize =
5770         StyleDisplay()->IsContainSize()
5771             ? 0
5772             : IntrinsicISize(aRenderingContext, IntrinsicISizeType::MinISize);
5773   }
5774 
5775   return mCachedMinISize;
5776 }
5777 
5778 /* virtual */
GetPrefISize(gfxContext * aRenderingContext)5779 nscoord nsFlexContainerFrame::GetPrefISize(gfxContext* aRenderingContext) {
5780   DISPLAY_PREF_INLINE_SIZE(this, mCachedPrefISize);
5781   if (mCachedPrefISize == NS_INTRINSIC_ISIZE_UNKNOWN) {
5782     mCachedPrefISize =
5783         StyleDisplay()->IsContainSize()
5784             ? 0
5785             : IntrinsicISize(aRenderingContext, IntrinsicISizeType::PrefISize);
5786   }
5787 
5788   return mCachedPrefISize;
5789 }
5790 
GetLineClampValue() const5791 uint32_t nsFlexContainerFrame::GetLineClampValue() const {
5792   // -webkit-line-clamp should only work on items in flex containers that are
5793   // display:-webkit-(inline-)box and -webkit-box-orient:vertical.
5794   //
5795   // This check makes -webkit-line-clamp work on display:-moz-box too, but
5796   // that shouldn't be a big deal.
5797   if (!HasAnyStateBits(NS_STATE_FLEX_IS_EMULATING_LEGACY_BOX) ||
5798       StyleXUL()->mBoxOrient != StyleBoxOrient::Vertical) {
5799     return 0;
5800   }
5801 
5802   return StyleDisplay()->mLineClamp;
5803 }
5804