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: grid | inline-grid" */
#include "nsGridContainerFrame.h"
#include <functional>
#include <limits>
#include <stdlib.h> // for div()
#include <type_traits>
#include "gfxContext.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/CSSAlignUtils.h"
#include "mozilla/dom/GridBinding.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/Maybe.h"
#include "mozilla/PodOperations.h" // for PodZero
#include "mozilla/Poison.h"
#include "mozilla/PresShell.h"
#include "nsAbsoluteContainingBlock.h"
#include "nsAlgorithm.h" // for clamped()
#include "nsBoxLayoutState.h"
#include "nsCSSAnonBoxes.h"
#include "nsCSSFrameConstructor.h"
#include "nsTHashMap.h"
#include "nsDisplayList.h"
#include "nsHashKeys.h"
#include "nsFieldSetFrame.h"
#include "nsIFrameInlines.h"
#include "nsPlaceholderFrame.h"
#include "nsPresContext.h"
#include "nsReadableUtils.h"
#include "nsTableWrapperFrame.h"
using namespace mozilla;
typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags;
typedef nsGridContainerFrame::TrackSize TrackSize;
typedef mozilla::CSSAlignUtils::AlignJustifyFlags AlignJustifyFlags;
using GridTemplate = StyleGridTemplateComponent;
using TrackListValue =
StyleGenericTrackListValue<LengthPercentage, StyleInteger>;
using TrackRepeat = StyleGenericTrackRepeat<LengthPercentage, StyleInteger>;
using NameList = StyleOwnedSlice<StyleCustomIdent>;
using SizingConstraint = nsGridContainerFrame::SizingConstraint;
static const int32_t kMaxLine = StyleMAX_GRID_LINE;
static const int32_t kMinLine = StyleMIN_GRID_LINE;
// The maximum line number, in the zero-based translated grid.
static const uint32_t kTranslatedMaxLine = uint32_t(kMaxLine - kMinLine);
static const uint32_t kAutoLine = kTranslatedMaxLine + 3457U;
static const nsFrameState kIsSubgridBits =
(NS_STATE_GRID_IS_COL_SUBGRID | NS_STATE_GRID_IS_ROW_SUBGRID);
namespace mozilla {
template <>
inline Span<const StyleOwnedSlice<StyleCustomIdent>>
GridTemplate::LineNameLists(bool aIsSubgrid) const {
if (IsTrackList()) {
return AsTrackList()->line_names.AsSpan();
}
if (IsSubgrid() && aIsSubgrid) {
return AsSubgrid()->names.AsSpan();
}
MOZ_ASSERT(IsNone() || IsMasonry() || (IsSubgrid() && !aIsSubgrid));
return {};
}
template <>
inline const StyleTrackBreadth& StyleTrackSize::GetMax() const {
if (IsBreadth()) {
return AsBreadth();
}
if (IsMinmax()) {
return AsMinmax()._1;
}
MOZ_ASSERT(IsFitContent());
return AsFitContent();
}
template <>
inline const StyleTrackBreadth& StyleTrackSize::GetMin() const {
static const StyleTrackBreadth kAuto = StyleTrackBreadth::Auto();
if (IsBreadth()) {
// <flex> behaves like minmax(auto, <flex>)
return AsBreadth().IsFr() ? kAuto : AsBreadth();
}
if (IsMinmax()) {
return AsMinmax()._0;
}
MOZ_ASSERT(IsFitContent());
return kAuto;
}
} // namespace mozilla
static nscoord ClampToCSSMaxBSize(nscoord aSize,
const ReflowInput* aReflowInput) {
auto maxSize = aReflowInput->ComputedMaxBSize();
if (MOZ_UNLIKELY(maxSize != NS_UNCONSTRAINEDSIZE)) {
MOZ_ASSERT(aReflowInput->ComputedMinBSize() <= maxSize);
aSize = std::min(aSize, maxSize);
}
return aSize;
}
// Same as above and set aStatus INCOMPLETE if aSize wasn't clamped.
// (If we clamp aSize it means our size is less than the break point,
// i.e. we're effectively breaking in our overflow, so we should leave
// aStatus as is (it will likely be set to OVERFLOW_INCOMPLETE later)).
static nscoord ClampToCSSMaxBSize(nscoord aSize,
const ReflowInput* aReflowInput,
nsReflowStatus* aStatus) {
auto maxSize = aReflowInput->ComputedMaxBSize();
if (MOZ_UNLIKELY(maxSize != NS_UNCONSTRAINEDSIZE)) {
MOZ_ASSERT(aReflowInput->ComputedMinBSize() <= maxSize);
if (aSize < maxSize) {
aStatus->SetIncomplete();
} else {
aSize = maxSize;
}
} else {
aStatus->SetIncomplete();
}
return aSize;
}
template <typename Size>
static bool IsPercentOfIndefiniteSize(const Size& aCoord,
nscoord aPercentBasis) {
return aPercentBasis == NS_UNCONSTRAINEDSIZE && aCoord.HasPercent();
}
static nscoord ResolveToDefiniteSize(const StyleTrackBreadth& aBreadth,
nscoord aPercentBasis) {
MOZ_ASSERT(aBreadth.IsBreadth());
if (::IsPercentOfIndefiniteSize(aBreadth.AsBreadth(), aPercentBasis)) {
return nscoord(0);
}
return std::max(nscoord(0), aBreadth.AsBreadth().Resolve(aPercentBasis));
}
// Synthesize a baseline from a border box. For an alphabetical baseline
// this is the end edge of the border box. For a central baseline it's
// the center of the border box.
// For a 'first baseline' the measure is from the border-box start edge and
// for a 'last baseline' the measure is from the border-box end edge.
static nscoord SynthesizeBaselineFromBorderBox(BaselineSharingGroup aGroup,
WritingMode aWM,
nscoord aBorderBoxSize) {
if (aGroup == BaselineSharingGroup::First) {
return aWM.IsAlphabeticalBaseline() ? aBorderBoxSize : aBorderBoxSize / 2;
}
MOZ_ASSERT(aGroup == BaselineSharingGroup::Last);
// Round up for central baseline offset, to be consistent with eFirst.
return aWM.IsAlphabeticalBaseline()
? 0
: (aBorderBoxSize / 2) + (aBorderBoxSize % 2);
}
// The input sizes for calculating the number of repeat(auto-fill/fit) tracks.
struct RepeatTrackSizingInput {
explicit RepeatTrackSizingInput(WritingMode aWM)
: mMin(aWM, 0, 0),
mSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
mMax(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) {}
RepeatTrackSizingInput(const LogicalSize& aMin, const LogicalSize& aSize,
const LogicalSize& aMax)
: mMin(aMin), mSize(aSize), mMax(aMax) {}
// This should be used in intrinsic sizing (i.e. when we can't initialize
// the sizes directly from ReflowInput values).
void InitFromStyle(LogicalAxis aAxis, WritingMode aWM,
const ComputedStyle* aStyle) {
const auto& pos = aStyle->StylePosition();
const bool borderBoxSizing = pos->mBoxSizing == StyleBoxSizing::Border;
nscoord bp = NS_UNCONSTRAINEDSIZE; // a sentinel to calculate it only once
auto adjustForBoxSizing = [borderBoxSizing, aWM, aAxis, aStyle,
&bp](nscoord aSize) {
if (!borderBoxSizing) {
return aSize;
}
if (bp == NS_UNCONSTRAINEDSIZE) {
const auto& padding = aStyle->StylePadding()->mPadding;
LogicalMargin border(aWM, aStyle->StyleBorder()->GetComputedBorder());
// We can use zero percentage basis since this is only called from
// intrinsic sizing code.
const nscoord percentageBasis = 0;
if (aAxis == eLogicalAxisInline) {
bp = std::max(padding.GetIStart(aWM).Resolve(percentageBasis), 0) +
std::max(padding.GetIEnd(aWM).Resolve(percentageBasis), 0) +
border.IStartEnd(aWM);
} else {
bp = std::max(padding.GetBStart(aWM).Resolve(percentageBasis), 0) +
std::max(padding.GetBEnd(aWM).Resolve(percentageBasis), 0) +
border.BStartEnd(aWM);
}
}
return std::max(aSize - bp, 0);
};
nscoord& min = mMin.Size(aAxis, aWM);
nscoord& size = mSize.Size(aAxis, aWM);
nscoord& max = mMax.Size(aAxis, aWM);
const auto& minCoord =
aAxis == eLogicalAxisInline ? pos->MinISize(aWM) : pos->MinBSize(aWM);
if (minCoord.ConvertsToLength()) {
min = adjustForBoxSizing(minCoord.ToLength());
}
const auto& maxCoord =
aAxis == eLogicalAxisInline ? pos->MaxISize(aWM) : pos->MaxBSize(aWM);
if (maxCoord.ConvertsToLength()) {
max = std::max(min, adjustForBoxSizing(maxCoord.ToLength()));
}
const auto& sizeCoord =
aAxis == eLogicalAxisInline ? pos->ISize(aWM) : pos->BSize(aWM);
if (sizeCoord.ConvertsToLength()) {
size = Clamp(adjustForBoxSizing(sizeCoord.ToLength()), min, max);
}
}
LogicalSize mMin;
LogicalSize mSize;
LogicalSize mMax;
};
enum class GridLineSide {
BeforeGridGap,
AfterGridGap,
};
struct nsGridContainerFrame::TrackSize {
enum StateBits : uint16_t {
// clang-format off
eAutoMinSizing = 0x1,
eMinContentMinSizing = 0x2,
eMaxContentMinSizing = 0x4,
eMinOrMaxContentMinSizing = eMinContentMinSizing | eMaxContentMinSizing,
eIntrinsicMinSizing = eMinOrMaxContentMinSizing | eAutoMinSizing,
eModified = 0x8,
eAutoMaxSizing = 0x10,
eMinContentMaxSizing = 0x20,
eMaxContentMaxSizing = 0x40,
eAutoOrMaxContentMaxSizing = eAutoMaxSizing | eMaxContentMaxSizing,
eIntrinsicMaxSizing = eAutoOrMaxContentMaxSizing | eMinContentMaxSizing,
eFlexMaxSizing = 0x80,
eFrozen = 0x100,
eSkipGrowUnlimited1 = 0x200,
eSkipGrowUnlimited2 = 0x400,
eSkipGrowUnlimited = eSkipGrowUnlimited1 | eSkipGrowUnlimited2,
eBreakBefore = 0x800,
eFitContent = 0x1000,
eInfinitelyGrowable = 0x2000,
// These are only used in the masonry axis. They share the same value
// as *MinSizing above, but that's OK because we don't use those in
// the masonry axis.
//
// This track corresponds to an item margin-box size that is stretching.
eItemStretchSize = 0x1,
// This bit says that we should clamp that size to mLimit.
eClampToLimit = 0x2,
// This bit says that the corresponding item has `auto` margin(s).
eItemHasAutoMargin = 0x4,
// clang-format on
};
StateBits Initialize(nscoord aPercentageBasis, const StyleTrackSize&);
bool IsFrozen() const { return mState & eFrozen; }
#ifdef DEBUG
static void DumpStateBits(StateBits aState);
void Dump() const;
#endif
static bool IsDefiniteMaxSizing(StateBits aStateBits) {
return (aStateBits & (eIntrinsicMaxSizing | eFlexMaxSizing)) == 0;
}
nscoord mBase;
nscoord mLimit;
nscoord mPosition; // zero until we apply 'align/justify-content'
// mBaselineSubtreeSize is the size of a baseline-aligned subtree within
// this track. One subtree per baseline-sharing group (per track).
PerBaseline<nscoord> mBaselineSubtreeSize;
StateBits mState;
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(TrackSize::StateBits)
namespace mozilla {
template <>
struct IsPod<nsGridContainerFrame::TrackSize> : std::true_type {};
} // namespace mozilla
TrackSize::StateBits nsGridContainerFrame::TrackSize::Initialize(
nscoord aPercentageBasis, const StyleTrackSize& aSize) {
using Tag = StyleTrackBreadth::Tag;
MOZ_ASSERT(mBase == 0 && mLimit == 0 && mState == 0,
"track size data is expected to be initialized to zero");
mBaselineSubtreeSize[BaselineSharingGroup::First] = nscoord(0);
mBaselineSubtreeSize[BaselineSharingGroup::Last] = nscoord(0);
auto& min = aSize.GetMin();
auto& max = aSize.GetMax();
Tag minSizeTag = min.tag;
Tag maxSizeTag = max.tag;
if (aSize.IsFitContent()) {
// In layout, fit-content(size) behaves as minmax(auto, max-content), with
// 'size' as an additional upper-bound.
mState = eFitContent;
minSizeTag = Tag::Auto;
maxSizeTag = Tag::MaxContent;
}
if (::IsPercentOfIndefiniteSize(min, aPercentageBasis)) {
// "If the inline or block size of the grid container is indefinite,
// <percentage> values relative to that size are treated as 'auto'."
minSizeTag = Tag::Auto;
}
if (::IsPercentOfIndefiniteSize(max, aPercentageBasis)) {
maxSizeTag = Tag::Auto;
}
switch (minSizeTag) {
case Tag::Auto:
mState |= eAutoMinSizing;
break;
case Tag::MinContent:
mState |= eMinContentMinSizing;
break;
case Tag::MaxContent:
mState |= eMaxContentMinSizing;
break;
default:
MOZ_ASSERT(!min.IsFr(), "<flex> min-sizing is invalid as a track size");
mBase = ::ResolveToDefiniteSize(min, aPercentageBasis);
}
switch (maxSizeTag) {
case Tag::Auto:
mState |= eAutoMaxSizing;
mLimit = NS_UNCONSTRAINEDSIZE;
break;
case Tag::MinContent:
case Tag::MaxContent:
mState |= maxSizeTag == Tag::MinContent ? eMinContentMaxSizing
: eMaxContentMaxSizing;
mLimit = NS_UNCONSTRAINEDSIZE;
break;
case Tag::Fr:
mState |= eFlexMaxSizing;
mLimit = mBase;
break;
default:
mLimit = ::ResolveToDefiniteSize(max, aPercentageBasis);
if (mLimit < mBase) {
mLimit = mBase;
}
}
return mState;
}
/**
* A LineRange can be definite or auto - when it's definite it represents
* a consecutive set of tracks between a starting line and an ending line.
* Before it's definite it can also represent an auto position with a span,
* where mStart == kAutoLine and mEnd is the (non-zero positive) span.
* For normal-flow items, the invariant mStart < mEnd holds when both
* lines are definite.
*
* For abs.pos. grid items, mStart and mEnd may both be kAutoLine, meaning
* "attach this side to the grid container containing block edge".
* Additionally, mStart <= mEnd holds when both are definite (non-kAutoLine),
* i.e. the invariant is slightly relaxed compared to normal flow items.
*/
struct nsGridContainerFrame::LineRange {
LineRange(int32_t aStart, int32_t aEnd)
: mUntranslatedStart(aStart), mUntranslatedEnd(aEnd) {
#ifdef DEBUG
if (!IsAutoAuto()) {
if (IsAuto()) {
MOZ_ASSERT(aEnd >= kMinLine && aEnd <= kMaxLine, "invalid span");
} else {
MOZ_ASSERT(aStart >= kMinLine && aStart <= kMaxLine,
"invalid start line");
MOZ_ASSERT(aEnd == int32_t(kAutoLine) ||
(aEnd >= kMinLine && aEnd <= kMaxLine),
"invalid end line");
}
}
#endif
}
bool IsAutoAuto() const { return mStart == kAutoLine && mEnd == kAutoLine; }
bool IsAuto() const { return mStart == kAutoLine; }
bool IsDefinite() const { return mStart != kAutoLine; }
uint32_t Extent() const {
MOZ_ASSERT(mEnd != kAutoLine, "Extent is undefined for abs.pos. 'auto'");
if (IsAuto()) {
MOZ_ASSERT(mEnd >= 1 && mEnd < uint32_t(kMaxLine), "invalid span");
return mEnd;
}
return mEnd - mStart;
}
/**
* Return an object suitable for iterating this range.
*/
auto Range() const { return IntegerRange<uint32_t>(mStart, mEnd); }
/**
* Resolve this auto range to start at aStart, making it definite.
* @param aClampMaxLine the maximum allowed line number (zero-based)
* Precondition: this range IsAuto()
*/
void ResolveAutoPosition(uint32_t aStart, uint32_t aClampMaxLine) {
MOZ_ASSERT(IsAuto(), "Why call me?");
mStart = aStart;
mEnd += aStart;
// Clamp to aClampMaxLine, which is where kMaxLine is in the explicit
// grid in a non-subgrid axis; this implements clamping per
// In a subgrid axis it's the end of the grid in that axis.
if (MOZ_UNLIKELY(mStart >= aClampMaxLine)) {
mEnd = aClampMaxLine;
mStart = mEnd - 1;
} else if (MOZ_UNLIKELY(mEnd > aClampMaxLine)) {
mEnd = aClampMaxLine;
}
}
/**
* Translate the lines to account for (empty) removed tracks. This method
* is only for grid items and should only be called after placement.
* aNumRemovedTracks contains a count for each line in the grid how many
* tracks were removed between the start of the grid and that line.
*/
void AdjustForRemovedTracks(const nsTArray<uint32_t>& aNumRemovedTracks) {
MOZ_ASSERT(mStart != kAutoLine, "invalid resolved line for a grid item");
MOZ_ASSERT(mEnd != kAutoLine, "invalid resolved line for a grid item");
uint32_t numRemovedTracks = aNumRemovedTracks[mStart];
MOZ_ASSERT(numRemovedTracks == aNumRemovedTracks[mEnd],
"tracks that a grid item spans can't be removed");
mStart -= numRemovedTracks;
mEnd -= numRemovedTracks;
}
/**
* Translate the lines to account for (empty) removed tracks. This method
* is only for abs.pos. children and should only be called after placement.
* Same as for in-flow items, but we don't touch 'auto' lines here and we
* also need to adjust areas that span into the removed tracks.
*/
void AdjustAbsPosForRemovedTracks(
const nsTArray<uint32_t>& aNumRemovedTracks) {
if (mStart != kAutoLine) {
mStart -= aNumRemovedTracks[mStart];
}
if (mEnd != kAutoLine) {
MOZ_ASSERT(mStart == kAutoLine || mEnd > mStart, "invalid line range");
mEnd -= aNumRemovedTracks[mEnd];
}
}
/**
* Return the contribution of this line range for step 2 in
*/
uint32_t HypotheticalEnd() const { return mEnd; }
/**
* Given an array of track sizes, return the starting position and length
* of the tracks in this line range.
*/
void ToPositionAndLength(const nsTArray<TrackSize>& aTrackSizes,
nscoord* aPos, nscoord* aLength) const;
/**
* Given an array of track sizes, return the length of the tracks in this
* line range.
*/
nscoord ToLength(const nsTArray<TrackSize>& aTrackSizes) const;
/**
* Given an array of track sizes and a grid origin coordinate, adjust the
* abs.pos. containing block along an axis given by aPos and aLength.
* aPos and aLength should already be initialized to the grid container
* containing block for this axis before calling this method.
*/
void ToPositionAndLengthForAbsPos(const Tracks& aTracks, nscoord aGridOrigin,
nscoord* aPos, nscoord* aLength) const;
void Translate(int32_t aOffset) {
MOZ_ASSERT(IsDefinite());
mStart += aOffset;
mEnd += aOffset;
}
/** Swap the start/end sides of this range. */
void ReverseDirection(uint32_t aGridEnd) {
MOZ_ASSERT(IsDefinite());
MOZ_ASSERT(aGridEnd >= mEnd);
uint32_t newStart = aGridEnd - mEnd;
mEnd = aGridEnd - mStart;
mStart = newStart;
}
/**
* @note We'll use the signed member while resolving definite positions
* to line numbers (1-based), which may become negative for implicit lines
* to the top/left of the explicit grid. PlaceGridItems() then translates
* the whole grid to a 0,0 origin and we'll use the unsigned member from
* there on.
*/
union {
uint32_t mStart;
int32_t mUntranslatedStart;
};
union {
uint32_t mEnd;
int32_t mUntranslatedEnd;
};
protected:
LineRange() : mStart(0), mEnd(0) {}
};
/**
* Helper class to construct a LineRange from translated lines.
* The ctor only accepts translated definite line numbers.
*/
struct nsGridContainerFrame::TranslatedLineRange : public LineRange {
TranslatedLineRange(uint32_t aStart, uint32_t aEnd) {
MOZ_ASSERT(aStart < aEnd && aEnd <= kTranslatedMaxLine);
mStart = aStart;
mEnd = aEnd;
}
};
/**
* A GridArea is the area in the grid for a grid item.
* The area is represented by two LineRanges, both of which can be auto
* (@see LineRange) in intermediate steps while the item is being placed.
* @see PlaceGridItems
*/
struct nsGridContainerFrame::GridArea {
GridArea(const LineRange& aCols, const LineRange& aRows)
: mCols(aCols), mRows(aRows) {}
bool IsDefinite() const { return mCols.IsDefinite() && mRows.IsDefinite(); }
LineRange& LineRangeForAxis(LogicalAxis aAxis) {
return aAxis == eLogicalAxisInline ? mCols : mRows;
}
const LineRange& LineRangeForAxis(LogicalAxis aAxis) const {
return aAxis == eLogicalAxisInline ? mCols : mRows;
}
LineRange mCols;
LineRange mRows;
};
struct nsGridContainerFrame::GridItemInfo {
/**
* Item state per axis.
*/
enum StateBits : uint16_t {
// clang-format off
eIsFlexing = 0x1, // does the item span a flex track?
eFirstBaseline = 0x2, // participate in 'first baseline' alignment?
// ditto 'last baseline', mutually exclusive w. eFirstBaseline
eLastBaseline = 0x4,
eIsBaselineAligned = eFirstBaseline | eLastBaseline,
// One of e[Self|Content]Baseline is set when eIsBaselineAligned is true
eSelfBaseline = 0x8, // is it *-self:[last ]baseline alignment?
// Ditto *-content:[last ]baseline. Mutually exclusive w. eSelfBaseline.
eContentBaseline = 0x10,
// The baseline affects the margin or padding on the item's end side when
// this bit is set. In a grid-axis it's always set for eLastBaseline and
// always unset for eFirstBaseline. In a masonry-axis, it's set for
// baseline groups in the EndStretch set and unset for the StartStretch set.
eEndSideBaseline = 0x20,
eAllBaselineBits = eIsBaselineAligned | eSelfBaseline | eContentBaseline |
eEndSideBaseline,
// Should apply Automatic Minimum Size per:
eApplyAutoMinSize = 0x40,
eClampMarginBoxMinSize = 0x80,
eIsSubgrid = 0x100,
// set on subgrids and items in subgrids if they are adjacent to the grid
// start/end edge (excluding grid-aligned abs.pos. frames)
eStartEdge = 0x200,
eEndEdge = 0x400,
eEdgeBits = eStartEdge | eEndEdge,
// Set if this item was auto-placed in this axis.
eAutoPlacement = 0x800,
// Set if this item is the last item in its track (masonry layout only)
eIsLastItemInMasonryTrack = 0x1000,
// clang-format on
};
GridItemInfo(nsIFrame* aFrame, const GridArea& aArea);
static bool BaselineAlignmentAffectsEndSide(StateBits state) {
return state & StateBits::eEndSideBaseline;
}
/**
* Inhibit subgrid layout unless the item is placed in the first "track" in
* a parent masonry-axis, or has definite placement or spans all tracks in
* the parent grid-axis.
* TODO: this is stricter than what the Masonry proposal currently states
*/
void MaybeInhibitSubgridInMasonry(nsGridContainerFrame* aParent,
uint32_t aGridAxisTrackCount);
/**
* Inhibit subgridding in aAxis for this item.
*/
void InhibitSubgrid(nsGridContainerFrame* aParent, LogicalAxis aAxis);
/**
* Return a copy of this item with its row/column data swapped.
*/
GridItemInfo Transpose() const {
GridItemInfo info(mFrame, GridArea(mArea.mRows, mArea.mCols));
info.mState[0] = mState[1];
info.mState[1] = mState[0];
info.mBaselineOffset[0] = mBaselineOffset[1];
info.mBaselineOffset[1] = mBaselineOffset[0];
return info;
}
/** Swap the start/end sides in aAxis. */
inline void ReverseDirection(LogicalAxis aAxis, uint32_t aGridEnd);
// Is this item a subgrid in the given container axis?
bool IsSubgrid(LogicalAxis aAxis) const {
return mState[aAxis] & StateBits::eIsSubgrid;
}
// Is this item a subgrid in either axis?
bool IsSubgrid() const {
return IsSubgrid(eLogicalAxisInline) || IsSubgrid(eLogicalAxisBlock);
}
// Return the (inner) grid container frame associated with this subgrid item.
nsGridContainerFrame* SubgridFrame() const {
MOZ_ASSERT(IsSubgrid());
nsGridContainerFrame* gridFrame = GetGridContainerFrame(mFrame);
MOZ_ASSERT(gridFrame && gridFrame->IsSubgrid());
return gridFrame;
}
/**
* Adjust our grid areas to account for removed auto-fit tracks in aAxis.
*/
void AdjustForRemovedTracks(LogicalAxis aAxis,
const nsTArray<uint32_t>& aNumRemovedTracks);
/**
* If the item is [align|justify]-self:[last ]baseline aligned in the given
* axis then set aBaselineOffset to the baseline offset and return aAlign.
* Otherwise, return a fallback alignment.
*/
StyleAlignFlags GetSelfBaseline(StyleAlignFlags aAlign, LogicalAxis aAxis,
nscoord* aBaselineOffset) const {
MOZ_ASSERT(aAlign == StyleAlignFlags::BASELINE ||
aAlign == StyleAlignFlags::LAST_BASELINE);
if (!(mState[aAxis] & eSelfBaseline)) {
return aAlign == StyleAlignFlags::BASELINE ? StyleAlignFlags::SELF_START
: StyleAlignFlags::SELF_END;
}
*aBaselineOffset = mBaselineOffset[aAxis];
return aAlign;
}
// Return true if we should apply Automatic Minimum Size to this item.
// @note the caller should also check that the item spans at least one track
// that has a min track sizing function that is 'auto' before applying it.
bool ShouldApplyAutoMinSize(WritingMode aContainerWM,
LogicalAxis aContainerAxis,
nscoord aPercentageBasis) const {
const bool isInlineAxis = aContainerAxis == eLogicalAxisInline;
const auto* pos =
mFrame->IsTableWrapperFrame()
? mFrame->PrincipalChildList().FirstChild()->StylePosition()
: mFrame->StylePosition();
const auto& size =
isInlineAxis ? pos->ISize(aContainerWM) : pos->BSize(aContainerWM);
// max-content and min-content should behave as initial value in block axis.
// FIXME: Bug 567039: moz-fit-content and -moz-available are not supported
// for block size dimension on sizing properties (e.g. height), so we
// treat it as `auto`.
bool isAuto = size.IsAuto() ||
(isInlineAxis ==
aContainerWM.IsOrthogonalTo(mFrame->GetWritingMode()) &&
size.BehavesLikeInitialValueOnBlockAxis());
// NOTE: if we have a definite size then our automatic minimum size
// can't affect our size. Excluding these simplifies applying
// the clamping in the right cases later.
if (!isAuto && !::IsPercentOfIndefiniteSize(size, aPercentageBasis)) {
return false;
}
const auto& minSize = isInlineAxis ? pos->MinISize(aContainerWM)
: pos->MinBSize(aContainerWM);
// max-content and min-content should behave as initial value in block axis.
// FIXME: Bug 567039: moz-fit-content and -moz-available are not supported
// for block size dimension on sizing properties (e.g. height), so we
// treat it as `auto`.
isAuto = minSize.IsAuto() ||
(isInlineAxis ==
aContainerWM.IsOrthogonalTo(mFrame->GetWritingMode()) &&
minSize.BehavesLikeInitialValueOnBlockAxis());
return isAuto &&
mFrame->StyleDisplay()->mOverflowX == StyleOverflow::Visible;
}
#ifdef DEBUG
void Dump() const;
#endif
static bool IsStartRowLessThan(const GridItemInfo* a, const GridItemInfo* b) {
return a->mArea.mRows.mStart < b->mArea.mRows.mStart;
}
// Sorting functions for 'masonry-auto-flow:next'. We sort the items that
// were placed into the first track by the Grid placement algorithm first
// (to honor that placement). All other items will be placed by the Masonry
// layout algorithm (their Grid placement in the masonry axis is irrelevant).
static bool RowMasonryOrdered(const GridItemInfo* a, const GridItemInfo* b) {
return a->mArea.mRows.mStart == 0 && b->mArea.mRows.mStart != 0 &&
!a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
}
static bool ColMasonryOrdered(const GridItemInfo* a, const GridItemInfo* b) {
return a->mArea.mCols.mStart == 0 && b->mArea.mCols.mStart != 0 &&
!a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
}
// Sorting functions for 'masonry-auto-flow:definite-first'. Similar to
// the above, but here we also sort items with a definite item placement in
// the grid axis in track order before 'auto'-placed items. We also sort all
// continuations first since they use the same placement as their
// first-in-flow (we treat them as "definite" regardless of eAutoPlacement).
static bool RowMasonryDefiniteFirst(const GridItemInfo* a,
const GridItemInfo* b) {
bool isContinuationA = a->mFrame->GetPrevInFlow();
bool isContinuationB = b->mFrame->GetPrevInFlow();
if (isContinuationA != isContinuationB) {
return isContinuationA;
}
auto masonryA = a->mArea.mRows.mStart;
auto gridA = a->mState[eLogicalAxisInline] & StateBits::eAutoPlacement;
auto masonryB = b->mArea.mRows.mStart;
auto gridB = b->mState[eLogicalAxisInline] & StateBits::eAutoPlacement;
return (masonryA == 0 ? masonryB != 0 : (masonryB != 0 && gridA < gridB)) &&
!a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
}
static bool ColMasonryDefiniteFirst(const GridItemInfo* a,
const GridItemInfo* b) {
MOZ_ASSERT(!a->mFrame->GetPrevInFlow() && !b->mFrame->GetPrevInFlow(),
"fragmentation not supported in inline axis");
auto masonryA = a->mArea.mCols.mStart;
auto gridA = a->mState[eLogicalAxisBlock] & StateBits::eAutoPlacement;
auto masonryB = b->mArea.mCols.mStart;
auto gridB = b->mState[eLogicalAxisBlock] & StateBits::eAutoPlacement;
return (masonryA == 0 ? masonryB != 0 : (masonryB != 0 && gridA < gridB)) &&
!a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
}
nsIFrame* const mFrame;
GridArea mArea;
// Offset from the margin edge to the baseline (LogicalAxis index). It's from
// the start edge when eFirstBaseline is set, end edge otherwise. It's mutable
// since we update the value fairly late (just before reflowing the item).
mutable nscoord mBaselineOffset[2];
mutable StateBits mState[2]; // state bits per axis (LogicalAxis index)
static_assert(mozilla::eLogicalAxisBlock == 0, "unexpected index value");
static_assert(mozilla::eLogicalAxisInline == 1, "unexpected index value");
};
using GridItemInfo = nsGridContainerFrame::GridItemInfo;
using ItemState = GridItemInfo::StateBits;
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ItemState)
GridItemInfo::GridItemInfo(nsIFrame* aFrame, const GridArea& aArea)
: mFrame(aFrame), mArea(aArea) {
mState[eLogicalAxisBlock] =
StateBits(mArea.mRows.mStart == kAutoLine ? eAutoPlacement : 0);
mState[eLogicalAxisInline] =
StateBits(mArea.mCols.mStart == kAutoLine ? eAutoPlacement : 0);
if (auto* gridFrame = GetGridContainerFrame(mFrame)) {
auto parentWM = aFrame->GetParent()->GetWritingMode();
bool isOrthogonal = parentWM.IsOrthogonalTo(gridFrame->GetWritingMode());
if (gridFrame->IsColSubgrid()) {
mState[isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline] |=
StateBits::eIsSubgrid;
}
if (gridFrame->IsRowSubgrid()) {
mState[isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock] |=
StateBits::eIsSubgrid;
}
}
mBaselineOffset[eLogicalAxisBlock] = nscoord(0);
mBaselineOffset[eLogicalAxisInline] = nscoord(0);
}
void GridItemInfo::ReverseDirection(LogicalAxis aAxis, uint32_t aGridEnd) {
mArea.LineRangeForAxis(aAxis).ReverseDirection(aGridEnd);
ItemState& state = mState[aAxis];
ItemState newState = state & ~ItemState::eEdgeBits;
if (state & ItemState::eStartEdge) {
newState |= ItemState::eEndEdge;
}
if (state & ItemState::eEndEdge) {
newState |= ItemState::eStartEdge;
}
state = newState;
}
void GridItemInfo::InhibitSubgrid(nsGridContainerFrame* aParent,
LogicalAxis aAxis) {
MOZ_ASSERT(IsSubgrid(aAxis));
auto bit = NS_STATE_GRID_IS_COL_SUBGRID;
if (aParent->GetWritingMode().IsOrthogonalTo(mFrame->GetWritingMode()) !=
(aAxis == eLogicalAxisBlock)) {
bit = NS_STATE_GRID_IS_ROW_SUBGRID;
}
MOZ_ASSERT(SubgridFrame()->HasAnyStateBits(bit));
SubgridFrame()->RemoveStateBits(bit);
mState[aAxis] &= StateBits(~StateBits::eIsSubgrid);
}
void GridItemInfo::MaybeInhibitSubgridInMasonry(nsGridContainerFrame* aParent,
uint32_t aGridAxisTrackCount) {
if (IsSubgrid(eLogicalAxisInline) && aParent->IsMasonry(eLogicalAxisBlock) &&
mArea.mRows.mStart != 0 && mArea.mCols.Extent() != aGridAxisTrackCount &&
(mState[eLogicalAxisInline] & eAutoPlacement)) {
InhibitSubgrid(aParent, eLogicalAxisInline);
return;
}
if (IsSubgrid(eLogicalAxisBlock) && aParent->IsMasonry(eLogicalAxisInline) &&
mArea.mCols.mStart != 0 && mArea.mRows.Extent() != aGridAxisTrackCount &&
(mState[eLogicalAxisBlock] & eAutoPlacement)) {
InhibitSubgrid(aParent, eLogicalAxisBlock);
}
}
// Each subgrid stores this data about its items etc on a frame property.
struct nsGridContainerFrame::Subgrid {
Subgrid(const GridArea& aArea, bool aIsOrthogonal, WritingMode aCBWM)
: mArea(aArea),
mGridColEnd(0),
mGridRowEnd(0),
mMarginBorderPadding(aCBWM),
mIsOrthogonal(aIsOrthogonal) {}
// Return the relevant line range for the subgrid column axis.
const LineRange& SubgridCols() const {
return mIsOrthogonal ? mArea.mRows : mArea.mCols;
}
// Return the relevant line range for the subgrid row axis.
const LineRange& SubgridRows() const {
return mIsOrthogonal ? mArea.mCols : mArea.mRows;
}
// The subgrid's items.
nsTArray<GridItemInfo> mGridItems;
// The subgrid's abs.pos. items.
nsTArray<GridItemInfo> mAbsPosItems;
// The subgrid's area as a grid item, i.e. in its parent's grid space.
GridArea mArea;
// The (inner) grid size for the subgrid, zero-based.
uint32_t mGridColEnd;
uint32_t mGridRowEnd;
// The margin+border+padding for the subgrid box in its parent grid's WM.
// (This also includes the size of any scrollbars.)
LogicalMargin mMarginBorderPadding;
// Does the subgrid frame have orthogonal writing-mode to its parent grid
// container?
bool mIsOrthogonal;
NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, Subgrid)
};
using Subgrid = nsGridContainerFrame::Subgrid;
void GridItemInfo::AdjustForRemovedTracks(
LogicalAxis aAxis, const nsTArray<uint32_t>& aNumRemovedTracks) {
const bool abspos = mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
auto& lines = mArea.LineRangeForAxis(aAxis);
if (abspos) {
lines.AdjustAbsPosForRemovedTracks(aNumRemovedTracks);
} else {
lines.AdjustForRemovedTracks(aNumRemovedTracks);
}
if (IsSubgrid()) {
auto* subgrid = SubgridFrame()->GetProperty(Subgrid::Prop());
if (subgrid) {
auto& lines = subgrid->mArea.LineRangeForAxis(aAxis);
if (abspos) {
lines.AdjustAbsPosForRemovedTracks(aNumRemovedTracks);
} else {
lines.AdjustForRemovedTracks(aNumRemovedTracks);
}
}
}
}
/**
* Track size data for use by subgrids (which don't do sizing of their own
* in a subgridded axis). A non-subgrid container stores its resolved sizes,
* but only if it has any subgrid children. A subgrid always stores one.
* In a subgridded axis, we copy the parent's sizes (see CopyUsedTrackSizes).
*
* This struct us stored on a frame property, which may be null before the track
* sizing step for the given container. A null property is semantically
* equivalent to mCanResolveLineRangeSize being false in both axes.
* @note the axis used to access this data is in the grid container's own
* writing-mode, same as in other track-sizing functions.
*/
struct nsGridContainerFrame::UsedTrackSizes {
UsedTrackSizes() : mCanResolveLineRangeSize{false, false} {}
/**
* Setup mSizes by copying track sizes from aFrame's grid container
* parent when aAxis is subgridded (and recurse if the parent is a subgrid
* that doesn't have sizes yet), or by running the Track Sizing Algo when
* the axis is not subgridded (for a subgrid).
* Set mCanResolveLineRangeSize[aAxis] to true once we have obtained
* sizes for an axis (if it's already true then this method is a NOP).
*/
void ResolveTrackSizesForAxis(nsGridContainerFrame* aFrame, LogicalAxis aAxis,
gfxContext& aRC);
/** Helper function for the above method */
void ResolveSubgridTrackSizesForAxis(nsGridContainerFrame* aFrame,
LogicalAxis aAxis, Subgrid* aSubgrid,
gfxContext& aRC,
nscoord aContentBoxSize);
// This only has valid sizes when mCanResolveLineRangeSize is true in
// the same axis. It may have zero tracks (a grid with only abs.pos.
// subgrids/items may have zero tracks).
PerLogicalAxis<nsTArray<TrackSize>> mSizes;
// True if mSizes can be used to resolve line range sizes in an axis.
PerLogicalAxis<bool> mCanResolveLineRangeSize;
NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, UsedTrackSizes)
};
using UsedTrackSizes = nsGridContainerFrame::UsedTrackSizes;
#ifdef DEBUG
void nsGridContainerFrame::GridItemInfo::Dump() const {
auto Dump1 = [this](const char* aMsg, LogicalAxis aAxis) {
auto state = mState[aAxis];
if (!state) {
return;
}
printf("%s", aMsg);
if (state & ItemState::eEdgeBits) {
printf("subgrid-adjacent-edges(");
if (state & ItemState::eStartEdge) {
printf("start ");
}
if (state & ItemState::eEndEdge) {
printf("end");
}
printf(") ");
}
if (state & ItemState::eAutoPlacement) {
printf("masonry-auto ");
}
if (state & ItemState::eIsSubgrid) {
printf("subgrid ");
}
if (state & ItemState::eIsFlexing) {
printf("flexing ");
}
if (state & ItemState::eApplyAutoMinSize) {
printf("auto-min-size ");
}
if (state & ItemState::eClampMarginBoxMinSize) {
printf("clamp ");
}
if (state & ItemState::eIsLastItemInMasonryTrack) {
printf("last-in-track ");
}
if (state & ItemState::eFirstBaseline) {
printf("first baseline %s-alignment ",
(state & ItemState::eSelfBaseline) ? "self" : "content");
}
if (state & ItemState::eLastBaseline) {
printf("last baseline %s-alignment ",
(state & ItemState::eSelfBaseline) ? "self" : "content");
}
if (state & ItemState::eIsBaselineAligned) {
printf("%.2fpx", NSAppUnitsToFloatPixels(mBaselineOffset[aAxis],
AppUnitsPerCSSPixel()));
}
printf("\n");
};
printf("grid-row: %d %d\n", mArea.mRows.mStart, mArea.mRows.mEnd);
Dump1(" grid block-axis: ", eLogicalAxisBlock);
printf("grid-column: %d %d\n", mArea.mCols.mStart, mArea.mCols.mEnd);
Dump1(" grid inline-axis: ", eLogicalAxisInline);
}
#endif
/**
* Encapsulates CSS track-sizing functions.
*/
struct nsGridContainerFrame::TrackSizingFunctions {
private:
TrackSizingFunctions(const GridTemplate& aTemplate,
const StyleImplicitGridTracks& aAutoSizing,
const Maybe<size_t>& aRepeatAutoIndex, bool aIsSubgrid)
: mTemplate(aTemplate),
mTrackListValues(aTemplate.TrackListValues()),
mAutoSizing(aAutoSizing),
mExplicitGridOffset(0),
mRepeatAutoStart(aRepeatAutoIndex.valueOr(0)),
mRepeatAutoEnd(mRepeatAutoStart),
mHasRepeatAuto(aRepeatAutoIndex.isSome()) {
MOZ_ASSERT(!mHasRepeatAuto || !aIsSubgrid,
"a track-list for a subgrid can't have an <auto-repeat> track");
if (!aIsSubgrid) {
ExpandNonRepeatAutoTracks();
}
#ifdef DEBUG
if (mHasRepeatAuto) {
MOZ_ASSERT(mExpandedTracks.Length() >= 1);
const unsigned maxTrack = kMaxLine - 1;
// If the exanded tracks are out of range of the maximum track, we
// can't compare the repeat-auto start. It will be removed later during
// grid item placement in that situation.
if (mExpandedTracks.Length() < maxTrack) {
MOZ_ASSERT(mRepeatAutoStart < mExpandedTracks.Length());
}
}
#endif
}
public:
TrackSizingFunctions(const GridTemplate& aGridTemplate,
const StyleImplicitGridTracks& aAutoSizing,
bool aIsSubgrid)
: TrackSizingFunctions(aGridTemplate, aAutoSizing,
aGridTemplate.RepeatAutoIndex(), aIsSubgrid) {}
private:
enum { ForSubgridFallbackTag };
TrackSizingFunctions(const GridTemplate& aGridTemplate,
const StyleImplicitGridTracks& aAutoSizing,
decltype(ForSubgridFallbackTag))
: TrackSizingFunctions(aGridTemplate, aAutoSizing, Nothing(),
/* aIsSubgrid */ true) {}
public:
/**
* This is used in a subgridded axis to resolve sizes before its parent's
* sizes are known for intrinsic sizing purposes. It copies the slice of
* the nearest non-subgridded axis' track sizing functions spanned by
* the subgrid.
*
* FIXME: this was written before there was a spec... the spec now says:
* "If calculating the layout of a grid item in this step depends on
* the available space in the block axis, assume the available space
* that it would have if any row with a definite max track sizing
* function had that size and all other rows were infinite."
*/
static TrackSizingFunctions ForSubgridFallback(
nsGridContainerFrame* aSubgridFrame, const Subgrid* aSubgrid,
nsGridContainerFrame* aParentGridContainer, LogicalAxis aParentAxis) {
MOZ_ASSERT(aSubgrid);
MOZ_ASSERT(aSubgridFrame->IsSubgrid(aSubgrid->mIsOrthogonal
? GetOrthogonalAxis(aParentAxis)
: aParentAxis));
nsGridContainerFrame* parent = aParentGridContainer;
auto parentAxis = aParentAxis;
LineRange range = aSubgrid->mArea.LineRangeForAxis(parentAxis);
// Find our nearest non-subgridded axis and use its track sizing functions.
while (parent->IsSubgrid(parentAxis)) {
const auto* parentSubgrid = parent->GetProperty(Subgrid::Prop());
auto* grandParent = parent->ParentGridContainerForSubgrid();
auto grandParentWM = grandParent->GetWritingMode();
bool isSameDirInAxis =
parent->GetWritingMode().ParallelAxisStartsOnSameSide(parentAxis,
grandParentWM);
if (MOZ_UNLIKELY(!isSameDirInAxis)) {
auto end = parentAxis == eLogicalAxisBlock ? parentSubgrid->mGridRowEnd
: parentSubgrid->mGridColEnd;
range.ReverseDirection(end);
// range is now in the same direction as the grand-parent's axis
}
auto grandParentAxis = parentSubgrid->mIsOrthogonal
? GetOrthogonalAxis(parentAxis)
: parentAxis;
const auto& parentRange =
parentSubgrid->mArea.LineRangeForAxis(grandParentAxis);
range.Translate(parentRange.mStart);
// range is now in the grand-parent's coordinates
parentAxis = grandParentAxis;
parent = grandParent;
}
const auto* pos = parent->StylePosition();
const auto isInlineAxis = parentAxis == eLogicalAxisInline;
const auto& szf =
isInlineAxis ? pos->mGridTemplateRows : pos->mGridTemplateColumns;
const auto& autoSizing =
isInlineAxis ? pos->mGridAutoColumns : pos->mGridAutoRows;
return TrackSizingFunctions(szf, autoSizing, ForSubgridFallbackTag);
}
/**
* Initialize the number of auto-fill/fit tracks to use.
* This can be zero if no auto-fill/fit track was specified, or if the repeat
* begins after the maximum allowed track.
*/
void InitRepeatTracks(const NonNegativeLengthPercentageOrNormal& aGridGap,
nscoord aMinSize, nscoord aSize, nscoord aMaxSize) {
const uint32_t maxTrack = kMaxLine - 1;
// Check for a repeat after the maximum allowed track.
if (MOZ_UNLIKELY(mRepeatAutoStart >= maxTrack)) {
mHasRepeatAuto = false;
mRepeatAutoStart = 0;
mRepeatAutoEnd = 0;
return;
}
uint32_t repeatTracks =
CalculateRepeatFillCount(aGridGap, aMinSize, aSize, aMaxSize) *
NumRepeatTracks();
// Clamp the number of repeat tracks to the maximum possible track.
repeatTracks = std::min(repeatTracks, maxTrack - mRepeatAutoStart);
SetNumRepeatTracks(repeatTracks);
// Blank out the removed flags for each of these tracks.
mRemovedRepeatTracks.SetLength(repeatTracks);
for (auto& track : mRemovedRepeatTracks) {
track = false;
}
}
uint32_t CalculateRepeatFillCount(
const NonNegativeLengthPercentageOrNormal& aGridGap, nscoord aMinSize,
nscoord aSize, nscoord aMaxSize) const {
if (!mHasRepeatAuto) {
return 0;
}
// At this point no tracks will have been collapsed, so the RepeatEndDelta
// should not be negative.
MOZ_ASSERT(RepeatEndDelta() >= 0);
// Note that this uses NumRepeatTracks and mRepeatAutoStart/End, although
// the result of this method is used to change those values to a fully
// expanded value. Spec quotes are from
const uint32_t numTracks = mExpandedTracks.Length() + RepeatEndDelta();
MOZ_ASSERT(numTracks >= 1, "expected at least the repeat() track");
if (MOZ_UNLIKELY(numTracks >= kMaxLine)) {
// The fixed tracks plus an entire repetition is either larger or as
// large as the maximum track, so we do not need to measure how many
// repetitions will fit. This also avoids needing to check for if
// kMaxLine - numTracks would underflow at the end where we clamp the
// result.
return 1;
}
nscoord maxFill = aSize != NS_UNCONSTRAINEDSIZE ? aSize : aMaxSize;
if (maxFill == NS_UNCONSTRAINEDSIZE && aMinSize == 0) {
// "Otherwise, the specified track list repeats only once."
return 1;
}
nscoord repeatTrackSum = 0;
// Note that one repeat() track size is included in |sum| in this loop.
nscoord sum = 0;
const nscoord percentBasis = aSize;
for (uint32_t i = 0; i < numTracks; ++i) {
// "treating each track as its max track sizing function if that is
// definite or as its minimum track sizing function otherwise"
const auto& sizingFunction = SizingFor(i);
const auto& maxCoord = sizingFunction.GetMax();
const auto* coord = &maxCoord;
if (!coord->IsBreadth()) {
coord = &sizingFunction.GetMin();
if (!coord->IsBreadth()) {
return 1;
}
}
nscoord trackSize = ::ResolveToDefiniteSize(*coord, percentBasis);
if (i >= mRepeatAutoStart && i < mRepeatAutoEnd) {
// Use a minimum 1px for the repeat() track-size.
if (trackSize < AppUnitsPerCSSPixel()) {
trackSize = AppUnitsPerCSSPixel();
}
repeatTrackSum += trackSize;
}
sum += trackSize;
}
nscoord gridGap = nsLayoutUtils::ResolveGapToLength(aGridGap, aSize);
if (numTracks > 1) {
// Add grid-gaps for all the tracks including the repeat() track.
sum += gridGap * (numTracks - 1);
}
// Calculate the max number of tracks that fits without overflow.
nscoord available = maxFill != NS_UNCONSTRAINEDSIZE ? maxFill : aMinSize;
nscoord spaceToFill = available - sum;
if (spaceToFill <= 0) {
// "if any number of repetitions would overflow, then 1 repetition"
return 1;
}
// Calculate the max number of tracks that fits without overflow.
// Since we already have one repetition in sum, we can simply add one grid
// gap for each element in the repeat.
div_t q = div(spaceToFill, repeatTrackSum + gridGap * NumRepeatTracks());
// The +1 here is for the one repeat track we already accounted for above.
uint32_t numRepeatTracks = q.quot + 1;
if (q.rem != 0 && maxFill == NS_UNCONSTRAINEDSIZE) {
// "Otherwise, if the grid container has a definite min size in
// the relevant axis, the number of repetitions is the largest possible
// positive integer that fulfills that minimum requirement."
++numRepeatTracks; // one more to ensure the grid is at least min-size
}
// Clamp the number of repeat tracks so that the last line <= kMaxLine.
// (note that |numTracks| already includes one repeat() track)
MOZ_ASSERT(numTracks >= NumRepeatTracks());
const uint32_t maxRepeatTrackCount = kMaxLine - numTracks;
const uint32_t maxRepetitions = maxRepeatTrackCount / NumRepeatTracks();
return std::min(numRepeatTracks, maxRepetitions);
}
/**
* Compute the explicit grid end line number (in a zero-based grid).
* @param aGridTemplateAreasEnd 'grid-template-areas' end line in this axis
*/
uint32_t ComputeExplicitGridEnd(uint32_t aGridTemplateAreasEnd) {
uint32_t end = NumExplicitTracks() + 1;
end = std::max(end, aGridTemplateAreasEnd);
end = std::min(end, uint32_t(kMaxLine));
return end;
}
const StyleTrackSize& SizingFor(uint32_t aTrackIndex) const {
static const StyleTrackSize kAutoTrackSize =
StyleTrackSize::Breadth(StyleTrackBreadth::Auto());
// |aIndex| is the relative index to mAutoSizing. A negative value means it
// is the last Nth element.
auto getImplicitSize = [this](int32_t aIndex) -> const StyleTrackSize& {
MOZ_ASSERT(!(mAutoSizing.Length() == 1 &&
mAutoSizing.AsSpan()[0] == kAutoTrackSize),
"It's impossible to have one track with auto value because we "
"filter out this case during parsing");
if (mAutoSizing.IsEmpty()) {
return kAutoTrackSize;
}
// If multiple track sizes are given, the pattern is repeated as necessary
// to find the size of the implicit tracks.
int32_t i = aIndex % int32_t(mAutoSizing.Length());
if (i < 0) {
i += mAutoSizing.Length();
}
return mAutoSizing.AsSpan()[i];
};
if (MOZ_UNLIKELY(aTrackIndex < mExplicitGridOffset)) {
// The last implicit grid track before the explicit grid receives the
// last specified size, and so on backwards. Therefore we pass the
// negative relative index to imply that we should get the implicit size
// from the last Nth specified grid auto size.
return getImplicitSize(int32_t(aTrackIndex) -
int32_t(mExplicitGridOffset));
}
uint32_t index = aTrackIndex - mExplicitGridOffset;
MOZ_ASSERT(mRepeatAutoStart <= mRepeatAutoEnd);
if (index >= mRepeatAutoStart) {
if (index < mRepeatAutoEnd) {
// Expand the repeat tracks.
const auto& indices = mExpandedTracks[mRepeatAutoStart];
const TrackListValue& value = mTrackListValues[indices.first];
// We expect the default to be used for all track repeats.
MOZ_ASSERT(indices.second == 0);
const auto& repeatTracks = value.AsTrackRepeat().track_sizes.AsSpan();
// Find the repeat track to use, skipping over any collapsed tracks.
const uint32_t finalRepeatIndex = (index - mRepeatAutoStart);
uint32_t repeatWithCollapsed = 0;
// NOTE: We need SizingFor before the final collapsed tracks are known.
// We know that it's invalid to have empty mRemovedRepeatTracks when
// there are any repeat tracks, so we can detect that situation here.
if (mRemovedRepeatTracks.IsEmpty()) {
repeatWithCollapsed = finalRepeatIndex;
} else {
// Count up through the repeat tracks, until we have seen
// finalRepeatIndex number of non-collapsed tracks.
for (uint32_t repeatNoCollapsed = 0;
repeatNoCollapsed < finalRepeatIndex; repeatWithCollapsed++) {
if (!mRemovedRepeatTracks[repeatWithCollapsed]) {
repeatNoCollapsed++;
}
}
// If we stopped iterating on a collapsed track, continue to the next
// non-collapsed track.
while (mRemovedRepeatTracks[repeatWithCollapsed]) {
repeatWithCollapsed++;
}
}
return repeatTracks[repeatWithCollapsed % repeatTracks.Length()];
} else {
// The index is after the repeat auto range, adjust it to skip over the
// repeat value. This will have no effect if there is no auto repeat,
// since then RepeatEndDelta will return zero.
index -= RepeatEndDelta();
}
}
if (index >= mExpandedTracks.Length()) {
return getImplicitSize(index - mExpandedTracks.Length());
}
auto& indices = mExpandedTracks[index];
const TrackListValue& value = mTrackListValues[indices.first];
if (value.IsTrackSize()) {
MOZ_ASSERT(indices.second == 0);
return value.AsTrackSize();
}
return value.AsTrackRepeat().track_sizes.AsSpan()[indices.second];
}
const StyleTrackBreadth& MaxSizingFor(uint32_t aTrackIndex) const {
return SizingFor(aTrackIndex).GetMax();
}
const StyleTrackBreadth& MinSizingFor(uint32_t aTrackIndex) const {
return SizingFor(aTrackIndex).GetMin();
}
uint32_t NumExplicitTracks() const {
return mExpandedTracks.Length() + RepeatEndDelta();
}
uint32_t NumRepeatTracks() const { return mRepeatAutoEnd - mRepeatAutoStart; }
// The difference between mExplicitGridEnd and mSizingFunctions.Length().
int32_t RepeatEndDelta() const {
return mHasRepeatAuto ? int32_t(NumRepeatTracks()) - 1 : 0;
}
void SetNumRepeatTracks(uint32_t aNumRepeatTracks) {
MOZ_ASSERT(mHasRepeatAuto || aNumRepeatTracks == 0);
mRepeatAutoEnd = mRepeatAutoStart + aNumRepeatTracks;
}
// Store mTrackListValues into mExpandedTracks with `repeat(INTEGER, ...)`
// tracks expanded.
void ExpandNonRepeatAutoTracks() {
for (size_t i = 0; i < mTrackListValues.Length(); ++i) {
auto& value = mTrackListValues[i];
if (value.IsTrackSize()) {
mExpandedTracks.EmplaceBack(i, 0);
continue;
}
auto& repeat = value.AsTrackRepeat();
if (!repeat.count.IsNumber()) {
MOZ_ASSERT(i == mRepeatAutoStart);
mRepeatAutoStart = mExpandedTracks.Length();
mRepeatAutoEnd = mRepeatAutoStart + repeat.track_sizes.Length();
mExpandedTracks.EmplaceBack(i, 0);
continue;
}
for (auto j : IntegerRange(repeat.count.AsNumber())) {
Unused << j;
size_t trackSizesCount = repeat.track_sizes.Length();
for (auto k : IntegerRange(trackSizesCount)) {
mExpandedTracks.EmplaceBack(i, k);
}
}
}
if (MOZ_UNLIKELY(mExpandedTracks.Length() > kMaxLine - 1)) {
mExpandedTracks.TruncateLength(kMaxLine - 1);
if (mHasRepeatAuto && mRepeatAutoStart > kMaxLine - 1) {
// The `repeat(auto-fill/fit)` track is outside the clamped grid.
mHasRepeatAuto = false;
}
}
}
// Some style data references, for easy access.
const GridTemplate& mTemplate;
const Span<const TrackListValue> mTrackListValues;
const StyleImplicitGridTracks& mAutoSizing;
// An array from expanded track sizes (without expanding auto-repeat, which is
// included just once at `mRepeatAutoStart`).
//
// Each entry contains two indices, the first into mTrackListValues, and a
// second one inside mTrackListValues' repeat value, if any, or zero
// otherwise.
nsTArray<std::pair<size_t, size_t>> mExpandedTracks;
// Offset from the start of the implicit grid to the first explicit track.
uint32_t mExplicitGridOffset;
// The index of the repeat(auto-fill/fit) track, or zero if there is none.
// Relative to mExplicitGridOffset (repeat tracks are explicit by definition).
uint32_t mRepeatAutoStart;
// The (hypothetical) index of the last such repeat() track.
uint32_t mRepeatAutoEnd;
// True if there is a specified repeat(auto-fill/fit) track.
bool mHasRepeatAuto;
// True if this track (relative to mRepeatAutoStart) is a removed auto-fit.
// Indexed relative to mExplicitGridOffset + mRepeatAutoStart.
nsTArray<bool> mRemovedRepeatTracks;
};
/**
* Utility class to find line names. It provides an interface to lookup line
* names with a dynamic number of repeat(auto-fill/fit) tracks taken into
* account.
*/
class MOZ_STACK_CLASS nsGridContainerFrame::LineNameMap {
public:
/**
* Create a LineNameMap.
* @param aStylePosition the style for the grid container
* @param aImplicitNamedAreas the implicit areas for the grid container
* @param aGridTemplate is the grid-template-rows/columns data for this axis
* @param aParentLineNameMap the parent grid's map parallel to this map, or
* null if this map isn't for a subgrid
* @param aRange the subgrid's range in the parent grid, or null
* @param aIsSameDirection true if our axis progresses in the same direction
* in the subgrid and parent
*/
LineNameMap(const nsStylePosition* aStylePosition,
const ImplicitNamedAreas* aImplicitNamedAreas,
const TrackSizingFunctions& aTracks,
const LineNameMap* aParentLineNameMap, const LineRange* aRange,
bool aIsSameDirection)
: mStylePosition(aStylePosition),
mAreas(aImplicitNamedAreas),
mRepeatAutoStart(aTracks.mRepeatAutoStart),
mRepeatAutoEnd(aTracks.mRepeatAutoEnd),
mRepeatEndDelta(aTracks.RepeatEndDelta()),
mParentLineNameMap(aParentLineNameMap),
mRange(aRange),
mIsSameDirection(aIsSameDirection),
mHasRepeatAuto(aTracks.mHasRepeatAuto) {
if (MOZ_UNLIKELY(aRange)) { // subgrid case
mClampMinLine = 1;
mClampMaxLine = 1 + aRange->Extent();
mRepeatAutoEnd = mRepeatAutoStart;
const auto& styleSubgrid = aTracks.mTemplate.AsSubgrid();
const auto fillLen = styleSubgrid->fill_len;
mHasRepeatAuto = fillLen != 0;
if (mHasRepeatAuto) {
const auto& lineNameLists = styleSubgrid->names;
const int32_t extraAutoFillLineCount =
mClampMaxLine - lineNameLists.Length();
// Maximum possible number of repeat name lists. This must be reduced
// to a whole number of repetitions of the fill length.
const uint32_t possibleRepeatLength =
std::max<int32_t>(0, extraAutoFillLineCount + fillLen);
const uint32_t repeatRemainder = possibleRepeatLength % fillLen;
mRepeatAutoStart = styleSubgrid->fill_start;
mRepeatAutoEnd =
mRepeatAutoStart + possibleRepeatLength - repeatRemainder;
}
} else {
mClampMinLine = kMinLine;
mClampMaxLine = kMaxLine;
if (mHasRepeatAuto) {
mTrackAutoRepeatLineNames =
aTracks.mTemplate.GetRepeatAutoValue()->line_names.AsSpan();
}
}
ExpandRepeatLineNames(!!aRange, aTracks);
if (mHasRepeatAuto) {
// We need mTemplateLinesEnd to be after all line names.
// mExpandedLineNames has one repetition of the repeat(auto-fit/fill)
// track name lists already, so we must subtract the number of repeat
// track name lists to get to the number of non-repeat tracks, minus 2
// because the first and last line name lists are shared with the
// preceding and following non-repeat line name lists. We then add
// mRepeatEndDelta to include the interior line name lists from repeat
// tracks.
mTemplateLinesEnd = mExpandedLineNames.Length() -
(mTrackAutoRepeatLineNames.Length() - 2) +
mRepeatEndDelta;
} else {
mTemplateLinesEnd = mExpandedLineNames.Length();
}
MOZ_ASSERT(mHasRepeatAuto || mRepeatEndDelta <= 0);
MOZ_ASSERT(!mHasRepeatAuto || aRange ||
(mExpandedLineNames.Length() >= 2 &&
mRepeatAutoStart <= mExpandedLineNames.Length()));
}
// Store line names into mExpandedLineNames with `repeat(INTEGER, ...)`
// expanded (for non-subgrid), and all `repeat(...)` expanded (for subgrid).
void ExpandRepeatLineNames(bool aIsSubgrid,
const TrackSizingFunctions& aTracks) {