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/Logging.h"
18 #include "mozilla/PresShell.h"
19 #include "mozilla/StaticPrefs_layout.h"
20 #include "mozilla/WritingModes.h"
21 #include "nsBlockFrame.h"
22 #include "nsContentUtils.h"
24 #include "nsDisplayList.h"
25 #include "nsFieldSetFrame.h"
26 #include "nsIFrameInlines.h"
27 #include "nsLayoutUtils.h"
28 #include "nsPlaceholderFrame.h"
29 #include "nsPresContext.h"
31 using namespace mozilla
;
32 using namespace mozilla::layout
;
34 // Convenience typedefs for helper classes that we forward-declare in .h file
35 // (so that nsFlexContainerFrame methods can use them as parameters):
36 using FlexItem
= nsFlexContainerFrame::FlexItem
;
37 using FlexLine
= nsFlexContainerFrame::FlexLine
;
38 using FlexboxAxisTracker
= nsFlexContainerFrame::FlexboxAxisTracker
;
39 using StrutInfo
= nsFlexContainerFrame::StrutInfo
;
40 using CachedBAxisMeasurement
= nsFlexContainerFrame::CachedBAxisMeasurement
;
41 using CachedFlexItemData
= nsFlexContainerFrame::CachedFlexItemData
;
43 static mozilla::LazyLogModule
gFlexContainerLog("FlexContainer");
45 // FLEX_LOG is a top-level general log print.
46 #define FLEX_LOG(message, ...) \
47 MOZ_LOG(gFlexContainerLog, LogLevel::Debug, (message, ##__VA_ARGS__));
49 // FLEX_ITEM_LOG is a top-level log print for flex item.
50 #define FLEX_ITEM_LOG(item_frame, message, ...) \
51 MOZ_LOG(gFlexContainerLog, LogLevel::Debug, \
52 ("Flex item %p: " message, item_frame, ##__VA_ARGS__));
54 // FLEX_LOGV is a verbose log print with built-in two spaces indentation. The
55 // convention to use FLEX_LOGV is that FLEX_LOGV statements should generally be
56 // preceded by one FLEX_LOG or FLEX_ITEM_LOG so that there's no need to repeat
57 // information presented in the preceding LOG statement. If you want extra level
58 // of indentation, just add two extra spaces at the start of the message string.
59 #define FLEX_LOGV(message, ...) \
60 MOZ_LOG(gFlexContainerLog, LogLevel::Verbose, (" " message, ##__VA_ARGS__));
62 static const char* BoolToYesNo(bool aArg
) { return aArg
? "yes" : "no"; }
64 // Returns true if aFlexContainer is a frame for some element that has
65 // display:-webkit-{inline-}box (or -moz-{inline-}box). aFlexContainer is
66 // expected to be an instance of nsFlexContainerFrame (enforced with an assert);
67 // otherwise, this function's state-bit-check here is bogus.
68 static bool IsLegacyBox(const nsIFrame
* aFlexContainer
) {
69 MOZ_ASSERT(aFlexContainer
->IsFlexContainerFrame(),
70 "only flex containers may be passed to this function");
71 return aFlexContainer
->HasAnyStateBits(
72 NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX
);
75 // Returns the OrderState enum we should pass to CSSOrderAwareFrameIterator
76 // (depending on whether aFlexContainer has
77 // NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER state bit).
78 static CSSOrderAwareFrameIterator::OrderState
OrderStateForIter(
79 const nsFlexContainerFrame
* aFlexContainer
) {
80 return aFlexContainer
->HasAnyStateBits(
81 NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER
)
82 ? CSSOrderAwareFrameIterator::OrderState::Ordered
83 : CSSOrderAwareFrameIterator::OrderState::Unordered
;
86 // Returns the OrderingProperty enum that we should pass to
87 // CSSOrderAwareFrameIterator (depending on whether it's a legacy box).
88 static CSSOrderAwareFrameIterator::OrderingProperty
OrderingPropertyForIter(
89 const nsFlexContainerFrame
* aFlexContainer
) {
90 return IsLegacyBox(aFlexContainer
)
91 ? CSSOrderAwareFrameIterator::OrderingProperty::BoxOrdinalGroup
92 : CSSOrderAwareFrameIterator::OrderingProperty::Order
;
95 // Returns the "align-items" value that's equivalent to the legacy "box-align"
96 // value in the given style struct.
97 static StyleAlignFlags
ConvertLegacyStyleToAlignItems(
98 const nsStyleXUL
* aStyleXUL
) {
99 // -[moz|webkit]-box-align corresponds to modern "align-items"
100 switch (aStyleXUL
->mBoxAlign
) {
101 case StyleBoxAlign::Stretch
:
102 return StyleAlignFlags::STRETCH
;
103 case StyleBoxAlign::Start
:
104 return StyleAlignFlags::FLEX_START
;
105 case StyleBoxAlign::Center
:
106 return StyleAlignFlags::CENTER
;
107 case StyleBoxAlign::Baseline
:
108 return StyleAlignFlags::BASELINE
;
109 case StyleBoxAlign::End
:
110 return StyleAlignFlags::FLEX_END
;
113 MOZ_ASSERT_UNREACHABLE("Unrecognized mBoxAlign enum value");
114 // Fall back to default value of "align-items" property:
115 return StyleAlignFlags::STRETCH
;
118 // Returns the "justify-content" value that's equivalent to the legacy
119 // "box-pack" value in the given style struct.
120 static StyleContentDistribution
ConvertLegacyStyleToJustifyContent(
121 const nsStyleXUL
* aStyleXUL
) {
122 // -[moz|webkit]-box-pack corresponds to modern "justify-content"
123 switch (aStyleXUL
->mBoxPack
) {
124 case StyleBoxPack::Start
:
125 return {StyleAlignFlags::FLEX_START
};
126 case StyleBoxPack::Center
:
127 return {StyleAlignFlags::CENTER
};
128 case StyleBoxPack::End
:
129 return {StyleAlignFlags::FLEX_END
};
130 case StyleBoxPack::Justify
:
131 return {StyleAlignFlags::SPACE_BETWEEN
};
134 MOZ_ASSERT_UNREACHABLE("Unrecognized mBoxPack enum value");
135 // Fall back to default value of "justify-content" property:
136 return {StyleAlignFlags::FLEX_START
};
139 // Check if the size is auto or it is a keyword in the block axis.
140 // |aIsInline| should represent whether aSize is in the inline axis, from the
141 // perspective of the writing mode of the flex item that the size comes from.
143 // max-content and min-content should behave as property's initial value.
144 // Bug 567039: We treat -moz-fit-content and -moz-available as property's
145 // initial value for now.
146 static inline bool IsAutoOrEnumOnBSize(const StyleSize
& aSize
, bool aIsInline
) {
147 return aSize
.IsAuto() || (!aIsInline
&& !aSize
.IsLengthPercentage());
150 // Encapsulates our flex container's main & cross axes. This class is backed by
151 // a FlexboxAxisInfo helper member variable, and it adds some convenience APIs
152 // on top of what that struct offers.
153 class MOZ_STACK_CLASS
nsFlexContainerFrame::FlexboxAxisTracker
{
155 explicit FlexboxAxisTracker(const nsFlexContainerFrame
* aFlexContainer
);
158 LogicalAxis
MainAxis() const {
159 return IsRowOriented() ? LogicalAxis::Inline
: LogicalAxis::Block
;
161 LogicalAxis
CrossAxis() const {
162 return IsRowOriented() ? LogicalAxis::Block
: LogicalAxis::Inline
;
165 LogicalSide
MainAxisStartSide() const;
166 LogicalSide
MainAxisEndSide() const {
167 return GetOppositeSide(MainAxisStartSide());
170 LogicalSide
CrossAxisStartSide() const;
171 LogicalSide
CrossAxisEndSide() const {
172 return GetOppositeSide(CrossAxisStartSide());
175 mozilla::Side
MainAxisPhysicalStartSide() const {
176 return mWM
.PhysicalSide(MainAxisStartSide());
178 mozilla::Side
MainAxisPhysicalEndSide() const {
179 return mWM
.PhysicalSide(MainAxisEndSide());
182 mozilla::Side
CrossAxisPhysicalStartSide() const {
183 return mWM
.PhysicalSide(CrossAxisStartSide());
185 mozilla::Side
CrossAxisPhysicalEndSide() const {
186 return mWM
.PhysicalSide(CrossAxisEndSide());
189 // Returns the flex container's writing mode.
190 WritingMode
GetWritingMode() const { return mWM
; }
192 // Returns true if our main axis is in the reverse direction of our
193 // writing mode's corresponding axis. (From 'flex-direction: *-reverse')
194 bool IsMainAxisReversed() const { return mAxisInfo
.mIsMainAxisReversed
; }
195 // Returns true if our cross axis is in the reverse direction of our
196 // writing mode's corresponding axis. (From 'flex-wrap: *-reverse')
197 bool IsCrossAxisReversed() const { return mAxisInfo
.mIsCrossAxisReversed
; }
199 bool IsRowOriented() const { return mAxisInfo
.mIsRowOriented
; }
200 bool IsColumnOriented() const { return !IsRowOriented(); }
202 // aSize is expected to match the flex container's WritingMode.
203 nscoord
MainComponent(const LogicalSize
& aSize
) const {
204 return IsRowOriented() ? aSize
.ISize(mWM
) : aSize
.BSize(mWM
);
206 int32_t MainComponent(const LayoutDeviceIntSize
& aIntSize
) const {
207 return IsMainAxisHorizontal() ? aIntSize
.width
: aIntSize
.height
;
210 // aSize is expected to match the flex container's WritingMode.
211 nscoord
CrossComponent(const LogicalSize
& aSize
) const {
212 return IsRowOriented() ? aSize
.BSize(mWM
) : aSize
.ISize(mWM
);
214 int32_t CrossComponent(const LayoutDeviceIntSize
& aIntSize
) const {
215 return IsMainAxisHorizontal() ? aIntSize
.height
: aIntSize
.width
;
218 // NOTE: aMargin is expected to use the flex container's WritingMode.
219 nscoord
MarginSizeInMainAxis(const LogicalMargin
& aMargin
) const {
220 // If we're row-oriented, our main axis is the inline axis.
221 return IsRowOriented() ? aMargin
.IStartEnd(mWM
) : aMargin
.BStartEnd(mWM
);
223 nscoord
MarginSizeInCrossAxis(const LogicalMargin
& aMargin
) const {
224 // If we're row-oriented, our cross axis is the block axis.
225 return IsRowOriented() ? aMargin
.BStartEnd(mWM
) : aMargin
.IStartEnd(mWM
);
229 * Converts a "flex-relative" point (a main-axis & cross-axis coordinate)
230 * into a LogicalPoint, using the flex container's writing mode.
232 * @arg aMainCoord The main-axis coordinate -- i.e an offset from the
233 * main-start edge of the flex container's content box.
234 * @arg aCrossCoord The cross-axis coordinate -- i.e an offset from the
235 * cross-start edge of the flex container's content box.
236 * @arg aContainerMainSize The main size of flex container's content box.
237 * @arg aContainerCrossSize The cross size of flex container's content box.
238 * @return A LogicalPoint, with the flex container's writing mode, that
239 * represents the same position. The logical coordinates are
240 * relative to the flex container's content box.
242 LogicalPoint
LogicalPointFromFlexRelativePoint(
243 nscoord aMainCoord
, nscoord aCrossCoord
, nscoord aContainerMainSize
,
244 nscoord aContainerCrossSize
) const {
245 nscoord logicalCoordInMainAxis
=
246 IsMainAxisReversed() ? aContainerMainSize
- aMainCoord
: aMainCoord
;
247 nscoord logicalCoordInCrossAxis
=
248 IsCrossAxisReversed() ? aContainerCrossSize
- aCrossCoord
: aCrossCoord
;
250 return IsRowOriented() ? LogicalPoint(mWM
, logicalCoordInMainAxis
,
251 logicalCoordInCrossAxis
)
252 : LogicalPoint(mWM
, logicalCoordInCrossAxis
,
253 logicalCoordInMainAxis
);
257 * Converts a "flex-relative" size (a main-axis & cross-axis size)
258 * into a LogicalSize, using the flex container's writing mode.
260 * @arg aMainSize The main-axis size.
261 * @arg aCrossSize The cross-axis size.
262 * @return A LogicalSize, with the flex container's writing mode, that
263 * represents the same size.
265 LogicalSize
LogicalSizeFromFlexRelativeSizes(nscoord aMainSize
,
266 nscoord aCrossSize
) const {
267 return IsRowOriented() ? LogicalSize(mWM
, aMainSize
, aCrossSize
)
268 : LogicalSize(mWM
, aCrossSize
, aMainSize
);
272 * Converts a "flex-relative" ascent (the distance from the flex container's
273 * content-box cross-start edge to its baseline) into a logical ascent (the
274 * distance from the flex container's content-box block-start edge to its
277 nscoord
LogicalAscentFromFlexRelativeAscent(
278 nscoord aFlexRelativeAscent
, nscoord aContentBoxCrossSize
) const {
279 return (IsCrossAxisReversed() ? aContentBoxCrossSize
- aFlexRelativeAscent
280 : aFlexRelativeAscent
);
283 bool IsMainAxisHorizontal() const {
284 // If we're row-oriented, and our writing mode is NOT vertical,
285 // or we're column-oriented and our writing mode IS vertical,
286 // then our main axis is horizontal. This handles all cases:
287 return IsRowOriented() != mWM
.IsVertical();
290 // Returns true if this flex item's inline axis in aItemWM is parallel (or
291 // antiparallel) to the container's main axis. Returns false, otherwise.
293 // Note: this is a helper used before constructing FlexItem. Inside of flex
294 // reflow code, FlexItem::IsInlineAxisMainAxis() is equivalent & more optimal.
295 bool IsInlineAxisMainAxis(WritingMode aItemWM
) const {
296 return IsRowOriented() != GetWritingMode().IsOrthogonalTo(aItemWM
);
299 // Maps justify-*: 'left' or 'right' to 'start' or 'end'.
300 StyleAlignFlags
ResolveJustifyLeftRight(const StyleAlignFlags
& aFlags
) const {
302 aFlags
== StyleAlignFlags::LEFT
|| aFlags
== StyleAlignFlags::RIGHT
,
303 "This helper accepts only 'LEFT' or 'RIGHT' flags!");
305 const auto wm
= GetWritingMode();
306 const bool isJustifyLeft
= aFlags
== StyleAlignFlags::LEFT
;
307 if (IsColumnOriented()) {
308 if (!wm
.IsVertical()) {
309 // Container's alignment axis (main axis) is *not* parallel to the
310 // line-left <-> line-right axis or the physical left <-> physical right
311 // axis, so we map both 'left' and 'right' to 'start'.
312 return StyleAlignFlags::START
;
315 MOZ_ASSERT(wm
.PhysicalAxis(MainAxis()) == PhysicalAxis::Horizontal
,
316 "Vertical column-oriented flex container's main axis should "
317 "be parallel to physical left <-> right axis!");
318 // Map 'left' or 'right' to 'start' or 'end', depending on its block flow
320 return isJustifyLeft
== wm
.IsVerticalLR() ? StyleAlignFlags::START
321 : StyleAlignFlags::END
;
324 MOZ_ASSERT(MainAxis() == LogicalAxis::Inline
,
325 "Row-oriented flex container's main axis should be parallel to "
326 "line-left <-> line-right axis!");
328 // If we get here, we're operating on the flex container's inline axis,
329 // so we map 'left' to whichever of 'start' or 'end' corresponds to the
330 // *line-relative* left side; and similar for 'right'.
331 return isJustifyLeft
== wm
.IsBidiLTR() ? StyleAlignFlags::START
332 : StyleAlignFlags::END
;
335 // Delete copy-constructor & reassignment operator, to prevent accidental
336 // (unnecessary) copying.
337 FlexboxAxisTracker(const FlexboxAxisTracker
&) = delete;
338 FlexboxAxisTracker
& operator=(const FlexboxAxisTracker
&) = delete;
341 const WritingMode mWM
; // The flex container's writing mode.
342 const FlexboxAxisInfo mAxisInfo
;
346 * Represents a flex item.
347 * Includes the various pieces of input that the Flexbox Layout Algorithm uses
348 * to resolve a flexible width.
350 class nsFlexContainerFrame::FlexItem final
{
352 // Normal constructor:
353 FlexItem(ReflowInput
& aFlexItemReflowInput
, float aFlexGrow
,
354 float aFlexShrink
, nscoord aFlexBaseSize
, nscoord aMainMinSize
,
355 nscoord aMainMaxSize
, nscoord aTentativeCrossSize
,
356 nscoord aCrossMinSize
, nscoord aCrossMaxSize
,
357 const FlexboxAxisTracker
& aAxisTracker
);
359 // Simplified constructor, to be used only for generating "struts":
360 // (NOTE: This "strut" constructor uses the *container's* writing mode, which
361 // we'll use on this FlexItem instead of the child frame's real writing mode.
362 // This is fine - it doesn't matter what writing mode we use for a
363 // strut, since it won't render any content and we already know its size.)
364 FlexItem(nsIFrame
* aChildFrame
, nscoord aCrossSize
, WritingMode aContainerWM
,
365 const FlexboxAxisTracker
& aAxisTracker
);
367 // Clone existing FlexItem for its underlying frame's continuation.
368 // @param aContinuation a continuation in our next-in-flow chain.
369 FlexItem
CloneFor(nsIFrame
* const aContinuation
) const {
370 MOZ_ASSERT(Frame() == aContinuation
->FirstInFlow(),
371 "aContinuation should be in aItem's continuation chain!");
372 FlexItem
item(*this);
373 item
.mFrame
= aContinuation
;
374 item
.mHadMeasuringReflow
= false;
379 nsIFrame
* Frame() const { return mFrame
; }
380 nscoord
FlexBaseSize() const { return mFlexBaseSize
; }
382 nscoord
MainMinSize() const {
383 MOZ_ASSERT(!mNeedsMinSizeAutoResolution
,
384 "Someone's using an unresolved 'auto' main min-size");
387 nscoord
MainMaxSize() const { return mMainMaxSize
; }
389 // Note: These return the main-axis position and size of our *content box*.
390 nscoord
MainSize() const { return mMainSize
; }
391 nscoord
MainPosition() const { return mMainPosn
; }
393 nscoord
CrossMinSize() const { return mCrossMinSize
; }
394 nscoord
CrossMaxSize() const { return mCrossMaxSize
; }
396 // Note: These return the cross-axis position and size of our *content box*.
397 nscoord
CrossSize() const { return mCrossSize
; }
398 nscoord
CrossPosition() const { return mCrossPosn
; }
400 // Lazy getter for mAscent or mAscentForLast.
401 nscoord
ResolvedAscent(bool aUseFirstBaseline
) const {
402 // XXX We should be using the *container's* writing-mode (mCBWM) here,
403 // instead of the item's (mWM). This is essentially bug 1155322.
404 nscoord
& ascent
= aUseFirstBaseline
? mAscent
: mAscentForLast
;
405 if (ascent
!= ReflowOutput::ASK_FOR_BASELINE
) {
409 // Use GetFirstLineBaseline() or GetLastLineBaseline() as appropriate:
410 bool found
= aUseFirstBaseline
411 ? nsLayoutUtils::GetFirstLineBaseline(mWM
, mFrame
, &ascent
)
412 : nsLayoutUtils::GetLastLineBaseline(mWM
, mFrame
, &ascent
);
417 // If the nsLayoutUtils getter fails, then ask the frame directly:
418 auto baselineGroup
= aUseFirstBaseline
? BaselineSharingGroup::First
419 : BaselineSharingGroup::Last
;
420 if (auto baseline
= mFrame
->GetNaturalBaselineBOffset(
421 mWM
, baselineGroup
, BaselineExportContext::Other
)) {
422 // Offset for last baseline from `GetNaturalBaselineBOffset` originates
423 // from the frame's block end, so convert it back.
424 ascent
= baselineGroup
== BaselineSharingGroup::First
426 : mFrame
->BSize(mWM
) - *baseline
;
430 // We couldn't determine a baseline, so we synthesize one from border box:
431 ascent
= Baseline::SynthesizeBOffsetFromBorderBox(
432 mFrame
, mWM
, BaselineSharingGroup::First
);
436 // Convenience methods to compute the main & cross size of our *margin-box*.
437 nscoord
OuterMainSize() const {
438 return mMainSize
+ MarginBorderPaddingSizeInMainAxis();
441 nscoord
OuterCrossSize() const {
442 return mCrossSize
+ MarginBorderPaddingSizeInCrossAxis();
445 // Convenience method to return the content-box block-size.
446 nscoord
BSize() const {
447 return IsBlockAxisMainAxis() ? MainSize() : CrossSize();
450 // Convenience method to return the measured content-box block-size computed
451 // in nsFlexContainerFrame::MeasureBSizeForFlexItem().
452 Maybe
<nscoord
> MeasuredBSize() const;
454 // Convenience methods to synthesize a style main size or a style cross size
455 // with box-size considered, to provide the size overrides when constructing
456 // ReflowInput for flex items.
457 StyleSize
StyleMainSize() const {
458 nscoord mainSize
= MainSize();
459 if (Frame()->StylePosition()->mBoxSizing
== StyleBoxSizing::Border
) {
460 mainSize
+= BorderPaddingSizeInMainAxis();
462 return StyleSize::LengthPercentage(
463 LengthPercentage::FromAppUnits(mainSize
));
466 StyleSize
StyleCrossSize() const {
467 nscoord crossSize
= CrossSize();
468 if (Frame()->StylePosition()->mBoxSizing
== StyleBoxSizing::Border
) {
469 crossSize
+= BorderPaddingSizeInCrossAxis();
471 return StyleSize::LengthPercentage(
472 LengthPercentage::FromAppUnits(crossSize
));
475 // Returns the distance between this FlexItem's baseline and the cross-start
476 // edge of its margin-box. Used in baseline alignment.
478 // (This function needs to be told which physical start side we're measuring
479 // the baseline from, so that it can look up the appropriate components from
481 nscoord
BaselineOffsetFromOuterCrossEdge(mozilla::Side aStartSide
,
482 bool aUseFirstLineBaseline
) const;
484 double ShareOfWeightSoFar() const { return mShareOfWeightSoFar
; }
486 bool IsFrozen() const { return mIsFrozen
; }
488 bool HadMinViolation() const {
489 MOZ_ASSERT(!mIsFrozen
, "min violation has no meaning for frozen items.");
490 return mHadMinViolation
;
493 bool HadMaxViolation() const {
494 MOZ_ASSERT(!mIsFrozen
, "max violation has no meaning for frozen items.");
495 return mHadMaxViolation
;
498 bool WasMinClamped() const {
499 MOZ_ASSERT(mIsFrozen
, "min clamping has no meaning for unfrozen items.");
500 return mHadMinViolation
;
503 bool WasMaxClamped() const {
504 MOZ_ASSERT(mIsFrozen
, "max clamping has no meaning for unfrozen items.");
505 return mHadMaxViolation
;
508 // Indicates whether this item received a preliminary "measuring" reflow
509 // before its actual reflow.
510 bool HadMeasuringReflow() const { return mHadMeasuringReflow
; }
512 // Indicates whether this item's computed cross-size property is 'auto'.
513 bool IsCrossSizeAuto() const;
515 // Indicates whether the cross-size property is set to something definite,
516 // for the purpose of preferred aspect ratio calculations.
517 bool IsCrossSizeDefinite(const ReflowInput
& aItemReflowInput
) const;
519 // Indicates whether this item's cross-size has been stretched (from having
520 // "align-self: stretch" with an auto cross-size and no auto margins in the
522 bool IsStretched() const { return mIsStretched
; }
524 bool IsFlexBaseSizeContentBSize() const {
525 return mIsFlexBaseSizeContentBSize
;
528 bool IsMainMinSizeContentBSize() const { return mIsMainMinSizeContentBSize
; }
530 // Indicates whether we need to resolve an 'auto' value for the main-axis
531 // min-[width|height] property.
532 bool NeedsMinSizeAutoResolution() const {
533 return mNeedsMinSizeAutoResolution
;
536 bool HasAnyAutoMargin() const { return mHasAnyAutoMargin
; }
538 BaselineSharingGroup
ItemBaselineSharingGroup() const {
539 MOZ_ASSERT(mAlignSelf
._0
== StyleAlignFlags::BASELINE
||
540 mAlignSelf
._0
== StyleAlignFlags::LAST_BASELINE
,
541 "mBaselineSharingGroup only gets a meaningful value "
542 "for baseline-aligned items");
543 return mBaselineSharingGroup
;
546 // Indicates whether this item is a "strut" left behind by an element with
547 // visibility:collapse.
548 bool IsStrut() const { return mIsStrut
; }
550 // The main axis and cross axis are relative to mCBWM.
551 LogicalAxis
MainAxis() const { return mMainAxis
; }
552 LogicalAxis
CrossAxis() const { return GetOrthogonalAxis(mMainAxis
); }
554 // IsInlineAxisMainAxis() returns true if this item's inline axis is parallel
555 // (or antiparallel) to the container's main axis. Otherwise (i.e. if this
556 // item's inline axis is orthogonal to the container's main axis), this
557 // function returns false. The next 3 methods are all other ways of asking
558 // the same question, and only exist for readability at callsites (depending
559 // on which axes those callsites are reasoning about).
560 bool IsInlineAxisMainAxis() const { return mIsInlineAxisMainAxis
; }
561 bool IsInlineAxisCrossAxis() const { return !mIsInlineAxisMainAxis
; }
562 bool IsBlockAxisMainAxis() const { return !mIsInlineAxisMainAxis
; }
563 bool IsBlockAxisCrossAxis() const { return mIsInlineAxisMainAxis
; }
565 WritingMode
GetWritingMode() const { return mWM
; }
566 WritingMode
ContainingBlockWM() const { return mCBWM
; }
567 StyleAlignSelf
AlignSelf() const { return mAlignSelf
; }
568 StyleAlignFlags
AlignSelfFlags() const { return mAlignSelfFlags
; }
570 // Returns the flex factor (flex-grow or flex-shrink), depending on
571 // 'aIsUsingFlexGrow'.
573 // Asserts fatally if called on a frozen item (since frozen items are not
575 float GetFlexFactor(bool aIsUsingFlexGrow
) {
576 MOZ_ASSERT(!IsFrozen(), "shouldn't need flex factor after item is frozen");
578 return aIsUsingFlexGrow
? mFlexGrow
: mFlexShrink
;
581 // Returns the weight that we should use in the "resolving flexible lengths"
582 // algorithm. If we're using the flex grow factor, we just return that;
583 // otherwise, we return the "scaled flex shrink factor" (scaled by our flex
584 // base size, so that when both large and small items are shrinking, the large
585 // items shrink more).
587 // I'm calling this a "weight" instead of a "[scaled] flex-[grow|shrink]
588 // factor", to more clearly distinguish it from the actual flex-grow &
589 // flex-shrink factors.
591 // Asserts fatally if called on a frozen item (since frozen items are not
593 float GetWeight(bool aIsUsingFlexGrow
) {
594 MOZ_ASSERT(!IsFrozen(), "shouldn't need weight after item is frozen");
596 if (aIsUsingFlexGrow
) {
600 // We're using flex-shrink --> return mFlexShrink * mFlexBaseSize
601 if (mFlexBaseSize
== 0) {
602 // Special-case for mFlexBaseSize == 0 -- we have no room to shrink, so
603 // regardless of mFlexShrink, we should just return 0.
604 // (This is really a special-case for when mFlexShrink is infinity, to
605 // avoid performing mFlexShrink * mFlexBaseSize = inf * 0 = undefined.)
608 return mFlexShrink
* mFlexBaseSize
;
611 bool TreatBSizeAsIndefinite() const { return mTreatBSizeAsIndefinite
; }
613 const AspectRatio
& GetAspectRatio() const { return mAspectRatio
; }
614 bool HasAspectRatio() const { return !!mAspectRatio
; }
616 // Getters for margin:
617 // ===================
618 LogicalMargin
Margin() const { return mMargin
; }
619 nsMargin
PhysicalMargin() const { return mMargin
.GetPhysicalMargin(mCBWM
); }
621 // Returns the margin component for a given LogicalSide in flex container's
623 nscoord
GetMarginComponentForSide(LogicalSide aSide
) const {
624 return mMargin
.Side(aSide
, mCBWM
);
627 // Returns the total space occupied by this item's margins in the given axis
628 nscoord
MarginSizeInMainAxis() const {
629 return mMargin
.StartEnd(MainAxis(), mCBWM
);
631 nscoord
MarginSizeInCrossAxis() const {
632 return mMargin
.StartEnd(CrossAxis(), mCBWM
);
635 // Getters for border/padding
636 // ==========================
637 // Returns the total space occupied by this item's borders and padding in
639 LogicalMargin
BorderPadding() const { return mBorderPadding
; }
640 nscoord
BorderPaddingSizeInMainAxis() const {
641 return mBorderPadding
.StartEnd(MainAxis(), mCBWM
);
643 nscoord
BorderPaddingSizeInCrossAxis() const {
644 return mBorderPadding
.StartEnd(CrossAxis(), mCBWM
);
647 // Getter for combined margin/border/padding
648 // =========================================
649 // Returns the total space occupied by this item's margins, borders and
650 // padding in the given axis
651 nscoord
MarginBorderPaddingSizeInMainAxis() const {
652 return MarginSizeInMainAxis() + BorderPaddingSizeInMainAxis();
654 nscoord
MarginBorderPaddingSizeInCrossAxis() const {
655 return MarginSizeInCrossAxis() + BorderPaddingSizeInCrossAxis();
660 // Helper to set the resolved value of min-[width|height]:auto for the main
661 // axis. (Should only be used if NeedsMinSizeAutoResolution() returns true.)
662 void UpdateMainMinSize(nscoord aNewMinSize
) {
663 NS_ASSERTION(aNewMinSize
>= 0,
664 "How did we end up with a negative min-size?");
666 mMainMaxSize
== NS_UNCONSTRAINEDSIZE
|| mMainMaxSize
>= aNewMinSize
,
667 "Should only use this function for resolving min-size:auto, "
668 "and main max-size should be an upper-bound for resolved val");
670 mNeedsMinSizeAutoResolution
&&
671 (mMainMinSize
== 0 || mFrame
->IsThemed(mFrame
->StyleDisplay())),
672 "Should only use this function for resolving min-size:auto, "
673 "so we shouldn't already have a nonzero min-size established "
674 "(unless it's a themed-widget-imposed minimum size)");
676 if (aNewMinSize
> mMainMinSize
) {
677 mMainMinSize
= aNewMinSize
;
678 // Also clamp main-size to be >= new min-size:
679 mMainSize
= std::max(mMainSize
, aNewMinSize
);
681 mNeedsMinSizeAutoResolution
= false;
684 // This sets our flex base size, and then sets our main size to the
685 // resulting "hypothetical main size" (the base size clamped to our
686 // main-axis [min,max] sizing constraints).
687 void SetFlexBaseSizeAndMainSize(nscoord aNewFlexBaseSize
) {
688 MOZ_ASSERT(!mIsFrozen
|| mFlexBaseSize
== NS_UNCONSTRAINEDSIZE
,
689 "flex base size shouldn't change after we're frozen "
690 "(unless we're just resolving an intrinsic size)");
691 mFlexBaseSize
= aNewFlexBaseSize
;
693 // Before we've resolved flexible lengths, we keep mMainSize set to
694 // the 'hypothetical main size', which is the flex base size, clamped
695 // to the [min,max] range:
696 mMainSize
= NS_CSS_MINMAX(mFlexBaseSize
, mMainMinSize
, mMainMaxSize
);
698 FLEX_ITEM_LOG(mFrame
, "Set flex base size: %d, hypothetical main size: %d",
699 mFlexBaseSize
, mMainSize
);
702 // Setters used while we're resolving flexible lengths
703 // ---------------------------------------------------
705 // Sets the main-size of our flex item's content-box.
706 void SetMainSize(nscoord aNewMainSize
) {
707 MOZ_ASSERT(!mIsFrozen
, "main size shouldn't change after we're frozen");
708 mMainSize
= aNewMainSize
;
711 void SetShareOfWeightSoFar(double aNewShare
) {
712 MOZ_ASSERT(!mIsFrozen
|| aNewShare
== 0.0,
713 "shouldn't be giving this item any share of the weight "
714 "after it's frozen");
715 mShareOfWeightSoFar
= aNewShare
;
720 // Now that we are frozen, the meaning of mHadMinViolation and
721 // mHadMaxViolation changes to indicate min and max clamping. Clear
722 // both of the member variables so that they are ready to be set
723 // as clamping state later, if necessary.
724 mHadMinViolation
= false;
725 mHadMaxViolation
= false;
728 void SetHadMinViolation() {
729 MOZ_ASSERT(!mIsFrozen
,
730 "shouldn't be changing main size & having violations "
731 "after we're frozen");
732 mHadMinViolation
= true;
734 void SetHadMaxViolation() {
735 MOZ_ASSERT(!mIsFrozen
,
736 "shouldn't be changing main size & having violations "
737 "after we're frozen");
738 mHadMaxViolation
= true;
740 void ClearViolationFlags() {
741 MOZ_ASSERT(!mIsFrozen
,
742 "shouldn't be altering violation flags after we're "
744 mHadMinViolation
= mHadMaxViolation
= false;
747 void SetWasMinClamped() {
748 MOZ_ASSERT(!mHadMinViolation
&& !mHadMaxViolation
, "only clamp once");
749 // This reuses the mHadMinViolation member variable to track clamping
750 // events. This is allowable because mHadMinViolation only reflects
751 // a violation up until the item is frozen.
752 MOZ_ASSERT(mIsFrozen
, "shouldn't set clamping state when we are unfrozen");
753 mHadMinViolation
= true;
755 void SetWasMaxClamped() {
756 MOZ_ASSERT(!mHadMinViolation
&& !mHadMaxViolation
, "only clamp once");
757 // This reuses the mHadMaxViolation member variable to track clamping
758 // events. This is allowable because mHadMaxViolation only reflects
759 // a violation up until the item is frozen.
760 MOZ_ASSERT(mIsFrozen
, "shouldn't set clamping state when we are unfrozen");
761 mHadMaxViolation
= true;
764 // Setters for values that are determined after we've resolved our main size
765 // -------------------------------------------------------------------------
767 // Sets the main-axis position of our flex item's content-box.
768 // (This is the distance between the main-start edge of the flex container
769 // and the main-start edge of the flex item's content-box.)
770 void SetMainPosition(nscoord aPosn
) {
771 MOZ_ASSERT(mIsFrozen
, "main size should be resolved before this");
775 // Sets the cross-size of our flex item's content-box.
776 void SetCrossSize(nscoord aCrossSize
) {
777 MOZ_ASSERT(!mIsStretched
,
778 "Cross size shouldn't be modified after it's been stretched");
779 mCrossSize
= aCrossSize
;
782 // Sets the cross-axis position of our flex item's content-box.
783 // (This is the distance between the cross-start edge of the flex container
784 // and the cross-start edge of the flex item.)
785 void SetCrossPosition(nscoord aPosn
) {
786 MOZ_ASSERT(mIsFrozen
, "main size should be resolved before this");
790 // After a FlexItem has had a reflow, this method can be used to cache its
791 // (possibly-unresolved) ascent, in case it's needed later for
792 // baseline-alignment or to establish the container's baseline.
793 // (NOTE: This can be marked 'const' even though it's modifying mAscent,
794 // because mAscent is mutable. It's nice for this to be 'const', because it
795 // means our final reflow can iterate over const FlexItem pointers, and we
796 // can be sure it's not modifying those FlexItems, except via this method.)
797 void SetAscent(nscoord aAscent
) const {
798 mAscent
= aAscent
; // NOTE: this may be ASK_FOR_BASELINE
801 void SetHadMeasuringReflow() { mHadMeasuringReflow
= true; }
803 void SetIsStretched() {
804 MOZ_ASSERT(mIsFrozen
, "main size should be resolved before this");
808 void SetIsFlexBaseSizeContentBSize() { mIsFlexBaseSizeContentBSize
= true; }
810 void SetIsMainMinSizeContentBSize() { mIsMainMinSizeContentBSize
= true; }
812 // Setter for margin components (for resolving "auto" margins)
813 void SetMarginComponentForSide(LogicalSide aSide
, nscoord aLength
) {
814 MOZ_ASSERT(mIsFrozen
, "main size should be resolved before this");
815 mMargin
.Side(aSide
, mCBWM
) = aLength
;
818 void ResolveStretchedCrossSize(nscoord aLineCrossSize
);
820 // Resolves flex base size if flex-basis' used value is 'content', using this
821 // item's preferred aspect ratio and cross size.
822 void ResolveFlexBaseSizeFromAspectRatio(const ReflowInput
& aItemReflowInput
);
824 uint32_t NumAutoMarginsInMainAxis() const {
825 return NumAutoMarginsInAxis(MainAxis());
828 uint32_t NumAutoMarginsInCrossAxis() const {
829 return NumAutoMarginsInAxis(CrossAxis());
832 // Once the main size has been resolved, should we bother doing layout to
833 // establish the cross size?
834 bool CanMainSizeInfluenceCrossSize() const;
836 // Returns a main size, clamped by any definite min and max cross size
837 // converted through the preferred aspect ratio. The caller is responsible for
838 // ensuring that the flex item's preferred aspect ratio is not zero.
839 nscoord
ClampMainSizeViaCrossAxisConstraints(
840 nscoord aMainSize
, const ReflowInput
& aItemReflowInput
) const;
842 // Indicates whether we think this flex item needs a "final" reflow
843 // (after its final flexed size & final position have been determined).
845 // @param aParentReflowInput the flex container's reflow input.
846 // @return true if such a reflow is needed, or false if we believe it can
847 // simply be moved to its final position and skip the reflow.
848 bool NeedsFinalReflow(const ReflowInput
& aParentReflowInput
) const;
850 // Gets the block frame that contains the flex item's content. This is
851 // Frame() itself or one of its descendants.
852 nsBlockFrame
* BlockFrame() const;
855 bool IsMinSizeAutoResolutionNeeded() const;
857 uint32_t NumAutoMarginsInAxis(LogicalAxis aAxis
) const;
859 // Values that we already know in constructor, and remain unchanged:
860 // The flex item's frame.
861 nsIFrame
* mFrame
= nullptr;
862 float mFlexGrow
= 0.0f
;
863 float mFlexShrink
= 0.0f
;
864 AspectRatio mAspectRatio
;
866 // The flex item's writing mode.
869 // The flex container's writing mode.
872 // The flex container's main axis in flex container's writing mode.
873 LogicalAxis mMainAxis
;
875 // Stored in flex container's writing mode.
876 LogicalMargin mBorderPadding
;
878 // Stored in flex container's writing mode. Its value can change when we
879 // resolve "auto" marigns.
880 LogicalMargin mMargin
;
882 // These are non-const so that we can lazily update them with the item's
883 // intrinsic size (obtained via a "measuring" reflow), when necessary.
884 // (e.g. for "flex-basis:auto;height:auto" & "min-height:auto")
885 nscoord mFlexBaseSize
= 0;
886 nscoord mMainMinSize
= 0;
887 nscoord mMainMaxSize
= 0;
889 // mCrossMinSize and mCrossMaxSize are not changed after constructor.
890 nscoord mCrossMinSize
= 0;
891 nscoord mCrossMaxSize
= 0;
893 // Values that we compute after constructor:
894 nscoord mMainSize
= 0;
895 nscoord mMainPosn
= 0;
896 nscoord mCrossSize
= 0;
897 nscoord mCrossPosn
= 0;
899 // Mutable b/c it's set & resolved lazily, sometimes via const pointer. See
900 // comment above SetAscent().
901 // We initialize this to ASK_FOR_BASELINE, and opportunistically fill it in
902 // with a real value if we end up reflowing this flex item. (But if we don't
903 // reflow this flex item, then this sentinel tells us that we don't know it
904 // yet & anyone who cares will need to explicitly request it.)
906 // Both mAscent and mAscentForLast are distance from the frame's border-box
908 mutable nscoord mAscent
= ReflowOutput::ASK_FOR_BASELINE
;
909 mutable nscoord mAscentForLast
= ReflowOutput::ASK_FOR_BASELINE
;
911 // Temporary state, while we're resolving flexible widths (for our main size)
912 // XXXdholbert To save space, we could use a union to make these variables
913 // overlay the same memory as some other member vars that aren't touched
914 // until after main-size has been resolved. In particular, these could share
915 // memory with mMainPosn through mAscent, and mIsStretched.
916 double mShareOfWeightSoFar
= 0.0;
918 bool mIsFrozen
= false;
919 bool mHadMinViolation
= false;
920 bool mHadMaxViolation
= false;
922 // Did this item get a preliminary reflow, to measure its desired height?
923 bool mHadMeasuringReflow
= false;
925 // See IsStretched() documentation.
926 bool mIsStretched
= false;
928 // Is this item a "strut" left behind by an element with visibility:collapse?
929 bool mIsStrut
= false;
931 // See IsInlineAxisMainAxis() documentation. This is not changed after
933 bool mIsInlineAxisMainAxis
= true;
935 // Does this item need to resolve a min-[width|height]:auto (in main-axis)?
937 // Note: mNeedsMinSizeAutoResolution needs to be declared towards the end of
938 // the member variables since it's initialized in a method that depends on
939 // other members declared above such as mCBWM, mMainAxis, and
940 // mIsInlineAxisMainAxis.
941 bool mNeedsMinSizeAutoResolution
= false;
943 // Should we take care to treat this item's resolved BSize as indefinite?
944 bool mTreatBSizeAsIndefinite
= false;
946 // Does this item have an auto margin in either main or cross axis?
947 bool mHasAnyAutoMargin
= false;
949 // Does this item have a content-based flex base size (and is that a size in
951 bool mIsFlexBaseSizeContentBSize
= false;
953 // Does this item have a content-based resolved auto min size (and is that a
954 // size in its block-axis)?
955 bool mIsMainMinSizeContentBSize
= false;
957 // If this item is {first,last}-baseline-aligned using 'align-self', which of
958 // its FlexLine's baseline sharing groups does it participate in?
959 BaselineSharingGroup mBaselineSharingGroup
= BaselineSharingGroup::First
;
961 // My "align-self" computed value (with "auto" swapped out for parent"s
962 // "align-items" value, in our constructor).
963 StyleAlignSelf mAlignSelf
{StyleAlignFlags::AUTO
};
965 // Flags for 'align-self' (safe/unsafe/legacy).
966 StyleAlignFlags mAlignSelfFlags
{0};
970 * Represents a single flex line in a flex container.
971 * Manages an array of the FlexItems that are in the line.
973 class nsFlexContainerFrame::FlexLine final
{
975 explicit FlexLine(nscoord aMainGapSize
) : mMainGapSize(aMainGapSize
) {}
977 nscoord
SumOfGaps() const {
978 return NumItems() > 0 ? (NumItems() - 1) * mMainGapSize
: 0;
981 // Returns the sum of our FlexItems' outer hypothetical main sizes plus the
982 // sum of main axis {row,column}-gaps between items.
983 // ("outer" = margin-box, and "hypothetical" = before flexing)
984 AuCoord64
TotalOuterHypotheticalMainSize() const {
985 return mTotalOuterHypotheticalMainSize
;
988 // Accessors for our FlexItems & information about them:
990 // Note: Callers must use IsEmpty() to ensure that the FlexLine is non-empty
991 // before calling accessors that return FlexItem.
992 FlexItem
& FirstItem() { return mItems
[0]; }
993 const FlexItem
& FirstItem() const { return mItems
[0]; }
995 FlexItem
& LastItem() { return mItems
.LastElement(); }
996 const FlexItem
& LastItem() const { return mItems
.LastElement(); }
998 // The "startmost"/"endmost" is from the perspective of the flex container's
999 // writing-mode, not from the perspective of the flex-relative main axis.
1000 const FlexItem
& StartmostItem(const FlexboxAxisTracker
& aAxisTracker
) const {
1001 return aAxisTracker
.IsMainAxisReversed() ? LastItem() : FirstItem();
1003 const FlexItem
& EndmostItem(const FlexboxAxisTracker
& aAxisTracker
) const {
1004 return aAxisTracker
.IsMainAxisReversed() ? FirstItem() : LastItem();
1007 bool IsEmpty() const { return mItems
.IsEmpty(); }
1009 uint32_t NumItems() const { return mItems
.Length(); }
1011 nsTArray
<FlexItem
>& Items() { return mItems
; }
1012 const nsTArray
<FlexItem
>& Items() const { return mItems
; }
1014 // Adds the last flex item's hypothetical outer main-size and
1015 // margin/border/padding to our totals. This should be called exactly once for
1016 // each flex item, after we've determined that this line is the correct home
1018 void AddLastItemToMainSizeTotals() {
1019 const FlexItem
& lastItem
= Items().LastElement();
1021 // Update our various bookkeeping member-vars:
1022 if (lastItem
.IsFrozen()) {
1026 mTotalItemMBP
+= lastItem
.MarginBorderPaddingSizeInMainAxis();
1027 mTotalOuterHypotheticalMainSize
+= lastItem
.OuterMainSize();
1029 // If the item added was not the first item in the line, we add in any gap
1031 if (NumItems() >= 2) {
1032 mTotalOuterHypotheticalMainSize
+= mMainGapSize
;
1036 // Computes the cross-size and baseline position of this FlexLine, based on
1038 void ComputeCrossSizeAndBaseline(const FlexboxAxisTracker
& aAxisTracker
);
1040 // Returns the cross-size of this line.
1041 nscoord
LineCrossSize() const { return mLineCrossSize
; }
1043 // Setter for line cross-size -- needed for cases where the flex container
1044 // imposes a cross-size on the line. (e.g. for single-line flexbox, or for
1045 // multi-line flexbox with 'align-content: stretch')
1046 void SetLineCrossSize(nscoord aLineCrossSize
) {
1047 mLineCrossSize
= aLineCrossSize
;
1051 * Returns the offset within this line where any baseline-aligned FlexItems
1052 * should place their baseline. The return value represents a distance from
1053 * the line's cross-start edge.
1055 * If there are no baseline-aligned FlexItems, returns nscoord_MIN.
1057 nscoord
FirstBaselineOffset() const { return mFirstBaselineOffset
; }
1060 * Returns the offset within this line where any last baseline-aligned
1061 * FlexItems should place their baseline. Opposite the case of the first
1062 * baseline offset, this represents a distance from the line's cross-end
1063 * edge (since last baseline-aligned items are flush to the cross-end edge).
1065 * If there are no last baseline-aligned FlexItems, returns nscoord_MIN.
1067 nscoord
LastBaselineOffset() const { return mLastBaselineOffset
; }
1069 // Extract a baseline from this line, which would be suitable for use as the
1070 // flex container's 'aBaselineGroup' (i.e. first/last) baseline.
1071 // https://drafts.csswg.org/css-flexbox-1/#flex-baselines
1073 // The return value always represents a distance from the line's cross-start
1074 // edge, even if we are querying last baseline. If this line has no flex items
1075 // in its aBaselineGroup group, this method falls back to trying the opposite
1076 // group. If this line has no baseline-aligned items at all, this returns
1078 nscoord
ExtractBaselineOffset(BaselineSharingGroup aBaselineGroup
) const;
1081 * Returns the gap size in the main axis for this line. Used for gap
1084 nscoord
MainGapSize() const { return mMainGapSize
; }
1086 // Runs the "Resolving Flexible Lengths" algorithm from section 9.7 of the
1087 // CSS flexbox spec to distribute aFlexContainerMainSize among our flex items.
1088 // https://drafts.csswg.org/css-flexbox-1/#resolve-flexible-lengths
1089 void ResolveFlexibleLengths(nscoord aFlexContainerMainSize
,
1090 ComputedFlexLineInfo
* aLineInfo
);
1092 void PositionItemsInMainAxis(const StyleContentDistribution
& aJustifyContent
,
1093 nscoord aContentBoxMainSize
,
1094 const FlexboxAxisTracker
& aAxisTracker
);
1096 void PositionItemsInCrossAxis(nscoord aLineStartPosition
,
1097 const FlexboxAxisTracker
& aAxisTracker
);
1100 // Helpers for ResolveFlexibleLengths():
1101 void FreezeItemsEarly(bool aIsUsingFlexGrow
, ComputedFlexLineInfo
* aLineInfo
);
1103 void FreezeOrRestoreEachFlexibleSize(const nscoord aTotalViolation
,
1104 bool aIsFinalIteration
);
1106 // Sum of FlexItems' outer hypothetical main sizes and all main-axis
1107 // {row,columnm}-gaps between items.
1108 // (i.e. their flex base sizes, clamped via their min/max-size properties,
1109 // plus their main-axis margin/border/padding, plus the sum of the gaps.)
1111 // This variable uses a 64-bit coord type to avoid integer overflow in case
1112 // several of the individual items have huge hypothetical main sizes, which
1113 // can happen with percent-width table-layout:fixed descendants. We have to
1114 // avoid integer overflow in order to shrink items properly in that scenario.
1115 AuCoord64 mTotalOuterHypotheticalMainSize
= 0;
1117 // Stores this line's flex items.
1118 nsTArray
<FlexItem
> mItems
;
1120 // Number of *frozen* FlexItems in this line, based on FlexItem::IsFrozen().
1121 // Mostly used for optimization purposes, e.g. to bail out early from loops
1122 // when we can tell they have nothing left to do.
1123 uint32_t mNumFrozenItems
= 0;
1125 // Sum of margin/border/padding for the FlexItems in this FlexLine.
1126 nscoord mTotalItemMBP
= 0;
1128 nscoord mLineCrossSize
= 0;
1129 nscoord mFirstBaselineOffset
= nscoord_MIN
;
1130 nscoord mLastBaselineOffset
= nscoord_MIN
;
1132 // Maintain size of each {row,column}-gap in the main axis
1133 const nscoord mMainGapSize
;
1136 // The "startmost"/"endmost" is from the perspective of the flex container's
1137 // writing-mode, not from the perspective of the flex-relative cross axis.
1138 const FlexLine
& StartmostLine(const nsTArray
<FlexLine
>& aLines
,
1139 const FlexboxAxisTracker
& aAxisTracker
) {
1140 return aAxisTracker
.IsCrossAxisReversed() ? aLines
.LastElement() : aLines
[0];
1142 const FlexLine
& EndmostLine(const nsTArray
<FlexLine
>& aLines
,
1143 const FlexboxAxisTracker
& aAxisTracker
) {
1144 return aAxisTracker
.IsCrossAxisReversed() ? aLines
[0] : aLines
.LastElement();
1147 // Information about a strut left behind by a FlexItem that's been collapsed
1148 // using "visibility:collapse".
1149 struct nsFlexContainerFrame::StrutInfo
{
1150 StrutInfo(uint32_t aItemIdx
, nscoord aStrutCrossSize
)
1151 : mItemIdx(aItemIdx
), mStrutCrossSize(aStrutCrossSize
) {}
1153 uint32_t mItemIdx
; // Index in the child list.
1154 nscoord mStrutCrossSize
; // The cross-size of this strut.
1157 // Flex data shared by the flex container frames in a continuation chain, owned
1158 // by the first-in-flow. The data is initialized at the end of the
1159 // first-in-flow's Reflow().
1160 struct nsFlexContainerFrame::SharedFlexData final
{
1161 // The flex lines generated in DoFlexLayout() by our first-in-flow.
1162 nsTArray
<FlexLine
> mLines
;
1164 // The final content main/cross size computed by DoFlexLayout.
1165 nscoord mContentBoxMainSize
= NS_UNCONSTRAINEDSIZE
;
1166 nscoord mContentBoxCrossSize
= NS_UNCONSTRAINEDSIZE
;
1168 // Update this struct. Called by the first-in-flow.
1169 void Update(FlexLayoutResult
&& aFlr
) {
1170 mLines
= std::move(aFlr
.mLines
);
1171 mContentBoxMainSize
= aFlr
.mContentBoxMainSize
;
1172 mContentBoxCrossSize
= aFlr
.mContentBoxCrossSize
;
1175 // The frame property under which this struct is stored. Set only on the
1177 NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop
, SharedFlexData
)
1180 // Flex data stored in every flex container's in-flow fragment (continuation).
1182 // It's intended to prevent quadratic operations resulting from each fragment
1183 // having to walk its full prev-in-flow chain, and also serves as an argument to
1184 // the flex container next-in-flow's ReflowChildren(), to compute the position
1185 // offset for each flex item.
1186 struct nsFlexContainerFrame::PerFragmentFlexData final
{
1187 // Suppose D is the distance from a flex container fragment's content-box
1188 // block-start edge to whichever is larger of either (a) the block-end edge of
1189 // its children, or (b) the available space's block-end edge. (Note: in case
1190 // (b), D is conceptually the sum of the block-size of the children, the
1191 // packing space before & in between them, and part of the packing space after
1194 // This variable stores the sum of the D values for the current flex container
1195 // fragments and for all its previous fragments
1196 nscoord mCumulativeContentBoxBSize
= 0;
1198 // This variable accumulates FirstLineOrFirstItemBAxisMetrics::mBEndEdgeShift,
1199 // for the current flex container fragment and for all its previous fragments.
1200 // See the comment of mBEndEdgeShift for its computation details. In short,
1201 // this value is the net block-end edge shift, accumulated for the children in
1202 // all the previous fragments. This number is non-negative.
1204 // This value is also used to grow a flex container's block-size if the
1205 // container's computed block-size is unconstrained. For example: a tall item
1206 // may be pushed to the next page/column, which leaves some wasted area at the
1207 // bottom of the current flex container fragment, and causes the flex
1208 // container fragments to be (collectively) larger than the hypothetical
1209 // unfragmented size. Another example: a tall flex item may be broken into
1210 // multiple fragments, and those fragments may have a larger collective
1211 // block-size as compared to the item's original unfragmented size; the
1212 // container would need to increase its block-size to account for this.
1213 nscoord mCumulativeBEndEdgeShift
= 0;
1215 // The frame property under which this struct is stored. Cached on every
1216 // in-flow fragment (continuation) at the end of the flex container's
1218 NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop
, PerFragmentFlexData
)
1221 static void BuildStrutInfoFromCollapsedItems(const nsTArray
<FlexLine
>& aLines
,
1222 nsTArray
<StrutInfo
>& aStruts
) {
1223 MOZ_ASSERT(aStruts
.IsEmpty(),
1224 "We should only build up StrutInfo once per reflow, so "
1225 "aStruts should be empty when this is called");
1227 uint32_t itemIdxInContainer
= 0;
1228 for (const FlexLine
& line
: aLines
) {
1229 for (const FlexItem
& item
: line
.Items()) {
1230 if (item
.Frame()->StyleVisibility()->IsCollapse()) {
1231 // Note the cross size of the line as the item's strut size.
1232 aStruts
.AppendElement(
1233 StrutInfo(itemIdxInContainer
, line
.LineCrossSize()));
1235 itemIdxInContainer
++;
1240 static mozilla::StyleAlignFlags
SimplifyAlignOrJustifyContentForOneItem(
1241 const StyleContentDistribution
& aAlignmentVal
, bool aIsAlign
) {
1242 // Mask away any explicit fallback, to get the main (non-fallback) part of
1243 // the specified value:
1244 StyleAlignFlags specified
= aAlignmentVal
.primary
;
1246 // XXX strip off <overflow-position> bits until we implement it (bug 1311892)
1247 specified
&= ~StyleAlignFlags::FLAG_BITS
;
1249 // FIRST: handle a special-case for "justify-content:stretch" (or equivalent),
1250 // which requires that we ignore any author-provided explicit fallback value.
1251 if (specified
== StyleAlignFlags::NORMAL
) {
1252 // In a flex container, *-content: "'normal' behaves as 'stretch'".
1253 // Do that conversion early, so it benefits from our 'stretch' special-case.
1254 // https://drafts.csswg.org/css-align-3/#distribution-flex
1255 specified
= StyleAlignFlags::STRETCH
;
1257 if (!aIsAlign
&& specified
== StyleAlignFlags::STRETCH
) {
1258 // In a flex container, in "justify-content Axis: [...] 'stretch' behaves
1259 // as 'flex-start' (ignoring the specified fallback alignment, if any)."
1260 // https://drafts.csswg.org/css-align-3/#distribution-flex
1261 // So, we just directly return 'flex-start', & ignore explicit fallback..
1262 return StyleAlignFlags::FLEX_START
;
1265 // TODO: Check for an explicit fallback value (and if it's present, use it)
1266 // here once we parse it, see https://github.com/w3c/csswg-drafts/issues/1002.
1268 // If there's no explicit fallback, use the implied fallback values for
1269 // space-{between,around,evenly} (since those values only make sense with
1270 // multiple alignment subjects), and otherwise just use the specified value:
1271 if (specified
== StyleAlignFlags::SPACE_BETWEEN
) {
1272 return StyleAlignFlags::FLEX_START
;
1274 if (specified
== StyleAlignFlags::SPACE_AROUND
||
1275 specified
== StyleAlignFlags::SPACE_EVENLY
) {
1276 return StyleAlignFlags::CENTER
;
1281 bool nsFlexContainerFrame::DrainSelfOverflowList() {
1282 return DrainAndMergeSelfOverflowList();
1285 void nsFlexContainerFrame::AppendFrames(ChildListID aListID
,
1286 nsFrameList
&& aFrameList
) {
1287 NoteNewChildren(aListID
, aFrameList
);
1288 nsContainerFrame::AppendFrames(aListID
, std::move(aFrameList
));
1291 void nsFlexContainerFrame::InsertFrames(
1292 ChildListID aListID
, nsIFrame
* aPrevFrame
,
1293 const nsLineList::iterator
* aPrevFrameLine
, nsFrameList
&& aFrameList
) {
1294 NoteNewChildren(aListID
, aFrameList
);
1295 nsContainerFrame::InsertFrames(aListID
, aPrevFrame
, aPrevFrameLine
,
1296 std::move(aFrameList
));
1299 void nsFlexContainerFrame::RemoveFrame(DestroyContext
& aContext
,
1300 ChildListID aListID
,
1301 nsIFrame
* aOldFrame
) {
1302 MOZ_ASSERT(aListID
== FrameChildListID::Principal
, "unexpected child list");
1305 SetDidPushItemsBitIfNeeded(aListID
, aOldFrame
);
1308 nsContainerFrame::RemoveFrame(aContext
, aListID
, aOldFrame
);
1311 StyleAlignFlags
nsFlexContainerFrame::CSSAlignmentForAbsPosChild(
1312 const ReflowInput
& aChildRI
, LogicalAxis aLogicalAxis
) const {
1313 const FlexboxAxisTracker
axisTracker(this);
1315 // If we're row-oriented and the caller is asking about our inline axis (or
1316 // alternately, if we're column-oriented and the caller is asking about our
1317 // block axis), then the caller is really asking about our *main* axis.
1318 // Otherwise, the caller is asking about our cross axis.
1319 const bool isMainAxis
=
1320 (axisTracker
.IsRowOriented() == (aLogicalAxis
== LogicalAxis::Inline
));
1321 const nsStylePosition
* containerStylePos
= StylePosition();
1322 const bool isAxisReversed
= isMainAxis
? axisTracker
.IsMainAxisReversed()
1323 : axisTracker
.IsCrossAxisReversed();
1325 StyleAlignFlags alignment
{0};
1326 StyleAlignFlags alignmentFlags
{0};
1328 // We're aligning in the main axis: align according to 'justify-content'.
1329 // (We don't care about justify-self; it has no effect on children of flex
1330 // containers, unless https://github.com/w3c/csswg-drafts/issues/7644
1332 alignment
= SimplifyAlignOrJustifyContentForOneItem(
1333 containerStylePos
->mJustifyContent
,
1334 /*aIsAlign = */ false);
1336 // We're aligning in the cross axis: align according to 'align-self'.
1337 // (We don't care about align-content; it has no effect on abspos flex
1338 // children, per https://github.com/w3c/csswg-drafts/issues/7596 )
1339 alignment
= aChildRI
.mStylePosition
->UsedAlignSelf(Style())._0
;
1340 // Extract and strip align flag bits
1341 alignmentFlags
= alignment
& StyleAlignFlags::FLAG_BITS
;
1342 alignment
&= ~StyleAlignFlags::FLAG_BITS
;
1344 if (alignment
== StyleAlignFlags::NORMAL
) {
1345 // "the 'normal' keyword behaves as 'start' on replaced
1346 // absolutely-positioned boxes, and behaves as 'stretch' on all other
1347 // absolutely-positioned boxes."
1348 // https://drafts.csswg.org/css-align/#align-abspos
1349 alignment
= aChildRI
.mFrame
->IsReplaced() ? StyleAlignFlags::START
1350 : StyleAlignFlags::STRETCH
;
1354 if (alignment
== StyleAlignFlags::STRETCH
) {
1355 // The default fallback alignment for 'stretch' is 'flex-start'.
1356 alignment
= StyleAlignFlags::FLEX_START
;
1359 // Resolve flex-start, flex-end, auto, left, right, baseline, last baseline;
1360 if (alignment
== StyleAlignFlags::FLEX_START
) {
1361 alignment
= isAxisReversed
? StyleAlignFlags::END
: StyleAlignFlags::START
;
1362 } else if (alignment
== StyleAlignFlags::FLEX_END
) {
1363 alignment
= isAxisReversed
? StyleAlignFlags::START
: StyleAlignFlags::END
;
1364 } else if (alignment
== StyleAlignFlags::LEFT
||
1365 alignment
== StyleAlignFlags::RIGHT
) {
1366 MOZ_ASSERT(isMainAxis
, "Only justify-* can have 'left' and 'right'!");
1367 alignment
= axisTracker
.ResolveJustifyLeftRight(alignment
);
1368 } else if (alignment
== StyleAlignFlags::BASELINE
) {
1369 alignment
= StyleAlignFlags::START
;
1370 } else if (alignment
== StyleAlignFlags::LAST_BASELINE
) {
1371 alignment
= StyleAlignFlags::END
;
1374 MOZ_ASSERT(alignment
!= StyleAlignFlags::STRETCH
,
1375 "We should've converted 'stretch' to the fallback alignment!");
1376 MOZ_ASSERT(alignment
!= StyleAlignFlags::FLEX_START
&&
1377 alignment
!= StyleAlignFlags::FLEX_END
,
1378 "nsAbsoluteContainingBlock doesn't know how to handle "
1379 "flex-relative axis for flex containers!");
1381 return (alignment
| alignmentFlags
);
1384 void nsFlexContainerFrame::GenerateFlexItemForChild(
1385 FlexLine
& aLine
, nsIFrame
* aChildFrame
,
1386 const ReflowInput
& aParentReflowInput
,
1387 const FlexboxAxisTracker
& aAxisTracker
,
1388 const nscoord aTentativeContentBoxCrossSize
) {
1389 const auto flexWM
= aAxisTracker
.GetWritingMode();
1390 const auto childWM
= aChildFrame
->GetWritingMode();
1392 // Note: we use GetStyleFrame() to access the sizing & flex properties here.
1393 // This lets us correctly handle table wrapper frames as flex items since
1394 // their inline-size and block-size properties are always 'auto'. In order for
1395 // 'flex-basis:auto' to actually resolve to the author's specified inline-size
1396 // or block-size, we need to dig through to the inner table.
1397 const auto* stylePos
=
1398 nsLayoutUtils::GetStyleFrame(aChildFrame
)->StylePosition();
1400 // Construct a StyleSizeOverrides for this flex item so that its ReflowInput
1401 // below will use and resolve its flex base size rather than its corresponding
1402 // preferred main size property (only for modern CSS flexbox).
1403 StyleSizeOverrides sizeOverrides
;
1404 if (!IsLegacyBox(this)) {
1405 Maybe
<StyleSize
> styleFlexBaseSize
;
1407 // When resolving flex base size, flex items use their 'flex-basis' property
1408 // in place of their preferred main size (e.g. 'width') for sizing purposes,
1409 // *unless* they have 'flex-basis:auto' in which case they use their
1410 // preferred main size after all.
1411 const auto& flexBasis
= stylePos
->mFlexBasis
;
1412 const auto& styleMainSize
= stylePos
->Size(aAxisTracker
.MainAxis(), flexWM
);
1413 if (IsUsedFlexBasisContent(flexBasis
, styleMainSize
)) {
1414 // If we get here, we're resolving the flex base size for a flex item, and
1415 // we fall into the flexbox spec section 9.2 step 3, substep C (if we have
1416 // a definite cross size) or E (if not).
1417 styleFlexBaseSize
.emplace(StyleSize::MaxContent());
1418 } else if (flexBasis
.IsSize() && !flexBasis
.IsAuto()) {
1419 // For all other non-'auto' flex-basis values, we just swap in the
1420 // flex-basis itself for the preferred main-size property.
1421 styleFlexBaseSize
.emplace(flexBasis
.AsSize());
1423 // else: flex-basis is 'auto', which is deferring to some explicit value
1424 // in the preferred main size.
1425 MOZ_ASSERT(flexBasis
.IsAuto());
1426 styleFlexBaseSize
.emplace(styleMainSize
);
1429 MOZ_ASSERT(styleFlexBaseSize
, "We should've emplace styleFlexBaseSize!");
1431 // Provide the size override for the preferred main size property.
1432 if (aAxisTracker
.IsInlineAxisMainAxis(childWM
)) {
1433 sizeOverrides
.mStyleISize
= std::move(styleFlexBaseSize
);
1435 sizeOverrides
.mStyleBSize
= std::move(styleFlexBaseSize
);
1438 // 'flex-basis' should works on the inner table frame for a table flex item,
1439 // just like how 'height' works on a table element.
1440 sizeOverrides
.mApplyOverridesVerbatim
= true;
1443 // Create temporary reflow input just for sizing -- to get hypothetical
1444 // main-size and the computed values of min / max main-size property.
1445 // (This reflow input will _not_ be used for reflow.)
1446 ReflowInput
childRI(PresContext(), aParentReflowInput
, aChildFrame
,
1447 aParentReflowInput
.ComputedSize(childWM
), Nothing(), {},
1448 sizeOverrides
, {ComputeSizeFlag::ShrinkWrap
});
1450 // FLEX GROW & SHRINK WEIGHTS
1451 // --------------------------
1452 float flexGrow
, flexShrink
;
1453 if (IsLegacyBox(this)) {
1454 flexGrow
= flexShrink
= aChildFrame
->StyleXUL()->mBoxFlex
;
1456 flexGrow
= stylePos
->mFlexGrow
;
1457 flexShrink
= stylePos
->mFlexShrink
;
1460 // MAIN SIZES (flex base size, min/max size)
1461 // -----------------------------------------
1462 const LogicalSize computedSizeInFlexWM
= childRI
.ComputedSize(flexWM
);
1463 const LogicalSize computedMinSizeInFlexWM
= childRI
.ComputedMinSize(flexWM
);
1464 const LogicalSize computedMaxSizeInFlexWM
= childRI
.ComputedMaxSize(flexWM
);
1466 const nscoord flexBaseSize
= aAxisTracker
.MainComponent(computedSizeInFlexWM
);
1467 const nscoord mainMinSize
=
1468 aAxisTracker
.MainComponent(computedMinSizeInFlexWM
);
1469 const nscoord mainMaxSize
=
1470 aAxisTracker
.MainComponent(computedMaxSizeInFlexWM
);
1472 // This is enforced by the ReflowInput where these values come from:
1473 MOZ_ASSERT(mainMinSize
<= mainMaxSize
, "min size is larger than max size");
1475 // CROSS SIZES (tentative cross size, min/max cross size)
1476 // ------------------------------------------------------
1477 // Grab the cross size from the reflow input. This might be the right value,
1478 // or we might resolve it to something else in SizeItemInCrossAxis(); hence,
1479 // it's tentative. See comment under "Cross Size Determination" for more.
1480 const nscoord tentativeCrossSize
=
1481 aAxisTracker
.CrossComponent(computedSizeInFlexWM
);
1482 const nscoord crossMinSize
=
1483 aAxisTracker
.CrossComponent(computedMinSizeInFlexWM
);
1484 const nscoord crossMaxSize
=
1485 aAxisTracker
.CrossComponent(computedMaxSizeInFlexWM
);
1487 // Construct the flex item!
1488 FlexItem
& item
= *aLine
.Items().EmplaceBack(
1489 childRI
, flexGrow
, flexShrink
, flexBaseSize
, mainMinSize
, mainMaxSize
,
1490 tentativeCrossSize
, crossMinSize
, crossMaxSize
, aAxisTracker
);
1492 // We may be about to do computations based on our item's cross-size
1493 // (e.g. using it as a constraint when measuring our content in the
1494 // main axis, or using it with the preferred aspect ratio to obtain a main
1495 // size). BEFORE WE DO THAT, we need let the item "pre-stretch" its cross size
1496 // (if it's got 'align-self:stretch'), for a certain case where the spec says
1497 // the stretched cross size is considered "definite". That case is if we
1498 // have a single-line (nowrap) flex container which itself has a definite
1499 // cross-size. Otherwise, we'll wait to do stretching, since (in other
1500 // cases) we don't know how much the item should stretch yet.
1501 const bool isSingleLine
=
1502 StyleFlexWrap::Nowrap
== aParentReflowInput
.mStylePosition
->mFlexWrap
;
1504 // Is container's cross size "definite"?
1505 // - If it's column-oriented, then "yes", because its cross size is its
1506 // inline-size which is always definite from its descendants' perspective.
1507 // - Otherwise (if it's row-oriented), then we check the actual size
1508 // and call it definite if it's not NS_UNCONSTRAINEDSIZE.
1509 if (aAxisTracker
.IsColumnOriented() ||
1510 aTentativeContentBoxCrossSize
!= NS_UNCONSTRAINEDSIZE
) {
1511 // Container's cross size is "definite", so we can resolve the item's
1512 // stretched cross size using that.
1513 item
.ResolveStretchedCrossSize(aTentativeContentBoxCrossSize
);
1517 // Before thinking about freezing the item at its base size, we need to give
1518 // it a chance to recalculate the base size from its cross size and aspect
1519 // ratio (since its cross size might've *just* now become definite due to
1521 item
.ResolveFlexBaseSizeFromAspectRatio(childRI
);
1523 // If we're inflexible, we can just freeze to our hypothetical main-size
1525 if (flexGrow
== 0.0f
&& flexShrink
== 0.0f
) {
1527 if (flexBaseSize
< mainMinSize
) {
1528 item
.SetWasMinClamped();
1529 } else if (flexBaseSize
> mainMaxSize
) {
1530 item
.SetWasMaxClamped();
1534 // Resolve "flex-basis:auto" and/or "min-[width|height]:auto" (which might
1535 // require us to reflow the item to measure content height)
1536 ResolveAutoFlexBasisAndMinSize(item
, childRI
, aAxisTracker
);
1539 // Static helper-functions for ResolveAutoFlexBasisAndMinSize():
1540 // -------------------------------------------------------------
1541 // Partially resolves "min-[width|height]:auto" and returns the resulting value.
1542 // By "partially", I mean we don't consider the min-content size (but we do
1543 // consider the main-size and main max-size properties, and the preferred aspect
1544 // ratio). The caller is responsible for computing & considering the min-content
1545 // size in combination with the partially-resolved value that this function
1548 // Basically, this function gets the specified size suggestion; if not, the
1549 // transferred size suggestion; if both sizes do not exist, return nscoord_MAX.
1551 // Spec reference: https://drafts.csswg.org/css-flexbox-1/#min-size-auto
1552 static nscoord
PartiallyResolveAutoMinSize(
1553 const FlexItem
& aFlexItem
, const ReflowInput
& aItemReflowInput
,
1554 const FlexboxAxisTracker
& aAxisTracker
) {
1555 MOZ_ASSERT(aFlexItem
.NeedsMinSizeAutoResolution(),
1556 "only call for FlexItems that need min-size auto resolution");
1558 const auto itemWM
= aFlexItem
.GetWritingMode();
1559 const auto cbWM
= aAxisTracker
.GetWritingMode();
1560 const auto& mainStyleSize
=
1561 aItemReflowInput
.mStylePosition
->Size(aAxisTracker
.MainAxis(), cbWM
);
1562 const auto& maxMainStyleSize
=
1563 aItemReflowInput
.mStylePosition
->MaxSize(aAxisTracker
.MainAxis(), cbWM
);
1564 const auto boxSizingAdjust
=
1565 aItemReflowInput
.mStylePosition
->mBoxSizing
== StyleBoxSizing::Border
1566 ? aFlexItem
.BorderPadding().Size(cbWM
)
1567 : LogicalSize(cbWM
);
1569 // If this flex item is a compressible replaced element list in CSS Sizing 3
1570 // §5.2.2, CSS Sizing 3 §5.2.1c requires us to resolve the percentage part of
1571 // the preferred main size property against zero, yielding a definite
1572 // specified size suggestion. Here we can use a zero percentage basis to
1573 // fulfill this requirement.
1574 const auto percentBasis
=
1575 aFlexItem
.Frame()->IsPercentageResolvedAgainstZero(mainStyleSize
,
1577 ? LogicalSize(cbWM
, 0, 0)
1578 : aItemReflowInput
.mContainingBlockSize
.ConvertTo(cbWM
, itemWM
);
1580 // Compute the specified size suggestion, which is the main-size property if
1582 nscoord specifiedSizeSuggestion
= nscoord_MAX
;
1584 if (aAxisTracker
.IsRowOriented()) {
1585 if (mainStyleSize
.IsLengthPercentage()) {
1586 // NOTE: We ignore extremum inline-size. This is OK because the caller is
1587 // responsible for computing the min-content inline-size and min()'ing it
1588 // with the value we return.
1589 specifiedSizeSuggestion
= aFlexItem
.Frame()->ComputeISizeValue(
1590 cbWM
, percentBasis
, boxSizingAdjust
,
1591 mainStyleSize
.AsLengthPercentage());
1594 if (!nsLayoutUtils::IsAutoBSize(mainStyleSize
, percentBasis
.BSize(cbWM
))) {
1595 // NOTE: We ignore auto and extremum block-size. This is OK because the
1596 // caller is responsible for computing the min-content block-size and
1597 // min()'ing it with the value we return.
1598 specifiedSizeSuggestion
= nsLayoutUtils::ComputeBSizeValue(
1599 percentBasis
.BSize(cbWM
), boxSizingAdjust
.BSize(cbWM
),
1600 mainStyleSize
.AsLengthPercentage());
1604 if (specifiedSizeSuggestion
!= nscoord_MAX
) {
1605 // We have the specified size suggestion. Return it now since we don't need
1606 // to consider transferred size suggestion.
1607 FLEX_LOGV("Specified size suggestion: %d", specifiedSizeSuggestion
);
1608 return specifiedSizeSuggestion
;
1611 // Compute the transferred size suggestion, which is the cross size converted
1612 // through the aspect ratio (if the item is replaced, and it has an aspect
1613 // ratio and a definite cross size).
1614 if (const auto& aspectRatio
= aFlexItem
.GetAspectRatio();
1615 aFlexItem
.Frame()->IsReplaced() && aspectRatio
&&
1616 aFlexItem
.IsCrossSizeDefinite(aItemReflowInput
)) {
1617 // We have a usable aspect ratio. (not going to divide by 0)
1618 nscoord transferredSizeSuggestion
= aspectRatio
.ComputeRatioDependentSize(
1619 aFlexItem
.MainAxis(), cbWM
, aFlexItem
.CrossSize(), boxSizingAdjust
);
1621 // Clamp the transferred size suggestion by any definite min and max
1622 // cross size converted through the aspect ratio.
1623 transferredSizeSuggestion
= aFlexItem
.ClampMainSizeViaCrossAxisConstraints(
1624 transferredSizeSuggestion
, aItemReflowInput
);
1626 FLEX_LOGV("Transferred size suggestion: %d", transferredSizeSuggestion
);
1627 return transferredSizeSuggestion
;
1633 // Note: If & when we handle "min-height: min-content" for flex items,
1634 // we may want to resolve that in this function, too.
1635 void nsFlexContainerFrame::ResolveAutoFlexBasisAndMinSize(
1636 FlexItem
& aFlexItem
, const ReflowInput
& aItemReflowInput
,
1637 const FlexboxAxisTracker
& aAxisTracker
) {
1638 // (Note: We can guarantee that the flex-basis will have already been
1639 // resolved if the main axis is the same as the item's inline
1640 // axis. Inline-axis values should always be resolvable without reflow.)
1641 const bool isMainSizeAuto
=
1642 (!aFlexItem
.IsInlineAxisMainAxis() &&
1643 NS_UNCONSTRAINEDSIZE
== aFlexItem
.FlexBaseSize());
1645 const bool isMainMinSizeAuto
= aFlexItem
.NeedsMinSizeAutoResolution();
1647 if (!isMainSizeAuto
&& !isMainMinSizeAuto
) {
1648 // Nothing to do; this function is only needed for flex items
1649 // with a used flex-basis of "auto" or a min-main-size of "auto".
1655 "Resolving auto main size? %s; resolving auto min main size? %s",
1656 BoolToYesNo(isMainSizeAuto
), BoolToYesNo(isMainMinSizeAuto
));
1658 nscoord resolvedMinSize
; // (only set/used if isMainMinSizeAuto==true)
1659 bool minSizeNeedsToMeasureContent
= false; // assume the best
1660 if (isMainMinSizeAuto
) {
1661 // Resolve the min-size, except for considering the min-content size.
1662 // (We'll consider that later, if we need to.)
1664 PartiallyResolveAutoMinSize(aFlexItem
, aItemReflowInput
, aAxisTracker
);
1665 if (resolvedMinSize
> 0) {
1666 // If resolvedMinSize were already at 0, we could skip calculating content
1667 // size suggestion because it can't go any lower.
1668 minSizeNeedsToMeasureContent
= true;
1672 const bool flexBasisNeedsToMeasureContent
= isMainSizeAuto
;
1674 // Measure content, if needed (w/ intrinsic-width method or a reflow)
1675 if (minSizeNeedsToMeasureContent
|| flexBasisNeedsToMeasureContent
) {
1676 // Compute the content size suggestion, which is the min-content size in the
1678 nscoord contentSizeSuggestion
= nscoord_MAX
;
1680 if (aFlexItem
.IsInlineAxisMainAxis()) {
1681 if (minSizeNeedsToMeasureContent
) {
1682 // Compute the flex item's content size suggestion, which is the
1683 // 'min-content' size on the main axis.
1684 // https://drafts.csswg.org/css-flexbox-1/#content-size-suggestion
1685 const auto cbWM
= aAxisTracker
.GetWritingMode();
1686 const auto itemWM
= aFlexItem
.GetWritingMode();
1687 const nscoord availISize
= 0; // for min-content size
1688 StyleSizeOverrides sizeOverrides
;
1689 sizeOverrides
.mStyleISize
.emplace(StyleSize::Auto());
1690 const auto sizeInItemWM
= aFlexItem
.Frame()->ComputeSize(
1691 aItemReflowInput
.mRenderingContext
, itemWM
,
1692 aItemReflowInput
.mContainingBlockSize
, availISize
,
1693 aItemReflowInput
.ComputedLogicalMargin(itemWM
).Size(itemWM
),
1694 aItemReflowInput
.ComputedLogicalBorderPadding(itemWM
).Size(itemWM
),
1695 sizeOverrides
, {ComputeSizeFlag::ShrinkWrap
});
1697 contentSizeSuggestion
= aAxisTracker
.MainComponent(
1698 sizeInItemWM
.mLogicalSize
.ConvertTo(cbWM
, itemWM
));
1700 NS_ASSERTION(!flexBasisNeedsToMeasureContent
,
1701 "flex-basis:auto should have been resolved in the "
1702 "reflow input, for horizontal flexbox. It shouldn't need "
1703 "special handling here");
1705 // If this item is flexible (in its block axis)...
1706 // OR if we're measuring its 'auto' min-BSize, with its main-size (in its
1707 // block axis) being something non-"auto"...
1708 // THEN: we assume that the computed BSize that we're reflowing with now
1709 // could be different from the one we'll use for this flex item's
1710 // "actual" reflow later on. In that case, we need to be sure the flex
1711 // item treats this as a block-axis resize (regardless of whether there
1712 // are actually any ancestors being resized in that axis).
1713 // (Note: We don't have to do this for the inline axis, because
1714 // InitResizeFlags will always turn on mIsIResize on when it sees that
1715 // the computed ISize is different from current ISize, and that's all we
1717 bool forceBResizeForMeasuringReflow
=
1718 !aFlexItem
.IsFrozen() || // Is the item flexible?
1719 !flexBasisNeedsToMeasureContent
; // Are we *only* measuring it for
1720 // 'min-block-size:auto'?
1722 const ReflowInput
& flexContainerRI
= *aItemReflowInput
.mParentReflowInput
;
1723 nscoord contentBSize
= MeasureFlexItemContentBSize(
1724 aFlexItem
, forceBResizeForMeasuringReflow
, flexContainerRI
);
1725 if (minSizeNeedsToMeasureContent
) {
1726 contentSizeSuggestion
= contentBSize
;
1728 if (flexBasisNeedsToMeasureContent
) {
1729 aFlexItem
.SetFlexBaseSizeAndMainSize(contentBSize
);
1730 aFlexItem
.SetIsFlexBaseSizeContentBSize();
1734 if (minSizeNeedsToMeasureContent
) {
1735 // Clamp the content size suggestion by any definite min and max cross
1736 // size converted through the aspect ratio.
1737 if (aFlexItem
.HasAspectRatio()) {
1738 contentSizeSuggestion
= aFlexItem
.ClampMainSizeViaCrossAxisConstraints(
1739 contentSizeSuggestion
, aItemReflowInput
);
1742 FLEX_LOGV("Content size suggestion: %d", contentSizeSuggestion
);
1743 resolvedMinSize
= std::min(resolvedMinSize
, contentSizeSuggestion
);
1745 // Clamp the resolved min main size by the max main size if it's definite.
1746 if (aFlexItem
.MainMaxSize() != NS_UNCONSTRAINEDSIZE
) {
1747 resolvedMinSize
= std::min(resolvedMinSize
, aFlexItem
.MainMaxSize());
1748 } else if (MOZ_UNLIKELY(resolvedMinSize
> nscoord_MAX
)) {
1749 NS_WARNING("Bogus resolved auto min main size!");
1750 // Our resolved min-size is bogus, probably due to some huge sizes in
1751 // the content. Clamp it to the valid nscoord range, so that we can at
1752 // least depend on it being <= the max-size (which is also the
1753 // nscoord_MAX sentinel value if we reach this point).
1754 resolvedMinSize
= nscoord_MAX
;
1756 FLEX_LOGV("Resolved auto min main size: %d", resolvedMinSize
);
1758 if (resolvedMinSize
== contentSizeSuggestion
) {
1759 // When we are here, we've measured the item's content-based size, and
1760 // we used it as the resolved auto min main size. Record the fact so
1761 // that we can use it to determine whether we allow a flex item to grow
1762 // its block-size in ReflowFlexItem().
1763 aFlexItem
.SetIsMainMinSizeContentBSize();
1768 if (isMainMinSizeAuto
) {
1769 aFlexItem
.UpdateMainMinSize(resolvedMinSize
);
1774 * A cached result for a flex item's block-axis measuring reflow. This cache
1775 * prevents us from doing exponential reflows in cases of deeply nested flex
1776 * and scroll frames.
1778 * We store the cached value in the flex item's frame property table, for
1781 * Right now, we cache the following as a "key", from the item's ReflowInput:
1782 * - its ComputedSize
1783 * - its min/max block size (in case its ComputedBSize is unconstrained)
1784 * - its AvailableBSize
1785 * ...and we cache the following as the "value", from the item's ReflowOutput:
1786 * - its final content-box BSize
1788 * The assumption here is that a given flex item measurement from our "value"
1789 * won't change unless one of the pieces of the "key" change, or the flex
1790 * item's intrinsic size is marked as dirty (due to a style or DOM change).
1791 * (The latter will cause the cached value to be discarded, in
1792 * nsIFrame::MarkIntrinsicISizesDirty.)
1794 * Note that the components of "Key" (mComputed{MinB,MaxB,}Size and
1795 * mAvailableBSize) are sufficient to catch any changes to the flex container's
1796 * size that the item may care about for its measuring reflow. Specifically:
1797 * - If the item cares about the container's size (e.g. if it has a percent
1798 * height and the container's height changes, in a horizontal-WM container)
1799 * then that'll be detectable via the item's ReflowInput's "ComputedSize()"
1800 * differing from the value in our Key. And the same applies for the
1802 * - If the item is fragmentable (pending bug 939897) and its measured BSize
1803 * depends on where it gets fragmented, then that sort of change can be
1804 * detected due to the item's ReflowInput's "AvailableBSize()" differing
1805 * from the value in our Key.
1807 * One particular case to consider (& need to be sure not to break when
1808 * changing this class): the flex item's computed BSize may change between
1809 * measuring reflows due to how the mIsFlexContainerMeasuringBSize flag affects
1810 * size computation (see bug 1336708). This is one reason we need to use the
1811 * computed BSize as part of the key.
1813 class nsFlexContainerFrame::CachedBAxisMeasurement
{
1815 const LogicalSize mComputedSize
;
1816 const nscoord mComputedMinBSize
;
1817 const nscoord mComputedMaxBSize
;
1818 const nscoord mAvailableBSize
;
1820 explicit Key(const ReflowInput
& aRI
)
1821 : mComputedSize(aRI
.ComputedSize()),
1822 mComputedMinBSize(aRI
.ComputedMinBSize()),
1823 mComputedMaxBSize(aRI
.ComputedMaxBSize()),
1824 mAvailableBSize(aRI
.AvailableBSize()) {}
1826 bool operator==(const Key
& aOther
) const {
1827 return mComputedSize
== aOther
.mComputedSize
&&
1828 mComputedMinBSize
== aOther
.mComputedMinBSize
&&
1829 mComputedMaxBSize
== aOther
.mComputedMaxBSize
&&
1830 mAvailableBSize
== aOther
.mAvailableBSize
;
1836 // This could/should be const, but it's non-const for now just because it's
1837 // assigned via a series of steps in the constructor body:
1841 CachedBAxisMeasurement(const ReflowInput
& aReflowInput
,
1842 const ReflowOutput
& aReflowOutput
)
1843 : mKey(aReflowInput
) {
1844 // To get content-box bsize, we have to subtract off border & padding
1845 // (and floor at 0 in case the border/padding are too large):
1846 WritingMode itemWM
= aReflowInput
.GetWritingMode();
1847 nscoord borderBoxBSize
= aReflowOutput
.BSize(itemWM
);
1850 aReflowInput
.ComputedLogicalBorderPadding(itemWM
).BStartEnd(itemWM
);
1851 mBSize
= std::max(0, mBSize
);
1855 * Returns true if this cached flex item measurement is valid for (i.e. can
1856 * be expected to match the output of) a measuring reflow whose input
1857 * parameters are given via aReflowInput.
1859 bool IsValidFor(const ReflowInput
& aReflowInput
) const {
1860 return mKey
== Key(aReflowInput
);
1863 nscoord
BSize() const { return mBSize
; }
1867 * A cached copy of various metrics from a flex item's most recent final reflow.
1868 * It can be used to determine whether we can optimize away the flex item's
1869 * final reflow, when we perform an incremental reflow of its flex container.
1871 class CachedFinalReflowMetrics final
{
1873 CachedFinalReflowMetrics(const ReflowInput
& aReflowInput
,
1874 const ReflowOutput
& aReflowOutput
)
1875 : CachedFinalReflowMetrics(aReflowInput
.GetWritingMode(), aReflowInput
,
1878 CachedFinalReflowMetrics(const FlexItem
& aItem
, const LogicalSize
& aSize
)
1879 : mBorderPadding(aItem
.BorderPadding().ConvertTo(
1880 aItem
.GetWritingMode(), aItem
.ContainingBlockWM())),
1882 mTreatBSizeAsIndefinite(aItem
.TreatBSizeAsIndefinite()) {}
1884 const LogicalSize
& Size() const { return mSize
; }
1885 const LogicalMargin
& BorderPadding() const { return mBorderPadding
; }
1886 bool TreatBSizeAsIndefinite() const { return mTreatBSizeAsIndefinite
; }
1889 // A convenience constructor with a WritingMode argument.
1890 CachedFinalReflowMetrics(WritingMode aWM
, const ReflowInput
& aReflowInput
,
1891 const ReflowOutput
& aReflowOutput
)
1892 : mBorderPadding(aReflowInput
.ComputedLogicalBorderPadding(aWM
)),
1893 mSize(aReflowOutput
.Size(aWM
) - mBorderPadding
.Size(aWM
)),
1894 mTreatBSizeAsIndefinite(aReflowInput
.mFlags
.mTreatBSizeAsIndefinite
) {}
1896 // The flex item's border and padding, in its own writing-mode, that it used
1897 // used during its most recent "final reflow".
1898 LogicalMargin mBorderPadding
;
1900 // The flex item's content-box size, in its own writing-mode, that it used
1901 // during its most recent "final reflow".
1904 // True if the flex item's BSize was considered "indefinite" in its most
1905 // recent "final reflow". (For a flex item "final reflow", this is fully
1906 // determined by the mTreatBSizeAsIndefinite flag in ReflowInput. See the
1907 // flag's documentation for more information.)
1908 bool mTreatBSizeAsIndefinite
;
1912 * When we instantiate/update a CachedFlexItemData, this enum must be used to
1913 * indicate the sort of reflow whose results we're capturing. This impacts
1914 * what we cache & how we use the cached information.
1916 enum class FlexItemReflowType
{
1917 // A reflow to measure the block-axis size of a flex item (as an input to the
1918 // flex layout algorithm).
1921 // A reflow with the flex item's "final" size at the end of the flex layout
1927 * This class stores information about the conditions and results for the most
1928 * recent ReflowChild call that we made on a given flex item. This information
1929 * helps us reason about whether we can assume that a subsequent ReflowChild()
1930 * invocation is unnecessary & skippable.
1932 class nsFlexContainerFrame::CachedFlexItemData
{
1934 CachedFlexItemData(const ReflowInput
& aReflowInput
,
1935 const ReflowOutput
& aReflowOutput
,
1936 FlexItemReflowType aType
) {
1937 Update(aReflowInput
, aReflowOutput
, aType
);
1940 // This method is intended to be called after we perform either a "measuring
1941 // reflow" or a "final reflow" for a given flex item.
1942 void Update(const ReflowInput
& aReflowInput
,
1943 const ReflowOutput
& aReflowOutput
, FlexItemReflowType aType
) {
1944 if (aType
== FlexItemReflowType::Measuring
) {
1945 mBAxisMeasurement
.reset();
1946 mBAxisMeasurement
.emplace(aReflowInput
, aReflowOutput
);
1947 // Clear any cached "last final reflow metrics", too, because now the most
1948 // recent reflow was *not* a "final reflow".
1949 mFinalReflowMetrics
.reset();
1953 MOZ_ASSERT(aType
== FlexItemReflowType::Final
);
1954 mFinalReflowMetrics
.reset();
1955 mFinalReflowMetrics
.emplace(aReflowInput
, aReflowOutput
);
1958 // This method is intended to be called for situations where we decide to
1959 // skip a final reflow because we've just done a measuring reflow which left
1960 // us (and our descendants) with the correct sizes. In this scenario, we
1961 // still want to cache the size as if we did a final reflow (because we've
1962 // determined that the recent measuring reflow was sufficient). That way,
1963 // our flex container can still skip a final reflow for this item in the
1964 // future as long as conditions are right.
1965 void Update(const FlexItem
& aItem
, const LogicalSize
& aSize
) {
1966 MOZ_ASSERT(!mFinalReflowMetrics
,
1967 "This version of the method is only intended to be called when "
1968 "the most recent reflow was a 'measuring reflow'; and that "
1969 "should have cleared out mFinalReflowMetrics");
1971 mFinalReflowMetrics
.reset(); // Just in case this assert^ fails.
1972 mFinalReflowMetrics
.emplace(aItem
, aSize
);
1975 // If the flex container needs a measuring reflow for the flex item, then the
1976 // resulting block-axis measurements can be cached here. If no measurement
1977 // has been needed so far, then this member will be Nothing().
1978 Maybe
<CachedBAxisMeasurement
> mBAxisMeasurement
;
1980 // The metrics that the corresponding flex item used in its most recent
1981 // "final reflow". (Note: the assumption here is that this reflow was this
1982 // item's most recent reflow of any type. If the item ends up undergoing a
1983 // subsequent measuring reflow, then this value needs to be cleared, because
1984 // at that point it's no longer an accurate way of reasoning about the
1985 // current state of the frame tree.)
1986 Maybe
<CachedFinalReflowMetrics
> mFinalReflowMetrics
;
1988 // Instances of this class are stored under this frame property, on
1989 // frames that are flex items:
1990 NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop
, CachedFlexItemData
)
1993 void nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(
1994 nsIFrame
* aItemFrame
) {
1995 MOZ_ASSERT(aItemFrame
->IsFlexItem());
1996 if (auto* cache
= aItemFrame
->GetProperty(CachedFlexItemData::Prop())) {
1997 cache
->mBAxisMeasurement
.reset();
1998 cache
->mFinalReflowMetrics
.reset();
2002 const CachedBAxisMeasurement
& nsFlexContainerFrame::MeasureBSizeForFlexItem(
2003 FlexItem
& aItem
, ReflowInput
& aChildReflowInput
) {
2004 auto* cachedData
= aItem
.Frame()->GetProperty(CachedFlexItemData::Prop());
2006 if (cachedData
&& cachedData
->mBAxisMeasurement
) {
2007 if (!aItem
.Frame()->IsSubtreeDirty() &&
2008 cachedData
->mBAxisMeasurement
->IsValidFor(aChildReflowInput
)) {
2009 FLEX_ITEM_LOG(aItem
.Frame(),
2010 "[perf] Accepted cached measurement: block-size %d",
2011 cachedData
->mBAxisMeasurement
->BSize());
2012 return *(cachedData
->mBAxisMeasurement
);
2014 FLEX_ITEM_LOG(aItem
.Frame(),
2015 "[perf] Rejected cached measurement: block-size %d",
2016 cachedData
->mBAxisMeasurement
->BSize());
2018 FLEX_ITEM_LOG(aItem
.Frame(), "[perf] No cached measurement");
2021 // CachedFlexItemData is stored in item's writing mode, so we pass
2022 // aChildReflowInput into ReflowOutput's constructor.
2023 ReflowOutput
childReflowOutput(aChildReflowInput
);
2024 nsReflowStatus childStatus
;
2026 const ReflowChildFlags flags
= ReflowChildFlags::NoMoveFrame
;
2027 const WritingMode outerWM
= GetWritingMode();
2028 const LogicalPoint
dummyPosition(outerWM
);
2029 const nsSize dummyContainerSize
;
2031 // We use NoMoveFrame, so the position and container size used here are
2033 ReflowChild(aItem
.Frame(), PresContext(), childReflowOutput
,
2034 aChildReflowInput
, outerWM
, dummyPosition
, dummyContainerSize
,
2035 flags
, childStatus
);
2036 aItem
.SetHadMeasuringReflow();
2038 // We always use unconstrained available block-size to measure flex items,
2039 // which means they should always complete.
2040 MOZ_ASSERT(childStatus
.IsComplete(),
2041 "We gave flex item unconstrained available block-size, so it "
2042 "should be complete");
2044 // Tell the child we're done with its initial reflow.
2045 // (Necessary for e.g. GetBaseline() to work below w/out asserting)
2046 FinishReflowChild(aItem
.Frame(), PresContext(), childReflowOutput
,
2047 &aChildReflowInput
, outerWM
, dummyPosition
,
2048 dummyContainerSize
, flags
);
2050 aItem
.SetAscent(childReflowOutput
.BlockStartAscent());
2052 // Update (or add) our cached measurement, so that we can hopefully skip this
2053 // measuring reflow the next time around:
2055 cachedData
->Update(aChildReflowInput
, childReflowOutput
,
2056 FlexItemReflowType::Measuring
);
2058 cachedData
= new CachedFlexItemData(aChildReflowInput
, childReflowOutput
,
2059 FlexItemReflowType::Measuring
);
2060 aItem
.Frame()->SetProperty(CachedFlexItemData::Prop(), cachedData
);
2062 return *(cachedData
->mBAxisMeasurement
);
2066 void nsFlexContainerFrame::MarkIntrinsicISizesDirty() {
2067 mCachedMinISize
= NS_INTRINSIC_ISIZE_UNKNOWN
;
2068 mCachedPrefISize
= NS_INTRINSIC_ISIZE_UNKNOWN
;
2070 nsContainerFrame::MarkIntrinsicISizesDirty();
2073 nscoord
nsFlexContainerFrame::MeasureFlexItemContentBSize(
2074 FlexItem
& aFlexItem
, bool aForceBResizeForMeasuringReflow
,
2075 const ReflowInput
& aParentReflowInput
) {
2076 FLEX_ITEM_LOG(aFlexItem
.Frame(), "Measuring item's content block-size");
2078 // Set up a reflow input for measuring the flex item's content block-size:
2079 WritingMode wm
= aFlexItem
.Frame()->GetWritingMode();
2080 LogicalSize availSize
= aParentReflowInput
.ComputedSize(wm
);
2081 availSize
.BSize(wm
) = NS_UNCONSTRAINEDSIZE
;
2083 StyleSizeOverrides sizeOverrides
;
2084 if (aFlexItem
.IsStretched()) {
2085 sizeOverrides
.mStyleISize
.emplace(aFlexItem
.StyleCrossSize());
2086 // Suppress any AspectRatio that we might have to prevent ComputeSize() from
2087 // transferring our inline-size override through the aspect-ratio to set the
2088 // block-size, because that would prevent us from measuring the content
2090 sizeOverrides
.mAspectRatio
.emplace(AspectRatio());
2091 FLEX_LOGV("Cross size override: %d", aFlexItem
.CrossSize());
2093 sizeOverrides
.mStyleBSize
.emplace(StyleSize::Auto());
2095 ReflowInput
childRIForMeasuringBSize(
2096 PresContext(), aParentReflowInput
, aFlexItem
.Frame(), availSize
,
2097 Nothing(), {}, sizeOverrides
, {ComputeSizeFlag::ShrinkWrap
});
2099 // When measuring flex item's content block-size, disregard the item's
2100 // min-block-size and max-block-size by resetting both to to their
2101 // unconstraining (extreme) values. The flexbox layout algorithm does still
2102 // explicitly clamp both sizes when resolving the target main size.
2103 childRIForMeasuringBSize
.SetComputedMinBSize(0);
2104 childRIForMeasuringBSize
.SetComputedMaxBSize(NS_UNCONSTRAINEDSIZE
);
2106 if (aForceBResizeForMeasuringReflow
) {
2107 childRIForMeasuringBSize
.SetBResize(true);
2108 // Not 100% sure this is needed, but be conservative for now:
2109 childRIForMeasuringBSize
.mFlags
.mIsBResizeForPercentages
= true;
2112 const CachedBAxisMeasurement
& measurement
=
2113 MeasureBSizeForFlexItem(aFlexItem
, childRIForMeasuringBSize
);
2115 return measurement
.BSize();
2118 FlexItem::FlexItem(ReflowInput
& aFlexItemReflowInput
, float aFlexGrow
,
2119 float aFlexShrink
, nscoord aFlexBaseSize
,
2120 nscoord aMainMinSize
, nscoord aMainMaxSize
,
2121 nscoord aTentativeCrossSize
, nscoord aCrossMinSize
,
2122 nscoord aCrossMaxSize
,
2123 const FlexboxAxisTracker
& aAxisTracker
)
2124 : mFrame(aFlexItemReflowInput
.mFrame
),
2125 mFlexGrow(aFlexGrow
),
2126 mFlexShrink(aFlexShrink
),
2127 mAspectRatio(mFrame
->GetAspectRatio()),
2128 mWM(aFlexItemReflowInput
.GetWritingMode()),
2129 mCBWM(aAxisTracker
.GetWritingMode()),
2130 mMainAxis(aAxisTracker
.MainAxis()),
2131 mBorderPadding(aFlexItemReflowInput
.ComputedLogicalBorderPadding(mCBWM
)),
2132 mMargin(aFlexItemReflowInput
.ComputedLogicalMargin(mCBWM
)),
2133 mMainMinSize(aMainMinSize
),
2134 mMainMaxSize(aMainMaxSize
),
2135 mCrossMinSize(aCrossMinSize
),
2136 mCrossMaxSize(aCrossMaxSize
),
2137 mCrossSize(aTentativeCrossSize
),
2138 mIsInlineAxisMainAxis(aAxisTracker
.IsInlineAxisMainAxis(mWM
)),
2139 mNeedsMinSizeAutoResolution(IsMinSizeAutoResolutionNeeded())
2140 // mAlignSelf, mHasAnyAutoMargin see below
2142 MOZ_ASSERT(mFrame
, "expecting a non-null child frame");
2143 MOZ_ASSERT(!mFrame
->IsPlaceholderFrame(),
2144 "placeholder frames should not be treated as flex items");
2145 MOZ_ASSERT(!mFrame
->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW
),
2146 "out-of-flow frames should not be treated as flex items");
2147 MOZ_ASSERT(mIsInlineAxisMainAxis
==
2148 nsFlexContainerFrame::IsItemInlineAxisMainAxis(mFrame
),
2149 "public API should be consistent with internal state (about "
2150 "whether flex item's inline axis is flex container's main axis)");
2152 const ReflowInput
* containerRS
= aFlexItemReflowInput
.mParentReflowInput
;
2153 if (IsLegacyBox(containerRS
->mFrame
)) {
2154 // For -webkit-{inline-}box and -moz-{inline-}box, we need to:
2155 // (1) Use prefixed "box-align" instead of "align-items" to determine the
2156 // container's cross-axis alignment behavior.
2157 // (2) Suppress the ability for flex items to override that with their own
2158 // cross-axis alignment. (The legacy box model doesn't support this.)
2159 // So, each FlexItem simply copies the container's converted "align-items"
2160 // value and disregards their own "align-self" property.
2161 const nsStyleXUL
* containerStyleXUL
= containerRS
->mFrame
->StyleXUL();
2162 mAlignSelf
= {ConvertLegacyStyleToAlignItems(containerStyleXUL
)};
2163 mAlignSelfFlags
= {0};
2165 mAlignSelf
= aFlexItemReflowInput
.mStylePosition
->UsedAlignSelf(
2166 containerRS
->mFrame
->Style());
2167 if (MOZ_LIKELY(mAlignSelf
._0
== StyleAlignFlags::NORMAL
)) {
2168 mAlignSelf
= {StyleAlignFlags::STRETCH
};
2171 // Store and strip off the <overflow-position> bits
2172 mAlignSelfFlags
= mAlignSelf
._0
& StyleAlignFlags::FLAG_BITS
;
2173 mAlignSelf
._0
&= ~StyleAlignFlags::FLAG_BITS
;
2176 // Our main-size is considered definite if any of these are true:
2177 // (a) main axis is the item's inline axis.
2178 // (b) flex container has definite main size.
2179 // (c) flex item has a definite flex basis.
2181 // Hence, we need to take care to treat the final main-size as *indefinite*
2182 // if none of these conditions are satisfied.
2183 if (mIsInlineAxisMainAxis
) {
2184 // The item's block-axis is the flex container's cross axis. We don't need
2185 // any special handling to treat cross sizes as indefinite, because the
2186 // cases where we stomp on the cross size with a definite value are all...
2187 // - situations where the spec requires us to treat the cross size as
2188 // definite; specifically, `align-self:stretch` whose cross size is
2190 // - situations where definiteness doesn't matter (e.g. for an element with
2191 // an aspect ratio, which for now are all leaf nodes and hence
2192 // can't have any percent-height descendants that would care about the
2193 // definiteness of its size. (Once bug 1528375 is fixed, we might need to
2194 // be more careful about definite vs. indefinite sizing on flex items with
2196 mTreatBSizeAsIndefinite
= false;
2198 // The item's block-axis is the flex container's main axis. So, the flex
2199 // item's main size is its BSize, and is considered definite under certain
2200 // conditions laid out for definite flex-item main-sizes in the spec.
2201 if (aAxisTracker
.IsRowOriented() ||
2202 (containerRS
->ComputedBSize() != NS_UNCONSTRAINEDSIZE
&&
2203 !containerRS
->mFlags
.mTreatBSizeAsIndefinite
)) {
2204 // The flex *container* has a definite main-size (either by being
2205 // row-oriented [and using its own inline size which is by definition
2206 // definite, or by being column-oriented and having a definite
2207 // block-size). The spec says this means all of the flex items'
2208 // post-flexing main sizes should *also* be treated as definite.
2209 mTreatBSizeAsIndefinite
= false;
2210 } else if (aFlexBaseSize
!= NS_UNCONSTRAINEDSIZE
) {
2211 // The flex item has a definite flex basis, which we'll treat as making
2212 // its main-size definite.
2213 mTreatBSizeAsIndefinite
= false;
2215 // Otherwise, we have to treat the item's BSize as indefinite.
2216 mTreatBSizeAsIndefinite
= true;
2220 SetFlexBaseSizeAndMainSize(aFlexBaseSize
);
2222 const nsStyleMargin
* styleMargin
= aFlexItemReflowInput
.mStyleMargin
;
2223 mHasAnyAutoMargin
= styleMargin
->HasInlineAxisAuto(mCBWM
) ||
2224 styleMargin
->HasBlockAxisAuto(mCBWM
);
2226 // Assert that any "auto" margin components are set to 0.
2227 // (We'll resolve them later; until then, we want to treat them as 0-sized.)
2230 for (const auto side
: LogicalSides::All
) {
2231 if (styleMargin
->mMargin
.Get(side
, mCBWM
).IsAuto()) {
2232 MOZ_ASSERT(GetMarginComponentForSide(side
) == 0,
2233 "Someone else tried to resolve our auto margin");
2239 if (mAlignSelf
._0
== StyleAlignFlags::BASELINE
||
2240 mAlignSelf
._0
== StyleAlignFlags::LAST_BASELINE
) {
2241 // Check which of the item's baselines we're meant to use (first vs. last)
2242 const bool usingItemFirstBaseline
=
2243 (mAlignSelf
._0
== StyleAlignFlags::BASELINE
);
2244 if (IsBlockAxisCrossAxis()) {
2245 // The flex item wants to be aligned in the cross axis using one of its
2246 // baselines; and the cross axis is the item's block axis, so
2247 // baseline-alignment in that axis makes sense.
2249 // To determine the item's baseline sharing group, we check whether the
2250 // item's block axis has the same vs. opposite flow direction as the
2251 // corresponding LogicalAxis on the flex container. We do this by
2252 // getting the physical side that corresponds to these axes' "logical
2253 // start" sides, and we compare those physical sides to find out if
2254 // they're the same vs. opposite.
2255 mozilla::Side itemBlockStartSide
= mWM
.PhysicalSide(LogicalSide::BStart
);
2257 // (Note: this is *not* the "flex-start" side; rather, it's the *logical*
2258 // i.e. WM-relative block-start or inline-start side.)
2259 mozilla::Side containerStartSideInCrossAxis
= mCBWM
.PhysicalSide(
2260 MakeLogicalSide(aAxisTracker
.CrossAxis(), LogicalEdge::Start
));
2262 // We already know these two Sides (the item's block-start and the
2263 // container's 'logical start' side for its cross axis) are in the same
2264 // physical axis, since we're inside of a check for
2265 // FlexItem::IsBlockAxisCrossAxis(). So these two Sides must be either
2266 // the same physical side or opposite from each other. If the Sides are
2267 // the same, then the flow direction is the same, which means the item's
2268 // {first,last} baseline participates in the {first,last}
2269 // baseline-sharing group in its FlexLine. Otherwise, the flow direction
2270 // is opposite, and so the item's {first,last} baseline participates in
2271 // the opposite i.e. {last,first} baseline-sharing group. This is
2272 // roughly per css-align-3 section 9.2, specifically the definition of
2273 // what makes baseline alignment preferences "compatible".
2274 bool itemBlockAxisFlowDirMatchesContainer
=
2275 (itemBlockStartSide
== containerStartSideInCrossAxis
);
2276 mBaselineSharingGroup
=
2277 (itemBlockAxisFlowDirMatchesContainer
== usingItemFirstBaseline
)
2278 ? BaselineSharingGroup::First
2279 : BaselineSharingGroup::Last
;
2281 // The flex item wants to be aligned in the cross axis using one of its
2282 // baselines, but we cannot get its baseline because the FlexItem's block
2283 // axis is *orthogonal* to the container's cross axis. To handle this, we
2284 // are supposed to synthesize a baseline from the item's border box and
2285 // using that for baseline alignment.
2286 mBaselineSharingGroup
= usingItemFirstBaseline
2287 ? BaselineSharingGroup::First
2288 : BaselineSharingGroup::Last
;
2293 // Simplified constructor for creating a special "strut" FlexItem, for a child
2294 // with visibility:collapse. The strut has 0 main-size, and it only exists to
2295 // impose a minimum cross size on whichever FlexLine it ends up in.
2296 FlexItem::FlexItem(nsIFrame
* aChildFrame
, nscoord aCrossSize
,
2297 WritingMode aContainerWM
,
2298 const FlexboxAxisTracker
& aAxisTracker
)
2299 : mFrame(aChildFrame
),
2300 mWM(aChildFrame
->GetWritingMode()),
2301 mCBWM(aContainerWM
),
2302 mMainAxis(aAxisTracker
.MainAxis()),
2303 mBorderPadding(mCBWM
),
2305 mCrossSize(aCrossSize
),
2306 // Struts don't do layout, so its WM doesn't matter at this point. So, we
2307 // just share container's WM for simplicity:
2309 mIsStrut(true), // (this is the constructor for making struts, after all)
2310 mAlignSelf({StyleAlignFlags::FLEX_START
}) {
2311 MOZ_ASSERT(mFrame
, "expecting a non-null child frame");
2312 MOZ_ASSERT(mFrame
->StyleVisibility()->IsCollapse(),
2313 "Should only make struts for children with 'visibility:collapse'");
2314 MOZ_ASSERT(!mFrame
->IsPlaceholderFrame(),
2315 "placeholder frames should not be treated as flex items");
2316 MOZ_ASSERT(!mFrame
->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW
),
2317 "out-of-flow frames should not be treated as flex items");
2320 bool FlexItem::IsMinSizeAutoResolutionNeeded() const {
2321 // We'll need special behavior for "min-[width|height]:auto" (whichever is in
2322 // the flex container's main axis) iff:
2323 // (a) its computed value is "auto", and
2324 // (b) the item is *not* a scroll container. (A scroll container's automatic
2325 // minimum size is zero.)
2326 // https://drafts.csswg.org/css-flexbox-1/#min-size-auto
2328 // Note that the scroll container case is redefined to be looking at the
2329 // computed value instead, see https://github.com/w3c/csswg-drafts/issues/7714
2330 const auto& mainMinSize
=
2331 Frame()->StylePosition()->MinSize(MainAxis(), ContainingBlockWM());
2333 return IsAutoOrEnumOnBSize(mainMinSize
, IsInlineAxisMainAxis()) &&
2334 !Frame()->StyleDisplay()->IsScrollableOverflow();
2337 Maybe
<nscoord
> FlexItem::MeasuredBSize() const {
2339 Frame()->FirstInFlow()->GetProperty(CachedFlexItemData::Prop());
2340 if (!cachedData
|| !cachedData
->mBAxisMeasurement
) {
2343 return Some(cachedData
->mBAxisMeasurement
->BSize());
2346 nscoord
FlexItem::BaselineOffsetFromOuterCrossEdge(
2347 mozilla::Side aStartSide
, bool aUseFirstLineBaseline
) const {
2349 // * We only use baselines for aligning in the flex container's cross axis.
2350 // * Baselines are a measurement in the item's block axis.
2351 if (IsBlockAxisMainAxis()) {
2352 // We get here if the item's block axis is *orthogonal* the container's
2353 // cross axis. For example, a flex item with writing-mode:horizontal-tb in a
2354 // column-oriented flex container. We need to synthesize the item's baseline
2355 // from its border-box edge.
2356 const bool isMainAxisHorizontal
=
2357 mCBWM
.PhysicalAxis(MainAxis()) == PhysicalAxis::Horizontal
;
2359 // When the main axis is horizontal, the synthesized baseline is the bottom
2360 // edge of the item's border-box. Otherwise, when the main axis is vertical,
2361 // the left edge. This is for compatibility with Google Chrome.
2362 nscoord marginTopOrLeftToBaseline
=
2363 isMainAxisHorizontal
? PhysicalMargin().top
: PhysicalMargin().left
;
2364 if (mCBWM
.IsAlphabeticalBaseline()) {
2365 marginTopOrLeftToBaseline
+= (isMainAxisHorizontal
? CrossSize() : 0);
2367 MOZ_ASSERT(mCBWM
.IsCentralBaseline());
2368 marginTopOrLeftToBaseline
+= CrossSize() / 2;
2371 return aStartSide
== mozilla::eSideTop
|| aStartSide
== mozilla::eSideLeft
2372 ? marginTopOrLeftToBaseline
2373 : OuterCrossSize() - marginTopOrLeftToBaseline
;
2376 // We get here if the item's block axis is parallel (or antiparallel) to the
2377 // container's cross axis. We call ResolvedAscent() to get the item's
2378 // baseline. If the item has no baseline, the method will synthesize one from
2379 // the border-box edge.
2380 MOZ_ASSERT(IsBlockAxisCrossAxis(),
2381 "Only expecting to be doing baseline computations when the "
2382 "cross axis is the block axis");
2384 mozilla::Side itemBlockStartSide
= mWM
.PhysicalSide(LogicalSide::BStart
);
2386 nscoord marginBStartToBaseline
= ResolvedAscent(aUseFirstLineBaseline
) +
2387 PhysicalMargin().Side(itemBlockStartSide
);
2389 return (aStartSide
== itemBlockStartSide
)
2390 ? marginBStartToBaseline
2391 : OuterCrossSize() - marginBStartToBaseline
;
2394 bool FlexItem::IsCrossSizeAuto() const {
2395 const nsStylePosition
* stylePos
=
2396 nsLayoutUtils::GetStyleFrame(mFrame
)->StylePosition();
2397 // Check whichever component is in the flex container's cross axis.
2398 // (IsInlineAxisCrossAxis() tells us whether that's our ISize or BSize, in
2399 // terms of our own WritingMode, mWM.)
2400 return IsInlineAxisCrossAxis() ? stylePos
->ISize(mWM
).IsAuto()
2401 : stylePos
->BSize(mWM
).IsAuto();
2404 bool FlexItem::IsCrossSizeDefinite(const ReflowInput
& aItemReflowInput
) const {
2405 if (IsStretched()) {
2406 // Definite cross-size, imposed via 'align-self:stretch' & flex container.
2410 const nsStylePosition
* pos
= aItemReflowInput
.mStylePosition
;
2411 const auto itemWM
= GetWritingMode();
2413 // The logic here should be similar to the logic for isAutoISize/isAutoBSize
2414 // in nsContainerFrame::ComputeSizeWithIntrinsicDimensions().
2415 if (IsInlineAxisCrossAxis()) {
2416 return !pos
->ISize(itemWM
).IsAuto();
2419 nscoord cbBSize
= aItemReflowInput
.mContainingBlockSize
.BSize(itemWM
);
2420 return !nsLayoutUtils::IsAutoBSize(pos
->BSize(itemWM
), cbBSize
);
2423 void FlexItem::ResolveFlexBaseSizeFromAspectRatio(
2424 const ReflowInput
& aItemReflowInput
) {
2425 // This implements the Flex Layout Algorithm Step 3B:
2426 // https://drafts.csswg.org/css-flexbox-1/#algo-main-item
2427 // If the flex item has ...
2428 // - an aspect ratio,
2429 // - a [used] flex-basis of 'content', and
2430 // - a definite cross size
2431 // then the flex base size is calculated from its inner cross size and the
2432 // flex item's preferred aspect ratio.
2433 if (HasAspectRatio() &&
2434 nsFlexContainerFrame::IsUsedFlexBasisContent(
2435 aItemReflowInput
.mStylePosition
->mFlexBasis
,
2436 aItemReflowInput
.mStylePosition
->Size(MainAxis(), mCBWM
)) &&
2437 IsCrossSizeDefinite(aItemReflowInput
)) {
2438 const LogicalSize contentBoxSizeToBoxSizingAdjust
=
2439 aItemReflowInput
.mStylePosition
->mBoxSizing
== StyleBoxSizing::Border
2440 ? BorderPadding().Size(mCBWM
)
2441 : LogicalSize(mCBWM
);
2442 const nscoord mainSizeFromRatio
= mAspectRatio
.ComputeRatioDependentSize(
2443 MainAxis(), mCBWM
, CrossSize(), contentBoxSizeToBoxSizingAdjust
);
2444 SetFlexBaseSizeAndMainSize(mainSizeFromRatio
);
2448 uint32_t FlexItem::NumAutoMarginsInAxis(LogicalAxis aAxis
) const {
2449 uint32_t numAutoMargins
= 0;
2450 const auto& styleMargin
= mFrame
->StyleMargin()->mMargin
;
2451 for (const auto edge
: {LogicalEdge::Start
, LogicalEdge::End
}) {
2452 const auto side
= MakeLogicalSide(aAxis
, edge
);
2453 if (styleMargin
.Get(side
, mCBWM
).IsAuto()) {
2458 // Mostly for clarity:
2459 MOZ_ASSERT(numAutoMargins
<= 2,
2460 "We're just looking at one item along one dimension, so we "
2461 "should only have examined 2 margins");
2463 return numAutoMargins
;
2466 bool FlexItem::CanMainSizeInfluenceCrossSize() const {
2468 // We've already had our cross-size stretched for "align-self:stretch").
2469 // The container is imposing its cross size on us.
2474 // Struts (for visibility:collapse items) have a predetermined size;
2475 // no need to measure anything.
2479 if (HasAspectRatio()) {
2480 // For flex items that have an aspect ratio (and maintain it, i.e. are
2481 // not stretched, which we already checked above): changes to main-size
2482 // *do* influence the cross size.
2486 if (IsInlineAxisCrossAxis()) {
2487 // If we get here, this function is really asking: "can changes to this
2488 // item's block size have an influence on its inline size"? For blocks and
2489 // tables, the answer is "no".
2490 if (mFrame
->IsBlockFrame() || mFrame
->IsTableWrapperFrame()) {
2491 // XXXdholbert (Maybe use an IsFrameOfType query or something more
2492 // general to test this across all frame types? For now, I'm just
2493 // optimizing for block and table, since those are common containers that
2494 // can contain arbitrarily-large subtrees (and that reliably have ISize
2495 // being unaffected by BSize, per CSS2). So optimizing away needless
2496 // relayout is possible & especially valuable for these containers.)
2499 // Other opt-outs can go here, as they're identified as being useful
2500 // (particularly for containers where an extra reflow is expensive). But in
2501 // general, we have to assume that a flexed BSize *could* influence the
2502 // ISize. Some examples where this can definitely happen:
2503 // * Intrinsically-sized multicol with fixed-ISize columns, which adds
2504 // columns (i.e. grows in inline axis) depending on its block size.
2505 // * Intrinsically-sized multi-line column-oriented flex container, which
2506 // adds flex lines (i.e. grows in inline axis) depending on its block size.
2509 // Default assumption, if we haven't proven otherwise: the resolved main size
2510 // *can* change the cross size.
2514 nscoord
FlexItem::ClampMainSizeViaCrossAxisConstraints(
2515 nscoord aMainSize
, const ReflowInput
& aItemReflowInput
) const {
2516 MOZ_ASSERT(HasAspectRatio(), "Caller should've checked the ratio is valid!");
2518 const LogicalSize contentBoxSizeToBoxSizingAdjust
=
2519 aItemReflowInput
.mStylePosition
->mBoxSizing
== StyleBoxSizing::Border
2520 ? BorderPadding().Size(mCBWM
)
2521 : LogicalSize(mCBWM
);
2523 const nscoord mainMinSizeFromRatio
= mAspectRatio
.ComputeRatioDependentSize(
2524 MainAxis(), mCBWM
, CrossMinSize(), contentBoxSizeToBoxSizingAdjust
);
2525 nscoord clampedMainSize
= std::max(aMainSize
, mainMinSizeFromRatio
);
2527 if (CrossMaxSize() != NS_UNCONSTRAINEDSIZE
) {
2528 const nscoord mainMaxSizeFromRatio
= mAspectRatio
.ComputeRatioDependentSize(
2529 MainAxis(), mCBWM
, CrossMaxSize(), contentBoxSizeToBoxSizingAdjust
);
2530 clampedMainSize
= std::min(clampedMainSize
, mainMaxSizeFromRatio
);
2533 return clampedMainSize
;
2537 * Returns true if aFrame or any of its children have the
2538 * NS_FRAME_CONTAINS_RELATIVE_BSIZE flag set -- i.e. if any of these frames (or
2539 * their descendants) might have a relative-BSize dependency on aFrame (or its
2542 static bool FrameHasRelativeBSizeDependency(nsIFrame
* aFrame
) {
2543 if (aFrame
->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE
)) {
2546 for (const auto& childList
: aFrame
->ChildLists()) {
2547 for (nsIFrame
* childFrame
: childList
.mList
) {
2548 if (childFrame
->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE
)) {
2556 bool FlexItem::NeedsFinalReflow(const ReflowInput
& aParentReflowInput
) const {
2557 if (!StaticPrefs::layout_flexbox_item_final_reflow_optimization_enabled()) {
2558 FLEX_ITEM_LOG(mFrame
,
2559 "[perf] Item needed a final reflow due to optimization being "
2560 "disabled via the preference");
2564 // NOTE: We can have continuations from an earlier constrained reflow.
2565 if (mFrame
->GetPrevInFlow() || mFrame
->GetNextInFlow()) {
2566 // This is an item has continuation(s). Reflow it.
2567 FLEX_ITEM_LOG(mFrame
,
2568 "[frag] Item needed a final reflow due to continuation(s)");
2572 // A flex item can grow its block-size in a fragmented context if there's any
2573 // force break within it (bug 1663079), or if it has a repeated table header
2574 // or footer (bug 1744363). We currently always reflow it.
2576 // Bug 1815294: investigate if we can design a more specific condition to
2577 // prevent triggering O(n^2) behavior when printing a deeply-nested flex
2579 if (aParentReflowInput
.IsInFragmentedContext()) {
2580 FLEX_ITEM_LOG(mFrame
,
2581 "[frag] Item needed both a measuring reflow and a final "
2582 "reflow due to being in a fragmented context");
2586 // Flex item's final content-box size (in terms of its own writing-mode):
2587 const LogicalSize finalSize
= mIsInlineAxisMainAxis
2588 ? LogicalSize(mWM
, mMainSize
, mCrossSize
)
2589 : LogicalSize(mWM
, mCrossSize
, mMainSize
);
2591 if (HadMeasuringReflow()) {
2592 // We've already reflowed this flex item once, to measure it. In that
2593 // reflow, did its frame happen to end up with the correct final size
2594 // that the flex container would like it to have?
2595 if (finalSize
!= mFrame
->ContentSize(mWM
)) {
2596 // The measuring reflow left the item with a different size than its
2597 // final flexed size. So, we need to reflow to give it the correct size.
2598 FLEX_ITEM_LOG(mFrame
,
2599 "[perf] Item needed both a measuring reflow and a final "
2600 "reflow due to measured size disagreeing with final size");
2604 if (FrameHasRelativeBSizeDependency(mFrame
)) {
2605 // This item has descendants with relative BSizes who may care that its
2606 // size may now be considered "definite" in the final reflow (whereas it
2607 // was indefinite during the measuring reflow).
2608 FLEX_ITEM_LOG(mFrame
,
2609 "[perf] Item needed both a measuring reflow and a final "
2610 "reflow due to BSize potentially becoming definite");
2614 // If we get here, then this flex item had a measuring reflow, it left us
2615 // with the correct size, none of its descendants care that its BSize may
2616 // now be considered definite, and it can fit into the available block-size.
2617 // So it doesn't need a final reflow.
2619 // We now cache this size as if we had done a final reflow (because we've
2620 // determined that the measuring reflow was effectively equivalent). This
2621 // way, in our next time through flex layout, we may be able to skip both
2622 // the measuring reflow *and* the final reflow (if conditions are the same
2623 // as they are now).
2624 if (auto* cache
= mFrame
->GetProperty(CachedFlexItemData::Prop())) {
2625 cache
->Update(*this, finalSize
);
2631 // This item didn't receive a measuring reflow (at least, not during this
2632 // reflow of our flex container). We may still be able to skip reflowing it
2633 // (i.e. return false from this function), if its subtree is clean & its most
2634 // recent "final reflow" had it at the correct content-box size &
2636 // Let's check for each condition that would still require us to reflow:
2637 if (mFrame
->IsSubtreeDirty()) {
2640 "[perf] Item needed a final reflow due to its subtree being dirty");
2644 // Cool; this item & its subtree haven't experienced any style/content
2645 // changes that would automatically require a reflow.
2647 // Did we cache the metrics from its most recent "final reflow"?
2648 auto* cache
= mFrame
->GetProperty(CachedFlexItemData::Prop());
2649 if (!cache
|| !cache
->mFinalReflowMetrics
) {
2650 FLEX_ITEM_LOG(mFrame
,
2651 "[perf] Item needed a final reflow due to lacking a cached "
2652 "mFinalReflowMetrics (maybe cache was cleared)");
2656 // Does the cached size match our current size?
2657 if (cache
->mFinalReflowMetrics
->Size() != finalSize
) {
2658 FLEX_ITEM_LOG(mFrame
,
2659 "[perf] Item needed a final reflow due to having a different "
2660 "content box size vs. its most recent final reflow");
2664 // Does the cached border and padding match our current ones?
2666 // Note: this is just to detect cases where we have a percent padding whose
2667 // basis has changed. Any other sort of change to BorderPadding() (e.g. a new
2668 // specified value) should result in the frame being marked dirty via proper
2669 // change hint (see nsStylePadding::CalcDifference()), which will force it to
2671 if (cache
->mFinalReflowMetrics
->BorderPadding() !=
2672 BorderPadding().ConvertTo(mWM
, mCBWM
)) {
2673 FLEX_ITEM_LOG(mFrame
,
2674 "[perf] Item needed a final reflow due to having a different "
2675 "border and padding vs. its most recent final reflow");
2679 // The flex container is giving this flex item the same size that the item
2680 // had on its most recent "final reflow". But if its definiteness changed and
2681 // one of the descendants cares, then it would still need a reflow.
2682 if (cache
->mFinalReflowMetrics
->TreatBSizeAsIndefinite() !=
2683 mTreatBSizeAsIndefinite
&&
2684 FrameHasRelativeBSizeDependency(mFrame
)) {
2685 FLEX_ITEM_LOG(mFrame
,
2686 "[perf] Item needed a final reflow due to having its BSize "
2687 "change definiteness & having a rel-BSize child");
2691 // If we get here, we can skip the final reflow! (The item's subtree isn't
2692 // dirty, and our current conditions are sufficiently similar to the most
2693 // recent "final reflow" that it should have left our subtree in the correct
2695 FLEX_ITEM_LOG(mFrame
, "[perf] Item didn't need a final reflow");
2699 // Keeps track of our position along a particular axis (where a '0' position
2700 // corresponds to the 'start' edge of that axis).
2701 // This class shouldn't be instantiated directly -- rather, it should only be
2702 // instantiated via its subclasses defined below.
2703 class MOZ_STACK_CLASS PositionTracker
{
2705 // Accessor for the current value of the position that we're tracking.
2706 inline nscoord
Position() const { return mPosition
; }
2707 inline LogicalAxis
Axis() const { return mAxis
; }
2709 inline LogicalSide
StartSide() {
2710 return MakeLogicalSide(
2711 mAxis
, mIsAxisReversed
? LogicalEdge::End
: LogicalEdge::Start
);
2714 inline LogicalSide
EndSide() {
2715 return MakeLogicalSide(
2716 mAxis
, mIsAxisReversed
? LogicalEdge::Start
: LogicalEdge::End
);
2719 // Advances our position across the start edge of the given margin, in the
2720 // axis we're tracking.
2721 void EnterMargin(const LogicalMargin
& aMargin
) {
2722 mPosition
+= aMargin
.Side(StartSide(), mWM
);
2725 // Advances our position across the end edge of the given margin, in the axis
2727 void ExitMargin(const LogicalMargin
& aMargin
) {
2728 mPosition
+= aMargin
.Side(EndSide(), mWM
);
2731 // Advances our current position from the start side of a child frame's
2732 // border-box to the frame's upper or left edge (depending on our axis).
2733 // (Note that this is a no-op if our axis grows in the same direction as
2734 // the corresponding logical axis.)
2735 void EnterChildFrame(nscoord aChildFrameSize
) {
2736 if (mIsAxisReversed
) {
2737 mPosition
+= aChildFrameSize
;
2741 // Advances our current position from a frame's upper or left border-box edge
2742 // (whichever is in the axis we're tracking) to the 'end' side of the frame
2743 // in the axis that we're tracking. (Note that this is a no-op if our axis
2744 // is reversed with respect to the corresponding logical axis.)
2745 void ExitChildFrame(nscoord aChildFrameSize
) {
2746 if (!mIsAxisReversed
) {
2747 mPosition
+= aChildFrameSize
;
2751 // Delete copy-constructor & reassignment operator, to prevent accidental
2752 // (unnecessary) copying.
2753 PositionTracker(const PositionTracker
&) = delete;
2754 PositionTracker
& operator=(const PositionTracker
&) = delete;
2757 // Protected constructor, to be sure we're only instantiated via a subclass.
2758 PositionTracker(WritingMode aWM
, LogicalAxis aAxis
, bool aIsAxisReversed
)
2759 : mWM(aWM
), mAxis(aAxis
), mIsAxisReversed(aIsAxisReversed
) {}
2762 // The position we're tracking.
2763 nscoord mPosition
= 0;
2765 // The flex container's writing mode.
2766 const WritingMode mWM
;
2768 // The axis along which we're moving.
2769 const LogicalAxis mAxis
= LogicalAxis::Inline
;
2771 // Is the axis along which we're moving reversed (e.g. LTR vs RTL) with
2772 // respect to the corresponding axis on the flex container's WM?
2773 const bool mIsAxisReversed
= false;
2776 // Tracks our position in the main axis, when we're laying out flex items.
2777 // The "0" position represents the main-start edge of the flex container's
2779 class MOZ_STACK_CLASS MainAxisPositionTracker
: public PositionTracker
{
2781 MainAxisPositionTracker(const FlexboxAxisTracker
& aAxisTracker
,
2782 const FlexLine
* aLine
,
2783 const StyleContentDistribution
& aJustifyContent
,
2784 nscoord aContentBoxMainSize
);
2786 ~MainAxisPositionTracker() {
2787 MOZ_ASSERT(mNumPackingSpacesRemaining
== 0,
2788 "miscounted the number of packing spaces");
2789 MOZ_ASSERT(mNumAutoMarginsInMainAxis
== 0,
2790 "miscounted the number of auto margins");
2793 // Advances past the gap space (if any) between two flex items
2794 void TraverseGap(nscoord aGapSize
) { mPosition
+= aGapSize
; }
2796 // Advances past the packing space (if any) between two flex items
2797 void TraversePackingSpace();
2799 // If aItem has any 'auto' margins in the main axis, this method updates the
2800 // corresponding values in its margin.
2801 void ResolveAutoMarginsInMainAxis(FlexItem
& aItem
);
2804 nscoord mPackingSpaceRemaining
= 0;
2805 uint32_t mNumAutoMarginsInMainAxis
= 0;
2806 uint32_t mNumPackingSpacesRemaining
= 0;
2807 StyleContentDistribution mJustifyContent
= {StyleAlignFlags::AUTO
};
2810 // Utility class for managing our position along the cross axis along
2811 // the whole flex container (at a higher level than a single line).
2812 // The "0" position represents the cross-start edge of the flex container's
2814 class MOZ_STACK_CLASS CrossAxisPositionTracker
: public PositionTracker
{
2816 CrossAxisPositionTracker(nsTArray
<FlexLine
>& aLines
,
2817 const ReflowInput
& aReflowInput
,
2818 nscoord aContentBoxCrossSize
,
2819 bool aIsCrossSizeDefinite
,
2820 const FlexboxAxisTracker
& aAxisTracker
,
2821 const nscoord aCrossGapSize
);
2823 // Advances past the gap (if any) between two flex lines
2824 void TraverseGap() { mPosition
+= mCrossGapSize
; }
2826 // Advances past the packing space (if any) between two flex lines
2827 void TraversePackingSpace();
2829 // Advances past the given FlexLine
2830 void TraverseLine(FlexLine
& aLine
) { mPosition
+= aLine
.LineCrossSize(); }
2832 // Redeclare the frame-related methods from PositionTracker with
2833 // = delete, to be sure (at compile time) that no client code can invoke
2834 // them. (Unlike the other PositionTracker derived classes, this class here
2835 // deals with FlexLines, not with individual FlexItems or frames.)
2836 void EnterMargin(const LogicalMargin
& aMargin
) = delete;
2837 void ExitMargin(const LogicalMargin
& aMargin
) = delete;
2838 void EnterChildFrame(nscoord aChildFrameSize
) = delete;
2839 void ExitChildFrame(nscoord aChildFrameSize
) = delete;
2842 nscoord mPackingSpaceRemaining
= 0;
2843 uint32_t mNumPackingSpacesRemaining
= 0;
2844 StyleContentDistribution mAlignContent
= {StyleAlignFlags::AUTO
};
2846 const nscoord mCrossGapSize
;
2849 // Utility class for managing our position along the cross axis, *within* a
2850 // single flex line.
2851 class MOZ_STACK_CLASS SingleLineCrossAxisPositionTracker
2852 : public PositionTracker
{
2854 explicit SingleLineCrossAxisPositionTracker(
2855 const FlexboxAxisTracker
& aAxisTracker
);
2857 void ResolveAutoMarginsInCrossAxis(const FlexLine
& aLine
, FlexItem
& aItem
);
2859 void EnterAlignPackingSpace(const FlexLine
& aLine
, const FlexItem
& aItem
,
2860 const FlexboxAxisTracker
& aAxisTracker
);
2862 // Resets our position to the cross-start edge of this line.
2863 inline void ResetPosition() { mPosition
= 0; }
2866 //----------------------------------------------------------------------
2868 // Frame class boilerplate
2869 // =======================
2871 NS_QUERYFRAME_HEAD(nsFlexContainerFrame
)
2872 NS_QUERYFRAME_ENTRY(nsFlexContainerFrame
)
2873 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
2875 NS_IMPL_FRAMEARENA_HELPERS(nsFlexContainerFrame
)
2877 nsContainerFrame
* NS_NewFlexContainerFrame(PresShell
* aPresShell
,
2878 ComputedStyle
* aStyle
) {
2879 return new (aPresShell
)
2880 nsFlexContainerFrame(aStyle
, aPresShell
->GetPresContext());
2883 //----------------------------------------------------------------------
2885 // nsFlexContainerFrame Method Implementations
2886 // ===========================================
2889 nsFlexContainerFrame::~nsFlexContainerFrame() = default;
2892 void nsFlexContainerFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
2893 nsIFrame
* aPrevInFlow
) {
2894 nsContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
2896 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER
)) {
2897 AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT
);
2900 auto displayInside
= StyleDisplay()->DisplayInside();
2901 // If this frame is for a scrollable element, then it will actually have
2902 // "display:block", and its *parent frame* will have the real
2903 // flex-flavored display value. So in that case, check the parent frame to
2904 // find out if we're legacy.
2906 // TODO(emilio): Maybe ::-moz-scrolled-content and co should inherit `display`
2907 // (or a blockified version thereof, to not hit bug 456484).
2908 if (displayInside
== StyleDisplayInside::Flow
) {
2909 MOZ_ASSERT(StyleDisplay()->mDisplay
== StyleDisplay::Block
);
2910 MOZ_ASSERT(Style()->GetPseudoType() == PseudoStyleType::buttonContent
||
2911 Style()->GetPseudoType() == PseudoStyleType::scrolledContent
,
2912 "The only way a nsFlexContainerFrame can have 'display:block' "
2913 "should be if it's the inner part of a scrollable or button "
2915 displayInside
= GetParent()->StyleDisplay()->DisplayInside();
2918 // Figure out if we should set a frame state bit to indicate that this frame
2919 // represents a legacy -moz-{inline-}box or -webkit-{inline-}box container.
2920 if (displayInside
== StyleDisplayInside::WebkitBox
) {
2921 AddStateBits(NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX
);
2925 #ifdef DEBUG_FRAME_DUMP
2926 nsresult
nsFlexContainerFrame::GetFrameName(nsAString
& aResult
) const {
2927 return MakeFrameName(u
"FlexContainer"_ns
, aResult
);
2931 void nsFlexContainerFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
2932 const nsDisplayListSet
& aLists
) {
2933 nsDisplayListCollection
tempLists(aBuilder
);
2935 DisplayBorderBackgroundOutline(aBuilder
, tempLists
);
2936 if (GetPrevInFlow()) {
2937 DisplayOverflowContainers(aBuilder
, tempLists
);
2940 // Our children are all block-level, so their borders/backgrounds all go on
2941 // the BlockBorderBackgrounds list.
2942 nsDisplayListSet
childLists(tempLists
, tempLists
.BlockBorderBackgrounds());
2944 CSSOrderAwareFrameIterator
iter(
2945 this, FrameChildListID::Principal
,
2946 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll
,
2947 OrderStateForIter(this), OrderingPropertyForIter(this));
2949 const auto flags
= DisplayFlagsForFlexOrGridItem();
2950 for (; !iter
.AtEnd(); iter
.Next()) {
2951 nsIFrame
* childFrame
= *iter
;
2952 BuildDisplayListForChild(aBuilder
, childFrame
, childLists
, flags
);
2955 tempLists
.MoveTo(aLists
);
2958 void FlexLine::FreezeItemsEarly(bool aIsUsingFlexGrow
,
2959 ComputedFlexLineInfo
* aLineInfo
) {
2960 // After we've established the type of flexing we're doing (growing vs.
2961 // shrinking), and before we try to flex any items, we freeze items that
2962 // obviously *can't* flex.
2964 // Quoting the spec:
2965 // # Freeze, setting its target main size to its hypothetical main size...
2966 // # - any item that has a flex factor of zero
2967 // # - if using the flex grow factor: any item that has a flex base size
2968 // # greater than its hypothetical main size
2969 // # - if using the flex shrink factor: any item that has a flex base size
2970 // # smaller than its hypothetical main size
2971 // https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths
2973 // (NOTE: At this point, item->MainSize() *is* the item's hypothetical
2974 // main size, since SetFlexBaseSizeAndMainSize() sets it up that way, and the
2975 // item hasn't had a chance to flex away from that yet.)
2977 // Since this loop only operates on unfrozen flex items, we can break as
2978 // soon as we have seen all of them.
2979 uint32_t numUnfrozenItemsToBeSeen
= NumItems() - mNumFrozenItems
;
2980 for (FlexItem
& item
: Items()) {
2981 if (numUnfrozenItemsToBeSeen
== 0) {
2985 if (!item
.IsFrozen()) {
2986 numUnfrozenItemsToBeSeen
--;
2987 bool shouldFreeze
= (0.0f
== item
.GetFlexFactor(aIsUsingFlexGrow
));
2988 if (!shouldFreeze
) {
2989 if (aIsUsingFlexGrow
) {
2990 if (item
.FlexBaseSize() > item
.MainSize()) {
2991 shouldFreeze
= true;
2993 } else { // using flex-shrink
2994 if (item
.FlexBaseSize() < item
.MainSize()) {
2995 shouldFreeze
= true;
3000 // Freeze item! (at its hypothetical main size)
3002 if (item
.FlexBaseSize() < item
.MainSize()) {
3003 item
.SetWasMinClamped();
3004 } else if (item
.FlexBaseSize() > item
.MainSize()) {
3005 item
.SetWasMaxClamped();
3012 MOZ_ASSERT(numUnfrozenItemsToBeSeen
== 0, "miscounted frozen items?");
3015 // Based on the sign of aTotalViolation, this function freezes a subset of our
3016 // flexible sizes, and restores the remaining ones to their initial pref sizes.
3017 void FlexLine::FreezeOrRestoreEachFlexibleSize(const nscoord aTotalViolation
,
3018 bool aIsFinalIteration
) {
3021 eFreezeMinViolations
,
3022 eFreezeMaxViolations
3025 FreezeType freezeType
;
3026 if (aTotalViolation
== 0) {
3027 freezeType
= eFreezeEverything
;
3028 } else if (aTotalViolation
> 0) {
3029 freezeType
= eFreezeMinViolations
;
3030 } else { // aTotalViolation < 0
3031 freezeType
= eFreezeMaxViolations
;
3034 // Since this loop only operates on unfrozen flex items, we can break as
3035 // soon as we have seen all of them.
3036 uint32_t numUnfrozenItemsToBeSeen
= NumItems() - mNumFrozenItems
;
3037 for (FlexItem
& item
: Items()) {
3038 if (numUnfrozenItemsToBeSeen
== 0) {
3042 if (!item
.IsFrozen()) {
3043 numUnfrozenItemsToBeSeen
--;
3045 MOZ_ASSERT(!item
.HadMinViolation() || !item
.HadMaxViolation(),
3046 "Can have either min or max violation, but not both");
3048 bool hadMinViolation
= item
.HadMinViolation();
3049 bool hadMaxViolation
= item
.HadMaxViolation();
3050 if (eFreezeEverything
== freezeType
||
3051 (eFreezeMinViolations
== freezeType
&& hadMinViolation
) ||
3052 (eFreezeMaxViolations
== freezeType
&& hadMaxViolation
)) {
3053 MOZ_ASSERT(item
.MainSize() >= item
.MainMinSize(),
3054 "Freezing item at a size below its minimum");
3055 MOZ_ASSERT(item
.MainSize() <= item
.MainMaxSize(),
3056 "Freezing item at a size above its maximum");
3059 if (hadMinViolation
) {
3060 item
.SetWasMinClamped();
3061 } else if (hadMaxViolation
) {
3062 item
.SetWasMaxClamped();
3065 } else if (MOZ_UNLIKELY(aIsFinalIteration
)) {
3066 // XXXdholbert If & when bug 765861 is fixed, we should upgrade this
3067 // assertion to be fatal except in documents with enormous lengths.
3069 "Final iteration still has unfrozen items, this shouldn't"
3070 " happen unless there was nscoord under/overflow.");
3073 } // else, we'll reset this item's main size to its flex base size on the
3074 // next iteration of this algorithm.
3076 if (!item
.IsFrozen()) {
3077 // Clear this item's violation(s), now that we've dealt with them
3078 item
.ClearViolationFlags();
3083 MOZ_ASSERT(numUnfrozenItemsToBeSeen
== 0, "miscounted frozen items?");
3086 void FlexLine::ResolveFlexibleLengths(nscoord aFlexContainerMainSize
,
3087 ComputedFlexLineInfo
* aLineInfo
) {
3088 // In this function, we use 64-bit coord type to avoid integer overflow in
3089 // case several of the individual items have huge hypothetical main sizes,
3090 // which can happen with percent-width table-layout:fixed descendants. Here we
3091 // promote the container's main size to 64-bit to make the arithmetic
3093 AuCoord64
flexContainerMainSize(aFlexContainerMainSize
);
3095 // Before we start resolving sizes: if we have an aLineInfo structure to fill
3096 // out, we inform it of each item's base size, and we initialize the "delta"
3097 // for each item to 0. (And if the flex algorithm wants to grow or shrink the
3098 // item, we'll update this delta further down.)
3100 uint32_t itemIndex
= 0;
3101 for (FlexItem
& item
: Items()) {
3102 aLineInfo
->mItems
[itemIndex
].mMainBaseSize
= item
.FlexBaseSize();
3103 aLineInfo
->mItems
[itemIndex
].mMainDeltaSize
= 0;
3108 // Determine whether we're going to be growing or shrinking items.
3109 const bool isUsingFlexGrow
=
3110 (mTotalOuterHypotheticalMainSize
< flexContainerMainSize
);
3113 aLineInfo
->mGrowthState
=
3114 isUsingFlexGrow
? mozilla::dom::FlexLineGrowthState::Growing
3115 : mozilla::dom::FlexLineGrowthState::Shrinking
;
3118 // Do an "early freeze" for flex items that obviously can't flex in the
3119 // direction we've chosen:
3120 FreezeItemsEarly(isUsingFlexGrow
, aLineInfo
);
3122 if ((mNumFrozenItems
== NumItems()) && !aLineInfo
) {
3123 // All our items are frozen, so we have no flexible lengths to resolve,
3124 // and we aren't being asked to generate computed line info.
3125 FLEX_LOG("No flexible length to resolve");
3128 MOZ_ASSERT(!IsEmpty() || aLineInfo
,
3129 "empty lines should take the early-return above");
3131 FLEX_LOG("Resolving flexible lengths for items");
3133 // Subtract space occupied by our items' margins/borders/padding/gaps, so
3134 // we can just be dealing with the space available for our flex items' content
3136 const AuCoord64 totalItemMBPAndGaps
= mTotalItemMBP
+ SumOfGaps();
3137 const AuCoord64 spaceAvailableForFlexItemsContentBoxes
=
3138 flexContainerMainSize
- totalItemMBPAndGaps
;
3140 Maybe
<AuCoord64
> origAvailableFreeSpace
;
3142 // NOTE: I claim that this chunk of the algorithm (the looping part) needs to
3143 // run the loop at MOST NumItems() times. This claim should hold up
3144 // because we'll freeze at least one item on each loop iteration, and once
3145 // we've run out of items to freeze, there's nothing left to do. However,
3146 // in most cases, we'll break out of this loop long before we hit that many
3148 for (uint32_t iterationCounter
= 0; iterationCounter
< NumItems();
3149 iterationCounter
++) {
3150 // Set every not-yet-frozen item's used main size to its
3151 // flex base size, and subtract all the used main sizes from our
3152 // total amount of space to determine the 'available free space'
3153 // (positive or negative) to be distributed among our flexible items.
3154 AuCoord64 availableFreeSpace
= spaceAvailableForFlexItemsContentBoxes
;
3155 for (FlexItem
& item
: Items()) {
3156 if (!item
.IsFrozen()) {
3157 item
.SetMainSize(item
.FlexBaseSize());
3159 availableFreeSpace
-= item
.MainSize();
3162 FLEX_LOGV("Available free space: %" PRId64
"; flex items should \"%s\"",
3163 availableFreeSpace
.value
, isUsingFlexGrow
? "grow" : "shrink");
3165 // The sign of our free space should agree with the type of flexing
3166 // (grow/shrink) that we're doing. Any disagreement should've made us use
3167 // the other type of flexing, or should've been resolved in
3168 // FreezeItemsEarly.
3170 // Note: it's possible that an individual flex item has huge
3171 // margin/border/padding that makes either its
3172 // MarginBorderPaddingSizeInMainAxis() or OuterMainSize() negative due to
3173 // integer overflow. If that happens, the accumulated
3174 // mTotalOuterHypotheticalMainSize or mTotalItemMBP could be negative due to
3175 // that one item's negative (overflowed) size. Likewise, a huge main gap
3176 // size between flex items can also make our accumulated SumOfGaps()
3177 // negative. In these case, we throw up our hands and don't require
3178 // isUsingFlexGrow to agree with availableFreeSpace. Luckily, we won't get
3179 // stuck in the algorithm below, and just distribute the wrong
3180 // availableFreeSpace with the wrong grow/shrink factors.
3181 MOZ_ASSERT(!(mTotalOuterHypotheticalMainSize
>= 0 && mTotalItemMBP
>= 0 &&
3182 totalItemMBPAndGaps
>= 0) ||
3183 (isUsingFlexGrow
&& availableFreeSpace
>= 0) ||
3184 (!isUsingFlexGrow
&& availableFreeSpace
<= 0),
3185 "availableFreeSpace's sign should match isUsingFlexGrow");
3187 // If we have any free space available, give each flexible item a portion
3188 // of availableFreeSpace.
3189 if (availableFreeSpace
!= AuCoord64(0)) {
3190 // The first time we do this, we initialize origAvailableFreeSpace.
3191 if (!origAvailableFreeSpace
) {
3192 origAvailableFreeSpace
.emplace(availableFreeSpace
);
3195 // STRATEGY: On each item, we compute & store its "share" of the total
3196 // weight that we've seen so far:
3197 // curWeight / weightSum
3199 // Then, when we go to actually distribute the space (in the next loop),
3200 // we can simply walk backwards through the elements and give each item
3201 // its "share" multiplied by the remaining available space.
3203 // SPECIAL CASE: If the sum of the weights is larger than the
3204 // maximum representable double (overflowing to infinity), then we can't
3205 // sensibly divide out proportional shares anymore. In that case, we
3206 // simply treat the flex item(s) with the largest weights as if
3207 // their weights were infinite (dwarfing all the others), and we
3208 // distribute all of the available space among them.
3209 double weightSum
= 0.0;
3210 double flexFactorSum
= 0.0;
3211 double largestWeight
= 0.0;
3212 uint32_t numItemsWithLargestWeight
= 0;
3214 // Since this loop only operates on unfrozen flex items, we can break as
3215 // soon as we have seen all of them.
3216 uint32_t numUnfrozenItemsToBeSeen
= NumItems() - mNumFrozenItems
;
3217 for (FlexItem
& item
: Items()) {
3218 if (numUnfrozenItemsToBeSeen
== 0) {
3222 if (!item
.IsFrozen()) {
3223 numUnfrozenItemsToBeSeen
--;
3225 const double curWeight
= item
.GetWeight(isUsingFlexGrow
);
3226 const double curFlexFactor
= item
.GetFlexFactor(isUsingFlexGrow
);
3227 MOZ_ASSERT(curWeight
>= 0.0, "weights are non-negative");
3228 MOZ_ASSERT(curFlexFactor
>= 0.0, "flex factors are non-negative");
3230 weightSum
+= curWeight
;
3231 flexFactorSum
+= curFlexFactor
;
3233 if (std::isfinite(weightSum
)) {
3234 if (curWeight
== 0.0) {
3235 item
.SetShareOfWeightSoFar(0.0);
3237 item
.SetShareOfWeightSoFar(curWeight
/ weightSum
);
3239 } // else, the sum of weights overflows to infinity, in which
3240 // case we don't bother with "SetShareOfWeightSoFar" since
3241 // we know we won't use it. (instead, we'll just give every
3242 // item with the largest weight an equal share of space.)
3244 // Update our largest-weight tracking vars
3245 if (curWeight
> largestWeight
) {
3246 largestWeight
= curWeight
;
3247 numItemsWithLargestWeight
= 1;
3248 } else if (curWeight
== largestWeight
) {
3249 numItemsWithLargestWeight
++;
3254 MOZ_ASSERT(numUnfrozenItemsToBeSeen
== 0, "miscounted frozen items?");
3256 if (weightSum
!= 0.0) {
3257 MOZ_ASSERT(flexFactorSum
!= 0.0,
3258 "flex factor sum can't be 0, if a weighted sum "
3259 "of its components (weightSum) is nonzero");
3260 if (flexFactorSum
< 1.0) {
3261 // Our unfrozen flex items don't want all of the original free space!
3262 // (Their flex factors add up to something less than 1.)
3263 // Hence, make sure we don't distribute any more than the portion of
3264 // our original free space that these items actually want.
3265 auto totalDesiredPortionOfOrigFreeSpace
=
3266 AuCoord64::FromRound(*origAvailableFreeSpace
* flexFactorSum
);
3268 // Clamp availableFreeSpace to be no larger than that ^^.
3269 // (using min or max, depending on sign).
3270 // This should not change the sign of availableFreeSpace (except
3271 // possibly by setting it to 0), as enforced by this assertion:
3272 NS_ASSERTION(totalDesiredPortionOfOrigFreeSpace
== AuCoord64(0) ||
3273 ((totalDesiredPortionOfOrigFreeSpace
> 0) ==
3274 (availableFreeSpace
> 0)),
3275 "When we reduce available free space for flex "
3276 "factors < 1, we shouldn't change the sign of the "
3279 if (availableFreeSpace
> 0) {
3280 availableFreeSpace
= std::min(availableFreeSpace
,
3281 totalDesiredPortionOfOrigFreeSpace
);
3283 availableFreeSpace
= std::max(availableFreeSpace
,
3284 totalDesiredPortionOfOrigFreeSpace
);
3288 FLEX_LOGV("Distributing available space:");
3289 // Since this loop only operates on unfrozen flex items, we can break as
3290 // soon as we have seen all of them.
3291 numUnfrozenItemsToBeSeen
= NumItems() - mNumFrozenItems
;
3293 // NOTE: It's important that we traverse our items in *reverse* order
3294 // here, for correct width distribution according to the items'
3295 // "ShareOfWeightSoFar" progressively-calculated values.
3296 for (FlexItem
& item
: Reversed(Items())) {
3297 if (numUnfrozenItemsToBeSeen
== 0) {
3301 if (!item
.IsFrozen()) {
3302 numUnfrozenItemsToBeSeen
--;
3304 // To avoid rounding issues, we compute the change in size for this
3305 // item, and then subtract it from the remaining available space.
3306 AuCoord64 sizeDelta
= 0;
3307 if (std::isfinite(weightSum
)) {
3308 double myShareOfRemainingSpace
= item
.ShareOfWeightSoFar();
3310 MOZ_ASSERT(myShareOfRemainingSpace
>= 0.0 &&
3311 myShareOfRemainingSpace
<= 1.0,
3312 "my share should be nonnegative fractional amount");
3314 if (myShareOfRemainingSpace
== 1.0) {
3315 // (We special-case 1.0 to avoid float error from converting
3316 // availableFreeSpace from integer*1.0 --> double --> integer)
3317 sizeDelta
= availableFreeSpace
;
3318 } else if (myShareOfRemainingSpace
> 0.0) {
3319 sizeDelta
= AuCoord64::FromRound(availableFreeSpace
*
3320 myShareOfRemainingSpace
);
3322 } else if (item
.GetWeight(isUsingFlexGrow
) == largestWeight
) {
3323 // Total flexibility is infinite, so we're just distributing
3324 // the available space equally among the items that are tied for
3325 // having the largest weight (and this is one of those items).
3326 sizeDelta
= AuCoord64::FromRound(
3327 availableFreeSpace
/ double(numItemsWithLargestWeight
));
3328 numItemsWithLargestWeight
--;
3331 availableFreeSpace
-= sizeDelta
;
3333 item
.SetMainSize(item
.MainSize() +
3334 nscoord(sizeDelta
.ToMinMaxClamped()));
3335 FLEX_LOGV(" Flex item %p receives %" PRId64
", for a total of %d",
3336 item
.Frame(), sizeDelta
.value
, item
.MainSize());
3340 MOZ_ASSERT(numUnfrozenItemsToBeSeen
== 0, "miscounted frozen items?");
3342 // If we have an aLineInfo structure to fill out, capture any
3343 // size changes that may have occurred in the previous loop.
3344 // We don't do this inside the previous loop, because we don't
3345 // want to burden layout when aLineInfo is null.
3347 uint32_t itemIndex
= 0;
3348 for (FlexItem
& item
: Items()) {
3349 if (!item
.IsFrozen()) {
3350 // Calculate a deltaSize that represents how much the flex sizing
3351 // algorithm "wants" to stretch or shrink this item during this
3352 // pass through the algorithm. Later passes through the algorithm
3353 // may overwrite this, until this item is frozen. Note that this
3354 // value may not reflect how much the size of the item is
3355 // actually changed, since the size of the item will be clamped
3356 // to min and max values later in this pass. That's intentional,
3357 // since we want to report the value that the sizing algorithm
3358 // tried to stretch or shrink the item.
3360 item
.MainSize() - aLineInfo
->mItems
[itemIndex
].mMainBaseSize
;
3362 aLineInfo
->mItems
[itemIndex
].mMainDeltaSize
= deltaSize
;
3370 // Fix min/max violations:
3371 nscoord totalViolation
= 0; // keeps track of adjustments for min/max
3372 FLEX_LOGV("Checking for violations:");
3374 // Since this loop only operates on unfrozen flex items, we can break as
3375 // soon as we have seen all of them.
3376 uint32_t numUnfrozenItemsToBeSeen
= NumItems() - mNumFrozenItems
;
3377 for (FlexItem
& item
: Items()) {
3378 if (numUnfrozenItemsToBeSeen
== 0) {
3382 if (!item
.IsFrozen()) {
3383 numUnfrozenItemsToBeSeen
--;
3385 if (item
.MainSize() < item
.MainMinSize()) {
3387 totalViolation
+= item
.MainMinSize() - item
.MainSize();
3388 item
.SetMainSize(item
.MainMinSize());
3389 item
.SetHadMinViolation();
3390 } else if (item
.MainSize() > item
.MainMaxSize()) {
3392 totalViolation
+= item
.MainMaxSize() - item
.MainSize();
3393 item
.SetMainSize(item
.MainMaxSize());
3394 item
.SetHadMaxViolation();
3399 MOZ_ASSERT(numUnfrozenItemsToBeSeen
== 0, "miscounted frozen items?");
3401 FreezeOrRestoreEachFlexibleSize(totalViolation
,
3402 iterationCounter
+ 1 == NumItems());
3404 FLEX_LOGV("Total violation: %d", totalViolation
);
3406 if (mNumFrozenItems
== NumItems()) {
3410 MOZ_ASSERT(totalViolation
!= 0,
3411 "Zero violation should've made us freeze all items & break");
3415 // Post-condition: all items should've been frozen.
3416 // Make sure the counts match:
3417 MOZ_ASSERT(mNumFrozenItems
== NumItems(), "All items should be frozen");
3419 // For good measure, check each item directly, in case our counts are busted:
3420 for (const FlexItem
& item
: Items()) {
3421 MOZ_ASSERT(item
.IsFrozen(), "All items should be frozen");
3426 MainAxisPositionTracker::MainAxisPositionTracker(
3427 const FlexboxAxisTracker
& aAxisTracker
, const FlexLine
* aLine
,
3428 const StyleContentDistribution
& aJustifyContent
,
3429 nscoord aContentBoxMainSize
)
3430 : PositionTracker(aAxisTracker
.GetWritingMode(), aAxisTracker
.MainAxis(),
3431 aAxisTracker
.IsMainAxisReversed()),
3432 // we chip away at this below
3433 mPackingSpaceRemaining(aContentBoxMainSize
),
3434 mJustifyContent(aJustifyContent
) {
3435 // Extract the flag portion of mJustifyContent and strip off the flag bits
3436 // NOTE: This must happen before any assignment to mJustifyContent to
3437 // avoid overwriting the flag bits.
3438 StyleAlignFlags justifyContentFlags
=
3439 mJustifyContent
.primary
& StyleAlignFlags::FLAG_BITS
;
3440 mJustifyContent
.primary
&= ~StyleAlignFlags::FLAG_BITS
;
3442 // 'normal' behaves as 'stretch', and 'stretch' behaves as 'flex-start',
3444 // https://drafts.csswg.org/css-align-3/#propdef-justify-content
3445 if (mJustifyContent
.primary
== StyleAlignFlags::NORMAL
||
3446 mJustifyContent
.primary
== StyleAlignFlags::STRETCH
) {
3447 mJustifyContent
.primary
= StyleAlignFlags::FLEX_START
;
3450 // mPackingSpaceRemaining is initialized to the container's main size. Now
3451 // we'll subtract out the main sizes of our flex items, so that it ends up
3452 // with the *actual* amount of packing space.
3453 for (const FlexItem
& item
: aLine
->Items()) {
3454 mPackingSpaceRemaining
-= item
.OuterMainSize();
3455 mNumAutoMarginsInMainAxis
+= item
.NumAutoMarginsInMainAxis();
3458 // Subtract space required for row/col gap from the remaining packing space
3459 mPackingSpaceRemaining
-= aLine
->SumOfGaps();
3461 if (mPackingSpaceRemaining
<= 0) {
3462 // No available packing space to use for resolving auto margins.
3463 mNumAutoMarginsInMainAxis
= 0;
3464 // If packing space is negative and <overflow-position> is set to 'safe'
3465 // all justify options fall back to 'start'
3466 if (justifyContentFlags
& StyleAlignFlags::SAFE
) {
3467 mJustifyContent
.primary
= StyleAlignFlags::START
;
3471 // If packing space is negative or we only have one item, 'space-between'
3472 // falls back to 'flex-start', and 'space-around' & 'space-evenly' fall back
3473 // to 'center'. In those cases, it's simplest to just pretend we have a
3474 // different 'justify-content' value and share code.
3475 if (mPackingSpaceRemaining
< 0 || aLine
->NumItems() == 1) {
3476 if (mJustifyContent
.primary
== StyleAlignFlags::SPACE_BETWEEN
) {
3477 mJustifyContent
.primary
= StyleAlignFlags::FLEX_START
;
3478 } else if (mJustifyContent
.primary
== StyleAlignFlags::SPACE_AROUND
||
3479 mJustifyContent
.primary
== StyleAlignFlags::SPACE_EVENLY
) {
3480 mJustifyContent
.primary
= StyleAlignFlags::CENTER
;
3484 // Map 'left'/'right' to 'start'/'end'
3485 if (mJustifyContent
.primary
== StyleAlignFlags::LEFT
||
3486 mJustifyContent
.primary
== StyleAlignFlags::RIGHT
) {
3487 mJustifyContent
.primary
=
3488 aAxisTracker
.ResolveJustifyLeftRight(mJustifyContent
.primary
);
3491 // Map 'start'/'end' to 'flex-start'/'flex-end'.
3492 if (mJustifyContent
.primary
== StyleAlignFlags::START
) {
3493 mJustifyContent
.primary
= aAxisTracker
.IsMainAxisReversed()
3494 ? StyleAlignFlags::FLEX_END
3495 : StyleAlignFlags::FLEX_START
;
3496 } else if (mJustifyContent
.primary
== StyleAlignFlags::END
) {
3497 mJustifyContent
.primary
= aAxisTracker
.IsMainAxisReversed()
3498 ? StyleAlignFlags::FLEX_START
3499 : StyleAlignFlags::FLEX_END
;
3502 // Figure out how much space we'll set aside for auto margins or
3503 // packing spaces, and advance past any leading packing-space.
3504 if (mNumAutoMarginsInMainAxis
== 0 && mPackingSpaceRemaining
!= 0 &&
3505 !aLine
->IsEmpty()) {
3506 if (mJustifyContent
.primary
== StyleAlignFlags::FLEX_START
) {
3507 // All packing space should go at the end --> nothing to do here.
3508 } else if (mJustifyContent
.primary
== StyleAlignFlags::FLEX_END
) {
3509 // All packing space goes at the beginning
3510 mPosition
+= mPackingSpaceRemaining
;
3511 } else if (mJustifyContent
.primary
== StyleAlignFlags::CENTER
) {
3512 // Half the packing space goes at the beginning
3513 mPosition
+= mPackingSpaceRemaining
/ 2;
3514 } else if (mJustifyContent
.primary
== StyleAlignFlags::SPACE_BETWEEN
||
3515 mJustifyContent
.primary
== StyleAlignFlags::SPACE_AROUND
||
3516 mJustifyContent
.primary
== StyleAlignFlags::SPACE_EVENLY
) {
3517 nsFlexContainerFrame::CalculatePackingSpace(
3518 aLine
->NumItems(), mJustifyContent
, &mPosition
,
3519 &mNumPackingSpacesRemaining
, &mPackingSpaceRemaining
);
3521 MOZ_ASSERT_UNREACHABLE("Unexpected justify-content value");
3525 MOZ_ASSERT(mNumPackingSpacesRemaining
== 0 || mNumAutoMarginsInMainAxis
== 0,
3526 "extra space should either go to packing space or to "
3527 "auto margins, but not to both");
3530 void MainAxisPositionTracker::ResolveAutoMarginsInMainAxis(FlexItem
& aItem
) {
3531 if (mNumAutoMarginsInMainAxis
) {
3532 const auto& styleMargin
= aItem
.Frame()->StyleMargin()->mMargin
;
3533 for (const auto side
: {StartSide(), EndSide()}) {
3534 if (styleMargin
.Get(side
, mWM
).IsAuto()) {
3535 // NOTE: This integer math will skew the distribution of remainder
3536 // app-units towards the end, which is fine.
3537 nscoord curAutoMarginSize
=
3538 mPackingSpaceRemaining
/ mNumAutoMarginsInMainAxis
;
3540 MOZ_ASSERT(aItem
.GetMarginComponentForSide(side
) == 0,
3541 "Expecting auto margins to have value '0' before we "
3543 aItem
.SetMarginComponentForSide(side
, curAutoMarginSize
);
3545 mNumAutoMarginsInMainAxis
--;
3546 mPackingSpaceRemaining
-= curAutoMarginSize
;
3552 void MainAxisPositionTracker::TraversePackingSpace() {
3553 if (mNumPackingSpacesRemaining
) {
3554 MOZ_ASSERT(mJustifyContent
.primary
== StyleAlignFlags::SPACE_BETWEEN
||
3555 mJustifyContent
.primary
== StyleAlignFlags::SPACE_AROUND
||
3556 mJustifyContent
.primary
== StyleAlignFlags::SPACE_EVENLY
,
3557 "mNumPackingSpacesRemaining only applies for "
3558 "space-between/space-around/space-evenly");
3560 MOZ_ASSERT(mPackingSpaceRemaining
>= 0,
3561 "ran out of packing space earlier than we expected");
3563 // NOTE: This integer math will skew the distribution of remainder
3564 // app-units towards the end, which is fine.
3565 nscoord curPackingSpace
=
3566 mPackingSpaceRemaining
/ mNumPackingSpacesRemaining
;
3568 mPosition
+= curPackingSpace
;
3569 mNumPackingSpacesRemaining
--;
3570 mPackingSpaceRemaining
-= curPackingSpace
;
3574 CrossAxisPositionTracker::CrossAxisPositionTracker(
3575 nsTArray
<FlexLine
>& aLines
, const ReflowInput
& aReflowInput
,
3576 nscoord aContentBoxCrossSize
, bool aIsCrossSizeDefinite
,
3577 const FlexboxAxisTracker
& aAxisTracker
, const nscoord aCrossGapSize
)
3578 : PositionTracker(aAxisTracker
.GetWritingMode(), aAxisTracker
.CrossAxis(),
3579 aAxisTracker
.IsCrossAxisReversed()),
3580 mAlignContent(aReflowInput
.mStylePosition
->mAlignContent
),
3581 mCrossGapSize(aCrossGapSize
) {
3582 // Extract and strip the flag bits from alignContent
3583 StyleAlignFlags alignContentFlags
=
3584 mAlignContent
.primary
& StyleAlignFlags::FLAG_BITS
;
3585 mAlignContent
.primary
&= ~StyleAlignFlags::FLAG_BITS
;
3587 // 'normal' behaves as 'stretch'
3588 if (mAlignContent
.primary
== StyleAlignFlags::NORMAL
) {
3589 mAlignContent
.primary
= StyleAlignFlags::STRETCH
;
3592 const bool isSingleLine
=
3593 StyleFlexWrap::Nowrap
== aReflowInput
.mStylePosition
->mFlexWrap
;
3595 MOZ_ASSERT(aLines
.Length() == 1,
3596 "If we're styled as single-line, we should only have 1 line");
3597 // "If the flex container is single-line and has a definite cross size, the
3598 // cross size of the flex line is the flex container's inner cross size."
3600 // SOURCE: https://drafts.csswg.org/css-flexbox/#algo-cross-line
3601 // NOTE: This means (by definition) that there's no packing space, which
3602 // means we don't need to be concerned with "align-content" at all and we
3603 // can return early. This is handy, because this is the usual case (for
3604 // single-line flexbox).
3605 if (aIsCrossSizeDefinite
) {
3606 aLines
[0].SetLineCrossSize(aContentBoxCrossSize
);
3610 // "If the flex container is single-line, then clamp the line's
3611 // cross-size to be within the container's computed min and max cross-size
3613 aLines
[0].SetLineCrossSize(
3614 aReflowInput
.ApplyMinMaxBSize(aLines
[0].LineCrossSize()));
3617 // NOTE: The rest of this function should essentially match
3618 // MainAxisPositionTracker's constructor, though with FlexLines instead of
3619 // FlexItems, and with the additional value "stretch" (and of course with
3620 // cross sizes instead of main sizes.)
3622 // Figure out how much packing space we have (container's cross size minus
3623 // all the lines' cross sizes). Also, share this loop to count how many
3624 // lines we have. (We need that count in some cases below.)
3625 mPackingSpaceRemaining
= aContentBoxCrossSize
;
3626 uint32_t numLines
= 0;
3627 for (FlexLine
& line
: aLines
) {
3628 mPackingSpaceRemaining
-= line
.LineCrossSize();
3632 // Subtract space required for row/col gap from the remaining packing space
3633 MOZ_ASSERT(numLines
>= 1,
3634 "GenerateFlexLines should've produced at least 1 line");
3635 mPackingSpaceRemaining
-= aCrossGapSize
* (numLines
- 1);
3637 // If <overflow-position> is 'safe' and packing space is negative
3638 // all align options fall back to 'start'
3639 if ((alignContentFlags
& StyleAlignFlags::SAFE
) &&
3640 mPackingSpaceRemaining
< 0) {
3641 mAlignContent
.primary
= StyleAlignFlags::START
;
3644 // If packing space is negative, 'space-between' and 'stretch' behave like
3645 // 'flex-start', and 'space-around' and 'space-evenly' behave like 'center'.
3646 // In those cases, it's simplest to just pretend we have a different
3647 // 'align-content' value and share code. (If we only have one line, all of
3648 // the 'space-*' keywords fall back as well, but 'stretch' doesn't because
3649 // even a single line can still stretch.)
3650 if (mPackingSpaceRemaining
< 0 &&
3651 mAlignContent
.primary
== StyleAlignFlags::STRETCH
) {
3652 mAlignContent
.primary
= StyleAlignFlags::FLEX_START
;
3653 } else if (mPackingSpaceRemaining
< 0 || numLines
== 1) {
3654 if (mAlignContent
.primary
== StyleAlignFlags::SPACE_BETWEEN
) {
3655 mAlignContent
.primary
= StyleAlignFlags::FLEX_START
;
3656 } else if (mAlignContent
.primary
== StyleAlignFlags::SPACE_AROUND
||
3657 mAlignContent
.primary
== StyleAlignFlags::SPACE_EVENLY
) {
3658 mAlignContent
.primary
= StyleAlignFlags::CENTER
;
3662 // Map 'start'/'end' to 'flex-start'/'flex-end'.
3663 if (mAlignContent
.primary
== StyleAlignFlags::START
) {
3664 mAlignContent
.primary
= aAxisTracker
.IsCrossAxisReversed()
3665 ? StyleAlignFlags::FLEX_END
3666 : StyleAlignFlags::FLEX_START
;
3667 } else if (mAlignContent
.primary
== StyleAlignFlags::END
) {
3668 mAlignContent
.primary
= aAxisTracker
.IsCrossAxisReversed()
3669 ? StyleAlignFlags::FLEX_START
3670 : StyleAlignFlags::FLEX_END
;
3673 // Figure out how much space we'll set aside for packing spaces, and advance
3674 // past any leading packing-space.
3675 if (mPackingSpaceRemaining
!= 0) {
3676 if (mAlignContent
.primary
== StyleAlignFlags::BASELINE
||
3677 mAlignContent
.primary
== StyleAlignFlags::LAST_BASELINE
) {
3678 // TODO: Bug 1480850 will implement 'align-content: [first/last] baseline'
3679 // for flexbox. Until then, behaves as if align-content is 'flex-start' by
3681 } else if (mAlignContent
.primary
== StyleAlignFlags::FLEX_START
) {
3682 // All packing space should go at the end --> nothing to do here.
3683 } else if (mAlignContent
.primary
== StyleAlignFlags::FLEX_END
) {
3684 // All packing space goes at the beginning
3685 mPosition
+= mPackingSpaceRemaining
;
3686 } else if (mAlignContent
.primary
== StyleAlignFlags::CENTER
) {
3687 // Half the packing space goes at the beginning
3688 mPosition
+= mPackingSpaceRemaining
/ 2;
3689 } else if (mAlignContent
.primary
== StyleAlignFlags::SPACE_BETWEEN
||
3690 mAlignContent
.primary
== StyleAlignFlags::SPACE_AROUND
||
3691 mAlignContent
.primary
== StyleAlignFlags::SPACE_EVENLY
) {
3692 nsFlexContainerFrame::CalculatePackingSpace(
3693 numLines
, mAlignContent
, &mPosition
, &mNumPackingSpacesRemaining
,
3694 &mPackingSpaceRemaining
);
3695 } else if (mAlignContent
.primary
== StyleAlignFlags::STRETCH
) {
3696 // Split space equally between the lines:
3697 MOZ_ASSERT(mPackingSpaceRemaining
> 0,
3698 "negative packing space should make us use 'flex-start' "
3699 "instead of 'stretch' (and we shouldn't bother with this "
3700 "code if we have 0 packing space)");
3702 uint32_t numLinesLeft
= numLines
;
3703 for (FlexLine
& line
: aLines
) {
3704 // Our share is the amount of space remaining, divided by the number
3705 // of lines remainig.
3706 MOZ_ASSERT(numLinesLeft
> 0, "miscalculated num lines");
3707 nscoord shareOfExtraSpace
= mPackingSpaceRemaining
/ numLinesLeft
;
3708 nscoord newSize
= line
.LineCrossSize() + shareOfExtraSpace
;
3709 line
.SetLineCrossSize(newSize
);
3711 mPackingSpaceRemaining
-= shareOfExtraSpace
;
3714 MOZ_ASSERT(numLinesLeft
== 0, "miscalculated num lines");
3716 MOZ_ASSERT_UNREACHABLE("Unexpected align-content value");
3721 void CrossAxisPositionTracker::TraversePackingSpace() {
3722 if (mNumPackingSpacesRemaining
) {
3723 MOZ_ASSERT(mAlignContent
.primary
== StyleAlignFlags::SPACE_BETWEEN
||
3724 mAlignContent
.primary
== StyleAlignFlags::SPACE_AROUND
||
3725 mAlignContent
.primary
== StyleAlignFlags::SPACE_EVENLY
,
3726 "mNumPackingSpacesRemaining only applies for "
3727 "space-between/space-around/space-evenly");
3729 MOZ_ASSERT(mPackingSpaceRemaining
>= 0,
3730 "ran out of packing space earlier than we expected");
3732 // NOTE: This integer math will skew the distribution of remainder
3733 // app-units towards the end, which is fine.
3734 nscoord curPackingSpace
=
3735 mPackingSpaceRemaining
/ mNumPackingSpacesRemaining
;
3737 mPosition
+= curPackingSpace
;
3738 mNumPackingSpacesRemaining
--;
3739 mPackingSpaceRemaining
-= curPackingSpace
;
3743 SingleLineCrossAxisPositionTracker::SingleLineCrossAxisPositionTracker(
3744 const FlexboxAxisTracker
& aAxisTracker
)
3745 : PositionTracker(aAxisTracker
.GetWritingMode(), aAxisTracker
.CrossAxis(),
3746 aAxisTracker
.IsCrossAxisReversed()) {}
3748 void FlexLine::ComputeCrossSizeAndBaseline(
3749 const FlexboxAxisTracker
& aAxisTracker
) {
3750 // NOTE: in these "cross{Start,End}ToFurthest{First,Last}Baseline" variables,
3751 // the "first/last" term is referring to the flex *line's* baseline-sharing
3752 // groups, which may or may not match any flex *item's* exact align-self
3753 // value. See the code that sets FlexItem::mBaselineSharingGroup for more
3755 nscoord crossStartToFurthestFirstBaseline
= nscoord_MIN
;
3756 nscoord crossEndToFurthestFirstBaseline
= nscoord_MIN
;
3757 nscoord crossStartToFurthestLastBaseline
= nscoord_MIN
;
3758 nscoord crossEndToFurthestLastBaseline
= nscoord_MIN
;
3760 nscoord largestOuterCrossSize
= 0;
3761 for (const FlexItem
& item
: Items()) {
3762 nscoord curOuterCrossSize
= item
.OuterCrossSize();
3764 if ((item
.AlignSelf()._0
== StyleAlignFlags::BASELINE
||
3765 item
.AlignSelf()._0
== StyleAlignFlags::LAST_BASELINE
) &&
3766 item
.NumAutoMarginsInCrossAxis() == 0) {
3767 const bool usingItemFirstBaseline
=
3768 (item
.AlignSelf()._0
== StyleAlignFlags::BASELINE
);
3770 // Find distance from our item's cross-start and cross-end margin-box
3771 // edges to its baseline.
3773 // Here's a diagram of a flex-item that we might be doing this on.
3774 // "mmm" is the margin-box, "bbb" is the border-box. The bottom of
3775 // the text "BASE" is the baseline.
3777 // ---(cross-start)---
3779 // mmmmmmmmmmmm | |margin-start |
3781 // m bbbbbbbb m |curOuterCrossSize | |crossStartToBaseline
3782 // m b b m | |ascent |
3783 // m b BASE b m | _|_ _|_
3785 // m bbbbbbbb m | |crossEndToBaseline
3787 // mmmmmmmmmmmm _|_ _|_
3789 // ---(cross-end)---
3791 // We already have the curOuterCrossSize, margin-start, and the ascent.
3792 // * We can get crossStartToBaseline by adding margin-start + ascent.
3793 // * If we subtract that from the curOuterCrossSize, we get
3794 // crossEndToBaseline.
3796 nscoord crossStartToBaseline
= item
.BaselineOffsetFromOuterCrossEdge(
3797 aAxisTracker
.CrossAxisPhysicalStartSide(), usingItemFirstBaseline
);
3798 nscoord crossEndToBaseline
= curOuterCrossSize
- crossStartToBaseline
;
3800 // Now, update our "largest" values for these (across all the flex items
3801 // in this flex line), so we can use them in computing the line's cross
3803 if (item
.ItemBaselineSharingGroup() == BaselineSharingGroup::First
) {
3804 crossStartToFurthestFirstBaseline
=
3805 std::max(crossStartToFurthestFirstBaseline
, crossStartToBaseline
);
3806 crossEndToFurthestFirstBaseline
=
3807 std::max(crossEndToFurthestFirstBaseline
, crossEndToBaseline
);
3809 crossStartToFurthestLastBaseline
=
3810 std::max(crossStartToFurthestLastBaseline
, crossStartToBaseline
);
3811 crossEndToFurthestLastBaseline
=
3812 std::max(crossEndToFurthestLastBaseline
, crossEndToBaseline
);
3815 largestOuterCrossSize
=
3816 std::max(largestOuterCrossSize
, curOuterCrossSize
);
3820 // The line's baseline offset is the distance from the line's edge to the
3821 // furthest item-baseline. The item(s) with that baseline will be exactly
3822 // aligned with the line's edge.
3823 mFirstBaselineOffset
= crossStartToFurthestFirstBaseline
;
3824 mLastBaselineOffset
= crossEndToFurthestLastBaseline
;
3826 // The line's cross-size is the larger of:
3827 // (a) [largest cross-start-to-baseline + largest baseline-to-cross-end] of
3828 // all baseline-aligned items with no cross-axis auto margins...
3830 // (b) [largest cross-start-to-baseline + largest baseline-to-cross-end] of
3831 // all last baseline-aligned items with no cross-axis auto margins...
3833 // (c) largest cross-size of all other children.
3834 mLineCrossSize
= std::max(
3836 crossStartToFurthestFirstBaseline
+ crossEndToFurthestFirstBaseline
,
3837 crossStartToFurthestLastBaseline
+ crossEndToFurthestLastBaseline
),
3838 largestOuterCrossSize
);
3841 nscoord
FlexLine::ExtractBaselineOffset(
3842 BaselineSharingGroup aBaselineGroup
) const {
3843 auto LastBaselineOffsetFromStartEdge
= [this]() {
3844 // Convert the distance to be relative from the line's cross-start edge.
3845 const nscoord offset
= LastBaselineOffset();
3846 return offset
!= nscoord_MIN
? LineCrossSize() - offset
: offset
;
3849 auto PrimaryBaseline
= [=]() {
3850 return aBaselineGroup
== BaselineSharingGroup::First
3851 ? FirstBaselineOffset()
3852 : LastBaselineOffsetFromStartEdge();
3854 auto SecondaryBaseline
= [=]() {
3855 return aBaselineGroup
== BaselineSharingGroup::First
3856 ? LastBaselineOffsetFromStartEdge()
3857 : FirstBaselineOffset();
3860 const nscoord primaryBaseline
= PrimaryBaseline();
3861 if (primaryBaseline
!= nscoord_MIN
) {
3862 return primaryBaseline
;
3864 return SecondaryBaseline();
3867 void FlexItem::ResolveStretchedCrossSize(nscoord aLineCrossSize
) {
3868 // We stretch IFF we are align-self:stretch, have no auto margins in
3869 // cross axis, and have cross-axis size property == "auto". If any of those
3870 // conditions don't hold up, we won't stretch.
3871 if (mAlignSelf
._0
!= StyleAlignFlags::STRETCH
||
3872 NumAutoMarginsInCrossAxis() != 0 || !IsCrossSizeAuto()) {
3876 // If we've already been stretched, we can bail out early, too.
3877 // No need to redo the calculation.
3882 // Reserve space for margins & border & padding, and then use whatever
3883 // remains as our item's cross-size (clamped to its min/max range).
3884 nscoord stretchedSize
= aLineCrossSize
- MarginBorderPaddingSizeInCrossAxis();
3886 stretchedSize
= NS_CSS_MINMAX(stretchedSize
, mCrossMinSize
, mCrossMaxSize
);
3888 // Update the cross-size & make a note that it's stretched, so we know to
3889 // override the reflow input's computed cross-size in our final reflow.
3890 SetCrossSize(stretchedSize
);
3891 mIsStretched
= true;
3894 static nsBlockFrame
* FindFlexItemBlockFrame(nsIFrame
* aFrame
) {
3895 if (nsBlockFrame
* block
= do_QueryFrame(aFrame
)) {
3898 for (nsIFrame
* f
: aFrame
->PrincipalChildList()) {
3899 if (nsBlockFrame
* block
= FindFlexItemBlockFrame(f
)) {
3906 nsBlockFrame
* FlexItem::BlockFrame() const {
3907 return FindFlexItemBlockFrame(Frame());
3910 void SingleLineCrossAxisPositionTracker::ResolveAutoMarginsInCrossAxis(
3911 const FlexLine
& aLine
, FlexItem
& aItem
) {
3912 // Subtract the space that our item is already occupying, to see how much
3913 // space (if any) is available for its auto margins.
3914 nscoord spaceForAutoMargins
= aLine
.LineCrossSize() - aItem
.OuterCrossSize();
3916 if (spaceForAutoMargins
<= 0) {
3917 return; // No available space --> nothing to do
3920 uint32_t numAutoMargins
= aItem
.NumAutoMarginsInCrossAxis();
3921 if (numAutoMargins
== 0) {
3922 return; // No auto margins --> nothing to do.
3925 // OK, we have at least one auto margin and we have some available space.
3926 // Give each auto margin a share of the space.
3927 const auto& styleMargin
= aItem
.Frame()->StyleMargin()->mMargin
;
3928 for (const auto side
: {StartSide(), EndSide()}) {
3929 if (styleMargin
.Get(side
, mWM
).IsAuto()) {
3930 MOZ_ASSERT(aItem
.GetMarginComponentForSide(side
) == 0,
3931 "Expecting auto margins to have value '0' before we "
3934 // NOTE: integer divison is fine here; numAutoMargins is either 1 or 2.
3935 // If it's 2 & spaceForAutoMargins is odd, 1st margin gets smaller half.
3936 nscoord curAutoMarginSize
= spaceForAutoMargins
/ numAutoMargins
;
3937 aItem
.SetMarginComponentForSide(side
, curAutoMarginSize
);
3939 spaceForAutoMargins
-= curAutoMarginSize
;
3944 void SingleLineCrossAxisPositionTracker::EnterAlignPackingSpace(
3945 const FlexLine
& aLine
, const FlexItem
& aItem
,
3946 const FlexboxAxisTracker
& aAxisTracker
) {
3947 // We don't do align-self alignment on items that have auto margins
3948 // in the cross axis.
3949 if (aItem
.NumAutoMarginsInCrossAxis()) {
3953 StyleAlignFlags alignSelf
= aItem
.AlignSelf()._0
;
3954 // NOTE: 'stretch' behaves like 'flex-start' once we've stretched any
3955 // auto-sized items (which we've already done).
3956 if (alignSelf
== StyleAlignFlags::STRETCH
) {
3957 alignSelf
= StyleAlignFlags::FLEX_START
;
3960 // Map 'self-start'/'self-end' to 'start'/'end'
3961 if (alignSelf
== StyleAlignFlags::SELF_START
||
3962 alignSelf
== StyleAlignFlags::SELF_END
) {
3963 const LogicalAxis logCrossAxis
=
3964 aAxisTracker
.IsRowOriented() ? LogicalAxis::Block
: LogicalAxis::Inline
;
3965 const WritingMode cWM
= aAxisTracker
.GetWritingMode();
3966 const bool sameStart
=
3967 cWM
.ParallelAxisStartsOnSameSide(logCrossAxis
, aItem
.GetWritingMode());
3968 alignSelf
= sameStart
== (alignSelf
== StyleAlignFlags::SELF_START
)
3969 ? StyleAlignFlags::START
3970 : StyleAlignFlags::END
;
3973 // Map 'start'/'end' to 'flex-start'/'flex-end'.
3974 if (alignSelf
== StyleAlignFlags::START
) {
3975 alignSelf
= aAxisTracker
.IsCrossAxisReversed()
3976 ? StyleAlignFlags::FLEX_END
3977 : StyleAlignFlags::FLEX_START
;
3978 } else if (alignSelf
== StyleAlignFlags::END
) {
3979 alignSelf
= aAxisTracker
.IsCrossAxisReversed() ? StyleAlignFlags::FLEX_START
3980 : StyleAlignFlags::FLEX_END
;
3983 // 'align-self' falls back to 'flex-start' if it is 'center'/'flex-end' and we
3984 // have cross axis overflow
3985 // XXX we should really be falling back to 'start' as of bug 1472843
3986 if (aLine
.LineCrossSize() < aItem
.OuterCrossSize() &&
3987 (aItem
.AlignSelfFlags() & StyleAlignFlags::SAFE
)) {
3988 alignSelf
= StyleAlignFlags::FLEX_START
;
3991 if (alignSelf
== StyleAlignFlags::FLEX_START
) {
3992 // No space to skip over -- we're done.
3993 } else if (alignSelf
== StyleAlignFlags::FLEX_END
) {
3994 mPosition
+= aLine
.LineCrossSize() - aItem
.OuterCrossSize();
3995 } else if (alignSelf
== StyleAlignFlags::CENTER
) {
3996 // Note: If cross-size is odd, the "after" space will get the extra unit.
3997 mPosition
+= (aLine
.LineCrossSize() - aItem
.OuterCrossSize()) / 2;
3998 } else if (alignSelf
== StyleAlignFlags::BASELINE
||
3999 alignSelf
== StyleAlignFlags::LAST_BASELINE
) {
4000 const bool usingItemFirstBaseline
=
4001 (alignSelf
== StyleAlignFlags::BASELINE
);
4003 // The first-baseline sharing group gets (collectively) aligned to the
4004 // FlexLine's cross-start side, and similarly the last-baseline sharing
4005 // group gets snapped to the cross-end side.
4006 const bool isFirstBaselineSharingGroup
=
4007 aItem
.ItemBaselineSharingGroup() == BaselineSharingGroup::First
;
4008 const mozilla::Side alignSide
=
4009 isFirstBaselineSharingGroup
? aAxisTracker
.CrossAxisPhysicalStartSide()
4010 : aAxisTracker
.CrossAxisPhysicalEndSide();
4012 // To compute the aligned position for our flex item, we determine:
4013 // (1) The distance from the item's alignSide edge to the item's relevant
4015 nscoord itemBaselineOffset
= aItem
.BaselineOffsetFromOuterCrossEdge(
4016 alignSide
, usingItemFirstBaseline
);
4018 // (2) The distance between the FlexLine's alignSide edge and the relevant
4019 // baseline-sharing-group's baseline position.
4020 nscoord lineBaselineOffset
= isFirstBaselineSharingGroup
4021 ? aLine
.FirstBaselineOffset()
4022 : aLine
.LastBaselineOffset();
4024 NS_ASSERTION(lineBaselineOffset
>= itemBaselineOffset
,
4025 "failed at finding largest baseline offset");
4027 // (3) The difference between the above offsets, which tells us how far we
4028 // need to shift the item away from the FlexLine's alignSide edge so
4029 // that its baseline is at the proper position for its group.
4030 nscoord itemOffsetFromLineEdge
= lineBaselineOffset
- itemBaselineOffset
;
4032 if (isFirstBaselineSharingGroup
) {
4033 // alignSide is the line's cross-start edge. mPosition is already there.
4034 // From there, we step *forward* by the baseline adjustment:
4035 mPosition
+= itemOffsetFromLineEdge
;
4037 // alignSide is the line's cross-end edge. Advance mPosition to align
4038 // item with that edge (as in FLEX_END case)...
4039 mPosition
+= aLine
.LineCrossSize() - aItem
.OuterCrossSize();
4040 // ...and step *back* by the baseline adjustment:
4041 mPosition
-= itemOffsetFromLineEdge
;
4044 MOZ_ASSERT_UNREACHABLE("Unexpected align-self value");
4048 FlexboxAxisInfo::FlexboxAxisInfo(const nsIFrame
* aFlexContainer
) {
4049 MOZ_ASSERT(aFlexContainer
&& aFlexContainer
->IsFlexContainerFrame(),
4050 "Only flex containers may be passed to this constructor!");
4051 if (IsLegacyBox(aFlexContainer
)) {
4052 InitAxesFromLegacyProps(aFlexContainer
);
4054 InitAxesFromModernProps(aFlexContainer
);
4058 void FlexboxAxisInfo::InitAxesFromLegacyProps(const nsIFrame
* aFlexContainer
) {
4059 const nsStyleXUL
* styleXUL
= aFlexContainer
->StyleXUL();
4061 const bool boxOrientIsVertical
=
4062 styleXUL
->mBoxOrient
== StyleBoxOrient::Vertical
;
4063 const bool wmIsVertical
= aFlexContainer
->GetWritingMode().IsVertical();
4065 // If box-orient agrees with our writing-mode, then we're "row-oriented"
4066 // (i.e. the flexbox main axis is the same as our writing mode's inline
4067 // direction). Otherwise, we're column-oriented (i.e. the flexbox's main
4068 // axis is perpendicular to the writing-mode's inline direction).
4069 mIsRowOriented
= (boxOrientIsVertical
== wmIsVertical
);
4071 // Legacy flexbox can use "-webkit-box-direction: reverse" to reverse the
4072 // main axis (so it runs in the reverse direction of the inline axis):
4073 mIsMainAxisReversed
= styleXUL
->mBoxDirection
== StyleBoxDirection::Reverse
;
4075 // Legacy flexbox does not support reversing the cross axis -- it has no
4076 // equivalent of modern flexbox's "flex-wrap: wrap-reverse".
4077 mIsCrossAxisReversed
= false;
4080 void FlexboxAxisInfo::InitAxesFromModernProps(const nsIFrame
* aFlexContainer
) {
4081 const nsStylePosition
* stylePos
= aFlexContainer
->StylePosition();
4082 StyleFlexDirection flexDirection
= stylePos
->mFlexDirection
;
4084 // Determine main axis:
4085 switch (flexDirection
) {
4086 case StyleFlexDirection::Row
:
4087 mIsRowOriented
= true;
4088 mIsMainAxisReversed
= false;
4090 case StyleFlexDirection::RowReverse
:
4091 mIsRowOriented
= true;
4092 mIsMainAxisReversed
= true;
4094 case StyleFlexDirection::Column
:
4095 mIsRowOriented
= false;
4096 mIsMainAxisReversed
= false;
4098 case StyleFlexDirection::ColumnReverse
:
4099 mIsRowOriented
= false;
4100 mIsMainAxisReversed
= true;
4104 // "flex-wrap: wrap-reverse" reverses our cross axis.
4105 mIsCrossAxisReversed
= stylePos
->mFlexWrap
== StyleFlexWrap::WrapReverse
;
4108 FlexboxAxisTracker::FlexboxAxisTracker(
4109 const nsFlexContainerFrame
* aFlexContainer
)
4110 : mWM(aFlexContainer
->GetWritingMode()), mAxisInfo(aFlexContainer
) {}
4112 LogicalSide
FlexboxAxisTracker::MainAxisStartSide() const {
4113 return MakeLogicalSide(
4114 MainAxis(), IsMainAxisReversed() ? LogicalEdge::End
: LogicalEdge::Start
);
4117 LogicalSide
FlexboxAxisTracker::CrossAxisStartSide() const {
4118 return MakeLogicalSide(CrossAxis(), IsCrossAxisReversed()
4120 : LogicalEdge::Start
);
4123 void nsFlexContainerFrame::GenerateFlexLines(
4124 const ReflowInput
& aReflowInput
, const nscoord aTentativeContentBoxMainSize
,
4125 const nscoord aTentativeContentBoxCrossSize
,
4126 const nsTArray
<StrutInfo
>& aStruts
, const FlexboxAxisTracker
& aAxisTracker
,
4127 nscoord aMainGapSize
, nsTArray
<nsIFrame
*>& aPlaceholders
,
4128 nsTArray
<FlexLine
>& aLines
, bool& aHasCollapsedItems
) {
4129 MOZ_ASSERT(aLines
.IsEmpty(), "Expecting outparam to start out empty");
4131 auto ConstructNewFlexLine
= [&aLines
, aMainGapSize
]() {
4132 return aLines
.EmplaceBack(aMainGapSize
);
4135 const bool isSingleLine
=
4136 StyleFlexWrap::Nowrap
== aReflowInput
.mStylePosition
->mFlexWrap
;
4138 // We have at least one FlexLine. Even an empty flex container has a single
4139 // (empty) flex line.
4140 FlexLine
* curLine
= ConstructNewFlexLine();
4142 nscoord wrapThreshold
;
4144 // Not wrapping. Set threshold to sentinel value that tells us not to wrap.
4145 wrapThreshold
= NS_UNCONSTRAINEDSIZE
;
4147 // Wrapping! Set wrap threshold to flex container's content-box main-size.
4148 wrapThreshold
= aTentativeContentBoxMainSize
;
4150 // If the flex container doesn't have a definite content-box main-size
4151 // (e.g. if main axis is vertical & 'height' is 'auto'), make sure we at
4152 // least wrap when we hit its max main-size.
4153 if (wrapThreshold
== NS_UNCONSTRAINEDSIZE
) {
4154 const nscoord flexContainerMaxMainSize
=
4155 aAxisTracker
.MainComponent(aReflowInput
.ComputedMaxSize());
4156 wrapThreshold
= flexContainerMaxMainSize
;
4160 // Tracks the index of the next strut, in aStruts (and when this hits
4161 // aStruts.Length(), that means there are no more struts):
4162 uint32_t nextStrutIdx
= 0;
4164 // Overall index of the current flex item in the flex container. (This gets
4165 // checked against entries in aStruts.)
4166 uint32_t itemIdxInContainer
= 0;
4168 CSSOrderAwareFrameIterator
iter(
4169 this, FrameChildListID::Principal
,
4170 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll
,
4171 CSSOrderAwareFrameIterator::OrderState::Unknown
,
4172 OrderingPropertyForIter(this));
4174 AddOrRemoveStateBits(NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER
,
4175 iter
.ItemsAreAlreadyInOrder());
4177 const bool useMozBoxCollapseBehavior
=
4178 StyleVisibility()->UseLegacyCollapseBehavior();
4180 for (; !iter
.AtEnd(); iter
.Next()) {
4181 nsIFrame
* childFrame
= *iter
;
4182 // Don't create flex items / lines for placeholder frames:
4183 if (childFrame
->IsPlaceholderFrame()) {
4184 aPlaceholders
.AppendElement(childFrame
);
4188 const bool collapsed
= childFrame
->StyleVisibility()->IsCollapse();
4189 aHasCollapsedItems
= aHasCollapsedItems
|| collapsed
;
4191 if (useMozBoxCollapseBehavior
&& collapsed
) {
4192 // Legacy visibility:collapse behavior: make a 0-sized strut. (No need to
4193 // bother with aStruts and remembering cross size.)
4194 curLine
->Items().EmplaceBack(childFrame
, 0, aReflowInput
.GetWritingMode(),
4196 } else if (nextStrutIdx
< aStruts
.Length() &&
4197 aStruts
[nextStrutIdx
].mItemIdx
== itemIdxInContainer
) {
4198 // Use the simplified "strut" FlexItem constructor:
4199 curLine
->Items().EmplaceBack(childFrame
,
4200 aStruts
[nextStrutIdx
].mStrutCrossSize
,
4201 aReflowInput
.GetWritingMode(), aAxisTracker
);
4204 GenerateFlexItemForChild(*curLine
, childFrame
, aReflowInput
, aAxisTracker
,
4205 aTentativeContentBoxCrossSize
);
4208 // Check if we need to wrap the newly appended item to a new line, i.e. if
4209 // its outer hypothetical main size pushes our line over the threshold.
4210 // But we don't wrap if the line-length is unconstrained, nor do we wrap if
4211 // this was the first item on the line.
4212 if (wrapThreshold
!= NS_UNCONSTRAINEDSIZE
&&
4213 curLine
->Items().Length() > 1) {
4214 // If the line will be longer than wrapThreshold or at least as long as
4215 // nscoord_MAX because of the newly appended item, then wrap and move the
4216 // item to a new line.
4217 auto newOuterSize
= curLine
->TotalOuterHypotheticalMainSize();
4218 newOuterSize
+= curLine
->Items().LastElement().OuterMainSize();
4220 // Account for gap between this line's previous item and this item.
4221 newOuterSize
+= aMainGapSize
;
4223 if (newOuterSize
>= nscoord_MAX
|| newOuterSize
> wrapThreshold
) {
4224 curLine
= ConstructNewFlexLine();
4226 // Get the previous line after adding a new line because the address can
4227 // change if nsTArray needs to reallocate a new space for the new line.
4228 FlexLine
& prevLine
= aLines
[aLines
.Length() - 2];
4230 // Move the item from the end of prevLine to the end of curLine.
4231 curLine
->Items().AppendElement(prevLine
.Items().PopLastElement());
4235 // Update the line's bookkeeping about how large its items collectively are.
4236 curLine
->AddLastItemToMainSizeTotals();
4237 itemIdxInContainer
++;
4241 nsFlexContainerFrame::FlexLayoutResult
4242 nsFlexContainerFrame::GenerateFlexLayoutResult() {
4243 MOZ_ASSERT(GetPrevInFlow(), "This should be called by non-first-in-flows!");
4245 auto* data
= FirstInFlow()->GetProperty(SharedFlexData::Prop());
4246 MOZ_ASSERT(data
, "SharedFlexData should be set by our first-in-flow!");
4248 FlexLayoutResult flr
;
4250 // The order state of the children is consistent across entire continuation
4251 // chain due to calling nsContainerFrame::NormalizeChildLists() at the
4252 // beginning of Reflow(), so we can align our state bit with our
4253 // prev-in-flow's state. Setup here before calling OrderStateForIter() below.
4254 AddOrRemoveStateBits(NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER
,
4255 GetPrevInFlow()->HasAnyStateBits(
4256 NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER
));
4258 // Construct flex items for this flex container fragment from existing flex
4259 // items in SharedFlexData.
4260 CSSOrderAwareFrameIterator
iter(
4261 this, FrameChildListID::Principal
,
4262 CSSOrderAwareFrameIterator::ChildFilter::SkipPlaceholders
,
4263 OrderStateForIter(this), OrderingPropertyForIter(this));
4265 auto ConstructNewFlexLine
= [&flr
]() {
4266 // Use zero main gap size since it doesn't matter in flex container's
4267 // next-in-flows. We've computed flex items' positions in first-in-flow.
4268 return flr
.mLines
.EmplaceBack(0);
4271 // We have at least one FlexLine. Even an empty flex container has a single
4272 // (empty) flex line.
4273 FlexLine
* currentLine
= ConstructNewFlexLine();
4275 if (!iter
.AtEnd()) {
4276 nsIFrame
* child
= *iter
;
4277 nsIFrame
* childFirstInFlow
= child
->FirstInFlow();
4279 // We are iterating nested for-loops over the FlexLines and FlexItems
4280 // generated by GenerateFlexLines() and cached in flex container's
4281 // first-in-flow. For each flex item, check if its frame (must be a
4282 // first-in-flow) is the first-in-flow of the first child frame in this flex
4283 // container continuation. If so, clone the data from that FlexItem into a
4284 // FlexLine. When we find a match for the item, we know that the next child
4285 // frame might have its first-in-flow as the next item in the same original
4286 // line. In this case, we'll put the cloned data in the same line here as
4288 for (const FlexLine
& line
: data
->mLines
) {
4289 // If currentLine is empty, either it is the first line, or all the items
4290 // in the previous line have been placed in our prev-in-flows. No need to
4291 // construct a new line.
4292 if (!currentLine
->IsEmpty()) {
4293 currentLine
= ConstructNewFlexLine();
4295 for (const FlexItem
& item
: line
.Items()) {
4296 if (item
.Frame() == childFirstInFlow
) {
4297 currentLine
->Items().AppendElement(item
.CloneFor(child
));
4300 // We've constructed flex items for all children. No need to check
4301 // rest of the items.
4302 child
= childFirstInFlow
= nullptr;
4306 childFirstInFlow
= child
->FirstInFlow();
4310 // We've constructed flex items for all children. No need to check
4311 // rest of the lines.
4317 flr
.mContentBoxMainSize
= data
->mContentBoxMainSize
;
4318 flr
.mContentBoxCrossSize
= data
->mContentBoxCrossSize
;
4323 // Returns the largest outer hypothetical main-size of any line in |aLines|.
4324 // (i.e. the hypothetical main-size of the largest line)
4325 static AuCoord64
GetLargestLineMainSize(nsTArray
<FlexLine
>& aLines
) {
4326 AuCoord64 largestLineOuterSize
= 0;
4327 for (const FlexLine
& line
: aLines
) {
4328 largestLineOuterSize
=
4329 std::max(largestLineOuterSize
, line
.TotalOuterHypotheticalMainSize());
4331 return largestLineOuterSize
;
4334 nscoord
nsFlexContainerFrame::ComputeMainSize(
4335 const ReflowInput
& aReflowInput
, const FlexboxAxisTracker
& aAxisTracker
,
4336 const nscoord aTentativeContentBoxMainSize
,
4337 nsTArray
<FlexLine
>& aLines
) const {
4338 if (aAxisTracker
.IsRowOriented()) {
4339 // Row-oriented --> our main axis is the inline axis, so our main size
4340 // is our inline size (which should already be resolved).
4341 return aTentativeContentBoxMainSize
;
4344 const bool shouldApplyAutomaticMinimumOnBlockAxis
=
4345 aReflowInput
.ShouldApplyAutomaticMinimumOnBlockAxis();
4346 if (aTentativeContentBoxMainSize
!= NS_UNCONSTRAINEDSIZE
&&
4347 !shouldApplyAutomaticMinimumOnBlockAxis
) {
4348 // Column-oriented case, with fixed BSize:
4349 // Just use our fixed block-size because we always assume the available
4350 // block-size is unconstrained, and the reflow input has already done the
4351 // appropriate min/max-BSize clamping.
4352 return aTentativeContentBoxMainSize
;
4355 // Column-oriented case, with size-containment in block axis:
4356 // Behave as if we had no content and just use our MinBSize.
4357 if (Maybe
<nscoord
> containBSize
=
4358 aReflowInput
.mFrame
->ContainIntrinsicBSize()) {
4359 return aReflowInput
.ApplyMinMaxBSize(*containBSize
);
4362 const AuCoord64 largestLineMainSize
= GetLargestLineMainSize(aLines
);
4363 const nscoord contentBSize
= aReflowInput
.ApplyMinMaxBSize(
4364 nscoord(largestLineMainSize
.ToMinMaxClamped()));
4366 // If the clamped largest FlexLine length is larger than the tentative main
4367 // size (which is resolved by aspect-ratio), we extend it to contain the
4369 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
4370 if (shouldApplyAutomaticMinimumOnBlockAxis
) {
4371 // Column-oriented case, with auto BSize which is resolved by
4373 return std::max(contentBSize
, aTentativeContentBoxMainSize
);
4376 // Column-oriented case, with auto BSize:
4377 // Resolve auto BSize to the largest FlexLine length, clamped to our
4378 // computed min/max main-size properties.
4379 return contentBSize
;
4382 nscoord
nsFlexContainerFrame::ComputeCrossSize(
4383 const ReflowInput
& aReflowInput
, const FlexboxAxisTracker
& aAxisTracker
,
4384 const nscoord aTentativeContentBoxCrossSize
, nscoord aSumLineCrossSizes
,
4385 bool* aIsDefinite
) const {
4386 MOZ_ASSERT(aIsDefinite
, "outparam pointer must be non-null");
4388 if (aAxisTracker
.IsColumnOriented()) {
4389 // Column-oriented --> our cross axis is the inline axis, so our cross size
4390 // is our inline size (which should already be resolved).
4391 *aIsDefinite
= true;
4392 // FIXME: Bug 1661847 - there are cases where aTentativeContentBoxCrossSize
4393 // (i.e. aReflowInput.ComputedISize()) might not be the right thing to
4394 // return here. Specifically: if our cross size is an intrinsic size, and we
4395 // have flex items that are flexible and have aspect ratios, then we may
4396 // need to take their post-flexing main sizes into account (multiplied
4397 // through their aspect ratios to get their cross sizes), in order to
4398 // determine their flex line's size & the flex container's cross size (e.g.
4399 // as `aSumLineCrossSizes`).
4400 return aTentativeContentBoxCrossSize
;
4403 const bool shouldApplyAutomaticMinimumOnBlockAxis
=
4404 aReflowInput
.ShouldApplyAutomaticMinimumOnBlockAxis();
4405 const nscoord computedBSize
= aReflowInput
.ComputedBSize();
4406 if (computedBSize
!= NS_UNCONSTRAINEDSIZE
&&
4407 !shouldApplyAutomaticMinimumOnBlockAxis
) {
4408 // Row-oriented case (cross axis is block-axis), with fixed BSize:
4409 *aIsDefinite
= true;
4411 // Just use our fixed block-size because we always assume the available
4412 // block-size is unconstrained, and the reflow input has already done the
4413 // appropriate min/max-BSize clamping.
4414 return computedBSize
;
4417 // Row-oriented case, with size-containment in block axis:
4418 // Behave as if we had no content and just use our MinBSize.
4419 if (Maybe
<nscoord
> containBSize
=
4420 aReflowInput
.mFrame
->ContainIntrinsicBSize()) {
4421 *aIsDefinite
= true;
4422 return aReflowInput
.ApplyMinMaxBSize(*containBSize
);
4425 // The cross size must not be definite in the following cases.
4426 *aIsDefinite
= false;
4428 const nscoord contentBSize
=
4429 aReflowInput
.ApplyMinMaxBSize(aSumLineCrossSizes
);
4430 // If the content block-size is larger than the effective computed
4431 // block-size, we extend the block-size to contain all the content.
4432 // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum
4433 if (shouldApplyAutomaticMinimumOnBlockAxis
) {
4434 // Row-oriented case (cross axis is block-axis), with auto BSize which is
4435 // resolved by aspect-ratio or content size.
4436 return std::max(contentBSize
, computedBSize
);
4439 // Row-oriented case (cross axis is block axis), with auto BSize:
4440 // Shrink-wrap our line(s), subject to our min-size / max-size
4441 // constraints in that (block) axis.
4442 return contentBSize
;
4445 LogicalSize
nsFlexContainerFrame::ComputeAvailableSizeForItems(
4446 const ReflowInput
& aReflowInput
,
4447 const mozilla::LogicalMargin
& aBorderPadding
) const {
4448 const WritingMode wm
= GetWritingMode();
4449 nscoord availableBSize
= aReflowInput
.AvailableBSize();
4451 if (availableBSize
!= NS_UNCONSTRAINEDSIZE
) {
4452 // Available block-size is constrained. Subtract block-start border and
4454 availableBSize
-= aBorderPadding
.BStart(wm
);
4456 if (aReflowInput
.mStyleBorder
->mBoxDecorationBreak
==
4457 StyleBoxDecorationBreak::Clone
) {
4458 // We have box-decoration-break:clone. Subtract block-end border and
4459 // padding from the available block-size as well.
4460 availableBSize
-= aBorderPadding
.BEnd(wm
);
4463 // Available block-size can became negative after subtracting block-axis
4464 // border and padding. Per spec, to guarantee progress, fragmentainers are
4465 // assumed to have a minimum block size of 1px regardless of their used
4466 // size. https://drafts.csswg.org/css-break/#breaking-rules
4468 std::max(nsPresContext::CSSPixelsToAppUnits(1), availableBSize
);
4471 return LogicalSize(wm
, aReflowInput
.ComputedISize(), availableBSize
);
4474 void FlexLine::PositionItemsInMainAxis(
4475 const StyleContentDistribution
& aJustifyContent
,
4476 nscoord aContentBoxMainSize
, const FlexboxAxisTracker
& aAxisTracker
) {
4477 MainAxisPositionTracker
mainAxisPosnTracker(
4478 aAxisTracker
, this, aJustifyContent
, aContentBoxMainSize
);
4479 for (FlexItem
& item
: Items()) {
4480 nscoord itemMainBorderBoxSize
=
4481 item
.MainSize() + item
.BorderPaddingSizeInMainAxis();
4483 // Resolve any main-axis 'auto' margins on aChild to an actual value.
4484 mainAxisPosnTracker
.ResolveAutoMarginsInMainAxis(item
);
4486 // Advance our position tracker to child's upper-left content-box corner,
4487 // and use that as its position in the main axis.
4488 mainAxisPosnTracker
.EnterMargin(item
.Margin());
4489 mainAxisPosnTracker
.EnterChildFrame(itemMainBorderBoxSize
);
4491 item
.SetMainPosition(mainAxisPosnTracker
.Position());
4493 mainAxisPosnTracker
.ExitChildFrame(itemMainBorderBoxSize
);
4494 mainAxisPosnTracker
.ExitMargin(item
.Margin());
4495 mainAxisPosnTracker
.TraversePackingSpace();
4496 if (&item
!= &Items().LastElement()) {
4497 mainAxisPosnTracker
.TraverseGap(mMainGapSize
);
4502 void nsFlexContainerFrame::SizeItemInCrossAxis(ReflowInput
& aChildReflowInput
,
4504 // If cross axis is the item's inline axis, just use ISize from reflow input,
4505 // and don't bother with a full reflow.
4506 if (aItem
.IsInlineAxisCrossAxis()) {
4507 aItem
.SetCrossSize(aChildReflowInput
.ComputedISize());
4511 MOZ_ASSERT(!aItem
.HadMeasuringReflow(),
4512 "We shouldn't need more than one measuring reflow");
4514 if (aItem
.AlignSelf()._0
== StyleAlignFlags::STRETCH
) {
4515 // This item's got "align-self: stretch", so we probably imposed a
4516 // stretched computed cross-size on it during its previous
4517 // reflow. We're not imposing that BSize for *this* "measuring" reflow, so
4518 // we need to tell it to treat this reflow as a resize in its block axis
4519 // (regardless of whether any of its ancestors are actually being resized).
4520 // (Note: we know that the cross axis is the item's *block* axis -- if it
4521 // weren't, then we would've taken the early-return above.)
4522 aChildReflowInput
.SetBResize(true);
4523 // Not 100% sure this is needed, but be conservative for now:
4524 aChildReflowInput
.mFlags
.mIsBResizeForPercentages
= true;
4527 // Potentially reflow the item, and get the sizing info.
4528 const CachedBAxisMeasurement
& measurement
=
4529 MeasureBSizeForFlexItem(aItem
, aChildReflowInput
);
4531 // Save the sizing info that we learned from this reflow
4532 // -----------------------------------------------------
4534 // Tentatively store the child's desired content-box cross-size.
4535 aItem
.SetCrossSize(measurement
.BSize());
4538 void FlexLine::PositionItemsInCrossAxis(
4539 nscoord aLineStartPosition
, const FlexboxAxisTracker
& aAxisTracker
) {
4540 SingleLineCrossAxisPositionTracker
lineCrossAxisPosnTracker(aAxisTracker
);
4542 for (FlexItem
& item
: Items()) {
4543 // First, stretch the item's cross size (if appropriate), and resolve any
4544 // auto margins in this axis.
4545 item
.ResolveStretchedCrossSize(mLineCrossSize
);
4546 lineCrossAxisPosnTracker
.ResolveAutoMarginsInCrossAxis(*this, item
);
4548 // Compute the cross-axis position of this item
4549 nscoord itemCrossBorderBoxSize
=
4550 item
.CrossSize() + item
.BorderPaddingSizeInCrossAxis();
4551 lineCrossAxisPosnTracker
.EnterAlignPackingSpace(*this, item
, aAxisTracker
);
4552 lineCrossAxisPosnTracker
.EnterMargin(item
.Margin());
4553 lineCrossAxisPosnTracker
.EnterChildFrame(itemCrossBorderBoxSize
);
4555 item
.SetCrossPosition(aLineStartPosition
+
4556 lineCrossAxisPosnTracker
.Position());
4558 // Back out to cross-axis edge of the line.
4559 lineCrossAxisPosnTracker
.ResetPosition();
4563 void nsFlexContainerFrame::Reflow(nsPresContext
* aPresContext
,
4564 ReflowOutput
& aReflowOutput
,
4565 const ReflowInput
& aReflowInput
,
4566 nsReflowStatus
& aStatus
) {
4567 if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
4572 DO_GLOBAL_REFLOW_COUNT("nsFlexContainerFrame");
4573 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
4574 MOZ_ASSERT(aPresContext
== PresContext());
4575 NS_WARNING_ASSERTION(
4576 aReflowInput
.ComputedISize() != NS_UNCONSTRAINEDSIZE
,
4577 "Unconstrained inline size; this should only result from huge sizes "
4578 "(not intrinsic sizing w/ orthogonal flows)");
4580 FLEX_LOG("Reflowing flex container frame %p ...", this);
4582 if (IsFrameTreeTooDeep(aReflowInput
, aReflowOutput
, aStatus
)) {
4586 NormalizeChildLists();
4589 mDidPushItemsBitMayLie
= false;
4590 SanityCheckChildListsBeforeReflow();
4593 // We (and our children) can only depend on our ancestor's bsize if we have
4594 // a percent-bsize, or if we're positioned and we have "block-start" and
4595 // "block-end" set and have block-size:auto. (There are actually other cases,
4596 // too -- e.g. if our parent is itself a block-dir flex container and we're
4597 // flexible -- but we'll let our ancestors handle those sorts of cases.)
4599 // TODO(emilio): the !bsize.IsLengthPercentage() preserves behavior, but it's
4600 // too conservative. min/max-content don't really depend on the container.
4601 WritingMode wm
= aReflowInput
.GetWritingMode();
4602 const nsStylePosition
* stylePos
= StylePosition();
4603 const auto& bsize
= stylePos
->BSize(wm
);
4604 if (bsize
.HasPercent() || (StyleDisplay()->IsAbsolutelyPositionedStyle() &&
4605 (bsize
.IsAuto() || !bsize
.IsLengthPercentage()) &&
4606 !stylePos
->mOffset
.GetBStart(wm
).IsAuto() &&
4607 !stylePos
->mOffset
.GetBEnd(wm
).IsAuto())) {
4608 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE
);
4611 const FlexboxAxisTracker
axisTracker(this);
4613 // Check to see if we need to create a computed info structure, to
4614 // be filled out for use by devtools.
4615 ComputedFlexContainerInfo
* containerInfo
= CreateOrClearFlexContainerInfo();
4617 FlexLayoutResult flr
;
4618 PerFragmentFlexData fragmentData
;
4619 const nsIFrame
* prevInFlow
= GetPrevInFlow();
4621 const LogicalSize tentativeContentBoxSize
= aReflowInput
.ComputedSize();
4622 const nscoord tentativeContentBoxMainSize
=
4623 axisTracker
.MainComponent(tentativeContentBoxSize
);
4624 const nscoord tentativeContentBoxCrossSize
=
4625 axisTracker
.CrossComponent(tentativeContentBoxSize
);
4627 // Calculate gap sizes for main and cross axis. We only need them in
4628 // DoFlexLayout in the first-in-flow, so no need to worry about consumed
4630 const auto& mainGapStyle
=
4631 axisTracker
.IsRowOriented() ? stylePos
->mColumnGap
: stylePos
->mRowGap
;
4632 const auto& crossGapStyle
=
4633 axisTracker
.IsRowOriented() ? stylePos
->mRowGap
: stylePos
->mColumnGap
;
4634 const nscoord mainGapSize
= nsLayoutUtils::ResolveGapToLength(
4635 mainGapStyle
, tentativeContentBoxMainSize
);
4636 const nscoord crossGapSize
= nsLayoutUtils::ResolveGapToLength(
4637 crossGapStyle
, tentativeContentBoxCrossSize
);
4639 // When fragmenting a flex container, we run the flex algorithm without
4640 // regards to pagination in order to compute the flex container's desired
4641 // content-box size. https://drafts.csswg.org/css-flexbox-1/#pagination-algo
4643 // Note: For a multi-line column-oriented flex container, the sample
4644 // algorithm suggests we wrap the flex line at the block-end edge of a
4645 // column/page, but we do not implement it intentionally. This brings the
4646 // layout result closer to the one as if there's no fragmentation.
4647 AutoTArray
<StrutInfo
, 1> struts
;
4648 flr
= DoFlexLayout(aReflowInput
, tentativeContentBoxMainSize
,
4649 tentativeContentBoxCrossSize
, axisTracker
, mainGapSize
,
4650 crossGapSize
, struts
, containerInfo
);
4652 if (!struts
.IsEmpty()) {
4653 // We're restarting flex layout, with new knowledge of collapsed items.
4655 flr
.mPlaceholders
.Clear();
4656 flr
= DoFlexLayout(aReflowInput
, tentativeContentBoxMainSize
,
4657 tentativeContentBoxCrossSize
, axisTracker
, mainGapSize
,
4658 crossGapSize
, struts
, containerInfo
);
4661 flr
= GenerateFlexLayoutResult();
4662 auto* fragmentDataProp
=
4663 prevInFlow
->GetProperty(PerFragmentFlexData::Prop());
4664 MOZ_ASSERT(fragmentDataProp
,
4665 "PerFragmentFlexData should be set in our prev-in-flow!");
4666 fragmentData
= *fragmentDataProp
;
4669 LogicalSize contentBoxSize
= axisTracker
.LogicalSizeFromFlexRelativeSizes(
4670 flr
.mContentBoxMainSize
, flr
.mContentBoxCrossSize
);
4672 const nscoord consumedBSize
= CalcAndCacheConsumedBSize();
4673 const nscoord effectiveContentBSize
=
4674 contentBoxSize
.BSize(wm
) - consumedBSize
;
4675 LogicalMargin borderPadding
= aReflowInput
.ComputedLogicalBorderPadding(wm
);
4676 if (MOZ_UNLIKELY(aReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
)) {
4677 // We assume we are the last fragment by using
4678 // PreReflowBlockLevelLogicalSkipSides(), and skip block-end border and
4679 // padding if needed.
4680 borderPadding
.ApplySkipSides(PreReflowBlockLevelLogicalSkipSides());
4683 // Determine this frame's tentative border-box size. This is used for logical
4684 // to physical coordinate conversion when positioning children.
4686 // Note that vertical-rl writing-mode is the only case where the block flow
4687 // direction progresses in a negative physical direction, and therefore block
4688 // direction coordinate conversion depends on knowing the width of the
4689 // coordinate space in order to translate between the logical and physical
4690 // origins. As a result, if our final border-box block-size is different from
4691 // this tentative one, and we are in vertical-rl writing mode, we need to
4692 // adjust our children's position after reflowing them.
4693 const LogicalSize
tentativeBorderBoxSize(
4694 wm
, contentBoxSize
.ISize(wm
) + borderPadding
.IStartEnd(wm
),
4695 std::min(effectiveContentBSize
+ borderPadding
.BStartEnd(wm
),
4696 aReflowInput
.AvailableBSize()));
4697 const nsSize containerSize
= tentativeBorderBoxSize
.GetPhysicalSize(wm
);
4699 OverflowAreas ocBounds
;
4700 nsReflowStatus ocStatus
;
4702 ReflowOverflowContainerChildren(
4703 aPresContext
, aReflowInput
, ocBounds
, ReflowChildFlags::Default
,
4704 ocStatus
, MergeSortedFrameListsFor
, Some(containerSize
));
4707 const LogicalSize availableSizeForItems
=
4708 ComputeAvailableSizeForItems(aReflowInput
, borderPadding
);
4709 const auto [childrenBEndEdge
, childrenStatus
] =
4710 ReflowChildren(aReflowInput
, containerSize
, availableSizeForItems
,
4711 borderPadding
, axisTracker
, flr
, fragmentData
);
4713 bool mayNeedNextInFlow
= false;
4714 if (aReflowInput
.IsInFragmentedContext()) {
4715 // This fragment's contribution to the flex container's cumulative
4716 // content-box block-size, if it turns out that this is the final vs.
4717 // non-final fragment:
4719 // * If it turns out we *are* the final fragment, then this fragment's
4720 // content-box contribution is the distance from the start of our content
4721 // box to the block-end edge of our children (note the borderPadding
4722 // subtraction is just to get us to a content-box-relative offset here):
4723 const nscoord bSizeContributionIfFinalFragment
=
4724 childrenBEndEdge
- borderPadding
.BStart(wm
);
4726 // * If it turns out we're *not* the final fragment, then this fragment's
4727 // content-box extends to the edge of the availableSizeForItems (at least),
4728 // regardless of whether we actually have items at that location:
4729 const nscoord bSizeContributionIfNotFinalFragment
= std::max(
4730 bSizeContributionIfFinalFragment
, availableSizeForItems
.BSize(wm
));
4732 // mCumulativeBEndEdgeShift was updated in ReflowChildren(), and our
4733 // children's block-size may grow in fragmented context. If our block-size
4734 // and max-block-size are unconstrained, then we allow the flex container to
4735 // grow to accommodate any children whose sizes grew as a result of
4737 if (aReflowInput
.ComputedBSize() == NS_UNCONSTRAINEDSIZE
) {
4738 contentBoxSize
.BSize(wm
) = aReflowInput
.ApplyMinMaxBSize(
4739 contentBoxSize
.BSize(wm
) + fragmentData
.mCumulativeBEndEdgeShift
);
4741 if (childrenStatus
.IsComplete()) {
4742 // All of the children fit! We know that we're using a content-based
4743 // block-size, and we know our children's block-size may have grown due
4744 // to fragmentation. So we allow ourselves to grow our block-size here
4745 // to contain the block-end edge of our last child (subject to our
4746 // min/max constraints).
4747 contentBoxSize
.BSize(wm
) = aReflowInput
.ApplyMinMaxBSize(std::max(
4748 contentBoxSize
.BSize(wm
), fragmentData
.mCumulativeContentBoxBSize
+
4749 bSizeContributionIfFinalFragment
));
4751 // As in the if-branch above, we extend our block-size, but in this case
4752 // we know that a child didn't fit and might overshot our available
4753 // size, so we assume this fragment won't be the final fragment, and
4754 // hence it should contribute bSizeContributionIfNotFinalFragment
4755 // (subject to our min/max constraints).
4756 contentBoxSize
.BSize(wm
) = aReflowInput
.ApplyMinMaxBSize(std::max(
4757 contentBoxSize
.BSize(wm
), fragmentData
.mCumulativeContentBoxBSize
+
4758 bSizeContributionIfNotFinalFragment
));
4760 if (aReflowInput
.ComputedMaxBSize() == NS_UNCONSTRAINEDSIZE
) {
4761 mayNeedNextInFlow
= true;
4763 // The definite max-block-size can be the upper bound of our
4764 // content-box block-size. We should check whether we need a
4766 mayNeedNextInFlow
= contentBoxSize
.BSize(wm
) - consumedBSize
>
4767 availableSizeForItems
.BSize(wm
);
4771 mayNeedNextInFlow
= contentBoxSize
.BSize(wm
) - consumedBSize
>
4772 availableSizeForItems
.BSize(wm
);
4774 fragmentData
.mCumulativeContentBoxBSize
+=
4775 bSizeContributionIfNotFinalFragment
;
4777 // If we may need a next-in-flow, we'll need to skip block-end border and
4779 if (mayNeedNextInFlow
&& aReflowInput
.mStyleBorder
->mBoxDecorationBreak
==
4780 StyleBoxDecorationBreak::Slice
) {
4781 borderPadding
.BEnd(wm
) = 0;
4785 PopulateReflowOutput(aReflowOutput
, aReflowInput
, aStatus
, contentBoxSize
,
4786 borderPadding
, consumedBSize
, mayNeedNextInFlow
,
4787 childrenBEndEdge
, childrenStatus
, axisTracker
, flr
);
4789 if (wm
.IsVerticalRL()) {
4790 // If the final border-box block-size is different from the tentative one,
4791 // adjust our children's position.
4792 const nscoord deltaBCoord
=
4793 tentativeBorderBoxSize
.BSize(wm
) - aReflowOutput
.Size(wm
).BSize(wm
);
4794 if (deltaBCoord
!= 0) {
4795 const LogicalPoint
delta(wm
, 0, deltaBCoord
);
4796 for (const FlexLine
& line
: flr
.mLines
) {
4797 for (const FlexItem
& item
: line
.Items()) {
4798 item
.Frame()->MovePositionBy(wm
, delta
);
4804 // Overflow area = union(my overflow area, children's overflow areas)
4805 aReflowOutput
.SetOverflowAreasToDesiredBounds();
4806 UnionInFlowChildOverflow(aReflowOutput
.mOverflowAreas
);
4808 // Merge overflow container bounds and status.
4809 aReflowOutput
.mOverflowAreas
.UnionWith(ocBounds
);
4810 aStatus
.MergeCompletionStatusFrom(ocStatus
);
4812 FinishReflowWithAbsoluteFrames(PresContext(), aReflowOutput
, aReflowInput
,
4815 // Finally update our line and item measurements in our containerInfo.
4816 if (MOZ_UNLIKELY(containerInfo
)) {
4817 UpdateFlexLineAndItemInfo(*containerInfo
, flr
.mLines
);
4820 // If we are the first-in-flow, we want to store data for our next-in-flows,
4821 // or clear the existing data if it is not needed.
4823 SharedFlexData
* sharedData
= GetProperty(SharedFlexData::Prop());
4824 if (!aStatus
.IsFullyComplete()) {
4826 sharedData
= new SharedFlexData
;
4827 SetProperty(SharedFlexData::Prop(), sharedData
);
4829 sharedData
->Update(std::move(flr
));
4830 } else if (sharedData
&& !GetNextInFlow()) {
4831 // We are fully-complete, so no next-in-flow is needed. However, if we
4832 // report SetInlineLineBreakBeforeAndReset() in an incremental reflow, our
4833 // next-in-flow might still exist. It can be reflowed again before us if
4834 // it is an overflow container. Delete the existing data only if we don't
4835 // have a next-in-flow.
4836 RemoveProperty(SharedFlexData::Prop());
4840 PerFragmentFlexData
* fragmentDataProp
=
4841 GetProperty(PerFragmentFlexData::Prop());
4842 if (!aStatus
.IsFullyComplete()) {
4843 if (!fragmentDataProp
) {
4844 fragmentDataProp
= new PerFragmentFlexData
;
4845 SetProperty(PerFragmentFlexData::Prop(), fragmentDataProp
);
4847 *fragmentDataProp
= fragmentData
;
4848 } else if (fragmentDataProp
&& !GetNextInFlow()) {
4849 // Similar to the condition to remove SharedFlexData, delete the
4850 // existing data only if we don't have a next-in-flow.
4851 RemoveProperty(PerFragmentFlexData::Prop());
4855 Maybe
<nscoord
> nsFlexContainerFrame::GetNaturalBaselineBOffset(
4856 WritingMode aWM
, BaselineSharingGroup aBaselineGroup
,
4857 BaselineExportContext
) const {
4858 if (StyleDisplay()->IsContainLayout() ||
4859 HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE
)) {
4862 return Some(aBaselineGroup
== BaselineSharingGroup::First
? mFirstBaseline
4866 void nsFlexContainerFrame::UnionInFlowChildOverflow(
4867 OverflowAreas
& aOverflowAreas
) {
4868 // The CSS Overflow spec [1] requires that a scrollable container's
4869 // scrollable overflow should include the following areas.
4871 // a) "the box's own content and padding areas": we treat the *content* as
4872 // the scrolled inner frame's theoretical content-box that's intrinsically
4873 // sized to the union of all the flex items' margin boxes, _without_
4874 // relative positioning applied. The *padding areas* is just inflation on
4875 // top of the theoretical content-box by the flex container's padding.
4877 // b) "the margin areas of grid item and flex item boxes for which the box
4878 // establishes a containing block": a) already includes the flex items'
4879 // normal-positioned margin boxes into the scrollable overflow, but their
4880 // relative-positioned margin boxes should also be included because relpos
4881 // children are still flex items.
4883 // [1] https://drafts.csswg.org/css-overflow-3/#scrollable.
4884 const bool isScrolledContent
=
4885 Style()->GetPseudoType() == PseudoStyleType::scrolledContent
;
4886 bool anyScrolledContentItem
= false;
4887 // Union of normal-positioned margin boxes for all the items.
4888 nsRect itemMarginBoxes
;
4889 // Overflow areas containing the union of relative-positioned and
4890 // stick-positioned margin boxes of relpos items.
4892 // Note for sticky-positioned margin boxes, we only union it with the ink
4893 // overflow to avoid circular dependencies with the scroll container. (The
4894 // scroll position and the scroll container's size impact the sticky position,
4895 // so we don't want the sticky position to impact them.)
4896 OverflowAreas relPosItemMarginBoxes
;
4897 const bool useMozBoxCollapseBehavior
=
4898 StyleVisibility()->UseLegacyCollapseBehavior();
4899 for (nsIFrame
* f
: mFrames
) {
4900 if (useMozBoxCollapseBehavior
&& f
->StyleVisibility()->IsCollapse()) {
4903 ConsiderChildOverflow(aOverflowAreas
, f
);
4904 if (!isScrolledContent
) {
4907 if (f
->IsPlaceholderFrame()) {
4910 anyScrolledContentItem
= true;
4911 if (MOZ_UNLIKELY(f
->IsRelativelyOrStickyPositioned())) {
4912 const nsRect marginRect
= f
->GetMarginRectRelativeToSelf();
4914 itemMarginBoxes
.Union(marginRect
+ f
->GetNormalPosition());
4915 if (f
->IsRelativelyPositioned()) {
4916 relPosItemMarginBoxes
.UnionAllWith(marginRect
+ f
->GetPosition());
4918 MOZ_ASSERT(f
->IsStickyPositioned());
4919 relPosItemMarginBoxes
.UnionWith(
4920 OverflowAreas(marginRect
+ f
->GetPosition(), nsRect()));
4923 itemMarginBoxes
= itemMarginBoxes
.Union(f
->GetMarginRect());
4927 if (anyScrolledContentItem
) {
4928 itemMarginBoxes
.Inflate(GetUsedPadding());
4929 aOverflowAreas
.UnionAllWith(itemMarginBoxes
);
4930 aOverflowAreas
.UnionWith(relPosItemMarginBoxes
);
4934 void nsFlexContainerFrame::UnionChildOverflow(OverflowAreas
& aOverflowAreas
) {
4935 UnionInFlowChildOverflow(aOverflowAreas
);
4936 // Union with child frames, skipping the principal list since we already
4937 // handled those above.
4938 nsLayoutUtils::UnionChildOverflow(this, aOverflowAreas
,
4939 {FrameChildListID::Principal
});
4942 void nsFlexContainerFrame::CalculatePackingSpace(
4943 uint32_t aNumThingsToPack
, const StyleContentDistribution
& aAlignVal
,
4944 nscoord
* aFirstSubjectOffset
, uint32_t* aNumPackingSpacesRemaining
,
4945 nscoord
* aPackingSpaceRemaining
) {
4946 StyleAlignFlags val
= aAlignVal
.primary
;
4947 MOZ_ASSERT(val
== StyleAlignFlags::SPACE_BETWEEN
||
4948 val
== StyleAlignFlags::SPACE_AROUND
||
4949 val
== StyleAlignFlags::SPACE_EVENLY
,
4950 "Unexpected alignment value");
4952 MOZ_ASSERT(*aPackingSpaceRemaining
>= 0,
4953 "Should not be called with negative packing space");
4955 // Note: In the aNumThingsToPack==1 case, the fallback behavior for
4956 // 'space-between' depends on precise information about the axes that we
4957 // don't have here. So, for that case, we just depend on the caller to
4958 // explicitly convert 'space-{between,around,evenly}' keywords to the
4959 // appropriate fallback alignment and skip this function.
4960 MOZ_ASSERT(aNumThingsToPack
> 1,
4961 "Should not be called unless there's more than 1 thing to pack");
4963 // Packing spaces between items:
4964 *aNumPackingSpacesRemaining
= aNumThingsToPack
- 1;
4966 if (val
== StyleAlignFlags::SPACE_BETWEEN
) {
4967 // No need to reserve space at beginning/end, so we're done.
4971 // We need to add 1 or 2 packing spaces, split between beginning/end, for
4972 // space-around / space-evenly:
4973 size_t numPackingSpacesForEdges
=
4974 val
== StyleAlignFlags::SPACE_AROUND
? 1 : 2;
4976 // How big will each "full" packing space be:
4977 nscoord packingSpaceSize
=
4978 *aPackingSpaceRemaining
/
4979 (*aNumPackingSpacesRemaining
+ numPackingSpacesForEdges
);
4980 // How much packing-space are we allocating to the edges:
4981 nscoord totalEdgePackingSpace
= numPackingSpacesForEdges
* packingSpaceSize
;
4983 // Use half of that edge packing space right now:
4984 *aFirstSubjectOffset
+= totalEdgePackingSpace
/ 2;
4985 // ...but we need to subtract all of it right away, so that we won't
4986 // hand out any of it to intermediate packing spaces.
4987 *aPackingSpaceRemaining
-= totalEdgePackingSpace
;
4990 ComputedFlexContainerInfo
*
4991 nsFlexContainerFrame::CreateOrClearFlexContainerInfo() {
4992 if (!HasAnyStateBits(NS_STATE_FLEX_COMPUTED_INFO
)) {
4996 // The flag that sets ShouldGenerateComputedInfo() will never be cleared.
4997 // That's acceptable because it's only set in a Chrome API invoked by
4998 // devtools, and won't impact normal browsing.
5000 // Re-use the ComputedFlexContainerInfo, if it exists.
5001 ComputedFlexContainerInfo
* info
= GetProperty(FlexContainerInfo());
5003 // We can reuse, as long as we clear out old data.
5004 info
->mLines
.Clear();
5006 info
= new ComputedFlexContainerInfo();
5007 SetProperty(FlexContainerInfo(), info
);
5013 nscoord
nsFlexContainerFrame::FlexItemConsumedBSize(const FlexItem
& aItem
) {
5014 nsSplittableFrame
* f
= do_QueryFrame(aItem
.Frame());
5015 return f
? ConsumedBSize(f
) : 0;
5018 void nsFlexContainerFrame::CreateFlexLineAndFlexItemInfo(
5019 ComputedFlexContainerInfo
& aContainerInfo
,
5020 const nsTArray
<FlexLine
>& aLines
) {
5021 for (const FlexLine
& line
: aLines
) {
5022 ComputedFlexLineInfo
* lineInfo
= aContainerInfo
.mLines
.AppendElement();
5023 // Most of the remaining lineInfo properties will be filled out in
5024 // UpdateFlexLineAndItemInfo (some will be provided by other functions),
5025 // when we have real values. But we still add all the items here, so
5026 // we can capture computed data for each item as we proceed.
5027 for (const FlexItem
& item
: line
.Items()) {
5028 nsIFrame
* frame
= item
.Frame();
5030 // The frame may be for an element, or it may be for an
5031 // anonymous flex item, e.g. wrapping one or more text nodes.
5032 // DevTools wants the content node for the actual child in
5033 // the DOM tree, so we descend through anonymous boxes.
5034 nsIContent
* content
= nullptr;
5035 nsIFrame
* targetFrame
= GetFirstNonAnonBoxInSubtree(frame
);
5037 content
= targetFrame
->GetContent();
5040 // Skip over content that is only whitespace, which might
5041 // have been broken off from a text node which is our real
5043 while (content
&& content
->TextIsOnlyWhitespace()) {
5044 // If content is only whitespace, try the frame sibling.
5045 targetFrame
= targetFrame
->GetNextSibling();
5047 content
= targetFrame
->GetContent();
5053 ComputedFlexItemInfo
* itemInfo
= lineInfo
->mItems
.AppendElement();
5055 itemInfo
->mNode
= content
;
5057 // itemInfo->mMainBaseSize and mMainDeltaSize will be filled out
5058 // in ResolveFlexibleLengths(). Other measurements will be captured in
5059 // UpdateFlexLineAndItemInfo.
5064 void nsFlexContainerFrame::ComputeFlexDirections(
5065 ComputedFlexContainerInfo
& aContainerInfo
,
5066 const FlexboxAxisTracker
& aAxisTracker
) {
5067 auto ConvertPhysicalStartSideToFlexPhysicalDirection
=
5068 [](mozilla::Side aStartSide
) {
5069 switch (aStartSide
) {
5071 return dom::FlexPhysicalDirection::Horizontal_lr
;
5073 return dom::FlexPhysicalDirection::Horizontal_rl
;
5075 return dom::FlexPhysicalDirection::Vertical_tb
;
5077 return dom::FlexPhysicalDirection::Vertical_bt
;
5080 MOZ_ASSERT_UNREACHABLE("We should handle all sides!");
5081 return dom::FlexPhysicalDirection::Horizontal_lr
;
5084 aContainerInfo
.mMainAxisDirection
=
5085 ConvertPhysicalStartSideToFlexPhysicalDirection(
5086 aAxisTracker
.MainAxisPhysicalStartSide());
5087 aContainerInfo
.mCrossAxisDirection
=
5088 ConvertPhysicalStartSideToFlexPhysicalDirection(
5089 aAxisTracker
.CrossAxisPhysicalStartSide());
5092 void nsFlexContainerFrame::UpdateFlexLineAndItemInfo(
5093 ComputedFlexContainerInfo
& aContainerInfo
,
5094 const nsTArray
<FlexLine
>& aLines
) {
5095 uint32_t lineIndex
= 0;
5096 for (const FlexLine
& line
: aLines
) {
5097 ComputedFlexLineInfo
& lineInfo
= aContainerInfo
.mLines
[lineIndex
];
5099 lineInfo
.mCrossSize
= line
.LineCrossSize();
5100 lineInfo
.mFirstBaselineOffset
= line
.FirstBaselineOffset();
5101 lineInfo
.mLastBaselineOffset
= line
.LastBaselineOffset();
5103 uint32_t itemIndex
= 0;
5104 for (const FlexItem
& item
: line
.Items()) {
5105 ComputedFlexItemInfo
& itemInfo
= lineInfo
.mItems
[itemIndex
];
5106 itemInfo
.mFrameRect
= item
.Frame()->GetRect();
5107 itemInfo
.mMainMinSize
= item
.MainMinSize();
5108 itemInfo
.mMainMaxSize
= item
.MainMaxSize();
5109 itemInfo
.mCrossMinSize
= item
.CrossMinSize();
5110 itemInfo
.mCrossMaxSize
= item
.CrossMaxSize();
5111 itemInfo
.mClampState
=
5112 item
.WasMinClamped()
5113 ? mozilla::dom::FlexItemClampState::Clamped_to_min
5114 : (item
.WasMaxClamped()
5115 ? mozilla::dom::FlexItemClampState::Clamped_to_max
5116 : mozilla::dom::FlexItemClampState::Unclamped
);
5123 nsFlexContainerFrame
* nsFlexContainerFrame::GetFlexFrameWithComputedInfo(
5125 // Prepare a lambda function that we may need to call multiple times.
5126 auto GetFlexContainerFrame
= [](nsIFrame
* aFrame
) {
5127 // Return the aFrame's content insertion frame, iff it is
5128 // a flex container frame.
5129 nsFlexContainerFrame
* flexFrame
= nullptr;
5132 nsIFrame
* inner
= aFrame
;
5133 if (MOZ_UNLIKELY(aFrame
->IsFieldSetFrame())) {
5134 inner
= static_cast<nsFieldSetFrame
*>(aFrame
)->GetInner();
5136 // Since "Get" methods like GetInner and GetContentInsertionFrame can
5137 // return null, we check the return values before dereferencing. Our
5138 // calling pattern makes this unlikely, but we're being careful.
5139 nsIFrame
* insertionFrame
=
5140 inner
? inner
->GetContentInsertionFrame() : nullptr;
5141 nsIFrame
* possibleFlexFrame
= insertionFrame
? insertionFrame
: aFrame
;
5142 flexFrame
= possibleFlexFrame
->IsFlexContainerFrame()
5143 ? static_cast<nsFlexContainerFrame
*>(possibleFlexFrame
)
5149 nsFlexContainerFrame
* flexFrame
= GetFlexContainerFrame(aFrame
);
5153 // Generate the FlexContainerInfo data, if it's not already there.
5154 if (flexFrame
->HasProperty(FlexContainerInfo())) {
5157 // Trigger a reflow that generates additional flex property data.
5158 // Hold onto aFrame while we do this, in case reflow destroys it.
5159 AutoWeakFrame
weakFrameRef(aFrame
);
5161 RefPtr
<mozilla::PresShell
> presShell
= flexFrame
->PresShell();
5162 flexFrame
->AddStateBits(NS_STATE_FLEX_COMPUTED_INFO
);
5163 presShell
->FrameNeedsReflow(flexFrame
, IntrinsicDirty::None
,
5165 presShell
->FlushPendingNotifications(FlushType::Layout
);
5167 // Since the reflow may have side effects, get the flex frame
5168 // again. But if the weakFrameRef is no longer valid, then we
5170 if (!weakFrameRef
.IsAlive()) {
5174 flexFrame
= GetFlexContainerFrame(weakFrameRef
.GetFrame());
5176 NS_WARNING_ASSERTION(
5177 !flexFrame
|| flexFrame
->HasProperty(FlexContainerInfo()),
5178 "The state bit should've made our forced-reflow "
5179 "generate a FlexContainerInfo object");
5184 bool nsFlexContainerFrame::IsItemInlineAxisMainAxis(nsIFrame
* aFrame
) {
5185 MOZ_ASSERT(aFrame
&& aFrame
->IsFlexItem(), "expecting arg to be a flex item");
5186 const WritingMode flexItemWM
= aFrame
->GetWritingMode();
5187 const nsIFrame
* flexContainer
= aFrame
->GetParent();
5189 if (IsLegacyBox(flexContainer
)) {
5190 // For legacy boxes, the main axis is determined by "box-orient", and we can
5191 // just directly check if that's vertical, and compare that to whether the
5192 // item's WM is also vertical:
5193 bool boxOrientIsVertical
=
5194 flexContainer
->StyleXUL()->mBoxOrient
== StyleBoxOrient::Vertical
;
5195 return flexItemWM
.IsVertical() == boxOrientIsVertical
;
5198 // For modern CSS flexbox, we get our return value by asking two questions
5199 // and comparing their answers.
5200 // Question 1: does aFrame have the same inline axis as its flex container?
5201 bool itemInlineAxisIsParallelToParent
=
5202 !flexItemWM
.IsOrthogonalTo(flexContainer
->GetWritingMode());
5204 // Question 2: is aFrame's flex container row-oriented? (This tells us
5205 // whether the flex container's main axis is its inline axis.)
5206 auto flexDirection
= flexContainer
->StylePosition()->mFlexDirection
;
5207 bool flexContainerIsRowOriented
=
5208 flexDirection
== StyleFlexDirection::Row
||
5209 flexDirection
== StyleFlexDirection::RowReverse
;
5211 // aFrame's inline axis is its flex container's main axis IFF the above
5212 // questions have the same answer.
5213 return flexContainerIsRowOriented
== itemInlineAxisIsParallelToParent
;
5217 bool nsFlexContainerFrame::IsUsedFlexBasisContent(
5218 const StyleFlexBasis
& aFlexBasis
, const StyleSize
& aMainSize
) {
5219 // We have a used flex-basis of 'content' if flex-basis explicitly has that
5220 // value, OR if flex-basis is 'auto' (deferring to the main-size property)
5221 // and the main-size property is also 'auto'.
5222 // See https://drafts.csswg.org/css-flexbox-1/#valdef-flex-basis-auto
5223 if (aFlexBasis
.IsContent()) {
5226 return aFlexBasis
.IsAuto() && aMainSize
.IsAuto();
5229 nsFlexContainerFrame::FlexLayoutResult
nsFlexContainerFrame::DoFlexLayout(
5230 const ReflowInput
& aReflowInput
, const nscoord aTentativeContentBoxMainSize
,
5231 const nscoord aTentativeContentBoxCrossSize
,
5232 const FlexboxAxisTracker
& aAxisTracker
, nscoord aMainGapSize
,
5233 nscoord aCrossGapSize
, nsTArray
<StrutInfo
>& aStruts
,
5234 ComputedFlexContainerInfo
* const aContainerInfo
) {
5235 FlexLayoutResult flr
;
5237 GenerateFlexLines(aReflowInput
, aTentativeContentBoxMainSize
,
5238 aTentativeContentBoxCrossSize
, aStruts
, aAxisTracker
,
5239 aMainGapSize
, flr
.mPlaceholders
, flr
.mLines
,
5240 flr
.mHasCollapsedItems
);
5242 if ((flr
.mLines
.Length() == 1 && flr
.mLines
[0].IsEmpty()) ||
5243 aReflowInput
.mStyleDisplay
->IsContainLayout()) {
5244 // We have no flex items, or we're layout-contained. So, we have no
5245 // baseline, and our parent should synthesize a baseline if needed.
5246 AddStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE
);
5248 RemoveStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE
);
5251 // Construct our computed info if we've been asked to do so. This is
5252 // necessary to do now so we can capture some computed values for
5253 // FlexItems during layout that would not otherwise be saved (like
5254 // size adjustments). We'll later fix up the line properties,
5255 // because the correct values aren't available yet.
5256 if (aContainerInfo
) {
5257 MOZ_ASSERT(HasAnyStateBits(NS_STATE_FLEX_COMPUTED_INFO
),
5258 "We should only have the info struct if we should generate it");
5260 if (!aStruts
.IsEmpty()) {
5261 // We restarted DoFlexLayout, and may have stale mLines to clear:
5262 aContainerInfo
->mLines
.Clear();
5264 MOZ_ASSERT(aContainerInfo
->mLines
.IsEmpty(), "Shouldn't have lines yet.");
5267 CreateFlexLineAndFlexItemInfo(*aContainerInfo
, flr
.mLines
);
5268 ComputeFlexDirections(*aContainerInfo
, aAxisTracker
);
5271 flr
.mContentBoxMainSize
= ComputeMainSize(
5272 aReflowInput
, aAxisTracker
, aTentativeContentBoxMainSize
, flr
.mLines
);
5274 uint32_t lineIndex
= 0;
5275 for (FlexLine
& line
: flr
.mLines
) {
5276 ComputedFlexLineInfo
* lineInfo
=
5277 aContainerInfo
? &aContainerInfo
->mLines
[lineIndex
] : nullptr;
5278 line
.ResolveFlexibleLengths(flr
.mContentBoxMainSize
, lineInfo
);
5282 // Cross Size Determination - Flexbox spec section 9.4
5283 // https://drafts.csswg.org/css-flexbox-1/#cross-sizing
5284 // ===================================================
5285 // Calculate the hypothetical cross size of each item:
5287 // 'sumLineCrossSizes' includes the size of all gaps between lines. We
5288 // initialize it with the sum of all the gaps, and add each line's cross size
5289 // at the end of the following for-loop.
5290 nscoord sumLineCrossSizes
= aCrossGapSize
* (flr
.mLines
.Length() - 1);
5291 for (FlexLine
& line
: flr
.mLines
) {
5292 for (FlexItem
& item
: line
.Items()) {
5293 // The item may already have the correct cross-size; only recalculate
5294 // if the item's main size resolution (flexing) could have influenced it:
5295 if (item
.CanMainSizeInfluenceCrossSize()) {
5296 StyleSizeOverrides sizeOverrides
;
5297 if (item
.IsInlineAxisMainAxis()) {
5298 sizeOverrides
.mStyleISize
.emplace(item
.StyleMainSize());
5300 sizeOverrides
.mStyleBSize
.emplace(item
.StyleMainSize());
5302 FLEX_ITEM_LOG(item
.Frame(), "Sizing item in cross axis");
5303 FLEX_LOGV("Main size override: %d", item
.MainSize());
5305 const WritingMode wm
= item
.GetWritingMode();
5306 LogicalSize availSize
= aReflowInput
.ComputedSize(wm
);
5307 availSize
.BSize(wm
) = NS_UNCONSTRAINEDSIZE
;
5308 ReflowInput
childReflowInput(PresContext(), aReflowInput
, item
.Frame(),
5309 availSize
, Nothing(), {}, sizeOverrides
,
5310 {ComputeSizeFlag::ShrinkWrap
});
5311 if (item
.IsBlockAxisMainAxis() && item
.TreatBSizeAsIndefinite()) {
5312 childReflowInput
.mFlags
.mTreatBSizeAsIndefinite
= true;
5315 SizeItemInCrossAxis(childReflowInput
, item
);
5318 // Now that we've finished with this line's items, size the line itself:
5319 line
.ComputeCrossSizeAndBaseline(aAxisTracker
);
5320 sumLineCrossSizes
+= line
.LineCrossSize();
5323 bool isCrossSizeDefinite
;
5324 flr
.mContentBoxCrossSize
= ComputeCrossSize(
5325 aReflowInput
, aAxisTracker
, aTentativeContentBoxCrossSize
,
5326 sumLineCrossSizes
, &isCrossSizeDefinite
);
5328 // Set up state for cross-axis alignment, at a high level (outside the
5329 // scope of a particular flex line)
5330 CrossAxisPositionTracker
crossAxisPosnTracker(
5331 flr
.mLines
, aReflowInput
, flr
.mContentBoxCrossSize
, isCrossSizeDefinite
,
5332 aAxisTracker
, aCrossGapSize
);
5334 // Now that we know the cross size of each line (including
5335 // "align-content:stretch" adjustments, from the CrossAxisPositionTracker
5336 // constructor), we can create struts for any flex items with
5337 // "visibility: collapse" (and restart flex layout).
5338 // Make sure to only do this if we had no struts.
5339 if (aStruts
.IsEmpty() && flr
.mHasCollapsedItems
&&
5340 !StyleVisibility()->UseLegacyCollapseBehavior()) {
5341 BuildStrutInfoFromCollapsedItems(flr
.mLines
, aStruts
);
5342 if (!aStruts
.IsEmpty()) {
5343 // Restart flex layout, using our struts.
5348 // If the flex container is row-oriented, it should derive its first/last
5349 // baseline from the WM-relative startmost/endmost FlexLine if any items in
5350 // the line participate in baseline alignment.
5351 // https://drafts.csswg.org/css-flexbox-1/#flex-baselines
5353 // Initialize the relevant variables here so that we can establish baselines
5354 // while iterating FlexLine later (while crossAxisPosnTracker is conveniently
5355 // pointing at the cross-start edge of that line, which the line's baseline
5356 // offset is measured from).
5357 const FlexLine
* lineForFirstBaseline
= nullptr;
5358 const FlexLine
* lineForLastBaseline
= nullptr;
5359 if (aAxisTracker
.IsRowOriented()) {
5360 lineForFirstBaseline
= &StartmostLine(flr
.mLines
, aAxisTracker
);
5361 lineForLastBaseline
= &EndmostLine(flr
.mLines
, aAxisTracker
);
5363 // For column-oriented flex container, use sentinel value to prompt us to
5364 // get baselines from the startmost/endmost items.
5365 flr
.mAscent
= nscoord_MIN
;
5366 flr
.mAscentForLast
= nscoord_MIN
;
5369 const auto justifyContent
=
5370 IsLegacyBox(aReflowInput
.mFrame
)
5371 ? ConvertLegacyStyleToJustifyContent(StyleXUL())
5372 : aReflowInput
.mStylePosition
->mJustifyContent
;
5375 for (FlexLine
& line
: flr
.mLines
) {
5376 // Main-Axis Alignment - Flexbox spec section 9.5
5377 // https://drafts.csswg.org/css-flexbox-1/#main-alignment
5378 // ==============================================
5379 line
.PositionItemsInMainAxis(justifyContent
, flr
.mContentBoxMainSize
,
5382 // See if we need to extract some computed info for this line.
5383 if (MOZ_UNLIKELY(aContainerInfo
)) {
5384 ComputedFlexLineInfo
& lineInfo
= aContainerInfo
->mLines
[lineIndex
];
5385 lineInfo
.mCrossStart
= crossAxisPosnTracker
.Position();
5388 // Cross-Axis Alignment - Flexbox spec section 9.6
5389 // https://drafts.csswg.org/css-flexbox-1/#cross-alignment
5390 // ===============================================
5391 line
.PositionItemsInCrossAxis(crossAxisPosnTracker
.Position(),
5394 // Flex Container Baselines - Flexbox spec section 8.5
5395 // https://drafts.csswg.org/css-flexbox-1/#flex-baselines
5396 auto ComputeAscentFromLine
= [&](const FlexLine
& aLine
,
5397 BaselineSharingGroup aBaselineGroup
) {
5398 MOZ_ASSERT(aAxisTracker
.IsRowOriented(),
5399 "This makes sense only if we are row-oriented!");
5401 // baselineOffsetInLine is a distance from the line's cross-start edge.
5402 const nscoord baselineOffsetInLine
=
5403 aLine
.ExtractBaselineOffset(aBaselineGroup
);
5405 if (baselineOffsetInLine
== nscoord_MIN
) {
5406 // No "first baseline"-aligned or "last baseline"-aligned items in
5407 // aLine. Return a sentinel value to prompt us to get baseline from the
5408 // startmost or endmost FlexItem after we've reflowed it.
5412 // This "ascent" variable is a distance from the flex container's
5413 // content-box block-start edge.
5414 const nscoord ascent
= aAxisTracker
.LogicalAscentFromFlexRelativeAscent(
5415 crossAxisPosnTracker
.Position() + baselineOffsetInLine
,
5416 flr
.mContentBoxCrossSize
);
5418 // Convert "ascent" variable to a distance from border-box start or end
5419 // edge, per documentation for FlexLayoutResult ascent members.
5420 const auto wm
= aAxisTracker
.GetWritingMode();
5421 if (aBaselineGroup
== BaselineSharingGroup::First
) {
5423 aReflowInput
.ComputedLogicalBorderPadding(wm
).BStart(wm
);
5425 return flr
.mContentBoxCrossSize
- ascent
+
5426 aReflowInput
.ComputedLogicalBorderPadding(wm
).BEnd(wm
);
5429 if (lineForFirstBaseline
&& lineForFirstBaseline
== &line
) {
5430 flr
.mAscent
= ComputeAscentFromLine(line
, BaselineSharingGroup::First
);
5432 if (lineForLastBaseline
&& lineForLastBaseline
== &line
) {
5433 flr
.mAscentForLast
=
5434 ComputeAscentFromLine(line
, BaselineSharingGroup::Last
);
5437 crossAxisPosnTracker
.TraverseLine(line
);
5438 crossAxisPosnTracker
.TraversePackingSpace();
5440 if (&line
!= &flr
.mLines
.LastElement()) {
5441 crossAxisPosnTracker
.TraverseGap();
5449 // This data structure is used in fragmentation, storing the block coordinate
5450 // metrics when reflowing 1) the BStart-most line in each fragment of a
5451 // row-oriented flex container or, 2) the BStart-most item in each fragment of a
5452 // single-line column-oriented flex container.
5454 // When we lay out a row-oriented flex container fragment, its first line might
5455 // contain one or more monolithic items that were pushed from the previous
5456 // fragment specifically to avoid having those monolithic items overlap the
5457 // page/column break. The situation is similar for single-row column-oriented
5458 // flex container fragments, but a bit simpler; only their first item might have
5459 // been pushed to avoid overlapping a page/column break.
5461 // We'll have to place any such pushed items at the block-start edge of the
5462 // current fragment's content-box, which is as close as we can get them to their
5463 // theoretical/unfragmented position (without slicing them); but it does
5464 // represent a shift away from their theoretical/unfragmented position (which
5465 // was somewhere in the previous fragment).
5467 // When that happens, we need to record the maximum such shift that we had to
5468 // perform so that we can apply the same block-endwards shift to "downstream"
5469 // items (items towards the block-end edge) that we could otherwise collide
5470 // with. We also potentially apply the same shift when computing the block-end
5471 // edge of this flex container fragment's content-box so that we don't
5472 // inadvertently shift the last item (or line-of-items) to overlap the flex
5473 // container's border, or content beyond the flex container.
5475 // We use this structure to keep track of several metrics, in service of this
5476 // goal. This structure is also necessary to adjust PerFragmentFlexData at the
5477 // end of ReflowChildren().
5479 // Note: "First" in the struct name means "BStart-most", not the order in the
5480 // flex line array or flex item array.
5481 struct FirstLineOrFirstItemBAxisMetrics final
{
5482 // This value stores the block-end edge shift for 1) the BStart-most line in
5483 // the current fragment of a row-oriented flex container, or 2) the
5484 // BStart-most item in the current fragment of a single-line column-oriented
5485 // flex container. This number is non-negative.
5487 // This value may become positive when any item is a first-in-flow and also
5488 // satisfies either the above condition 1) or 2), since that's a hint that it
5489 // could be monolithic or have a monolithic first descendant, and therefore an
5490 // item that might incur a page/column-break-dodging position-shift that this
5491 // variable needs to track.
5493 // This value also stores the fragmentation-imposed growth in the block-size
5494 // of a) the BStart-most line in the current fragment of a row-oriented flex
5495 // container, or b) the BStart-most item in the current fragment of a
5496 // single-line column-oriented flex container. This number is non-negative.
5497 nscoord mBEndEdgeShift
= 0;
5499 // The first and second value in the pair store the max block-end edges for
5500 // items before and after applying the per-item position-shift in the block
5501 // axis. We only record the block-end edges for items with first-in-flow
5502 // frames placed in the current flex container fragment. This is used only by
5503 // row-oriented flex containers.
5504 Maybe
<std::pair
<nscoord
, nscoord
>> mMaxBEndEdge
;
5507 std::tuple
<nscoord
, nsReflowStatus
> nsFlexContainerFrame::ReflowChildren(
5508 const ReflowInput
& aReflowInput
, const nsSize
& aContainerSize
,
5509 const LogicalSize
& aAvailableSizeForItems
,
5510 const LogicalMargin
& aBorderPadding
, const FlexboxAxisTracker
& aAxisTracker
,
5511 FlexLayoutResult
& aFlr
, PerFragmentFlexData
& aFragmentData
) {
5512 if (HidesContentForLayout()) {
5513 return {0, nsReflowStatus()};
5516 // Before giving each child a final reflow, calculate the origin of the
5517 // flex container's content box (with respect to its border-box), so that
5518 // we can compute our flex item's final positions.
5519 WritingMode flexWM
= aReflowInput
.GetWritingMode();
5520 const LogicalPoint containerContentBoxOrigin
=
5521 aBorderPadding
.StartOffset(flexWM
);
5523 // The block-end of children is relative to the flex container's border-box.
5524 nscoord maxBlockEndEdgeOfChildren
= containerContentBoxOrigin
.B(flexWM
);
5526 FirstLineOrFirstItemBAxisMetrics bAxisMetrics
;
5527 FrameHashtable pushedItems
;
5528 FrameHashtable incompleteItems
;
5529 FrameHashtable overflowIncompleteItems
;
5531 const bool isSingleLine
=
5532 StyleFlexWrap::Nowrap
== aReflowInput
.mStylePosition
->mFlexWrap
;
5533 const FlexLine
& startmostLine
= StartmostLine(aFlr
.mLines
, aAxisTracker
);
5534 const FlexLine
& endmostLine
= EndmostLine(aFlr
.mLines
, aAxisTracker
);
5535 const FlexItem
* startmostItem
=
5536 startmostLine
.IsEmpty() ? nullptr
5537 : &startmostLine
.StartmostItem(aAxisTracker
);
5538 const FlexItem
* endmostItem
=
5539 endmostLine
.IsEmpty() ? nullptr : &endmostLine
.EndmostItem(aAxisTracker
);
5541 bool endmostItemOrLineHasBreakAfter
= false;
5542 // If true, push all remaining flex items to the container's next-in-flow.
5543 bool shouldPushRemainingItems
= false;
5545 // FINAL REFLOW: Give each child frame another chance to reflow.
5546 const size_t numLines
= aFlr
.mLines
.Length();
5547 for (size_t lineIdx
= 0; lineIdx
< numLines
; ++lineIdx
) {
5548 // Iterate flex lines from the startmost to endmost (relative to flex
5549 // container's writing-mode).
5551 aFlr
.mLines
[aAxisTracker
.IsCrossAxisReversed() ? numLines
- lineIdx
- 1
5553 MOZ_ASSERT(lineIdx
!= 0 || &line
== &startmostLine
,
5554 "Logic for finding startmost line should be consistent!");
5556 // These two variables can be set when we are a row-oriented flex container
5557 // during fragmentation.
5558 bool lineHasBreakBefore
= false;
5559 bool lineHasBreakAfter
= false;
5561 const size_t numItems
= line
.Items().Length();
5562 for (size_t itemIdx
= 0; itemIdx
< numItems
; ++itemIdx
) {
5563 // Iterate flex items from the startmost to endmost (relative to flex
5564 // container's writing-mode).
5565 const FlexItem
& item
= line
.Items()[aAxisTracker
.IsMainAxisReversed()
5566 ? numItems
- itemIdx
- 1
5568 MOZ_ASSERT(lineIdx
!= 0 || itemIdx
!= 0 || &item
== startmostItem
,
5569 "Logic for finding startmost item should be consistent!");
5571 LogicalPoint framePos
= aAxisTracker
.LogicalPointFromFlexRelativePoint(
5572 item
.MainPosition(), item
.CrossPosition(), aFlr
.mContentBoxMainSize
,
5573 aFlr
.mContentBoxCrossSize
);
5574 // This variable records the item's block-end edge before we give it a
5575 // per-item-position-shift, if the item is a first-in-flow in the
5576 // startmost line of a row-oriented flex container fragment. It is used to
5577 // determine the block-end edge shift for the startmost line at the end of
5579 Maybe
<nscoord
> frameBPosBeforePerItemShift
;
5581 if (item
.Frame()->GetPrevInFlow()) {
5582 // The item is a continuation. Lay it out at the beginning of the
5584 framePos
.B(flexWM
) = 0;
5585 } else if (GetPrevInFlow()) {
5586 // The item we're placing is not a continuation; though we're placing it
5587 // into a flex container fragment which *is* a continuation. To compute
5588 // the item's correct position in this fragment, we adjust the item's
5589 // theoretical/unfragmented block-direction position by subtracting the
5590 // cumulative content-box block-size for all the previous fragments and
5591 // adding the cumulative block-end edge shift.
5593 // Note that the item's position in this fragment has not been finalized
5594 // yet. At this point, we've adjusted the item's
5595 // theoretical/unfragmented position to be relative to the block-end
5596 // edge of the previous container fragment's content-box. Later, we'll
5597 // compute per-item position-shift to finalize its position.
5598 framePos
.B(flexWM
) -= aFragmentData
.mCumulativeContentBoxBSize
;
5599 framePos
.B(flexWM
) += aFragmentData
.mCumulativeBEndEdgeShift
;
5601 // This helper gets the per-item position-shift in the block-axis.
5602 auto GetPerItemPositionShiftToBEnd
= [&]() {
5603 if (framePos
.B(flexWM
) >= 0) {
5604 // The item final position might be in current flex container
5605 // fragment or in any of the later fragments. No adjustment needed.
5609 // The item's block position is negative, but we want to place it at
5610 // the content-box block-start edge of this container fragment. To
5611 // achieve this, return a negated (positive) value to make the final
5612 // block position zero.
5614 // This scenario occurs when fragmenting a row-oriented flex container
5615 // where this item is pushed to this container fragment.
5616 return -framePos
.B(flexWM
);
5619 if (aAxisTracker
.IsRowOriented()) {
5620 if (&line
== &startmostLine
) {
5621 frameBPosBeforePerItemShift
.emplace(framePos
.B(flexWM
));
5622 framePos
.B(flexWM
) += GetPerItemPositionShiftToBEnd();
5624 // We've computed two things for the startmost line during the outer
5625 // loop's first iteration: 1) how far the block-end edge had to
5626 // shift and 2) how large the block-size needed to grow. Here, we
5627 // just shift all items in the rest of the lines the same amount.
5628 framePos
.B(flexWM
) += bAxisMetrics
.mBEndEdgeShift
;
5631 MOZ_ASSERT(aAxisTracker
.IsColumnOriented());
5633 if (&item
== startmostItem
) {
5634 bAxisMetrics
.mBEndEdgeShift
= GetPerItemPositionShiftToBEnd();
5636 framePos
.B(flexWM
) += bAxisMetrics
.mBEndEdgeShift
;
5638 // Bug 1806717: We need a more sophisticated solution for multi-line
5639 // column-oriented flex container when each line has a different
5640 // position-shift value. For now, we don't shift them.
5645 // Adjust available block-size for the item. (We compute it here because
5646 // framePos is still relative to the container's content-box.)
5648 // Note: The available block-size can become negative if item's
5649 // block-direction position is below available space's block-end.
5650 const nscoord availableBSizeForItem
=
5651 aAvailableSizeForItems
.BSize(flexWM
) == NS_UNCONSTRAINEDSIZE
5652 ? NS_UNCONSTRAINEDSIZE
5653 : aAvailableSizeForItems
.BSize(flexWM
) - framePos
.B(flexWM
);
5655 // Adjust framePos to be relative to the container's border-box
5656 // (i.e. its frame rect), instead of the container's content-box:
5657 framePos
+= containerContentBoxOrigin
;
5659 // Check if we can skip reflowing the item because it will be pushed to
5660 // our next-in-flow -- i.e. if there was a forced break before it, or its
5661 // position is beyond the available space's block-end.
5662 bool itemInPushedItems
= false;
5663 if (shouldPushRemainingItems
) {
5666 "[frag] Item needed to be pushed to container's next-in-flow due "
5667 "to a forced break before it");
5668 pushedItems
.Insert(item
.Frame());
5669 itemInPushedItems
= true;
5670 } else if (availableBSizeForItem
!= NS_UNCONSTRAINEDSIZE
&&
5671 availableBSizeForItem
<= 0) {
5672 // The item's position is beyond the available space, so we have to push
5675 // Note: Even if all of our items are beyond the available space & get
5676 // pushed here, we'll be guaranteed to place at least one of them (and
5677 // make progress) in one of the flex container's *next* fragment. It's
5678 // because ComputeAvailableSizeForItems() always reserves at least 1px
5679 // available block-size for its children, and we consume all available
5680 // block-size and add it to
5681 // PerFragmentFlexData::mCumulativeContentBoxBSize even if we are not
5682 // laying out any child.
5685 "[frag] Item needed to be pushed to container's next-in-flow due "
5686 "to being positioned beyond block-end edge of available space");
5687 pushedItems
.Insert(item
.Frame());
5688 itemInPushedItems
= true;
5689 } else if (item
.NeedsFinalReflow(aReflowInput
)) {
5690 // The available size must be in item's writing-mode.
5691 const WritingMode itemWM
= item
.GetWritingMode();
5692 const auto availableSize
=
5693 LogicalSize(flexWM
, aAvailableSizeForItems
.ISize(flexWM
),
5694 availableBSizeForItem
)
5695 .ConvertTo(itemWM
, flexWM
);
5697 const bool isAdjacentWithBStart
=
5698 framePos
.B(flexWM
) == containerContentBoxOrigin
.B(flexWM
);
5699 const nsReflowStatus childStatus
=
5700 ReflowFlexItem(aAxisTracker
, aReflowInput
, item
, framePos
,
5701 isAdjacentWithBStart
, availableSize
, aContainerSize
);
5703 if (aReflowInput
.IsInFragmentedContext()) {
5704 const bool itemHasBreakBefore
=
5705 item
.Frame()->ShouldBreakBefore(aReflowInput
.mBreakType
) ||
5706 childStatus
.IsInlineBreakBefore();
5707 if (itemHasBreakBefore
) {
5708 if (aAxisTracker
.IsRowOriented()) {
5709 lineHasBreakBefore
= true;
5710 } else if (isSingleLine
) {
5711 if (&item
== startmostItem
) {
5712 if (!GetPrevInFlow() && !aReflowInput
.mFlags
.mIsTopOfPage
) {
5713 // If we are first-in-flow and not at top-of-page, early
5714 // return here to propagate forced break-before from the
5715 // startmost item to the flex container.
5716 nsReflowStatus childrenStatus
;
5717 childrenStatus
.SetInlineLineBreakBeforeAndReset();
5718 return {0, childrenStatus
};
5721 shouldPushRemainingItems
= true;
5724 // Bug 1806717: We haven't implemented fragmentation for
5725 // multi-line column-oriented flex container, so we just ignore
5726 // forced breaks for now.
5731 const bool shouldPushItem
= [&]() {
5732 if (shouldPushRemainingItems
) {
5735 if (availableBSizeForItem
== NS_UNCONSTRAINEDSIZE
) {
5736 // If the available block-size is unconstrained, then we're not
5737 // fragmenting and we don't want to push the item.
5740 if (isAdjacentWithBStart
) {
5741 // The flex item is adjacent with block-start of the container's
5742 // content-box. Don't push it, or we'll trap in an infinite loop.
5745 if (item
.Frame()->BSize() <= availableBSizeForItem
) {
5748 if (aAxisTracker
.IsColumnOriented() &&
5749 item
.Frame()->StyleDisplay()->mBreakBefore
==
5750 StyleBreakBetween::Avoid
) {
5755 if (shouldPushItem
) {
5758 "[frag] Item needed to be pushed to container's next-in-flow "
5759 "because it encounters a forced break before it, or its "
5760 "block-size is larger than the available space");
5761 pushedItems
.Insert(item
.Frame());
5762 itemInPushedItems
= true;
5763 } else if (childStatus
.IsIncomplete()) {
5764 incompleteItems
.Insert(item
.Frame());
5765 } else if (childStatus
.IsOverflowIncomplete()) {
5766 overflowIncompleteItems
.Insert(item
.Frame());
5769 if (aReflowInput
.IsInFragmentedContext()) {
5770 const bool itemHasBreakAfter
=
5771 item
.Frame()->ShouldBreakAfter(aReflowInput
.mBreakType
) ||
5772 childStatus
.IsInlineBreakAfter();
5773 if (itemHasBreakAfter
) {
5774 if (aAxisTracker
.IsRowOriented()) {
5775 lineHasBreakAfter
= true;
5776 } else if (isSingleLine
) {
5777 shouldPushRemainingItems
= true;
5778 if (&item
== endmostItem
) {
5779 endmostItemOrLineHasBreakAfter
= true;
5782 // Bug 1806717: We haven't implemented fragmentation for
5783 // multi-line column-oriented flex container, so we just ignore
5784 // forced breaks for now.
5789 // We already reflowed the item with the right content-box size, so we
5790 // can simply move it into place.
5791 MoveFlexItemToFinalPosition(item
, framePos
, aContainerSize
);
5794 if (!itemInPushedItems
) {
5795 const nscoord borderBoxBSize
= item
.Frame()->BSize(flexWM
);
5796 const nscoord bEndEdgeAfterPerItemShift
=
5797 framePos
.B(flexWM
) + borderBoxBSize
;
5799 // The item (or a fragment thereof) was placed in this flex container
5800 // fragment. Update the max block-end edge with the item's block-end
5802 maxBlockEndEdgeOfChildren
=
5803 std::max(maxBlockEndEdgeOfChildren
, bEndEdgeAfterPerItemShift
);
5805 if (frameBPosBeforePerItemShift
) {
5806 // Make the block-end edge relative to flex container's border-box
5807 // because bEndEdgeAfterPerItemShift is relative to the border-box.
5808 const nscoord bEndEdgeBeforePerItemShift
=
5809 containerContentBoxOrigin
.B(flexWM
) +
5810 *frameBPosBeforePerItemShift
+ borderBoxBSize
;
5812 if (bAxisMetrics
.mMaxBEndEdge
) {
5813 auto& [before
, after
] = *bAxisMetrics
.mMaxBEndEdge
;
5814 before
= std::max(before
, bEndEdgeBeforePerItemShift
);
5815 after
= std::max(after
, bEndEdgeAfterPerItemShift
);
5817 bAxisMetrics
.mMaxBEndEdge
.emplace(bEndEdgeBeforePerItemShift
,
5818 bEndEdgeAfterPerItemShift
);
5822 if (item
.Frame()->GetPrevInFlow()) {
5823 // Items with a previous-continuation may experience some
5824 // fragmentation-imposed growth in their block-size; we compute that
5826 const nscoord bSizeOfThisFragment
=
5827 item
.Frame()->ContentSize(flexWM
).BSize(flexWM
);
5828 const nscoord consumedBSize
= FlexItemConsumedBSize(item
);
5829 const nscoord unfragmentedBSize
= item
.BSize();
5830 nscoord bSizeGrowthOfThisFragment
= 0;
5832 if (consumedBSize
>= unfragmentedBSize
) {
5833 // The item's block-size has been grown to exceed the unfragmented
5834 // block-size in the previous fragments.
5835 bSizeGrowthOfThisFragment
= bSizeOfThisFragment
;
5836 } else if (consumedBSize
+ bSizeOfThisFragment
>= unfragmentedBSize
) {
5837 // The item's block-size just grows in the current fragment to
5838 // exceed the unfragmented block-size.
5839 bSizeGrowthOfThisFragment
=
5840 consumedBSize
+ bSizeOfThisFragment
- unfragmentedBSize
;
5843 if (aAxisTracker
.IsRowOriented()) {
5844 if (&line
== &startmostLine
) {
5845 bAxisMetrics
.mBEndEdgeShift
= std::max(
5846 bAxisMetrics
.mBEndEdgeShift
, bSizeGrowthOfThisFragment
);
5849 MOZ_ASSERT(aAxisTracker
.IsColumnOriented());
5851 if (&item
== startmostItem
) {
5852 MOZ_ASSERT(bAxisMetrics
.mBEndEdgeShift
== 0,
5853 "The item's frame is a continuation, so it "
5854 "shouldn't shift!");
5855 bAxisMetrics
.mBEndEdgeShift
= bSizeGrowthOfThisFragment
;
5858 // Bug 1806717: We need a more sophisticated solution for
5859 // multi-line column-oriented flex container when each line has a
5860 // different block-size growth value. For now, we don't deal with
5867 // If the item has auto margins, and we were tracking the UsedMargin
5868 // property, set the property to the computed margin values.
5869 if (item
.HasAnyAutoMargin()) {
5870 nsMargin
* propValue
=
5871 item
.Frame()->GetProperty(nsIFrame::UsedMarginProperty());
5873 *propValue
= item
.PhysicalMargin();
5878 if (aReflowInput
.IsInFragmentedContext() && aAxisTracker
.IsRowOriented()) {
5879 // Propagate forced break values from the flex items to its flex line.
5880 if (lineHasBreakBefore
) {
5881 if (&line
== &startmostLine
) {
5882 if (!GetPrevInFlow() && !aReflowInput
.mFlags
.mIsTopOfPage
) {
5883 // If we are first-in-flow and not at top-of-page, early return here
5884 // to propagate forced break-before from the startmost line to the
5886 nsReflowStatus childrenStatus
;
5887 childrenStatus
.SetInlineLineBreakBeforeAndReset();
5888 return {0, childrenStatus
};
5891 // Current non-startmost line has forced break-before, so push all the
5892 // items in this line.
5893 for (const FlexItem
& item
: line
.Items()) {
5894 pushedItems
.Insert(item
.Frame());
5895 incompleteItems
.Remove(item
.Frame());
5896 overflowIncompleteItems
.Remove(item
.Frame());
5898 shouldPushRemainingItems
= true;
5901 if (lineHasBreakAfter
) {
5902 shouldPushRemainingItems
= true;
5903 if (&line
== &endmostLine
) {
5904 endmostItemOrLineHasBreakAfter
= true;
5909 // Now we've finished processing all the items in the startmost line.
5910 // Determine the amount by which the startmost line's block-end edge has
5911 // shifted, so we can apply the same shift for the remaining lines.
5912 if (GetPrevInFlow() && aAxisTracker
.IsRowOriented() &&
5913 &line
== &startmostLine
&& bAxisMetrics
.mMaxBEndEdge
) {
5914 auto& [before
, after
] = *bAxisMetrics
.mMaxBEndEdge
;
5915 bAxisMetrics
.mBEndEdgeShift
=
5916 std::max(bAxisMetrics
.mBEndEdgeShift
, after
- before
);
5920 if (!aFlr
.mPlaceholders
.IsEmpty()) {
5921 ReflowPlaceholders(aReflowInput
, aFlr
.mPlaceholders
,
5922 containerContentBoxOrigin
, aContainerSize
);
5925 nsReflowStatus childrenStatus
;
5926 if (!pushedItems
.IsEmpty() || !incompleteItems
.IsEmpty()) {
5927 childrenStatus
.SetIncomplete();
5928 } else if (!overflowIncompleteItems
.IsEmpty()) {
5929 childrenStatus
.SetOverflowIncomplete();
5930 } else if (endmostItemOrLineHasBreakAfter
) {
5931 childrenStatus
.SetInlineLineBreakAfter();
5933 PushIncompleteChildren(pushedItems
, incompleteItems
, overflowIncompleteItems
);
5935 // TODO: Try making this a fatal assertion after we fix bug 1751260.
5936 NS_ASSERTION(childrenStatus
.IsFullyComplete() ||
5937 aAvailableSizeForItems
.BSize(flexWM
) != NS_UNCONSTRAINEDSIZE
,
5938 "We shouldn't have any incomplete children if the available "
5939 "block-size is unconstrained!");
5941 if (!pushedItems
.IsEmpty()) {
5942 AddStateBits(NS_STATE_FLEX_DID_PUSH_ITEMS
);
5945 if (GetPrevInFlow()) {
5946 aFragmentData
.mCumulativeBEndEdgeShift
+= bAxisMetrics
.mBEndEdgeShift
;
5949 return {maxBlockEndEdgeOfChildren
, childrenStatus
};
5952 void nsFlexContainerFrame::PopulateReflowOutput(
5953 ReflowOutput
& aReflowOutput
, const ReflowInput
& aReflowInput
,
5954 nsReflowStatus
& aStatus
, const LogicalSize
& aContentBoxSize
,
5955 const LogicalMargin
& aBorderPadding
, const nscoord aConsumedBSize
,
5956 const bool aMayNeedNextInFlow
, const nscoord aMaxBlockEndEdgeOfChildren
,
5957 const nsReflowStatus
& aChildrenStatus
,
5958 const FlexboxAxisTracker
& aAxisTracker
, FlexLayoutResult
& aFlr
) {
5959 const WritingMode flexWM
= aReflowInput
.GetWritingMode();
5961 // Compute flex container's desired size (in its own writing-mode).
5962 LogicalSize
desiredSizeInFlexWM(flexWM
);
5963 desiredSizeInFlexWM
.ISize(flexWM
) =
5964 aContentBoxSize
.ISize(flexWM
) + aBorderPadding
.IStartEnd(flexWM
);
5966 // Unconditionally skip adding block-end border and padding for now. We add it
5967 // lower down, after we've established baseline and decided whether bottom
5968 // border-padding fits (if we're fragmented).
5969 const nscoord effectiveContentBSizeWithBStartBP
=
5970 aContentBoxSize
.BSize(flexWM
) - aConsumedBSize
+
5971 aBorderPadding
.BStart(flexWM
);
5972 nscoord blockEndContainerBP
= aBorderPadding
.BEnd(flexWM
);
5974 if (aMayNeedNextInFlow
) {
5975 // We assume our status should be reported as incomplete because we may need
5977 bool isStatusIncomplete
= true;
5979 const nscoord availableBSizeMinusBEndBP
=
5980 aReflowInput
.AvailableBSize() - aBorderPadding
.BEnd(flexWM
);
5982 if (aMaxBlockEndEdgeOfChildren
<= availableBSizeMinusBEndBP
) {
5983 // Consume all the available block-size.
5984 desiredSizeInFlexWM
.BSize(flexWM
) = availableBSizeMinusBEndBP
;
5986 // This case happens if we have some tall unbreakable children exceeding
5987 // the available block-size.
5988 desiredSizeInFlexWM
.BSize(flexWM
) = std::min(
5989 effectiveContentBSizeWithBStartBP
, aMaxBlockEndEdgeOfChildren
);
5991 if ((aReflowInput
.ComputedBSize() != NS_UNCONSTRAINEDSIZE
||
5992 aChildrenStatus
.IsFullyComplete()) &&
5993 aMaxBlockEndEdgeOfChildren
>= effectiveContentBSizeWithBStartBP
) {
5994 // We have some tall unbreakable child that's sticking off the end of
5995 // our fragment, *and* forcing us to consume all of our remaining
5996 // content block-size and call ourselves complete.
5998 // - If we have a definite block-size: we get here if the tall child
5999 // makes us reach that block-size.
6000 // - If we have a content-based block-size: we get here if the tall
6001 // child makes us reach the content-based block-size from a
6002 // theoretical unfragmented layout, *and* all our children are
6003 // complete. (Note that if we have some incomplete child, then we
6004 // instead prefer to return an incomplete status, so we can get a
6005 // next-in-flow to include that child's requested next-in-flow, in the
6006 // spirit of having a block-size that fits the content.)
6008 // TODO: the auto-height case might need more subtlety; see bug 1828977.
6009 isStatusIncomplete
= false;
6011 // We also potentially need to get the unskipped block-end border and
6012 // padding (if we assumed it'd be skipped as part of our tentative
6013 // assumption that we'd be incomplete).
6014 if (aReflowInput
.mStyleBorder
->mBoxDecorationBreak
==
6015 StyleBoxDecorationBreak::Slice
) {
6016 blockEndContainerBP
=
6017 aReflowInput
.ComputedLogicalBorderPadding(flexWM
).BEnd(flexWM
);
6022 if (isStatusIncomplete
) {
6023 aStatus
.SetIncomplete();
6026 // Our own effective content-box block-size can fit within the available
6028 desiredSizeInFlexWM
.BSize(flexWM
) = effectiveContentBSizeWithBStartBP
;
6031 // Now, we account for how the block-end border and padding (if any) impacts
6032 // our desired size. If adding it pushes us over the available block-size,
6033 // then we become incomplete (unless we already weren't asking for any
6034 // block-size, in which case we stay complete to avoid looping forever).
6036 // NOTE: If we have auto block-size, we allow our block-end border and padding
6037 // to push us over the available block-size without requesting a continuation,
6038 // for consistency with the behavior of "display:block" elements.
6039 const nscoord effectiveContentBSizeWithBStartEndBP
=
6040 desiredSizeInFlexWM
.BSize(flexWM
) + blockEndContainerBP
;
6042 if (aReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
&&
6043 effectiveContentBSizeWithBStartEndBP
> aReflowInput
.AvailableBSize() &&
6044 desiredSizeInFlexWM
.BSize(flexWM
) != 0 &&
6045 aReflowInput
.ComputedBSize() != NS_UNCONSTRAINEDSIZE
) {
6046 // We couldn't fit with the block-end border and padding included, so we'll
6047 // need a continuation.
6048 aStatus
.SetIncomplete();
6050 if (aReflowInput
.mStyleBorder
->mBoxDecorationBreak
==
6051 StyleBoxDecorationBreak::Slice
) {
6052 blockEndContainerBP
= 0;
6056 // The variable "blockEndContainerBP" now accurately reflects how much (if
6057 // any) block-end border and padding we want for this frame, so we can proceed
6059 desiredSizeInFlexWM
.BSize(flexWM
) += blockEndContainerBP
;
6061 if (aStatus
.IsComplete() && !aChildrenStatus
.IsFullyComplete()) {
6062 aStatus
.SetOverflowIncomplete();
6063 aStatus
.SetNextInFlowNeedsReflow();
6066 // If we are the first-in-flow and not fully complete (either our block-size
6067 // or any of our flex items cannot fit in the available block-size), and the
6068 // style requires us to avoid breaking inside, set the status to prompt our
6069 // parent to push us to the next page/column.
6070 if (!GetPrevInFlow() && !aStatus
.IsFullyComplete() &&
6071 ShouldAvoidBreakInside(aReflowInput
)) {
6072 aStatus
.SetInlineLineBreakBeforeAndReset();
6076 // Propagate forced break values from flex items or flex lines.
6077 if (aChildrenStatus
.IsInlineBreakBefore()) {
6078 aStatus
.SetInlineLineBreakBeforeAndReset();
6080 if (aChildrenStatus
.IsInlineBreakAfter()) {
6081 aStatus
.SetInlineLineBreakAfter();
6084 // If we haven't established a baseline for the container yet, i.e. if we
6085 // don't have any flex item in the startmost flex line that participates in
6086 // baseline alignment, then use the startmost flex item to derive the
6087 // container's baseline.
6088 if (const FlexLine
& line
= StartmostLine(aFlr
.mLines
, aAxisTracker
);
6089 aFlr
.mAscent
== nscoord_MIN
&& !line
.IsEmpty()) {
6090 const FlexItem
& item
= line
.StartmostItem(aAxisTracker
);
6091 aFlr
.mAscent
= item
.Frame()
6092 ->GetLogicalPosition(
6093 flexWM
, desiredSizeInFlexWM
.GetPhysicalSize(flexWM
))
6095 item
.ResolvedAscent(true);
6098 // Likewise, if we don't have any flex item in the endmost flex line that
6099 // participates in last baseline alignment, then use the endmost flex item to
6100 // derived the container's last baseline.
6101 if (const FlexLine
& line
= EndmostLine(aFlr
.mLines
, aAxisTracker
);
6102 aFlr
.mAscentForLast
== nscoord_MIN
&& !line
.IsEmpty()) {
6103 const FlexItem
& item
= line
.EndmostItem(aAxisTracker
);
6104 const nscoord lastAscent
=
6106 ->GetLogicalPosition(flexWM
,
6107 desiredSizeInFlexWM
.GetPhysicalSize(flexWM
))
6109 item
.ResolvedAscent(false);
6111 aFlr
.mAscentForLast
= desiredSizeInFlexWM
.BSize(flexWM
) - lastAscent
;
6114 if (aFlr
.mAscent
== nscoord_MIN
) {
6115 // Still don't have our baseline set -- this happens if we have no
6116 // children, if our children are huge enough that they have nscoord_MIN
6117 // as their baseline, or our content is hidden in which case, we'll use the
6118 // wrong baseline (but no big deal).
6119 NS_WARNING_ASSERTION(
6120 HidesContentForLayout() || aFlr
.mLines
[0].IsEmpty(),
6121 "Have flex items but didn't get an ascent - that's odd (or there are "
6122 "just gigantic sizes involved)");
6123 // Per spec, synthesize baseline from the flex container's content box
6124 // (i.e. use block-end side of content-box)
6125 // XXXdholbert This only makes sense if parent's writing mode is
6126 // horizontal (& even then, really we should be using the BSize in terms
6127 // of the parent's writing mode, not ours). Clean up in bug 1155322.
6128 aFlr
.mAscent
= effectiveContentBSizeWithBStartBP
;
6131 if (aFlr
.mAscentForLast
== nscoord_MIN
) {
6132 // Still don't have our last baseline set -- this happens if we have no
6133 // children, if our children are huge enough that they have nscoord_MIN
6134 // as their baseline, or our content is hidden in which case, we'll use the
6135 // wrong baseline (but no big deal).
6136 NS_WARNING_ASSERTION(
6137 HidesContentForLayout() || aFlr
.mLines
[0].IsEmpty(),
6138 "Have flex items but didn't get an ascent - that's odd (or there are "
6139 "just gigantic sizes involved)");
6140 // Per spec, synthesize baseline from the flex container's content box
6141 // (i.e. use block-end side of content-box)
6142 // XXXdholbert This only makes sense if parent's writing mode is
6143 // horizontal (& even then, really we should be using the BSize in terms
6144 // of the parent's writing mode, not ours). Clean up in bug 1155322.
6145 aFlr
.mAscentForLast
= blockEndContainerBP
;
6148 if (HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE
)) {
6149 // This will force our parent to call GetLogicalBaseline, which will
6150 // synthesize a margin-box baseline.
6151 aReflowOutput
.SetBlockStartAscent(ReflowOutput::ASK_FOR_BASELINE
);
6153 // XXXdholbert aFlr.mAscent needs to be in terms of our parent's
6154 // writing-mode here. See bug 1155322.
6155 aReflowOutput
.SetBlockStartAscent(aFlr
.mAscent
);
6158 // Cache the container baselines so that our parent can baseline-align us.
6159 mFirstBaseline
= aFlr
.mAscent
;
6160 mLastBaseline
= aFlr
.mAscentForLast
;
6162 // Convert flex container's final desired size to parent's WM, for outparam.
6163 aReflowOutput
.SetSize(flexWM
, desiredSizeInFlexWM
);
6166 void nsFlexContainerFrame::MoveFlexItemToFinalPosition(
6167 const FlexItem
& aItem
, const LogicalPoint
& aFramePos
,
6168 const nsSize
& aContainerSize
) {
6169 const WritingMode outerWM
= aItem
.ContainingBlockWM();
6170 const nsStyleDisplay
* display
= aItem
.Frame()->StyleDisplay();
6171 LogicalPoint
pos(aFramePos
);
6172 if (display
->IsRelativelyOrStickyPositionedStyle()) {
6173 // If the item is relatively positioned, look up its offsets (cached from
6174 // previous reflow). A sticky positioned item can pass a dummy
6175 // logicalOffsets into ApplyRelativePositioning().
6176 LogicalMargin
logicalOffsets(outerWM
);
6177 if (display
->IsRelativelyPositionedStyle()) {
6178 nsMargin
* cachedOffsets
=
6179 aItem
.Frame()->GetProperty(nsIFrame::ComputedOffsetProperty());
6182 "relpos previously-reflowed frame should've cached its offsets");
6183 logicalOffsets
= LogicalMargin(outerWM
, *cachedOffsets
);
6185 ReflowInput::ApplyRelativePositioning(aItem
.Frame(), outerWM
,
6186 logicalOffsets
, &pos
, aContainerSize
);
6189 FLEX_ITEM_LOG(aItem
.Frame(), "Moving item to its desired position %s",
6190 ToString(pos
).c_str());
6191 aItem
.Frame()->SetPosition(outerWM
, pos
, aContainerSize
);
6192 PositionFrameView(aItem
.Frame());
6193 PositionChildViews(aItem
.Frame());
6196 nsReflowStatus
nsFlexContainerFrame::ReflowFlexItem(
6197 const FlexboxAxisTracker
& aAxisTracker
, const ReflowInput
& aReflowInput
,
6198 const FlexItem
& aItem
, const LogicalPoint
& aFramePos
,
6199 const bool aIsAdjacentWithBStart
, const LogicalSize
& aAvailableSize
,
6200 const nsSize
& aContainerSize
) {
6201 FLEX_ITEM_LOG(aItem
.Frame(), "Doing final reflow");
6203 // Returns true if we should use 'auto' in block axis's StyleSizeOverrides to
6204 // allow fragmentation-imposed block-size growth.
6205 auto ComputeBSizeOverrideWithAuto
= [&]() {
6206 if (!aReflowInput
.IsInFragmentedContext()) {
6209 if (aItem
.Frame()->IsReplaced()) {
6210 // Disallow fragmentation-imposed block-size growth for replaced elements
6211 // since they are monolithic, and cannot be fragmented.
6214 if (aItem
.HasAspectRatio()) {
6215 // Aspect-ratio's automatic content-based minimum size doesn't work
6216 // properly in a fragmented context (Bug 1868284) when we use 'auto'
6217 // block-size to apply the fragmentation-imposed block-size growth.
6218 // Disable it for now so that items with aspect-ratios can still use their
6219 // known block-sizes (from flex layout algorithm) in final reflow.
6222 if (aItem
.IsBlockAxisMainAxis()) {
6223 if (aItem
.IsFlexBaseSizeContentBSize()) {
6224 // The flex item resolved its indefinite flex-basis to the content
6226 if (aItem
.IsMainMinSizeContentBSize()) {
6227 // The item's flex base size and main min-size are both content
6228 // block-size. We interpret this content-based block-size as
6229 // permission to apply fragmentation-imposed block-size growth.
6232 if (aReflowInput
.ComputedBSize() == NS_UNCONSTRAINEDSIZE
) {
6233 // The flex container has an indefinite block-size. We allow the
6234 // item's to apply fragmentation-imposed block-size growth.
6241 MOZ_ASSERT(aItem
.IsBlockAxisCrossAxis());
6242 MOZ_ASSERT(aItem
.IsStretched(),
6243 "No need to override block-size with 'auto' if the item is not "
6244 "stretched in the cross axis!");
6246 Maybe
<nscoord
> measuredBSize
= aItem
.MeasuredBSize();
6247 if (measuredBSize
&& aItem
.CrossSize() == *measuredBSize
) {
6248 // The item has a measured content-based block-size due to having an
6249 // indefinite cross-size. If its cross-size is equal to the content-based
6250 // block-size, then it is the tallest item that established the cross-size
6251 // of the flex line. We allow it apply fragmentation-imposed block-size
6254 // Note: We only allow the tallest item to grow because it is likely to
6255 // have the most impact on the overall flex container block-size growth.
6256 // This is not a perfect solution since other shorter items in the same
6257 // line might also have fragmentation-imposed block-size growth, but
6258 // currently there is no reliable way to detect whether they will outgrow
6259 // the tallest item.
6265 StyleSizeOverrides sizeOverrides
;
6266 bool overrideBSizeWithAuto
= false;
6268 // Override flex item's main size.
6269 if (aItem
.IsInlineAxisMainAxis()) {
6270 sizeOverrides
.mStyleISize
.emplace(aItem
.StyleMainSize());
6271 FLEX_LOGV("Main size (inline-size) override: %d", aItem
.MainSize());
6273 overrideBSizeWithAuto
= ComputeBSizeOverrideWithAuto();
6274 if (overrideBSizeWithAuto
) {
6275 sizeOverrides
.mStyleBSize
.emplace(StyleSize::Auto());
6276 FLEX_LOGV("Main size (block-size) override: Auto");
6278 sizeOverrides
.mStyleBSize
.emplace(aItem
.StyleMainSize());
6279 FLEX_LOGV("Main size (block-size) override: %d", aItem
.MainSize());
6283 // Override flex item's cross size if it was stretched in the cross axis (in
6284 // which case we're imposing a cross size).
6285 if (aItem
.IsStretched()) {
6286 if (aItem
.IsInlineAxisCrossAxis()) {
6287 sizeOverrides
.mStyleISize
.emplace(aItem
.StyleCrossSize());
6288 FLEX_LOGV("Cross size (inline-size) override: %d", aItem
.CrossSize());
6290 overrideBSizeWithAuto
= ComputeBSizeOverrideWithAuto();
6291 if (overrideBSizeWithAuto
) {
6292 sizeOverrides
.mStyleBSize
.emplace(StyleSize::Auto());
6293 FLEX_LOGV("Cross size (block-size) override: Auto");
6295 sizeOverrides
.mStyleBSize
.emplace(aItem
.StyleCrossSize());
6296 FLEX_LOGV("Cross size (block-size) override: %d", aItem
.CrossSize());
6300 if (sizeOverrides
.mStyleBSize
) {
6301 // We are overriding the block-size. For robustness, we always assume that
6302 // this represents a block-axis resize for the frame. This may be
6303 // conservative, but we do capture all the conditions in the block-axis
6304 // (checked in NeedsFinalReflow()) that make this item require a final
6305 // reflow. This sets relevant flags in ReflowInput::InitResizeFlags().
6306 aItem
.Frame()->SetHasBSizeChange(true);
6309 ReflowInput
childReflowInput(PresContext(), aReflowInput
, aItem
.Frame(),
6310 aAvailableSize
, Nothing(), {}, sizeOverrides
,
6311 {ComputeSizeFlag::ShrinkWrap
});
6312 if (overrideBSizeWithAuto
) {
6313 // If we use 'auto' to override the item's block-size, set the item's
6314 // original block-size to min-size as a lower bound.
6315 childReflowInput
.SetComputedMinBSize(aItem
.BSize());
6317 // Set the item's block-size as the percentage basis so that its children
6318 // can resolve percentage sizes correctly.
6319 childReflowInput
.SetPercentageBasisInBlockAxis(aItem
.BSize());
6322 if (aItem
.TreatBSizeAsIndefinite() && aItem
.IsBlockAxisMainAxis()) {
6323 childReflowInput
.mFlags
.mTreatBSizeAsIndefinite
= true;
6326 if (aItem
.IsStretched() && aItem
.IsBlockAxisCrossAxis()) {
6327 // This item is stretched (in the cross axis), and that axis is its block
6328 // axis. That stretching effectively gives it a relative BSize.
6329 // XXXdholbert This flag only makes a difference if we use the flex items'
6330 // frame-state when deciding whether to reflow them -- and we don't, as of
6331 // the changes in bug 851607. So this has no effect right now, but it might
6332 // make a difference if we optimize to use dirty bits in the
6333 // future. (Reftests flexbox-resizeviewport-1.xhtml and -2.xhtml are
6334 // intended to catch any regressions here, if we end up relying on this bit
6335 // & neglecting to set it.)
6336 aItem
.Frame()->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE
);
6339 if (!aIsAdjacentWithBStart
) {
6340 // mIsTopOfPage bit in childReflowInput is carried over from aReflowInput.
6341 // However, if this item's position is not adjacent with the flex
6342 // container's content-box block-start edge, we should clear it.
6343 childReflowInput
.mFlags
.mIsTopOfPage
= false;
6346 // NOTE: Be very careful about doing anything else with childReflowInput
6347 // after this point, because some of its methods (e.g. SetComputedWidth)
6348 // internally call InitResizeFlags and stomp on mVResize & mHResize.
6350 FLEX_ITEM_LOG(aItem
.Frame(), "Reflowing item at its desired position %s",
6351 ToString(aFramePos
).c_str());
6353 // CachedFlexItemData is stored in item's writing mode, so we pass
6354 // aChildReflowInput into ReflowOutput's constructor.
6355 ReflowOutput
childReflowOutput(childReflowInput
);
6356 nsReflowStatus childStatus
;
6357 WritingMode outerWM
= aReflowInput
.GetWritingMode();
6358 ReflowChild(aItem
.Frame(), PresContext(), childReflowOutput
, childReflowInput
,
6359 outerWM
, aFramePos
, aContainerSize
, ReflowChildFlags::Default
,
6362 // XXXdholbert Perhaps we should call CheckForInterrupt here; see bug 1495532.
6364 FinishReflowChild(aItem
.Frame(), PresContext(), childReflowOutput
,
6365 &childReflowInput
, outerWM
, aFramePos
, aContainerSize
,
6366 ReflowChildFlags::ApplyRelativePositioning
);
6368 aItem
.SetAscent(childReflowOutput
.BlockStartAscent());
6370 // Update our cached flex item info:
6371 if (auto* cached
= aItem
.Frame()->GetProperty(CachedFlexItemData::Prop())) {
6372 cached
->Update(childReflowInput
, childReflowOutput
,
6373 FlexItemReflowType::Final
);
6375 cached
= new CachedFlexItemData(childReflowInput
, childReflowOutput
,
6376 FlexItemReflowType::Final
);
6377 aItem
.Frame()->SetProperty(CachedFlexItemData::Prop(), cached
);
6383 void nsFlexContainerFrame::ReflowPlaceholders(
6384 const ReflowInput
& aReflowInput
, nsTArray
<nsIFrame
*>& aPlaceholders
,
6385 const LogicalPoint
& aContentBoxOrigin
, const nsSize
& aContainerSize
) {
6386 WritingMode outerWM
= aReflowInput
.GetWritingMode();
6388 // As noted in this method's documentation, we'll reflow every entry in
6389 // |aPlaceholders| at the container's content-box origin.
6390 for (nsIFrame
* placeholder
: aPlaceholders
) {
6391 MOZ_ASSERT(placeholder
->IsPlaceholderFrame(),
6392 "placeholders array should only contain placeholder frames");
6393 WritingMode wm
= placeholder
->GetWritingMode();
6394 LogicalSize availSize
= aReflowInput
.ComputedSize(wm
);
6395 ReflowInput
childReflowInput(PresContext(), aReflowInput
, placeholder
,
6397 // No need to set the -webkit-line-clamp related flags when reflowing
6399 ReflowOutput
childReflowOutput(outerWM
);
6400 nsReflowStatus childStatus
;
6401 ReflowChild(placeholder
, PresContext(), childReflowOutput
, childReflowInput
,
6402 outerWM
, aContentBoxOrigin
, aContainerSize
,
6403 ReflowChildFlags::Default
, childStatus
);
6405 FinishReflowChild(placeholder
, PresContext(), childReflowOutput
,
6406 &childReflowInput
, outerWM
, aContentBoxOrigin
,
6407 aContainerSize
, ReflowChildFlags::Default
);
6409 // Mark the placeholder frame to indicate that it's not actually at the
6410 // element's static position, because we need to apply CSS Alignment after
6411 // we determine the OOF's size:
6412 placeholder
->AddStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN
);
6416 nscoord
nsFlexContainerFrame::IntrinsicISize(gfxContext
* aRenderingContext
,
6417 IntrinsicISizeType aType
) {
6418 if (Maybe
<nscoord
> containISize
= ContainIntrinsicISize()) {
6419 return *containISize
;
6422 nscoord containerISize
= 0;
6423 const nsStylePosition
* stylePos
= StylePosition();
6424 const FlexboxAxisTracker
axisTracker(this);
6426 nscoord mainGapSize
;
6427 if (axisTracker
.IsRowOriented()) {
6428 mainGapSize
= nsLayoutUtils::ResolveGapToLength(stylePos
->mColumnGap
,
6429 NS_UNCONSTRAINEDSIZE
);
6431 mainGapSize
= nsLayoutUtils::ResolveGapToLength(stylePos
->mRowGap
,
6432 NS_UNCONSTRAINEDSIZE
);
6435 const bool useMozBoxCollapseBehavior
=
6436 StyleVisibility()->UseLegacyCollapseBehavior();
6438 // The loop below sets aside space for a gap before each item besides the
6439 // first. This bool helps us handle that special-case.
6440 bool onFirstChild
= true;
6442 for (nsIFrame
* childFrame
: mFrames
) {
6443 // Skip out-of-flow children because they don't participate in flex layout.
6444 if (childFrame
->IsPlaceholderFrame()) {
6448 if (useMozBoxCollapseBehavior
&&
6449 childFrame
->StyleVisibility()->IsCollapse()) {
6450 // If we're using legacy "visibility:collapse" behavior, then we don't
6451 // care about the sizes of any collapsed children.
6455 nscoord childISize
= nsLayoutUtils::IntrinsicForContainer(
6456 aRenderingContext
, childFrame
, aType
);
6458 // * For a row-oriented single-line flex container, the intrinsic
6459 // {min/pref}-isize is the sum of its items' {min/pref}-isizes and
6460 // (n-1) column gaps.
6461 // * For a column-oriented flex container, the intrinsic min isize
6462 // is the max of its items' min isizes.
6463 // * For a row-oriented multi-line flex container, the intrinsic
6464 // pref isize is former (sum), and its min isize is the latter (max).
6465 bool isSingleLine
= (StyleFlexWrap::Nowrap
== stylePos
->mFlexWrap
);
6466 if (axisTracker
.IsRowOriented() &&
6467 (isSingleLine
|| aType
== IntrinsicISizeType::PrefISize
)) {
6468 containerISize
+= childISize
;
6469 if (!onFirstChild
) {
6470 containerISize
+= mainGapSize
;
6472 onFirstChild
= false;
6473 } else { // (col-oriented, or MinISize for multi-line row flex container)
6474 containerISize
= std::max(containerISize
, childISize
);
6478 return containerISize
;
6482 nscoord
nsFlexContainerFrame::GetMinISize(gfxContext
* aRenderingContext
) {
6483 if (mCachedMinISize
== NS_INTRINSIC_ISIZE_UNKNOWN
) {
6485 IntrinsicISize(aRenderingContext
, IntrinsicISizeType::MinISize
);
6488 return mCachedMinISize
;
6492 nscoord
nsFlexContainerFrame::GetPrefISize(gfxContext
* aRenderingContext
) {
6493 if (mCachedPrefISize
== NS_INTRINSIC_ISIZE_UNKNOWN
) {
6495 IntrinsicISize(aRenderingContext
, IntrinsicISizeType::PrefISize
);
6498 return mCachedPrefISize
;
6501 int32_t nsFlexContainerFrame::GetNumLines() const {
6502 // TODO(emilio, bug 1793251): Treating all row oriented frames as single-lines
6503 // might not be great for flex-wrap'd containers, consider trying to do
6504 // better? We probably would need to persist more stuff than we do after
6506 return FlexboxAxisInfo(this).mIsRowOriented
? 1 : mFrames
.GetLength();
6509 bool nsFlexContainerFrame::IsLineIteratorFlowRTL() {
6510 FlexboxAxisInfo
info(this);
6511 if (info
.mIsRowOriented
) {
6512 const bool isRtl
= StyleVisibility()->mDirection
== StyleDirection::Rtl
;
6513 return info
.mIsMainAxisReversed
!= isRtl
;
6518 Result
<nsILineIterator::LineInfo
, nsresult
> nsFlexContainerFrame::GetLine(
6519 int32_t aLineNumber
) {
6520 if (aLineNumber
< 0 || aLineNumber
>= GetNumLines()) {
6521 return Err(NS_ERROR_FAILURE
);
6523 FlexboxAxisInfo
info(this);
6525 if (info
.mIsRowOriented
) {
6526 lineInfo
.mLineBounds
= GetRect();
6527 lineInfo
.mFirstFrameOnLine
= mFrames
.FirstChild();
6528 // This isn't quite ideal for multi-line row flexbox, see bug 1793251.
6529 lineInfo
.mNumFramesOnLine
= mFrames
.GetLength();
6531 // TODO(emilio, bug 1793322): Deal with column-reverse (mIsMainAxisReversed)
6532 nsIFrame
* f
= mFrames
.FrameAt(aLineNumber
);
6533 lineInfo
.mLineBounds
= f
->GetRect();
6534 lineInfo
.mFirstFrameOnLine
= f
;
6535 lineInfo
.mNumFramesOnLine
= 1;
6540 int32_t nsFlexContainerFrame::FindLineContaining(nsIFrame
* aFrame
,
6541 int32_t aStartLine
) {
6542 const int32_t index
= mFrames
.IndexOf(aFrame
);
6546 const FlexboxAxisInfo
info(this);
6547 if (info
.mIsRowOriented
) {
6550 if (index
< aStartLine
) {
6557 nsFlexContainerFrame::CheckLineOrder(int32_t aLine
, bool* aIsReordered
,
6558 nsIFrame
** aFirstVisual
,
6559 nsIFrame
** aLastVisual
) {
6560 *aIsReordered
= false;
6561 *aFirstVisual
= nullptr;
6562 *aLastVisual
= nullptr;
6567 nsFlexContainerFrame::FindFrameAt(int32_t aLineNumber
, nsPoint aPos
,
6568 nsIFrame
** aFrameFound
,
6569 bool* aPosIsBeforeFirstFrame
,
6570 bool* aPosIsAfterLastFrame
) {
6571 const auto wm
= GetWritingMode();
6572 const LogicalPoint
pos(wm
, aPos
, GetSize());
6573 const FlexboxAxisInfo
info(this);
6575 *aFrameFound
= nullptr;
6576 *aPosIsBeforeFirstFrame
= true;
6577 *aPosIsAfterLastFrame
= false;
6579 if (!info
.mIsRowOriented
) {
6580 nsIFrame
* f
= mFrames
.FrameAt(aLineNumber
);
6585 auto rect
= f
->GetLogicalRect(wm
, GetSize());
6587 *aPosIsBeforeFirstFrame
= pos
.I(wm
) < rect
.IStart(wm
);
6588 *aPosIsAfterLastFrame
= pos
.I(wm
) > rect
.IEnd(wm
);
6592 LineFrameFinder
finder(aPos
, GetSize(), GetWritingMode(),
6593 IsLineIteratorFlowRTL());
6594 for (nsIFrame
* f
: mFrames
) {
6596 if (finder
.IsDone()) {
6600 finder
.Finish(aFrameFound
, aPosIsBeforeFirstFrame
, aPosIsAfterLastFrame
);