Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* rendering object for CSS "display: flex" */
#include "nsFlexContainerFrame.h"
#include <algorithm>
#include "gfxContext.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/CSSOrderAwareFrameIterator.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Logging.h"
#include "mozilla/PresShell.h"
#include "mozilla/WritingModes.h"
#include "nsBlockFrame.h"
#include "nsContentUtils.h"
#include "nsCSSAnonBoxes.h"
#include "nsDisplayList.h"
#include "nsFieldSetFrame.h"
#include "nsIFrameInlines.h"
#include "nsLayoutUtils.h"
#include "nsPlaceholderFrame.h"
#include "nsPresContext.h"
using namespace mozilla;
using namespace mozilla::layout;
// Convenience typedefs for helper classes that we forward-declare in .h file
// (so that nsFlexContainerFrame methods can use them as parameters):
using FlexItem = nsFlexContainerFrame::FlexItem;
using FlexLine = nsFlexContainerFrame::FlexLine;
using FlexboxAxisTracker = nsFlexContainerFrame::FlexboxAxisTracker;
using StrutInfo = nsFlexContainerFrame::StrutInfo;
using CachedBAxisMeasurement = nsFlexContainerFrame::CachedBAxisMeasurement;
static mozilla::LazyLogModule gFlexContainerLog("FlexContainer");
#define FLEX_LOG(...) \
MOZ_LOG(gFlexContainerLog, LogLevel::Debug, (__VA_ARGS__));
#define FLEX_LOGV(...) \
MOZ_LOG(gFlexContainerLog, LogLevel::Verbose, (__VA_ARGS__));
// XXXdholbert Some of this helper-stuff should be separated out into a general
// "main/cross-axis utils" header, shared by grid & flexbox?
// (Particularly when grid gets support for align-*/justify-* properties.)
// Helper structs / classes / methods
// ==================================
// Returns true iff the given nsStyleDisplay has display:-webkit-{inline-}box
// or display:-moz-{inline-}box.
static inline bool IsDisplayValueLegacyBox(const nsStyleDisplay* aStyleDisp) {
return aStyleDisp->mDisplay == mozilla::StyleDisplay::WebkitBox ||
aStyleDisp->mDisplay == mozilla::StyleDisplay::WebkitInlineBox ||
aStyleDisp->mDisplay == mozilla::StyleDisplay::MozBox ||
aStyleDisp->mDisplay == mozilla::StyleDisplay::MozInlineBox;
}
// Returns true if aFlexContainer is a frame for some element that has
// display:-webkit-{inline-}box (or -moz-{inline-}box). aFlexContainer is
// expected to be an instance of nsFlexContainerFrame (enforced with an assert);
// otherwise, this function's state-bit-check here is bogus.
static bool IsLegacyBox(const nsIFrame* aFlexContainer) {
MOZ_ASSERT(aFlexContainer->IsFlexContainerFrame(),
"only flex containers may be passed to this function");
return aFlexContainer->HasAnyStateBits(NS_STATE_FLEX_IS_EMULATING_LEGACY_BOX);
}
// Returns the OrderState enum we should pass to CSSOrderAwareFrameIterator
// (depending on whether aFlexContainer has
// NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER state bit).
static CSSOrderAwareFrameIterator::OrderState OrderStateForIter(
const nsFlexContainerFrame* aFlexContainer) {
return aFlexContainer->HasAnyStateBits(
NS_STATE_FLEX_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER)
? CSSOrderAwareFrameIterator::OrderState::Ordered
: CSSOrderAwareFrameIterator::OrderState::Unordered;
}
// Returns the OrderingProperty enum that we should pass to
// CSSOrderAwareFrameIterator (depending on whether it's a legacy box).
static CSSOrderAwareFrameIterator::OrderingProperty OrderingPropertyForIter(
const nsFlexContainerFrame* aFlexContainer) {
return IsLegacyBox(aFlexContainer)
? CSSOrderAwareFrameIterator::OrderingProperty::BoxOrdinalGroup
: CSSOrderAwareFrameIterator::OrderingProperty::Order;
}
// Returns the "align-items" value that's equivalent to the legacy "box-align"
// value in the given style struct.
static StyleAlignFlags ConvertLegacyStyleToAlignItems(
const nsStyleXUL* aStyleXUL) {
// -[moz|webkit]-box-align corresponds to modern "align-items"
switch (aStyleXUL->mBoxAlign) {
case StyleBoxAlign::Stretch:
return StyleAlignFlags::STRETCH;
case StyleBoxAlign::Start:
return StyleAlignFlags::FLEX_START;
case StyleBoxAlign::Center:
return StyleAlignFlags::CENTER;
case StyleBoxAlign::Baseline:
return StyleAlignFlags::BASELINE;
case StyleBoxAlign::End:
return StyleAlignFlags::FLEX_END;
}
MOZ_ASSERT_UNREACHABLE("Unrecognized mBoxAlign enum value");
// Fall back to default value of "align-items" property:
return StyleAlignFlags::STRETCH;
}
// Returns the "justify-content" value that's equivalent to the legacy
// "box-pack" value in the given style struct.
static StyleContentDistribution ConvertLegacyStyleToJustifyContent(
const nsStyleXUL* aStyleXUL) {
// -[moz|webkit]-box-pack corresponds to modern "justify-content"
switch (aStyleXUL->mBoxPack) {
case StyleBoxPack::Start:
return {StyleAlignFlags::FLEX_START};
case StyleBoxPack::Center:
return {StyleAlignFlags::CENTER};
case StyleBoxPack::End:
return {StyleAlignFlags::FLEX_END};
case StyleBoxPack::Justify:
return {StyleAlignFlags::SPACE_BETWEEN};
}
MOZ_ASSERT_UNREACHABLE("Unrecognized mBoxPack enum value");
// Fall back to default value of "justify-content" property:
return {StyleAlignFlags::FLEX_START};
}
/**
* Converts a "flex-relative" coordinate in a single axis (a main- or cross-axis
* coordinate) into a coordinate in the corresponding physical (x or y) axis. If
* the flex-relative axis in question already maps *directly* to a physical
* axis (i.e. if it's LTR or TTB), then the physical coordinate has the same
* numeric value as the provided flex-relative coordinate. Otherwise, we have to
* subtract the flex-relative coordinate from the flex container's size in that
* axis, to flip the polarity. (So e.g. a main-axis position of 2px in a RTL
* 20px-wide container would correspond to a physical coordinate (x-value) of
* 18px.)
*/
static nscoord PhysicalCoordFromFlexRelativeCoord(nscoord aFlexRelativeCoord,
nscoord aContainerSize,
mozilla::Side aStartSide) {
if (aStartSide == eSideLeft || aStartSide == eSideTop) {
return aFlexRelativeCoord;
}
return aContainerSize - aFlexRelativeCoord;
}
// Add two nscoord values, using CheckedInt to handle integer overflow.
// This function returns the sum of its two args -- but if we trigger integer
// overflow while adding them, then this function returns nscoord_MAX instead.
static nscoord AddChecked(nscoord aFirst, nscoord aSecond) {
CheckedInt<nscoord> checkedResult = CheckedInt<nscoord>(aFirst) + aSecond;
return checkedResult.isValid() ? checkedResult.value() : nscoord_MAX;
}
// Check if the size is auto or it is a keyword in the block axis.
// |aIsInline| should represent whether aSize is in the inline axis, from the
// perspective of the writing mode of the flex item that the size comes from.
//
// max-content and min-content should behave as property's initial value.
// Bug 567039: We treat -moz-fit-content and -moz-available as property's
// initial value for now.
static inline bool IsAutoOrEnumOnBSize(const StyleSize& aSize, bool aIsInline) {
return aSize.IsAuto() || (!aIsInline && !aSize.IsLengthPercentage());
}
// Helper-macros to let us pick one of two expressions to evaluate
// (an inline-axis expression vs. a block-axis expression), to get a
// main-axis or cross-axis component.
// For code that has e.g. a LogicalSize object, the methods
// FlexboxAxisTracker::MainComponent and CrossComponent are cleaner
// than these macros. But in cases where we simply have two separate
// expressions for ISize and BSize (which may be expensive to evaluate),
// these macros can be used to ensure that only the needed expression is
// evaluated.
#define GET_MAIN_COMPONENT_LOGICAL(axisTracker_, wm_, isize_, bsize_) \
(axisTracker_).IsInlineAxisMainAxis((wm_)) ? (isize_) : (bsize_)
#define GET_CROSS_COMPONENT_LOGICAL(axisTracker_, wm_, isize_, bsize_) \
(axisTracker_).IsInlineAxisMainAxis((wm_)) ? (bsize_) : (isize_)
// Encapsulates our flex container's main & cross axes. This class is backed by
// a FlexboxAxisInfo helper member variable, and it adds some convenience APIs
// on top of what that struct offers.
class MOZ_STACK_CLASS nsFlexContainerFrame::FlexboxAxisTracker {
public:
explicit FlexboxAxisTracker(const nsFlexContainerFrame* aFlexContainer);
// Accessors:
LogicalAxis MainAxis() const {
return IsRowOriented() ? eLogicalAxisInline : eLogicalAxisBlock;
}
LogicalAxis CrossAxis() const {
return IsRowOriented() ? eLogicalAxisBlock : eLogicalAxisInline;
}
LogicalSide MainAxisStartSide() const;
LogicalSide MainAxisEndSide() const {
return GetOppositeSide(MainAxisStartSide());
}
LogicalSide CrossAxisStartSide() const;
LogicalSide CrossAxisEndSide() const {
return GetOppositeSide(CrossAxisStartSide());
}
mozilla::Side MainAxisPhysicalStartSide() const {
return mWM.PhysicalSide(MainAxisStartSide());
}
mozilla::Side MainAxisPhysicalEndSide() const {
return mWM.PhysicalSide(MainAxisEndSide());
}
mozilla::Side CrossAxisPhysicalStartSide() const {
return mWM.PhysicalSide(CrossAxisStartSide());
}
mozilla::Side CrossAxisPhysicalEndSide() const {
return mWM.PhysicalSide(CrossAxisEndSide());
}
// Returns the flex container's writing mode.
WritingMode GetWritingMode() const { return mWM; }
// Returns true if our main axis is in the reverse direction of our
// writing mode's corresponding axis. (From 'flex-direction: *-reverse')
bool IsMainAxisReversed() const { return mAxisInfo.mIsMainAxisReversed; }
// Returns true if our cross axis is in the reverse direction of our
// writing mode's corresponding axis. (From 'flex-wrap: *-reverse')
bool IsCrossAxisReversed() const { return mAxisInfo.mIsCrossAxisReversed; }
bool IsRowOriented() const { return mAxisInfo.mIsRowOriented; }
bool IsColumnOriented() const { return !IsRowOriented(); }
// aSize is expected to match the flex container's WritingMode.
nscoord MainComponent(const LogicalSize& aSize) const {
return IsRowOriented() ? aSize.ISize(mWM) : aSize.BSize(mWM);
}
int32_t MainComponent(const LayoutDeviceIntSize& aIntSize) const {
return IsMainAxisHorizontal() ? aIntSize.width : aIntSize.height;
}
// aSize is expected to match the flex container's WritingMode.
nscoord CrossComponent(const LogicalSize& aSize) const {
return IsRowOriented() ? aSize.BSize(mWM) : aSize.ISize(mWM);
}
int32_t CrossComponent(const LayoutDeviceIntSize& aIntSize) const {
return IsMainAxisHorizontal() ? aIntSize.height : aIntSize.width;
}
// NOTE: aMargin is expected to use the flex container's WritingMode.
nscoord MarginSizeInMainAxis(const LogicalMargin& aMargin) const {
// If we're row-oriented, our main axis is the inline axis.
return IsRowOriented() ? aMargin.IStartEnd(mWM) : aMargin.BStartEnd(mWM);
}
nscoord MarginSizeInCrossAxis(const LogicalMargin& aMargin) const {
// If we're row-oriented, our cross axis is the block axis.
return IsRowOriented() ? aMargin.BStartEnd(mWM) : aMargin.IStartEnd(mWM);
}
/**
* Converts a "flex-relative" point (a main-axis & cross-axis coordinate)
* into a LogicalPoint, using the flex container's writing mode.
*
* @arg aMainCoord The main-axis coordinate -- i.e an offset from the
* main-start edge of the flex container's content box.
* @arg aCrossCoord The cross-axis coordinate -- i.e an offset from the
* cross-start edge of the flex container's content box.
* @arg aContainerMainSize The main size of flex container's content box.
* @arg aContainerCrossSize The cross size of flex container's content box.
* @return A LogicalPoint, with the flex container's writing mode, that
* represents the same position. The logical coordinates are
* relative to the flex container's content box.
*/
LogicalPoint LogicalPointFromFlexRelativePoint(
nscoord aMainCoord, nscoord aCrossCoord, nscoord aContainerMainSize,
nscoord aContainerCrossSize) const {
nscoord logicalCoordInMainAxis =
IsMainAxisReversed() ? aContainerMainSize - aMainCoord : aMainCoord;
nscoord logicalCoordInCrossAxis =
IsCrossAxisReversed() ? aContainerCrossSize - aCrossCoord : aCrossCoord;
return IsRowOriented() ? LogicalPoint(mWM, logicalCoordInMainAxis,
logicalCoordInCrossAxis)
: LogicalPoint(mWM, logicalCoordInCrossAxis,
logicalCoordInMainAxis);
}
/**
* Converts a "flex-relative" size (a main-axis & cross-axis size)
* into a LogicalSize, using the flex container's writing mode.
*
* @arg aMainSize The main-axis size.
* @arg aCrossSize The cross-axis size.
* @return A LogicalSize, with the flex container's writing mode, that
* represents the same size.
*/
LogicalSize LogicalSizeFromFlexRelativeSizes(nscoord aMainSize,
nscoord aCrossSize) const {
return IsRowOriented() ? LogicalSize(mWM, aMainSize, aCrossSize)
: LogicalSize(mWM, aCrossSize, aMainSize);
}
bool IsMainAxisHorizontal() const {
// If we're row-oriented, and our writing mode is NOT vertical,
// or we're column-oriented and our writing mode IS vertical,
// then our main axis is horizontal. This handles all cases:
return IsRowOriented() != mWM.IsVertical();
}
// Returns true if this flex item's inline axis in aItemWM is parallel (or
// antiparallel) to the container's main axis. Returns false, otherwise.
//
// Note: this is a helper for implementing macros and can also be used before
// constructing FlexItem. Inside of flex reflow code,
// FlexItem::IsInlineAxisMainAxis() is equivalent & more optimal.
bool IsInlineAxisMainAxis(WritingMode aItemWM) const {
return IsRowOriented() != GetWritingMode().IsOrthogonalTo(aItemWM);
}
// Delete copy-constructor & reassignment operator, to prevent accidental
// (unnecessary) copying.
FlexboxAxisTracker(const FlexboxAxisTracker&) = delete;
FlexboxAxisTracker& operator=(const FlexboxAxisTracker&) = delete;
private:
const WritingMode mWM; // The flex container's writing mode.
const FlexboxAxisInfo mAxisInfo;
};
/**
* Represents a flex item.
* Includes the various pieces of input that the Flexbox Layout Algorithm uses
* to resolve a flexible width.
*/
class nsFlexContainerFrame::FlexItem final {
public:
// Normal constructor:
FlexItem(ReflowInput& aFlexItemReflowInput, float aFlexGrow,
float aFlexShrink, nscoord aFlexBaseSize, nscoord aMainMinSize,
nscoord aMainMaxSize, nscoord aTentativeCrossSize,
nscoord aCrossMinSize, nscoord aCrossMaxSize,
const FlexboxAxisTracker& aAxisTracker);
// Simplified constructor, to be used only for generating "struts":
// (NOTE: This "strut" constructor uses the *container's* writing mode, which
// we'll use on this FlexItem instead of the child frame's real writing mode.
// This is fine - it doesn't matter what writing mode we use for a
// strut, since it won't render any content and we already know its size.)
FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize, WritingMode aContainerWM,
const FlexboxAxisTracker& aAxisTracker);
// Clone existing FlexItem for its underlying frame's continuation.
// @param aContinuation a continuation in our next-in-flow chain.
FlexItem CloneFor(nsIFrame* const aContinuation) const {
MOZ_ASSERT(Frame() == aContinuation->FirstInFlow(),
"aContinuation should be in aItem's continuation chain!");
FlexItem item(*this);
item.mFrame = aContinuation;
return item;
}
// Accessors
nsIFrame* Frame() const { return mFrame; }
nscoord FlexBaseSize() const { return mFlexBaseSize; }
nscoord MainMinSize() const {
MOZ_ASSERT(!mNeedsMinSizeAutoResolution,
"Someone's using an unresolved 'auto' main min-size");
return mMainMinSize;
}
nscoord MainMaxSize() const { return mMainMaxSize; }
// Note: These return the main-axis position and size of our *content box*.
nscoord MainSize() const { return mMainSize; }
nscoord MainPosition() const { return mMainPosn; }
nscoord CrossMinSize() const { return mCrossMinSize; }
nscoord CrossMaxSize() const { return mCrossMaxSize; }
// Note: These return the cross-axis position and size of our *content box*.
nscoord CrossSize() const { return mCrossSize; }
nscoord CrossPosition() const { return mCrossPosn; }
nscoord ResolvedAscent(bool aUseFirstBaseline) const {
if (mAscent == ReflowOutput::ASK_FOR_BASELINE) {
// XXXdholbert We should probably be using the *container's* writing-mode
// here, instead of the item's -- though it doesn't much matter right
// now, because all of the baseline-handling code here essentially
// assumes that the container & items have the same writing-mode. This
// will matter more (& can be expanded/tested) once we officially support
// logical directions & vertical writing-modes in flexbox, in bug 1079155
// or a dependency.
// Use GetFirstLineBaseline() or GetLastLineBaseline() as appropriate,
// or just GetLogicalBaseline() if that fails.
bool found =
aUseFirstBaseline
? nsLayoutUtils::GetFirstLineBaseline(mWM, mFrame, &mAscent)
: nsLayoutUtils::GetLastLineBaseline(mWM, mFrame, &mAscent);
if (!found) {
mAscent = mFrame->SynthesizeBaselineBOffsetFromBorderBox(
mWM, BaselineSharingGroup::First);
}
}
return mAscent;
}
// Convenience methods to compute the main & cross size of our *margin-box*.
nscoord OuterMainSize() const {
return mMainSize + MarginBorderPaddingSizeInMainAxis();
}
nscoord OuterCrossSize() const {
return mCrossSize + MarginBorderPaddingSizeInCrossAxis();
}
// Convenience methods to synthesize a style main size or a style cross size
// with box-size considered, to provide the size overrides when constructing
// ReflowInput for flex items.
StyleSize StyleMainSize() const {
nscoord mainSize = MainSize();
if (Frame()->StylePosition()->mBoxSizing == StyleBoxSizing::Border) {
mainSize += BorderPaddingSizeInMainAxis();
}
return StyleSize::LengthPercentage(
LengthPercentage::FromAppUnits(mainSize));
}
StyleSize StyleCrossSize() const {
nscoord crossSize = CrossSize();
if (Frame()->StylePosition()->mBoxSizing == StyleBoxSizing::Border) {
crossSize += BorderPaddingSizeInCrossAxis();
}
return StyleSize::LengthPercentage(
LengthPercentage::FromAppUnits(crossSize));
}
// Returns the distance between this FlexItem's baseline and the cross-start
// edge of its margin-box. Used in baseline alignment.
//
// (This function needs to be told which physical start side we're measuring
// the baseline from, so that it can look up the appropriate components from
// margin.)
nscoord BaselineOffsetFromOuterCrossEdge(mozilla::Side aStartSide,
bool aUseFirstLineBaseline) const;
float ShareOfWeightSoFar() const { return mShareOfWeightSoFar; }
bool IsFrozen() const { return mIsFrozen; }
bool HadMinViolation() const {
MOZ_ASSERT(!mIsFrozen, "min violation has no meaning for frozen items.");
return mHadMinViolation;
}
bool HadMaxViolation() const {
MOZ_ASSERT(!mIsFrozen, "max violation has no meaning for frozen items.");
return mHadMaxViolation;
}
bool WasMinClamped() const {
MOZ_ASSERT(mIsFrozen, "min clamping has no meaning for unfrozen items.");
return mHadMinViolation;
}
bool WasMaxClamped() const {
MOZ_ASSERT(mIsFrozen, "max clamping has no meaning for unfrozen items.");
return mHadMaxViolation;
}
// Indicates whether this item received a preliminary "measuring" reflow
// before its actual reflow.
bool HadMeasuringReflow() const { return mHadMeasuringReflow; }
// Indicates whether this item's computed cross-size property is 'auto'.
bool IsCrossSizeAuto() const;
// Indicates whether the cross-size property is set to something definite,
// for the purpose of preferred aspect ratio calculations.
bool IsCrossSizeDefinite(const ReflowInput& aItemReflowInput) const;
// Indicates whether this item's cross-size has been stretched (from having
// "align-self: stretch" with an auto cross-size and no auto margins in the
// cross axis).
bool IsStretched() const { return mIsStretched; }
// Indicates whether we need to resolve an 'auto' value for the main-axis
// min-[width|height] property.
bool NeedsMinSizeAutoResolution() const {
return mNeedsMinSizeAutoResolution;
}
bool HasAnyAutoMargin() const { return mHasAnyAutoMargin; }
// Indicates whether this item is a "strut" left behind by an element with
// visibility:collapse.
bool IsStrut() const { return mIsStrut; }
// The main axis and cross axis are relative to mCBWM.
LogicalAxis MainAxis() const { return mMainAxis; }
LogicalAxis CrossAxis() const { return GetOrthogonalAxis(mMainAxis); }
// IsInlineAxisMainAxis() returns true if this item's inline axis is parallel
// (or antiparallel) to the container's main axis. Otherwise (i.e. if this
// item's inline axis is orthogonal to the container's main axis), this
// function returns false. The next 3 methods are all other ways of asking
// the same question, and only exist for readability at callsites (depending
// on which axes those callsites are reasoning about).
bool IsInlineAxisMainAxis() const { return mIsInlineAxisMainAxis; }
bool IsInlineAxisCrossAxis() const { return !mIsInlineAxisMainAxis; }
bool IsBlockAxisMainAxis() const { return !mIsInlineAxisMainAxis; }
bool IsBlockAxisCrossAxis() const { return mIsInlineAxisMainAxis; }
WritingMode GetWritingMode() const { return mWM; }
StyleAlignSelf AlignSelf() const { return mAlignSelf; }
StyleAlignFlags AlignSelfFlags() const { return mAlignSelfFlags; }
// Returns the flex factor (flex-grow or flex-shrink), depending on
// 'aIsUsingFlexGrow'.
//
// Asserts fatally if called on a frozen item (since frozen items are not
// flexible).
float GetFlexFactor(bool aIsUsingFlexGrow) {
MOZ_ASSERT(!IsFrozen(), "shouldn't need flex factor after item is frozen");
return aIsUsingFlexGrow ? mFlexGrow : mFlexShrink;
}
// Returns the weight that we should use in the "resolving flexible lengths"
// algorithm. If we're using the flex grow factor, we just return that;
// otherwise, we return the "scaled flex shrink factor" (scaled by our flex
// base size, so that when both large and small items are shrinking, the large
// items shrink more).
//
// I'm calling this a "weight" instead of a "[scaled] flex-[grow|shrink]
// factor", to more clearly distinguish it from the actual flex-grow &
// flex-shrink factors.
//
// Asserts fatally if called on a frozen item (since frozen items are not
// flexible).
float GetWeight(bool aIsUsingFlexGrow) {
MOZ_ASSERT(!IsFrozen(), "shouldn't need weight after item is frozen");
if (aIsUsingFlexGrow) {
return mFlexGrow;
}
// We're using flex-shrink --> return mFlexShrink * mFlexBaseSize
if (mFlexBaseSize == 0) {
// Special-case for mFlexBaseSize == 0 -- we have no room to shrink, so
// regardless of mFlexShrink, we should just return 0.
// (This is really a special-case for when mFlexShrink is infinity, to
// avoid performing mFlexShrink * mFlexBaseSize = inf * 0 = undefined.)
return 0.0f;
}
return mFlexShrink * mFlexBaseSize;
}
bool TreatBSizeAsIndefinite() const { return mTreatBSizeAsIndefinite; }
const AspectRatio& GetAspectRatio() const { return mAspectRatio; }
bool HasAspectRatio() const { return !!mAspectRatio; }
// Getters for margin:
// ===================
LogicalMargin Margin() const { return mMargin; }
nsMargin PhysicalMargin() const { return mMargin.GetPhysicalMargin(mCBWM); }
// Returns the margin component for a given LogicalSide in flex container's
// writing-mode.
nscoord GetMarginComponentForSide(LogicalSide aSide) const {
return mMargin.Side(aSide, mCBWM);
}
// Returns the total space occupied by this item's margins in the given axis
nscoord MarginSizeInMainAxis() const {
return mMargin.StartEnd(MainAxis(), mCBWM);
}
nscoord MarginSizeInCrossAxis() const {
return mMargin.StartEnd(CrossAxis(), mCBWM);
}
// Getters for border/padding
// ==========================
// Returns the total space occupied by this item's borders and padding in
// the given axis
LogicalMargin BorderPadding() const { return mBorderPadding; }
nscoord BorderPaddingSizeInMainAxis() const {
return mBorderPadding.StartEnd(MainAxis(), mCBWM);
}
nscoord BorderPaddingSizeInCrossAxis() const {
return mBorderPadding.StartEnd(CrossAxis(), mCBWM);
}
// Getter for combined margin/border/padding
// =========================================
// Returns the total space occupied by this item's margins, borders and
// padding in the given axis
nscoord MarginBorderPaddingSizeInMainAxis() const {
return MarginSizeInMainAxis() + BorderPaddingSizeInMainAxis();
}
nscoord MarginBorderPaddingSizeInCrossAxis() const {
return MarginSizeInCrossAxis() + BorderPaddingSizeInCrossAxis();
}
// Setters
// =======
// Helper to set the resolved value of min-[width|height]:auto for the main
// axis. (Should only be used if NeedsMinSizeAutoResolution() returns true.)
void UpdateMainMinSize(nscoord aNewMinSize) {
NS_ASSERTION(aNewMinSize >= 0,
"How did we end up with a negative min-size?");
MOZ_ASSERT(mMainMaxSize >= aNewMinSize,
"Should only use this function for resolving min-size:auto, "
"and main max-size should be an upper-bound for resolved val");
MOZ_ASSERT(
mNeedsMinSizeAutoResolution &&
(mMainMinSize == 0 || mFrame->IsThemed(mFrame->StyleDisplay())),
"Should only use this function for resolving min-size:auto, "
"so we shouldn't already have a nonzero min-size established "
"(unless it's a themed-widget-imposed minimum size)");
if (aNewMinSize > mMainMinSize) {
mMainMinSize = aNewMinSize;
// Also clamp main-size to be >= new min-size:
mMainSize = std::max(mMainSize, aNewMinSize);
}
mNeedsMinSizeAutoResolution = false;
}
// This sets our flex base size, and then sets our main size to the
// resulting "hypothetical main size" (the base size clamped to our
// main-axis [min,max] sizing constraints).
void SetFlexBaseSizeAndMainSize(nscoord aNewFlexBaseSize) {
MOZ_ASSERT(!mIsFrozen || mFlexBaseSize == NS_UNCONSTRAINEDSIZE,
"flex base size shouldn't change after we're frozen "
"(unless we're just resolving an intrinsic size)");
mFlexBaseSize = aNewFlexBaseSize;
// Before we've resolved flexible lengths, we keep mMainSize set to
// the 'hypothetical main size', which is the flex base size, clamped
// to the [min,max] range:
mMainSize = NS_CSS_MINMAX(mFlexBaseSize, mMainMinSize, mMainMaxSize);
FLEX_LOGV(
"Set flex base size: %d, hypothetical main size: %d for flex item %p",
mFlexBaseSize, mMainSize, mFrame);
}
// Setters used while we're resolving flexible lengths
// ---------------------------------------------------
// Sets the main-size of our flex item's content-box.
void SetMainSize(nscoord aNewMainSize) {
MOZ_ASSERT(!mIsFrozen, "main size shouldn't change after we're frozen");
mMainSize = aNewMainSize;
}
void SetShareOfWeightSoFar(float aNewShare) {
MOZ_ASSERT(!mIsFrozen || aNewShare == 0.0f,
"shouldn't be giving this item any share of the weight "
"after it's frozen");
mShareOfWeightSoFar = aNewShare;
}
void Freeze() {
mIsFrozen = true;
// Now that we are frozen, the meaning of mHadMinViolation and
// mHadMaxViolation changes to indicate min and max clamping. Clear
// both of the member variables so that they are ready to be set
// as clamping state later, if necessary.
mHadMinViolation = false;
mHadMaxViolation = false;
}
void SetHadMinViolation() {
MOZ_ASSERT(!mIsFrozen,
"shouldn't be changing main size & having violations "
"after we're frozen");
mHadMinViolation = true;
}
void SetHadMaxViolation() {
MOZ_ASSERT(!mIsFrozen,
"shouldn't be changing main size & having violations "
"after we're frozen");
mHadMaxViolation = true;
}
void ClearViolationFlags() {
MOZ_ASSERT(!mIsFrozen,
"shouldn't be altering violation flags after we're "
"frozen");
mHadMinViolation = mHadMaxViolation = false;
}
void SetWasMinClamped() {
MOZ_ASSERT(!mHadMinViolation && !mHadMaxViolation, "only clamp once");
// This reuses the mHadMinViolation member variable to track clamping
// events. This is allowable because mHadMinViolation only reflects
// a violation up until the item is frozen.
MOZ_ASSERT(mIsFrozen, "shouldn't set clamping state when we are unfrozen");
mHadMinViolation = true;
}
void SetWasMaxClamped() {
MOZ_ASSERT(!mHadMinViolation && !mHadMaxViolation, "only clamp once");
// This reuses the mHadMaxViolation member variable to track clamping
// events. This is allowable because mHadMaxViolation only reflects
// a violation up until the item is frozen.
MOZ_ASSERT(mIsFrozen, "shouldn't set clamping state when we are unfrozen");
mHadMaxViolation = true;
}
// Setters for values that are determined after we've resolved our main size
// -------------------------------------------------------------------------
// Sets the main-axis position of our flex item's content-box.
// (This is the distance between the main-start edge of the flex container
// and the main-start edge of the flex item's content-box.)
void SetMainPosition(nscoord aPosn) {
MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
mMainPosn = aPosn;
}
// Sets the cross-size of our flex item's content-box.
void SetCrossSize(nscoord aCrossSize) {
MOZ_ASSERT(!mIsStretched,
"Cross size shouldn't be modified after it's been stretched");
mCrossSize = aCrossSize;
}
// Sets the cross-axis position of our flex item's content-box.
// (This is the distance between the cross-start edge of the flex container
// and the cross-start edge of the flex item.)
void SetCrossPosition(nscoord aPosn) {
MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
mCrossPosn = aPosn;
}
// After a FlexItem has had a reflow, this method can be used to cache its
// (possibly-unresolved) ascent, in case it's needed later for
// baseline-alignment or to establish the container's baseline.
// (NOTE: This can be marked 'const' even though it's modifying mAscent,
// because mAscent is mutable. It's nice for this to be 'const', because it
// means our final reflow can iterate over const FlexItem pointers, and we
// can be sure it's not modifying those FlexItems, except via this method.)
void SetAscent(nscoord aAscent) const {
mAscent = aAscent; // NOTE: this may be ASK_FOR_BASELINE
}
void SetHadMeasuringReflow() { mHadMeasuringReflow = true; }
void SetIsStretched() {
MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
mIsStretched = true;
}
// Setter for margin components (for resolving "auto" margins)
void SetMarginComponentForSide(LogicalSide aSide, nscoord aLength) {
MOZ_ASSERT(mIsFrozen, "main size should be resolved before this");
mMargin.Side(aSide, mCBWM) = aLength;
}
void ResolveStretchedCrossSize(nscoord aLineCrossSize);
// Resolves flex base size if flex-basis' used value is 'content', using this
// item's preferred aspect ratio and cross size.
void ResolveFlexBaseSizeFromAspectRatio(const ReflowInput& aItemReflowInput);
uint32_t NumAutoMarginsInMainAxis() const {
return NumAutoMarginsInAxis(MainAxis());
};
uint32_t NumAutoMarginsInCrossAxis() const {
return NumAutoMarginsInAxis(CrossAxis());
};
// Once the main size has been resolved, should we bother doing layout to
// establish the cross size?
bool CanMainSizeInfluenceCrossSize() const;
// Returns a main size, clamped by any definite min and max cross size
// converted through the preferred aspect ratio. The caller is responsible for
// ensuring that the flex item's preferred aspect ratio is not zero.
nscoord ClampMainSizeViaCrossAxisConstraints(
nscoord aMainSize, const ReflowInput& aItemReflowInput) const;
// Indicates whether we think this flex item needs a "final" reflow
// (after its final flexed size & final position have been determined).
//
// @param aAvailableBSizeForItem the available block-size for this item (in
// flex container's writing-mode)
// @return true if such a reflow is needed, or false if we believe it can
// simply be moved to its final position and skip the reflow.
bool NeedsFinalReflow(const nscoord aAvailableBSizeForItem) const;
// Gets the block frame that contains the flex item's content. This is
// Frame() itself or one of its descendants.
nsBlockFrame* BlockFrame() const;
protected:
// Helper called by the constructor, to set mNeedsMinSizeAutoResolution:
void CheckForMinSizeAuto(const ReflowInput& aFlexItemReflowInput,
const FlexboxAxisTracker& aAxisTracker);
uint32_t NumAutoMarginsInAxis(LogicalAxis aAxis) const;
// Values that we already know in constructor, and remain unchanged:
// The flex item's frame.
nsIFrame* mFrame = nullptr;
float mFlexGrow = 0.0f;
float mFlexShrink = 0.0f;
AspectRatio mAspectRatio;
// The flex item's writing mode.
WritingMode mWM;
// The flex container's writing mode.
WritingMode mCBWM;
// The flex container's main axis in flex container's writing mode.
LogicalAxis mMainAxis;
// Stored in flex container's writing mode.
LogicalMargin mBorderPadding;
// Stored in flex container's writing mode. Its value can change when we
// resolve "auto" marigns.
LogicalMargin mMargin;
// These are non-const so that we can lazily update them with the item's
// intrinsic size (obtained via a "measuring" reflow), when necessary.
// (e.g. for "flex-basis:auto;height:auto" & "min-height:auto")
nscoord mFlexBaseSize = 0;
nscoord mMainMinSize = 0;
nscoord mMainMaxSize = 0;
// mCrossMinSize and mCrossMaxSize are not changed after constructor.
nscoord mCrossMinSize = 0;
nscoord mCrossMaxSize = 0;
// Values that we compute after constructor:
nscoord mMainSize = 0;
nscoord mMainPosn = 0;
nscoord mCrossSize = 0;
nscoord mCrossPosn = 0;
// Mutable b/c it's set & resolved lazily, sometimes via const pointer. See
// comment above SetAscent().
// We initialize this to ASK_FOR_BASELINE, and opportunistically fill it in
// with a real value if we end up reflowing this flex item. (But if we don't
// reflow this flex item, then this sentinel tells us that we don't know it
// yet & anyone who cares will need to explicitly request it.)
mutable nscoord mAscent = ReflowOutput::ASK_FOR_BASELINE;
// Temporary state, while we're resolving flexible widths (for our main size)
// XXXdholbert To save space, we could use a union to make these variables
// overlay the same memory as some other member vars that aren't touched
// until after main-size has been resolved. In particular, these could share
// memory with mMainPosn through mAscent, and mIsStretched.
float mShareOfWeightSoFar = 0.0f;
bool mIsFrozen = false;
bool mHadMinViolation = false;
bool mHadMaxViolation = false;
// Did this item get a preliminary reflow, to measure its desired height?
bool mHadMeasuringReflow = false;
// See IsStretched() documentation.
bool mIsStretched = false;
// Is this item a "strut" left behind by an element with visibility:collapse?
bool mIsStrut = false;
// See IsInlineAxisMainAxis() documentation. This is not changed after
// constructor.
bool mIsInlineAxisMainAxis = true;
// Does this item need to resolve a min-[width|height]:auto (in main-axis).
bool mNeedsMinSizeAutoResolution = false;
// Should we take care to treat this item's resolved BSize as indefinite?
bool mTreatBSizeAsIndefinite = false;
// Does this item have an auto margin in either main or cross axis?
bool mHasAnyAutoMargin = false;
// My "align-self" computed value (with "auto" swapped out for parent"s
// "align-items" value, in our constructor).
StyleAlignSelf mAlignSelf{StyleAlignFlags::AUTO};
// Flags for 'align-self' (safe/unsafe/legacy).
StyleAlignFlags mAlignSelfFlags{0};
};
/**
* Represents a single flex line in a flex container.
* Manages an array of the FlexItems that are in the line.
*/
class nsFlexContainerFrame::FlexLine final {
public:
explicit FlexLine(nscoord aMainGapSize) : mMainGapSize(aMainGapSize) {}
nscoord SumOfGaps() const {
return NumItems() > 0 ? (NumItems() - 1) * mMainGapSize : 0;
}
// Returns the sum of our FlexItems' outer hypothetical main sizes plus the
// sum of main axis {row,column}-gaps between items.
// ("outer" = margin-box, and "hypothetical" = before flexing)
nscoord TotalOuterHypotheticalMainSize() const {
return mTotalOuterHypotheticalMainSize;
}
// Accessors for our FlexItems & information about them:
//
// Note: Using IsEmpty() to ensure that the FlexLine is non-empty before
// calling FirstItem() or LastItem().
FlexItem& FirstItem() { return mItems[0]; }
const FlexItem& FirstItem() const { return mItems[0]; }
FlexItem& LastItem() { return mItems.LastElement(); }
const FlexItem& LastItem() const { return mItems.LastElement(); }
bool IsEmpty() const { return mItems.IsEmpty(); }
uint32_t NumItems() const { return mItems.Length(); }
nsTArray<FlexItem>& Items() { return mItems; }
const nsTArray<FlexItem>& Items() const { return mItems; }
// Adds the last flex item's hypothetical outer main-size and
// margin/border/padding to our totals. This should be called exactly once for
// each flex item, after we've determined that this line is the correct home
// for that item.
void AddLastItemToMainSizeTotals() {
const FlexItem& lastItem = Items().LastElement();
// Update our various bookkeeping member-vars:
if (lastItem.IsFrozen()) {
mNumFrozenItems++;
}
// Note: If our flex item is (or contains) a table with
// "table-layout:fixed", it may have a value near nscoord_MAX as its
// hypothetical main size. This means we can run into absurdly large sizes
// here, even when the author didn't explicitly specify anything huge.
// We'd really rather not allow that to cause integer overflow (e.g. we
// don't want that to make mTotalOuterHypotheticalMainSize overflow to a
// negative value), because that'd make us incorrectly think that we should
// grow our flex items rather than shrink them when it comes time to
// resolve flexible items. Hence, we sum up the hypothetical sizes using a
// helper function AddChecked() to avoid overflow.
mTotalItemMBP =
AddChecked(mTotalItemMBP, lastItem.MarginBorderPaddingSizeInMainAxis());
mTotalOuterHypotheticalMainSize =
AddChecked(mTotalOuterHypotheticalMainSize, lastItem.OuterMainSize());
// If the item added was not the first item in the line, we add in any gap
// space as needed.
if (NumItems() >= 2) {
mTotalOuterHypotheticalMainSize =
AddChecked(mTotalOuterHypotheticalMainSize, mMainGapSize);
}
}
// Computes the cross-size and baseline position of this FlexLine, based on
// its FlexItems.
void ComputeCrossSizeAndBaseline(const FlexboxAxisTracker& aAxisTracker);
// Returns the cross-size of this line.
nscoord LineCrossSize() const { return mLineCrossSize; }
// Setter for line cross-size -- needed for cases where the flex container
// imposes a cross-size on the line. (e.g. for single-line flexbox, or for
// multi-line flexbox with 'align-content: stretch')
void SetLineCrossSize(nscoord aLineCrossSize) {
mLineCrossSize = aLineCrossSize;
}
/**
* Returns the offset within this line where any baseline-aligned FlexItems
* should place their baseline. The return value represents a distance from
* the line's cross-start edge.
*
* If there are no baseline-aligned FlexItems, returns nscoord_MIN.
*/
nscoord FirstBaselineOffset() const { return mFirstBaselineOffset; }
/**
* Returns the offset within this line where any last baseline-aligned
* FlexItems should place their baseline. Opposite the case of the first
* baseline offset, this represents a distance from the line's cross-end
* edge (since last baseline-aligned items are flush to the cross-end edge).
* If we're internally reversing the axes, this instead represents the
* distance from the line's cross-start edge.
*
* If there are no last baseline-aligned FlexItems, returns nscoord_MIN.
*/
nscoord LastBaselineOffset() const { return mLastBaselineOffset; }
/**
* Returns the gap size in the main axis for this line. Used for gap
* calculations.
*/
nscoord MainGapSize() const { return mMainGapSize; }
// Runs the "Resolving Flexible Lengths" algorithm from section 9.7 of the
// CSS flexbox spec to distribute aFlexContainerMainSize among our flex items.
void ResolveFlexibleLengths(nscoord aFlexContainerMainSize,
ComputedFlexLineInfo* aLineInfo);
void PositionItemsInMainAxis(const StyleContentDistribution& aJustifyContent,
nscoord aContentBoxMainSize,
const FlexboxAxisTracker& aAxisTracker);
void PositionItemsInCrossAxis(nscoord aLineStartPosition,
const FlexboxAxisTracker& aAxisTracker);
private:
// Helpers for ResolveFlexibleLengths():
void FreezeItemsEarly(bool aIsUsingFlexGrow, ComputedFlexLineInfo* aLineInfo);
void FreezeOrRestoreEachFlexibleSize(const nscoord aTotalViolation,
bool aIsFinalIteration);
// Stores this line's flex items.
nsTArray<FlexItem> mItems;
// Number of *frozen* FlexItems in this line, based on FlexItem::IsFrozen().
// Mostly used for optimization purposes, e.g. to bail out early from loops
// when we can tell they have nothing left to do.
uint32_t mNumFrozenItems = 0;
// Sum of margin/border/padding for the FlexItems in this FlexLine.
nscoord mTotalItemMBP = 0;
// Sum of FlexItems' outer hypothetical main sizes and all main-axis
// {row,columnm}-gaps between items.
// (i.e. their flex base sizes, clamped via their min/max-size properties,
// plus their main-axis margin/border/padding, plus the sum of the gaps.)
nscoord mTotalOuterHypotheticalMainSize = 0;
nscoord mLineCrossSize = 0;
nscoord mFirstBaselineOffset = nscoord_MIN;
nscoord mLastBaselineOffset = nscoord_MIN;
// Maintain size of each {row,column}-gap in the main axis
const nscoord mMainGapSize;
};
// Information about a strut left behind by a FlexItem that's been collapsed
// using "visibility:collapse".
struct nsFlexContainerFrame::StrutInfo {
StrutInfo(uint32_t aItemIdx, nscoord aStrutCrossSize)
: mItemIdx(aItemIdx), mStrutCrossSize(aStrutCrossSize) {}
uint32_t mItemIdx; // Index in the child list.
nscoord mStrutCrossSize; // The cross-size of this strut.
};
// Flex data shared by the flex container frames in a continuation chain, owned
// by the first-in-flow. The data is initialized at the end of the
// first-in-flow's Reflow().
struct nsFlexContainerFrame::SharedFlexData {
nsTArray<FlexLine> mLines;
// The final content main/cross size computed by DoFlexLayout.
nscoord mContentBoxMainSize = NS_UNCONSTRAINEDSIZE;
nscoord mContentBoxCrossSize = NS_UNCONSTRAINEDSIZE;
// The frame property under which this struct is stored. Set only on the
// first-in-flow.
NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, SharedFlexData)
};
// Forward iterate all the FlexItems in aLines.
class nsFlexContainerFrame::FlexItemIterator final {
public:
explicit FlexItemIterator(const nsTArray<FlexLine>& aLines)
: mLineIter(aLines.begin()),
mLineIterEnd(aLines.end()),
mItemIter(mLineIter->Items().begin()),
mItemIterEnd(mLineIter->Items().end()) {
MOZ_ASSERT(mLineIter != mLineIterEnd,
"Flex container should have at least one FlexLine!");
if (mItemIter == mItemIterEnd) {
// The flex container is empty, so advance to mLineIterEnd.
++mLineIter;
MOZ_ASSERT(AtEnd());
}
}
void Next() {
MOZ_ASSERT(!AtEnd());
++mItemIter;
if (mItemIter == mItemIterEnd) {
// We are pointing to the end of the flex items, so advance to the next
// line.
++mLineIter;
if (mLineIter != mLineIterEnd) {
mItemIter = mLineIter->Items().begin();
mItemIterEnd = mLineIter->Items().end();
MOZ_ASSERT(mItemIter != mItemIterEnd,
"Why do we have a FlexLine with no FlexItem?");
}
}
}
bool AtEnd() const {
MOZ_ASSERT(
(mLineIter == mLineIterEnd && mItemIter == mItemIterEnd) ||
(mLineIter != mLineIterEnd && mItemIter != mItemIterEnd),
"Line & item iterators should agree on whether we're at the end!");
return mLineIter == mLineIterEnd;
}
const FlexItem& operator*() const {
MOZ_ASSERT(!AtEnd());
return mItemIter.operator*();
}
const FlexItem* operator->() const {
MOZ_ASSERT(!AtEnd());
return mItemIter.operator->();
}
private:
nsTArray<FlexLine>::const_iterator mLineIter;
nsTArray<FlexLine>::const_iterator mLineIterEnd;
nsTArray<FlexItem>::const_iterator mItemIter;
nsTArray<FlexItem>::const_iterator mItemIterEnd;
};
static void BuildStrutInfoFromCollapsedItems(const nsTArray<FlexLine>& aLines,
nsTArray<StrutInfo>& aStruts) {
MOZ_ASSERT(aStruts.IsEmpty(),
"We should only build up StrutInfo once per reflow, so "
"aStruts should be empty when this is called");
uint32_t itemIdxInContainer = 0;
for (const FlexLine& line : aLines) {
for (const FlexItem& item : line.Items()) {
if (StyleVisibility::Collapse ==
item.Frame()->StyleVisibility()->mVisible) {
// Note the cross size of the line as the item's strut size.
aStruts.AppendElement(
StrutInfo(itemIdxInContainer, line.LineCrossSize()));
}
itemIdxInContainer++;
}
}
}
static mozilla::StyleAlignFlags SimplifyAlignOrJustifyContentForOneItem(
const StyleContentDistribution& aAlignmentVal, bool aIsAlign) {
// Mask away any explicit fallback, to get the main (non-fallback) part of
// the specified value:
StyleAlignFlags specified = aAlignmentVal.primary;
// XXX strip off <overflow-position> bits until we implement it (bug 1311892)
specified &= ~StyleAlignFlags::FLAG_BITS;
// FIRST: handle a special-case for "justify-content:stretch" (or equivalent),
// which requires that we ignore any author-provided explicit fallback value.
if (specified == StyleAlignFlags::NORMAL) {
// In a flex container, *-content: "'normal' behaves as 'stretch'".
// Do that conversion early, so it benefits from our 'stretch' special-case.
specified = StyleAlignFlags::STRETCH;
}
if (!aIsAlign && specified == StyleAlignFlags::STRETCH) {
// In a flex container, in "justify-content Axis: [...] 'stretch' behaves
// as 'flex-start' (ignoring the specified fallback alignment, if any)."
// So, we just directly return 'flex-start', & ignore explicit fallback..
return StyleAlignFlags::FLEX_START;
}
// TODO: Check for an explicit fallback value (and if it's present, use it)
// here once we parse it, see https://github.com/w3c/csswg-drafts/issues/1002.
// If there's no explicit fallback, use the implied fallback values for
// space-{between,around,evenly} (since those values only make sense with
// multiple alignment subjects), and otherwise just use the specified value:
if (specified == StyleAlignFlags::SPACE_BETWEEN) {
return StyleAlignFlags::START;
}
if (specified == StyleAlignFlags::SPACE_AROUND ||
specified == StyleAlignFlags::SPACE_EVENLY) {
return StyleAlignFlags::CENTER;
}
return specified;
}
bool nsFlexContainerFrame::DrainSelfOverflowList() {
return DrainAndMergeSelfOverflowList();
}
void nsFlexContainerFrame::AppendFrames(ChildListID aListID,
nsFrameList& aFrameList) {
NoteNewChildren(aListID, aFrameList);
nsContainerFrame::AppendFrames(aListID, aFrameList);
}
void nsFlexContainerFrame::InsertFrames(
ChildListID aListID, nsIFrame* aPrevFrame,
const nsLineList::iterator* aPrevFrameLine, nsFrameList& aFrameList) {
NoteNewChildren(aListID, aFrameList);
nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
aFrameList);
}
void nsFlexContainerFrame::RemoveFrame(ChildListID aListID,
nsIFrame* aOldFrame) {
MOZ_ASSERT(aListID == kPrincipalList, "unexpected child list");
#ifdef DEBUG
SetDidPushItemsBitIfNeeded(aListID, aOldFrame);
#endif
nsContainerFrame::RemoveFrame(aListID, aOldFrame);
}
StyleAlignFlags nsFlexContainerFrame::CSSAlignmentForAbsPosChild(
const ReflowInput& aChildRI, LogicalAxis aLogicalAxis) const {
const FlexboxAxisTracker axisTracker(this);
// If we're row-oriented and the caller is asking about our inline axis (or
// alternately, if we're column-oriented and the caller is asking about our
// block axis), then the caller is really asking about our *main* axis.
// Otherwise, the caller is asking about our cross axis.
const bool isMainAxis =
(axisTracker.IsRowOriented() == (aLogicalAxis == eLogicalAxisInline));
const nsStylePosition* containerStylePos = StylePosition();
const bool isAxisReversed = isMainAxis ? axisTracker.IsMainAxisReversed()
: axisTracker.IsCrossAxisReversed();
StyleAlignFlags alignment{0};
StyleAlignFlags alignmentFlags{0};
if (isMainAxis) {
alignment = SimplifyAlignOrJustifyContentForOneItem(
containerStylePos->mJustifyContent,
/*aIsAlign = */ false);
} else {
const StyleAlignFlags alignContent =
SimplifyAlignOrJustifyContentForOneItem(
containerStylePos->mAlignContent,
/*aIsAlign = */ true);
if (StyleFlexWrap::Nowrap != containerStylePos->mFlexWrap &&
alignContent != StyleAlignFlags::STRETCH) {
// Multi-line, align-content isn't stretch --> align-content determines
// this child's alignment in the cross axis.
alignment = alignContent;
} else {
// Single-line, or multi-line but the (one) line stretches to fill
// container. Respect align-self.
alignment = aChildRI.mStylePosition->UsedAlignSelf(Style())._0;
// Extract and strip align flag bits
alignmentFlags = alignment & StyleAlignFlags::FLAG_BITS;
alignment &= ~StyleAlignFlags::FLAG_BITS;
if (alignment == StyleAlignFlags::NORMAL) {
// "the 'normal' keyword behaves as 'start' on replaced
// absolutely-positioned boxes, and behaves as 'stretch' on all other
// absolutely-positioned boxes."
alignment = aChildRI.mFrame->IsFrameOfType(nsIFrame::eReplaced)
? StyleAlignFlags::START
: StyleAlignFlags::STRETCH;
}
}
}
// Resolve flex-start, flex-end, auto, left, right, baseline, last baseline;
if (alignment == StyleAlignFlags::FLEX_START) {
alignment = isAxisReversed ? StyleAlignFlags::END : StyleAlignFlags::START;
} else if (alignment == StyleAlignFlags::FLEX_END) {
alignment = isAxisReversed ? StyleAlignFlags::START : StyleAlignFlags::END;
} else if (alignment == StyleAlignFlags::LEFT ||
alignment == StyleAlignFlags::RIGHT) {
if (aLogicalAxis == eLogicalAxisInline) {
const bool isLeft = (alignment == StyleAlignFlags::LEFT);
alignment = (isLeft == GetWritingMode().IsBidiLTR())
? StyleAlignFlags::START
: StyleAlignFlags::END;
} else {
alignment = StyleAlignFlags::START;
}
} else if (alignment == StyleAlignFlags::BASELINE) {
alignment = StyleAlignFlags::START;
} else if (alignment == StyleAlignFlags::LAST_BASELINE) {
alignment = StyleAlignFlags::END;
}
return (alignment | alignmentFlags);
}
FlexItem* nsFlexContainerFrame::GenerateFlexItemForChild(
FlexLine& aLine, nsIFrame* aChildFrame,
const ReflowInput& aParentReflowInput,
const FlexboxAxisTracker& aAxisTracker, bool aHasLineClampEllipsis) {
const auto flexWM = aAxisTracker.GetWritingMode();
const auto childWM = aChildFrame->GetWritingMode();
// Note: we use GetStyleFrame() to access the sizing & flex properties here.
// This lets us correctly handle table wrapper frames as flex items since
// their inline-size and block-size properties are always 'auto'. In order for
// 'flex-basis:auto' to actually resolve to the author's specified inline-size
// or block-size, we need to dig through to the inner table.
const auto* stylePos =
nsLayoutUtils::GetStyleFrame(aChildFrame)->StylePosition();
// Construct a StyleSizeOverrides for this flex item so that its ReflowInput
// below will use and resolve its flex base size rather than its corresponding
// preferred main size property (only for modern CSS flexbox).
StyleSizeOverrides sizeOverrides;
if (!IsLegacyBox(this)) {
Maybe<StyleSize> styleFlexBaseSize;
// When resolving flex base size, flex items use their 'flex-basis' property
// in place of their preferred main size (e.g. 'width') for sizing purposes,
// *unless* they have 'flex-basis:auto' in which case they use their
// preferred main size after all.
const auto& flexBasis = stylePos->mFlexBasis;
const auto& styleMainSize = stylePos->Size(aAxisTracker.MainAxis(), flexWM);
if (IsUsedFlexBasisContent(flexBasis, styleMainSize)) {
// If we get here, we're resolving the flex base size for a flex item, and
// we fall into the flexbox spec section 9.2 step 3, substep C (if we have
// a definite cross size) or E (if not).
if (aChildFrame->GetAspectRatio()) {
// FIXME: This is a workaround. Once bug 1670151 is fixed, aspect-ratio
// will be considered when resolving flex item's flex base size with the
// value 'max-content'.
styleFlexBaseSize.emplace(StyleSize::Auto());
} else {
styleFlexBaseSize.emplace(StyleSize::MaxContent());
}
} else if (flexBasis.IsSize() && !flexBasis.IsAuto()) {
// For all other non-'auto' flex-basis values, we just swap in the
// flex-basis itself for the preferred main-size property.
styleFlexBaseSize.emplace(flexBasis.AsSize());
} else {
// else: flex-basis is 'auto', which is deferring to some explicit value
// in the preferred main size.
MOZ_ASSERT(flexBasis.IsAuto());
styleFlexBaseSize.emplace(styleMainSize);
}
MOZ_ASSERT(styleFlexBaseSize, "We should've emplace styleFlexBaseSize!");
// Provide the size override for the preferred main size property.
if (aAxisTracker.IsInlineAxisMainAxis(childWM)) {
sizeOverrides.mStyleISize = std::move(styleFlexBaseSize);
} else {
sizeOverrides.mStyleBSize = std::move(styleFlexBaseSize);
}
// 'flex-basis' should works on the inner table frame for a table flex item,
// just like how 'height' works on a table element.
sizeOverrides.mApplyOverridesVerbatim = true;
}
// Create temporary reflow input just for sizing -- to get hypothetical
// main-size and the computed values of min / max main-size property.
// (This reflow input will _not_ be used for reflow.)
ReflowInput childRI(PresContext(), aParentReflowInput, aChildFrame,
aParentReflowInput.ComputedSize(childWM), Nothing(), {},
sizeOverrides);
childRI.mFlags.mInsideLineClamp = GetLineClampValue() != 0;
// FLEX GROW & SHRINK WEIGHTS
// --------------------------
float flexGrow, flexShrink;
if (IsLegacyBox(this)) {
if (GetLineClampValue() != 0) {
// Items affected by -webkit-line-clamp are always inflexible.
flexGrow = flexShrink = 0;
} else {
flexGrow = flexShrink = aChildFrame->StyleXUL()->mBoxFlex;
}
} else {
flexGrow = stylePos->mFlexGrow;
flexShrink = stylePos->mFlexShrink;
}
// MAIN SIZES (flex base size, min/max size)
// -----------------------------------------
nscoord flexBaseSize = GET_MAIN_COMPONENT_LOGICAL(
aAxisTracker, childWM, childRI.ComputedISize(), childRI.ComputedBSize());
nscoord mainMinSize = GET_MAIN_COMPONENT_LOGICAL(aAxisTracker, childWM,
childRI.ComputedMinISize(),
childRI.ComputedMinBSize());
nscoord mainMaxSize = GET_MAIN_COMPONENT_LOGICAL(aAxisTracker, childWM,
childRI.ComputedMaxISize(),
childRI.ComputedMaxBSize());
// This is enforced by the ReflowInput where these values come from:
MOZ_ASSERT(mainMinSize <= mainMaxSize, "min size is larger than max size");
// CROSS SIZES (tentative cross size, min/max cross size)
// ------------------------------------------------------
// Grab the cross size from the reflow input. This might be the right value,
// or we might resolve it to something else in SizeItemInCrossAxis(); hence,
// it's tentative. See comment under "Cross Size Determination" for more.
nscoord tentativeCrossSize = GET_CROSS_COMPONENT_LOGICAL(
aAxisTracker, childWM, childRI.ComputedISize(), childRI.ComputedBSize());
nscoord crossMinSize = GET_CROSS_COMPONENT_LOGICAL(
aAxisTracker, childWM, childRI.ComputedMinISize(),
childRI.ComputedMinBSize());
nscoord crossMaxSize = GET_CROSS_COMPONENT_LOGICAL(
aAxisTracker, childWM, childRI.ComputedMaxISize(),
childRI.ComputedMaxBSize());
// SPECIAL-CASE FOR WIDGET-IMPOSED SIZES
// Check if we're a themed widget, in which case we might have a minimum
// main & cross size imposed by our widget (which we can't go below), or
// (more severe) our widget might have only a single valid size.
bool isFixedSizeWidget = false;
const nsStyleDisplay* disp = aChildFrame->StyleDisplay();
if (aChildFrame->IsThemed(disp)) {
LayoutDeviceIntSize widgetMinSize;
bool canOverride = true;
PresContext()->Theme()->GetMinimumWidgetSize(PresContext(), aChildFrame,
disp->EffectiveAppearance(),
&widgetMinSize, &canOverride);
nscoord widgetMainMinSize = PresContext()->DevPixelsToAppUnits(
aAxisTracker.MainComponent(widgetMinSize));
nscoord widgetCrossMinSize = PresContext()->DevPixelsToAppUnits(
aAxisTracker.CrossComponent(widgetMinSize));
// GetMinimumWidgetSize() returns border-box. We need content-box, so
// subtract borderPadding.
const LogicalMargin bpInFlexWM =
childRI.ComputedLogicalBorderPadding(flexWM);
widgetMainMinSize -= aAxisTracker.MarginSizeInMainAxis(bpInFlexWM);
widgetCrossMinSize -= aAxisTracker.MarginSizeInCrossAxis(bpInFlexWM);
// ... (but don't let that push these min sizes below 0).
widgetMainMinSize = std::max(0, widgetMainMinSize);
widgetCrossMinSize = std::max(0, widgetCrossMinSize);
if (!canOverride) {
// Fixed-size widget: freeze our main-size at the widget's mandated size.
// (Set min and max main-sizes to that size, too, to keep us from
// clamping to any other size later on.)
flexBaseSize = mainMinSize = mainMaxSize = widgetMainMinSize;
tentativeCrossSize = crossMinSize = crossMaxSize = widgetCrossMinSize;
isFixedSizeWidget = true;
} else {
// Variable-size widget: ensure our min/max sizes are at least as large
// as the widget's mandated minimum size, so we don't flex below that.
mainMinSize = std::max(mainMinSize, widgetMainMinSize);
mainMaxSize = std::max(mainMaxSize, widgetMainMinSize);
if (tentativeCrossSize != NS_UNCONSTRAINEDSIZE) {
tentativeCrossSize = std::max(tentativeCrossSize, widgetCrossMinSize);
}
crossMinSize = std::max(crossMinSize, widgetCrossMinSize);
crossMaxSize = std::max(crossMaxSize, widgetCrossMinSize);
}
}
// Construct the flex item!
FlexItem* item = aLine.Items().EmplaceBack(
childRI, flexGrow, flexShrink, flexBaseSize, mainMinSize, mainMaxSize,
tentativeCrossSize, crossMinSize, crossMaxSize, aAxisTracker);
// We may be about to do computations based on our item's cross-size
// (e.g. using it as a constraint when measuring our content in the
// main axis, or using it with the preferred aspect ratio to obtain a main
// size). BEFORE WE DO THAT, we need let the item "pre-stretch" its cross size
// (if it's got 'align-self:stretch'), for a certain case where the spec says
// the stretched cross size is considered "definite". That case is if we
// have a single-line (nowrap) flex container which itself has a definite
// cross-size. Otherwise, we'll wait to do stretching, since (in other
// cases) we don't know how much the item should stretch yet.
const bool isSingleLine =
StyleFlexWrap::Nowrap == aParentReflowInput.mStylePosition->mFlexWrap;
if (isSingleLine) {
// XXXdholbert Maybe this should share logic with ComputeCrossSize()...
// Alternately, maybe tentative container cross size should be passed down.
nscoord containerCrossSize = GET_CROSS_COMPONENT_LOGICAL(
aAxisTracker, flexWM, aParentReflowInput.ComputedISize(),
aParentReflowInput.ComputedBSize());
// Is container's cross size "definite"?
// - If it's column-oriented, then "yes", because its cross size is its
// inline-size which is always definite from its descendants' perspective.
// - Otherwise (if it's row-oriented), then we check the actual size
// and call it definite if it's not NS_UNCONSTRAINEDSIZE.
if (aAxisTracker.IsColumnOriented() ||
containerCrossSize != NS_UNCONSTRAINEDSIZE) {
// Container's cross size is "definite", so we can resolve the item's
// stretched cross size using that.
item->ResolveStretchedCrossSize(containerCrossSize);
}
}
// Before thinking about freezing the item at its base size, we need to give
// it a chance to recalculate the base size from its cross size and aspect
// ratio (since its cross size might've *just* now become definite due to
// 'stretch' above)
item->ResolveFlexBaseSizeFromAspectRatio(childRI);
// If we're inflexible, we can just freeze to our hypothetical main-size
// up-front. Similarly, if we're a fixed-size widget, we only have one
// valid size, so we freeze to keep ourselves from flexing.
if (isFixedSizeWidget || (flexGrow == 0.0f && flexShrink == 0.0f)) {
item->Freeze();
if (flexBaseSize < mainMinSize) {
item->SetWasMinClamped();
} else if (flexBaseSize > mainMaxSize) {
item->SetWasMaxClamped();
}
}
// Resolve "flex-basis:auto" and/or "min-[width|height]:auto" (which might
// require us to reflow the item to measure content height)
ResolveAutoFlexBasisAndMinSize(*item, childRI, aAxisTracker,
aHasLineClampEllipsis);
return item;
}
// Static helper-functions for ResolveAutoFlexBasisAndMinSize():
// -------------------------------------------------------------
// Partially resolves "min-[width|height]:auto" and returns the resulting value.
// By "partially", I mean we don't consider the min-content size (but we do
// consider the main-size and main max-size properties, and the preferred aspect
// ratio). The caller is responsible for computing & considering the min-content
// size in combination with the partially-resolved value that this function
// returns.
//
// Basically, this function gets the specified size suggestion; if not, the
// transferred size suggestion; if both sizes do not exist, return nscoord_MAX.
//
static nscoord PartiallyResolveAutoMinSize(
const FlexItem& aFlexItem, const ReflowInput& aItemReflowInput,
const FlexboxAxisTracker& aAxisTracker) {
MOZ_ASSERT(aFlexItem.NeedsMinSizeAutoResolution(),
"only call for FlexItems that need min-size auto resolution");
const auto itemWM = aFlexItem.GetWritingMode();
const auto cbWM = aAxisTracker.GetWritingMode();
const auto& mainStyleSize =
aItemReflowInput.mStylePosition->Size(aAxisTracker.MainAxis(), cbWM);
const auto& maxMainStyleSize =
aItemReflowInput.mStylePosition->MaxSize(aAxisTracker.MainAxis(), cbWM);
const auto boxSizingAdjust =
aItemReflowInput.mStylePosition->mBoxSizing == StyleBoxSizing::Border
? aFlexItem.BorderPadding().Size(cbWM)
: LogicalSize(