gecko-dev/layout/generic/nsGridContainerFrame.cpp
Daniel Holbert 10796eda46 Bug 1340309 part 1: Resolve "align-self:auto"/"justify-self:auto" to flex/grid parent's *-items value, when resolving static pos of abspos children. r=mats
MozReview-Commit-ID: 6fSmo1WGqow

--HG--
extra : rebase_source : 0b07e02f2e584261a18f78ef9e6c16637a022e16
2017-02-24 08:38:31 -08:00

7107 lines
274 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/. */
/* rendering object for CSS "display: grid | inline-grid" */
#include "nsGridContainerFrame.h"
#include <algorithm> // for std::stable_sort
#include <functional>
#include <limits>
#include "mozilla/CSSAlignUtils.h"
#include "mozilla/dom/GridBinding.h"
#include "mozilla/Maybe.h"
#include "mozilla/PodOperations.h" // for PodZero
#include "mozilla/Poison.h"
#include "nsAbsoluteContainingBlock.h"
#include "nsAlgorithm.h" // for clamped()
#include "nsCSSAnonBoxes.h"
#include "nsCSSFrameConstructor.h"
#include "nsDataHashtable.h"
#include "nsDisplayList.h"
#include "nsHashKeys.h"
#include "nsIFrameInlines.h"
#include "nsPresContext.h"
#include "nsReadableUtils.h"
#include "nsRenderingContext.h"
#include "nsRuleNode.h"
#include "nsStyleContext.h"
#include "nsTableWrapperFrame.h"
#if defined(__clang__) && __clang_major__ == 3 && __clang_minor__ <= 8
#define CLANG_CRASH_BUG 1
#endif
using namespace mozilla;
typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags;
typedef nsGridContainerFrame::TrackSize TrackSize;
const uint32_t nsGridContainerFrame::kTranslatedMaxLine =
uint32_t(nsStyleGridLine::kMaxLine - nsStyleGridLine::kMinLine);
const uint32_t nsGridContainerFrame::kAutoLine = kTranslatedMaxLine + 3457U;
typedef nsTHashtable< nsPtrHashKey<nsIFrame> > FrameHashtable;
typedef mozilla::CSSAlignUtils::AlignJustifyFlags AlignJustifyFlags;
typedef nsLayoutUtils::IntrinsicISizeType IntrinsicISizeType;
// https://drafts.csswg.org/css-sizing/#constraints
enum class SizingConstraint
{
eMinContent, // sizing under min-content constraint
eMaxContent, // sizing under max-content constraint
eNoConstraint // no constraint, used during Reflow
};
static void
ReparentFrame(nsIFrame* aFrame, nsContainerFrame* aOldParent,
nsContainerFrame* aNewParent)
{
NS_ASSERTION(aOldParent == aFrame->GetParent(),
"Parent not consistent with expectations");
aFrame->SetParent(aNewParent);
// When pushing and pulling frames we need to check for whether any
// views need to be reparented
nsContainerFrame::ReparentFrameView(aFrame, aOldParent, aNewParent);
}
static void
ReparentFrames(nsFrameList& aFrameList, nsContainerFrame* aOldParent,
nsContainerFrame* aNewParent)
{
for (auto f : aFrameList) {
ReparentFrame(f, aOldParent, aNewParent);
}
}
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;
}
static bool
IsPercentOfIndefiniteSize(const nsStyleCoord& aCoord, nscoord aPercentBasis)
{
return aPercentBasis == NS_UNCONSTRAINEDSIZE && aCoord.HasPercent();
}
static nscoord
ResolveToDefiniteSize(const nsStyleCoord& aCoord, nscoord aPercentBasis)
{
MOZ_ASSERT(aCoord.IsCoordPercentCalcUnit());
if (::IsPercentOfIndefiniteSize(aCoord, aPercentBasis)) {
return nscoord(0);
}
return std::max(nscoord(0),
nsRuleNode::ComputeCoordPercentCalc(aCoord, aPercentBasis));
}
static bool
GetPercentSizeParts(const nsStyleCoord& aCoord, nscoord* aLength, float* aPercent)
{
switch (aCoord.GetUnit()) {
case eStyleUnit_Percent:
*aLength = 0;
*aPercent = aCoord.GetPercentValue();
return true;
case eStyleUnit_Calc: {
nsStyleCoord::Calc* calc = aCoord.GetCalcValue();
*aLength = calc->mLength;
*aPercent = calc->mPercent;
return true;
}
default:
return false;
}
}
static void
ResolvePercentSizeParts(const nsStyleCoord& aCoord, nscoord aPercentBasis,
nscoord* aLength, float* aPercent)
{
MOZ_ASSERT(aCoord.IsCoordPercentCalcUnit());
if (aPercentBasis != NS_UNCONSTRAINEDSIZE) {
*aLength = std::max(nscoord(0),
nsRuleNode::ComputeCoordPercentCalc(aCoord,
aPercentBasis));
*aPercent = 0.0f;
return;
}
if (!GetPercentSizeParts(aCoord, aLength, aPercent)) {
*aLength = aCoord.ToLength();
*aPercent = 0.0f;
}
}
// 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-baselines
// 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::eFirst) {
return aWM.IsAlphabeticalBaseline() ? aBorderBoxSize : aBorderBoxSize / 2;
}
MOZ_ASSERT(aGroup == BaselineSharingGroup::eLast);
// Round up for central baseline offset, to be consistent with eFirst.
return aWM.IsAlphabeticalBaseline() ? 0 :
(aBorderBoxSize / 2) + (aBorderBoxSize % 2);
}
enum class GridLineSide
{
eBeforeGridGap,
eAfterGridGap,
};
struct nsGridContainerFrame::TrackSize
{
enum StateBits : uint16_t {
eAutoMinSizing = 0x1,
eMinContentMinSizing = 0x2,
eMaxContentMinSizing = 0x4,
eMinOrMaxContentMinSizing = eMinContentMinSizing | eMaxContentMinSizing,
eIntrinsicMinSizing = eMinOrMaxContentMinSizing | eAutoMinSizing,
// 0x8 is unused, feel free to take it!
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,
};
StateBits Initialize(nscoord aPercentageBasis,
const nsStyleCoord& aMinCoord,
const nsStyleCoord& aMaxCoord);
bool IsFrozen() const { return mState & eFrozen; }
#ifdef DEBUG
void Dump() const;
#endif
static bool IsMinContent(const nsStyleCoord& aCoord)
{
return aCoord.GetUnit() == eStyleUnit_Enumerated &&
aCoord.GetEnumValue<StyleGridTrackBreadth>() == StyleGridTrackBreadth::MinContent;
}
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).
nscoord mBaselineSubtreeSize[2];
StateBits mState;
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(TrackSize::StateBits)
namespace mozilla {
template <>
struct IsPod<nsGridContainerFrame::TrackSize> : TrueType {};
}
TrackSize::StateBits
nsGridContainerFrame::TrackSize::Initialize(nscoord aPercentageBasis,
const nsStyleCoord& aMinCoord,
const nsStyleCoord& aMaxCoord)
{
MOZ_ASSERT(mBase == 0 && mLimit == 0 && mState == 0,
"track size data is expected to be initialized to zero");
auto minSizeUnit = aMinCoord.GetUnit();
auto maxSizeUnit = aMaxCoord.GetUnit();
if (minSizeUnit == eStyleUnit_None) {
// This track is sized using fit-content(size) (represented in style system
// with minCoord=None,maxCoord=size). In layout, fit-content(size) behaves
// as minmax(auto, max-content), with 'size' as an additional upper-bound.
mState = eFitContent;
minSizeUnit = eStyleUnit_Auto;
maxSizeUnit = eStyleUnit_Enumerated; // triggers max-content sizing below
}
if (::IsPercentOfIndefiniteSize(aMinCoord, aPercentageBasis)) {
// https://drafts.csswg.org/css-grid/#valdef-grid-template-columns-percentage
// "If the inline or block size of the grid container is indefinite,
// <percentage> values relative to that size are treated as 'auto'."
minSizeUnit = eStyleUnit_Auto;
}
if (::IsPercentOfIndefiniteSize(aMaxCoord, aPercentageBasis)) {
maxSizeUnit = eStyleUnit_Auto;
}
// http://dev.w3.org/csswg/css-grid/#algo-init
switch (minSizeUnit) {
case eStyleUnit_Auto:
mState |= eAutoMinSizing;
break;
case eStyleUnit_Enumerated:
mState |= IsMinContent(aMinCoord) ? eMinContentMinSizing
: eMaxContentMinSizing;
break;
default:
MOZ_ASSERT(minSizeUnit != eStyleUnit_FlexFraction,
"<flex> min-sizing is invalid as a track size");
mBase = ::ResolveToDefiniteSize(aMinCoord, aPercentageBasis);
}
switch (maxSizeUnit) {
case eStyleUnit_Auto:
mState |= eAutoMaxSizing;
mLimit = NS_UNCONSTRAINEDSIZE;
break;
case eStyleUnit_Enumerated:
mState |= IsMinContent(aMaxCoord) ? eMinContentMaxSizing
: eMaxContentMaxSizing;
mLimit = NS_UNCONSTRAINEDSIZE;
break;
case eStyleUnit_FlexFraction:
mState |= eFlexMaxSizing;
mLimit = mBase;
break;
default:
mLimit = ::ResolveToDefiniteSize(aMaxCoord, aPercentageBasis);
if (mLimit < mBase) {
mLimit = mBase;
}
}
mBaselineSubtreeSize[BaselineSharingGroup::eFirst] = nscoord(0);
mBaselineSubtreeSize[BaselineSharingGroup::eLast] = nscoord(0);
return mState;
}
/**
* Is aFrame1 a prev-continuation of aFrame2?
*/
static bool
IsPrevContinuationOf(nsIFrame* aFrame1, nsIFrame* aFrame2)
{
nsIFrame* prev = aFrame2;
while ((prev = prev->GetPrevContinuation())) {
if (prev == aFrame1) {
return true;
}
}
return false;
}
/**
* Moves all frames from aSrc into aDest such that the resulting aDest
* is still sorted in document content order and continuation order.
* Precondition: both |aSrc| and |aDest| must be sorted to begin with.
* @param aCommonAncestor a hint for nsLayoutUtils::CompareTreePosition
*/
static void
MergeSortedFrameLists(nsFrameList& aDest, nsFrameList& aSrc,
nsIContent* aCommonAncestor)
{
nsIFrame* dest = aDest.FirstChild();
for (nsIFrame* src = aSrc.FirstChild(); src; ) {
if (!dest) {
aDest.AppendFrames(nullptr, aSrc);
break;
}
nsIContent* srcContent = src->GetContent();
nsIContent* destContent = dest->GetContent();
int32_t result = nsLayoutUtils::CompareTreePosition(srcContent,
destContent,
aCommonAncestor);
if (MOZ_UNLIKELY(result == 0)) {
// NOTE: we get here when comparing ::before/::after for the same element.
if (MOZ_UNLIKELY(srcContent->IsGeneratedContentContainerForBefore())) {
if (MOZ_LIKELY(!destContent->IsGeneratedContentContainerForBefore()) ||
::IsPrevContinuationOf(src, dest)) {
result = -1;
}
} else if (MOZ_UNLIKELY(srcContent->IsGeneratedContentContainerForAfter())) {
if (MOZ_UNLIKELY(destContent->IsGeneratedContentContainerForAfter()) &&
::IsPrevContinuationOf(src, dest)) {
result = -1;
}
} else if (::IsPrevContinuationOf(src, dest)) {
result = -1;
}
}
if (result < 0) {
// src should come before dest
nsIFrame* next = src->GetNextSibling();
aSrc.RemoveFrame(src);
aDest.InsertFrame(nullptr, dest->GetPrevSibling(), src);
src = next;
} else {
dest = dest->GetNextSibling();
}
}
MOZ_ASSERT(aSrc.IsEmpty());
}
static void
MergeSortedFrameListsFor(nsFrameList& aDest, nsFrameList& aSrc,
nsContainerFrame* aParent)
{
MergeSortedFrameLists(aDest, aSrc, aParent->GetContent());
}
template<typename Iterator>
class nsGridContainerFrame::GridItemCSSOrderIteratorT
{
public:
enum OrderState { eUnknownOrder, eKnownOrdered, eKnownUnordered };
enum ChildFilter { eSkipPlaceholders, eIncludeAll };
GridItemCSSOrderIteratorT(nsIFrame* aGridContainer,
nsIFrame::ChildListID aListID,
ChildFilter aFilter = eSkipPlaceholders,
OrderState aState = eUnknownOrder)
: mChildren(aGridContainer->GetChildList(aListID))
, mArrayIndex(0)
, mGridItemIndex(0)
, mSkipPlaceholders(aFilter == eSkipPlaceholders)
#ifdef DEBUG
, mGridContainer(aGridContainer)
, mListID(aListID)
#endif
{
size_t count = 0;
bool isOrdered = aState != eKnownUnordered;
if (aState == eUnknownOrder) {
auto maxOrder = std::numeric_limits<int32_t>::min();
for (auto child : mChildren) {
++count;
int32_t order = child->StylePosition()->mOrder;
if (order < maxOrder) {
isOrdered = false;
break;
}
maxOrder = order;
}
}
if (isOrdered) {
mIter.emplace(begin(mChildren));
mIterEnd.emplace(end(mChildren));
} else {
count *= 2; // XXX somewhat arbitrary estimate for now...
mArray.emplace(count);
for (Iterator i(begin(mChildren)), iEnd(end(mChildren)); i != iEnd; ++i) {
mArray->AppendElement(*i);
}
// XXX replace this with nsTArray::StableSort when bug 1147091 is fixed.
std::stable_sort(mArray->begin(), mArray->end(), CSSOrderComparator);
}
if (mSkipPlaceholders) {
SkipPlaceholders();
}
}
~GridItemCSSOrderIteratorT()
{
MOZ_ASSERT(IsForward() == mGridItemCount.isNothing());
}
bool IsForward() const;
Iterator begin(const nsFrameList& aList);
Iterator end(const nsFrameList& aList);
nsIFrame* operator*() const
{
MOZ_ASSERT(!AtEnd());
if (mIter.isSome()) {
return **mIter;
}
return (*mArray)[mArrayIndex];
}
/**
* Return the child index of the current item, placeholders not counted.
* It's forbidden to call this method when the current frame is placeholder.
*/
size_t GridItemIndex() const
{
MOZ_ASSERT(!AtEnd());
MOZ_ASSERT((**this)->GetType() != nsGkAtoms::placeholderFrame,
"MUST not call this when at a placeholder");
MOZ_ASSERT(IsForward() || mGridItemIndex < *mGridItemCount,
"Returning an out-of-range mGridItemIndex...");
return mGridItemIndex;
}
void SetGridItemCount(size_t aGridItemCount)
{
#ifndef CLANG_CRASH_BUG
MOZ_ASSERT(mIter.isSome() || mArray->Length() == aGridItemCount,
"grid item count mismatch");
#endif
mGridItemCount.emplace(aGridItemCount);
// Note: it's OK if mGridItemIndex underflows -- GridItemIndex()
// will not be called unless there is at least one item.
mGridItemIndex = IsForward() ? 0 : *mGridItemCount - 1;
}
/**
* Skip over placeholder children.
*/
void SkipPlaceholders()
{
if (mIter.isSome()) {
for (; *mIter != *mIterEnd; ++*mIter) {
nsIFrame* child = **mIter;
if (child->GetType() != nsGkAtoms::placeholderFrame) {
return;
}
}
} else {
for (; mArrayIndex < mArray->Length(); ++mArrayIndex) {
nsIFrame* child = (*mArray)[mArrayIndex];
if (child->GetType() != nsGkAtoms::placeholderFrame) {
return;
}
}
}
}
bool AtEnd() const
{
#ifndef CLANG_CRASH_BUG
// Clang 3.6.2 crashes when compiling this assertion:
MOZ_ASSERT(mIter.isSome() || mArrayIndex <= mArray->Length());
#endif
return mIter ? (*mIter == *mIterEnd) : mArrayIndex >= mArray->Length();
}
void Next()
{
#ifdef DEBUG
MOZ_ASSERT(!AtEnd());
nsFrameList list = mGridContainer->GetChildList(mListID);
MOZ_ASSERT(list.FirstChild() == mChildren.FirstChild() &&
list.LastChild() == mChildren.LastChild(),
"the list of child frames must not change while iterating!");
#endif
if (mSkipPlaceholders ||
(**this)->GetType() != nsGkAtoms::placeholderFrame) {
IsForward() ? ++mGridItemIndex : --mGridItemIndex;
}
if (mIter.isSome()) {
++*mIter;
} else {
++mArrayIndex;
}
if (mSkipPlaceholders) {
SkipPlaceholders();
}
}
void Reset(ChildFilter aFilter = eSkipPlaceholders)
{
if (mIter.isSome()) {
mIter.reset();
mIter.emplace(begin(mChildren));
mIterEnd.reset();
mIterEnd.emplace(end(mChildren));
} else {
mArrayIndex = 0;
}
mGridItemIndex = IsForward() ? 0 : *mGridItemCount - 1;
mSkipPlaceholders = aFilter == eSkipPlaceholders;
if (mSkipPlaceholders) {
SkipPlaceholders();
}
}
bool IsValid() const { return mIter.isSome() || mArray.isSome(); }
void Invalidate()
{
mIter.reset();
mArray.reset();
mozWritePoison(&mChildren, sizeof(mChildren));
}
bool ItemsAreAlreadyInOrder() const { return mIter.isSome(); }
static bool CSSOrderComparator(nsIFrame* const& a, nsIFrame* const& b);
private:
nsFrameList mChildren;
// Used if child list is already in ascending 'order'.
Maybe<Iterator> mIter;
Maybe<Iterator> mIterEnd;
// Used if child list is *not* in ascending 'order'.
// This array is pre-sorted in reverse order for a reverse iterator.
Maybe<nsTArray<nsIFrame*>> mArray;
size_t mArrayIndex;
// The index of the current grid item (placeholders excluded).
size_t mGridItemIndex;
// The number of grid items (placeholders excluded).
// It's only initialized and used in a reverse iterator.
Maybe<size_t> mGridItemCount;
// Skip placeholder children in the iteration?
bool mSkipPlaceholders;
#ifdef DEBUG
nsIFrame* mGridContainer;
nsIFrame::ChildListID mListID;
#endif
};
using GridItemCSSOrderIterator = nsGridContainerFrame::GridItemCSSOrderIterator;
using ReverseGridItemCSSOrderIterator = nsGridContainerFrame::ReverseGridItemCSSOrderIterator;
template<>
bool
GridItemCSSOrderIterator::CSSOrderComparator(nsIFrame* const& a,
nsIFrame* const& b)
{ return a->StylePosition()->mOrder < b->StylePosition()->mOrder; }
template<>
bool
GridItemCSSOrderIterator::IsForward() const { return true; }
template<>
nsFrameList::iterator
GridItemCSSOrderIterator::begin(const nsFrameList& aList)
{ return aList.begin(); }
template<>
nsFrameList::iterator GridItemCSSOrderIterator::end(const nsFrameList& aList)
{ return aList.end(); }
template<>
bool
ReverseGridItemCSSOrderIterator::CSSOrderComparator(nsIFrame* const& a,
nsIFrame* const& b)
{ return a->StylePosition()->mOrder > b->StylePosition()->mOrder; }
template<>
bool
ReverseGridItemCSSOrderIterator::IsForward() const
{ return false; }
template<>
nsFrameList::reverse_iterator
ReverseGridItemCSSOrderIterator::begin(const nsFrameList& aList)
{ return aList.rbegin(); }
template<>
nsFrameList::reverse_iterator
ReverseGridItemCSSOrderIterator::end(const nsFrameList& aList)
{ return aList.rend(); }
/**
* 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 >= nsStyleGridLine::kMinLine &&
aEnd <= nsStyleGridLine::kMaxLine, "invalid span");
} else {
MOZ_ASSERT(aStart >= nsStyleGridLine::kMinLine &&
aStart <= nsStyleGridLine::kMaxLine, "invalid start line");
MOZ_ASSERT(aEnd == int32_t(kAutoLine) ||
(aEnd >= nsStyleGridLine::kMinLine &&
aEnd <= nsStyleGridLine::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(nsStyleGridLine::kMaxLine),
"invalid span");
return mEnd;
}
return mEnd - mStart;
}
/**
* Resolve this auto range to start at aStart, making it definite.
* Precondition: this range IsAuto()
*/
void ResolveAutoPosition(uint32_t aStart, uint32_t aExplicitGridOffset)
{
MOZ_ASSERT(IsAuto(), "Why call me?");
mStart = aStart;
mEnd += aStart;
// Clamping to where kMaxLine is in the explicit grid, per
// http://dev.w3.org/csswg/css-grid/#overlarge-grids :
uint32_t translatedMax = aExplicitGridOffset + nsStyleGridLine::kMaxLine;
if (MOZ_UNLIKELY(mStart >= translatedMax)) {
mEnd = translatedMax;
mStart = mEnd - 1;
} else if (MOZ_UNLIKELY(mEnd > translatedMax)) {
mEnd = translatedMax;
}
}
/**
* 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 != nsGridContainerFrame::kAutoLine) {
mStart -= aNumRemovedTracks[mStart];
}
if (mEnd != nsGridContainerFrame::kAutoLine) {
MOZ_ASSERT(mStart == nsGridContainerFrame::kAutoLine ||
mEnd > mStart, "invalid line range");
mEnd -= aNumRemovedTracks[mEnd];
}
if (mStart == mEnd) {
mEnd = nsGridContainerFrame::kAutoLine;
}
}
/**
* Return the contribution of this line range for step 2 in
* http://dev.w3.org/csswg/css-grid/#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;
/**
* @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() {}
};
/**
* 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 mCols;
LineRange mRows;
};
struct nsGridContainerFrame::GridItemInfo
{
/**
* Item state per axis.
*/
enum StateBits : uint8_t {
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,
eAllBaselineBits = eIsBaselineAligned | eSelfBaseline | eContentBaseline,
// Clamp per https://drafts.csswg.org/css-grid/#min-size-auto
eClampMarginBoxMinSize = 0x20,
};
explicit GridItemInfo(nsIFrame* aFrame,
const GridArea& aArea)
: mFrame(aFrame)
, mArea(aArea)
{
mState[eLogicalAxisBlock] = StateBits(0);
mState[eLogicalAxisInline] = StateBits(0);
mBaselineOffset[eLogicalAxisBlock] = nscoord(0);
mBaselineOffset[eLogicalAxisInline] = nscoord(0);
}
/**
* 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.
*/
uint8_t GetSelfBaseline(uint8_t aAlign, LogicalAxis aAxis,
nscoord* aBaselineOffset) const
{
MOZ_ASSERT(aAlign == NS_STYLE_ALIGN_BASELINE ||
aAlign == NS_STYLE_ALIGN_LAST_BASELINE);
if (!(mState[aAxis] & eSelfBaseline)) {
return aAlign == NS_STYLE_ALIGN_BASELINE ? NS_STYLE_ALIGN_SELF_START
: NS_STYLE_ALIGN_SELF_END;
}
*aBaselineOffset = mBaselineOffset[aAxis];
return aAlign;
}
// Return true if we should we clamp this item's Automatic Minimum Size.
// https://drafts.csswg.org/css-grid/#min-size-auto
bool ShouldClampMinSize(WritingMode aContainerWM,
LogicalAxis aContainerAxis,
nscoord aPercentageBasis) const
{
const auto pos = mFrame->StylePosition();
const auto& size = aContainerAxis == eLogicalAxisInline ?
pos->ISize(aContainerWM) : pos->BSize(aContainerWM);
// NOTE: if we have a definite or 'max-content' size then our automatic
// minimum size can't affect our size. Excluding these simplifies applying
// the clamping in the right cases later.
if (size.GetUnit() == eStyleUnit_Auto ||
::IsPercentOfIndefiniteSize(size, aPercentageBasis) || // same as 'auto'
(size.GetUnit() == eStyleUnit_Enumerated &&
size.GetIntValue() != NS_STYLE_WIDTH_MAX_CONTENT)) {
const auto& minSize = aContainerAxis == eLogicalAxisInline ?
pos->MinISize(aContainerWM) : pos->MinBSize(aContainerWM);
return minSize.GetUnit() == eStyleUnit_Auto &&
mFrame->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE;
}
return false;
}
#ifdef DEBUG
void Dump() const;
#endif
static bool IsStartRowLessThan(const GridItemInfo* a, const GridItemInfo* b)
{
return a->mArea.mRows.mStart < b->mArea.mRows.mStart;
}
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)
#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::eIsFlexing) {
printf("flexing ");
}
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
/**
* 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 aGridTemplate is the grid-template-rows/columns data for this axis
* @param aNumRepeatTracks the number of actual tracks associated with
* a repeat(auto-fill/fit) track (zero or more), or zero if there is no
* specified repeat(auto-fill/fit) track
*/
LineNameMap(const nsStyleGridTemplate& aGridTemplate,
uint32_t aNumRepeatTracks)
: mLineNameLists(aGridTemplate.mLineNameLists)
, mRepeatAutoLineNameListBefore(aGridTemplate.mRepeatAutoLineNameListBefore)
, mRepeatAutoLineNameListAfter(aGridTemplate.mRepeatAutoLineNameListAfter)
, mRepeatAutoStart(aGridTemplate.HasRepeatAuto() ?
aGridTemplate.mRepeatAutoIndex : 0)
, mRepeatAutoEnd(mRepeatAutoStart + aNumRepeatTracks)
, mRepeatEndDelta(aGridTemplate.HasRepeatAuto() ?
int32_t(aNumRepeatTracks) - 1 :
0)
, mTemplateLinesEnd(mLineNameLists.Length() + mRepeatEndDelta)
, mHasRepeatAuto(aGridTemplate.HasRepeatAuto())
{
MOZ_ASSERT(mHasRepeatAuto || aNumRepeatTracks == 0);
MOZ_ASSERT(mRepeatAutoStart <= mLineNameLists.Length());
MOZ_ASSERT(!mHasRepeatAuto || mLineNameLists.Length() >= 2);
}
/**
* Find the aNth occurrence of aName, searching forward if aNth is positive,
* and in reverse if aNth is negative (aNth == 0 is invalid), starting from
* aFromIndex (not inclusive), and return a 1-based line number.
* Also take into account there is an unconditional match at aImplicitLine
* unless it's zero.
* Return zero if aNth occurrences can't be found. In that case, aNth has
* been decremented with the number of occurrences that were found (if any).
*
* E.g. to search for "A 2" forward from the start of the grid: aName is "A"
* aNth is 2 and aFromIndex is zero. To search for "A -2", aNth is -2 and
* aFromIndex is ExplicitGridEnd + 1 (which is the line "before" the last
* line when we're searching in reverse). For "span A 2", aNth is 2 when
* used on a grid-[row|column]-end property and -2 for a *-start property,
* and aFromIndex is the line (which we should skip) on the opposite property.
*/
uint32_t FindNamedLine(const nsString& aName, int32_t* aNth,
uint32_t aFromIndex, uint32_t aImplicitLine) const
{
MOZ_ASSERT(aNth && *aNth != 0);
if (*aNth > 0) {
return FindLine(aName, aNth, aFromIndex, aImplicitLine);
}
int32_t nth = -*aNth;
int32_t line = RFindLine(aName, &nth, aFromIndex, aImplicitLine);
*aNth = -nth;
return line;
}
private:
/**
* @see FindNamedLine, this function searches forward.
*/
uint32_t FindLine(const nsString& aName, int32_t* aNth,
uint32_t aFromIndex, uint32_t aImplicitLine) const
{
MOZ_ASSERT(aNth && *aNth > 0);
int32_t nth = *aNth;
const uint32_t end = mTemplateLinesEnd;
uint32_t line;
uint32_t i = aFromIndex;
for (; i < end; i = line) {
line = i + 1;
if (line == aImplicitLine || Contains(i, aName)) {
if (--nth == 0) {
return line;
}
}
}
if (aImplicitLine > i) {
// aImplicitLine is after the lines we searched above so it's last.
// (grid-template-areas has more tracks than grid-template-[rows|columns])
if (--nth == 0) {
return aImplicitLine;
}
}
MOZ_ASSERT(nth > 0, "should have returned a valid line above already");
*aNth = nth;
return 0;
}
/**
* @see FindNamedLine, this function searches in reverse.
*/
uint32_t RFindLine(const nsString& aName, int32_t* aNth,
uint32_t aFromIndex, uint32_t aImplicitLine) const
{
MOZ_ASSERT(aNth && *aNth > 0);
if (MOZ_UNLIKELY(aFromIndex == 0)) {
return 0; // There are no named lines beyond the start of the explicit grid.
}
--aFromIndex; // (shift aFromIndex so we can treat it as inclusive)
int32_t nth = *aNth;
// The implicit line may be beyond the explicit grid so we match
// this line first if it's within the mTemplateLinesEnd..aFromIndex range.
const uint32_t end = mTemplateLinesEnd;
if (aImplicitLine > end && aImplicitLine < aFromIndex) {
if (--nth == 0) {
return aImplicitLine;
}
}
for (uint32_t i = std::min(aFromIndex, end); i; --i) {
if (i == aImplicitLine || Contains(i - 1, aName)) {
if (--nth == 0) {
return i;
}
}
}
MOZ_ASSERT(nth > 0, "should have returned a valid line above already");
*aNth = nth;
return 0;
}
// Return true if aName exists at aIndex.
bool Contains(uint32_t aIndex, const nsString& aName) const
{
if (!mHasRepeatAuto) {
return mLineNameLists[aIndex].Contains(aName);
}
if (aIndex < mRepeatAutoEnd && aIndex >= mRepeatAutoStart &&
mRepeatAutoLineNameListBefore.Contains(aName)) {
return true;
}
if (aIndex <= mRepeatAutoEnd && aIndex > mRepeatAutoStart &&
mRepeatAutoLineNameListAfter.Contains(aName)) {
return true;
}
if (aIndex <= mRepeatAutoStart) {
return mLineNameLists[aIndex].Contains(aName) ||
(aIndex == mRepeatAutoEnd &&
mLineNameLists[aIndex + 1].Contains(aName));
}
return aIndex >= mRepeatAutoEnd &&
mLineNameLists[aIndex - mRepeatEndDelta].Contains(aName);
}
// Some style data references, for easy access.
const nsTArray<nsTArray<nsString>>& mLineNameLists;
const nsTArray<nsString>& mRepeatAutoLineNameListBefore;
const nsTArray<nsString>& mRepeatAutoLineNameListAfter;
// The index of the repeat(auto-fill/fit) track, or zero if there is none.
const uint32_t mRepeatAutoStart;
// The (hypothetical) index of the last such repeat() track.
const uint32_t mRepeatAutoEnd;
// The difference between mTemplateLinesEnd and mLineNameLists.Length().
const int32_t mRepeatEndDelta;
// The end of the line name lists with repeat(auto-fill/fit) tracks accounted
// for. It is equal to mLineNameLists.Length() when a repeat() track
// generates one track (making mRepeatEndDelta == 0).
const uint32_t mTemplateLinesEnd;
// True if there is a specified repeat(auto-fill/fit) track.
const bool mHasRepeatAuto;
};
/**
* Encapsulates CSS track-sizing functions.
*/
struct nsGridContainerFrame::TrackSizingFunctions
{
TrackSizingFunctions(const nsStyleGridTemplate& aGridTemplate,
const nsStyleCoord& aAutoMinSizing,
const nsStyleCoord& aAutoMaxSizing)
: mMinSizingFunctions(aGridTemplate.mMinTrackSizingFunctions)
, mMaxSizingFunctions(aGridTemplate.mMaxTrackSizingFunctions)
, mAutoMinSizing(aAutoMinSizing)
, mAutoMaxSizing(aAutoMaxSizing)
, mExplicitGridOffset(0)
, mRepeatAutoStart(aGridTemplate.HasRepeatAuto() ?
aGridTemplate.mRepeatAutoIndex : 0)
, mRepeatAutoEnd(mRepeatAutoStart)
, mRepeatEndDelta(0)
, mHasRepeatAuto(aGridTemplate.HasRepeatAuto())
{
MOZ_ASSERT(mMinSizingFunctions.Length() == mMaxSizingFunctions.Length());
MOZ_ASSERT(!mHasRepeatAuto ||
(mMinSizingFunctions.Length() >= 1 &&
mRepeatAutoStart < mMinSizingFunctions.Length()));
}
/**
* Initialize the number of auto-fill/fit tracks to use and return that.
* (zero if no auto-fill/fit track was specified)
*/
uint32_t InitRepeatTracks(const nsStyleCoord& aGridGap, nscoord aMinSize,
nscoord aSize, nscoord aMaxSize)
{
uint32_t repeatTracks =
CalculateRepeatFillCount(aGridGap, aMinSize, aSize, aMaxSize);
SetNumRepeatTracks(repeatTracks);
// Blank out the removed flags for each of these tracks.
mRemovedRepeatTracks.SetLength(repeatTracks);
for (auto& track : mRemovedRepeatTracks) {
track = false;
}
return repeatTracks;
}
uint32_t CalculateRepeatFillCount(const nsStyleCoord& aGridGap,
nscoord aMinSize,
nscoord aSize,
nscoord aMaxSize) const
{
if (!mHasRepeatAuto) {
return 0;
}
// Spec quotes are from https://drafts.csswg.org/css-grid/#repeat-notation
const uint32_t numTracks = mMinSizingFunctions.Length();
MOZ_ASSERT(numTracks >= 1, "expected at least the repeat() track");
nscoord maxFill = aSize != NS_UNCONSTRAINEDSIZE ? aSize : aMaxSize;
if (maxFill == NS_UNCONSTRAINEDSIZE && aMinSize == 0) {
// "Otherwise, the specified track list repeats only once."
return 1;
}
nscoord repeatTrackSize = 0;
// Note that the 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/#valdef-repeat-auto-fill
const auto& maxCoord = mMaxSizingFunctions[i];
const auto* coord = &maxCoord;
if (!coord->IsCoordPercentCalcUnit()) {
coord = &mMinSizingFunctions[i];
if (!coord->IsCoordPercentCalcUnit()) {
return 1;
}
}
nscoord trackSize = ::ResolveToDefiniteSize(*coord, percentBasis);
if (i == mRepeatAutoStart) {
if (percentBasis != NS_UNCONSTRAINEDSIZE) {
// Use a minimum 1px for the repeat() track-size.
if (trackSize < AppUnitsPerCSSPixel()) {
trackSize = AppUnitsPerCSSPixel();
}
}
repeatTrackSize = trackSize;
}
sum += trackSize;
}
nscoord gridGap;
float percentSum = 0.0f;
float gridGapPercent;
ResolvePercentSizeParts(aGridGap, percentBasis, &gridGap, &gridGapPercent);
if (numTracks > 1) {
// Add grid-gaps for all the tracks including the repeat() track.
sum += gridGap * (numTracks - 1);
percentSum = gridGapPercent * (numTracks - 1);
}
// Calculate the max number of tracks that fits without overflow.
nscoord available = maxFill != NS_UNCONSTRAINEDSIZE ? maxFill : aMinSize;
nscoord size = nsLayoutUtils::AddPercents(sum, percentSum);
if (available - size < 0) {
// "if any number of repetitions would overflow, then 1 repetition"
return 1;
}
uint32_t numRepeatTracks = 1;
bool exactFit = false;
while (true) {
sum += gridGap + repeatTrackSize;
percentSum += gridGapPercent;
nscoord newSize = nsLayoutUtils::AddPercents(sum, percentSum);
if (newSize <= size) {
// Adding more repeat-tracks won't make forward progress.
return numRepeatTracks;
}
size = newSize;
nscoord remaining = available - size;
exactFit = remaining == 0;
if (remaining >= 0) {
++numRepeatTracks;
}
if (remaining <= 0) {
break;
}
}
if (!exactFit && 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)
const uint32_t maxRepeatTracks = nsStyleGridLine::kMaxLine - numTracks;
return std::min(numRepeatTracks, maxRepeatTracks);
}
/**
* 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(nsStyleGridLine::kMaxLine));
return end;
}
const nsStyleCoord& MinSizingFor(uint32_t aTrackIndex) const
{
if (MOZ_UNLIKELY(aTrackIndex < mExplicitGridOffset)) {
return mAutoMinSizing;
}
uint32_t index = aTrackIndex - mExplicitGridOffset;
if (index >= mRepeatAutoStart) {
if (index < mRepeatAutoEnd) {
return mMinSizingFunctions[mRepeatAutoStart];
}
index -= mRepeatEndDelta;
}
return index < mMinSizingFunctions.Length() ?
mMinSizingFunctions[index] : mAutoMinSizing;
}
const nsStyleCoord& MaxSizingFor(uint32_t aTrackIndex) const
{
if (MOZ_UNLIKELY(aTrackIndex < mExplicitGridOffset)) {
return mAutoMaxSizing;
}
uint32_t index = aTrackIndex - mExplicitGridOffset;
if (index >= mRepeatAutoStart) {
if (index < mRepeatAutoEnd) {
return mMaxSizingFunctions[mRepeatAutoStart];
}
index -= mRepeatEndDelta;
}
return index < mMaxSizingFunctions.Length() ?
mMaxSizingFunctions[index] : mAutoMaxSizing;
}
uint32_t NumExplicitTracks() const
{
return mMinSizingFunctions.Length() + mRepeatEndDelta;
}
uint32_t NumRepeatTracks() const
{
return mRepeatAutoEnd - mRepeatAutoStart;
}
void SetNumRepeatTracks(uint32_t aNumRepeatTracks)
{
MOZ_ASSERT(mHasRepeatAuto || aNumRepeatTracks == 0);
mRepeatAutoEnd = mRepeatAutoStart + aNumRepeatTracks;
mRepeatEndDelta = mHasRepeatAuto ?
int32_t(aNumRepeatTracks) - 1 :
0;
}
// Some style data references, for easy access.
const nsTArray<nsStyleCoord>& mMinSizingFunctions;
const nsTArray<nsStyleCoord>& mMaxSizingFunctions;
const nsStyleCoord& mAutoMinSizing;
const nsStyleCoord& mAutoMaxSizing;
// 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.
const uint32_t mRepeatAutoStart;
// The (hypothetical) index of the last such repeat() track.
uint32_t mRepeatAutoEnd;
// The difference between mExplicitGridEnd and mMinSizingFunctions.Length().
int32_t mRepeatEndDelta;
// True if there is a specified repeat(auto-fill/fit) track.
const bool mHasRepeatAuto;
// True if this track (relative to mRepeatAutoStart) is a removed auto-fit.
nsTArray<bool> mRemovedRepeatTracks;
};
/**
* State for the tracks in one dimension.
*/
struct nsGridContainerFrame::Tracks
{
explicit Tracks(LogicalAxis aAxis)
: mStateUnion(TrackSize::StateBits(0))
, mAxis(aAxis)
, mCanResolveLineRangeSize(false)
{
mBaselineSubtreeAlign[BaselineSharingGroup::eFirst] = NS_STYLE_ALIGN_AUTO;
mBaselineSubtreeAlign[BaselineSharingGroup::eLast] = NS_STYLE_ALIGN_AUTO;
mBaseline[BaselineSharingGroup::eFirst] = NS_INTRINSIC_WIDTH_UNKNOWN;
mBaseline[BaselineSharingGroup::eLast] = NS_INTRINSIC_WIDTH_UNKNOWN;
}
void Initialize(const TrackSizingFunctions& aFunctions,
const nsStyleCoord& aGridGap,
uint32_t aNumTracks,
nscoord aContentBoxSize);
/**
* Return true if aRange spans at least one track with an intrinsic sizing
* function and does not span any tracks with a <flex> max-sizing function.
* @param aRange the span of tracks to check
* @param aState will be set to the union of the state bits of all the spanned
* tracks, unless a flex track is found - then it only contains
* the union of the tracks up to and including the flex track.
*/
bool HasIntrinsicButNoFlexSizingInRange(const LineRange& aRange,
TrackSize::StateBits* aState) const;
// Some data we collect for aligning baseline-aligned items.
struct ItemBaselineData
{
uint32_t mBaselineTrack;
nscoord mBaseline;
nscoord mSize;
GridItemInfo* mGridItem;
static bool IsBaselineTrackLessThan(const ItemBaselineData& a,
const ItemBaselineData& b)
{
return a.mBaselineTrack < b.mBaselineTrack;
}
};
/**
* Calculate baseline offsets for the given set of items.
* Helper for InitialzeItemBaselines.
*/
void CalculateItemBaselines(nsTArray<ItemBaselineData>& aBaselineItems,
BaselineSharingGroup aBaselineGroup);
/**
* Initialize grid item baseline state and offsets.
*/
void InitializeItemBaselines(GridReflowInput& aState,
nsTArray<GridItemInfo>& aGridItems);
/**
* Apply the additional alignment needed to align the baseline-aligned subtree
* the item belongs to within its baseline track.
*/
void AlignBaselineSubtree(const GridItemInfo& aGridItem) const;
/**
* Resolve Intrinsic Track Sizes.
* http://dev.w3.org/csswg/css-grid/#algo-content
*/
void ResolveIntrinsicSize(GridReflowInput& aState,
nsTArray<GridItemInfo>& aGridItems,
const TrackSizingFunctions& aFunctions,
LineRange GridArea::* aRange,
nscoord aPercentageBasis,
SizingConstraint aConstraint);
/**
* Helper for ResolveIntrinsicSize. It implements step 1 "size tracks to fit
* non-spanning items" in the spec. Return true if the track has a <flex>
* max-sizing function, false otherwise.
*/
bool ResolveIntrinsicSizeStep1(GridReflowInput& aState,
const TrackSizingFunctions& aFunctions,
nscoord aPercentageBasis,
SizingConstraint aConstraint,
const LineRange& aRange,
const GridItemInfo& aGridItem);
/**
* Collect the tracks which are growable (matching aSelector) into
* aGrowableTracks, and return the amount of space that can be used
* to grow those tracks. Specifically, we return aAvailableSpace minus
* the sum of mBase's (and corresponding grid gaps) in aPlan (clamped to 0)
* for the tracks in aRange, or zero when there are no growable tracks.
* @note aPlan[*].mBase represents a planned new base or limit.
*/
nscoord CollectGrowable(nscoord aAvailableSpace,
const nsTArray<TrackSize>& aPlan,
const LineRange& aRange,
TrackSize::StateBits aSelector,
nsTArray<uint32_t>& aGrowableTracks) const
{
MOZ_ASSERT(aAvailableSpace > 0, "why call me?");
nscoord space = aAvailableSpace - mGridGap * (aRange.Extent() - 1);
const uint32_t start = aRange.mStart;
const uint32_t end = aRange.mEnd;
for (uint32_t i = start; i < end; ++i) {
const TrackSize& sz = aPlan[i];
space -= sz.mBase;
if (space <= 0) {
return 0;
}
if ((sz.mState & aSelector) && !sz.IsFrozen()) {
aGrowableTracks.AppendElement(i);
}
}
return aGrowableTracks.IsEmpty() ? 0 : space;
}
void SetupGrowthPlan(nsTArray<TrackSize>& aPlan,
const nsTArray<uint32_t>& aTracks) const
{
for (uint32_t track : aTracks) {
aPlan[track] = mSizes[track];
}
}
void CopyPlanToBase(const nsTArray<TrackSize>& aPlan,
const nsTArray<uint32_t>& aTracks)
{
for (uint32_t track : aTracks) {
MOZ_ASSERT(mSizes[track].mBase <= aPlan[track].mBase);
mSizes[track].mBase = aPlan[track].mBase;
}
}
void CopyPlanToLimit(const nsTArray<TrackSize>& aPlan,
const nsTArray<uint32_t>& aTracks)
{
for (uint32_t track : aTracks) {
MOZ_ASSERT(mSizes[track].mLimit == NS_UNCONSTRAINEDSIZE ||
mSizes[track].mLimit <= aPlan[track].mBase);
mSizes[track].mLimit = aPlan[track].mBase;
}
}
using FitContentClamper =
std::function<bool(uint32_t aTrack, nscoord aMinSize, nscoord* aSize)>;
/**
* Grow the planned size for tracks in aGrowableTracks up to their limit
* and then freeze them (all aGrowableTracks must be unfrozen on entry).
* Subtract the space added from aAvailableSpace and return that.
*/
nscoord GrowTracksToLimit(nscoord aAvailableSpace,
nsTArray<TrackSize>& aPlan,
const nsTArray<uint32_t>& aGrowableTracks,
FitContentClamper aFitContentClamper) const
{
MOZ_ASSERT(aAvailableSpace > 0 && aGrowableTracks.Length() > 0);
nscoord space = aAvailableSpace;
uint32_t numGrowable = aGrowableTracks.Length();
while (true) {
nscoord spacePerTrack = std::max<nscoord>(space / numGrowable, 1);
for (uint32_t track : aGrowableTracks) {
TrackSize& sz = aPlan[track];
if (sz.IsFrozen()) {
continue;
}
nscoord newBase = sz.mBase + spacePerTrack;
nscoord limit = sz.mLimit;
if (MOZ_UNLIKELY((sz.mState & TrackSize::eFitContent) &&
aFitContentClamper)) {
// Clamp the limit to the fit-content() size, for §12.5.2 step 5/6.
aFitContentClamper(track, sz.mBase, &limit);
}
if (newBase > limit) {
nscoord consumed = limit - sz.mBase;
if (consumed > 0) {
space -= consumed;
sz.mBase = limit;
}
sz.mState |= TrackSize::eFrozen;
if (--numGrowable == 0) {
return space;
}
} else {
sz.mBase = newBase;
space -= spacePerTrack;
}
MOZ_ASSERT(space >= 0);
if (space == 0) {
return 0;
}
}
}
MOZ_ASSERT_UNREACHABLE("we don't exit the loop above except by return");
return 0;
}
/**
* Helper for GrowSelectedTracksUnlimited. For the set of tracks (S) that
* match aMinSizingSelector: if a track in S doesn't match aMaxSizingSelector
* then mark it with aSkipFlag. If all tracks in S were marked then unmark
* them. Return aNumGrowable minus the number of tracks marked. It is
* assumed that aPlan have no aSkipFlag set for tracks in aGrowableTracks
* on entry to this method.
*/
uint32_t MarkExcludedTracks(nsTArray<TrackSize>& aPlan,
uint32_t aNumGrowable,
const nsTArray<uint32_t>& aGrowableTracks,
TrackSize::StateBits aMinSizingSelector,
TrackSize::StateBits aMaxSizingSelector,
TrackSize::StateBits aSkipFlag) const
{
bool foundOneSelected = false;
bool foundOneGrowable = false;
uint32_t numGrowable = aNumGrowable;
for (uint32_t track : aGrowableTracks) {
TrackSize& sz = aPlan[track];
const auto state = sz.mState;
if (state & aMinSizingSelector) {
foundOneSelected = true;
if (state & aMaxSizingSelector) {
foundOneGrowable = true;
continue;
}
sz.mState |= aSkipFlag;
MOZ_ASSERT(numGrowable != 0);
--numGrowable;
}
}
// 12.5 "if there are no such tracks, then all affected tracks"
if (foundOneSelected && !foundOneGrowable) {
for (uint32_t track : aGrowableTracks) {
aPlan[track].mState &= ~aSkipFlag;
}
numGrowable = aNumGrowable;
}
return numGrowable;
}
/**
* Increase the planned size for tracks in aGrowableTracks that match
* aSelector (or all tracks if aSelector is zero) beyond their limit.
* This implements the "Distribute space beyond growth limits" step in
* https://drafts.csswg.org/css-grid/#distribute-extra-space
*/
void GrowSelectedTracksUnlimited(nscoord aAvailableSpace,
nsTArray<TrackSize>& aPlan,
const nsTArray<uint32_t>& aGrowableTracks,
TrackSize::StateBits aSelector,
FitContentClamper aFitContentClamper) const
{
MOZ_ASSERT(aAvailableSpace > 0 && aGrowableTracks.Length() > 0);
uint32_t numGrowable = aGrowableTracks.Length();
if (aSelector) {
MOZ_ASSERT(aSelector == (aSelector & TrackSize::eIntrinsicMinSizing) &&
(aSelector & TrackSize::eMaxContentMinSizing),
"Should only get here for track sizing steps 2.1 to 2.3");
// Note that eMaxContentMinSizing is always included. We do those first:
numGrowable = MarkExcludedTracks(aPlan, numGrowable, aGrowableTracks,
TrackSize::eMaxContentMinSizing,
TrackSize::eMaxContentMaxSizing,
TrackSize::eSkipGrowUnlimited1);
// Now mark min-content/auto min-sizing tracks if requested.
auto minOrAutoSelector = aSelector & ~TrackSize::eMaxContentMinSizing;
if (minOrAutoSelector) {
numGrowable = MarkExcludedTracks(aPlan, numGrowable, aGrowableTracks,
minOrAutoSelector,
TrackSize::eIntrinsicMaxSizing,
TrackSize::eSkipGrowUnlimited2);
}
}
nscoord space = aAvailableSpace;
DebugOnly<bool> didClamp = false;
while (numGrowable) {
nscoord spacePerTrack = std::max<nscoord>(space / numGrowable, 1);
for (uint32_t track : aGrowableTracks) {
TrackSize& sz = aPlan[track];
if (sz.mState & TrackSize::eSkipGrowUnlimited) {
continue; // an excluded track
}
nscoord delta = spacePerTrack;
nscoord newBase = sz.mBase + delta;
if (MOZ_UNLIKELY((sz.mState & TrackSize::eFitContent) &&
aFitContentClamper)) {
// Clamp newBase to the fit-content() size, for §12.5.2 step 5/6.
if (aFitContentClamper(track, sz.mBase, &newBase)) {
didClamp = true;
delta = newBase - sz.mBase;
MOZ_ASSERT(delta >= 0, "track size shouldn't shrink");
sz.mState |= TrackSize::eSkipGrowUnlimited1;
--numGrowable;
}
}
sz.mBase = newBase;
space -= delta;
MOZ_ASSERT(space >= 0);
if (space == 0) {
return;
}
}
}
MOZ_ASSERT(didClamp, "we don't exit the loop above except by return, "
"unless we clamped some track's size");
}
/**
* Distribute aAvailableSpace to the planned base size for aGrowableTracks
* up to their limits, then distribute the remaining space beyond the limits.
*/
void DistributeToTrackBases(nscoord aAvailableSpace,
nsTArray<TrackSize>& aPlan,
nsTArray<uint32_t>& aGrowableTracks,
TrackSize::StateBits aSelector)
{
SetupGrowthPlan(aPlan, aGrowableTracks);
nscoord space = GrowTracksToLimit(aAvailableSpace, aPlan, aGrowableTracks, nullptr);
if (space > 0) {
GrowSelectedTracksUnlimited(space, aPlan, aGrowableTracks, aSelector, nullptr);
}
CopyPlanToBase(aPlan, aGrowableTracks);
}
/**
* Distribute aAvailableSpace to the planned limits for aGrowableTracks.
*/
void DistributeToTrackLimits(nscoord aAvailableSpace,
nsTArray<TrackSize>& aPlan,
nsTArray<uint32_t>& aGrowableTracks,
const TrackSizingFunctions& aFunctions,
nscoord aPercentageBasis)
{
auto fitContentClamper = [&aFunctions, aPercentageBasis] (uint32_t aTrack,
nscoord aMinSize,
nscoord* aSize) {
nscoord fitContentLimit =
::ResolveToDefiniteSize(aFunctions.MaxSizingFor(aTrack), aPercentageBasis);
if (*aSize > fitContentLimit) {
*aSize = std::max(aMinSize, fitContentLimit);
return true;
}
return false;
};
nscoord space = GrowTracksToLimit(aAvailableSpace, aPlan, aGrowableTracks,
fitContentClamper);
if (space > 0) {
GrowSelectedTracksUnlimited(aAvailableSpace, aPlan, aGrowableTracks,
TrackSize::StateBits(0), fitContentClamper);
}
CopyPlanToLimit(aPlan, aGrowableTracks);
}
/**
* Distribute aAvailableSize to the tracks. This implements 12.6 at:
* http://dev.w3.org/csswg/css-grid/#algo-grow-tracks
*/
void DistributeFreeSpace(nscoord aAvailableSize)
{
const uint32_t numTracks = mSizes.Length();
if (MOZ_UNLIKELY(numTracks == 0 || aAvailableSize <= 0)) {
return;
}
if (aAvailableSize == NS_UNCONSTRAINEDSIZE) {
for (TrackSize& sz : mSizes) {
sz.mBase = sz.mLimit;
}
} else {
// Compute free space and count growable tracks.
nscoord space = aAvailableSize;
uint32_t numGrowable = numTracks;
for (const TrackSize& sz : mSizes) {
space -= sz.mBase;
MOZ_ASSERT(sz.mBase <= sz.mLimit);
if (sz.mBase == sz.mLimit) {
--numGrowable;
}
}
// Distribute the free space evenly to the growable tracks. If not exactly
// divisable the remainder is added to the leading tracks.
while (space > 0 && numGrowable) {
nscoord spacePerTrack =
std::max<nscoord>(space / numGrowable, 1);
for (uint32_t i = 0; i < numTracks && space > 0; ++i) {
TrackSize& sz = mSizes[i];
if (sz.mBase == sz.mLimit) {
continue;
}
nscoord newBase = sz.mBase + spacePerTrack;
if (newBase >= sz.mLimit) {
space -= sz.mLimit - sz.mBase;
sz.mBase = sz.mLimit;
--numGrowable;
} else {
space -= spacePerTrack;
sz.mBase = newBase;
}
}
}
}
}
/**
* Implements "12.7.1. Find the Size of an 'fr'".
* http://dev.w3.org/csswg/css-grid/#algo-find-fr-size
* (The returned value is a 'nscoord' divided by a factor - a floating type
* is used to avoid intermediary rounding errors.)
*/
float FindFrUnitSize(const LineRange& aRange,
const nsTArray<uint32_t>& aFlexTracks,
const TrackSizingFunctions& aFunctions,
nscoord aSpaceToFill) const;
/**
* Implements the "find the used flex fraction" part of StretchFlexibleTracks.
* (The returned value is a 'nscoord' divided by a factor - a floating type
* is used to avoid intermediary rounding errors.)
*/
float FindUsedFlexFraction(GridReflowInput& aState,
nsTArray<GridItemInfo>& aGridItems,
const nsTArray<uint32_t>& aFlexTracks,
const TrackSizingFunctions& aFunctions,
nscoord aAvailableSize) const;
/**
* Implements "12.7. Stretch Flexible Tracks"
* http://dev.w3.org/csswg/css-grid/#algo-flex-tracks
*/
void StretchFlexibleTracks(GridReflowInput& aState,
nsTArray<GridItemInfo>& aGridItems,
const TrackSizingFunctions& aFunctions,
nscoord aAvailableSize);
/**
* Implements "12.3. Track Sizing Algorithm"
* http://dev.w3.org/csswg/css-grid/#algo-track-sizing
*/
void CalculateSizes(GridReflowInput& aState,
nsTArray<GridItemInfo>& aGridItems,
const TrackSizingFunctions& aFunctions,
nscoord aContentSize,
LineRange GridArea::* aRange,
SizingConstraint aConstraint);
/**
* Apply 'align/justify-content', whichever is relevant for this axis.
* https://drafts.csswg.org/css-align-3/#propdef-align-content
*/
void AlignJustifyContent(const nsStylePosition* aStyle,
WritingMode aWM,
const LogicalSize& aContainerSize);
/**
* Return the intrinsic size by back-computing percentages as:
* IntrinsicSize = SumOfCoordSizes / (1 - SumOfPercentages).
*/
nscoord BackComputedIntrinsicSize(const TrackSizingFunctions& aFunctions,
const nsStyleCoord& aGridGap) const;
nscoord GridLineEdge(uint32_t aLine, GridLineSide aSide) const
{
if (MOZ_UNLIKELY(mSizes.IsEmpty())) {
// https://drafts.csswg.org/css-grid/#grid-definition
// "... the explicit grid still contains one grid line in each axis."
MOZ_ASSERT(aLine == 0, "We should only resolve line 1 in an empty grid");
return nscoord(0);
}
MOZ_ASSERT(aLine <= mSizes.Length(), "mSizes is too small");
if (aSide == GridLineSide::eBeforeGridGap) {
if (aLine == 0) {
return nscoord(0);
}
const TrackSize& sz = mSizes[aLine - 1];
return sz.mPosition + sz.mBase;
}
if (aLine == mSizes.Length()) {
return mContentBoxSize;
}
return mSizes[aLine].mPosition;
}
nscoord SumOfGridGaps() const
{
auto len = mSizes.Length();
return MOZ_LIKELY(len > 1) ? (len - 1) * mGridGap : 0;
}
/**
* Break before aRow, i.e. set the eBreakBefore flag on aRow and set the grid
* gap before aRow to zero (and shift all rows after it by the removed gap).
*/
void BreakBeforeRow(uint32_t aRow)
{
MOZ_ASSERT(mAxis == eLogicalAxisBlock,
"Should only be fragmenting in the block axis (between rows)");
nscoord prevRowEndPos = 0;
if (aRow != 0) {
auto& prevSz = mSizes[aRow - 1];
prevRowEndPos = prevSz.mPosition + prevSz.mBase;
}
auto& sz = mSizes[aRow];
const nscoord gap = sz.mPosition - prevRowEndPos;
sz.mState |= TrackSize::eBreakBefore;
if (gap != 0) {
for (uint32_t i = aRow, len = mSizes.Length(); i < len; ++i) {
mSizes[i].mPosition -= gap;
}
}
}
/**
* Set the size of aRow to aSize and adjust the position of all rows after it.
*/
void ResizeRow(uint32_t aRow, nscoord aNewSize)
{
MOZ_ASSERT(mAxis == eLogicalAxisBlock,
"Should only be fragmenting in the block axis (between rows)");
MOZ_ASSERT(aNewSize >= 0);
auto& sz = mSizes[aRow];
nscoord delta = aNewSize - sz.mBase;
NS_WARNING_ASSERTION(delta != nscoord(0), "Useless call to ResizeRow");
sz.mBase = aNewSize;
const uint32_t numRows = mSizes.Length();
for (uint32_t r = aRow + 1; r < numRows; ++r) {
mSizes[r].mPosition += delta;
}
}
nscoord ResolveSize(const LineRange& aRange) const
{
MOZ_ASSERT(mCanResolveLineRangeSize);
MOZ_ASSERT(aRange.Extent() > 0, "grid items cover at least one track");
nscoord pos, size;
aRange.ToPositionAndLength(mSizes, &pos, &size);
return size;
}
nsTArray<nsString> GetExplicitLineNamesAtIndex(
const nsStyleGridTemplate& aGridTemplate,
const TrackSizingFunctions& aFunctions,
uint32_t aIndex)
{
nsTArray<nsString> lineNames;
bool hasRepeatAuto = aGridTemplate.HasRepeatAuto();
const nsTArray<nsTArray<nsString>>& lineNameLists(
aGridTemplate.mLineNameLists);
if (!hasRepeatAuto) {
if (aIndex < lineNameLists.Length()) {
lineNames.AppendElements(lineNameLists[aIndex]);
}
} else {
const uint32_t repeatTrackCount = aFunctions.NumRepeatTracks();
const uint32_t repeatAutoStart = aGridTemplate.mRepeatAutoIndex;
const uint32_t repeatAutoEnd = (repeatAutoStart + repeatTrackCount);
const int32_t repeatEndDelta = int32_t(repeatTrackCount - 1);
if (aIndex <= repeatAutoStart) {
if (aIndex < lineNameLists.Length()) {
lineNames.AppendElements(lineNameLists[aIndex]);
}
if (aIndex == repeatAutoEnd) {
uint32_t i = aIndex + 1;
if (i < lineNameLists.Length()) {
lineNames.AppendElements(lineNameLists[i]);
}
}
}
if (aIndex <= repeatAutoEnd && aIndex > repeatAutoStart) {
lineNames.AppendElements(aGridTemplate.mRepeatAutoLineNameListAfter);
}
if (aIndex < repeatAutoEnd && aIndex >= repeatAutoStart) {
lineNames.AppendElements(aGridTemplate.mRepeatAutoLineNameListBefore);
}
if (aIndex >= repeatAutoEnd && aIndex > repeatAutoStart) {
uint32_t i = aIndex - repeatEndDelta;
if (i < lineNameLists.Length()) {
lineNames.AppendElements(lineNameLists[i]);
}
}
}
return lineNames;
}
#ifdef DEBUG
void Dump() const
{
for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) {
printf(" %d: ", i);
mSizes[i].Dump();
printf("\n");
}
}
#endif
AutoTArray<TrackSize, 32> mSizes;
nscoord mContentBoxSize;
nscoord mGridGap;
// The first(last)-baseline for the first(last) track in this axis.
nscoord mBaseline[2]; // index by BaselineSharingGroup
// The union of the track min/max-sizing state bits in this axis.
TrackSize::StateBits mStateUnion;
LogicalAxis mAxis;
// Used for aligning a baseline-aligned subtree of items. The only possible
// values are NS_STYLE_ALIGN_{START,END,CENTER,AUTO}. AUTO means there are
// no baseline-aligned items in any track in that axis.
// There is one alignment value for each BaselineSharingGroup.
uint8_t mBaselineSubtreeAlign[2];
// True if track positions and sizes are final in this axis.
bool mCanResolveLineRangeSize;
};
/**
* Grid data shared by all continuations, owned by the first-in-flow.
* The data is initialized from the first-in-flow's GridReflowInput at
* the end of its reflow. Fragmentation will modify mRows.mSizes -
* the mPosition to remove the row gap at the break boundary, the mState
* by setting the eBreakBefore flag, and mBase is modified when we decide
* to grow a row. mOriginalRowData is setup by the first-in-flow and
* not modified after that. It's used for undoing the changes to mRows.
* mCols, mGridItems, mAbsPosItems are used for initializing the grid
* reflow state for continuations, see GridReflowInput::Initialize below.
*/
struct nsGridContainerFrame::SharedGridData
{
SharedGridData() :
mCols(eLogicalAxisInline),
mRows(eLogicalAxisBlock),
mGenerateComputedGridInfo(false) {}
Tracks mCols;
Tracks mRows;
struct RowData {
nscoord mBase; // the original track size
nscoord mGap; // the original gap before a track
};
nsTArray<RowData> mOriginalRowData;
nsTArray<GridItemInfo> mGridItems;
nsTArray<GridItemInfo> mAbsPosItems;
bool mGenerateComputedGridInfo;
/**
* Only set on the first-in-flow. Continuations will Initialize() their
* GridReflowInput from it.
*/
NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, SharedGridData)
};
struct MOZ_STACK_CLASS nsGridContainerFrame::GridReflowInput
{
GridReflowInput(nsGridContainerFrame* aFrame,
const ReflowInput& aRI)
: GridReflowInput(aFrame, *aRI.mRenderingContext, &aRI, aRI.mStylePosition,
aRI.GetWritingMode())
{}
GridReflowInput(nsGridContainerFrame* aFrame,
nsRenderingContext& aRC)
: GridReflowInput(aFrame, aRC, nullptr, aFrame->StylePosition(),
aFrame->GetWritingMode())
{}
/**
* Initialize our track sizes and grid item info using the shared
* state from aGridContainerFrame first-in-flow.
*/
void InitializeForContinuation(nsGridContainerFrame* aGridContainerFrame,
nscoord aConsumedBSize)
{
MOZ_ASSERT(aGridContainerFrame->GetPrevInFlow(),
"don't call this on the first-in-flow");
MOZ_ASSERT(mGridItems.IsEmpty() && mAbsPosItems.IsEmpty(),
"shouldn't have any item data yet");
// Get the SharedGridData from the first-in-flow. Also calculate the number
// of fragments before this so that we can figure out our start row below.
uint32_t fragment = 0;
nsIFrame* firstInFlow = aGridContainerFrame;
for (auto pif = aGridContainerFrame->GetPrevInFlow();
pif; pif = pif->GetPrevInFlow()) {
++fragment;
firstInFlow = pif;
}
mSharedGridData = firstInFlow->Properties().Get(SharedGridData::Prop());
MOZ_ASSERT(mSharedGridData, "first-in-flow must have SharedGridData");
// Find the start row for this fragment and undo breaks after that row
// since the breaks might be different from the last reflow.
auto& rowSizes = mSharedGridData->mRows.mSizes;
const uint32_t numRows = rowSizes.Length();
mStartRow = numRows;
for (uint32_t row = 0, breakCount = 0; row < numRows; ++row) {
if (rowSizes[row].mState & TrackSize::eBreakBefore) {
if (fragment == ++breakCount) {
mStartRow = row;
mFragBStart = rowSizes[row].mPosition;
// Restore the original size for |row| and grid gaps / state after it.
const auto& origRowData = mSharedGridData->mOriginalRowData;
rowSizes[row].mBase = origRowData[row].mBase;
nscoord prevEndPos = rowSizes[row].mPosition + rowSizes[row].mBase;
while (++row < numRows) {
auto& sz = rowSizes[row];
const auto& orig = origRowData[row];
sz.mPosition = prevEndPos + orig.mGap;
sz.mBase = orig.mBase;
sz.mState &= ~TrackSize::eBreakBefore;
prevEndPos = sz.mPosition + sz.mBase;
}
break;
}
}
}
if (mStartRow == numRows) {
// All of the grid's rows fit inside of previous grid-container fragments.
mFragBStart = aConsumedBSize;
}
// Copy the shared track state.
// XXX consider temporarily swapping the array elements instead and swapping
// XXX them back after we're done reflowing, for better performance.
// XXX (bug 1252002)
mCols = mSharedGridData->mCols;
mRows = mSharedGridData->mRows;
// Copy item data from each child's first-in-flow data in mSharedGridData.
// XXX NOTE: This is O(n^2) in the number of items. (bug 1252186)
mIter.Reset();
for (; !mIter.AtEnd(); mIter.Next()) {
nsIFrame* child = *mIter;
nsIFrame* childFirstInFlow = child->FirstInFlow();
DebugOnly<size_t> len = mGridItems.Length();
for (auto& itemInfo : mSharedGridData->mGridItems) {
if (itemInfo.mFrame == childFirstInFlow) {
auto item = mGridItems.AppendElement(GridItemInfo(child, itemInfo.mArea));
// Copy the item's baseline data so that the item's last fragment can do
// 'last baseline' alignment if necessary.
item->mState[0] |= itemInfo.mState[0] & ItemState::eAllBaselineBits;
item->mState[1] |= itemInfo.mState[1] & ItemState::eAllBaselineBits;
item->mBaselineOffset[0] = itemInfo.mBaselineOffset[0];
item->mBaselineOffset[1] = itemInfo.mBaselineOffset[1];
break;
}
}
MOZ_ASSERT(mGridItems.Length() == len + 1, "can't find GridItemInfo");
}
// XXX NOTE: This is O(n^2) in the number of abs.pos. items. (bug 1252186)
nsFrameList absPosChildren(aGridContainerFrame->GetChildList(
aGridContainerFrame->GetAbsoluteListID()));
for (auto f : absPosChildren) {
nsIFrame* childFirstInFlow = f->FirstInFlow();
DebugOnly<size_t> len = mAbsPosItems.Length();
for (auto& itemInfo : mSharedGridData->mAbsPosItems) {
if (itemInfo.mFrame == childFirstInFlow) {
mAbsPosItems.AppendElement(GridItemInfo(f, itemInfo.mArea));
break;
}
}
MOZ_ASSERT(mAbsPosItems.Length() == len + 1, "can't find GridItemInfo");
}
// Copy in the computed grid info state bit
if (mSharedGridData->mGenerateComputedGridInfo) {
aGridContainerFrame->AddStateBits(NS_STATE_GRID_GENERATE_COMPUTED_VALUES);
}
}
/**
* Calculate our track sizes. If the given aContentBox block-axis size is
* unconstrained, it is assigned to the resulting intrinsic block-axis size.
*/
void CalculateTrackSizes(const Grid& aGrid,
LogicalSize& aContentBox,
SizingConstraint aConstraint);
/**
* Return the containing block for a grid item occupying aArea.
*/
LogicalRect ContainingBlockFor(const GridArea& aArea) const;
/**
* Return the containing block for an abs.pos. grid item occupying aArea.
* Any 'auto' lines in the grid area will be aligned with grid container
* containing block on that side.
* @param aGridOrigin the origin of the grid
* @param aGridCB the grid container containing block (its padding area)
*/
LogicalRect ContainingBlockForAbsPos(const GridArea& aArea,
const LogicalPoint& aGridOrigin,
const LogicalRect& aGridCB) const;
GridItemCSSOrderIterator mIter;
const nsStylePosition* const mGridStyle;
Tracks mCols;
Tracks mRows;
TrackSizingFunctions mColFunctions;
TrackSizingFunctions mRowFunctions;
/**
* Info about each (normal flow) grid item.
*/
nsTArray<GridItemInfo> mGridItems;
/**
* Info about each grid-aligned abs.pos. child.
*/
nsTArray<GridItemInfo> mAbsPosItems;
/**
* @note mReflowInput may be null when using the 2nd ctor above. In this case
* we'll construct a dummy parent reflow state if we need it to calculate
* min/max-content contributions when sizing tracks.
*/
const ReflowInput* const mReflowInput;
nsRenderingContext& mRenderingContext;
nsGridContainerFrame* const mFrame;
SharedGridData* mSharedGridData; // [weak] owned by mFrame's first-in-flow.
/** Computed border+padding with mSkipSides applied. */
LogicalMargin mBorderPadding;
/**
* BStart of this fragment in "grid space" (i.e. the concatenation of content
* areas of all fragments). Equal to mRows.mSizes[mStartRow].mPosition,
* or, if this fragment starts after the last row, the ConsumedBSize().
*/
nscoord mFragBStart;
/** The start row for this fragment. */
uint32_t mStartRow;
/**
* The start row for the next fragment, if any. If mNextFragmentStartRow ==
* mStartRow then there are no rows in this fragment.
*/
uint32_t mNextFragmentStartRow;
/** Our tentative ApplySkipSides bits. */
LogicalSides mSkipSides;
const WritingMode mWM;
/** Initialized lazily, when we find the fragmentainer. */
bool mInFragmentainer;
private:
GridReflowInput(nsGridContainerFrame* aFrame,
nsRenderingContext& aRenderingContext,
const ReflowInput* aReflowInput,
const nsStylePosition* aGridStyle,
const WritingMode& aWM)
: mIter(aFrame, kPrincipalList)
, mGridStyle(aGridStyle)
, mCols(eLogicalAxisInline)
, mRows(eLogicalAxisBlock)
, mColFunctions(mGridStyle->mGridTemplateColumns,
mGridStyle->mGridAutoColumnsMin,
mGridStyle->mGridAutoColumnsMax)
, mRowFunctions(mGridStyle->mGridTemplateRows,
mGridStyle->mGridAutoRowsMin,
mGridStyle->mGridAutoRowsMax)
, mReflowInput(aReflowInput)
, mRenderingContext(aRenderingContext)
, mFrame(aFrame)
, mSharedGridData(nullptr)
, mBorderPadding(aWM)
, mFragBStart(0)
, mStartRow(0)
, mNextFragmentStartRow(0)
, mWM(aWM)
, mInFragmentainer(false)
{
MOZ_ASSERT(!aReflowInput || aReflowInput->mFrame == mFrame);
if (aReflowInput) {
mBorderPadding = aReflowInput->ComputedLogicalBorderPadding();
mSkipSides = aFrame->PreReflowBlockLevelLogicalSkipSides();
mBorderPadding.ApplySkipSides(mSkipSides);
}
}
};
using GridReflowInput = nsGridContainerFrame::GridReflowInput;
/**
* The Grid implements grid item placement and the state of the grid -
* the size of the explicit/implicit grid, which cells are occupied etc.
*/
struct MOZ_STACK_CLASS nsGridContainerFrame::Grid
{
/**
* Place all child frames into the grid and expand the (implicit) grid as
* needed. The allocated GridAreas are stored in the GridAreaProperty
* frame property on the child frame.
* @param aComputedMinSize the container's min-size - used to determine
* the number of repeat(auto-fill/fit) tracks.
* @param aComputedSize the container's size - used to determine
* the number of repeat(auto-fill/fit) tracks.
* @param aComputedMaxSize the container's max-size - used to determine
* the number of repeat(auto-fill/fit) tracks.
*/
void PlaceGridItems(GridReflowInput& aState,
const LogicalSize& aComputedMinSize,
const LogicalSize& aComputedSize,
const LogicalSize& aComputedMaxSize);
/**
* As above but for an abs.pos. child. Any 'auto' lines will be represented
* by kAutoLine in the LineRange result.
* @param aGridStart the first line in the final, but untranslated grid
* @param aGridEnd the last line in the final, but untranslated grid
*/
LineRange ResolveAbsPosLineRange(const nsStyleGridLine& aStart,
const nsStyleGridLine& aEnd,
const LineNameMap& aNameMap,
uint32_t GridNamedArea::* aAreaStart,
uint32_t GridNamedArea::* aAreaEnd,
uint32_t aExplicitGridEnd,
int32_t aGridStart,
int32_t aGridEnd,
const nsStylePosition* aStyle);
/**
* Return a GridArea for abs.pos. item with non-auto lines placed at
* a definite line (1-based) with placement errors resolved. One or both
* positions may still be 'auto'.
* @param aChild the abs.pos. grid item to place
* @param aStyle the StylePosition() for the grid container
*/
GridArea PlaceAbsPos(nsIFrame* aChild,
const LineNameMap& aColLineNameMap,
const LineNameMap& aRowLineNameMap,
const nsStylePosition* aStyle);
/**
* Find the first column in row aLockedRow starting at aStartCol where aArea
* could be placed without overlapping other items. The returned column may
* cause aArea to overflow the current implicit grid bounds if placed there.
*/
uint32_t FindAutoCol(uint32_t aStartCol, uint32_t aLockedRow,
const GridArea* aArea) const;
/**
* Place aArea in the first column (in row aArea->mRows.mStart) starting at
* aStartCol without overlapping other items. The resulting aArea may
* overflow the current implicit grid bounds.
* Pre-condition: aArea->mRows.IsDefinite() is true.
* Post-condition: aArea->IsDefinite() is true.
*/
void PlaceAutoCol(uint32_t aStartCol, GridArea* aArea) const;
/**
* Find the first row in column aLockedCol starting at aStartRow where aArea
* could be placed without overlapping other items. The returned row may
* cause aArea to overflow the current implicit grid bounds if placed there.
*/
uint32_t FindAutoRow(uint32_t aLockedCol, uint32_t aStartRow,
const GridArea* aArea) const;
/**
* Place aArea in the first row (in column aArea->mCols.mStart) starting at
* aStartRow without overlapping other items. The resulting aArea may
* overflow the current implicit grid bounds.
* Pre-condition: aArea->mCols.IsDefinite() is true.
* Post-condition: aArea->IsDefinite() is true.
*/
void PlaceAutoRow(uint32_t aStartRow, GridArea* aArea) const;
/**
* Place aArea in the first column starting at aStartCol,aStartRow without
* causing it to overlap other items or overflow mGridColEnd.
* If there's no such column in aStartRow, continue in position 1,aStartRow+1.
* Pre-condition: aArea->mCols.IsAuto() && aArea->mRows.IsAuto() is true.
* Post-condition: aArea->IsDefinite() is true.
*/
void PlaceAutoAutoInRowOrder(uint32_t aStartCol,
uint32_t aStartRow,
GridArea* aArea) const;
/**
* Place aArea in the first row starting at aStartCol,aStartRow without
* causing it to overlap other items or overflow mGridRowEnd.
* If there's no such row in aStartCol, continue in position aStartCol+1,1.
* Pre-condition: aArea->mCols.IsAuto() && aArea->mRows.IsAuto() is true.
* Post-condition: aArea->IsDefinite() is true.
*/
void PlaceAutoAutoInColOrder(uint32_t aStartCol,
uint32_t aStartRow,
GridArea* aArea) const;
/**
* Return aLine if it's inside the aMin..aMax range (inclusive),
* otherwise return kAutoLine.
*/
static int32_t
AutoIfOutside(int32_t aLine, int32_t aMin, int32_t aMax)
{
MOZ_ASSERT(aMin <= aMax);
if (aLine < aMin || aLine > aMax) {
return kAutoLine;
}
return aLine;
}
/**
* Inflate the implicit grid to include aArea.
* @param aArea may be definite or auto
*/
void InflateGridFor(const GridArea& aArea)
{
mGridColEnd = std::max(mGridColEnd, aArea.mCols.HypotheticalEnd());
mGridRowEnd = std::max(mGridRowEnd, aArea.mRows.HypotheticalEnd());
MOZ_ASSERT(mGridColEnd <= kTranslatedMaxLine &&
mGridRowEnd <= kTranslatedMaxLine);
}
enum LineRangeSide {
eLineRangeSideStart, eLineRangeSideEnd
};
/**
* Return a line number for (non-auto) aLine, per:
* http://dev.w3.org/csswg/css-grid/#line-placement
* @param aLine style data for the line (must be non-auto)
* @param aNth a number of lines to find from aFromIndex, negative if the
* search should be in reverse order. In the case aLine has
* a specified line name, it's permitted to pass in zero which
* will be treated as one.
* @param aFromIndex the zero-based index to start counting from
* @param aLineNameList the explicit named lines
* @param aAreaStart a pointer to GridNamedArea::mColumnStart/mRowStart
* @param aAreaEnd a pointer to GridNamedArea::mColumnEnd/mRowEnd
* @param aExplicitGridEnd the last line in the explicit grid
* @param aEdge indicates whether we are resolving a start or end line
* @param aStyle the StylePosition() for the grid container
* @return a definite line (1-based), clamped to the kMinLine..kMaxLine range
*/
int32_t ResolveLine(const nsStyleGridLine& aLine,
int32_t aNth,
uint32_t aFromIndex,
const LineNameMap& aNameMap,
uint32_t GridNamedArea::* aAreaStart,
uint32_t GridNamedArea::* aAreaEnd,
uint32_t aExplicitGridEnd,
LineRangeSide aSide,
const nsStylePosition* aStyle);
/**
* Helper method for ResolveLineRange.
* @see ResolveLineRange
* @return a pair (start,end) of lines
*/
typedef std::pair<int32_t, int32_t> LinePair;
LinePair ResolveLineRangeHelper(const nsStyleGridLine& aStart,
const nsStyleGridLine& aEnd,
const LineNameMap& aNameMap,
uint32_t GridNamedArea::* aAreaStart,
uint32_t GridNamedArea::* aAreaEnd,
uint32_t aExplicitGridEnd,
const nsStylePosition* aStyle);
/**
* Return a LineRange based on the given style data. Non-auto lines
* are resolved to a definite line number (1-based) per:
* http://dev.w3.org/csswg/css-grid/#line-placement
* with placement errors corrected per:
* http://dev.w3.org/csswg/css-grid/#grid-placement-errors
* @param aStyle the StylePosition() for the grid container
* @param aStart style data for the start line
* @param aEnd style data for the end line
* @param aLineNameList the explicit named lines
* @param aAreaStart a pointer to GridNamedArea::mColumnStart/mRowStart
* @param aAreaEnd a pointer to GridNamedArea::mColumnEnd/mRowEnd
* @param aExplicitGridEnd the last line in the explicit grid
* @param aStyle the StylePosition() for the grid container
*/
LineRange ResolveLineRange(const nsStyleGridLine& aStart,
const nsStyleGridLine& aEnd,
const LineNameMap& aNameMap,
uint32_t GridNamedArea::* aAreaStart,
uint32_t GridNamedArea::* aAreaEnd,
uint32_t aExplicitGridEnd,
const nsStylePosition* aStyle);
/**
* Return a GridArea with non-auto lines placed at a definite line (1-based)
* with placement errors resolved. One or both positions may still
* be 'auto'.
* @param aChild the grid item
* @param aStyle the StylePosition() for the grid container
*/
GridArea PlaceDefinite(nsIFrame* aChild,
const LineNameMap& aColLineNameMap,
const LineNameMap& aRowLineNameMap,
const nsStylePosition* aStyle);
bool HasImplicitNamedArea(const nsString& aName) const
{
return mAreas && mAreas->Contains(aName);
}
/**
* A convenience method to lookup a name in 'grid-template-areas'.
* @param aStyle the StylePosition() for the grid container
* @return null if not found
*/
static const css::GridNamedArea*
FindNamedArea(const nsSubstring& aName, const nsStylePosition* aStyle)
{
if (!aStyle->mGridTemplateAreas) {
return nullptr;
}
const nsTArray<css::GridNamedArea>& areas =
aStyle->mGridTemplateAreas->mNamedAreas;
size_t len = areas.Length();
for (size_t i = 0; i < len; ++i) {
const css::GridNamedArea& area = areas[i];
if (area.mName == aName) {
return &area;
}
}
return nullptr;
}
// Return true if aString ends in aSuffix and has at least one character before
// the suffix. Assign aIndex to where the suffix starts.
static bool
IsNameWithSuffix(const nsString& aString, const nsString& aSuffix,
uint32_t* aIndex)
{
if (StringEndsWith(aString, aSuffix)) {
*aIndex = aString.Length() - aSuffix.Length();
return *aIndex != 0;
}
return false;
}
static bool
IsNameWithEndSuffix(const nsString& aString, uint32_t* aIndex)
{
return IsNameWithSuffix(aString, NS_LITERAL_STRING("-end"), aIndex);
}
static bool
IsNameWithStartSuffix(const nsString& aString, uint32_t* aIndex)
{
return IsNameWithSuffix(aString, NS_LITERAL_STRING("-start"), aIndex);
}
/**
* A CellMap holds state for each cell in the grid.
* It's row major. It's sparse in the sense that it only has enough rows to
* cover the last row that has a grid item. Each row only has enough entries
* to cover columns that are occupied *on that row*, i.e. it's not a full
* matrix covering the entire implicit grid. An absent Cell means that it's
* unoccupied by any grid item.
*/
struct CellMap {
struct Cell {
Cell() : mIsOccupied(false) {}
bool mIsOccupied : 1;
};
void Fill(const GridArea& aGridArea)
{
MOZ_ASSERT(aGridArea.IsDefinite());
MOZ_ASSERT(aGridArea.mRows.mStart < aGridArea.mRows.mEnd);
MOZ_ASSERT(aGridArea.mCols.mStart < aGridArea.mCols.mEnd);
const auto numRows = aGridArea.mRows.mEnd;
const auto numCols = aGridArea.mCols.mEnd;
mCells.EnsureLengthAtLeast(numRows);
for (auto i = aGridArea.mRows.mStart; i < numRows; ++i) {
nsTArray<Cell>& cellsInRow = mCells[i];
cellsInRow.EnsureLengthAtLeast(numCols);
for (auto j = aGridArea.mCols.mStart; j < numCols; ++j) {
cellsInRow[j].mIsOccupied = true;
}
}
}
uint32_t IsEmptyCol(uint32_t aCol) const
{
for (auto& row : mCells) {
if (aCol < row.Length() && row[aCol].mIsOccupied) {
return false;
}
}
return true;
}
uint32_t IsEmptyRow(uint32_t aRow) const
{
if (aRow >= mCells.Length()) {
return true;
}
for (const Cell& cell : mCells[aRow]) {
if (cell.mIsOccupied) {
return false;
}
}
return true;
}
#ifdef DEBUG
void Dump() const
{
const size_t numRows = mCells.Length();
for (size_t i = 0; i < numRows; ++i) {
const nsTArray<Cell>& cellsInRow = mCells[i];
const size_t numCols = cellsInRow.Length();
printf("%lu:\t", (unsigned long)i + 1);
for (size_t j = 0; j < numCols; ++j) {
printf(cellsInRow[j].mIsOccupied ? "X " : ". ");
}
printf("\n");
}
}
#endif
nsTArray<nsTArray<Cell>> mCells;
};
/**
* State for each cell in the grid.
*/
CellMap mCellMap;
/**
* @see HasImplicitNamedArea.
*/
ImplicitNamedAreas* mAreas;
/**
* The last column grid line (1-based) in the explicit grid.
* (i.e. the number of explicit columns + 1)
*/
uint32_t mExplicitGridColEnd;
/**
* The last row grid line (1-based) in the explicit grid.
* (i.e. the number of explicit rows + 1)
*/
uint32_t mExplicitGridRowEnd;
// Same for the implicit grid, except these become zero-based after
// resolving definite lines.
uint32_t mGridColEnd;
uint32_t mGridRowEnd;
/**
* Offsets from the start of the implicit grid to the start of the translated
* explicit grid. They are zero if there are no implicit lines before 1,1.
* e.g. "grid-column: span 3 / 1" makes mExplicitGridOffsetCol = 3 and the
* corresponding GridArea::mCols will be 0 / 3 in the zero-based translated
* grid.
*/
uint32_t mExplicitGridOffsetCol;
uint32_t mExplicitGridOffsetRow;
};
void
nsGridContainerFrame::GridReflowInput::CalculateTrackSizes(
const Grid& aGrid,
LogicalSize& aContentBox,
SizingConstraint aConstraint)
{
mCols.Initialize(mColFunctions, mGridStyle->mGridColumnGap,
aGrid.mGridColEnd, aContentBox.ISize(mWM));
mRows.Initialize(mRowFunctions, mGridStyle->mGridRowGap,
aGrid.mGridRowEnd, aContentBox.BSize(mWM));
mCols.CalculateSizes(*this, mGridItems, mColFunctions,
aContentBox.ISize(mWM), &GridArea::mCols,
aConstraint);
mCols.AlignJustifyContent(mGridStyle, mWM, aContentBox);
// Column positions and sizes are now final.
mCols.mCanResolveLineRangeSize = true;
mRows.CalculateSizes(*this, mGridItems, mRowFunctions,
aContentBox.BSize(mWM), &GridArea::mRows,
aConstraint);
if (aContentBox.BSize(mWM) == NS_AUTOHEIGHT) {
aContentBox.BSize(mWM) =
mRows.BackComputedIntrinsicSize(mRowFunctions, mGridStyle->mGridRowGap);
mRows.mGridGap =
::ResolveToDefiniteSize(mGridStyle->mGridRowGap, aContentBox.BSize(mWM));
}
}
/**
* (XXX share this utility function with nsFlexContainerFrame at some point)
*
* Helper for BuildDisplayList, to implement this special-case for grid
* items from the spec:
* The painting order of grid items is exactly the same as inline blocks,
* except that [...] 'z-index' values other than 'auto' create a stacking
* context even if 'position' is 'static'.
* http://dev.w3.org/csswg/css-grid/#z-order
*/
static uint32_t
GetDisplayFlagsForGridItem(nsIFrame* aFrame)
{
const nsStylePosition* pos = aFrame->StylePosition();
if (pos->mZIndex.GetUnit() == eStyleUnit_Integer) {
return nsIFrame::DISPLAY_CHILD_FORCE_STACKING_CONTEXT;
}
return nsIFrame::DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT;
}
// Align an item's margin box in its aAxis inside aCBSize.
static void
AlignJustifySelf(uint8_t aAlignment, LogicalAxis aAxis,
AlignJustifyFlags aFlags,
nscoord aBaselineAdjust, nscoord aCBSize,
const ReflowInput& aRI, const LogicalSize& aChildSize,
LogicalPoint* aPos)
{
MOZ_ASSERT(aAlignment != NS_STYLE_ALIGN_AUTO, "unexpected 'auto' "
"computed value for normal flow grid item");
// NOTE: this is the resulting frame offset (border box).
nscoord offset =
CSSAlignUtils::AlignJustifySelf(aAlignment, aAxis, aFlags,
aBaselineAdjust, aCBSize,
aRI, aChildSize);
// Set the position (aPos) for the requested alignment.
if (offset != 0) {
WritingMode wm = aRI.GetWritingMode();
nscoord& pos = aAxis == eLogicalAxisBlock ? aPos->B(wm) : aPos->I(wm);
pos += MOZ_LIKELY(aFlags & AlignJustifyFlags::eSameSide) ? offset : -offset;
}
}
static void
AlignSelf(const nsGridContainerFrame::GridItemInfo& aGridItem,
uint8_t aAlignSelf, nscoord aCBSize, const WritingMode aCBWM,
const ReflowInput& aRI, const LogicalSize& aSize,
LogicalPoint* aPos)
{
auto alignSelf = aAlignSelf;
AlignJustifyFlags flags = AlignJustifyFlags::eNoFlags;
if (alignSelf & NS_STYLE_ALIGN_SAFE) {
flags |= AlignJustifyFlags::eOverflowSafe;
}
alignSelf &= ~NS_STYLE_ALIGN_FLAG_BITS;
WritingMode childWM = aRI.GetWritingMode();
if (aCBWM.ParallelAxisStartsOnSameSide(eLogicalAxisBlock, childWM)) {
flags |= AlignJustifyFlags::eSameSide;
}
// Grid's 'align-self' axis is never parallel to the container's inline axis.
if (alignSelf == NS_STYLE_ALIGN_LEFT || alignSelf == NS_STYLE_ALIGN_RIGHT) {
alignSelf = NS_STYLE_ALIGN_START;
}
if (MOZ_LIKELY(alignSelf == NS_STYLE_ALIGN_NORMAL)) {
alignSelf = NS_STYLE_ALIGN_STRETCH;
}
nscoord baselineAdjust = 0;
if (alignSelf == NS_STYLE_ALIGN_BASELINE ||
alignSelf == NS_STYLE_ALIGN_LAST_BASELINE) {
alignSelf = aGridItem.GetSelfBaseline(alignSelf, eLogicalAxisBlock,
&baselineAdjust);
}
bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM);
LogicalAxis axis = isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock;
AlignJustifySelf(alignSelf, axis, flags, baselineAdjust,
aCBSize, aRI, aSize, aPos);
}
static void
JustifySelf(const nsGridContainerFrame::GridItemInfo& aGridItem,
uint8_t aJustifySelf, nscoord aCBSize, const WritingMode aCBWM,
const ReflowInput& aRI, const LogicalSize& aSize,
LogicalPoint* aPos)
{
auto justifySelf = aJustifySelf;
AlignJustifyFlags flags = AlignJustifyFlags::eNoFlags;
if (justifySelf & NS_STYLE_JUSTIFY_SAFE) {
flags |= AlignJustifyFlags::eOverflowSafe;
}
justifySelf &= ~NS_STYLE_JUSTIFY_FLAG_BITS;
WritingMode childWM = aRI.GetWritingMode();
if (aCBWM.ParallelAxisStartsOnSameSide(eLogicalAxisInline, childWM)) {
flags |= AlignJustifyFlags::eSameSide;
}
if (MOZ_LIKELY(justifySelf == NS_STYLE_ALIGN_NORMAL)) {
justifySelf = NS_STYLE_ALIGN_STRETCH;
}
nscoord baselineAdjust = 0;
// Grid's 'justify-self' axis is always parallel to the container's inline
// axis, so justify-self:left|right always applies.
switch (justifySelf) {
case NS_STYLE_JUSTIFY_LEFT:
justifySelf = aCBWM.IsBidiLTR() ? NS_STYLE_JUSTIFY_START
: NS_STYLE_JUSTIFY_END;
break;
case NS_STYLE_JUSTIFY_RIGHT:
justifySelf = aCBWM.IsBidiLTR() ? NS_STYLE_JUSTIFY_END
: NS_STYLE_JUSTIFY_START;
break;
case NS_STYLE_JUSTIFY_BASELINE:
case NS_STYLE_JUSTIFY_LAST_BASELINE:
justifySelf = aGridItem.GetSelfBaseline(justifySelf, eLogicalAxisInline,
&baselineAdjust);
break;
}
bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM);
LogicalAxis axis = isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline;
AlignJustifySelf(justifySelf, axis, flags, baselineAdjust,
aCBSize, aRI, aSize, aPos);
}
static uint16_t
GetAlignJustifyValue(uint16_t aAlignment, const WritingMode aWM,
const bool aIsAlign, bool* aOverflowSafe)
{
*aOverflowSafe = aAlignment & NS_STYLE_ALIGN_SAFE;
aAlignment &= (NS_STYLE_ALIGN_ALL_BITS & ~NS_STYLE_ALIGN_FLAG_BITS);
// Map some alignment values to 'start' / 'end'.
switch (aAlignment) {
case NS_STYLE_ALIGN_LEFT:
case NS_STYLE_ALIGN_RIGHT: {
if (aIsAlign) {
// Grid's 'align-content' axis is never parallel to the inline axis.
return NS_STYLE_ALIGN_START;
}
bool isStart = aWM.IsBidiLTR() == (aAlignment == NS_STYLE_ALIGN_LEFT);
return isStart ? NS_STYLE_ALIGN_START : NS_STYLE_ALIGN_END;
}
case NS_STYLE_ALIGN_FLEX_START: // same as 'start' for Grid
return NS_STYLE_ALIGN_START;
case NS_STYLE_ALIGN_FLEX_END: // same as 'end' for Grid
return NS_STYLE_ALIGN_END;
}
return aAlignment;
}
static uint16_t
GetAlignJustifyFallbackIfAny(uint16_t aAlignment, const WritingMode aWM,
const bool aIsAlign, bool* aOverflowSafe)
{
uint16_t fallback = aAlignment >> NS_STYLE_ALIGN_ALL_SHIFT;
if (fallback) {
return GetAlignJustifyValue(fallback, aWM, aIsAlign, aOverflowSafe);
}
// https://drafts.csswg.org/css-align-3/#fallback-alignment
switch (aAlignment) {
case NS_STYLE_ALIGN_STRETCH:
case NS_STYLE_ALIGN_SPACE_BETWEEN:
return NS_STYLE_ALIGN_START;
case NS_STYLE_ALIGN_SPACE_AROUND:
case NS_STYLE_ALIGN_SPACE_EVENLY:
return NS_STYLE_ALIGN_CENTER;
}
return 0;
}
//----------------------------------------------------------------------
// Frame class boilerplate
// =======================
NS_QUERYFRAME_HEAD(nsGridContainerFrame)
NS_QUERYFRAME_ENTRY(nsGridContainerFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
NS_IMPL_FRAMEARENA_HELPERS(nsGridContainerFrame)
nsContainerFrame*
NS_NewGridContainerFrame(nsIPresShell* aPresShell,
nsStyleContext* aContext)
{
return new (aPresShell) nsGridContainerFrame(aContext);
}
//----------------------------------------------------------------------
// nsGridContainerFrame Method Implementations
// ===========================================
/*static*/ const nsRect&
nsGridContainerFrame::GridItemCB(nsIFrame* aChild)
{
MOZ_ASSERT((aChild->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
aChild->IsAbsolutelyPositioned());
nsRect* cb = aChild->Properties().Get(GridItemContainingBlockRect());
MOZ_ASSERT(cb, "this method must only be called on grid items, and the grid "
"container should've reflowed this item by now and set up cb");
return *cb;
}
void
nsGridContainerFrame::AddImplicitNamedAreas(
const nsTArray<nsTArray<nsString>>& aLineNameLists)
{
// http://dev.w3.org/csswg/css-grid/#implicit-named-areas
// Note: recording these names for fast lookup later is just an optimization.
const uint32_t len =
std::min(aLineNameLists.Length(), size_t(nsStyleGridLine::kMaxLine));
nsTHashtable<nsStringHashKey> currentStarts;
ImplicitNamedAreas* areas = GetImplicitNamedAreas();
for (uint32_t i = 0; i < len; ++i) {
for (const nsString& name : aLineNameLists[i]) {
uint32_t indexOfSuffix;
if (Grid::IsNameWithStartSuffix(name, &indexOfSuffix) ||
Grid::IsNameWithEndSuffix(name, &indexOfSuffix)) {
// Extract the name that was found earlier.
nsDependentSubstring areaName(name, 0, indexOfSuffix);
// Lazily create the ImplicitNamedAreas.
if (!areas) {
areas = new ImplicitNamedAreas;
Properties().Set(ImplicitNamedAreasProperty(), areas);
}
mozilla::css::GridNamedArea area;
if (!areas->Get(areaName, &area)) {
// Not found, so prep the newly-seen area with a name and empty
// boundary information, which will get filled in later.
area.mName = areaName;
area.mRowStart = 0;
area.mRowEnd = 0;
area.mColumnStart = 0;
area.mColumnEnd = 0;
areas->Put(areaName, area);
}
}
}
}
}
void
nsGridContainerFrame::InitImplicitNamedAreas(const nsStylePosition* aStyle)
{
ImplicitNamedAreas* areas = GetImplicitNamedAreas();
if (areas) {
// Clear it, but reuse the hashtable itself for now. We'll remove it
// below if it isn't needed anymore.
areas->Clear();
}
AddImplicitNamedAreas(aStyle->mGridTemplateColumns.mLineNameLists);
AddImplicitNamedAreas(aStyle->mGridTemplateRows.mLineNameLists);
if (areas && areas->Count() == 0) {
Properties().Delete(ImplicitNamedAreasProperty());
}
}
int32_t
nsGridContainerFrame::Grid::ResolveLine(const nsStyleGridLine& aLine,
int32_t aNth,
uint32_t aFromIndex,
const LineNameMap& aNameMap,
uint32_t GridNamedArea::* aAreaStart,
uint32_t GridNamedArea::* aAreaEnd,
uint32_t aExplicitGridEnd,
LineRangeSide aSide,
const nsStylePosition* aStyle)
{
MOZ_ASSERT(!aLine.IsAuto());
int32_t line = 0;
if (aLine.mLineName.IsEmpty()) {
MOZ_ASSERT(aNth != 0, "css-grid 9.2: <integer> must not be zero.");
line = int32_t(aFromIndex) + aNth;
} else {
if (aNth == 0) {
// <integer> was omitted; treat it as 1.
aNth = 1;
}
bool isNameOnly = !aLine.mHasSpan && aLine.mInteger == 0;
if (isNameOnly) {
const GridNamedArea* area = FindNamedArea(aLine.mLineName, aStyle);
if (area || HasImplicitNamedArea(aLine.mLineName)) {
// The given name is a named area - look for explicit lines named
// <name>-start/-end depending on which side we're resolving.
// http://dev.w3.org/csswg/css-grid/#grid-placement-slot
uint32_t implicitLine = 0;
nsAutoString lineName(aLine.mLineName);
if (aSide == eLineRangeSideStart) {
lineName.AppendLiteral("-start");
implicitLine = area ? area->*aAreaStart : 0;
} else {
lineName.AppendLiteral("-end");
implicitLine = area ? area->*aAreaEnd : 0;
}
line = aNameMap.FindNamedLine(lineName, &aNth, aFromIndex,
implicitLine);
}
}
if (line == 0) {
// If mLineName ends in -start/-end, try the prefix as a named area.
uint32_t implicitLine = 0;
uint32_t index;
auto GridNamedArea::* areaEdge = aAreaStart;
bool found = IsNameWithStartSuffix(aLine.mLineName, &index);
if (!found) {
found = IsNameWithEndSuffix(aLine.mLineName, &index);
areaEdge = aAreaEnd;
}
if (found) {
const GridNamedArea* area =
FindNamedArea(nsDependentSubstring(aLine.mLineName, 0, index),
aStyle);
if (area) {
implicitLine = area->*areaEdge;
}
}
line = aNameMap.FindNamedLine(aLine.mLineName, &aNth, aFromIndex,
implicitLine);
}
if (line == 0) {
MOZ_ASSERT(aNth != 0, "we found all N named lines but 'line' is zero!");
int32_t edgeLine;
if (aLine.mHasSpan) {
// http://dev.w3.org/csswg/css-grid/#grid-placement-span-int
// 'span <custom-ident> N'
edgeLine = aSide == eLineRangeSideStart ? 1 : aExplicitGridEnd;
} else {
// http://dev.w3.org/csswg/css-grid/#grid-placement-int
// '<custom-ident> N'
edgeLine = aNth < 0 ? 1 : aExplicitGridEnd;
}
// "If not enough lines with that name exist, all lines in the implicit
// grid are assumed to have that name..."
line = edgeLine + aNth;
}
}
return clamped(line, nsStyleGridLine::kMinLine, nsStyleGridLine::kMaxLine);
}
nsGridContainerFrame::Grid::LinePair
nsGridContainerFrame::Grid::ResolveLineRangeHelper(
const nsStyleGridLine& aStart,
const nsStyleGridLine& aEnd,
const LineNameMap& aNameMap,
uint32_t GridNamedArea::* aAreaStart,
uint32_t GridNamedArea::* aAreaEnd,
uint32_t aExplicitGridEnd,
const nsStylePosition* aStyle)
{
MOZ_ASSERT(int32_t(nsGridContainerFrame::kAutoLine) > nsStyleGridLine::kMaxLine);
if (aStart.mHasSpan) {
if (aEnd.mHasSpan || aEnd.IsAuto()) {
// http://dev.w3.org/csswg/css-grid/#grid-placement-errors
if (aStart.mLineName.IsEmpty()) {
// span <integer> / span *
// span <integer> / auto
return LinePair(kAutoLine, aStart.mInteger);
}
// span <custom-ident> / span *
// span <custom-ident> / auto
return LinePair(kAutoLine, 1); // XXX subgrid explicit size instead of 1?
}
uint32_t from = aEnd.mInteger < 0 ? aExplicitGridEnd + 1: 0;
auto end = ResolveLine(aEnd, aEnd.mInteger, from, aNameMap, aAreaStart,
aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd,
aStyle);
int32_t span = aStart.mInteger == 0 ? 1 : aStart.mInteger;
if (end <= 1) {
// The end is at or before the first explicit line, thus all lines before
// it match <custom-ident> since they're implicit.
int32_t start = std::max(end - span, nsStyleGridLine::kMinLine);
return LinePair(start, end);
}
auto start = ResolveLine(aStart, -span, end, aNameMap, aAreaStart,
aAreaEnd, aExplicitGridEnd, eLineRangeSideStart,
aStyle);
return LinePair(start, end);
}
int32_t start = kAutoLine;
if (aStart.IsAuto()) {
if (aEnd.IsAuto()) {
// auto / auto
return LinePair(start, 1); // XXX subgrid explicit size instead of 1?
}
if (aEnd.mHasSpan) {
if (aEnd.mLineName.IsEmpty()) {
// auto / span <integer>
MOZ_ASSERT(aEnd.mInteger != 0);
return LinePair(start, aEnd.mInteger);
}
// http://dev.w3.org/csswg/css-grid/#grid-placement-errors
// auto / span <custom-ident>
return LinePair(start, 1); // XXX subgrid explicit size instead of 1?
}
} else {
uint32_t from = aStart.mInteger < 0 ? aExplicitGridEnd + 1: 0;
start = ResolveLine(aStart, aStart.mInteger, from, aNameMap,
aAreaStart, aAreaEnd, aExplicitGridEnd,
eLineRangeSideStart, aStyle);
if (aEnd.IsAuto()) {
// A "definite line / auto" should resolve the auto to 'span 1'.
// The error handling in ResolveLineRange will make that happen and also
// clamp the end line correctly if we return "start / start".
return LinePair(start, start);
}
}
uint32_t from;
int32_t nth = aEnd.mInteger == 0 ? 1 : aEnd.mInteger;
if (aEnd.mHasSpan) {
if (MOZ_UNLIKELY(start < 0)) {
if (aEnd.mLineName.IsEmpty()) {
return LinePair(start, start + nth);
}
from = 0;
} else {
if (start >= int32_t(aExplicitGridEnd)) {
// The start is at or after the last explicit line, thus all lines
// after it match <custom-ident> since they're implicit.
return LinePair(start, std::min(start + nth, nsStyleGridLine::kMaxLine));
}
from = start;
}
} else {
from = aEnd.mInteger < 0 ? aExplicitGridEnd + 1: 0;
}
auto end = ResolveLine(aEnd, nth, from, aNameMap, aAreaStart,
aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd, aStyle);
if (start == int32_t(kAutoLine)) {
// auto / definite line
start = std::max(nsStyleGridLine::kMinLine, end - 1);
}
return LinePair(start, end);
}
nsGridContainerFrame::LineRange
nsGridContainerFrame::Grid::ResolveLineRange(
const nsStyleGridLine& aStart,
const nsStyleGridLine& aEnd,
const LineNameMap& aNameMap,
uint32_t GridNamedArea::* aAreaStart,
uint32_t GridNamedArea::* aAreaEnd,
uint32_t aExplicitGridEnd,
const nsStylePosition* aStyle)
{
LinePair r = ResolveLineRangeHelper(aStart, aEnd, aNameMap, aAreaStart,
aAreaEnd, aExplicitGridEnd, aStyle);
MOZ_ASSERT(r.second != int32_t(kAutoLine));
if (r.first == int32_t(kAutoLine)) {
// r.second is a span, clamp it to kMaxLine - 1 so that the returned
// range has a HypotheticalEnd <= kMaxLine.
// http://dev.w3.org/csswg/css-grid/#overlarge-grids
r.second = std::min(r.second, nsStyleGridLine::kMaxLine - 1);
} else {
// http://dev.w3.org/csswg/css-grid/#grid-placement-errors
if (r.first > r.second) {
Swap(r.first, r.second);
} else if (r.first == r.second) {
if (MOZ_UNLIKELY(r.first == nsStyleGridLine::kMaxLine)) {
r.first = nsStyleGridLine::kMaxLine - 1;
}
r.second = r.first + 1; // XXX subgrid explicit size instead of 1?
}
}
return LineRange(r.first, r.second);
}
nsGridContainerFrame::GridArea
nsGridContainerFrame::Grid::PlaceDefinite(nsIFrame* aChild,
const LineNameMap& aColLineNameMap,
const LineNameMap& aRowLineNameMap,
const nsStylePosition* aStyle)
{
const nsStylePosition* itemStyle = aChild->StylePosition();
return GridArea(
ResolveLineRange(itemStyle->mGridColumnStart, itemStyle->mGridColumnEnd,
aColLineNameMap,
&GridNamedArea::mColumnStart, &GridNamedArea::mColumnEnd,
mExplicitGridColEnd, aStyle),
ResolveLineRange(itemStyle->mGridRowStart, itemStyle->mGridRowEnd,
aRowLineNameMap,
&GridNamedArea::mRowStart, &GridNamedArea::mRowEnd,
mExplicitGridRowEnd, aStyle));
}
nsGridContainerFrame::LineRange
nsGridContainerFrame::Grid::ResolveAbsPosLineRange(
const nsStyleGridLine& aStart,
const nsStyleGridLine& aEnd,
const LineNameMap& aNameMap,
uint32_t GridNamedArea::* aAreaStart,
uint32_t GridNamedArea::* aAreaEnd,
uint32_t aExplicitGridEnd,
int32_t aGridStart,
int32_t aGridEnd,
const nsStylePosition* aStyle)
{
if (aStart.IsAuto()) {
if (aEnd.IsAuto()) {
return LineRange(kAutoLine, kAutoLine);
}
uint32_t from = aEnd.mInteger < 0 ? aExplicitGridEnd + 1: 0;
int32_t end =
ResolveLine(aEnd, aEnd.mInteger, from, aNameMap, aAreaStart,
aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd, aStyle);
if (aEnd.mHasSpan) {
++end;
}
// A line outside the existing grid is treated as 'auto' for abs.pos (10.1).
end = AutoIfOutside(end, aGridStart, aGridEnd);
return LineRange(kAutoLine, end);
}
if (aEnd.IsAuto()) {
uint32_t from = aStart.mInteger < 0 ? aExplicitGridEnd + 1: 0;
int32_t start =
ResolveLine(aStart, aStart.mInteger, from, aNameMap, aAreaStart,
aAreaEnd, aExplicitGridEnd, eLineRangeSideStart, aStyle);
if (aStart.mHasSpan) {
start = std::max(aGridEnd - start, aGridStart);
}
start = AutoIfOutside(start, aGridStart, aGridEnd);
return LineRange(start, kAutoLine);
}
LineRange r = ResolveLineRange(aStart, aEnd, aNameMap, aAreaStart,
aAreaEnd, aExplicitGridEnd, aStyle);
if (r.IsAuto()) {
MOZ_ASSERT(aStart.mHasSpan && aEnd.mHasSpan, "span / span is the only case "
"leading to IsAuto here -- we dealt with the other cases above");
// The second span was ignored per 9.2.1. For abs.pos., 10.1 says that this
// case should result in "auto / auto" unlike normal flow grid items.
return LineRange(kAutoLine, kAutoLine);
}
return LineRange(AutoIfOutside(r.mUntranslatedStart, aGridStart, aGridEnd),
AutoIfOutside(r.mUntranslatedEnd, aGridStart, aGridEnd));
}
nsGridContainerFrame::GridArea
nsGridContainerFrame::Grid::PlaceAbsPos(nsIFrame* aChild,
const LineNameMap& aColLineNameMap,
const LineNameMap& aRowLineNameMap,
const nsStylePosition* aStyle)
{
const nsStylePosition* itemStyle = aChild->StylePosition();
int32_t gridColStart = 1 - mExplicitGridOffsetCol;
int32_t gridRowStart = 1 - mExplicitGridOffsetRow;
return GridArea(
ResolveAbsPosLineRange(itemStyle->mGridColumnStart,
itemStyle->mGridColumnEnd,
aColLineNameMap,
&GridNamedArea::mColumnStart,
&GridNamedArea::mColumnEnd,
mExplicitGridColEnd, gridColStart, mGridColEnd,
aStyle),
ResolveAbsPosLineRange(itemStyle->mGridRowStart,
itemStyle->mGridRowEnd,
aRowLineNameMap,
&GridNamedArea::mRowStart,
&GridNamedArea::mRowEnd,
mExplicitGridRowEnd, gridRowStart, mGridRowEnd,
aStyle));
}
uint32_t
nsGridContainerFrame::Grid::FindAutoCol(uint32_t aStartCol, uint32_t aLockedRow,
const GridArea* aArea) const
{
const uint32_t extent = aArea->mCols.Extent();
const uint32_t iStart = aLockedRow;
const uint32_t iEnd = iStart + aArea->mRows.Extent();
uint32_t candidate = aStartCol;
for (uint32_t i = iStart; i < iEnd; ) {
if (i >= mCellMap.mCells.Length()) {
break;
}
const nsTArray<CellMap::Cell>& cellsInRow = mCellMap.mCells[i];
const uint32_t len = cellsInRow.Length();
const uint32_t lastCandidate = candidate;
// Find the first gap in the current row that's at least 'extent' wide.
// ('gap' tracks how wide the current column gap is.)
for (uint32_t j = candidate, gap = 0; j < len && gap < extent; ++j) {
if (!cellsInRow[j].mIsOccupied) {
++gap;
continue;
}
candidate = j + 1;
gap = 0;
}
if (lastCandidate < candidate && i != iStart) {
// Couldn't fit 'extent' tracks at 'lastCandidate' here so we must
// restart from the beginning with the new 'candidate'.
i = iStart;
} else {
++i;
}
}
return candidate;
}
void
nsGridContainerFrame::Grid::PlaceAutoCol(uint32_t aStartCol,
GridArea* aArea) const
{
MOZ_ASSERT(aArea->mRows.IsDefinite() && aArea->mCols.IsAuto());
uint32_t col = FindAutoCol(aStartCol, aArea->mRows.mStart, aArea);
aArea->mCols.ResolveAutoPosition(col, mExplicitGridOffsetCol);
MOZ_ASSERT(aArea->IsDefinite());
}
uint32_t
nsGridContainerFrame::Grid::FindAutoRow(uint32_t aLockedCol, uint32_t aStartRow,
const GridArea* aArea) const
{
const uint32_t extent = aArea->mRows.Extent();
const uint32_t jStart = aLockedCol;
const uint32_t jEnd = jStart + aArea->mCols.Extent();
const uint32_t iEnd = mCellMap.mCells.Length();
uint32_t candidate = aStartRow;
// Find the first gap in the rows that's at least 'extent' tall.
// ('gap' tracks how tall the current row gap is.)
for (uint32_t i = candidate, gap = 0; i < iEnd && gap < extent; ++i) {
++gap; // tentative, but we may reset it below if a column is occupied
const nsTArray<CellMap::Cell>& cellsInRow = mCellMap.mCells[i];
const uint32_t clampedJEnd = std::min<uint32_t>(jEnd, cellsInRow.Length());
// Check if the current row is unoccupied from jStart to jEnd.
for (uint32_t j = jStart; j < clampedJEnd; ++j) {
if (cellsInRow[j].mIsOccupied) {
// Couldn't fit 'extent' rows at 'candidate' here; we hit something
// at row 'i'. So, try the row after 'i' as our next candidate.
candidate = i + 1;
gap = 0;
break;
}
}
}
return candidate;
}
void
nsGridContainerFrame::Grid::PlaceAutoRow(uint32_t aStartRow,
GridArea* aArea) const
{
MOZ_ASSERT(aArea->mCols.IsDefinite() && aArea->mRows.IsAuto());
uint32_t row = FindAutoRow(aArea->mCols.mStart, aStartRow, aArea);
aArea->mRows.ResolveAutoPosition(row, mExplicitGridOffsetRow);
MOZ_ASSERT(aArea->IsDefinite());
}
void
nsGridContainerFrame::Grid::PlaceAutoAutoInRowOrder(uint32_t aStartCol,
uint32_t aStartRow,
GridArea* aArea) const
{
MOZ_ASSERT(aArea->mCols.IsAuto() && aArea->mRows.IsAuto());
const uint32_t colExtent = aArea->mCols.Extent();
const uint32_t gridRowEnd = mGridRowEnd;
const uint32_t gridColEnd = mGridColEnd;
uint32_t col = aStartCol;
uint32_t row = aStartRow;
for (; row < gridRowEnd; ++row) {
col = FindAutoCol(col, row, aArea);
if (col + colExtent <= gridColEnd) {
break;
}
col = 0;
}
MOZ_ASSERT(row < gridRowEnd || col == 0,
"expected column 0 for placing in a new row");
aArea->mCols.ResolveAutoPosition(col, mExplicitGridOffsetCol);
aArea->mRows.ResolveAutoPosition(row, mExplicitGridOffsetRow);
MOZ_ASSERT(aArea->IsDefinite());
}
void
nsGridContainerFrame::Grid::PlaceAutoAutoInColOrder(uint32_t aStartCol,
uint32_t aStartRow,
GridArea* aArea) const
{
MOZ_ASSERT(aArea->mCols.IsAuto() && aArea->mRows.IsAuto());
const uint32_t rowExtent = aArea->mRows.Extent();
const uint32_t gridRowEnd = mGridRowEnd;
const uint32_t gridColEnd = mGridColEnd;
uint32_t col = aStartCol;
uint32_t row = aStartRow;
for (; col < gridColEnd; ++col) {
row = FindAutoRow(col, row, aArea);
if (row + rowExtent <= gridRowEnd) {
break;
}
row = 0;
}
MOZ_ASSERT(col < gridColEnd || row == 0,
"expected row 0 for placing in a new column");
aArea->mCols.ResolveAutoPosition(col, mExplicitGridOffsetCol);
aArea->mRows.ResolveAutoPosition(row, mExplicitGridOffsetRow);
MOZ_ASSERT(aArea->IsDefinite());
}
void
nsGridContainerFrame::Grid::PlaceGridItems(GridReflowInput& aState,
const LogicalSize& aComputedMinSize,
const LogicalSize& aComputedSize,
const LogicalSize& aComputedMaxSize)
{
mAreas = aState.mFrame->GetImplicitNamedAreas();
const nsStylePosition* const gridStyle = aState.mGridStyle;
MOZ_ASSERT(mCellMap.mCells.IsEmpty(), "unexpected entries in cell map");
// http://dev.w3.org/csswg/css-grid/#grid-definition
// Initialize the end lines of the Explicit Grid (mExplicitGridCol[Row]End).
// This is determined by the larger of the number of rows/columns defined
// by 'grid-template-areas' and the 'grid-template-rows'/'-columns', plus one.
// Also initialize the Implicit Grid (mGridCol[Row]End) to the same values.
// Note that this is for a grid with a 1,1 origin. We'll change that
// to a 0,0 based grid after placing definite lines.
auto areas = gridStyle->mGridTemplateAreas.get();
uint32_t numRepeatCols = aState.mColFunctions.InitRepeatTracks(
gridStyle->mGridColumnGap,
aComputedMinSize.ISize(aState.mWM),
aComputedSize.ISize(aState.mWM),
aComputedMaxSize.ISize(aState.mWM));
mGridColEnd = mExplicitGridColEnd =
aState.mColFunctions.ComputeExplicitGridEnd(areas ? areas->mNColumns + 1 : 1);
LineNameMap colLineNameMap(gridStyle->mGridTemplateColumns, numRepeatCols);
uint32_t numRepeatRows = aState.mRowFunctions.InitRepeatTracks(
gridStyle->mGridRowGap,
aComputedMinSize.BSize(aState.mWM),
aComputedSize.BSize(aState.mWM),
aComputedMaxSize.BSize(aState.mWM));
mGridRowEnd = mExplicitGridRowEnd =
aState.mRowFunctions.ComputeExplicitGridEnd(areas ? areas->NRows() + 1 : 1);
LineNameMap rowLineNameMap(gridStyle->mGridTemplateRows, numRepeatRows);
// http://dev.w3.org/csswg/css-grid/#line-placement
// Resolve definite positions per spec chap 9.2.
int32_t minCol = 1;
int32_t minRow = 1;
aState.mGridItems.ClearAndRetainStorage();
aState.mIter.Reset();
for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
nsIFrame* child = *aState.mIter;
GridItemInfo* info =
aState.mGridItems.AppendElement(GridItemInfo(child,
PlaceDefinite(child,
colLineNameMap,
rowLineNameMap,
gridStyle)));
MOZ_ASSERT(aState.mIter.GridItemIndex() == aState.mGridItems.Length() - 1,
"GridItemIndex() is broken");
GridArea& area = info->mArea;
if (area.mCols.IsDefinite()) {
minCol = std::min(minCol, area.mCols.mUntranslatedStart);
}
if (area.mRows.IsDefinite()) {
minRow = std::min(minRow, area.mRows.mUntranslatedStart);
}
}
// Translate the whole grid so that the top-/left-most area is at 0,0.
mExplicitGridOffsetCol = 1 - minCol; // minCol/Row is always <= 1, see above
mExplicitGridOffsetRow = 1 - minRow;
aState.mColFunctions.mExplicitGridOffset = mExplicitGridOffsetCol;
aState.mRowFunctions.mExplicitGridOffset = mExplicitGridOffsetRow;
const int32_t offsetToColZero = int32_t(mExplicitGridOffsetCol) - 1;
const int32_t offsetToRowZero = int32_t(mExplicitGridOffsetRow) - 1;
mGridColEnd += offsetToColZero;
mGridRowEnd += offsetToRowZero;
aState.mIter.Reset();
for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
GridArea& area = aState.mGridItems[aState.mIter.GridItemIndex()].mArea;
if (area.mCols.IsDefinite()) {
area.mCols.mStart = area.mCols.mUntranslatedStart + offsetToColZero;
area.mCols.mEnd = area.mCols.mUntranslatedEnd + offsetToColZero;
}
if (area.mRows.IsDefinite()) {
area.mRows.mStart = area.mRows.mUntranslatedStart + offsetToRowZero;
area.mRows.mEnd = area.mRows.mUntranslatedEnd + offsetToRowZero;
}
if (area.IsDefinite()) {
mCellMap.Fill(area);
InflateGridFor(area);
}
}
// http://dev.w3.org/csswg/css-grid/#auto-placement-algo
// Step 1, place 'auto' items that have one definite position -
// definite row (column) for grid-auto-flow:row (column).
auto flowStyle = gridStyle->mGridAutoFlow;
const bool isRowOrder = (flowStyle & NS_STYLE_GRID_AUTO_FLOW_ROW);
const bool isSparse = !(flowStyle & NS_STYLE_GRID_AUTO_FLOW_DENSE);
// We need 1 cursor per row (or column) if placement is sparse.
{
Maybe<nsDataHashtable<nsUint32HashKey, uint32_t>> cursors;
if (isSparse) {
cursors.emplace();
}
auto placeAutoMinorFunc = isRowOrder ? &Grid::PlaceAutoCol
: &Grid::PlaceAutoRow;
aState.mIter.Reset();
for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
GridArea& area = aState.mGridItems[aState.mIter.GridItemIndex()].mArea;
LineRange& major = isRowOrder ? area.mRows : area.mCols;
LineRange& minor = isRowOrder ? area.mCols : area.mRows;
if (major.IsDefinite() && minor.IsAuto()) {
// Items with 'auto' in the minor dimension only.
uint32_t cursor = 0;
if (isSparse) {
cursors->Get(major.mStart, &cursor);
}
(this->*placeAutoMinorFunc)(cursor, &area);
mCellMap.Fill(area);
if (isSparse) {
cursors->Put(major.mStart, minor.mEnd);
}
}
InflateGridFor(area); // Step 2, inflating for auto items too
}
}
// XXX NOTE possible spec issue.
// XXX It's unclear if the remaining major-dimension auto and
// XXX auto in both dimensions should use the same cursor or not,
// XXX https://www.w3.org/Bugs/Public/show_bug.cgi?id=16044
// XXX seems to indicate it shouldn't.
// XXX http://dev.w3.org/csswg/css-grid/#auto-placement-cursor
// XXX now says it should (but didn't in earlier versions)
// Step 3, place the remaining grid items
uint32_t cursorMajor = 0; // for 'dense' these two cursors will stay at 0,0
uint32_t cursorMinor = 0;
auto placeAutoMajorFunc = isRowOrder ? &Grid::PlaceAutoRow
: &Grid::PlaceAutoCol;
aState.mIter.Reset();
for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
GridArea& area = aState.mGridItems[aState.mIter.GridItemIndex()].mArea;
MOZ_ASSERT(*aState.mIter == aState.mGridItems[aState.mIter.GridItemIndex()].mFrame,
"iterator out of sync with aState.mGridItems");
LineRange& major = isRowOrder ? area.mRows : area.mCols;
LineRange& minor = isRowOrder ? area.mCols : area.mRows;
if (major.IsAuto()) {
if (minor.IsDefinite()) {
// Items with 'auto' in the major dimension only.
if (isSparse) {
if (minor.mStart < cursorMinor) {
++cursorMajor;
}
cursorMinor = minor.mStart;
}
(this->*placeAutoMajorFunc)(cursorMajor, &area);
if (isSparse) {
cursorMajor = major.mStart;
}
} else {
// Items with 'auto' in both dimensions.
if (isRowOrder) {
PlaceAutoAutoInRowOrder(cursorMinor, cursorMajor, &area);
} else {
PlaceAutoAutoInColOrder(cursorMajor, cursorMinor, &area);
}
if (isSparse) {
cursorMajor = major.mStart;
cursorMinor = minor.mEnd;
#ifdef DEBUG
uint32_t gridMajorEnd = isRowOrder ? mGridRowEnd : mGridColEnd;
uint32_t gridMinorEnd = isRowOrder ? mGridColEnd : mGridRowEnd;
MOZ_ASSERT(cursorMajor <= gridMajorEnd,
"we shouldn't need to place items further than 1 track "
"past the current end of the grid, in major dimension");
MOZ_ASSERT(cursorMinor <= gridMinorEnd,
"we shouldn't add implicit minor tracks for auto/auto");
#endif
}
}
mCellMap.Fill(area);
InflateGridFor(area);
}
}
if (aState.mFrame->IsAbsoluteContainer()) {
// 9.4 Absolutely-positioned Grid Items
// http://dev.w3.org/csswg/css-grid/#abspos-items
// We only resolve definite lines here; we'll align auto positions to the
// grid container later during reflow.
nsFrameList children(aState.mFrame->GetChildList(
aState.mFrame->GetAbsoluteListID()));
const int32_t offsetToColZero = int32_t(mExplicitGridOffsetCol) - 1;
const int32_t offsetToRowZero = int32_t(mExplicitGridOffsetRow) - 1;
// Untranslate the grid again temporarily while resolving abs.pos. lines.
AutoRestore<uint32_t> save1(mGridColEnd);
AutoRestore<uint32_t> save2(mGridRowEnd);
mGridColEnd -= offsetToColZero;
mGridRowEnd -= offsetToRowZero;
aState.mAbsPosItems.ClearAndRetainStorage();
size_t i = 0;
for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next(), ++i) {
nsIFrame* child = e.get();
GridItemInfo* info =
aState.mAbsPosItems.AppendElement(GridItemInfo(child,
PlaceAbsPos(child,
colLineNameMap,
rowLineNameMap,
gridStyle)));
GridArea& area = info->mArea;
if (area.mCols.mUntranslatedStart != int32_t(kAutoLine)) {
area.mCols.mStart = area.mCols.mUntranslatedStart + offsetToColZero;
}
if (area.mCols.mUntranslatedEnd != int32_t(kAutoLine)) {
area.mCols.mEnd = area.mCols.mUntranslatedEnd + offsetToColZero;
}
if (area.mRows.mUntranslatedStart != int32_t(kAutoLine)) {
area.mRows.mStart = area.mRows.mUntranslatedStart + offsetToRowZero;
}
if (area.mRows.mUntranslatedEnd != int32_t(kAutoLine)) {
area.mRows.mEnd = area.mRows.mUntranslatedEnd + offsetToRowZero;
}
}
}
// Count empty 'auto-fit' tracks in the repeat() range.
// |colAdjust| will have a count for each line in the grid of how many
// tracks were empty between the start of the grid and that line.
Maybe<nsTArray<uint32_t>> colAdjust;
uint32_t numEmptyCols = 0;
if (aState.mColFunctions.mHasRepeatAuto &&
!gridStyle->mGridTemplateColumns.mIsAutoFill &&
aState.mColFunctions.NumRepeatTracks() > 0) {
for (uint32_t col = aState.mColFunctions.mRepeatAutoStart,
endRepeat = aState.mColFunctions.mRepeatAutoEnd,
numColLines = mGridColEnd + 1;
col < numColLines; ++col) {
if (numEmptyCols) {
(*colAdjust)[col] = numEmptyCols;
}
if (col < endRepeat && mCellMap.IsEmptyCol(col)) {
++numEmptyCols;
if (colAdjust.isNothing()) {
colAdjust.emplace(numColLines);
colAdjust->SetLength(numColLines);
PodZero(colAdjust->Elements(), colAdjust->Length());
}
uint32_t repeatIndex = col - aState.mColFunctions.mRepeatAutoStart;
MOZ_ASSERT(aState.mColFunctions.mRemovedRepeatTracks.Length() >
repeatIndex);
aState.mColFunctions.mRemovedRepeatTracks[repeatIndex] = true;
}
}
}
Maybe<nsTArray<uint32_t>> rowAdjust;
uint32_t numEmptyRows = 0;
if (aState.mRowFunctions.mHasRepeatAuto &&
!gridStyle->mGridTemplateRows.mIsAutoFill &&
aState.mRowFunctions.NumRepeatTracks() > 0) {
for (uint32_t row = aState.mRowFunctions.mRepeatAutoStart,
endRepeat = aState.mRowFunctions.mRepeatAutoEnd,
numRowLines = mGridRowEnd + 1;
row < numRowLines; ++row) {
if (numEmptyRows) {
(*rowAdjust)[row] = numEmptyRows;
}
if (row < endRepeat && mCellMap.IsEmptyRow(row)) {
++numEmptyRows;
if (rowAdjust.isNothing()) {
rowAdjust.emplace(numRowLines);
rowAdjust->SetLength(numRowLines);
PodZero(rowAdjust->Elements(), rowAdjust->Length());
}
uint32_t repeatIndex = row - aState.mRowFunctions.mRepeatAutoStart;
MOZ_ASSERT(aState.mRowFunctions.mRemovedRepeatTracks.Length() >
repeatIndex);
aState.mRowFunctions.mRemovedRepeatTracks[repeatIndex] = true;
}
}
}
// Remove the empty 'auto-fit' tracks we found above, if any.
if (numEmptyCols || numEmptyRows) {
// Adjust the line numbers in the grid areas.
for (auto& item : aState.mGridItems) {
GridArea& area = item.mArea;
if (numEmptyCols) {
area.mCols.AdjustForRemovedTracks(*colAdjust);
}
if (numEmptyRows) {
area.mRows.AdjustForRemovedTracks(*rowAdjust);
}
}
for (auto& item : aState.mAbsPosItems) {
GridArea& area = item.mArea;
if (numEmptyCols) {
area.mCols.AdjustAbsPosForRemovedTracks(*colAdjust);
}
if (numEmptyRows) {
area.mRows.AdjustAbsPosForRemovedTracks(*rowAdjust);
}
}
// Adjust the grid size.
mGridColEnd -= numEmptyCols;
mExplicitGridColEnd -= numEmptyCols;
mGridRowEnd -= numEmptyRows;
mExplicitGridRowEnd -= numEmptyRows;
// Adjust the track mapping to unmap the removed tracks.
auto colRepeatCount = aState.mColFunctions.NumRepeatTracks();
aState.mColFunctions.SetNumRepeatTracks(colRepeatCount - numEmptyCols);
auto rowRepeatCount = aState.mRowFunctions.NumRepeatTracks();
aState.mRowFunctions.SetNumRepeatTracks(rowRepeatCount - numEmptyRows);
}
// Update the line boundaries of the implicit grid areas, if needed.
if (mAreas &&
aState.mFrame->HasAnyStateBits(NS_STATE_GRID_GENERATE_COMPUTED_VALUES)) {
for (auto iter = mAreas->Iter(); !iter.Done(); iter.Next()) {
auto& areaInfo = iter.Data();
// Resolve the lines for the area. We use the name of the area as the
// name of the lines, knowing that the line placement algorithm will
// add the -start and -end suffixes as appropriate for layout.
nsStyleGridLine lineStartAndEnd;
lineStartAndEnd.mLineName = areaInfo.mName;
LineRange columnLines = ResolveLineRange(
lineStartAndEnd, lineStartAndEnd,
colLineNameMap,
&GridNamedArea::mColumnStart, &GridNamedArea::mColumnEnd,
mExplicitGridColEnd, gridStyle);
LineRange rowLines = ResolveLineRange(
lineStartAndEnd, lineStartAndEnd,
rowLineNameMap,
&GridNamedArea::mRowStart, &GridNamedArea::mRowEnd,
mExplicitGridRowEnd, gridStyle);
// Put the resolved line indices back into the area structure.
areaInfo.mColumnStart = columnLines.mStart + mExplicitGridOffsetCol;
areaInfo.mColumnEnd = columnLines.mEnd + mExplicitGridOffsetCol;
areaInfo.mRowStart = rowLines.mStart + mExplicitGridOffsetRow;
areaInfo.mRowEnd = rowLines.mEnd + mExplicitGridOffsetRow;
}
}
}
void
nsGridContainerFrame::Tracks::Initialize(
const TrackSizingFunctions& aFunctions,
const nsStyleCoord& aGridGap,
uint32_t aNumTracks,
nscoord aContentBoxSize)
{
MOZ_ASSERT(aNumTracks >= aFunctions.mExplicitGridOffset +
aFunctions.NumExplicitTracks());
mSizes.SetLength(aNumTracks);
PodZero(mSizes.Elements(), mSizes.Length());
for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) {
mStateUnion |= mSizes[i].Initialize(aContentBoxSize,
aFunctions.MinSizingFor(i),
aFunctions.MaxSizingFor(i));
}
mGridGap = ::ResolveToDefiniteSize(aGridGap, aContentBoxSize);
mContentBoxSize = aContentBoxSize;
}
/**
* Reflow aChild in the given aAvailableSize.
*/
static nscoord
MeasuringReflow(nsIFrame* aChild,
const ReflowInput* aReflowInput,
nsRenderingContext* aRC,
const LogicalSize& aAvailableSize,
const LogicalSize& aCBSize,
nscoord aIMinSizeClamp = NS_MAXSIZE,
nscoord aBMinSizeClamp = NS_MAXSIZE)
{
nsContainerFrame* parent = aChild->GetParent();
nsPresContext* pc = aChild->PresContext();
Maybe<ReflowInput> dummyParentState;
const ReflowInput* rs = aReflowInput;
if (!aReflowInput) {
MOZ_ASSERT(!parent->HasAnyStateBits(NS_FRAME_IN_REFLOW));
dummyParentState.emplace(pc, parent, aRC,
LogicalSize(parent->GetWritingMode(), 0,
NS_UNCONSTRAINEDSIZE),
ReflowInput::DUMMY_PARENT_REFLOW_STATE);
rs = dummyParentState.ptr();
}
#ifdef DEBUG
// This will suppress various CRAZY_SIZE warnings for this reflow.
parent->Properties().Set(
nsContainerFrame::DebugReflowingWithInfiniteISize(), true);
#endif
uint32_t riFlags = ReflowInput::COMPUTE_SIZE_SHRINK_WRAP |
ReflowInput::COMPUTE_SIZE_USE_AUTO_BSIZE;
if (aIMinSizeClamp != NS_MAXSIZE) {
riFlags |= ReflowInput::I_CLAMP_MARGIN_BOX_MIN_SIZE;
}
if (aBMinSizeClamp != NS_MAXSIZE) {
riFlags |= ReflowInput::B_CLAMP_MARGIN_BOX_MIN_SIZE;
aChild->Properties().Set(nsIFrame::BClampMarginBoxMinSizeProperty(),
aBMinSizeClamp);
} else {
aChild->Properties().Delete(nsIFrame::BClampMarginBoxMinSizeProperty());
}
ReflowInput childRI(pc, *rs, aChild, aAvailableSize, &aCBSize, riFlags);
ReflowOutput childSize(childRI);
nsReflowStatus childStatus;
const uint32_t flags = NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_SIZE_VIEW;
WritingMode wm = childRI.GetWritingMode();
parent->ReflowChild(aChild, pc, childSize, childRI, wm,
LogicalPoint(wm), nsSize(), flags, childStatus);
parent->FinishReflowChild(aChild, pc, childSize, &childRI, wm,
LogicalPoint(wm), nsSize(), flags);
#ifdef DEBUG
parent->Properties().Delete(nsContainerFrame::DebugReflowingWithInfiniteISize());
#endif
return childSize.BSize(wm);
}
/**
* Return the [min|max]-content contribution of aChild to its parent (i.e.
* the child's margin-box) in aAxis.
*/
static nscoord
ContentContribution(const GridItemInfo& aGridItem,
const GridReflowInput& aState,
nsRenderingContext* aRC,
WritingMode aCBWM,
LogicalAxis aAxis,
IntrinsicISizeType aConstraint,
nscoord aMinSizeClamp = NS_MAXSIZE,
uint32_t aFlags = 0)
{
nsIFrame* child = aGridItem.mFrame;
PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis));
nscoord size = nsLayoutUtils::IntrinsicForAxis(axis, aRC, child, aConstraint,
aFlags | nsLayoutUtils::BAIL_IF_REFLOW_NEEDED |
nsLayoutUtils::ADD_PERCENTS,
aMinSizeClamp);
if (size == NS_INTRINSIC_WIDTH_UNKNOWN) {
// We need to reflow the child to find its BSize contribution.
// XXX this will give mostly correct results for now (until bug 1174569).
nscoord availISize = INFINITE_ISIZE_COORD;
nscoord availBSize = NS_UNCONSTRAINEDSIZE;
auto childWM = child->GetWritingMode();
const bool isOrthogonal = childWM.IsOrthogonalTo(aCBWM);
// The next two variables are MinSizeClamp values in the child's axes.
nscoord iMinSizeClamp = NS_MAXSIZE;
nscoord bMinSizeClamp = NS_MAXSIZE;
LogicalSize cbSize(childWM, 0, 0);
if (aState.mCols.mCanResolveLineRangeSize) {
nscoord sz = aState.mCols.ResolveSize(aGridItem.mArea.mCols);
if (isOrthogonal) {
availBSize = sz;
cbSize.BSize(childWM) = sz;
if (aGridItem.mState[aAxis] & ItemState::eClampMarginBoxMinSize) {
bMinSizeClamp = sz;
}
} else {
availISize = sz;
cbSize.ISize(childWM) = sz;
if (aGridItem.mState[aAxis] & ItemState::eClampMarginBoxMinSize) {
iMinSizeClamp = sz;
}
}
}
if (isOrthogonal == (aAxis == eLogicalAxisInline)) {
bMinSizeClamp = aMinSizeClamp;
} else {
iMinSizeClamp = aMinSizeClamp;
}
LogicalSize availableSize(childWM, availISize, availBSize);
size = ::MeasuringReflow(child, aState.mReflowInput, aRC, availableSize,
cbSize, iMinSizeClamp, bMinSizeClamp);
nsIFrame::IntrinsicISizeOffsetData offsets = child->IntrinsicBSizeOffsets();
size += offsets.hMargin;
auto percent = offsets.hPctMargin;
if (availBSize == NS_UNCONSTRAINEDSIZE) {
// We always want to add in percent padding too, unless we already did so
// using a resolved column size above.
percent += offsets.hPctPadding;
}
size = nsLayoutUtils::AddPercents(size, percent);
nscoord overflow = size - aMinSizeClamp;
if (MOZ_UNLIKELY(overflow > 0)) {
nscoord contentSize = child->ContentBSize(childWM);
nscoord newContentSize = std::max(nscoord(0), contentSize - overflow);
// XXXmats deal with percentages better, see bug 1300369 comment 27.
size -= contentSize - newContentSize;
}
}
MOZ_ASSERT(aGridItem.mBaselineOffset[aAxis] >= 0,
"baseline offset should be non-negative at this point");
MOZ_ASSERT((aGridItem.mState[aAxis] & ItemState::eIsBaselineAligned) ||
aGridItem.mBaselineOffset[aAxis] == nscoord(0),
"baseline offset should be zero when not baseline-aligned");
size += aGridItem.mBaselineOffset[aAxis];
return std::max(size, 0);
}
struct CachedIntrinsicSizes
{
Maybe<nscoord> mMinSize;
Maybe<nscoord> mMinContentContribution;
Maybe<nscoord> mMaxContentContribution;
// "if the grid item spans only grid tracks that have a fixed max track
// sizing function, its automatic minimum size in that dimension is
// further clamped to less than or equal to the size necessary to fit its
// margin box within the resulting grid area (flooring at zero)"
// https://drafts.csswg.org/css-grid/#min-size-auto
// This is the clamp value to use for that:
nscoord mMinSizeClamp = NS_MAXSIZE;
};
static nscoord
MinContentContribution(const GridItemInfo& aGridItem,
const GridReflowInput& aState,
nsRenderingContext* aRC,
WritingMode aCBWM,
LogicalAxis aAxis,
CachedIntrinsicSizes* aCache)
{
if (aCache->mMinContentContribution.isSome()) {
return aCache->mMinContentContribution.value();
}
nscoord s = ContentContribution(aGridItem, aState, aRC, aCBWM, aAxis,
nsLayoutUtils::MIN_ISIZE,
aCache->mMinSizeClamp);
aCache->mMinContentContribution.emplace(s);
return s;
}
static nscoord
MaxContentContribution(const GridItemInfo& aGridItem,
const GridReflowInput& aState,
nsRenderingContext* aRC,
WritingMode aCBWM,
LogicalAxis aAxis,
CachedIntrinsicSizes* aCache)
{
if (aCache->mMaxContentContribution.isSome()) {
return aCache->mMaxContentContribution.value();
}
nscoord s = ContentContribution(aGridItem, aState, aRC, aCBWM, aAxis,
nsLayoutUtils::PREF_ISIZE,
aCache->mMinSizeClamp);
aCache->mMaxContentContribution.emplace(s);
return s;
}
// Computes the min-size contribution for a grid item, as defined at
// https://drafts.csswg.org/css-grid/#min-size-contributions
static nscoord
MinSize(const GridItemInfo& aGridItem,
const GridReflowInput& aState,
nsRenderingContext* aRC,
WritingMode aCBWM,
LogicalAxis aAxis,
CachedIntrinsicSizes* aCache)
{
if (aCache->mMinSize.isSome()) {
return aCache->mMinSize.value();
}
nsIFrame* child = aGridItem.mFrame;
PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis));
const nsStylePosition* stylePos = child->StylePosition();
const nsStyleCoord& sizeStyle =
axis == eAxisHorizontal ? stylePos->mWidth : stylePos->mHeight;
if (sizeStyle.GetUnit() != eStyleUnit_Auto) {
nscoord s =
MinContentContribution(aGridItem, aState, aRC, aCBWM, aAxis, aCache);
aCache->mMinSize.emplace(s);
return s;
}
// https://drafts.csswg.org/css-grid/#min-size-auto
// This calculates the min-content contribution from either a definite
// min-width (or min-height depending on aAxis), or the "specified /
// transferred size" for min-width:auto if overflow == visible (as min-width:0
// otherwise), or NS_UNCONSTRAINEDSIZE for other min-width intrinsic values
// (which results in always taking the "content size" part below).
MOZ_ASSERT(aGridItem.mBaselineOffset[aAxis] >= 0,
"baseline offset should be non-negative at this point");
MOZ_ASSERT((aGridItem.mState[aAxis] & ItemState::eIsBaselineAligned) ||
aGridItem.mBaselineOffset[aAxis] == nscoord(0),
"baseline offset should be zero when not baseline-aligned");
nscoord sz = aGridItem.mBaselineOffset[aAxis] +
nsLayoutUtils::MinSizeContributionForAxis(axis, aRC, child,
nsLayoutUtils::MIN_ISIZE);
const nsStyleCoord& style = axis == eAxisHorizontal ? stylePos->mMinWidth
: stylePos->mMinHeight;
auto unit = style.GetUnit();
if (unit == eStyleUnit_Enumerated ||
(unit == eStyleUnit_Auto &&
child->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE)) {
// Now calculate the "content size" part and return whichever is smaller.
MOZ_ASSERT(unit != eStyleUnit_Enumerated || sz == NS_UNCONSTRAINEDSIZE);
sz = std::min(sz, ContentContribution(aGridItem, aState, aRC, aCBWM, aAxis,
nsLayoutUtils::MIN_ISIZE,
aCache->mMinSizeClamp,
nsLayoutUtils::MIN_INTRINSIC_ISIZE));
}
aCache->mMinSize.emplace(sz);
return sz;
}
void
nsGridContainerFrame::Tracks::CalculateSizes(
GridReflowInput& aState,
nsTArray<GridItemInfo>& aGridItems,
const TrackSizingFunctions& aFunctions,
nscoord aContentBoxSize,
LineRange GridArea::* aRange,
SizingConstraint aConstraint)
{
nscoord percentageBasis = aContentBoxSize;
if (percentageBasis == NS_UNCONSTRAINEDSIZE) {
percentageBasis = 0;
}
InitializeItemBaselines(aState, aGridItems);
ResolveIntrinsicSize(aState, aGridItems, aFunctions, aRange, percentageBasis,
aConstraint);
if (aConstraint != SizingConstraint::eMinContent) {
nscoord freeSpace = aContentBoxSize;
if (freeSpace != NS_UNCONSTRAINEDSIZE) {
freeSpace -= SumOfGridGaps();
}
DistributeFreeSpace(freeSpace);
StretchFlexibleTracks(aState, aGridItems, aFunctions, freeSpace);
}
}
bool
nsGridContainerFrame::Tracks::HasIntrinsicButNoFlexSizingInRange(
const LineRange& aRange,
TrackSize::StateBits* aState) const
{
MOZ_ASSERT(!aRange.IsAuto(), "must have a definite range");
const uint32_t start = aRange.mStart;
const uint32_t end = aRange.mEnd;
const TrackSize::StateBits selector =
TrackSize::eIntrinsicMinSizing | TrackSize::eIntrinsicMaxSizing;
bool foundIntrinsic = false;
for (uint32_t i = start; i < end; ++i) {
TrackSize::StateBits state = mSizes[i].mState;
*aState |= state;
if (state & TrackSize::eFlexMaxSizing) {
return false;
}
if (state & selector) {
foundIntrinsic = true;
}
}
return foundIntrinsic;
}
bool
nsGridContainerFrame::Tracks::ResolveIntrinsicSizeStep1(
GridReflowInput& aState,
const TrackSizingFunctions& aFunctions,
nscoord aPercentageBasis,
SizingConstraint aConstraint,
const LineRange& aRange,
const GridItemInfo& aGridItem)
{
CachedIntrinsicSizes cache;
TrackSize& sz = mSizes[aRange.mStart];
WritingMode wm = aState.mWM;
// Calculate data for "Automatic Minimum Size" clamping, if needed.
bool needed = ((sz.mState & TrackSize::eIntrinsicMinSizing) ||
aConstraint == SizingConstraint::eNoConstraint);
if (needed && TrackSize::IsDefiniteMaxSizing(sz.mState) &&
aGridItem.ShouldClampMinSize(wm, mAxis, aPercentageBasis)) {
if (sz.mState & TrackSize::eIntrinsicMinSizing) {
auto maxCoord = aFunctions.MaxSizingFor(aRange.mStart);
cache.mMinSizeClamp =
nsRuleNode::ComputeCoordPercentCalc(maxCoord, aPercentageBasis);
}
aGridItem.mState[mAxis] |= ItemState::eClampMarginBoxMinSize;
}
// min sizing
nsRenderingContext* rc = &aState.mRenderingContext;
if (sz.mState & TrackSize::eAutoMinSizing) {
nscoord s;
if (aConstraint == SizingConstraint::eMinContent) {
s = MinContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
} else if (aConstraint == SizingConstraint::eMaxContent) {
s = MaxContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
} else {
MOZ_ASSERT(aConstraint == SizingConstraint::eNoConstraint);
s = MinSize(aGridItem, aState, rc, wm, mAxis, &cache);
}
sz.mBase = std::max(sz.mBase, s);
} else if (sz.mState & TrackSize::eMinContentMinSizing) {
auto s = MinContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
sz.mBase = std::max(sz.mBase, s);
} else if (sz.mState & TrackSize::eMaxContentMinSizing) {
auto s = MaxContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
sz.mBase = std::max(sz.mBase, s);
}
// max sizing
if (sz.mState & TrackSize::eMinContentMaxSizing) {
auto s = MinContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
if (sz.mLimit == NS_UNCONSTRAINEDSIZE) {
sz.mLimit = s;
} else {
sz.mLimit = std::max(sz.mLimit, s);
}
} else if (sz.mState & (TrackSize::eAutoMaxSizing |
TrackSize::eMaxContentMaxSizing)) {
auto s = MaxContentContribution(aGridItem, aState, rc, wm, mAxis, &cache);
if (sz.mLimit == NS_UNCONSTRAINEDSIZE) {
sz.mLimit = s;
} else {
sz.mLimit = std::max(sz.mLimit, s);
}
if (MOZ_UNLIKELY(sz.mState & TrackSize::eFitContent)) {
// Clamp mLimit to the fit-content() size, for §12.5.1.
auto maxCoord = aFunctions.MaxSizingFor(aRange.mStart);
nscoord fitContentClamp =
nsRuleNode::ComputeCoordPercentCalc(maxCoord, aPercentageBasis);
sz.mLimit = std::min(sz.mLimit, fitContentClamp);
}
}
if (sz.mLimit < sz.mBase) {
sz.mLimit = sz.mBase;
}
return sz.mState & TrackSize::eFlexMaxSizing;
}
void
nsGridContainerFrame::Tracks::CalculateItemBaselines(
nsTArray<ItemBaselineData>& aBaselineItems,
BaselineSharingGroup aBaselineGroup)
{
if (aBaselineItems.IsEmpty()) {
return;
}
// Sort the collected items on their baseline track.
std::sort(aBaselineItems.begin(), aBaselineItems.end(),
ItemBaselineData::IsBaselineTrackLessThan);
MOZ_ASSERT(mSizes.Length() > 0, "having an item implies at least one track");
const uint32_t lastTrack = mSizes.Length() - 1;
nscoord maxBaseline = 0;
nscoord maxDescent = 0;
uint32_t currentTrack = kAutoLine; // guaranteed to not match any item
uint32_t trackStartIndex = 0;
for (uint32_t i = 0, len = aBaselineItems.Length(); true ; ++i) {
// Find the maximum baseline and descent in the current track.
if (i != len) {
const ItemBaselineData& item = aBaselineItems[i];
if (currentTrack == item.mBaselineTrack) {
maxBaseline = std::max(maxBaseline, item.mBaseline);
maxDescent = std::max(maxDescent, item.mSize - item.mBaseline);
continue;
}
}
// Iterate the current track again and update the baseline offsets making
// all items baseline-aligned within this group in this track.
for (uint32_t j = trackStartIndex; j < i; ++j) {
const ItemBaselineData& item = aBaselineItems[j];
item.mGridItem->mBaselineOffset[mAxis] = maxBaseline - item.mBaseline;
MOZ_ASSERT(item.mGridItem->mBaselineOffset[mAxis] >= 0);
}
if (i != 0) {
// Store the size of this baseline-aligned subtree.
mSizes[currentTrack].mBaselineSubtreeSize[aBaselineGroup] =
maxBaseline + maxDescent;
// Record the first(last) baseline for the first(last) track.
if (currentTrack == 0 && aBaselineGroup == BaselineSharingGroup::eFirst) {
mBaseline[aBaselineGroup] = maxBaseline;
}
if (currentTrack == lastTrack &&
aBaselineGroup == BaselineSharingGroup::eLast) {
mBaseline[aBaselineGroup] = maxBaseline;
}
}
if (i == len) {
break;
}
// Initialize data for the next track with baseline-aligned items.
const ItemBaselineData& item = aBaselineItems[i];
currentTrack = item.mBaselineTrack;
trackStartIndex = i;
maxBaseline = item.mBaseline;
maxDescent = item.mSize - item.mBaseline;
}
}
void
nsGridContainerFrame::Tracks::InitializeItemBaselines(
GridReflowInput& aState,
nsTArray<GridItemInfo>& aGridItems)
{
nsTArray<ItemBaselineData> firstBaselineItems;
nsTArray<ItemBaselineData> lastBaselineItems;
WritingMode wm = aState.mWM;
nsStyleContext* containerSC = aState.mFrame->StyleContext();
GridItemCSSOrderIterator& iter = aState.mIter;
iter.Reset();
for (; !iter.AtEnd(); iter.Next()) {
nsIFrame* child = *iter;
GridItemInfo& gridItem = aGridItems[iter.GridItemIndex()];
uint32_t baselineTrack = kAutoLine;
auto state = ItemState(0);
auto childWM = child->GetWritingMode();
const bool isOrthogonal = wm.IsOrthogonalTo(childWM);
const bool isInlineAxis = mAxis == eLogicalAxisInline; // i.e. columns
// XXX update the line below to include orthogonal grid/table boxes
// XXX since they have baselines in both dimensions. And flexbox with
// XXX reversed main/cross axis?
const bool itemHasBaselineParallelToTrack = isInlineAxis == isOrthogonal;
if (itemHasBaselineParallelToTrack) {
// [align|justify]-self:[last ]baseline.
auto selfAlignment = isOrthogonal ?
child->StylePosition()->UsedJustifySelf(containerSC) :
child->StylePosition()->UsedAlignSelf(containerSC);
selfAlignment &= ~NS_STYLE_ALIGN_FLAG_BITS;
if (selfAlignment == NS_STYLE_ALIGN_BASELINE) {
state |= ItemState::eFirstBaseline | ItemState::eSelfBaseline;
const GridArea& area = gridItem.mArea;
baselineTrack = isInlineAxis ? area.mCols.mStart : area.mRows.mStart;
} else if (selfAlignment == NS_STYLE_ALIGN_LAST_BASELINE) {
state |= ItemState::eLastBaseline | ItemState::eSelfBaseline;
const GridArea& area = gridItem.mArea;
baselineTrack = (isInlineAxis ? area.mCols.mEnd : area.mRows.mEnd) - 1;
}
// [align|justify]-content:[last ]baseline.
// https://drafts.csswg.org/css-align-3/#baseline-align-content
// "[...] and its computed 'align-self' or 'justify-self' (whichever
// affects its block axis) is 'stretch' or 'self-start' ('self-end').
// For this purpose, the 'start', 'end', 'flex-start', and 'flex-end'
// values of 'align-self' are treated as either 'self-start' or
// 'self-end', whichever they end up equivalent to.
auto alignContent = child->StylePosition()->mAlignContent;
alignContent &= ~NS_STYLE_ALIGN_FLAG_BITS;
if (alignContent == NS_STYLE_ALIGN_BASELINE ||
alignContent == NS_STYLE_ALIGN_LAST_BASELINE) {
const auto selfAlignEdge = alignContent == NS_STYLE_ALIGN_BASELINE ?
NS_STYLE_ALIGN_SELF_START : NS_STYLE_ALIGN_SELF_END;
bool validCombo = selfAlignment == NS_STYLE_ALIGN_NORMAL ||
selfAlignment == NS_STYLE_ALIGN_STRETCH ||
selfAlignment == selfAlignEdge;
if (!validCombo) {
// We're doing alignment in the axis that's orthogonal to mAxis here.
LogicalAxis alignAxis = GetOrthogonalAxis(mAxis);
// |sameSide| is true if the container's start side in this axis is
// the same as the child's start side, in the child's parallel axis.
bool sameSide = wm.ParallelAxisStartsOnSameSide(alignAxis, childWM);
switch (selfAlignment) {
case NS_STYLE_ALIGN_LEFT:
selfAlignment = !isInlineAxis || wm.IsBidiLTR() ? NS_STYLE_ALIGN_START
: NS_STYLE_ALIGN_END;
break;
case NS_STYLE_ALIGN_RIGHT:
selfAlignment = isInlineAxis && wm.IsBidiLTR() ? NS_STYLE_ALIGN_END
: NS_STYLE_ALIGN_START;
break;
}
switch (selfAlignment) {
case NS_STYLE_ALIGN_START:
case NS_STYLE_ALIGN_FLEX_START:
validCombo = sameSide ==
(alignContent == NS_STYLE_ALIGN_BASELINE);
break;
case NS_STYLE_ALIGN_END:
case NS_STYLE_ALIGN_FLEX_END:
validCombo = sameSide ==
(alignContent == NS_STYLE_ALIGN_LAST_BASELINE);
break;
}
}
if (validCombo) {
const GridArea& area = gridItem.mArea;
if (alignContent == NS_STYLE_ALIGN_BASELINE) {
state |= ItemState::eFirstBaseline | ItemState::eContentBaseline;
baselineTrack = isInlineAxis ? area.mCols.mStart : area.mRows.mStart;
} else if (alignContent == NS_STYLE_ALIGN_LAST_BASELINE) {
state |= ItemState::eLastBaseline | ItemState::eContentBaseline;
baselineTrack = (isInlineAxis ? area.mCols.mEnd : area.mRows.mEnd) - 1;
}
}
}
}
if (state & ItemState::eIsBaselineAligned) {
// XXX available size issue
LogicalSize avail(childWM, INFINITE_ISIZE_COORD, NS_UNCONSTRAINEDSIZE);
auto* rc = &aState.mRenderingContext;
// XXX figure out if we can avoid/merge this reflow with the main reflow.
// XXX (after bug 1174569 is sorted out)
//
// XXX How should we handle percentage padding here? (bug 1330866)
// XXX (see ::ContentContribution and how it deals with percentages)
// XXX What if the true baseline after line-breaking differs from this
// XXX hypothetical baseline based on an infinite inline size?
// XXX Maybe we should just call ::ContentContribution here instead?
// XXX For now we just pass a zero-sized CB:
LogicalSize cbSize(childWM, 0, 0);
::MeasuringReflow(child, aState.mReflowInput, rc, avail, cbSize);
nscoord baseline;
nsGridContainerFrame* grid = do_QueryFrame(child);
if (state & ItemState::eFirstBaseline) {
if (grid) {
if (isOrthogonal == isInlineAxis) {
grid->GetBBaseline(BaselineSharingGroup::eFirst, &baseline);
} else {
grid->GetIBaseline(BaselineSharingGroup::eFirst, &baseline);
}
}
if (grid ||
nsLayoutUtils::GetFirstLineBaseline(wm, child, &baseline)) {
NS_ASSERTION(baseline != NS_INTRINSIC_WIDTH_UNKNOWN,
"about to use an unknown baseline");
auto frameSize = isInlineAxis ? child->ISize(wm) : child->BSize(wm);
auto m = child->GetLogicalUsedMargin(wm);
baseline += isInlineAxis ? m.IStart(wm) : m.BStart(wm);
auto alignSize = frameSize + (isInlineAxis ? m.IStartEnd(wm)
: m.BStartEnd(wm));
firstBaselineItems.AppendElement(ItemBaselineData(
{ baselineTrack, baseline, alignSize, &gridItem }));
} else {
state &= ~ItemState::eAllBaselineBits;
}
} else {
if (grid) {
if (isOrthogonal == isInlineAxis) {
grid->GetBBaseline(BaselineSharingGroup::eLast, &baseline);
} else {
grid->GetIBaseline(BaselineSharingGroup::eLast, &baseline);
}
}
if (grid ||
nsLayoutUtils::GetLastLineBaseline(wm, child, &baseline)) {
NS_ASSERTION(baseline != NS_INTRINSIC_WIDTH_UNKNOWN,
"about to use an unknown baseline");
auto frameSize = isInlineAxis ? child->ISize(wm) : child->BSize(wm);
auto m = child->GetLogicalUsedMargin(wm);
if (!grid) {
// Convert to distance from border-box end.
baseline = frameSize - baseline;
}
auto descent = baseline + (isInlineAxis ? m.IEnd(wm) : m.BEnd(wm));
auto alignSize = frameSize + (isInlineAxis ? m.IStartEnd(wm)
: m.BStartEnd(wm));
lastBaselineItems.AppendElement(ItemBaselineData(
{ baselineTrack, descent, alignSize, &gridItem }));
} else {
state &= ~ItemState::eAllBaselineBits;
}
}
}
MOZ_ASSERT((state &
(ItemState::eFirstBaseline | ItemState::eLastBaseline)) !=
(ItemState::eFirstBaseline | ItemState::eLastBaseline),
"first/last baseline bits are mutually exclusive");
MOZ_ASSERT((state &
(ItemState::eSelfBaseline | ItemState::eContentBaseline)) !=
(ItemState::eSelfBaseline | ItemState::eContentBaseline),
"*-self and *-content baseline bits are mutually exclusive");
MOZ_ASSERT(!(state &
(ItemState::eFirstBaseline | ItemState::eLastBaseline)) ==
!(state &
(ItemState::eSelfBaseline | ItemState::eContentBaseline)),
"first/last bit requires self/content bit and vice versa");
gridItem.mState[mAxis] = state;
gridItem.mBaselineOffset[mAxis] = nscoord(0);
}
if (firstBaselineItems.IsEmpty() && lastBaselineItems.IsEmpty()) {
return;
}
// TODO: CSS Align spec issue - how to align a baseline subtree in a track?
// https://lists.w3.org/Archives/Public/www-style/2016May/0141.html
mBaselineSubtreeAlign[BaselineSharingGroup::eFirst] = NS_STYLE_ALIGN_START;
mBaselineSubtreeAlign[BaselineSharingGroup::eLast] = NS_STYLE_ALIGN_END;
CalculateItemBaselines(firstBaselineItems, BaselineSharingGroup::eFirst);
CalculateItemBaselines(lastBaselineItems, BaselineSharingGroup::eLast);
}
void
nsGridContainerFrame::Tracks::AlignBaselineSubtree(
const GridItemInfo& aGridItem) const
{
auto state = aGridItem.mState[mAxis];
if (!(state & ItemState::eIsBaselineAligned)) {
return;
}
const GridArea& area = aGridItem.mArea;
int32_t baselineTrack;
const bool isFirstBaseline = state & ItemState::eFirstBaseline;
if (isFirstBaseline) {
baselineTrack = mAxis == eLogicalAxisBlock ? area.mRows.mStart
: area.mCols.mStart;
} else {
baselineTrack = (mAxis == eLogicalAxisBlock ? area.mRows.mEnd
: area.mCols.mEnd) - 1;
}
const TrackSize& sz = mSizes[baselineTrack];
auto baselineGroup = isFirstBaseline ? BaselineSharingGroup::eFirst
: BaselineSharingGroup::eLast;
nscoord delta = sz.mBase - sz.mBaselineSubtreeSize[baselineGroup];
const auto subtreeAlign = mBaselineSubtreeAlign[baselineGroup];
switch (subtreeAlign) {
case NS_STYLE_ALIGN_START:
if (state & ItemState::eLastBaseline) {
aGridItem.mBaselineOffset[mAxis] += delta;
}
break;
case NS_STYLE_ALIGN_END:
if (isFirstBaseline) {
aGridItem.mBaselineOffset[mAxis] += delta;
}
break;
case NS_STYLE_ALIGN_CENTER:
aGridItem.mBaselineOffset[mAxis] += delta / 2;
break;
default:
MOZ_ASSERT_UNREACHABLE("unexpected baseline subtree alignment");
}
}
void
nsGridContainerFrame::Tracks::ResolveIntrinsicSize(
GridReflowInput& aState,
nsTArray<GridItemInfo>& aGridItems,
const TrackSizingFunctions& aFunctions,
LineRange GridArea::* aRange,
nscoord aPercentageBasis,
SizingConstraint aConstraint)
{
// Some data we collect on each item for Step 2 of the algorithm below.
struct Step2ItemData
{
uint32_t mSpan;
TrackSize::StateBits mState;
LineRange mLineRange;
nscoord mMinSize;
nscoord mMinContentContribution;
nscoord mMaxContentContribution;
nsIFrame* mFrame;
static bool IsSpanLessThan(const Step2ItemData& a, const Step2ItemData& b)
{
return a.mSpan < b.mSpan;
}
};
// Resolve Intrinsic Track Sizes
// http://dev.w3.org/csswg/css-grid/#algo-content
// We're also setting eIsFlexing on the item state here to speed up
// FindUsedFlexFraction later.
AutoTArray<TrackSize::StateBits, 16> stateBitsPerSpan;
nsTArray<Step2ItemData> step2Items;
GridItemCSSOrderIterator& iter = aState.mIter;
nsRenderingContext* rc = &aState.mRenderingContext;
WritingMode wm = aState.mWM;
uint32_t maxSpan = 0; // max span of the step2Items items
// Setup track selector for step 2.2:
const auto contentBasedMinSelector =
aConstraint == SizingConstraint::eMinContent ?
TrackSize::eIntrinsicMinSizing : TrackSize::eMinOrMaxContentMinSizing;
// Setup track selector for step 2.3:
const auto maxContentMinSelector =
aConstraint == SizingConstraint::eMaxContent ?
(TrackSize::eMaxContentMinSizing | TrackSize::eAutoMinSizing) :
TrackSize::eMaxContentMinSizing;
iter.Reset();
for (; !iter.AtEnd(); iter.Next()) {
auto& gridItem = aGridItems[iter.GridItemIndex()];
const GridArea& area = gridItem.mArea;
const LineRange& lineRange = area.*aRange;
uint32_t span = lineRange.Extent();
if (span == 1) {
// Step 1. Size tracks to fit non-spanning items.
if (ResolveIntrinsicSizeStep1(aState, aFunctions, aPercentageBasis,
aConstraint, lineRange, gridItem)) {
gridItem.mState[mAxis] |= ItemState::eIsFlexing;
}
} else {
TrackSize::StateBits state = TrackSize::StateBits(0);
if (HasIntrinsicButNoFlexSizingInRange(lineRange, &state)) {
// Collect data for Step 2.
maxSpan = std::max(maxSpan, span);
if (span >= stateBitsPerSpan.Length()) {
uint32_t len = 2 * span;
stateBitsPerSpan.SetCapacity(len);
for (uint32_t i = stateBitsPerSpan.Length(); i < len; ++i) {
stateBitsPerSpan.AppendElement(TrackSize::StateBits(0));
}
}
stateBitsPerSpan[span] |= state;
CachedIntrinsicSizes cache;
// Calculate data for "Automatic Minimum Size" clamping, if needed.
bool needed = ((state & TrackSize::eIntrinsicMinSizing) ||
aConstraint == SizingConstraint::eNoConstraint);
if (needed && TrackSize::IsDefiniteMaxSizing(state) &&
gridItem.ShouldClampMinSize(wm, mAxis, aPercentageBasis)) {
nscoord minSizeClamp = 0;
for (auto i = lineRange.mStart, end = lineRange.mEnd; i < end; ++i) {
auto maxCoord = aFunctions.MaxSizingFor(i);
minSizeClamp +=
nsRuleNode::ComputeCoordPercentCalc(maxCoord, aPercentageBasis);
}
minSizeClamp += mGridGap * (span - 1);
cache.mMinSizeClamp = minSizeClamp;
gridItem.mState[mAxis] |= ItemState::eClampMarginBoxMinSize;
}
// Collect the various grid item size contributions we need.
nscoord minSize = 0;
if (state & (TrackSize::eIntrinsicMinSizing | // for 2.1
TrackSize::eIntrinsicMaxSizing)) { // for 2.5
minSize = MinSize(gridItem, aState, rc, wm, mAxis, &cache);
}
nscoord minContent = 0;
if (state & contentBasedMinSelector) { // for 2.2
minContent = MinContentContribution(gridItem, aState,
rc, wm, mAxis, &cache);
}
nscoord maxContent = 0;
if (state & (maxContentMinSelector | // for 2.3
TrackSize::eAutoOrMaxContentMaxSizing)) { // for 2.6
maxContent = MaxContentContribution(gridItem, aState,
rc, wm, mAxis, &cache);
}
step2Items.AppendElement(
Step2ItemData({span, state, lineRange, minSize,
minContent, maxContent, *iter}));
} else {
if (state & TrackSize::eFlexMaxSizing) {
gridItem.mState[mAxis] |= ItemState::eIsFlexing;
} else if (aConstraint == SizingConstraint::eNoConstraint &&
TrackSize::IsDefiniteMaxSizing(state) &&
gridItem.ShouldClampMinSize(wm, mAxis, aPercentageBasis)) {
gridItem.mState[mAxis] |= ItemState::eClampMarginBoxMinSize;
}
}
}
}
// Step 2.
if (maxSpan) {
// Sort the collected items on span length, shortest first.
std::stable_sort(step2Items.begin(), step2Items.end(),
Step2ItemData::IsSpanLessThan);
nsTArray<uint32_t> tracks(maxSpan);
nsTArray<TrackSize> plan(mSizes.Length());
plan.SetLength(mSizes.Length());
for (uint32_t i = 0, len = step2Items.Length(); i < len; ) {
// Start / end index for items of the same span length:
const uint32_t spanGroupStartIndex = i;
uint32_t spanGroupEndIndex = len;
const uint32_t span = step2Items[i].mSpan;
for (++i; i < len; ++i) {
if (step2Items[i].mSpan != span) {
spanGroupEndIndex = i;
break;
}
}
bool updatedBase = false; // Did we update any mBase in step 2.1 - 2.3?
TrackSize::StateBits selector(TrackSize::eIntrinsicMinSizing);
if (stateBitsPerSpan[span] & selector) {
// Step 2.1 MinSize to intrinsic min-sizing.
for (i = spanGroupStartIndex; i < spanGroupEndIndex; ++i) {
Step2ItemData& item = step2Items[i];
if (!(item.mState & selector)) {
continue;
}
nscoord space = item.mMinSize;
if (space <= 0) {
continue;
}
tracks.ClearAndRetainStorage();
space = CollectGrowable(space, mSizes, item.mLineRange, selector,
tracks);
if (space > 0) {
DistributeToTrackBases(space, plan, tracks, selector);
updatedBase = true;
}
}
}
selector = contentBasedMinSelector;
if (stateBitsPerSpan[span] & selector) {
// Step 2.2 MinContentContribution to min-/max-content (and 'auto' when
// sizing under a min-content constraint) min-sizing.
for (i = spanGroupStartIndex; i < spanGroupEndIndex; ++i) {
Step2ItemData& item = step2Items[i];
if (!(item.mState & selector)) {
continue;
}
nscoord space = item.mMinContentContribution;
if (space <= 0) {
continue;
}
tracks.ClearAndRetainStorage();
space = CollectGrowable(space, mSizes, item.mLineRange, selector,
tracks);
if (space > 0) {
DistributeToTrackBases(space, plan, tracks, selector);
updatedBase = true;
}
}
}
selector = maxContentMinSelector;
if (stateBitsPerSpan[span] & selector) {
// Step 2.3 MaxContentContribution to max-content (and 'auto' when
// sizing under a max-content constraint) min-sizing.
for (i = spanGroupStartIndex; i < spanGroupEndIndex; ++i) {
Step2ItemData& item = step2Items[i];
if (!(item.mState & selector)) {
continue;
}
nscoord space = item.mMaxContentContribution;
if (space <= 0) {
continue;
}
tracks.ClearAndRetainStorage();
space = CollectGrowable(space, mSizes, item.mLineRange, selector,
tracks);
if (space > 0) {
DistributeToTrackBases(space, plan, tracks, selector);
updatedBase = true;
}
}
}
if (updatedBase) {
// Step 2.4
for (TrackSize& sz : mSizes) {
if (sz.mBase > sz.mLimit) {
sz.mLimit = sz.mBase;
}
}
}
if (stateBitsPerSpan[span] & TrackSize::eIntrinsicMaxSizing) {
plan = mSizes;
for (TrackSize& sz : plan) {
if (sz.mLimit == NS_UNCONSTRAINEDSIZE) {
// use mBase as the planned limit
} else {
sz.mBase = sz.mLimit;
}
}
// Step 2.5 MinSize to intrinsic max-sizing.
for (i = spanGroupStartIndex; i < spanGroupEndIndex; ++i) {
Step2ItemData& item = step2Items[i];
if (!(item.mState & TrackSize::eIntrinsicMaxSizing)) {
continue;
}
nscoord space = item.mMinSize;
if (space <= 0) {
continue;
}
tracks.ClearAndRetainStorage();
space = CollectGrowable(space, plan, item.mLineRange,
TrackSize::eIntrinsicMaxSizing,
tracks);
if (space > 0) {
DistributeToTrackLimits(space, plan, tracks, aFunctions,
aPercentageBasis);
}
}
for (size_t j = 0, len = mSizes.Length(); j < len; ++j) {
TrackSize& sz = plan[j];
sz.mState &= ~(TrackSize::eFrozen | TrackSize::eSkipGrowUnlimited);
if (sz.mLimit != NS_UNCONSTRAINEDSIZE) {
sz.mLimit = sz.mBase; // collect the results from 2.5
}
}
if (stateBitsPerSpan[span] & TrackSize::eAutoOrMaxContentMaxSizing) {
// Step 2.6 MaxContentContribution to max-content max-sizing.
for (i = spanGroupStartIndex; i < spanGroupEndIndex; ++i) {
Step2ItemData& item = step2Items[i];
if (!(item.mState & TrackSize::eAutoOrMaxContentMaxSizing)) {
continue;
}
nscoord space = item.mMaxContentContribution;
if (space <= 0) {
continue;
}
tracks.ClearAndRetainStorage();
space = CollectGrowable(space, plan, item.mLineRange,
TrackSize::eAutoOrMaxContentMaxSizing,
tracks);
if (space > 0) {
DistributeToTrackLimits(space, plan, tracks, aFunctions,
aPercentageBasis);
}
}
}
}
}
}
// Step 3.
for (TrackSize& sz : mSizes) {
if (sz.mLimit == NS_UNCONSTRAINEDSIZE) {
sz.mLimit = sz.mBase;
}
}
}
float
nsGridContainerFrame::Tracks::FindFrUnitSize(
const LineRange& aRange,
const nsTArray<uint32_t>& aFlexTracks,
const TrackSizingFunctions& aFunctions,
nscoord aSpaceToFill) const
{
MOZ_ASSERT(aSpaceToFill > 0 && !aFlexTracks.IsEmpty());
float flexFactorSum = 0.0f;
nscoord leftOverSpace = aSpaceToFill;
for (uint32_t i = aRange.mStart, end = aRange.mEnd; i < end; ++i) {
const TrackSize& sz = mSizes[i];
if (sz.mState & TrackSize::eFlexMaxSizing) {
flexFactorSum += aFunctions.MaxSizingFor(i).GetFlexFractionValue();
} else {
leftOverSpace -= sz.mBase;
if (leftOverSpace <= 0) {
return 0.0f;
}
}
}
bool restart;
float hypotheticalFrSize;
nsTArray<uint32_t> flexTracks(aFlexTracks);
uint32_t numFlexTracks = flexTracks.Length();
do {
restart = false;
hypotheticalFrSize = leftOverSpace / std::max(flexFactorSum, 1.0f);
for (uint32_t i = 0, len = flexTracks.Length(); i < len; ++i) {
uint32_t track = flexTracks[i];
if (track == kAutoLine) {
continue; // Track marked as inflexible in a prev. iter of this loop.
}
float flexFactor = aFunctions.MaxSizingFor(track).GetFlexFractionValue();
const nscoord base = mSizes[track].mBase;
if (flexFactor * hypotheticalFrSize < base) {
// 12.7.1.4: Treat this track as inflexible.
flexTracks[i] = kAutoLine;
flexFactorSum -= flexFactor;
leftOverSpace -= base;
--numFlexTracks;
if (numFlexTracks == 0 || leftOverSpace <= 0) {
return 0.0f;
}
restart = true;
// break; XXX (bug 1176621 comment 16) measure which is more common
}
}
} while (restart);
return hypotheticalFrSize;
}
float
nsGridContainerFrame::Tracks::FindUsedFlexFraction(
GridReflowInput& aState,
nsTArray<GridItemInfo>& aGridItems,
const nsTArray<uint32_t>& aFlexTracks,
const TrackSizingFunctions& aFunctions,
nscoord aAvailableSize) const
{
if (aAvailableSize != NS_UNCONSTRAINEDSIZE) {
// Use all of the grid tracks and a 'space to fill' of the available space.
const TranslatedLineRange range(0, mSizes.Length());
return FindFrUnitSize(range, aFlexTracks, aFunctions, aAvailableSize);
}
// The used flex fraction is the maximum of:
// ... each flexible track's base size divided by its flex factor (which is
// floored at 1).
float fr = 0.0f;
for (uint32_t track : aFlexTracks) {
float flexFactor = aFunctions.MaxSizingFor(track).GetFlexFractionValue();
float possiblyDividedBaseSize = (flexFactor > 1.0f)
? mSizes[track].mBase / flexFactor
: mSizes[track].mBase;
fr = std::max(fr, possiblyDividedBaseSize);
}
WritingMode wm = aState.mWM;
nsRenderingContext* rc = &aState.mRenderingContext;
GridItemCSSOrderIterator& iter = aState.mIter;
iter.Reset();
// ... the result of 'finding the size of an fr' for each item that spans
// a flex track with its max-content contribution as 'space to fill'
for (; !iter.AtEnd(); iter.Next()) {
const GridItemInfo& item = aGridItems[iter.GridItemIndex()];
if (item.mState[mAxis] & ItemState::eIsFlexing) {
// XXX optimize: bug 1194446
nscoord spaceToFill = ContentContribution(item, aState, rc, wm, mAxis,
nsLayoutUtils::PREF_ISIZE);
if (spaceToFill <= 0) {
continue;
}
// ... and all its spanned tracks as input.
const LineRange& range =
mAxis == eLogicalAxisInline ? item.mArea.mCols : item.mArea.mRows;
nsTArray<uint32_t> itemFlexTracks;
for (uint32_t i = range.mStart, end = range.mEnd; i < end; ++i) {
if (mSizes[i].mState & TrackSize::eFlexMaxSizing) {
itemFlexTracks.AppendElement(i);
}
}
float itemFr =
FindFrUnitSize(range, itemFlexTracks, aFunctions, spaceToFill);
fr = std::max(fr, itemFr);
}
}
return fr;
}
void
nsGridContainerFrame::Tracks::StretchFlexibleTracks(
GridReflowInput& aState,
nsTArray<GridItemInfo>& aGridItems,
const TrackSizingFunctions& aFunctions,
nscoord aAvailableSize)
{
if (aAvailableSize <= 0) {
return;
}
nsTArray<uint32_t> flexTracks(mSizes.Length());
for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) {
if (mSizes[i].mState & TrackSize::eFlexMaxSizing) {
flexTracks.AppendElement(i);
}
}
if (flexTracks.IsEmpty()) {
return;
}
nscoord minSize = 0;
nscoord maxSize = NS_UNCONSTRAINEDSIZE;
if (aState.mReflowInput) {
auto* ri = aState.mReflowInput;
minSize = mAxis == eLogicalAxisBlock ? ri->ComputedMinBSize()
: ri->ComputedMinISize();
maxSize = mAxis == eLogicalAxisBlock ? ri->ComputedMaxBSize()
: ri->ComputedMaxISize();
}
Maybe<nsTArray<TrackSize>> origSizes;
// We iterate twice at most. The 2nd time if the grid size changed after
// applying a min/max-size (can only occur if aAvailableSize is indefinite).
while (true) {
float fr = FindUsedFlexFraction(aState, aGridItems, flexTracks,
aFunctions, aAvailableSize);
if (fr != 0.0f) {
bool applyMinMax = (minSize != 0 || maxSize != NS_UNCONSTRAINEDSIZE) &&
aAvailableSize == NS_UNCONSTRAINEDSIZE;
for (uint32_t i : flexTracks) {
float flexFactor = aFunctions.MaxSizingFor(i).GetFlexFractionValue();
nscoord flexLength = NSToCoordRound(flexFactor * fr);
nscoord& base = mSizes[i].mBase;
if (flexLength > base) {
if (applyMinMax && origSizes.isNothing()) {
origSizes.emplace(mSizes);
}
base = flexLength;
}
}
if (applyMinMax && origSizes.isSome()) {
// https://drafts.csswg.org/css-grid/#algo-flex-tracks
// "If using this flex fraction would cause the grid to be smaller than
// the grid containers min-width/height (or larger than the grid
// containers max-width/height), then redo this step, treating the free
// space as definite [...]"
nscoord newSize = 0;
for (auto& sz : mSizes) {
newSize += sz.mBase;
}
const auto sumOfGridGaps = SumOfGridGaps();
newSize += sumOfGridGaps;
if (newSize > maxSize) {
aAvailableSize = maxSize;
} else if (newSize < minSize) {
aAvailableSize = minSize;
}
if (aAvailableSize != NS_UNCONSTRAINEDSIZE) {
// Reset min/max-size to ensure 'applyMinMax' becomes false next time.
minSize = 0;
maxSize = NS_UNCONSTRAINEDSIZE;
aAvailableSize = std::max(0, aAvailableSize - sumOfGridGaps);
// Restart with the original track sizes and definite aAvailableSize.
mSizes = Move(*origSizes);
origSizes.reset();
if (aAvailableSize == 0) {
break; // zero available size wouldn't change any sizes though...
}
continue;
}
}
}
break;
}
}
void
nsGridContainerFrame::Tracks::AlignJustifyContent(
const nsStylePosition* aStyle,
WritingMode aWM,
const LogicalSize& aContainerSize)
{
if (mSizes.IsEmpty()) {
return;
}
const bool isAlign = mAxis == eLogicalAxisBlock;
auto valueAndFallback = isAlign ? aStyle->mAlignContent :
aStyle->mJustifyContent;
bool overflowSafe;
auto alignment = ::GetAlignJustifyValue(valueAndFallback, aWM, isAlign,
&overflowSafe);
if (alignment == NS_STYLE_ALIGN_NORMAL) {
MOZ_ASSERT(valueAndFallback == NS_STYLE_ALIGN_NORMAL,
"*-content:normal cannot be specified with explicit fallback");
alignment = NS_STYLE_ALIGN_STRETCH;
valueAndFallback = alignment; // we may need a fallback for 'stretch' below
}
// Compute the free space and count auto-sized tracks.
size_t numAutoTracks = 0;
nscoord space;
if (alignment != NS_STYLE_ALIGN_START) {
nscoord trackSizeSum = 0;
for (const TrackSize& sz : mSizes) {
trackSizeSum += sz.mBase;
if (sz.mState & TrackSize::eAutoMaxSizing) {
++numAutoTracks;
}
}
nscoord cbSize = isAlign ? aContainerSize.BSize(aWM)
: aContainerSize.ISize(aWM);
space = cbSize - trackSizeSum - SumOfGridGaps();
// Use the fallback value instead when applicable.
if (space < 0 ||
(alignment == NS_STYLE_ALIGN_SPACE_BETWEEN && mSizes.Length() == 1)) {
auto fallback = ::GetAlignJustifyFallbackIfAny(valueAndFallback, aWM,
isAlign, &overflowSafe);
if (fallback) {
alignment = fallback;
}
}
if (space == 0 || (space < 0 && overflowSafe)) {
// XXX check that this makes sense also for [last ]baseline (bug 1151204).
alignment = NS_STYLE_ALIGN_START;
}
}
// Optimize the cases where we just need to set each track's position.
nscoord pos = 0;
bool distribute = true;
switch (alignment) {
case NS_STYLE_ALIGN_BASELINE:
case NS_STYLE_ALIGN_LAST_BASELINE:
NS_WARNING("NYI: 'first/last baseline' (bug 1151204)"); // XXX
MOZ_FALLTHROUGH;
case NS_STYLE_ALIGN_START:
distribute = false;
break;
case NS_STYLE_ALIGN_END:
pos = space;
distribute = false;
break;
case NS_STYLE_ALIGN_CENTER:
pos = space / 2;
distribute = false;
break;
case NS_STYLE_ALIGN_STRETCH:
distribute = numAutoTracks != 0;
break;
}
if (!distribute) {
for (TrackSize& sz : mSizes) {
sz.mPosition = pos;
pos += sz.mBase + mGridGap;
}
return;
}
// Distribute free space to/between tracks and set their position.
MOZ_ASSERT(space > 0, "should've handled that on the fallback path above");
nscoord between, roundingError;
switch (alignment) {
case NS_STYLE_ALIGN_STRETCH: {
MOZ_ASSERT(numAutoTracks > 0, "we handled numAutoTracks == 0 above");
nscoord spacePerTrack;
roundingError = NSCoordDivRem(space, numAutoTracks, &spacePerTrack);
for (TrackSize& sz : mSizes) {
sz.mPosition = pos;
if (!(sz.mState & TrackSize::eAutoMaxSizing)) {
pos += sz.mBase + mGridGap;
continue;
}
nscoord stretch = spacePerTrack;
if (roundingError) {
roundingError -= 1;
stretch += 1;
}
nscoord newBase = sz.mBase + stretch;
sz.mBase = newBase;
pos += newBase + mGridGap;
}
MOZ_ASSERT(!roundingError, "we didn't distribute all rounding error?");
return;
}
case NS_STYLE_ALIGN_SPACE_BETWEEN:
MOZ_ASSERT(mSizes.Length() > 1, "should've used a fallback above");
roundingError = NSCoordDivRem(space, mSizes.Length() - 1, &between);
break;
case NS_STYLE_ALIGN_SPACE_AROUND:
roundingError = NSCoordDivRem(space, mSizes.Length(), &between);
pos = between / 2;
break;
case NS_STYLE_ALIGN_SPACE_EVENLY:
roundingError = NSCoordDivRem(space, mSizes.Length() + 1, &between);
pos = between;
break;
default:
MOZ_ASSERT_UNREACHABLE("unknown align-/justify-content value");
between = 0; // just to avoid a compiler warning
}
between += mGridGap;
for (TrackSize& sz : mSizes) {
sz.mPosition = pos;
nscoord spacing = between;
if (roundingError) {
roundingError -= 1;
spacing += 1;
}
pos += sz.mBase + spacing;
}
MOZ_ASSERT(!roundingError, "we didn't distribute all rounding error?");
}
nscoord
nsGridContainerFrame::Tracks::BackComputedIntrinsicSize(
const TrackSizingFunctions& aFunctions,
const nsStyleCoord& aGridGap) const
{
// Sum up the current sizes (where percentage tracks were treated as 'auto')
// in 'size'.
nscoord size = 0;
for (size_t i = 0, len = mSizes.Length(); i < len; ++i) {
size += mSizes[i].mBase;
}
// Add grid-gap contributions to 'size' and calculate a 'percent' sum.
float percent = 0.0f;
size_t numTracks = mSizes.Length();
if (numTracks > 1) {
const size_t gridGapCount = numTracks - 1;
nscoord gridGapLength;
float gridGapPercent;
if (::GetPercentSizeParts(aGridGap, &gridGapLength, &gridGapPercent)) {
percent = gridGapCount * gridGapPercent;
} else {
gridGapLength = aGridGap.ToLength();
}
size += gridGapCount * gridGapLength;
}
return std::max(0, nsLayoutUtils::AddPercents(size, percent));
}
void
nsGridContainerFrame::LineRange::ToPositionAndLength(
const nsTArray<TrackSize>& aTrackSizes, nscoord* aPos, nscoord* aLength) const
{
MOZ_ASSERT(mStart != kAutoLine && mEnd != kAutoLine,
"expected a definite LineRange");
MOZ_ASSERT(mStart < mEnd);
nscoord startPos = aTrackSizes[mStart].mPosition;
const TrackSize& sz = aTrackSizes[mEnd - 1];
*aPos = startPos;
*aLength = (sz.mPosition + sz.mBase) - startPos;
}
nscoord
nsGridContainerFrame::LineRange::ToLength(
const nsTArray<TrackSize>& aTrackSizes) const
{
MOZ_ASSERT(mStart != kAutoLine && mEnd != kAutoLine,
"expected a definite LineRange");
MOZ_ASSERT(mStart < mEnd);
nscoord startPos = aTrackSizes[mStart].mPosition;
const TrackSize& sz = aTrackSizes[mEnd - 1];
return (sz.mPosition + sz.mBase) - startPos;
}
void
nsGridContainerFrame::LineRange::ToPositionAndLengthForAbsPos(
const Tracks& aTracks, nscoord aGridOrigin,
nscoord* aPos, nscoord* aLength) const
{
// kAutoLine for abspos children contributes the corresponding edge
// of the grid container's padding-box.
if (mEnd == kAutoLine) {
if (mStart == kAutoLine) {
// done
} else {
const nscoord endPos = *aPos + *aLength;
auto side = mStart == aTracks.mSizes.Length() ? GridLineSide::eBeforeGridGap
: GridLineSide::eAfterGridGap;
nscoord startPos = aTracks.GridLineEdge(mStart, side);
*aPos = aGridOrigin + startPos;
*aLength = std::max(endPos - *aPos, 0);
}
} else {
if (mStart == kAutoLine) {
auto side = mEnd == 0 ? GridLineSide::eAfterGridGap
: GridLineSide::eBeforeGridGap;
nscoord endPos = aTracks.GridLineEdge(mEnd, side);
*aLength = std::max(aGridOrigin + endPos, 0);
} else {
nscoord pos;
ToPositionAndLength(aTracks.mSizes, &pos, aLength);
*aPos = aGridOrigin + pos;
}
}
}
LogicalRect
nsGridContainerFrame::GridReflowInput::ContainingBlockFor(const GridArea& aArea) const
{
nscoord i, b, iSize, bSize;
MOZ_ASSERT(aArea.mCols.Extent() > 0, "grid items cover at least one track");
MOZ_ASSERT(aArea.mRows.Extent() > 0, "grid items cover at least one track");
aArea.mCols.ToPositionAndLength(mCols.mSizes, &i, &iSize);
aArea.mRows.ToPositionAndLength(mRows.mSizes, &b, &bSize);
return LogicalRect(mWM, i, b, iSize, bSize);
}
LogicalRect
nsGridContainerFrame::GridReflowInput::ContainingBlockForAbsPos(
const GridArea& aArea,
const LogicalPoint& aGridOrigin,
const LogicalRect& aGridCB) const
{
nscoord i = aGridCB.IStart(mWM);
nscoord b = aGridCB.BStart(mWM);
nscoord iSize = aGridCB.ISize(mWM);
nscoord bSize = aGridCB.BSize(mWM);
aArea.mCols.ToPositionAndLengthForAbsPos(mCols, aGridOrigin.I(mWM),
&i, &iSize);
aArea.mRows.ToPositionAndLengthForAbsPos(mRows, aGridOrigin.B(mWM),
&b, &bSize);
return LogicalRect(mWM, i, b, iSize, bSize);
}
/**
* Return a Fragmentainer object if we have a fragmentainer frame in our
* ancestor chain of containing block (CB) reflow states. We'll only
* continue traversing the ancestor chain as long as the CBs have
* the same writing-mode and have overflow:visible.
*/
Maybe<nsGridContainerFrame::Fragmentainer>
nsGridContainerFrame::GetNearestFragmentainer(const GridReflowInput& aState) const
{
Maybe<nsGridContainerFrame::Fragmentainer> data;
const ReflowInput* gridRI = aState.mReflowInput;
if (gridRI->AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
return data;
}
WritingMode wm = aState.mWM;
const ReflowInput* cbRI = gridRI->mCBReflowInput;
for ( ; cbRI; cbRI = cbRI->mCBReflowInput) {
nsIScrollableFrame* sf = do_QueryFrame(cbRI->mFrame);
if (sf) {
break;
}
if (wm.IsOrthogonalTo(cbRI->GetWritingMode())) {
break;
}
nsIAtom* frameType = cbRI->mFrame->GetType();
if ((frameType == nsGkAtoms::canvasFrame &&
PresContext()->IsPaginated()) ||
frameType == nsGkAtoms::columnSetFrame) {
data.emplace();
data->mIsTopOfPage = gridRI->mFlags.mIsTopOfPage;
data->mToFragmentainerEnd = aState.mFragBStart +
gridRI->AvailableBSize() - aState.mBorderPadding.BStart(wm);
const auto numRows = aState.mRows.mSizes.Length();
data->mCanBreakAtStart =
numRows > 0 && aState.mRows.mSizes[0].mPosition > 0;
nscoord bSize = gridRI->ComputedBSize();
data->mIsAutoBSize = bSize == NS_AUTOHEIGHT;
if (data->mIsAutoBSize) {
bSize = gridRI->ComputedMinBSize();
} else {
bSize = NS_CSS_MINMAX(bSize,
gridRI->ComputedMinBSize(),
gridRI->ComputedMaxBSize());
}
nscoord gridEnd =
aState.mRows.GridLineEdge(numRows, GridLineSide::eBeforeGridGap);
data->mCanBreakAtEnd = bSize > gridEnd &&
bSize > aState.mFragBStart;
break;
}
}
return data;
}
void
nsGridContainerFrame::ReflowInFlowChild(nsIFrame* aChild,
const GridItemInfo* aGridItemInfo,
nsSize aContainerSize,
const Maybe<nscoord>& aStretchBSize,
const Fragmentainer* aFragmentainer,
const GridReflowInput& aState,
const LogicalRect& aContentArea,
ReflowOutput& aDesiredSize,
nsReflowStatus& aStatus)
{
nsPresContext* pc = PresContext();
nsStyleContext* containerSC = StyleContext();
WritingMode wm = aState.mReflowInput->GetWritingMode();
LogicalMargin pad(aState.mReflowInput->ComputedLogicalPadding());
const LogicalPoint padStart(wm, pad.IStart(wm), pad.BStart(wm));
const bool isGridItem = !!aGridItemInfo;
auto childType = aChild->GetType();
MOZ_ASSERT(isGridItem == (childType != nsGkAtoms::placeholderFrame));
LogicalRect cb(wm);
WritingMode childWM = aChild->GetWritingMode();
bool isConstrainedBSize = false;
nscoord toFragmentainerEnd;
// The part of the child's grid area that's in previous container fragments.
nscoord consumedGridAreaBSize = 0;
const bool isOrthogonal = wm.IsOrthogonalTo(childWM);
if (MOZ_LIKELY(isGridItem)) {
MOZ_ASSERT(aGridItemInfo->mFrame == aChild);
const GridArea& area = aGridItemInfo->mArea;
MOZ_ASSERT(area.IsDefinite());
cb = aState.ContainingBlockFor(area);
isConstrainedBSize = aFragmentainer && !wm.IsOrthogonalTo(childWM);
if (isConstrainedBSize) {
// |gridAreaBOffset| is the offset of the child's grid area in this
// container fragment (if negative, that distance is the child CB size
// consumed in previous container fragments). Note that cb.BStart
// (initially) and aState.mFragBStart are in "global" grid coordinates
// (like all track positions).
nscoord gridAreaBOffset = cb.BStart(wm) - aState.mFragBStart;
consumedGridAreaBSize = std::max(0, -gridAreaBOffset);
cb.BStart(wm) = std::max(0, gridAreaBOffset);
toFragmentainerEnd = aFragmentainer->mToFragmentainerEnd -
aState.mFragBStart - cb.BStart(wm);
toFragmentainerEnd = std::max(toFragmentainerEnd, 0);
}
cb += aContentArea.Origin(wm);
aState.mRows.AlignBaselineSubtree(*aGridItemInfo);
aState.mCols.AlignBaselineSubtree(*aGridItemInfo);
// Setup [align|justify]-content:[last ]baseline related frame properties.
// These are added to the padding in SizeComputationInput::InitOffsets.
// (a negative value signals the value is for 'last baseline' and should be
// added to the (logical) end padding)
typedef const FramePropertyDescriptor<SmallValueHolder<nscoord>>* Prop;
auto SetProp = [aGridItemInfo, aChild] (LogicalAxis aGridAxis,
Prop aProp) {
auto state = aGridItemInfo->mState[aGridAxis];
auto baselineAdjust = (state & ItemState::eContentBaseline) ?
aGridItemInfo->mBaselineOffset[aGridAxis] : nscoord(0);
if (baselineAdjust < nscoord(0)) {
// This happens when the subtree overflows its track.
// XXX spec issue? it's unclear how to handle this.
baselineAdjust = nscoord(0);
} else if (baselineAdjust > nscoord(0) &&
(state & ItemState::eLastBaseline)) {
baselineAdjust = -baselineAdjust;
}
if (baselineAdjust != nscoord(0)) {
aChild->Properties().Set(aProp, baselineAdjust);
} else {
aChild->Properties().Delete(aProp);
}
};
SetProp(eLogicalAxisBlock, isOrthogonal ? IBaselinePadProperty() :
BBaselinePadProperty());
SetProp(eLogicalAxisInline, isOrthogonal ? BBaselinePadProperty() :
IBaselinePadProperty());
} else {
// By convention, for frames that perform CSS Box Alignment, we position
// placeholder children at the start corner of their alignment container,
// and in this case that's usually the grid's padding box.
// ("Usually" - the exception is when the grid *also* forms the
// abs.pos. containing block. In that case, the alignment container isn't
// the padding box -- it's some grid area instead. But that case doesn't
// require any special handling here, because we handle it later using a
// special flag (STATIC_POS_IS_CB_ORIGIN) which will make us ignore the
// placeholder's position entirely.)
cb = aContentArea - padStart;
aChild->AddStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN);
}
LogicalSize reflowSize(cb.Size(wm));
if (isConstrainedBSize) {
reflowSize.BSize(wm) = toFragmentainerEnd;
}
LogicalSize childCBSize = reflowSize.ConvertTo(childWM, wm);
// Setup the ClampMarginBoxMinSize reflow flags and property, if needed.
uint32_t flags = 0;
if (aGridItemInfo) {
auto childIAxis = isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline;
if (aGridItemInfo->mState[childIAxis] & ItemState::eClampMarginBoxMinSize) {
flags |= ReflowInput::I_CLAMP_MARGIN_BOX_MIN_SIZE;
}
auto childBAxis = GetOrthogonalAxis(childIAxis);
if (aGridItemInfo->mState[childBAxis] & ItemState::eClampMarginBoxMinSize) {
flags |= ReflowInput::B_CLAMP_MARGIN_BOX_MIN_SIZE;
aChild->Properties().Set(BClampMarginBoxMinSizeProperty(),
childCBSize.BSize(childWM));
} else {
aChild->Properties().Delete(BClampMarginBoxMinSizeProperty());
}
}
if (!isConstrainedBSize) {
childCBSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE;
}
LogicalSize percentBasis(cb.Size(wm).ConvertTo(childWM, wm));
ReflowInput childRI(pc, *aState.mReflowInput, aChild, childCBSize,
&percentBasis, flags);
childRI.mFlags.mIsTopOfPage = aFragmentainer ? aFragmentainer->mIsTopOfPage : false;
// A table-wrapper needs to propagate the CB size we give it to its
// inner table frame later. @see nsTableWrapperFrame::InitChildReflowInput.
if (childType == nsGkAtoms::tableWrapperFrame) {
const auto& props = aChild->Properties();
LogicalSize* cb = props.Get(nsTableWrapperFrame::GridItemCBSizeProperty());
if (!cb) {
cb = new LogicalSize(childWM);
props.Set(nsTableWrapperFrame::GridItemCBSizeProperty(), cb);
}
*cb = percentBasis;
}
// If the child is stretching in its block axis, and we might be fragmenting
// it in that axis, then setup a frame property to tell
// nsBlockFrame::ComputeFinalSize the size.
if (isConstrainedBSize && !wm.IsOrthogonalTo(childWM)) {
bool stretch = false;
if (!childRI.mStyleMargin->HasBlockAxisAuto(childWM) &&
childRI.mStylePosition->BSize(childWM).GetUnit() == eStyleUnit_Auto) {
auto blockAxisAlignment =
childRI.mStylePosition->UsedAlignSelf(StyleContext());
if (blockAxisAlignment == NS_STYLE_ALIGN_NORMAL ||
blockAxisAlignment == NS_STYLE_ALIGN_STRETCH) {
stretch = true;
}
}
if (stretch) {
aChild->Properties().Set(FragStretchBSizeProperty(), *aStretchBSize);
} else {
aChild->Properties().Delete(FragStretchBSizeProperty());
}
}
// We need the width of the child before we can correctly convert
// the writing-mode of its origin, so we reflow at (0, 0) using a dummy
// aContainerSize, and then pass the correct position to FinishReflowChild.
ReflowOutput childSize(childRI);
const nsSize dummyContainerSize;
ReflowChild(aChild, pc, childSize, childRI, childWM, LogicalPoint(childWM),
dummyContainerSize, 0, aStatus);
LogicalPoint childPos =
cb.Origin(wm).ConvertTo(childWM, wm,
aContainerSize - childSize.PhysicalSize());
// Apply align/justify-self and reflow again if that affects the size.
if (MOZ_LIKELY(isGridItem)) {
LogicalSize size = childSize.Size(childWM); // from the ReflowChild()
if (aStatus.IsComplete()) {
auto align = childRI.mStylePosition->UsedAlignSelf(containerSC);
auto state = aGridItemInfo->mState[eLogicalAxisBlock];
if (state & ItemState::eContentBaseline) {
align = (state & ItemState::eFirstBaseline) ? NS_STYLE_ALIGN_SELF_START
: NS_STYLE_ALIGN_SELF_END;
}
nscoord cbsz = cb.BSize(wm) - consumedGridAreaBSize;
AlignSelf(*aGridItemInfo, align, cbsz, wm, childRI, size, &childPos);
}
auto justify = childRI.mStylePosition->UsedJustifySelf(containerSC);
auto state = aGridItemInfo->mState[eLogicalAxisInline];
if (state & ItemState::eContentBaseline) {
justify = (state & ItemState::eFirstBaseline) ? NS_STYLE_JUSTIFY_SELF_START
: NS_STYLE_JUSTIFY_SELF_END;
}
nscoord cbsz = cb.ISize(wm);
JustifySelf(*aGridItemInfo, justify, cbsz, wm, childRI, size, &childPos);
} // else, nsAbsoluteContainingBlock.cpp will handle align/justify-self.
childRI.ApplyRelativePositioning(&childPos, aContainerSize);
FinishReflowChild(aChild, pc, childSize, &childRI, childWM, childPos,
aContainerSize, 0);
ConsiderChildOverflow(aDesiredSize.mOverflowAreas, aChild);
}
nscoord
nsGridContainerFrame::ReflowInFragmentainer(GridReflowInput& aState,
const LogicalRect& aContentArea,
ReflowOutput& aDesiredSize,
nsReflowStatus& aStatus,
Fragmentainer& aFragmentainer,
const nsSize& aContainerSize)
{
MOZ_ASSERT(aStatus.IsEmpty());
MOZ_ASSERT(aState.mReflowInput);
// Collect our grid items and sort them in row order. Collect placeholders
// and put them in a separate array.
nsTArray<const GridItemInfo*> sortedItems(aState.mGridItems.Length());
nsTArray<nsIFrame*> placeholders(aState.mAbsPosItems.Length());
aState.mIter.Reset(GridItemCSSOrderIterator::eIncludeAll);
for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
nsIFrame* child = *aState.mIter;
if (child->GetType() != nsGkAtoms::placeholderFrame) {
const GridItemInfo* info = &aState.mGridItems[aState.mIter.GridItemIndex()];
sortedItems.AppendElement(info);
} else {
placeholders.AppendElement(child);
}
}
// NOTE: no need to use stable_sort here, there are no dependencies on
// having content order between items on the same row in the code below.
std::sort(sortedItems.begin(), sortedItems.end(),
GridItemInfo::IsStartRowLessThan);
// Reflow our placeholder children; they must all be complete.
for (auto child : placeholders) {
nsReflowStatus childStatus;
ReflowInFlowChild(child, nullptr, aContainerSize, Nothing(), &aFragmentainer,
aState, aContentArea, aDesiredSize, childStatus);
MOZ_ASSERT(childStatus.IsComplete(),
"nsPlaceholderFrame should never need to be fragmented");
}
// The available size for children - we'll set this to the edge of the last
// row in most cases below, but for now use the full size.
nscoord childAvailableSize = aFragmentainer.mToFragmentainerEnd;
const uint32_t startRow = aState.mStartRow;
const uint32_t numRows = aState.mRows.mSizes.Length();
bool isBDBClone = aState.mReflowInput->mStyleBorder->mBoxDecorationBreak ==
StyleBoxDecorationBreak::Clone;
nscoord bpBEnd = aState.mBorderPadding.BEnd(aState.mWM);
// Set |endRow| to the first row that doesn't fit.
uint32_t endRow = numRows;
for (uint32_t row = startRow; row < numRows; ++row) {
auto& sz = aState.mRows.mSizes[row];
const nscoord bEnd = sz.mPosition + sz.mBase;
nscoord remainingAvailableSize = childAvailableSize - bEnd;
if (remainingAvailableSize < 0 ||
(isBDBClone && remainingAvailableSize < bpBEnd)) {
endRow = row;
break;
}
}
// Check for forced breaks on the items.
const bool isTopOfPage = aFragmentainer.mIsTopOfPage;
bool isForcedBreak = false;
const bool avoidBreakInside = ShouldAvoidBreakInside(*aState.mReflowInput);
for (const GridItemInfo* info : sortedItems) {
uint32_t itemStartRow = info->mArea.mRows.mStart;
if (itemStartRow == endRow) {
break;
}
auto disp = info->mFrame->StyleDisplay();
if (disp->mBreakBefore) {
// Propagate break-before on the first row to the container unless we're
// already at top-of-page.
if ((itemStartRow == 0 && !isTopOfPage) || avoidBreakInside) {
aStatus.SetInlineLineBreakBeforeAndReset();
return aState.mFragBStart;
}
if ((itemStartRow > startRow ||
(itemStartRow == startRow && !isTopOfPage)) &&
itemStartRow < endRow) {
endRow = itemStartRow;
isForcedBreak = true;
// reset any BREAK_AFTER we found on an earlier item
aStatus.Reset();
break; // we're done since the items are sorted in row order
}
}
uint32_t itemEndRow = info->mArea.mRows.mEnd;
if (disp->mBreakAfter) {
if (itemEndRow != numRows) {
if (itemEndRow > startRow && itemEndRow < endRow) {
endRow = itemEndRow;
isForcedBreak = true;
// No "break;" here since later items with break-after may have
// a shorter span.
}
} else {
// Propagate break-after on the last row to the container, we may still
// find a break-before on this row though (and reset aStatus).
aStatus.SetInlineLineBreakAfter(); // tentative
}
}
}
// Consume at least one row in each fragment until we have consumed them all.
// Except for the first row if there's a break opportunity before it.
if (startRow == endRow && startRow != numRows &&
(startRow != 0 || !aFragmentainer.mCanBreakAtStart)) {
++endRow;
}
// Honor break-inside:avoid if we can't fit all rows.
if (avoidBreakInside && endRow < numRows) {
aStatus.SetInlineLineBreakBeforeAndReset();
return aState.mFragBStart;
}
// Calculate the block-size including this fragment.
nscoord bEndRow =
aState.mRows.GridLineEdge(endRow, GridLineSide::eBeforeGridGap);
nscoord bSize;
if (aFragmentainer.mIsAutoBSize) {
// We only apply min-bsize once all rows are complete (when bsize is auto).
if (endRow < numRows) {
bSize = bEndRow;
auto clampedBSize = ClampToCSSMaxBSize(bSize, aState.mReflowInput);
if (MOZ_UNLIKELY(clampedBSize != bSize)) {
// We apply max-bsize in all fragments though.
bSize = clampedBSize;
} else if (!isBDBClone) {
// The max-bsize won't make this fragment COMPLETE, so the block-end
// border will be in a later fragment.
bpBEnd = 0;
}
} else {
bSize = NS_CSS_MINMAX(bEndRow,
aState.mReflowInput->ComputedMinBSize(),
aState.mReflowInput->ComputedMaxBSize());
}
} else {
bSize = NS_CSS_MINMAX(aState.mReflowInput->ComputedBSize(),
aState.mReflowInput->ComputedMinBSize(),
aState.mReflowInput->ComputedMaxBSize());
}
// Check for overflow and set aStatus INCOMPLETE if so.
bool overflow = bSize + bpBEnd > childAvailableSize;
if (overflow) {
if (avoidBreakInside) {
aStatus.SetInlineLineBreakBeforeAndReset();
return aState.mFragBStart;
}
bool breakAfterLastRow = endRow == numRows && aFragmentainer.mCanBreakAtEnd;
if (breakAfterLastRow) {
MOZ_ASSERT(bEndRow < bSize, "bogus aFragmentainer.mCanBreakAtEnd");
nscoord availableSize = childAvailableSize;
if (isBDBClone) {
availableSize -= bpBEnd;
}
// Pretend we have at least 1px available size, otherwise we'll never make
// progress in consuming our bSize.
availableSize = std::max(availableSize,
aState.mFragBStart + AppUnitsPerCSSPixel());
// Fill the fragmentainer, but not more than our desired block-size and
// at least to the size of the last row (even if that overflows).
nscoord newBSize = std::min(bSize, availableSize);
newBSize = std::max(newBSize, bEndRow);
// If it's just the border+padding that is overflowing and we have
// box-decoration-break:clone then we are technically COMPLETE. There's
// no point in creating another zero-bsize fragment in this case.
if (newBSize < bSize || !isBDBClone) {
aStatus.SetIncomplete();
}
bSize = newBSize;
} else if (bSize <= bEndRow && startRow + 1 < endRow) {
if (endRow == numRows) {
// We have more than one row in this fragment, so we can break before
// the last row instead.
--endRow;
bEndRow = aState.mRows.GridLineEdge(endRow, GridLineSide::eBeforeGridGap);
bSize = bEndRow;
if (aFragmentainer.mIsAutoBSize) {
bSize = ClampToCSSMaxBSize(bSize, aState.mReflowInput);
}
}
aStatus.SetIncomplete();
} else if (endRow < numRows) {
bSize = ClampToCSSMaxBSize(bEndRow, aState.mReflowInput, &aStatus);
} // else - no break opportunities.
} else {
// Even though our block-size fits we need to honor forced breaks, or if
// a row doesn't fit in an auto-sized container (unless it's constrained
// by a max-bsize which make us overflow-incomplete).
if (endRow < numRows && (isForcedBreak ||
(aFragmentainer.mIsAutoBSize && bEndRow == bSize))) {
bSize = ClampToCSSMaxBSize(bEndRow, aState.mReflowInput, &aStatus);
}
}
// If we can't fit all rows then we're at least overflow-incomplete.
if (endRow < numRows) {
childAvailableSize = bEndRow;
if (aStatus.IsComplete()) {
aStatus.SetOverflowIncomplete();
aStatus.SetNextInFlowNeedsReflow();
}
} else {
// Children always have the full size of the rows in this fragment.
childAvailableSize = std::max(childAvailableSize, bEndRow);
}
return ReflowRowsInFragmentainer(aState, aContentArea, aDesiredSize, aStatus,
aFragmentainer, aContainerSize, sortedItems,
startRow, endRow, bSize, childAvailableSize);
}
nscoord
nsGridContainerFrame::ReflowRowsInFragmentainer(
GridReflowInput& aState,
const LogicalRect& aContentArea,
ReflowOutput& aDesiredSize,
nsReflowStatus& aStatus,
Fragmentainer& aFragmentainer,
const nsSize& aContainerSize,
const nsTArray<const GridItemInfo*>& aSortedItems,
uint32_t aStartRow,
uint32_t aEndRow,
nscoord aBSize,
nscoord aAvailableSize)
{
FrameHashtable pushedItems;
FrameHashtable incompleteItems;
FrameHashtable overflowIncompleteItems;
bool isBDBClone = aState.mReflowInput->mStyleBorder->mBoxDecorationBreak ==
StyleBoxDecorationBreak::Clone;
bool didGrowRow = false;
// As we walk across rows, we track whether the current row is at the top
// of its grid-fragment, to help decide whether we can break before it. When
// this function starts, our row is at the top of the current fragment if:
// - we're starting with a nonzero row (i.e. we're a continuation)
// OR:
// - we're starting with the first row, & we're not allowed to break before
// it (which makes it effectively at the top of its grid-fragment).
bool isRowTopOfPage = aStartRow != 0 || !aFragmentainer.mCanBreakAtStart;
const bool isStartRowTopOfPage = isRowTopOfPage;
// Save our full available size for later.
const nscoord gridAvailableSize = aFragmentainer.mToFragmentainerEnd;
// Propagate the constrained size to our children.
aFragmentainer.mToFragmentainerEnd = aAvailableSize;
// Reflow the items in row order up to |aEndRow| and push items after that.
uint32_t row = 0;
// |i| is intentionally signed, so we can set it to -1 to restart the loop.
for (int32_t i = 0, len = aSortedItems.Length(); i < len; ++i) {
const GridItemInfo* const info = aSortedItems[i];
nsIFrame* child = info->mFrame;
row = info->mArea.mRows.mStart;
MOZ_ASSERT(child->GetPrevInFlow() ? row < aStartRow : row >= aStartRow,
"unexpected child start row");
if (row >= aEndRow) {
pushedItems.PutEntry(child);
continue;
}
bool rowCanGrow = false;
nscoord maxRowSize = 0;
if (row >= aStartRow) {
if (row > aStartRow) {
isRowTopOfPage = false;
}
// Can we grow this row? Only consider span=1 items per spec...
rowCanGrow = !didGrowRow && info->mArea.mRows.Extent() == 1;
if (rowCanGrow) {
auto& sz = aState.mRows.mSizes[row];
// and only min-/max-content rows or flex rows in an auto-sized container
rowCanGrow = (sz.mState & TrackSize::eMinOrMaxContentMinSizing) ||
((sz.mState & TrackSize::eFlexMaxSizing) &&
aFragmentainer.mIsAutoBSize);
if (rowCanGrow) {
if (isBDBClone) {
maxRowSize = gridAvailableSize -
aState.mBorderPadding.BEnd(aState.mWM);
} else {
maxRowSize = gridAvailableSize;
}
maxRowSize -= sz.mPosition;
// ...and only if there is space for it to grow.
rowCanGrow = maxRowSize > sz.mBase;
}
}
}
// aFragmentainer.mIsTopOfPage is propagated to the child reflow state.
// When it's false the child can request BREAK_BEFORE. We intentionally
// set it to false when the row is growable (as determined in CSS Grid
// Fragmentation) and there is a non-zero space between it and the
// fragmentainer end (that can be used to grow it). If the child reports
// a forced break in this case, we grow this row to fill the fragment and
// restart the loop. We also restart the loop with |aEndRow = row|
// (but without growing any row) for a BREAK_BEFORE child if it spans
// beyond the last row in this fragment. This is to avoid fragmenting it.
// We only restart the loop once.
aFragmentainer.mIsTopOfPage = isRowTopOfPage && !rowCanGrow;
nsReflowStatus childStatus;
// Pass along how much to stretch this fragment, in case it's needed.
nscoord bSize =
aState.mRows.GridLineEdge(std::min(aEndRow, info->mArea.mRows.mEnd),
GridLineSide::eBeforeGridGap) -
aState.mRows.GridLineEdge(std::max(aStartRow, row),
GridLineSide::eAfterGridGap);
ReflowInFlowChild(child, info, aContainerSize, Some(bSize), &aFragmentainer,
aState, aContentArea, aDesiredSize, childStatus);
MOZ_ASSERT(childStatus.IsInlineBreakBefore() ||
!childStatus.IsFullyComplete() ||
!child->GetNextInFlow(),
"fully-complete reflow should destroy any NIFs");
if (childStatus.IsInlineBreakBefore()) {
MOZ_ASSERT(!child->GetPrevInFlow(),
"continuations should never report BREAK_BEFORE status");
MOZ_ASSERT(!aFragmentainer.mIsTopOfPage,
"got IsInlineBreakBefore() at top of page");
if (!didGrowRow) {
if (rowCanGrow) {
// Grow this row and restart with the next row as |aEndRow|.
aState.mRows.ResizeRow(row, maxRowSize);
if (aState.mSharedGridData) {
aState.mSharedGridData->mRows.ResizeRow(row, maxRowSize);
}
didGrowRow = true;
aEndRow = row + 1; // growing this row makes the next one not fit
i = -1; // i == 0 after the next loop increment
isRowTopOfPage = isStartRowTopOfPage;
overflowIncompleteItems.Clear();
incompleteItems.Clear();
nscoord bEndRow =
aState.mRows.GridLineEdge(aEndRow, GridLineSide::eBeforeGridGap);
aFragmentainer.mToFragmentainerEnd = bEndRow;
if (aFragmentainer.mIsAutoBSize) {
aBSize = ClampToCSSMaxBSize(bEndRow, aState.mReflowInput, &aStatus);
} else if (aStatus.IsIncomplete()) {
aBSize = NS_CSS_MINMAX(aState.mReflowInput->ComputedBSize(),
aState.mReflowInput->ComputedMinBSize(),
aState.mReflowInput->ComputedMaxBSize());
aBSize = std::min(bEndRow, aBSize);
}
continue;
}
if (!isRowTopOfPage) {
// We can break before this row - restart with it as the new end row.
aEndRow = row;
aBSize = aState.mRows.GridLineEdge(aEndRow, GridLineSide::eBeforeGridGap);
i = -1; // i == 0 after the next loop increment
isRowTopOfPage = isStartRowTopOfPage;
overflowIncompleteItems.Clear();
incompleteItems.Clear();
aStatus.SetIncomplete();
continue;
}
NS_ERROR("got BREAK_BEFORE at top-of-page");
childStatus.Reset();
} else {
NS_ERROR("got BREAK_BEFORE again after growing the row?");
childStatus.SetIncomplete();
}
} else if (childStatus.IsInlineBreakAfter()) {
MOZ_ASSERT_UNREACHABLE("unexpected child reflow status");
}
if (childStatus.IsIncomplete()) {
incompleteItems.PutEntry(child);
} else if (!childStatus.IsFullyComplete()) {
overflowIncompleteItems.PutEntry(child);
}
}
// Record a break before |aEndRow|.
aState.mNextFragmentStartRow = aEndRow;
if (aEndRow < aState.mRows.mSizes.Length()) {
aState.mRows.BreakBeforeRow(aEndRow);
if (aState.mSharedGridData) {
aState.mSharedGridData->mRows.BreakBeforeRow(aEndRow);
}
}
if (!pushedItems.IsEmpty() ||
!incompleteItems.IsEmpty() ||
!overflowIncompleteItems.IsEmpty()) {
if (aStatus.IsComplete()) {
aStatus.SetOverflowIncomplete();
aStatus.SetNextInFlowNeedsReflow();
}
// Iterate the children in normal document order and append them (or a NIF)
// to one of the following frame lists according to their status.
nsFrameList pushedList;
nsFrameList incompleteList;
nsFrameList overflowIncompleteList;
auto* pc = PresContext();
auto* fc = pc->PresShell()->FrameConstructor();
for (nsIFrame* child = GetChildList(kPrincipalList).FirstChild(); child; ) {
MOZ_ASSERT((pushedItems.Contains(child) ? 1 : 0) +
(incompleteItems.Contains(child) ? 1 : 0) +
(overflowIncompleteItems.Contains(child) ? 1 : 0) <= 1,
"child should only be in one of these sets");
// Save the next-sibling so we can continue the loop if |child| is moved.
nsIFrame* next = child->GetNextSibling();
if (pushedItems.Contains(child)) {
MOZ_ASSERT(child->GetParent() == this);
StealFrame(child);
pushedList.AppendFrame(nullptr, child);
} else if (incompleteItems.Contains(child)) {
nsIFrame* childNIF = child->GetNextInFlow();
if (!childNIF) {
childNIF = fc->CreateContinuingFrame(pc, child, this);
incompleteList.AppendFrame(nullptr, childNIF);
} else {
auto parent = static_cast<nsGridContainerFrame*>(childNIF->GetParent());
MOZ_ASSERT(parent != this || !mFrames.ContainsFrame(childNIF),
"child's NIF shouldn't be in the same principal list");
// If child's existing NIF is an overflow container, convert it to an
// actual NIF, since now |child| has non-overflow stuff to give it.
// Or, if it's further away then our next-in-flow, then pull it up.
if ((childNIF->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) ||
(parent != this && parent != GetNextInFlow())) {
parent->StealFrame(childNIF);
childNIF->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
if (parent == this) {
incompleteList.AppendFrame(nullptr, childNIF);
} else {
// If childNIF already lives on the next grid fragment, then we
// don't need to reparent it, since we know it's destined to end
// up there anyway. Just move it to its parent's overflow list.
if (parent == GetNextInFlow()) {
nsFrameList toMove(childNIF, childNIF);
parent->MergeSortedOverflow(toMove);
} else {
ReparentFrame(childNIF, parent, this);
incompleteList.AppendFrame(nullptr, childNIF);
}
}
}
}
} else if (overflowIncompleteItems.Contains(child)) {
nsIFrame* childNIF = child->GetNextInFlow();
if (!childNIF) {
childNIF = fc->CreateContinuingFrame(pc, child, this);
childNIF->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
overflowIncompleteList.AppendFrame(nullptr, childNIF);
} else {
DebugOnly<nsGridContainerFrame*> lastParent = this;
auto nif = static_cast<nsGridContainerFrame*>(GetNextInFlow());
// If child has any non-overflow-container NIFs, convert them to
// overflow containers, since that's all |child| needs now.
while (childNIF &&
!childNIF->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
auto parent = static_cast<nsGridContainerFrame*>(childNIF->GetParent());
parent->StealFrame(childNIF);
childNIF->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
if (parent == this) {
overflowIncompleteList.AppendFrame(nullptr, childNIF);
} else {
if (!nif || parent == nif) {
nsFrameList toMove(childNIF, childNIF);
parent->MergeSortedExcessOverflowContainers(toMove);
} else {
ReparentFrame(childNIF, parent, nif);
nsFrameList toMove(childNIF, childNIF);
nif->MergeSortedExcessOverflowContainers(toMove);
}
// We only need to reparent the first childNIF (or not at all if
// its parent is our NIF).
nif = nullptr;
}
lastParent = parent;
childNIF = childNIF->GetNextInFlow();
}
}
}
child = next;
}
// Merge the results into our respective overflow child lists.
if (!pushedList.IsEmpty()) {
MergeSortedOverflow(pushedList);
AddStateBits(NS_STATE_GRID_DID_PUSH_ITEMS);
// NOTE since we messed with our child list here, we intentionally
// make aState.mIter invalid to avoid any use of it after this point.
aState.mIter.Invalidate();
}
if (!incompleteList.IsEmpty()) {
MergeSortedOverflow(incompleteList);
// NOTE since we messed with our child list here, we intentionally
// make aState.mIter invalid to avoid any use of it after this point.
aState.mIter.Invalidate();
}
if (!overflowIncompleteList.IsEmpty()) {
MergeSortedExcessOverflowContainers(overflowIncompleteList);
}
}
return aBSize;
}
nscoord
nsGridContainerFrame::ReflowChildren(GridReflowInput& aState,
const LogicalRect& aContentArea,
ReflowOutput& aDesiredSize,
nsReflowStatus& aStatus)
{
MOZ_ASSERT(aState.mReflowInput);
aStatus.Reset();
nsOverflowAreas ocBounds;
nsReflowStatus ocStatus;
if (GetPrevInFlow()) {
ReflowOverflowContainerChildren(PresContext(), *aState.mReflowInput,
ocBounds, 0, ocStatus,
MergeSortedFrameListsFor);
}
WritingMode wm = aState.mReflowInput->GetWritingMode();
const nsSize containerSize =
(aContentArea.Size(wm) + aState.mBorderPadding.Size(wm)).GetPhysicalSize(wm);
nscoord bSize = aContentArea.BSize(wm);
Maybe<Fragmentainer> fragmentainer = GetNearestFragmentainer(aState);
if (MOZ_UNLIKELY(fragmentainer.isSome())) {
aState.mInFragmentainer = true;
bSize = ReflowInFragmentainer(aState, aContentArea, aDesiredSize, aStatus,
*fragmentainer, containerSize);
} else {
aState.mIter.Reset(GridItemCSSOrderIterator::eIncludeAll);
for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
nsIFrame* child = *aState.mIter;
const GridItemInfo* info = nullptr;
if (child->GetType() != nsGkAtoms::placeholderFrame) {
info = &aState.mGridItems[aState.mIter.GridItemIndex()];
}
ReflowInFlowChild(*aState.mIter, info, containerSize, Nothing(), nullptr,
aState, aContentArea, aDesiredSize, aStatus);
MOZ_ASSERT(aStatus.IsComplete(), "child should be complete "
"in unconstrained reflow");
}
}
// Merge overflow container bounds and status.
aDesiredSize.mOverflowAreas.UnionWith(ocBounds);
aStatus.MergeCompletionStatusFrom(ocStatus);
if (IsAbsoluteContainer()) {
nsFrameList children(GetChildList(GetAbsoluteListID()));
if (!children.IsEmpty()) {
// 'gridOrigin' is the origin of the grid (the start of the first track),
// with respect to the grid container's padding-box (CB).
LogicalMargin pad(aState.mReflowInput->ComputedLogicalPadding());
const LogicalPoint gridOrigin(wm, pad.IStart(wm), pad.BStart(wm));
const LogicalRect gridCB(wm, 0, 0,
aContentArea.ISize(wm) + pad.IStartEnd(wm),
bSize + pad.BStartEnd(wm));
const nsSize gridCBPhysicalSize = gridCB.Size(wm).GetPhysicalSize(wm);
size_t i = 0;
for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next(), ++i) {
nsIFrame* child = e.get();
MOZ_ASSERT(i < aState.mAbsPosItems.Length());
MOZ_ASSERT(aState.mAbsPosItems[i].mFrame == child);
GridArea& area = aState.mAbsPosItems[i].mArea;
LogicalRect itemCB =
aState.ContainingBlockForAbsPos(area, gridOrigin, gridCB);
// nsAbsoluteContainingBlock::Reflow uses physical coordinates.
nsRect* cb = child->Properties().Get(GridItemContainingBlockRect());
if (!cb) {
cb = new nsRect;
child->Properties().Set(GridItemContainingBlockRect(), cb);
}
*cb = itemCB.GetPhysicalRect(wm, gridCBPhysicalSize);
}
// We pass a dummy rect as CB because each child has its own CB rect.
// The eIsGridContainerCB flag tells nsAbsoluteContainingBlock::Reflow to
// use those instead.
nsRect dummyRect;
AbsPosReflowFlags flags =
AbsPosReflowFlags::eCBWidthAndHeightChanged; // XXX could be optimized
flags |= AbsPosReflowFlags::eConstrainHeight;
flags |= AbsPosReflowFlags::eIsGridContainerCB;
GetAbsoluteContainingBlock()->Reflow(this, PresContext(),
*aState.mReflowInput,
aStatus, dummyRect, flags,
&aDesiredSize.mOverflowAreas);
}
}
return bSize;
}
void
nsGridContainerFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus)
{
MarkInReflow();
DO_GLOBAL_REFLOW_COUNT("nsGridContainerFrame");
DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
if (IsFrameTreeTooDeep(aReflowInput, aDesiredSize, aStatus)) {
return;
}
// First we gather child frames we should include in our reflow,
// i.e. overflowed children from our prev-in-flow, and pushed first-in-flow
// children (that might now fit). It's important to note that these children
// can be in arbitrary order vis-a-vis the current children in our lists.
// E.g. grid items in the document order: A, B, C may be placed in the rows
// 3, 2, 1. Assume each row goes in a separate grid container fragment,
// and we reflow the second fragment. Now if C (in fragment 1) overflows,
// we can't just prepend it to our mFrames like we usually do because that
// would violate the document order invariant that other code depends on.
// Similarly if we pull up child A (from fragment 3) we can't just append
// that for the same reason. Instead, we must sort these children into
// our child lists. (The sorting is trivial given that both lists are
// already fully sorted individually - it's just a merge.)
//
// The invariants that we maintain are that each grid container child list
// is sorted in the normal document order at all times, but that children
// in different grid container continuations may be in arbitrary order.
auto prevInFlow = static_cast<nsGridContainerFrame*>(GetPrevInFlow());
// Merge overflow frames from our prev-in-flow into our principal child list.
if (prevInFlow) {
AutoFrameListPtr overflow(aPresContext,
prevInFlow->StealOverflowFrames());
if (overflow) {
ReparentFrames(*overflow, prevInFlow, this);
::MergeSortedFrameLists(mFrames, *overflow, GetContent());
// Move trailing next-in-flows into our overflow list.
nsFrameList continuations;
for (nsIFrame* f = mFrames.FirstChild(); f; ) {
nsIFrame* next = f->GetNextSibling();
nsIFrame* pif = f->GetPrevInFlow();
if (pif && pif->GetParent() == this) {
mFrames.RemoveFrame(f);
continuations.AppendFrame(nullptr, f);
}
f = next;
}
MergeSortedOverflow(continuations);
// Move trailing OC next-in-flows into our excess overflow containers list.
nsFrameList* overflowContainers =
GetPropTableFrames(OverflowContainersProperty());
if (overflowContainers) {
nsFrameList moveToEOC;
for (nsIFrame* f = overflowContainers->FirstChild(); f; ) {
nsIFrame* next = f->GetNextSibling();
nsIFrame* pif = f->GetPrevInFlow();
if (pif && pif->GetParent() == this) {
overflowContainers->RemoveFrame(f);
moveToEOC.AppendFrame(nullptr, f);
}
f = next;
}
if (overflowContainers->IsEmpty()) {
Properties().Delete(OverflowContainersProperty());
}
MergeSortedExcessOverflowContainers(moveToEOC);
}
}
}
// Merge our own overflow frames into our principal child list,
// except those that are a next-in-flow for one of our items.
DebugOnly<bool> foundOwnPushedChild = false;
{
nsFrameList* ourOverflow = GetOverflowFrames();
if (ourOverflow) {
nsFrameList items;
for (nsIFrame* f = ourOverflow->FirstChild(); f; ) {
nsIFrame* next = f->GetNextSibling();
nsIFrame* pif = f->GetPrevInFlow();
if (!pif || pif->GetParent() != this) {
MOZ_ASSERT(f->GetParent() == this);
ourOverflow->RemoveFrame(f);
items.AppendFrame(nullptr, f);
if (!pif) {
foundOwnPushedChild = true;
}
}
f = next;
}
::MergeSortedFrameLists(mFrames, items, GetContent());
if (ourOverflow->IsEmpty()) {
DestroyOverflowList();
}
}
}
// Pull up any first-in-flow children we might have pushed.
if (HasAnyStateBits(NS_STATE_GRID_DID_PUSH_ITEMS)) {
RemoveStateBits(NS_STATE_GRID_DID_PUSH_ITEMS);
nsFrameList items;
auto nif = static_cast<nsGridContainerFrame*>(GetNextInFlow());
auto firstNIF = nif;
DebugOnly<bool> nifNeedPushedItem = false;
while (nif) {
nsFrameList nifItems;
for (nsIFrame* nifChild = nif->GetChildList(kPrincipalList).FirstChild();
nifChild; ) {
nsIFrame* next = nifChild->GetNextSibling();
if (!nifChild->GetPrevInFlow()) {
nif->StealFrame(nifChild);
ReparentFrame(nifChild, nif, this);
nifItems.AppendFrame(nullptr, nifChild);
nifNeedPushedItem = false;
}
nifChild = next;
}
::MergeSortedFrameLists(items, nifItems, GetContent());
if (!nif->HasAnyStateBits(NS_STATE_GRID_DID_PUSH_ITEMS)) {
MOZ_ASSERT(!nifNeedPushedItem || mDidPushItemsBitMayLie,
"NS_STATE_GRID_DID_PUSH_ITEMS lied");
break;
}
nifNeedPushedItem = true;
for (nsIFrame* nifChild = nif->GetChildList(kOverflowList).FirstChild();
nifChild; ) {
nsIFrame* next = nifChild->GetNextSibling();
if (!nifChild->GetPrevInFlow()) {
nif->StealFrame(nifChild);
ReparentFrame(nifChild, nif, this);
nifItems.AppendFrame(nullptr, nifChild);
nifNeedPushedItem = false;
}
nifChild = next;
}
::MergeSortedFrameLists(items, nifItems, GetContent());
nif->RemoveStateBits(NS_STATE_GRID_DID_PUSH_ITEMS);
nif = static_cast<nsGridContainerFrame*>(nif->GetNextInFlow());
MOZ_ASSERT(nif || !nifNeedPushedItem || mDidPushItemsBitMayLie,
"NS_STATE_GRID_DID_PUSH_ITEMS lied");
}
if (!items.IsEmpty()) {
// Pull up the first next-in-flow of the pulled up items too, unless its
// parent is our nif (to avoid leaving a hole there).
nsFrameList childNIFs;
nsFrameList childOCNIFs;
for (auto child : items) {
auto childNIF = child->GetNextInFlow();
if (childNIF && childNIF->GetParent() != firstNIF) {
auto parent = childNIF->GetParent();
parent->StealFrame(childNIF);
ReparentFrame(childNIF, parent, firstNIF);
if ((childNIF->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)) {
childOCNIFs.AppendFrame(nullptr, childNIF);
} else {
childNIFs.AppendFrame(nullptr, childNIF);
}
}
}
// Merge items' NIFs into our NIF's respective overflow child lists.
firstNIF->MergeSortedOverflow(childNIFs);
firstNIF->MergeSortedExcessOverflowContainers(childOCNIFs);
}
MOZ_ASSERT(foundOwnPushedChild || !items.IsEmpty() || mDidPushItemsBitMayLie,
"NS_STATE_GRID_DID_PUSH_ITEMS lied");
::MergeSortedFrameLists(mFrames, items, GetContent());
}
RenumberList();
#ifdef DEBUG
mDidPushItemsBitMayLie = false;
SanityCheckGridItemsBeforeReflow();
#endif // DEBUG
mBaseline[0][0] = NS_INTRINSIC_WIDTH_UNKNOWN;
mBaseline[0][1] = NS_INTRINSIC_WIDTH_UNKNOWN;
mBaseline[1][0] = NS_INTRINSIC_WIDTH_UNKNOWN;
mBaseline[1][1] = NS_INTRINSIC_WIDTH_UNKNOWN;
const nsStylePosition* stylePos = aReflowInput.mStylePosition;
if (!prevInFlow) {
InitImplicitNamedAreas(stylePos);
}
GridReflowInput gridReflowInput(this, aReflowInput);
if (gridReflowInput.mIter.ItemsAreAlreadyInOrder()) {
AddStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER);
} else {
RemoveStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER);
}
if (gridReflowInput.mIter.AtEnd()) {
// We have no grid items, our parent should synthesize a baseline if needed.
AddStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE);
} else {
RemoveStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE);
}
const nscoord computedBSize = aReflowInput.ComputedBSize();
const nscoord computedISize = aReflowInput.ComputedISize();
const WritingMode& wm = gridReflowInput.mWM;
LogicalSize computedSize(wm, computedISize, computedBSize);
nscoord consumedBSize = 0;
nscoord bSize;
if (!prevInFlow) {
Grid grid;
grid.PlaceGridItems(gridReflowInput, aReflowInput.ComputedMinSize(),
computedSize, aReflowInput.ComputedMaxSize());
gridReflowInput.CalculateTrackSizes(grid, computedSize,
SizingConstraint::eNoConstraint);
bSize = computedSize.BSize(wm);
} else {
consumedBSize = ConsumedBSize(wm);
gridReflowInput.InitializeForContinuation(this, consumedBSize);
const uint32_t numRows = gridReflowInput.mRows.mSizes.Length();
bSize = gridReflowInput.mRows.GridLineEdge(numRows,
GridLineSide::eAfterGridGap);
}
if (computedBSize == NS_AUTOHEIGHT) {
bSize = NS_CSS_MINMAX(bSize,
aReflowInput.ComputedMinBSize(),
aReflowInput.ComputedMaxBSize());
} else {
bSize = computedBSize;
}
bSize = std::max(bSize - consumedBSize, 0);
auto& bp = gridReflowInput.mBorderPadding;
LogicalRect contentArea(wm, bp.IStart(wm), bp.BStart(wm),
computedISize, bSize);
if (!prevInFlow) {
// Apply 'align/justify-content' to the grid.
// CalculateTrackSizes did the columns.
gridReflowInput.mRows.AlignJustifyContent(stylePos, wm, contentArea.Size(wm));
}
bSize = ReflowChildren(gridReflowInput, contentArea, aDesiredSize, aStatus);
bSize = std::max(bSize - consumedBSize, 0);
// Skip our block-end border if we're INCOMPLETE.
if (!aStatus.IsComplete() &&
!gridReflowInput.mSkipSides.BEnd() &&
StyleBorder()->mBoxDecorationBreak !=
StyleBoxDecorationBreak::Clone) {
bp.BEnd(wm) = nscoord(0);
}
LogicalSize desiredSize(wm, computedISize + bp.IStartEnd(wm),
bSize + bp.BStartEnd(wm));
aDesiredSize.SetSize(wm, desiredSize);
nsRect frameRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height());
aDesiredSize.mOverflowAreas.UnionAllWith(frameRect);
// Convert INCOMPLETE -> OVERFLOW_INCOMPLETE and zero bsize if we're an OC.
if (HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
if (!aStatus.IsComplete()) {
aStatus.SetOverflowIncomplete();
aStatus.SetNextInFlowNeedsReflow();
}
bSize = 0;
desiredSize.BSize(wm) = bSize + bp.BStartEnd(wm);
aDesiredSize.SetSize(wm, desiredSize);
}
if (!gridReflowInput.mInFragmentainer) {
MOZ_ASSERT(gridReflowInput.mIter.IsValid());
auto sz = frameRect.Size();
CalculateBaselines(BaselineSet::eBoth, &gridReflowInput.mIter,
&gridReflowInput.mGridItems, gridReflowInput.mCols,
0, gridReflowInput.mCols.mSizes.Length(),
wm, sz, bp.IStart(wm),
bp.IEnd(wm), desiredSize.ISize(wm));
CalculateBaselines(BaselineSet::eBoth, &gridReflowInput.mIter,
&gridReflowInput.mGridItems, gridReflowInput.mRows,
0, gridReflowInput.mRows.mSizes.Length(),
wm, sz, bp.BStart(wm),
bp.BEnd(wm), desiredSize.BSize(wm));
} else {
// Only compute 'first baseline' if this fragment contains the first track.
// XXXmats maybe remove this condition? bug 1306499
BaselineSet baselines = BaselineSet::eNone;
if (gridReflowInput.mStartRow == 0 &&
gridReflowInput.mStartRow != gridReflowInput.mNextFragmentStartRow) {
baselines = BaselineSet::eFirst;
}
// Only compute 'last baseline' if this fragment contains the last track.
// XXXmats maybe remove this condition? bug 1306499
uint32_t len = gridReflowInput.mRows.mSizes.Length();
if (gridReflowInput.mStartRow != len &&
gridReflowInput.mNextFragmentStartRow == len) {
baselines = BaselineSet(baselines | BaselineSet::eLast);
}
Maybe<GridItemCSSOrderIterator> iter;
Maybe<nsTArray<GridItemInfo>> gridItems;
if (baselines != BaselineSet::eNone) {
// We need to create a new iterator and GridItemInfo array because we
// might have pushed some children at this point.
// Even if the gridReflowInput iterator is invalid we can reuse its
// state about order to optimize initialization of the new iterator.
// An ordered child list can't become unordered by pushing frames.
// An unordered list can become ordered in a number of cases, but we
// ignore that here and guess that the child list is still unordered.
// XXX this is O(n^2) in the number of items in this fragment: bug 1306705
using Filter = GridItemCSSOrderIterator::ChildFilter;
using Order = GridItemCSSOrderIterator::OrderState;
bool ordered = gridReflowInput.mIter.ItemsAreAlreadyInOrder();
auto orderState = ordered ? Order::eKnownOrdered : Order::eKnownUnordered;
iter.emplace(this, kPrincipalList, Filter::eSkipPlaceholders, orderState);
gridItems.emplace();
for (; !iter->AtEnd(); iter->Next()) {
auto child = **iter;
for (const auto& info : gridReflowInput.mGridItems) {
if (info.mFrame == child) {
gridItems->AppendElement(info);
}
}
}
}
auto sz = frameRect.Size();
CalculateBaselines(baselines, iter.ptrOr(nullptr), gridItems.ptrOr(nullptr),
gridReflowInput.mCols, 0,
gridReflowInput.mCols.mSizes.Length(), wm, sz,
bp.IStart(wm), bp.IEnd(wm), desiredSize.ISize(wm));
CalculateBaselines(baselines, iter.ptrOr(nullptr), gridItems.ptrOr(nullptr),
gridReflowInput.mRows, gridReflowInput.mStartRow,
gridReflowInput.mNextFragmentStartRow, wm, sz,
bp.BStart(wm), bp.BEnd(wm), desiredSize.BSize(wm));
}
if (HasAnyStateBits(NS_STATE_GRID_GENERATE_COMPUTED_VALUES)) {
// This state bit will never be cleared, since reflow can be called
// multiple times in fragmented grids, and it's challenging to scope
// the bit to only that sequence of calls. This is relatively harmless
// since this bit is only set by accessing a ChromeOnly property, and
// therefore can't unduly slow down normal web browsing.
// Now that we know column and row sizes and positions, set
// the ComputedGridTrackInfo and related properties
uint32_t colTrackCount = gridReflowInput.mCols.mSizes.Length();
nsTArray<nscoord> colTrackPositions(colTrackCount);
nsTArray<nscoord> colTrackSizes(colTrackCount);
nsTArray<uint32_t> colTrackStates(colTrackCount);
nsTArray<bool> colRemovedRepeatTracks(
gridReflowInput.mColFunctions.mRemovedRepeatTracks);
uint32_t col = 0;
for (const TrackSize& sz : gridReflowInput.mCols.mSizes) {
colTrackPositions.AppendElement(sz.mPosition);
colTrackSizes.AppendElement(sz.mBase);
bool isRepeat = ((col >= gridReflowInput.mColFunctions.mRepeatAutoStart) &&
(col < gridReflowInput.mColFunctions.mRepeatAutoEnd));
colTrackStates.AppendElement(
isRepeat ?
(uint32_t)mozilla::dom::GridTrackState::Repeat :
(uint32_t)mozilla::dom::GridTrackState::Static
);
col++;
}
ComputedGridTrackInfo* colInfo = new ComputedGridTrackInfo(
gridReflowInput.mColFunctions.mExplicitGridOffset,
gridReflowInput.mColFunctions.NumExplicitTracks(),
0,
col,
Move(colTrackPositions),
Move(colTrackSizes),
Move(colTrackStates),
Move(colRemovedRepeatTracks),
gridReflowInput.mColFunctions.mRepeatAutoStart);
Properties().Set(GridColTrackInfo(), colInfo);
uint32_t rowTrackCount = gridReflowInput.mRows.mSizes.Length();
nsTArray<nscoord> rowTrackPositions(rowTrackCount);
nsTArray<nscoord> rowTrackSizes(rowTrackCount);
nsTArray<uint32_t> rowTrackStates(rowTrackCount);
nsTArray<bool> rowRemovedRepeatTracks(
gridReflowInput.mRowFunctions.mRemovedRepeatTracks);
uint32_t row = 0;
for (const TrackSize& sz : gridReflowInput.mRows.mSizes) {
rowTrackPositions.AppendElement(sz.mPosition);
rowTrackSizes.AppendElement(sz.mBase);
bool isRepeat = ((row >= gridReflowInput.mRowFunctions.mRepeatAutoStart) &&
(row < gridReflowInput.mRowFunctions.mRepeatAutoEnd));
rowTrackStates.AppendElement(
isRepeat ?
(uint32_t)mozilla::dom::GridTrackState::Repeat :
(uint32_t)mozilla::dom::GridTrackState::Static
);
row++;
}
// Row info has to accomodate fragmentation of the grid, which may happen in
// later calls to Reflow. For now, presume that no more fragmentation will
// occur.
ComputedGridTrackInfo* rowInfo = new ComputedGridTrackInfo(
gridReflowInput.mRowFunctions.mExplicitGridOffset,
gridReflowInput.mRowFunctions.NumExplicitTracks(),
gridReflowInput.mStartRow,
row,
Move(rowTrackPositions),
Move(rowTrackSizes),
Move(rowTrackStates),
Move(rowRemovedRepeatTracks),
gridReflowInput.mRowFunctions.mRepeatAutoStart);
Properties().Set(GridRowTrackInfo(), rowInfo);
if (prevInFlow) {
// This frame is fragmenting rows from a previous frame, so patch up
// the prior GridRowTrackInfo with a new end row.
// FIXME: This can be streamlined and/or removed when bug 1151204 lands.
ComputedGridTrackInfo* priorRowInfo =
prevInFlow->Properties().Get(GridRowTrackInfo());
// Adjust track positions based on the first track in this fragment.
if (priorRowInfo->mPositions.Length() >
priorRowInfo->mStartFragmentTrack) {
nscoord delta =
priorRowInfo->mPositions[priorRowInfo->mStartFragmentTrack];
for (nscoord& pos : priorRowInfo->mPositions) {
pos -= delta;
}
}
ComputedGridTrackInfo* revisedPriorRowInfo = new ComputedGridTrackInfo(
priorRowInfo->mNumLeadingImplicitTracks,
priorRowInfo->mNumExplicitTracks,
priorRowInfo->mStartFragmentTrack,
gridReflowInput.mStartRow,
Move(priorRowInfo->mPositions),
Move(priorRowInfo->mSizes),
Move(priorRowInfo->mStates),
Move(priorRowInfo->mRemovedRepeatTracks),
priorRowInfo->mRepeatFirstTrack);
prevInFlow->Properties().Set(GridRowTrackInfo(), revisedPriorRowInfo);
}
// Generate the line info properties. We need to provide the number of
// repeat tracks produced in the reflow. Only explicit names are assigned
// to lines here; the mozilla::dom::GridLines class will later extract
// implicit names from grid areas and assign them to the appropriate lines.
// Generate column lines first.
uint32_t capacity = gridReflowInput.mCols.mSizes.Length();
const nsStyleGridTemplate& gridColTemplate =
gridReflowInput.mGridStyle->mGridTemplateColumns;
nsTArray<nsTArray<nsString>> columnLineNames(capacity);
for (col = 0; col <= gridReflowInput.mCols.mSizes.Length(); col++) {
// Offset col by the explicit grid offset, to get the original names.
nsTArray<nsString> explicitNames =
gridReflowInput.mCols.GetExplicitLineNamesAtIndex(
gridColTemplate,
gridReflowInput.mColFunctions,
col - gridReflowInput.mColFunctions.mExplicitGridOffset);
columnLineNames.AppendElement(explicitNames);
}
ComputedGridLineInfo* columnLineInfo = new ComputedGridLineInfo(
Move(columnLineNames),
gridColTemplate.mRepeatAutoLineNameListBefore,
gridColTemplate.mRepeatAutoLineNameListAfter);
Properties().Set(GridColumnLineInfo(), columnLineInfo);
// Generate row lines next.
capacity = gridReflowInput.mRows.mSizes.Length();
const nsStyleGridTemplate& gridRowTemplate =
gridReflowInput.mGridStyle->mGridTemplateRows;
nsTArray<nsTArray<nsString>> rowLineNames(capacity);
for (row = 0; row <= gridReflowInput.mRows.mSizes.Length(); row++) {
// Offset row by the explicit grid offset, to get the original names.
nsTArray<nsString> explicitNames =
gridReflowInput.mRows.GetExplicitLineNamesAtIndex(
gridRowTemplate,
gridReflowInput.mRowFunctions,
row - gridReflowInput.mRowFunctions.mExplicitGridOffset);
rowLineNames.AppendElement(explicitNames);
}
ComputedGridLineInfo* rowLineInfo = new ComputedGridLineInfo(
Move(rowLineNames),
gridRowTemplate.mRepeatAutoLineNameListBefore,
gridRowTemplate.mRepeatAutoLineNameListAfter);
Properties().Set(GridRowLineInfo(), rowLineInfo);
// Generate area info for explicit areas. Implicit areas are handled
// elsewhere.
if (gridReflowInput.mGridStyle->mGridTemplateAreas) {
nsTArray<css::GridNamedArea>* areas = new nsTArray<css::GridNamedArea>(
gridReflowInput.mGridStyle->mGridTemplateAreas->mNamedAreas);
Properties().Set(ExplicitNamedAreasProperty(), areas);
} else {
Properties().Delete(ExplicitNamedAreasProperty());
}
}
if (!prevInFlow) {
SharedGridData* sharedGridData = Properties().Get(SharedGridData::Prop());
if (!aStatus.IsFullyComplete()) {
if (!sharedGridData) {
sharedGridData = new SharedGridData;
Properties().Set(SharedGridData::Prop(), sharedGridData);
}
sharedGridData->mCols.mSizes.Clear();
sharedGridData->mCols.mSizes.SwapElements(gridReflowInput.mCols.mSizes);
sharedGridData->mCols.mContentBoxSize = gridReflowInput.mCols.mContentBoxSize;
sharedGridData->mCols.mBaselineSubtreeAlign[0] =
gridReflowInput.mCols.mBaselineSubtreeAlign[0];
sharedGridData->mCols.mBaselineSubtreeAlign[1] =
gridReflowInput.mCols.mBaselineSubtreeAlign[1];
sharedGridData->mRows.mSizes.Clear();
sharedGridData->mRows.mSizes.SwapElements(gridReflowInput.mRows.mSizes);
// Save the original row grid sizes and gaps so we can restore them later
// in GridReflowInput::Initialize for the continuations.
auto& origRowData = sharedGridData->mOriginalRowData;
origRowData.ClearAndRetainStorage();
origRowData.SetCapacity(sharedGridData->mRows.mSizes.Length());
nscoord prevTrackEnd = 0;
for (auto& sz : sharedGridData->mRows.mSizes) {
SharedGridData::RowData data = {sz.mBase, sz.mPosition - prevTrackEnd};
origRowData.AppendElement(data);
prevTrackEnd = sz.mPosition + sz.mBase;
}
sharedGridData->mRows.mContentBoxSize = gridReflowInput.mRows.mContentBoxSize;
sharedGridData->mRows.mBaselineSubtreeAlign[0] =
gridReflowInput.mRows.mBaselineSubtreeAlign[0];
sharedGridData->mRows.mBaselineSubtreeAlign[1] =
gridReflowInput.mRows.mBaselineSubtreeAlign[1];
sharedGridData->mGridItems.Clear();
sharedGridData->mGridItems.SwapElements(gridReflowInput.mGridItems);
sharedGridData->mAbsPosItems.Clear();
sharedGridData->mAbsPosItems.SwapElements(gridReflowInput.mAbsPosItems);
sharedGridData->mGenerateComputedGridInfo =
HasAnyStateBits(NS_STATE_GRID_GENERATE_COMPUTED_VALUES);
} else if (sharedGridData && !GetNextInFlow()) {
Properties().Delete(SharedGridData::Prop());
}
}
FinishAndStoreOverflow(&aDesiredSize);
NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
}
nscoord
nsGridContainerFrame::IntrinsicISize(nsRenderingContext* aRenderingContext,
IntrinsicISizeType aType)
{
RenumberList();
// Calculate the sum of column sizes under intrinsic sizing.
// http://dev.w3.org/csswg/css-grid/#intrinsic-sizes
GridReflowInput state(this, *aRenderingContext);
InitImplicitNamedAreas(state.mGridStyle); // XXX optimize
auto GetDefiniteSizes = [] (const nsStyleCoord& aMinCoord,
const nsStyleCoord& aSizeCoord,
const nsStyleCoord& aMaxCoord,
nscoord* aMin,
nscoord* aSize,
nscoord* aMax) {
if (aMinCoord.ConvertsToLength()) {
*aMin = aMinCoord.ToLength();
}
if (aMaxCoord.ConvertsToLength()) {
*aMax = std::max(*aMin, aMaxCoord.ToLength());
}
if (aSizeCoord.ConvertsToLength()) {
*aSize = Clamp(aSizeCoord.ToLength(), *aMin, *aMax);
}
};
// The min/sz/max sizes are the input to the "repeat-to-fill" algorithm:
// https://drafts.csswg.org/css-grid/#auto-repeat
// They're only used for auto-repeat so we skip computing them otherwise.
LogicalSize min(state.mWM, 0, 0);
LogicalSize sz(state.mWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
LogicalSize max(state.mWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
if (state.mColFunctions.mHasRepeatAuto) {
GetDefiniteSizes(state.mGridStyle->MinISize(state.mWM),
state.mGridStyle->ISize(state.mWM),
state.mGridStyle->MaxISize(state.mWM),
&min.ISize(state.mWM),
&sz.ISize(state.mWM),
&max.ISize(state.mWM));
}
if (state.mRowFunctions.mHasRepeatAuto &&
!(state.mGridStyle->mGridAutoFlow & NS_STYLE_GRID_AUTO_FLOW_ROW)) {
// Only 'grid-auto-flow:column' can create new implicit columns, so that's
// the only case where our block-size can affect the number of columns.
GetDefiniteSizes(state.mGridStyle->MinBSize(state.mWM),
state.mGridStyle->BSize(state.mWM),
state.mGridStyle->MaxBSize(state.mWM),
&min.BSize(state.mWM),
&sz.BSize(state.mWM),
&max.BSize(state.mWM));
}
Grid grid;
grid.PlaceGridItems(state, min, sz, max); // XXX optimize
if (grid.mGridColEnd == 0) {
return 0;
}
state.mCols.Initialize(state.mColFunctions, state.mGridStyle->mGridColumnGap,
grid.mGridColEnd, NS_UNCONSTRAINEDSIZE);
auto constraint = aType == nsLayoutUtils::MIN_ISIZE ?
SizingConstraint::eMinContent : SizingConstraint::eMaxContent;
state.mCols.CalculateSizes(state, state.mGridItems, state.mColFunctions,
NS_UNCONSTRAINEDSIZE, &GridArea::mCols,
constraint);
return state.mCols.BackComputedIntrinsicSize(state.mColFunctions,
state.mGridStyle->mGridColumnGap);
}
nscoord
nsGridContainerFrame::GetMinISize(nsRenderingContext* aRC)
{
DISPLAY_MIN_WIDTH(this, mCachedMinISize);
if (mCachedMinISize == NS_INTRINSIC_WIDTH_UNKNOWN) {
mCachedMinISize = IntrinsicISize(aRC, nsLayoutUtils::MIN_ISIZE);
}
return mCachedMinISize;
}
nscoord
nsGridContainerFrame::GetPrefISize(nsRenderingContext* aRC)
{
DISPLAY_PREF_WIDTH(this, mCachedPrefISize);
if (mCachedPrefISize == NS_INTRINSIC_WIDTH_UNKNOWN) {
mCachedPrefISize = IntrinsicISize(aRC, nsLayoutUtils::PREF_ISIZE);
}
return mCachedPrefISize;
}
void
nsGridContainerFrame::MarkIntrinsicISizesDirty()
{
mCachedMinISize = NS_INTRINSIC_WIDTH_UNKNOWN;
mCachedPrefISize = NS_INTRINSIC_WIDTH_UNKNOWN;
mBaseline[0][0] = NS_INTRINSIC_WIDTH_UNKNOWN;
mBaseline[0][1] = NS_INTRINSIC_WIDTH_UNKNOWN;
mBaseline[1][0] = NS_INTRINSIC_WIDTH_UNKNOWN;
mBaseline[1][1] = NS_INTRINSIC_WIDTH_UNKNOWN;
nsContainerFrame::MarkIntrinsicISizesDirty();
}
nsIAtom*
nsGridContainerFrame::GetType() const
{
return nsGkAtoms::gridContainerFrame;
}
void
nsGridContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsRect& aDirtyRect,
const nsDisplayListSet& aLists)
{
DisplayBorderBackgroundOutline(aBuilder, aLists);
if (GetPrevInFlow()) {
DisplayOverflowContainers(aBuilder, aDirtyRect, aLists);
}
// Our children are all grid-level boxes, which behave the same as
// inline-blocks in painting, so their borders/backgrounds all go on
// the BlockBorderBackgrounds list.
typedef GridItemCSSOrderIterator::OrderState OrderState;
OrderState order = HasAnyStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER)
? OrderState::eKnownOrdered
: OrderState::eKnownUnordered;
GridItemCSSOrderIterator iter(this, kPrincipalList,
GridItemCSSOrderIterator::eIncludeAll, order);
for (; !iter.AtEnd(); iter.Next()) {
nsIFrame* child = *iter;
BuildDisplayListForChild(aBuilder, child, aDirtyRect, aLists,
::GetDisplayFlagsForGridItem(child));
}
}
bool
nsGridContainerFrame::DrainSelfOverflowList()
{
// Unlike nsContainerFrame::DrainSelfOverflowList we need to merge these lists
// so that the resulting mFrames is in document content order.
// NOTE: nsContainerFrame::AppendFrames/InsertFrames calls this method.
AutoFrameListPtr overflowFrames(PresContext(), StealOverflowFrames());
if (overflowFrames) {
::MergeSortedFrameLists(mFrames, *overflowFrames, GetContent());
return true;
}
return false;
}
void
nsGridContainerFrame::AppendFrames(ChildListID aListID, nsFrameList& aFrameList)
{
NoteNewChildren(aListID, aFrameList);
nsContainerFrame::AppendFrames(aListID, aFrameList);
}
void
nsGridContainerFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
nsFrameList& aFrameList)
{
NoteNewChildren(aListID, aFrameList);
nsContainerFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
}
void
nsGridContainerFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame)
{
#ifdef DEBUG
ChildListIDs supportedLists =
kAbsoluteList | kFixedList | kPrincipalList | kNoReflowPrincipalList;
MOZ_ASSERT(supportedLists.Contains(aListID), "unexpected child list");
// Note that kPrincipalList doesn't mean aOldFrame must be on that list.
// It can also be on kOverflowList, in which case it might be a pushed
// item, and if it's the only pushed item our DID_PUSH_ITEMS bit will lie.
if (aListID == kPrincipalList && !aOldFrame->GetPrevInFlow()) {
// Since the bit may lie, set the mDidPushItemsBitMayLie value to true for
// ourself and for all our contiguous previous-in-flow nsGridContainerFrames.
nsGridContainerFrame* frameThatMayLie = this;
do {
frameThatMayLie->mDidPushItemsBitMayLie = true;
frameThatMayLie = static_cast<nsGridContainerFrame*>(
frameThatMayLie->GetPrevInFlow());
} while (frameThatMayLie);
}
#endif
nsContainerFrame::RemoveFrame(aListID, aOldFrame);
}
uint16_t
nsGridContainerFrame::CSSAlignmentForAbsPosChild(const ReflowInput& aChildRI,
LogicalAxis aLogicalAxis) const
{
MOZ_ASSERT(aChildRI.mFrame->IsAbsolutelyPositioned(),
"This method should only be called for abspos children");
uint16_t alignment = (aLogicalAxis == eLogicalAxisInline) ?
aChildRI.mStylePosition->UsedJustifySelf(StyleContext()) :
aChildRI.mStylePosition->UsedAlignSelf(StyleContext());
// XXX strip off <overflow-position> bits until we implement it
// (bug 1311892)
alignment &= ~NS_STYLE_ALIGN_FLAG_BITS;
if (alignment == NS_STYLE_ALIGN_NORMAL) {
// "the 'normal' keyword behaves as 'start' on replaced
// absolutely-positioned boxes, and behaves as 'stretch' on all other
// absolutely-positioned boxes."
// https://drafts.csswg.org/css-align/#align-abspos
// https://drafts.csswg.org/css-align/#justify-abspos
alignment = aChildRI.mFrame->IsFrameOfType(nsIFrame::eReplaced) ?
NS_STYLE_ALIGN_START : NS_STYLE_ALIGN_STRETCH;
} else if (alignment == NS_STYLE_ALIGN_FLEX_START) {
alignment = NS_STYLE_ALIGN_START;
} else if (alignment == NS_STYLE_ALIGN_FLEX_END) {
alignment = NS_STYLE_ALIGN_END;
} else if (alignment == NS_STYLE_ALIGN_LEFT ||
alignment == NS_STYLE_ALIGN_RIGHT) {
if (aLogicalAxis == eLogicalAxisInline) {
const bool isLeft = (alignment == NS_STYLE_ALIGN_LEFT);
WritingMode wm = GetWritingMode();
alignment = (isLeft == wm.IsBidiLTR()) ? NS_STYLE_ALIGN_START
: NS_STYLE_ALIGN_END;
} else {
alignment = NS_STYLE_ALIGN_START;
}
} else if (alignment == NS_STYLE_ALIGN_BASELINE) {
alignment = NS_STYLE_ALIGN_START;
} else if (alignment == NS_STYLE_ALIGN_LAST_BASELINE) {
alignment = NS_STYLE_ALIGN_END;
}
return alignment;
}
nscoord
nsGridContainerFrame::SynthesizeBaseline(
const FindItemInGridOrderResult& aGridOrderItem,
LogicalAxis aAxis,
BaselineSharingGroup aGroup,
const nsSize& aCBPhysicalSize,
nscoord aCBSize,
WritingMode aCBWM)
{
if (MOZ_UNLIKELY(!aGridOrderItem.mItem)) {
// No item in this fragment - synthesize a baseline from our border-box.
return ::SynthesizeBaselineFromBorderBox(aGroup, aCBWM, aCBSize);
}
auto GetBBaseline = [] (BaselineSharingGroup aGroup, WritingMode aWM,
const nsIFrame* aFrame, nscoord* aBaseline) {
return aGroup == BaselineSharingGroup::eFirst ?
nsLayoutUtils::GetFirstLineBaseline(aWM, aFrame, aBaseline) :
nsLayoutUtils::GetLastLineBaseline(aWM, aFrame, aBaseline);
};
nsIFrame* child = aGridOrderItem.mItem->mFrame;
nsGridContainerFrame* grid = do_QueryFrame(child);
auto childWM = child->GetWritingMode();
bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM);
nscoord baseline;
nscoord start;
nscoord size;
if (aAxis == eLogicalAxisBlock) {
start = child->GetLogicalNormalPosition(aCBWM, aCBPhysicalSize).B(aCBWM);
size = child->BSize(aCBWM);
if (grid && aGridOrderItem.mIsInEdgeTrack) {
isOrthogonal ? grid->GetIBaseline(aGroup, &baseline) :
grid->GetBBaseline(aGroup, &baseline);
} else if (!isOrthogonal && aGridOrderItem.mIsInEdgeTrack) {
baseline = child->BaselineBOffset(childWM, aGroup, AlignmentContext::eGrid);
} else {
baseline = ::SynthesizeBaselineFromBorderBox(aGroup, childWM, size);
}
} else {
start = child->GetLogicalNormalPosition(aCBWM, aCBPhysicalSize).I(aCBWM);
size = child->ISize(aCBWM);
if (grid && aGridOrderItem.mIsInEdgeTrack) {
isOrthogonal ? grid->GetBBaseline(aGroup, &baseline) :
grid->GetIBaseline(aGroup, &baseline);
} else if (isOrthogonal && aGridOrderItem.mIsInEdgeTrack &&
GetBBaseline(aGroup, childWM, child, &baseline)) {
if (aGroup == BaselineSharingGroup::eLast) {
baseline = size - baseline; // convert to distance from border-box end
}
} else {
baseline = ::SynthesizeBaselineFromBorderBox(aGroup, childWM, size);
}
}
return aGroup == BaselineSharingGroup::eFirst ? start + baseline :
aCBSize - start - size + baseline;
}
void
nsGridContainerFrame::CalculateBaselines(
BaselineSet aBaselineSet,
GridItemCSSOrderIterator* aIter,
const nsTArray<GridItemInfo>* aGridItems,
const Tracks& aTracks,
uint32_t aFragmentStartTrack,
uint32_t aFirstExcludedTrack,
WritingMode aWM,
const nsSize& aCBPhysicalSize,
nscoord aCBBorderPaddingStart,
nscoord aCBBorderPaddingEnd,
nscoord aCBSize)
{
const auto axis = aTracks.mAxis;
auto firstBaseline = aTracks.mBaseline[BaselineSharingGroup::eFirst];
if (!(aBaselineSet & BaselineSet::eFirst)) {
mBaseline[axis][BaselineSharingGroup::eFirst] =
::SynthesizeBaselineFromBorderBox(BaselineSharingGroup::eFirst, aWM,
aCBSize);
} else if (firstBaseline == NS_INTRINSIC_WIDTH_UNKNOWN) {
FindItemInGridOrderResult gridOrderFirstItem =
FindFirstItemInGridOrder(*aIter, *aGridItems,
axis == eLogicalAxisBlock ? &GridArea::mRows : &GridArea::mCols,
axis == eLogicalAxisBlock ? &GridArea::mCols : &GridArea::mRows,
aFragmentStartTrack);
mBaseline[axis][BaselineSharingGroup::eFirst] =
SynthesizeBaseline(gridOrderFirstItem,
axis,
BaselineSharingGroup::eFirst,
aCBPhysicalSize,
aCBSize,
aWM);
} else {
// We have a 'first baseline' group in the start track in this fragment.
// Convert it from track to grid container border-box coordinates.
MOZ_ASSERT(!aGridItems->IsEmpty());
nscoord gapBeforeStartTrack = aFragmentStartTrack == 0 ?
aTracks.GridLineEdge(aFragmentStartTrack, GridLineSide::eAfterGridGap) :
nscoord(0); // no content gap at start of fragment
mBaseline[axis][BaselineSharingGroup::eFirst] =
aCBBorderPaddingStart + gapBeforeStartTrack + firstBaseline;
}
auto lastBaseline = aTracks.mBaseline[BaselineSharingGroup::eLast];
if (!(aBaselineSet & BaselineSet::eLast)) {
mBaseline[axis][BaselineSharingGroup::eLast] =
::SynthesizeBaselineFromBorderBox(BaselineSharingGroup::eLast, aWM,
aCBSize);
} else if (lastBaseline == NS_INTRINSIC_WIDTH_UNKNOWN) {
// For finding items for the 'last baseline' we need to create a reverse
// iterator ('aIter' is the forward iterator from the GridReflowInput).
using Iter = ReverseGridItemCSSOrderIterator;
auto orderState = aIter->ItemsAreAlreadyInOrder() ?
Iter::OrderState::eKnownOrdered : Iter::OrderState::eKnownUnordered;
Iter iter(this, kPrincipalList, Iter::ChildFilter::eSkipPlaceholders,
orderState);
iter.SetGridItemCount(aGridItems->Length());
FindItemInGridOrderResult gridOrderLastItem =
FindLastItemInGridOrder(iter, *aGridItems,
axis == eLogicalAxisBlock ? &GridArea::mRows : &GridArea::mCols,
axis == eLogicalAxisBlock ? &GridArea::mCols : &GridArea::mRows,
aFragmentStartTrack, aFirstExcludedTrack);
mBaseline[axis][BaselineSharingGroup::eLast] =
SynthesizeBaseline(gridOrderLastItem,
axis,
BaselineSharingGroup::eLast,
aCBPhysicalSize,
aCBSize,
aWM);
} else {
// We have a 'last baseline' group in the end track in this fragment.
// Convert it from track to grid container border-box coordinates.
MOZ_ASSERT(!aGridItems->IsEmpty());
auto borderBoxStartToEndOfEndTrack = aCBBorderPaddingStart +
aTracks.GridLineEdge(aFirstExcludedTrack, GridLineSide::eBeforeGridGap) -
aTracks.GridLineEdge(aFragmentStartTrack, GridLineSide::eBeforeGridGap);
mBaseline[axis][BaselineSharingGroup::eLast] =
(aCBSize - borderBoxStartToEndOfEndTrack) + lastBaseline;
}
}
#ifdef DEBUG_FRAME_DUMP
nsresult
nsGridContainerFrame::GetFrameName(nsAString& aResult) const
{
return MakeFrameName(NS_LITERAL_STRING("GridContainer"), aResult);
}
#endif
void
nsGridContainerFrame::NoteNewChildren(ChildListID aListID,
const nsFrameList& aFrameList)
{
#ifdef DEBUG
ChildListIDs supportedLists =
kAbsoluteList | kFixedList | kPrincipalList | kNoReflowPrincipalList;
MOZ_ASSERT(supportedLists.Contains(aListID), "unexpected child list");
#endif
nsIPresShell* shell = PresContext()->PresShell();
for (auto pif = GetPrevInFlow(); pif; pif = pif->GetPrevInFlow()) {
if (aListID == kPrincipalList) {
pif->AddStateBits(NS_STATE_GRID_DID_PUSH_ITEMS);
}
shell->FrameNeedsReflow(pif, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY);
}
}
void
nsGridContainerFrame::MergeSortedOverflow(nsFrameList& aList)
{
if (aList.IsEmpty()) {
return;
}
MOZ_ASSERT(!aList.FirstChild()->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER),
"this is the wrong list to put this child frame");
MOZ_ASSERT(aList.FirstChild()->GetParent() == this);
nsFrameList* overflow = GetOverflowFrames();
if (overflow) {
::MergeSortedFrameLists(*overflow, aList, GetContent());
} else {
SetOverflowFrames(aList);
}
}
void
nsGridContainerFrame::MergeSortedExcessOverflowContainers(nsFrameList& aList)
{
if (aList.IsEmpty()) {
return;
}
MOZ_ASSERT(aList.FirstChild()->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER),
"this is the wrong list to put this child frame");
MOZ_ASSERT(aList.FirstChild()->GetParent() == this);
nsFrameList* eoc = GetPropTableFrames(ExcessOverflowContainersProperty());
if (eoc) {
::MergeSortedFrameLists(*eoc, aList, GetContent());
} else {
SetPropTableFrames(new (PresContext()->PresShell()) nsFrameList(aList),
ExcessOverflowContainersProperty());
}
}
/* static */ nsGridContainerFrame::FindItemInGridOrderResult
nsGridContainerFrame::FindFirstItemInGridOrder(
GridItemCSSOrderIterator& aIter,
const nsTArray<GridItemInfo>& aGridItems,
LineRange GridArea::* aMajor,
LineRange GridArea::* aMinor,
uint32_t aFragmentStartTrack)
{
FindItemInGridOrderResult result = { nullptr, false };
uint32_t minMajor = kTranslatedMaxLine + 1;
uint32_t minMinor = kTranslatedMaxLine + 1;
aIter.Reset();
for (; !aIter.AtEnd(); aIter.Next()) {
const GridItemInfo& item = aGridItems[aIter.GridItemIndex()];
if ((item.mArea.*aMajor).mEnd <= aFragmentStartTrack) {
continue; // item doesn't span any track in this fragment
}
uint32_t major = (item.mArea.*aMajor).mStart;
uint32_t minor = (item.mArea.*aMinor).mStart;
if (major < minMajor || (major == minMajor && minor < minMinor)) {
minMajor = major;
minMinor = minor;
result.mItem = &item;
result.mIsInEdgeTrack = major == 0U;
}
}
return result;
}
/* static */ nsGridContainerFrame::FindItemInGridOrderResult
nsGridContainerFrame::FindLastItemInGridOrder(
ReverseGridItemCSSOrderIterator& aIter,
const nsTArray<GridItemInfo>& aGridItems,
LineRange GridArea::* aMajor,
LineRange GridArea::* aMinor,
uint32_t aFragmentStartTrack,
uint32_t aFirstExcludedTrack)
{
FindItemInGridOrderResult result = { nullptr, false };
int32_t maxMajor = -1;
int32_t maxMinor = -1;
aIter.Reset();
int32_t lastMajorTrack = int32_t(aFirstExcludedTrack) - 1;
for (; !aIter.AtEnd(); aIter.Next()) {
const GridItemInfo& item = aGridItems[aIter.GridItemIndex()];
// Subtract 1 from the end line to get the item's last track index.
int32_t major = (item.mArea.*aMajor).mEnd - 1;
// Currently, this method is only called with aFirstExcludedTrack ==
// the first track in the next fragment, so we take the opportunity
// to assert this item really belongs to this fragment.
MOZ_ASSERT((item.mArea.*aMajor).mStart < aFirstExcludedTrack,
"found an item that belongs to some later fragment");
if (major < int32_t(aFragmentStartTrack)) {
continue; // item doesn't span any track in this fragment
}
int32_t minor = (item.mArea.*aMinor).mEnd - 1;
MOZ_ASSERT(minor >= 0 && major >= 0, "grid item must have span >= 1");
if (major > maxMajor || (major == maxMajor && minor > maxMinor)) {
maxMajor = major;
maxMinor = minor;
result.mItem = &item;
result.mIsInEdgeTrack = major == lastMajorTrack;
}
}
return result;
}
#ifdef DEBUG
void
nsGridContainerFrame::SetInitialChildList(ChildListID aListID,
nsFrameList& aChildList)
{
ChildListIDs supportedLists = kAbsoluteList | kFixedList | kPrincipalList;
MOZ_ASSERT(supportedLists.Contains(aListID), "unexpected child list");
return nsContainerFrame::SetInitialChildList(aListID, aChildList);
}
void
nsGridContainerFrame::SanityCheckGridItemsBeforeReflow() const
{
ChildListIDs absLists = kAbsoluteList | kFixedList |
kOverflowContainersList | kExcessOverflowContainersList;
ChildListIDs itemLists = kPrincipalList | kOverflowList;
for (const nsIFrame* f = this; f; f = f->GetNextInFlow()) {
MOZ_ASSERT(!f->HasAnyStateBits(NS_STATE_GRID_DID_PUSH_ITEMS),
"At start of reflow, we should've pulled items back from all "
"NIFs and cleared NS_STATE_GRID_DID_PUSH_ITEMS in the process");
for (nsIFrame::ChildListIterator childLists(f);
!childLists.IsDone(); childLists.Next()) {
if (!itemLists.Contains(childLists.CurrentID())) {
MOZ_ASSERT(absLists.Contains(childLists.CurrentID()),
"unexpected non-empty child list");
continue;
}
for (auto child : childLists.CurrentList()) {
MOZ_ASSERT(f == this || child->GetPrevInFlow(),
"all pushed items must be pulled up before reflow");
}
}
}
// If we have a prev-in-flow, each of its children's next-in-flow
// should be one of our children or be null.
const auto pif = static_cast<nsGridContainerFrame*>(GetPrevInFlow());
if (pif) {
const nsFrameList* oc =
GetPropTableFrames(OverflowContainersProperty());
const nsFrameList* eoc =
GetPropTableFrames(ExcessOverflowContainersProperty());
const nsFrameList* pifEOC =
pif->GetPropTableFrames(ExcessOverflowContainersProperty());
for (const nsIFrame* child : pif->GetChildList(kPrincipalList)) {
const nsIFrame* childNIF = child->GetNextInFlow();
MOZ_ASSERT(!childNIF || mFrames.ContainsFrame(childNIF) ||
(pifEOC && pifEOC->ContainsFrame(childNIF)) ||
(oc && oc->ContainsFrame(childNIF)) ||
(eoc && eoc->ContainsFrame(childNIF)));
}
}
}
void
nsGridContainerFrame::TrackSize::Dump() const
{
printf("mPosition=%d mBase=%d mLimit=%d", mPosition, mBase, mLimit);
printf(" min:");
if (mState & eAutoMinSizing) {
printf("auto ");
} else if (mState & eMinContentMinSizing) {
printf("min-content ");
} else if (mState & eMaxContentMinSizing) {
printf("max-content ");
}
printf(" max:");
if (mState & eAutoMaxSizing) {
printf("auto ");
} else if (mState & eMinContentMaxSizing) {
printf("min-content ");
} else if (mState & eMaxContentMaxSizing) {
printf("max-content ");
} else if (mState & eFlexMaxSizing) {
printf("flex ");
}
if (mState & eFrozen) {
printf("frozen ");
}
if (mState & eBreakBefore) {
printf("break-before ");
}
}
#endif // DEBUG
nsGridContainerFrame*
nsGridContainerFrame::GetGridFrameWithComputedInfo(nsIFrame* aFrame)
{
// Prepare a lambda function that we may need to call multiple times.
auto GetGridContainerFrame = [](nsIFrame *aFrame) {
// Return the aFrame's content insertion frame, iff it is
// a grid container.
nsGridContainerFrame* gridFrame = nullptr;
if (aFrame) {
nsIFrame* contentFrame = aFrame->GetContentInsertionFrame();
if (contentFrame &&
(contentFrame->GetType() == nsGkAtoms::gridContainerFrame)) {
gridFrame = static_cast<nsGridContainerFrame*>(contentFrame);
}
}
return gridFrame;
};
nsGridContainerFrame* gridFrame = GetGridContainerFrame(aFrame);
if (gridFrame) {
// if any of our properties are missing, generate them
bool reflowNeeded = (!gridFrame->Properties().Has(GridColTrackInfo()) ||
!gridFrame->Properties().Has(GridRowTrackInfo()) ||
!gridFrame->Properties().Has(GridColumnLineInfo()) ||
!gridFrame->Properties().Has(GridRowLineInfo()));
if (reflowNeeded) {
// Trigger a reflow that generates additional grid property data.
nsIPresShell* shell = gridFrame->PresContext()->PresShell();
gridFrame->AddStateBits(NS_STATE_GRID_GENERATE_COMPUTED_VALUES);
shell->FrameNeedsReflow(gridFrame,
nsIPresShell::eResize,
NS_FRAME_IS_DIRTY);
shell->FlushPendingNotifications(FlushType::Layout);
// Since the reflow may have side effects, get the grid frame again.
gridFrame = GetGridContainerFrame(aFrame);
// Assert the grid properties are present
MOZ_ASSERT(!gridFrame ||
gridFrame->Properties().Has(GridColTrackInfo()));
MOZ_ASSERT(!gridFrame ||
gridFrame->Properties().Has(GridRowTrackInfo()));
MOZ_ASSERT(!gridFrame ||
gridFrame->Properties().Has(GridColumnLineInfo()));
MOZ_ASSERT(!gridFrame ||
gridFrame->Properties().Has(GridRowLineInfo()));
}
}
return gridFrame;
}