/* -*- 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 <stdlib.h>
// for div()
#include <type_traits>
#include "gfxContext.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/Baseline.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/CSSAlignUtils.h"
#include "mozilla/dom/Grid.h"
#include "mozilla/dom/GridBinding.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/Maybe.h"
#include "mozilla/PodOperations.h" // for PodZero
#include "mozilla/PresShell.h"
#include "mozilla/ScrollContainerFrame.h"
#include "mozilla/StaticPrefs_layout.h"
#include "nsAbsoluteContainingBlock.h"
#include "nsCSSFrameConstructor.h"
#include "nsDisplayList.h"
#include "nsFieldSetFrame.h"
#include "nsHTMLButtonControlFrame.h"
#include "nsHashKeys.h"
#include "nsIFrameInlines.h" // for nsIFrame::GetLogicalNormalPosition (don't remove)
#include "nsLayoutUtils.h"
#include "nsPlaceholderFrame.h"
#include "nsPresContext.h"
#include "nsReadableUtils.h"
#include "nsTableWrapperFrame.h"
using namespace mozilla;
using AbsPosReflowFlags = nsAbsoluteContainingBlock::AbsPosReflowFlags;
using AlignJustifyFlags = CSSAlignUtils::AlignJustifyFlags;
using GridItemCachedBAxisMeasurement =
nsGridContainerFrame::CachedBAxisMeasurement;
using GridTemplate = StyleGridTemplateComponent;
using NameList = StyleOwnedSlice<StyleCustomIdent>;
using SizingConstraint = nsGridContainerFrame::SizingConstraint;
using TrackListValue =
StyleGenericTrackListValue<LengthPercentage, StyleInteger>;
using TrackRepeat = StyleGenericTrackRepeat<LengthPercentage, StyleInteger>;
using TrackSize = nsGridContainerFrame::TrackSize;
static mozilla::LazyLogModule gGridContainerLog(
"GridContainer");
#define GRID_LOG(...) \
MOZ_LOG(gGridContainerLog, LogLevel::Debug, (__VA_ARGS__));
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) {
// For subgrid, we need to resolve <line-name-list> from each
// StyleGenericLineNameListValue, so return empty.
return {};
}
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.
// https://drafts.csswg.org/css-align-3/#synthesize-baseline
// 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.
//
// The 'LogicalAxis aAxis' represents the axis (in terms of aWM) that the
// baseline corresponds to. (Typically, baselines are a measurement in the
// block axis; e.g. for English horizontal-tb text, a traditional baseline
// would be a y-axis measurement. But in some cases (e.g. orthogonal WMs), we
// may need to synthesize a baseline in a child's inline axis, which is when
// this function might receive an aAxis of LogicalAxis::Inline. In that case, we
// assume that the writing mode's preference for central vs. alphabetic
// baselines is irrelevant, since that's a choice about its block-axis
// baselines, and we just unconditionally use the alphabetic baseline
// (e.g. border-box bottom edge).
static nscoord SynthesizeBaselineFromBorderBox(BaselineSharingGroup aGroup,
WritingMode aWM,
LogicalAxis aAxis,
nscoord aBorderBoxSize) {
const bool useAlphabeticBaseline =
(aAxis == LogicalAxis::
Inline) ?
true : aWM.IsAlphabeticalBaseline();
if (aGroup == BaselineSharingGroup::First) {
return useAlphabeticBaseline ? aBorderBoxSize : aBorderBoxSize / 2;
}
MOZ_ASSERT(aGroup == BaselineSharingGroup::Last);
// Round up for central baseline offset, to be consistent with eFirst.
return useAlphabeticBaseline ? 0
: (aBorderBoxSize / 2) + (aBorderBoxSize % 2);
}
// The helper struct to hold the box sizing adjustment.
struct BoxSizingAdjustment {
BoxSizingAdjustment() =
delete;
BoxSizingAdjustment(
const WritingMode aWM,
const ComputedStyle& aStyle)
: mWM(aWM), mStyle(aStyle) {}
const LogicalSize& EnsureAndGet() {
if (mValue) {
return mValue.ref();
}
if (mStyle.StylePosition()->mBoxSizing != StyleBoxSizing::Border) {
// Use default, (0, 0).
mValue.emplace(mWM);
return mValue.ref();
}
const auto& padding = mStyle.StylePadding()->mPadding;
LogicalMargin border(mWM, mStyle.StyleBorder()->GetComputedBorder());
// We can use zero percentage basis since this is only called from
// intrinsic sizing code.
const nscoord percentageBasis = 0;
const nscoord iBP =
std::max(padding.GetIStart(mWM).Resolve(percentageBasis), 0) +
std::max(padding.GetIEnd(mWM).Resolve(percentageBasis), 0) +
border.IStartEnd(mWM);
const nscoord bBP =
std::max(padding.GetBStart(mWM).Resolve(percentageBasis), 0) +
std::max(padding.GetBEnd(mWM).Resolve(percentageBasis), 0) +
border.BStartEnd(mWM);
mValue.emplace(mWM, iBP, bBP);
return mValue.ref();
}
private:
const WritingMode mWM;
const ComputedStyle& mStyle;
// The wrapped value we would like to use for the box sizing adjustment.
Maybe<LogicalSize> mValue;
};
static Maybe<nscoord> GetPercentageBasisForAR(
const LogicalAxis aRatioDeterminingAxis,
const WritingMode aWM,
const Maybe<LogicalSize>& aContainingBlockSize) {
if (!aContainingBlockSize) {
return Nothing();
}
const nscoord basis = aContainingBlockSize->Size(aRatioDeterminingAxis, aWM);
// If the basis is unconstrained (because we are still computing the
// containing block size), we should treat it as no basis.
return basis == NS_UNCONSTRAINEDSIZE ? Nothing() : Some(basis);
}
template <
typename Type>
static Maybe<nscoord> ComputeTransferredSize(
const Type& aRatioDeterminingSize,
const LogicalAxis aAxis,
const WritingMode aWM,
const AspectRatio& aAspectRatio,
BoxSizingAdjustment& aBoxSizingAdjustment,
const Maybe<LogicalSize>& aContainingBlockSize) {
// Use GetOrthogonalAxis() to get the ratio-determining axis.
const Maybe<nscoord> basis = GetPercentageBasisForAR(
GetOrthogonalAxis(aAxis), aWM, aContainingBlockSize);
nscoord rdSize = 0;
if (aRatioDeterminingSize.ConvertsToLength()) {
rdSize = aRatioDeterminingSize.ToLength();
}
else if (aRatioDeterminingSize.HasPercent() && basis) {
rdSize = aRatioDeterminingSize.AsLengthPercentage().Resolve(*basis);
}
else {
// Either we are not using LengthPercentage or there is no percentage basis.
return Nothing();
}
return Some(aAspectRatio.ComputeRatioDependentSize(
aAxis, aWM, rdSize, aBoxSizingAdjustment.EnsureAndGet()));
}
// The input sizes for calculating the number of repeat(auto-fill/fit) tracks.
// https://drafts.csswg.org/css-grid-2/#auto-repeat
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 AspectRatio& aAspectRatio,
const Maybe<LogicalSize>& aContainingBlockSize) {
const auto& pos = aStyle->StylePosition();
BoxSizingAdjustment boxSizingAdjustment(aWM, *aStyle);
const nscoord cbSizeInAxis = aContainingBlockSize
? aContainingBlockSize->Size(aAxis, aWM)
: NS_UNCONSTRAINEDSIZE;
auto adjustForBoxSizing = [aWM, aAxis,
&boxSizingAdjustment](nscoord aSize) {
return std::max(
aSize - boxSizingAdjustment.EnsureAndGet().Size(aAxis, aWM), 0);
};
nscoord& min = mMin.Size(aAxis, aWM);
const auto& styleMinSize = pos->MinSize(aAxis, aWM);
if (styleMinSize.ConvertsToLength()) {
min = adjustForBoxSizing(styleMinSize.ToLength());
}
else if (styleMinSize.HasPercent() &&
cbSizeInAxis != NS_UNCONSTRAINEDSIZE) {
min = adjustForBoxSizing(
styleMinSize.AsLengthPercentage().Resolve(cbSizeInAxis));
}
else if (aAspectRatio && styleMinSize.BehavesLikeInitialValue(aAxis)) {
// Use GetOrthogonalAxis() to get the ratio-determining axis. Same for max
// and size below in this function.
const auto& styleRDMinSize = pos->MinSize(GetOrthogonalAxis(aAxis), aWM);
if (Maybe<nscoord> resolvedMinSize = ComputeTransferredSize(
styleRDMinSize, aAxis, aWM, aAspectRatio, boxSizingAdjustment,
aContainingBlockSize)) {
min = *resolvedMinSize;
}
}
nscoord& max = mMax.Size(aAxis, aWM);
const auto& styleMaxSize = pos->MaxSize(aAxis, aWM);
if (styleMaxSize.ConvertsToLength()) {
max = std::max(min, adjustForBoxSizing(styleMaxSize.ToLength()));
}
else if (styleMaxSize.HasPercent() &&
cbSizeInAxis != NS_UNCONSTRAINEDSIZE) {
max = std::max(
min, adjustForBoxSizing(
styleMaxSize.AsLengthPercentage().Resolve(cbSizeInAxis)));
}
else if (aAspectRatio && styleMaxSize.BehavesLikeInitialValue(aAxis)) {
const auto& styleRDMaxSize = pos->MaxSize(GetOrthogonalAxis(aAxis), aWM);
if (Maybe<nscoord> resolvedMaxSize = ComputeTransferredSize(
styleRDMaxSize, aAxis, aWM, aAspectRatio, boxSizingAdjustment,
aContainingBlockSize)) {
max = std::max(min, *resolvedMaxSize);
}
}
nscoord& size = mSize.Size(aAxis, aWM);
// When computing the intrinsic inline size, disregard the explicit
// inline-size property as it should not affect the final result.
const auto& styleSize =
aAxis == LogicalAxis::
Inline ? StyleSize::
Auto() : pos->BSize(aWM);
if (styleSize.ConvertsToLength()) {
size = std::clamp(adjustForBoxSizing(styleSize.ToLength()), min, max);
}
else if (styleSize.HasPercent() && cbSizeInAxis != NS_UNCONSTRAINEDSIZE) {
size =
std::clamp(adjustForBoxSizing(
styleSize.AsLengthPercentage().Resolve(cbSizeInAxis)),
min, max);
}
else if (aAspectRatio && styleSize.BehavesLikeInitialValue(aAxis)) {
const auto& styleRDSize = pos->Size(GetOrthogonalAxis(aAxis), aWM);
if (Maybe<nscoord> resolvedSize = ComputeTransferredSize(
styleRDSize, aAxis, aWM, aAspectRatio, boxSizingAdjustment,
aContainingBlockSize)) {
size = std::clamp(*resolvedSize, 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,
eApplyFitContentClamping = 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;
}
// Base size of this track.
// https://drafts.csswg.org/css-grid-2/#base-size
nscoord mBase;
// Growth limit of this track.
// https://drafts.csswg.org/css-grid-2/#growth-limit
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)
static_assert(
std::is_trivially_copyable<nsGridContainerFrame::TrackSize>::value,
"Must be trivially copyable");
static_assert(
std::is_trivially_destructible<nsGridContainerFrame::TrackSize>::value,
"Must be trivially destructible");
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.
if (!::IsPercentOfIndefiniteSize(aSize.AsFitContent(), aPercentageBasis)) {
mState = eApplyFitContentClamping;
}
minSizeTag = Tag::
Auto;
maxSizeTag = Tag::MaxContent;
}
if (::IsPercentOfIndefiniteSize(min, aPercentageBasis)) {
// https://drafts.csswg.org/css-grid-2/#valdef-grid-template-columns-length-percentage-0
// "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;
}
// https://drafts.csswg.org/css-grid-2/#algo-init
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(),
" 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 = NS_UNCONSTRAINEDSIZE;
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
// https://drafts.csswg.org/css-grid-2/#overlarge-grids
// 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
* https://drafts.csswg.org/css-grid-2/#auto-placement-algo
*/
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 == LogicalAxis::
Inline ? mCols : mRows;
}
const LineRange& LineRangeForAxis(LogicalAxis aAxis)
const {
return aAxis == LogicalAxis::
Inline ? mCols : mRows;
}
LineRange mCols;
LineRange mRows;
};
struct nsGridContainerFrame::GridItemInfo {
/**
* Item state per axis.
*/
enum StateBits : uint16_t {
// Does the item span a flex track?
eIsFlexing = 0x1,
// First or last baseline alignment preference. They are mutually exclusive.
// This does *NOT* represent the baseline alignment group. See the member
// variable for that.
// <https://drafts.csswg.org/css-align-3/#baseline-alignment-preference>
eFirstBaseline = 0x2,
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:
// https://drafts.csswg.org/css-grid-2/#min-size-auto
eApplyAutoMinSize = 0x40,
// Clamp per https://drafts.csswg.org/css-grid-2/#min-size-auto
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,
};
GridItemInfo(nsIFrame* aFrame,
const GridArea& aArea);
GridItemInfo(
const GridItemInfo& aOther)
: mFrame(aOther.mFrame), mArea(aOther.mArea) {
mBaselineOffset = aOther.mBaselineOffset;
mState = aOther.mState;
}
GridItemInfo&
operator=(
const GridItemInfo&) =
delete;
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
* (bug 1627581)
*/
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[LogicalAxis::Block] = mState[LogicalAxis::
Inline];
info.mState[LogicalAxis::
Inline] = mState[LogicalAxis::Block];
info.mBaselineOffset[LogicalAxis::Block] =
mBaselineOffset[LogicalAxis::
Inline];
info.mBaselineOffset[LogicalAxis::
Inline] =
mBaselineOffset[LogicalAxis::Block];
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(LogicalAxis::
Inline) || IsSubgrid(LogicalAxis::Block);
}
// 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 use MinContribution on items that do not span
// any flex tracks to determine the minimum contribution, and if we should
// set the eApplyAutoMinSize flag on grid items.
//
// In part this is determined by whether or not the minimum contribution
// of the item is content-based.
// https://drafts.csswg.org/css-grid-2/#min-size-auto
//
// @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)
const {
if ((mState[aContainerAxis] & StateBits::eIsFlexing) &&
mArea.LineRangeForAxis(aContainerAxis).Extent() > 1) {
// If the item spans multiple tracks in a given axis, none of those
// tracks may be flexible.
return false;
}
const LogicalAxis itemAxis =
aContainerWM.IsOrthogonalTo(mFrame->GetWritingMode())
? GetOrthogonalAxis(aContainerAxis)
: aContainerAxis;
const auto* pos =
mFrame->IsTableWrapperFrame()
? mFrame->PrincipalChildList().FirstChild()->StylePosition()
: mFrame->StylePosition();
const auto& size = pos->Size(aContainerAxis, 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.BehavesLikeInitialValue(itemAxis);
// TODO alaskanemily: This probably shouldn't be a special case.
// Although this being a percentage isn't relevant to whether or not the
// minimum contribution is content-based or not, but this matches the
// expectations of MinContribution().
if (!isAuto && !size.HasPercent()) {
return false;
}
const auto& minSize = pos->MinSize(aContainerAxis, 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.BehavesLikeInitialValue(itemAxis);
return isAuto && !mFrame->StyleDisplay()->IsScrollableOverflow();
}
#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[LogicalAxis::
Inline] & StateBits::eAutoPlacement;
auto masonryB = b->mArea.mRows.mStart;
auto gridB = b->mState[LogicalAxis::
Inline] & 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[LogicalAxis::Block] & StateBits::eAutoPlacement;
auto masonryB = b->mArea.mCols.mStart;
auto gridB = b->mState[LogicalAxis::Block] & StateBits::eAutoPlacement;
return (masonryA == 0 ? masonryB != 0 : (masonryB != 0 && gridA < gridB)) &&
!a->mFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
}
// Return true if this items block size is dependent on the size of the
// container it is in.
bool IsBSizeDependentOnContainerSize(WritingMode aContainerWM)
const {
const auto IsDependentOnContainerSize = [](
const auto& size) ->
bool {
// XXXdholbert The BehavesLikeStretchOnInlineAxis usage seems like
// maybe it should be considering block-axis instead?
return size.HasPercent() || size.BehavesLikeStretchOnInlineAxis();
};
const nsStylePosition* stylePos = mFrame->StylePosition();
bool isItemAutoSize =
IsDependentOnContainerSize(stylePos->BSize(aContainerWM)) ||
IsDependentOnContainerSize(stylePos->MinBSize(aContainerWM)) ||
IsDependentOnContainerSize(stylePos->MaxBSize(aContainerWM));
return isItemAutoSize;
}
nsIFrame*
const mFrame;
GridArea mArea;
// Offset from the margin edge to the baseline (LogicalAxis index). It's from
// the start edge for first baseline sharing group, otherwise from the end
// edge.
// It's mutable since we update the value fairly late (just before reflowing
// the item).
mutable PerLogicalAxis<nscoord> mBaselineOffset;
// State bits per axis.
mutable PerLogicalAxis<StateBits> mState;
};
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), mBaselineOffset{0, 0} {
mState[LogicalAxis::Block] =
StateBits(mArea.mRows.mStart == kAutoLine ? eAutoPlacement : 0);
mState[LogicalAxis::
Inline] =
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 ? LogicalAxis::Block : LogicalAxis::
Inline] |=
StateBits::eIsSubgrid;
}
if (gridFrame->IsRowSubgrid()) {
mState[isOrthogonal ? LogicalAxis::
Inline : LogicalAxis::Block] |=
StateBits::eIsSubgrid;
}
}
}
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 == LogicalAxis::Block)) {
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(LogicalAxis::
Inline) &&
aParent->IsMasonry(LogicalAxis::Block) && mArea.mRows.mStart != 0 &&
mArea.mCols.Extent() != aGridAxisTrackCount &&
(mState[LogicalAxis::
Inline] & eAutoPlacement)) {
InhibitSubgrid(aParent, LogicalAxis::
Inline);
return;
}
if (IsSubgrid(LogicalAxis::Block) &&
aParent->IsMasonry(LogicalAxis::
Inline) && mArea.mCols.mStart != 0 &&
mArea.mRows.Extent() != aGridAxisTrackCount &&
(mState[LogicalAxis::Block] & eAutoPlacement)) {
InhibitSubgrid(aParent, LogicalAxis::Block);
}
}
// 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: ", LogicalAxis::Block);
printf(
"grid-column: %d %d\n", mArea.mCols.mStart, mArea.mCols.mEnd);
Dump1(
" grid inline-axis: ", LogicalAxis::
Inline);
}
#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 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."
* https://drafts.csswg.org/css-grid-2/#subgrid-sizing
*/
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 == LogicalAxis::Block
? 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 == LogicalAxis::
Inline;
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
// https://drafts.csswg.org/css-grid-2/#repeat-notation
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"
// https://drafts.csswg.org/css-grid-2/#valdef-repeat-auto-fill
nscoord trackSize;
{
const auto& sizingFunction = SizingFor(i);
const auto& maxCoord = sizingFunction.GetMax();
const auto& minCoord = sizingFunction.GetMin();
if (maxCoord.IsBreadth() && minCoord.IsBreadth()) {
// If the max is less than the min, then the max will be floored by
// the min (essentially yielding minmax(min, min))
// https://drafts.csswg.org/css-grid-2/#funcdef-grid-template-columns-minmax
const nscoord minSize =
::ResolveToDefiniteSize(minCoord, percentBasis);
const nscoord maxSize =
::ResolveToDefiniteSize(maxCoord, percentBasis);
trackSize = std::max(maxSize, minSize);
}
else {
const auto* coord = &maxCoord;
if (!coord->IsBreadth()) {
coord = &minCoord;
if (!coord->IsBreadth()) {
return 1;
}
}
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
--> --------------------
--> maximum size reached
--> --------------------