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