Bug 1895153 - Implement "Find in page..." menu functionality r=android-reviewers...
[gecko.git] / layout / generic / nsFlexContainerFrame.cpp
blob81b9fed9189e44f0da4244e3eafb8a8e6022a4ae
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* rendering object for CSS "display: flex" */
9 #include "nsFlexContainerFrame.h"
11 #include <algorithm>
13 #include "gfxContext.h"
14 #include "mozilla/Baseline.h"
15 #include "mozilla/ComputedStyle.h"
16 #include "mozilla/CSSOrderAwareFrameIterator.h"
17 #include "mozilla/Logging.h"
18 #include "mozilla/PresShell.h"
19 #include "mozilla/StaticPrefs_layout.h"
20 #include "mozilla/WritingModes.h"
21 #include "nsBlockFrame.h"
22 #include "nsContentUtils.h"
23 #include "nsDebug.h"
24 #include "nsDisplayList.h"
25 #include "nsFieldSetFrame.h"
26 #include "nsIFrameInlines.h"
27 #include "nsLayoutUtils.h"
28 #include "nsPlaceholderFrame.h"
29 #include "nsPresContext.h"
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 {
154 public:
155 explicit FlexboxAxisTracker(const nsFlexContainerFrame* aFlexContainer);
157 // Accessors:
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
275 * baseline).
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 {
301 MOZ_ASSERT(
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
319 // direction.
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;
340 private:
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 {
351 public:
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;
375 return item;
378 // Accessors
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");
385 return mMainMinSize;
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) {
406 return ascent;
409 // Use GetFirstLineBaseline() or GetLastLineBaseline() as appropriate:
410 bool found = aUseFirstBaseline
411 ? nsLayoutUtils::GetFirstLineBaseline(mWM, mFrame, &ascent)
412 : nsLayoutUtils::GetLastLineBaseline(mWM, mFrame, &ascent);
413 if (found) {
414 return 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
425 ? *baseline
426 : mFrame->BSize(mWM) - *baseline;
427 return ascent;
430 // We couldn't determine a baseline, so we synthesize one from border box:
431 ascent = Baseline::SynthesizeBOffsetFromBorderBox(
432 mFrame, mWM, BaselineSharingGroup::First);
433 return ascent;
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
480 // margin.)
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
521 // cross axis).
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
574 // flexible).
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
592 // flexible).
593 float GetWeight(bool aIsUsingFlexGrow) {
594 MOZ_ASSERT(!IsFrozen(), "shouldn't need weight after item is frozen");
596 if (aIsUsingFlexGrow) {
597 return mFlexGrow;
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.)
606 return 0.0f;
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
622 // writing-mode.
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
638 // the given axis
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();
658 // Setters
659 // =======
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?");
665 MOZ_ASSERT(
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");
669 MOZ_ASSERT(
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;
718 void Freeze() {
719 mIsFrozen = true;
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 "
743 "frozen");
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");
772 mMainPosn = aPosn;
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");
787 mCrossPosn = aPosn;
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");
805 mIsStretched = true;
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;
854 protected:
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.
867 WritingMode mWM;
869 // The flex container's writing mode.
870 WritingMode mCBWM;
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
907 // block-start edge.
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
932 // constructor.
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
950 // its block-axis)?
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 {
974 public:
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
1017 // for that item.
1018 void AddLastItemToMainSizeTotals() {
1019 const FlexItem& lastItem = Items().LastElement();
1021 // Update our various bookkeeping member-vars:
1022 if (lastItem.IsFrozen()) {
1023 mNumFrozenItems++;
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
1030 // space as needed.
1031 if (NumItems() >= 2) {
1032 mTotalOuterHypotheticalMainSize += mMainGapSize;
1036 // Computes the cross-size and baseline position of this FlexLine, based on
1037 // its FlexItems.
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
1077 // nscoord_MIN.
1078 nscoord ExtractBaselineOffset(BaselineSharingGroup aBaselineGroup) const;
1081 * Returns the gap size in the main axis for this line. Used for gap
1082 * calculations.
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);
1099 private:
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
1176 // first-in-flow.
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
1192 // them.)
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
1217 // Reflow().
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;
1278 return specified;
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");
1304 #ifdef DEBUG
1305 SetDidPushItemsBitIfNeeded(aListID, aOldFrame);
1306 #endif
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};
1327 if (isMainAxis) {
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
1331 // changes that.)
1332 alignment = SimplifyAlignOrJustifyContentForOneItem(
1333 containerStylePos->mJustifyContent,
1334 /*aIsAlign = */ false);
1335 } else {
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());
1422 } else {
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);
1434 } else {
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;
1455 } else {
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;
1503 if (isSingleLine) {
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
1520 // 'stretch' above)
1521 item.ResolveFlexBaseSizeFromAspectRatio(childRI);
1523 // If we're inflexible, we can just freeze to our hypothetical main-size
1524 // up-front.
1525 if (flexGrow == 0.0f && flexShrink == 0.0f) {
1526 item.Freeze();
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
1546 // returns.
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,
1576 maxMainStyleSize)
1577 ? LogicalSize(cbWM, 0, 0)
1578 : aItemReflowInput.mContainingBlockSize.ConvertTo(cbWM, itemWM);
1580 // Compute the specified size suggestion, which is the main-size property if
1581 // it's definite.
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());
1593 } else {
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;
1630 return nscoord_MAX;
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".
1650 return;
1653 FLEX_ITEM_LOG(
1654 aFlexItem.Frame(),
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.)
1663 resolvedMinSize =
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
1677 // main axis.
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");
1704 } else {
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
1716 // need.)
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
1779 * simplicity.
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
1801 * inline axis.
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 {
1814 struct Key {
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;
1834 const Key mKey;
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:
1838 nscoord mBSize;
1840 public:
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);
1848 mBSize =
1849 borderBoxBSize -
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 {
1872 public:
1873 CachedFinalReflowMetrics(const ReflowInput& aReflowInput,
1874 const ReflowOutput& aReflowOutput)
1875 : CachedFinalReflowMetrics(aReflowInput.GetWritingMode(), aReflowInput,
1876 aReflowOutput) {}
1878 CachedFinalReflowMetrics(const FlexItem& aItem, const LogicalSize& aSize)
1879 : mBorderPadding(aItem.BorderPadding().ConvertTo(
1880 aItem.GetWritingMode(), aItem.ContainingBlockWM())),
1881 mSize(aSize),
1882 mTreatBSizeAsIndefinite(aItem.TreatBSizeAsIndefinite()) {}
1884 const LogicalSize& Size() const { return mSize; }
1885 const LogicalMargin& BorderPadding() const { return mBorderPadding; }
1886 bool TreatBSizeAsIndefinite() const { return mTreatBSizeAsIndefinite; }
1888 private:
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".
1902 LogicalSize mSize;
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).
1919 Measuring,
1921 // A reflow with the flex item's "final" size at the end of the flex layout
1922 // algorithm.
1923 Final,
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 {
1933 public:
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();
1950 return;
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());
2017 } else {
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
2032 // unimportant.
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:
2054 if (cachedData) {
2055 cachedData->Update(aChildReflowInput, childReflowOutput,
2056 FlexItemReflowType::Measuring);
2057 } else {
2058 cachedData = new CachedFlexItemData(aChildReflowInput, childReflowOutput,
2059 FlexItemReflowType::Measuring);
2060 aItem.Frame()->SetProperty(CachedFlexItemData::Prop(), cachedData);
2062 return *(cachedData->mBAxisMeasurement);
2065 /* virtual */
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
2089 // block-size.
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};
2164 } else {
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
2189 // definite.
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
2195 // aspect ratios.)
2196 mTreatBSizeAsIndefinite = false;
2197 } else {
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;
2214 } else {
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.)
2228 #ifdef DEBUG
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");
2237 #endif // DEBUG
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;
2280 } else {
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),
2304 mMargin(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:
2308 mIsFrozen(true),
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 {
2338 auto* cachedData =
2339 Frame()->FirstInFlow()->GetProperty(CachedFlexItemData::Prop());
2340 if (!cachedData || !cachedData->mBAxisMeasurement) {
2341 return Nothing();
2343 return Some(cachedData->mBAxisMeasurement->BSize());
2346 nscoord FlexItem::BaselineOffsetFromOuterCrossEdge(
2347 mozilla::Side aStartSide, bool aUseFirstLineBaseline) const {
2348 // NOTE:
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);
2366 } else {
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.
2407 return true;
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()) {
2454 numAutoMargins++;
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 {
2467 if (mIsStretched) {
2468 // We've already had our cross-size stretched for "align-self:stretch").
2469 // The container is imposing its cross size on us.
2470 return false;
2473 if (mIsStrut) {
2474 // Struts (for visibility:collapse items) have a predetermined size;
2475 // no need to measure anything.
2476 return false;
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.
2483 return true;
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.)
2497 return false;
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.
2511 return true;
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
2540 * ancestors).
2542 static bool FrameHasRelativeBSizeDependency(nsIFrame* aFrame) {
2543 if (aFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
2544 return true;
2546 for (const auto& childList : aFrame->ChildLists()) {
2547 for (nsIFrame* childFrame : childList.mList) {
2548 if (childFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
2549 return true;
2553 return false;
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");
2561 return true;
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)");
2569 return true;
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
2578 // container.
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");
2583 return true;
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");
2601 return true;
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");
2611 return true;
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);
2628 return false;
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 &
2635 // definiteness.
2636 // Let's check for each condition that would still require us to reflow:
2637 if (mFrame->IsSubtreeDirty()) {
2638 FLEX_ITEM_LOG(
2639 mFrame,
2640 "[perf] Item needed a final reflow due to its subtree being dirty");
2641 return true;
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)");
2653 return true;
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");
2661 return true;
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
2670 // reflow.
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");
2676 return true;
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");
2688 return true;
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
2694 // state.)
2695 FLEX_ITEM_LOG(mFrame, "[perf] Item didn't need a final reflow");
2696 return false;
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 {
2704 public:
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
2726 // we're tracking.
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;
2756 protected:
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) {}
2761 // Member data:
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
2778 // content-box.
2779 class MOZ_STACK_CLASS MainAxisPositionTracker : public PositionTracker {
2780 public:
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);
2803 private:
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
2813 // content-box.
2814 class MOZ_STACK_CLASS CrossAxisPositionTracker : public PositionTracker {
2815 public:
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;
2841 private:
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 {
2853 public:
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 // ===========================================
2888 /* virtual */
2889 nsFlexContainerFrame::~nsFlexContainerFrame() = default;
2891 /* virtual */
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 "
2914 "element");
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);
2929 #endif
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) {
2982 break;
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;
2999 if (shouldFreeze) {
3000 // Freeze item! (at its hypothetical main size)
3001 item.Freeze();
3002 if (item.FlexBaseSize() < item.MainSize()) {
3003 item.SetWasMinClamped();
3004 } else if (item.FlexBaseSize() > item.MainSize()) {
3005 item.SetWasMaxClamped();
3007 mNumFrozenItems++;
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) {
3019 enum FreezeType {
3020 eFreezeEverything,
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) {
3039 break;
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");
3058 item.Freeze();
3059 if (hadMinViolation) {
3060 item.SetWasMinClamped();
3061 } else if (hadMaxViolation) {
3062 item.SetWasMaxClamped();
3064 mNumFrozenItems++;
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.
3068 NS_ERROR(
3069 "Final iteration still has unfrozen items, this shouldn't"
3070 " happen unless there was nscoord under/overflow.");
3071 item.Freeze();
3072 mNumFrozenItems++;
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
3092 // convenient.
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.)
3099 if (aLineInfo) {
3100 uint32_t itemIndex = 0;
3101 for (FlexItem& item : Items()) {
3102 aLineInfo->mItems[itemIndex].mMainBaseSize = item.FlexBaseSize();
3103 aLineInfo->mItems[itemIndex].mMainDeltaSize = 0;
3104 ++itemIndex;
3108 // Determine whether we're going to be growing or shrinking items.
3109 const bool isUsingFlexGrow =
3110 (mTotalOuterHypotheticalMainSize < flexContainerMainSize);
3112 if (aLineInfo) {
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");
3126 return;
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
3135 // boxes.
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
3147 // iterations.
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) {
3219 break;
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);
3236 } else {
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 "
3277 "free space...");
3279 if (availableFreeSpace > 0) {
3280 availableFreeSpace = std::min(availableFreeSpace,
3281 totalDesiredPortionOfOrigFreeSpace);
3282 } else {
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) {
3298 break;
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.
3346 if (aLineInfo) {
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.
3359 nscoord deltaSize =
3360 item.MainSize() - aLineInfo->mItems[itemIndex].mMainBaseSize;
3362 aLineInfo->mItems[itemIndex].mMainDeltaSize = deltaSize;
3364 ++itemIndex;
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) {
3379 break;
3382 if (!item.IsFrozen()) {
3383 numUnfrozenItemsToBeSeen--;
3385 if (item.MainSize() < item.MainMinSize()) {
3386 // min violation
3387 totalViolation += item.MainMinSize() - item.MainSize();
3388 item.SetMainSize(item.MainMinSize());
3389 item.SetHadMinViolation();
3390 } else if (item.MainSize() > item.MainMaxSize()) {
3391 // max violation
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()) {
3407 break;
3410 MOZ_ASSERT(totalViolation != 0,
3411 "Zero violation should've made us freeze all items & break");
3414 #ifdef DEBUG
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");
3423 #endif // DEBUG
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',
3443 // in the main axis
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);
3520 } else {
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 "
3542 "resolve them");
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;
3594 if (isSingleLine) {
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);
3607 return;
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
3612 // properties."
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();
3629 numLines++;
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
3680 // doing nothing.
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;
3712 numLinesLeft--;
3714 MOZ_ASSERT(numLinesLeft == 0, "miscalculated num lines");
3715 } else {
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
3754 // details.
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)---
3778 // ___ ___ ___
3779 // mmmmmmmmmmmm | |margin-start |
3780 // m m | _|_ ___ |
3781 // m bbbbbbbb m |curOuterCrossSize | |crossStartToBaseline
3782 // m b b m | |ascent |
3783 // m b BASE b m | _|_ _|_
3784 // m b b m | |
3785 // m bbbbbbbb m | |crossEndToBaseline
3786 // m m | |
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
3802 // size below:
3803 if (item.ItemBaselineSharingGroup() == BaselineSharingGroup::First) {
3804 crossStartToFurthestFirstBaseline =
3805 std::max(crossStartToFurthestFirstBaseline, crossStartToBaseline);
3806 crossEndToFurthestFirstBaseline =
3807 std::max(crossEndToFurthestFirstBaseline, crossEndToBaseline);
3808 } else {
3809 crossStartToFurthestLastBaseline =
3810 std::max(crossStartToFurthestLastBaseline, crossStartToBaseline);
3811 crossEndToFurthestLastBaseline =
3812 std::max(crossEndToFurthestLastBaseline, crossEndToBaseline);
3814 } else {
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...
3829 // and
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...
3832 // and
3833 // (c) largest cross-size of all other children.
3834 mLineCrossSize = std::max(
3835 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()) {
3873 return;
3876 // If we've already been stretched, we can bail out early, too.
3877 // No need to redo the calculation.
3878 if (mIsStretched) {
3879 return;
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)) {
3896 return block;
3898 for (nsIFrame* f : aFrame->PrincipalChildList()) {
3899 if (nsBlockFrame* block = FindFlexItemBlockFrame(f)) {
3900 return block;
3903 return nullptr;
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 "
3932 "update them");
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);
3938 numAutoMargins--;
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()) {
3950 return;
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
4014 // baseline.
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;
4036 } else {
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;
4043 } else {
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);
4053 } else {
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;
4089 break;
4090 case StyleFlexDirection::RowReverse:
4091 mIsRowOriented = true;
4092 mIsMainAxisReversed = true;
4093 break;
4094 case StyleFlexDirection::Column:
4095 mIsRowOriented = false;
4096 mIsMainAxisReversed = false;
4097 break;
4098 case StyleFlexDirection::ColumnReverse:
4099 mIsRowOriented = false;
4100 mIsMainAxisReversed = true;
4101 break;
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()
4119 ? LogicalEdge::End
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;
4143 if (isSingleLine) {
4144 // Not wrapping. Set threshold to sentinel value that tells us not to wrap.
4145 wrapThreshold = NS_UNCONSTRAINEDSIZE;
4146 } else {
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);
4185 continue;
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(),
4195 aAxisTracker);
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);
4202 nextStrutIdx++;
4203 } else {
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
4287 // well.
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));
4298 iter.Next();
4299 if (iter.AtEnd()) {
4300 // We've constructed flex items for all children. No need to check
4301 // rest of the items.
4302 child = childFirstInFlow = nullptr;
4303 break;
4305 child = *iter;
4306 childFirstInFlow = child->FirstInFlow();
4309 if (iter.AtEnd()) {
4310 // We've constructed flex items for all children. No need to check
4311 // rest of the lines.
4312 break;
4317 flr.mContentBoxMainSize = data->mContentBoxMainSize;
4318 flr.mContentBoxCrossSize = data->mContentBoxCrossSize;
4320 return flr;
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
4368 // entire FlexLine.
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
4372 // aspect-ratio.
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
4453 // padding from it.
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
4467 availableBSize =
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,
4503 FlexItem& aItem) {
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());
4508 return;
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()) {
4568 return;
4571 MarkInReflow();
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)) {
4583 return;
4586 NormalizeChildLists();
4588 #ifdef DEBUG
4589 mDidPushItemsBitMayLie = false;
4590 SanityCheckChildListsBeforeReflow();
4591 #endif // DEBUG
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();
4620 if (!prevInFlow) {
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
4629 // block-size.
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.
4654 flr.mLines.Clear();
4655 flr.mPlaceholders.Clear();
4656 flr = DoFlexLayout(aReflowInput, tentativeContentBoxMainSize,
4657 tentativeContentBoxCrossSize, axisTracker, mainGapSize,
4658 crossGapSize, struts, containerInfo);
4660 } else {
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;
4701 if (prevInFlow) {
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
4736 // fragmentation.
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));
4750 } else {
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;
4762 } else {
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
4765 // next-in-flow.
4766 mayNeedNextInFlow = contentBoxSize.BSize(wm) - consumedBSize >
4767 availableSizeForItems.BSize(wm);
4770 } else {
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
4778 // padding.
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,
4813 aStatus);
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.
4822 if (!prevInFlow) {
4823 SharedFlexData* sharedData = GetProperty(SharedFlexData::Prop());
4824 if (!aStatus.IsFullyComplete()) {
4825 if (!sharedData) {
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)) {
4860 return Nothing{};
4862 return Some(aBaselineGroup == BaselineSharingGroup::First ? mFirstBaseline
4863 : mLastBaseline);
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()) {
4901 continue;
4903 ConsiderChildOverflow(aOverflowAreas, f);
4904 if (!isScrolledContent) {
4905 continue;
4907 if (f->IsPlaceholderFrame()) {
4908 continue;
4910 anyScrolledContentItem = true;
4911 if (MOZ_UNLIKELY(f->IsRelativelyOrStickyPositioned())) {
4912 const nsRect marginRect = f->GetMarginRectRelativeToSelf();
4913 itemMarginBoxes =
4914 itemMarginBoxes.Union(marginRect + f->GetNormalPosition());
4915 if (f->IsRelativelyPositioned()) {
4916 relPosItemMarginBoxes.UnionAllWith(marginRect + f->GetPosition());
4917 } else {
4918 MOZ_ASSERT(f->IsStickyPositioned());
4919 relPosItemMarginBoxes.UnionWith(
4920 OverflowAreas(marginRect + f->GetPosition(), nsRect()));
4922 } else {
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.
4968 return;
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)) {
4993 return nullptr;
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());
5002 if (info) {
5003 // We can reuse, as long as we clear out old data.
5004 info->mLines.Clear();
5005 } else {
5006 info = new ComputedFlexContainerInfo();
5007 SetProperty(FlexContainerInfo(), info);
5010 return 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);
5036 if (targetFrame) {
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
5042 // target.
5043 while (content && content->TextIsOnlyWhitespace()) {
5044 // If content is only whitespace, try the frame sibling.
5045 targetFrame = targetFrame->GetNextSibling();
5046 if (targetFrame) {
5047 content = targetFrame->GetContent();
5048 } else {
5049 content = nullptr;
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) {
5070 case eSideLeft:
5071 return dom::FlexPhysicalDirection::Horizontal_lr;
5072 case eSideRight:
5073 return dom::FlexPhysicalDirection::Horizontal_rl;
5074 case eSideTop:
5075 return dom::FlexPhysicalDirection::Vertical_tb;
5076 case eSideBottom:
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);
5117 ++itemIndex;
5119 ++lineIndex;
5123 nsFlexContainerFrame* nsFlexContainerFrame::GetFlexFrameWithComputedInfo(
5124 nsIFrame* aFrame) {
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;
5131 if (aFrame) {
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)
5144 : nullptr;
5146 return flexFrame;
5149 nsFlexContainerFrame* flexFrame = GetFlexContainerFrame(aFrame);
5150 if (!flexFrame) {
5151 return nullptr;
5153 // Generate the FlexContainerInfo data, if it's not already there.
5154 if (flexFrame->HasProperty(FlexContainerInfo())) {
5155 return flexFrame;
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,
5164 NS_FRAME_IS_DIRTY);
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
5169 // must bail out.
5170 if (!weakFrameRef.IsAlive()) {
5171 return nullptr;
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");
5180 return flexFrame;
5183 /* static */
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;
5216 /* static */
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()) {
5224 return true;
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);
5247 } else {
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();
5263 } else {
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);
5279 ++lineIndex;
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());
5299 } else {
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.
5344 return flr;
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);
5362 } else {
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;
5374 lineIndex = 0;
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,
5380 aAxisTracker);
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(),
5392 aAxisTracker);
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.
5409 return nscoord_MIN;
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) {
5422 return ascent +
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();
5443 ++lineIndex;
5446 return flr;
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).
5550 const auto& line =
5551 aFlr.mLines[aAxisTracker.IsCrossAxisReversed() ? numLines - lineIdx - 1
5552 : lineIdx];
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
5567 : itemIdx];
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
5578 // the outer loop.
5579 Maybe<nscoord> frameBPosBeforePerItemShift;
5581 if (item.Frame()->GetPrevInFlow()) {
5582 // The item is a continuation. Lay it out at the beginning of the
5583 // available space.
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.
5606 return 0;
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();
5623 } else {
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;
5630 } else {
5631 MOZ_ASSERT(aAxisTracker.IsColumnOriented());
5632 if (isSingleLine) {
5633 if (&item == startmostItem) {
5634 bAxisMetrics.mBEndEdgeShift = GetPerItemPositionShiftToBEnd();
5636 framePos.B(flexWM) += bAxisMetrics.mBEndEdgeShift;
5637 } else {
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) {
5664 FLEX_ITEM_LOG(
5665 item.Frame(),
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
5673 // it.
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.
5683 FLEX_ITEM_LOG(
5684 item.Frame(),
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};
5720 } else {
5721 shouldPushRemainingItems = true;
5723 } else {
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) {
5733 return true;
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.
5738 return false;
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.
5743 return false;
5745 if (item.Frame()->BSize() <= availableBSizeForItem) {
5746 return false;
5748 if (aAxisTracker.IsColumnOriented() &&
5749 item.Frame()->StyleDisplay()->mBreakBefore ==
5750 StyleBreakBetween::Avoid) {
5751 return false;
5753 return true;
5754 }();
5755 if (shouldPushItem) {
5756 FLEX_ITEM_LOG(
5757 item.Frame(),
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;
5781 } else {
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.
5788 } else {
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
5801 // edge.
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);
5816 } else {
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
5825 // here.
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);
5848 } else {
5849 MOZ_ASSERT(aAxisTracker.IsColumnOriented());
5850 if (isSingleLine) {
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;
5857 } else {
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
5861 // them.
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());
5872 if (propValue) {
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
5885 // flex container.
5886 nsReflowStatus childrenStatus;
5887 childrenStatus.SetInlineLineBreakBeforeAndReset();
5888 return {0, childrenStatus};
5890 } else {
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
5976 // a next-in-flow.
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;
5985 } else {
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();
6025 } else {
6026 // Our own effective content-box block-size can fit within the available
6027 // block-size.
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
6058 // to add it in.
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();
6073 return;
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))
6094 .B(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 =
6105 item.Frame()
6106 ->GetLogicalPosition(flexWM,
6107 desiredSizeInFlexWM.GetPhysicalSize(flexWM))
6108 .B(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);
6152 } else {
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());
6180 MOZ_ASSERT(
6181 cachedOffsets,
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()) {
6207 return false;
6209 if (aItem.Frame()->IsReplaced()) {
6210 // Disallow fragmentation-imposed block-size growth for replaced elements
6211 // since they are monolithic, and cannot be fragmented.
6212 return false;
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.
6220 return false;
6222 if (aItem.IsBlockAxisMainAxis()) {
6223 if (aItem.IsFlexBaseSizeContentBSize()) {
6224 // The flex item resolved its indefinite flex-basis to the content
6225 // block-size.
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.
6230 return true;
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.
6235 return true;
6238 return false;
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
6252 // growth.
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.
6260 return true;
6262 return false;
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());
6272 } else {
6273 overrideBSizeWithAuto = ComputeBSizeOverrideWithAuto();
6274 if (overrideBSizeWithAuto) {
6275 sizeOverrides.mStyleBSize.emplace(StyleSize::Auto());
6276 FLEX_LOGV("Main size (block-size) override: Auto");
6277 } else {
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());
6289 } else {
6290 overrideBSizeWithAuto = ComputeBSizeOverrideWithAuto();
6291 if (overrideBSizeWithAuto) {
6292 sizeOverrides.mStyleBSize.emplace(StyleSize::Auto());
6293 FLEX_LOGV("Cross size (block-size) override: Auto");
6294 } else {
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,
6360 childStatus);
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);
6374 } else {
6375 cached = new CachedFlexItemData(childReflowInput, childReflowOutput,
6376 FlexItemReflowType::Final);
6377 aItem.Frame()->SetProperty(CachedFlexItemData::Prop(), cached);
6380 return childStatus;
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,
6396 availSize);
6397 // No need to set the -webkit-line-clamp related flags when reflowing
6398 // a placeholder.
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);
6430 } else {
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()) {
6445 continue;
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.
6452 continue;
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;
6481 /* virtual */
6482 nscoord nsFlexContainerFrame::GetMinISize(gfxContext* aRenderingContext) {
6483 if (mCachedMinISize == NS_INTRINSIC_ISIZE_UNKNOWN) {
6484 mCachedMinISize =
6485 IntrinsicISize(aRenderingContext, IntrinsicISizeType::MinISize);
6488 return mCachedMinISize;
6491 /* virtual */
6492 nscoord nsFlexContainerFrame::GetPrefISize(gfxContext* aRenderingContext) {
6493 if (mCachedPrefISize == NS_INTRINSIC_ISIZE_UNKNOWN) {
6494 mCachedPrefISize =
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
6505 // layout.
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;
6515 return false;
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);
6524 LineInfo lineInfo;
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();
6530 } else {
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;
6537 return lineInfo;
6540 int32_t nsFlexContainerFrame::FindLineContaining(nsIFrame* aFrame,
6541 int32_t aStartLine) {
6542 const int32_t index = mFrames.IndexOf(aFrame);
6543 if (index < 0) {
6544 return -1;
6546 const FlexboxAxisInfo info(this);
6547 if (info.mIsRowOriented) {
6548 return 0;
6550 if (index < aStartLine) {
6551 return -1;
6553 return index;
6556 NS_IMETHODIMP
6557 nsFlexContainerFrame::CheckLineOrder(int32_t aLine, bool* aIsReordered,
6558 nsIFrame** aFirstVisual,
6559 nsIFrame** aLastVisual) {
6560 *aIsReordered = false;
6561 *aFirstVisual = nullptr;
6562 *aLastVisual = nullptr;
6563 return NS_OK;
6566 NS_IMETHODIMP
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);
6581 if (!f) {
6582 return NS_OK;
6585 auto rect = f->GetLogicalRect(wm, GetSize());
6586 *aFrameFound = f;
6587 *aPosIsBeforeFirstFrame = pos.I(wm) < rect.IStart(wm);
6588 *aPosIsAfterLastFrame = pos.I(wm) > rect.IEnd(wm);
6589 return NS_OK;
6592 LineFrameFinder finder(aPos, GetSize(), GetWritingMode(),
6593 IsLineIteratorFlowRTL());
6594 for (nsIFrame* f : mFrames) {
6595 finder.Scan(f);
6596 if (finder.IsDone()) {
6597 break;
6600 finder.Finish(aFrameFound, aPosIsBeforeFirstFrame, aPosIsAfterLastFrame);
6601 return NS_OK;