gecko/layout/generic/nsGridContainerFrame.cpp

614 lines
21 KiB
C++
Raw Normal View History

/* -*- 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 "nsCSSAnonBoxes.h"
#include "nsPresContext.h"
#include "nsReadableUtils.h"
#include "nsStyleContext.h"
using namespace mozilla;
/**
* Search for the aNth occurrence of aName in aNameList (forward), starting at
* the zero-based aFromIndex, and return the 1-based index (line number).
* Also take into account there is an unconditional match at aImplicitLine
* unless it's zero.
* Return the last match if aNth occurrences can't be found, or zero if no
* occurrence can be found.
*/
static uint32_t
FindLine(const nsString& aName, uint32_t aNth,
uint32_t aFromIndex, uint32_t aImplicitLine,
const nsTArray<nsTArray<nsString>>& aNameList)
{
MOZ_ASSERT(aNth != 0);
const uint32_t len = aNameList.Length();
uint32_t lastFound = 0;
uint32_t line;
uint32_t i = aFromIndex;
for (; i < len; i = line) {
line = i + 1;
if (line == aImplicitLine || aNameList[i].Contains(aName)) {
lastFound = line;
if (--aNth == 0) {
return lastFound;
}
}
}
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])
lastFound = aImplicitLine;
}
return lastFound;
}
/**
* @see FindLine, this function does the same but searches in reverse.
*/
static uint32_t
RFindLine(const nsString& aName, uint32_t aNth,
uint32_t aFromIndex, uint32_t aImplicitLine,
const nsTArray<nsTArray<nsString>>& aNameList)
{
MOZ_ASSERT(aNth != 0);
const uint32_t len = aNameList.Length();
uint32_t lastFound = 0;
// The implicit line may be beyond the length of aNameList so we match this
// line first if it's within the 0..aFromIndex range.
if (aImplicitLine > len && aImplicitLine < aFromIndex) {
lastFound = aImplicitLine;
if (--aNth == 0) {
return lastFound;
}
}
uint32_t i = aFromIndex == 0 ? len : std::min(aFromIndex, len);
for (; i; --i) {
if (i == aImplicitLine || aNameList[i - 1].Contains(aName)) {
lastFound = i;
if (--aNth == 0) {
break;
}
}
}
return lastFound;
}
static uint32_t
FindNamedLine(const nsString& aName, int32_t aNth,
uint32_t aFromIndex, uint32_t aImplicitLine,
const nsTArray<nsTArray<nsString>>& aNameList)
{
MOZ_ASSERT(aNth != 0);
if (aNth > 0) {
return ::FindLine(aName, aNth, aFromIndex, aImplicitLine, aNameList);
}
return ::RFindLine(aName, -aNth, aFromIndex, aImplicitLine, aNameList);
}
/**
* 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);
}
//----------------------------------------------------------------------
// 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
// ===========================================
void
nsGridContainerFrame::AddImplicitNamedAreas(
const nsTArray<nsTArray<nsString>>& aLineNameLists)
{
// http://dev.w3.org/csswg/css-grid/#implicit-named-areas
// XXX this just checks x-start .. x-end in one dimension and there's
// no other error checking. A few wrong cases (maybe):
// (x-start x-end)
// (x-start) 0 (x-start) 0 (x-end)
// (x-end) 0 (x-start) 0 (x-end)
// (x-start) 0 (x-end) 0 (x-start) 0 (x-end)
const uint32_t len = aLineNameLists.Length();
nsTHashtable<nsStringHashKey> currentStarts;
ImplicitNamedAreas* areas = GetImplicitNamedAreas();
for (uint32_t i = 0; i < len; ++i) {
const nsTArray<nsString>& names(aLineNameLists[i]);
const uint32_t jLen = names.Length();
for (uint32_t j = 0; j < jLen; ++j) {
const nsString& name = names[j];
uint32_t index;
if (::IsNameWithStartSuffix(name, &index)) {
currentStarts.PutEntry(nsDependentSubstring(name, 0, index));
} else if (::IsNameWithEndSuffix(name, &index)) {
nsDependentSubstring area(name, 0, index);
if (currentStarts.Contains(area)) {
if (!areas) {
areas = new ImplicitNamedAreas;
Properties().Set(ImplicitNamedAreasProperty(), areas);
}
areas->PutEntry(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());
}
}
uint32_t
nsGridContainerFrame::ResolveLine(
const nsStyleGridLine& aLine,
int32_t aNth,
uint32_t aFromIndex,
const nsTArray<nsTArray<nsString>>& aLineNameList,
uint32_t GridNamedArea::* aAreaStart,
uint32_t GridNamedArea::* aAreaEnd,
uint32_t aExplicitGridEnd,
LineRangeSide aSide,
const nsStylePosition* aStyle)
{
MOZ_ASSERT(!aLine.IsAuto());
uint32_t line = 0;
if (aLine.mLineName.IsEmpty()) {
MOZ_ASSERT(aNth != 0, "css-grid 9.2: <integer> must not be zero.");
line = std::max(int32_t(aFromIndex) + aNth, 1);
} 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;
}
// XXX must Implicit Named Areas have all four lines?
// http://dev.w3.org/csswg/css-grid/#implicit-named-areas
line = ::FindNamedLine(lineName, aNth, aFromIndex, implicitLine,
aLineNameList);
}
}
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 = ::FindNamedLine(aLine.mLineName, aNth, aFromIndex, implicitLine,
aLineNameList);
}
if (line == 0) {
// No line matching <custom-ident> exists.
if (aLine.mHasSpan) {
// http://dev.w3.org/csswg/css-grid/#grid-placement-span-int
// Treat 'span <custom-ident> N' as 'span N'.
line = std::max(int32_t(aFromIndex) + aNth, 1);
} else {
// http://dev.w3.org/csswg/css-grid/#grid-placement-int
// Treat '<custom-ident> N' as first/last line depending on N's sign.
// XXX this is wrong due to a spec change, see bug 1009776 comment 17.
// XXX we want to possibly expand the implicit grid here.
line = aNth >= 0 ? 1 : aExplicitGridEnd;
}
}
}
// The only case which can result in "auto" (line == 0) is a plain
// <custom-ident> (without <integer> or 'span') which wasn't found.
MOZ_ASSERT(line != 0 || (!aLine.mHasSpan && aLine.mInteger == 0),
"Given a <integer> or 'span' the result should never be auto");
return line;
}
nsGridContainerFrame::LinePair
nsGridContainerFrame::ResolveLineRangeHelper(
const nsStyleGridLine& aStart,
const nsStyleGridLine& aEnd,
const nsTArray<nsTArray<nsString>>& aLineNameList,
uint32_t GridNamedArea::* aAreaStart,
uint32_t GridNamedArea::* aAreaEnd,
uint32_t aExplicitGridEnd,
const nsStylePosition* aStyle)
{
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(0, aStart.mInteger);
}
// span <custom-ident> / span *
// span <custom-ident> / auto
return LinePair(0, 1); // XXX subgrid explicit size instead of 1?
}
auto end = ResolveLine(aEnd, aEnd.mInteger, 0, aLineNameList, aAreaStart,
aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd,
aStyle);
if (end == 0) {
// span * / <custom-ident> that can't be found
return LinePair(0, aStart.mInteger);
}
int32_t span = aStart.mInteger == 0 ? 1 : aStart.mInteger;
auto start = ResolveLine(aStart, -span, end, aLineNameList, aAreaStart,
aAreaEnd, aExplicitGridEnd, eLineRangeSideStart,
aStyle);
MOZ_ASSERT(start > 0, "A start span can never resolve to 'auto'");
return LinePair(start, end);
}
uint32_t start = 0;
if (!aStart.IsAuto()) {
start = ResolveLine(aStart, aStart.mInteger, 0, aLineNameList, aAreaStart,
aAreaEnd, aExplicitGridEnd, eLineRangeSideStart,
aStyle);
}
if (aEnd.IsAuto()) {
// * (except span) / auto
return LinePair(start, 1); // XXX subgrid explicit size instead of 1?
}
if (start == 0 && aEnd.mHasSpan) {
if (aEnd.mLineName.IsEmpty()) {
// auto (or not found <custom-ident>) / span <integer>
MOZ_ASSERT(aEnd.mInteger != 0);
return LinePair(0, aEnd.mInteger);
}
// http://dev.w3.org/csswg/css-grid/#grid-placement-errors
// auto (or not found <custom-ident>) / span <custom-ident>
return LinePair(0, 1); // XXX subgrid explicit size instead of 1?
}
uint32_t from = aEnd.mHasSpan ? start : 0;
auto end = ResolveLine(aEnd, aEnd.mInteger, from, aLineNameList, aAreaStart,
aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd, aStyle);
if (end == 0) {
// * (except span) / not found <custom-ident>
end = 1; // XXX subgrid explicit size instead of 1?
} else if (start == 0) {
// auto (or not found <custom-ident>) / definite line
start = std::max(1U, end - 1);
}
return LinePair(start, end);
}
nsGridContainerFrame::LineRange
nsGridContainerFrame::ResolveLineRange(
const nsStyleGridLine& aStart,
const nsStyleGridLine& aEnd,
const nsTArray<nsTArray<nsString>>& aLineNameList,
uint32_t GridNamedArea::* aAreaStart,
uint32_t GridNamedArea::* aAreaEnd,
uint32_t aExplicitGridEnd,
const nsStylePosition* aStyle)
{
LinePair r = ResolveLineRangeHelper(aStart, aEnd, aLineNameList, aAreaStart,
aAreaEnd, aExplicitGridEnd, aStyle);
MOZ_ASSERT(r.second != 0);
// http://dev.w3.org/csswg/css-grid/#grid-placement-errors
if (r.second <= r.first) {
r.second = r.first + 1;
}
return LineRange(r.first, r.second);
}
nsGridContainerFrame::GridArea
nsGridContainerFrame::PlaceDefinite(nsIFrame* aChild,
const nsStylePosition* aStyle)
{
const nsStylePosition* itemStyle = aChild->StylePosition();
return GridArea(
ResolveLineRange(itemStyle->mGridColumnStart, itemStyle->mGridColumnEnd,
aStyle->mGridTemplateColumns.mLineNameLists,
&GridNamedArea::mColumnStart, &GridNamedArea::mColumnEnd,
mExplicitGridColEnd, aStyle),
ResolveLineRange(itemStyle->mGridRowStart, itemStyle->mGridRowEnd,
aStyle->mGridTemplateRows.mLineNameLists,
&GridNamedArea::mRowStart, &GridNamedArea::mRowEnd,
mExplicitGridRowEnd, aStyle));
}
void
nsGridContainerFrame::InitializeGridBounds(const nsStylePosition* aStyle)
{
// http://dev.w3.org/csswg/css-grid/#grid-definition
uint32_t colEnd = aStyle->mGridTemplateColumns.mLineNameLists.Length();
uint32_t rowEnd = aStyle->mGridTemplateRows.mLineNameLists.Length();
auto areas = aStyle->mGridTemplateAreas.get();
mExplicitGridColEnd = std::max(colEnd, areas ? areas->mNColumns + 1 : 1);
mExplicitGridRowEnd = std::max(rowEnd, areas ? areas->NRows() + 1 : 1);
mGridColEnd = mExplicitGridColEnd;
mGridRowEnd = mExplicitGridRowEnd;
}
void
nsGridContainerFrame::PlaceGridItems(const nsStylePosition* aStyle)
{
InitializeGridBounds(aStyle);
// http://dev.w3.org/csswg/css-grid/#auto-placement-algo
// Step 1, place definite positions.
for (nsFrameList::Enumerator e(PrincipalChildList()); !e.AtEnd(); e.Next()) {
nsIFrame* child = e.get();
const GridArea& area = PlaceDefinite(child, aStyle);
GridArea* prop = GetGridAreaForChild(child);
if (prop) {
*prop = area;
} else {
child->Properties().Set(GridAreaProperty(), new GridArea(area));
}
if (area.IsDefinite()) {
InflateGridFor(area);
}
}
}
void
nsGridContainerFrame::Reflow(nsPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus)
{
DO_GLOBAL_REFLOW_COUNT("nsGridContainerFrame");
DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
if (IsFrameTreeTooDeep(aReflowState, aDesiredSize, aStatus)) {
return;
}
#ifdef DEBUG
SanityCheckAnonymousGridItems();
#endif // DEBUG
LogicalMargin bp = aReflowState.ComputedLogicalBorderPadding();
bp.ApplySkipSides(GetLogicalSkipSides());
nscoord contentBSize = GetEffectiveComputedBSize(aReflowState);
if (contentBSize == NS_AUTOHEIGHT) {
contentBSize = 0;
}
WritingMode wm = aReflowState.GetWritingMode();
LogicalSize finalSize(wm,
aReflowState.ComputedISize() + bp.IStartEnd(wm),
contentBSize + bp.BStartEnd(wm));
aDesiredSize.SetSize(wm, finalSize);
aDesiredSize.SetOverflowAreasToDesiredBounds();
const nsStylePosition* stylePos = aReflowState.mStylePosition;
InitImplicitNamedAreas(stylePos);
PlaceGridItems(stylePos);
aStatus = NS_FRAME_COMPLETE;
NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
}
nsIAtom*
nsGridContainerFrame::GetType() const
{
return nsGkAtoms::gridContainerFrame;
}
#ifdef DEBUG_FRAME_DUMP
nsresult
nsGridContainerFrame::GetFrameName(nsAString& aResult) const
{
return MakeFrameName(NS_LITERAL_STRING("GridContainer"), aResult);
}
#endif
void
nsGridContainerFrame::CellMap::Fill(const GridArea& aGridArea)
{
MOZ_ASSERT(aGridArea.IsDefinite());
MOZ_ASSERT(aGridArea.mRows.mStart < aGridArea.mRows.mEnd);
MOZ_ASSERT(aGridArea.mRows.mStart > 0);
MOZ_ASSERT(aGridArea.mCols.mStart < aGridArea.mCols.mEnd);
MOZ_ASSERT(aGridArea.mCols.mStart > 0);
// Line numbers are 1-based so convert them to a zero-based index.
const auto numRows = aGridArea.mRows.mEnd - 1;
const auto numCols = aGridArea.mCols.mEnd - 1;
mCells.EnsureLengthAtLeast(numRows);
for (auto i = aGridArea.mRows.mStart - 1; i < numRows; ++i) {
nsTArray<Cell>& cellsInRow = mCells[i];
cellsInRow.EnsureLengthAtLeast(numCols);
for (auto j = aGridArea.mCols.mStart - 1; j < numCols; ++j) {
cellsInRow[j].mIsOccupied = true;
}
}
}
void
nsGridContainerFrame::CellMap::ClearOccupied()
{
const size_t numRows = mCells.Length();
for (size_t i = 0; i < numRows; ++i) {
nsTArray<Cell>& cellsInRow = mCells[i];
const size_t numCols = cellsInRow.Length();
for (size_t j = 0; j < numCols; ++j) {
cellsInRow[j].mIsOccupied = false;
}
}
}
#ifdef DEBUG
void
nsGridContainerFrame::CellMap::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");
}
}
static bool
FrameWantsToBeInAnonymousGridItem(nsIFrame* aFrame)
{
// Note: This needs to match the logic in
// nsCSSFrameConstructor::FrameConstructionItem::NeedsAnonFlexOrGridItem()
return (aFrame->IsFrameOfType(nsIFrame::eLineParticipant) ||
nsGkAtoms::placeholderFrame == aFrame->GetType());
}
// Debugging method, to let us assert that our anonymous grid items are
// set up correctly -- in particular, we assert:
// (1) we don't have any inline non-replaced children
// (2) we don't have any consecutive anonymous grid items
// (3) we don't have any empty anonymous grid items
// (4) all children are on the expected child lists
void
nsGridContainerFrame::SanityCheckAnonymousGridItems() const
{
// XXX handle kOverflowContainersList / kExcessOverflowContainersList
// when we implement fragmentation?
ChildListIDs noCheckLists = kAbsoluteList | kFixedList;
ChildListIDs checkLists = kPrincipalList | kOverflowList;
for (nsIFrame::ChildListIterator childLists(this);
!childLists.IsDone(); childLists.Next()) {
if (!checkLists.Contains(childLists.CurrentID())) {
MOZ_ASSERT(noCheckLists.Contains(childLists.CurrentID()),
"unexpected non-empty child list");
continue;
}
bool prevChildWasAnonGridItem = false;
nsFrameList children = childLists.CurrentList();
for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next()) {
nsIFrame* child = e.get();
MOZ_ASSERT(!FrameWantsToBeInAnonymousGridItem(child),
"frame wants to be inside an anonymous grid item, "
"but it isn't");
if (child->StyleContext()->GetPseudo() ==
nsCSSAnonBoxes::anonymousGridItem) {
/*
XXX haven't decided yet whether to reorder children or not.
XXX If we do, we want this assertion instead of the one below.
MOZ_ASSERT(!prevChildWasAnonGridItem ||
HasAnyStateBits(NS_STATE_GRID_CHILDREN_REORDERED),
"two anon grid items in a row (shouldn't happen, unless our "
"children have been reordered with the 'order' property)");
*/
MOZ_ASSERT(!prevChildWasAnonGridItem, "two anon grid items in a row");
nsIFrame* firstWrappedChild = child->GetFirstPrincipalChild();
MOZ_ASSERT(firstWrappedChild,
"anonymous grid item is empty (shouldn't happen)");
prevChildWasAnonGridItem = true;
} else {
prevChildWasAnonGridItem = false;
}
}
}
}
#endif // DEBUG