Bug 983175 - Part 2: Add style system support for 'subgrid' in the grid-template* properties. r=dholbert

http://dev.w3.org/csswg/css-grid/#subgrids
This commit is contained in:
Simon Sapin 2014-03-27 11:54:40 -04:00
parent 92a6d809c4
commit be63f95a8a
11 changed files with 287 additions and 92 deletions

View File

@ -522,6 +522,7 @@ CSS_KEY(style, style)
CSS_KEY(styleset, styleset)
CSS_KEY(stylistic, stylistic)
CSS_KEY(sub, sub)
CSS_KEY(subgrid, subgrid)
CSS_KEY(super, super)
CSS_KEY(sw-resize, sw_resize)
CSS_KEY(swash, swash)

View File

@ -657,6 +657,7 @@ protected:
//
// If aValue is already a eCSSUnit_List, append to that list.
CSSParseResult ParseGridLineNames(nsCSSValue& aValue);
bool ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue);
bool ParseGridTrackBreadth(nsCSSValue& aValue);
CSSParseResult ParseGridTrackSize(nsCSSValue& aValue);
bool ParseGridAutoColumnsRows(nsCSSProperty aPropID);
@ -7005,6 +7006,29 @@ CSSParserImpl::ParseGridLineNames(nsCSSValue& aValue)
}
}
// Assuming a 'subgrid' keyword was already consumed, parse <line-name-list>?
bool
CSSParserImpl::ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue)
{
nsCSSValueList* item = aValue.SetListValue();
// This marker distinguishes the value from a <track-list>.
item->mValue.SetIntValue(NS_STYLE_GRID_TEMPLATE_SUBGRID,
eCSSUnit_Enumerated);
for (;;) {
nsCSSValue lineNames;
CSSParseResult result = ParseGridLineNames(lineNames);
if (result == CSSParseResult::NotFound) {
return true;
}
if (result == CSSParseResult::Error) {
return false;
}
item->mNext = new nsCSSValueList;
item = item->mNext;
item->mValue = lineNames;
}
}
// Parse a <track-breadth>
bool
CSSParserImpl::ParseGridTrackBreadth(nsCSSValue& aValue)
@ -7126,7 +7150,18 @@ CSSParserImpl::ParseGridTemplateColumnsRows(nsCSSProperty aPropID)
AppendValue(aPropID, value);
return true;
}
// FIXME (bug 983175): add subgrid parsing
nsSubstring* ident = NextIdent();
if (ident) {
if (ident->LowerCaseEqualsLiteral("subgrid")) {
if (!ParseOptionalLineNameListAfterSubgrid(value)) {
return false;
}
AppendValue(aPropID, value);
return true;
}
UngetToken();
}
nsCSSValue firstLineNames;
if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error ||
@ -7270,6 +7305,8 @@ CSSParserImpl::ParseGridTemplate()
// TODO (bug 983175): add parsing for 'subgrid' by itself
// 'none' can appear either by itself,
// or as the beginning of <'grid-template-columns'> / <'grid-template-rows'>
if (ParseVariant(value, VARIANT_NONE, nullptr)) {
AppendValue(eCSSProperty_grid_template_columns, value);
if (ExpectSymbol('/', true)) {
@ -7280,8 +7317,33 @@ CSSParserImpl::ParseGridTemplate()
return true;
}
// TODO (bug 983175): add parsing for subgrid
// as part of <'grid-template-columns'>
// 'subgrid' can appear either by itself,
// or as the beginning of <'grid-template-columns'> / <'grid-template-rows'>
nsSubstring* ident = NextIdent();
if (ident) {
if (ident->LowerCaseEqualsLiteral("subgrid")) {
if (!ParseOptionalLineNameListAfterSubgrid(value)) {
return false;
}
AppendValue(eCSSProperty_grid_template_columns, value);
if (ExpectSymbol('/', true)) {
return ParseGridTemplateAfterSlash(/* aColumnsIsTrackList = */ false);
}
if (value.GetListValue()->mNext) {
// Non-empty <line-name-list> after 'subgrid'.
// This is only valid as part of <'grid-template-columns'>,
// which must be followed by a slash.
return false;
}
// 'subgrid' by itself sets both grid-template-columns
// and grid-template-rows.
AppendValue(eCSSProperty_grid_template_rows, value);
value.SetNoneValue();
AppendValue(eCSSProperty_grid_template_areas, value);
return true;
}
UngetToken();
}
// [ <line-names>? ] here is ambiguous:
// it can be either the start of a <track-list>,
@ -7309,34 +7371,61 @@ CSSParserImpl::ParseGridTemplate()
}
// Helper for parsing the 'grid-template' shorthand
//
// NOTE: This parses the portion after the slash, for *one* of the
// following types of expressions:
// - <'grid-template-columns'> / <'grid-template-rows'>
// - <track-list> / [ <line-names>? <string> <track-size>? <line-names>? ]+
//
// We don't know which type of expression we've got until we've parsed the
// second half, since the pre-slash part is ambiguous. The various return
// clauses below are labeled with the type of expression they're completing.
bool
CSSParserImpl::ParseGridTemplateAfterSlash(bool aColumnsIsTrackList)
{
nsCSSValue rowsValue;
if (!ParseVariant(rowsValue, VARIANT_NONE, nullptr)) {
// TODO (bug 983175): add parsing for subgrid
// as part of <'grid-template-rows'>
if (ParseVariant(rowsValue, VARIANT_NONE, nullptr)) {
// <'grid-template-columns'> / <'grid-template-rows'>
AppendValue(eCSSProperty_grid_template_rows, rowsValue);
nsCSSValue areasValue(eCSSUnit_None); // implied
AppendValue(eCSSProperty_grid_template_areas, areasValue);
return true;
}
nsCSSValue firstLineNames;
if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error ||
!GetToken(true)) {
return false;
}
if (aColumnsIsTrackList && mToken.mType == eCSSToken_String) {
return ParseGridTemplateAfterString(firstLineNames);
nsSubstring* ident = NextIdent();
if (ident) {
if (ident->LowerCaseEqualsLiteral("subgrid")) {
if (!ParseOptionalLineNameListAfterSubgrid(rowsValue)) {
return false;
}
// <'grid-template-columns'> / <'grid-template-rows'>
AppendValue(eCSSProperty_grid_template_rows, rowsValue);
nsCSSValue areasValue(eCSSUnit_None); // implied
AppendValue(eCSSProperty_grid_template_areas, areasValue);
return true;
}
UngetToken();
if (!ParseGridTrackListWithFirstLineNames(rowsValue, firstLineNames)) {
return false;
}
}
nsCSSValue firstLineNames;
if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error ||
!GetToken(true)) {
return false;
}
if (aColumnsIsTrackList && mToken.mType == eCSSToken_String) {
// [ <track-list> / ]? [ <line-names>? <string> <track-size>? <line-names>? ]+
return ParseGridTemplateAfterString(firstLineNames);
}
UngetToken();
if (!ParseGridTrackListWithFirstLineNames(rowsValue, firstLineNames)) {
return false;
}
// <'grid-template-columns'> / <'grid-template-rows'>
// rowsValue is set by either ParseVariant
// or ParseGridTrackListWithFirstLineNames
AppendValue(eCSSProperty_grid_template_rows, rowsValue);
nsCSSValue areasValue(eCSSUnit_None); // implied
AppendValue(eCSSProperty_grid_template_areas, areasValue);
AppendValue(eCSSProperty_grid_template_rows, rowsValue);
return true;
}

View File

@ -1679,13 +1679,24 @@ AppendGridTemplateToString(const nsCSSValueList* val,
nsCSSValue::Serialization aSerialization)
{
// This is called for the "list" that's the top-level value of the property.
bool isSubgrid = false;
for (;;) {
bool addSpaceSeparator = true;
nsCSSUnit unit = val->mValue.GetUnit();
if (unit == eCSSUnit_Null) {
// Empty or omitted <line-names>. Serializes to nothing.
addSpaceSeparator = false; // Avoid a double space.
if (unit == eCSSUnit_Enumerated &&
val->mValue.GetIntValue() == NS_STYLE_GRID_TEMPLATE_SUBGRID) {
isSubgrid = true;
aResult.AppendLiteral("subgrid");
} else if (unit == eCSSUnit_Null) {
// Empty or omitted <line-names>.
if (isSubgrid) {
aResult.AppendLiteral("()");
} else {
// Serializes to nothing.
addSpaceSeparator = false; // Avoid a double space.
}
} else if (unit == eCSSUnit_List || unit == eCSSUnit_ListDep) {
// Non-empty <line-names>

View File

@ -2302,14 +2302,15 @@ nsComputedDOMStyle::GetGridLineNames(const nsTArray<nsString>& aLineNames)
nsROCSSPrimitiveValue *val = new nsROCSSPrimitiveValue;
nsAutoString lineNamesString;
uint32_t i_end = aLineNames.Length();
MOZ_ASSERT(i_end > 0, "GetGridLineNames called with an empty array");
lineNamesString.AssignLiteral("(");
for (uint32_t i = 0;;) {
nsStyleUtil::AppendEscapedCSSIdent(aLineNames[i], lineNamesString);
if (++i == i_end) {
break;
if (i_end > 0) {
for (uint32_t i = 0;;) {
nsStyleUtil::AppendEscapedCSSIdent(aLineNames[i], lineNamesString);
if (++i == i_end) {
break;
}
lineNamesString.AppendLiteral(" ");
}
lineNamesString.AppendLiteral(" ");
}
lineNamesString.AppendLiteral(")");
val->SetString(lineNamesString);
@ -2353,8 +2354,24 @@ nsComputedDOMStyle::GetGridTrackSize(const nsStyleCoord& aMinValue,
}
CSSValue*
nsComputedDOMStyle::GetGridTrackList(const nsStyleGridTrackList& aTrackList)
nsComputedDOMStyle::GetGridTemplateColumnsRows(const nsStyleGridTemplate& aTrackList)
{
if (aTrackList.mIsSubgrid) {
NS_ASSERTION(aTrackList.mMinTrackSizingFunctions.IsEmpty() &&
aTrackList.mMaxTrackSizingFunctions.IsEmpty(),
"Unexpected sizing functions with subgrid");
nsDOMCSSValueList* valueList = GetROCSSValueList(false);
nsROCSSPrimitiveValue* subgridKeyword = new nsROCSSPrimitiveValue;
subgridKeyword->SetIdent(eCSSKeyword_subgrid);
valueList->AppendCSSValue(subgridKeyword);
for (uint32_t i = 0; i < aTrackList.mLineNameLists.Length(); i++) {
valueList->AppendCSSValue(GetGridLineNames(aTrackList.mLineNameLists[i]));
}
return valueList;
}
uint32_t numSizes = aTrackList.mMinTrackSizingFunctions.Length();
MOZ_ASSERT(aTrackList.mMaxTrackSizingFunctions.Length() == numSizes,
"Different number of min and max track sizing functions");
@ -2373,7 +2390,7 @@ nsComputedDOMStyle::GetGridTrackList(const nsStyleGridTrackList& aTrackList)
for (uint32_t i = 0;; i++) {
const nsTArray<nsString>& lineNames = aTrackList.mLineNameLists[i];
if (!lineNames.IsEmpty()) {
valueList->AppendCSSValue(GetGridLineNames(aTrackList.mLineNameLists[i]));
valueList->AppendCSSValue(GetGridLineNames(lineNames));
}
if (i == numSizes) {
break;
@ -2416,13 +2433,13 @@ nsComputedDOMStyle::DoGetGridAutoRows()
CSSValue*
nsComputedDOMStyle::DoGetGridTemplateColumns()
{
return GetGridTrackList(StylePosition()->mGridTemplateColumns);
return GetGridTemplateColumnsRows(StylePosition()->mGridTemplateColumns);
}
CSSValue*
nsComputedDOMStyle::DoGetGridTemplateRows()
{
return GetGridTrackList(StylePosition()->mGridTemplateRows);
return GetGridTemplateColumnsRows(StylePosition()->mGridTemplateRows);
}
CSSValue*

View File

@ -192,7 +192,7 @@ private:
mozilla::dom::CSSValue* GetGridLineNames(const nsTArray<nsString>& aLineNames);
mozilla::dom::CSSValue* GetGridTrackSize(const nsStyleCoord& aMinSize,
const nsStyleCoord& aMaxSize);
mozilla::dom::CSSValue* GetGridTrackList(const nsStyleGridTrackList& aTrackList);
mozilla::dom::CSSValue* GetGridTemplateColumnsRows(const nsStyleGridTemplate& aTrackList);
mozilla::dom::CSSValue* GetGridLine(const nsStyleGridLine& aGridLine);
bool GetLineHeightCoord(nscoord& aCoord);

View File

@ -7161,10 +7161,27 @@ SetGridAutoColumnsRows(const nsCSSValue& aValue,
}
}
static void
AppendGridLineNames(const nsCSSValue& aValue,
nsStyleGridTemplate& aResult)
{
// Compute a <line-names> value
nsTArray<nsString>* nameList = aResult.mLineNameLists.AppendElement();
// Null unit means empty list, nothing more to do.
if (aValue.GetUnit() != eCSSUnit_Null) {
const nsCSSValueList* item = aValue.GetListValue();
do {
nsString* name = nameList->AppendElement();
item->mValue.GetStringValue(*name);
item = item->mNext;
} while (item);
}
}
static void
SetGridTrackList(const nsCSSValue& aValue,
nsStyleGridTrackList& aResult,
const nsStyleGridTrackList& aParentValue,
nsStyleGridTemplate& aResult,
const nsStyleGridTemplate& aParentValue,
nsStyleContext* aStyleContext,
nsPresContext* aPresContext,
bool& aCanStoreInRuleTree)
@ -7176,6 +7193,7 @@ SetGridTrackList(const nsCSSValue& aValue,
case eCSSUnit_Inherit:
aCanStoreInRuleTree = false;
aResult.mIsSubgrid = aParentValue.mIsSubgrid;
aResult.mLineNameLists = aParentValue.mLineNameLists;
aResult.mMinTrackSizingFunctions = aParentValue.mMinTrackSizingFunctions;
aResult.mMaxTrackSizingFunctions = aParentValue.mMaxTrackSizingFunctions;
@ -7184,6 +7202,7 @@ SetGridTrackList(const nsCSSValue& aValue,
case eCSSUnit_Initial:
case eCSSUnit_Unset:
case eCSSUnit_None:
aResult.mIsSubgrid = false;
aResult.mLineNameLists.Clear();
aResult.mMinTrackSizingFunctions.Clear();
aResult.mMaxTrackSizingFunctions.Clear();
@ -7193,42 +7212,45 @@ SetGridTrackList(const nsCSSValue& aValue,
aResult.mLineNameLists.Clear();
aResult.mMinTrackSizingFunctions.Clear();
aResult.mMaxTrackSizingFunctions.Clear();
// This list is expected to have odd number of items, at least 3
// starting with a <line-names> (sub list of identifiers),
// and alternating between that and <track-size>.
const nsCSSValueList* item = aValue.GetListValue();
for (;;) {
// Compute a <line-names> value
nsTArray<nsString>* nameList = aResult.mLineNameLists.AppendElement();
// Null unit means empty list, nothing more to do.
if (item->mValue.GetUnit() != eCSSUnit_Null) {
const nsCSSValueList* subItem = item->mValue.GetListValue();
do {
nsString* name = nameList->AppendElement();
subItem->mValue.GetStringValue(*name);
subItem = subItem->mNext;
} while (subItem);
}
if (item->mValue.GetUnit() == eCSSUnit_Enumerated &&
item->mValue.GetIntValue() == NS_STYLE_GRID_TEMPLATE_SUBGRID) {
// subgrid <line-name-list>?
aResult.mIsSubgrid = true;
item = item->mNext;
if (!item) {
break;
while (item) {
AppendGridLineNames(item->mValue, aResult);
item = item->mNext;
}
} else {
// <track-list>
// The list is expected to have odd number of items, at least 3
// starting with a <line-names> (sub list of identifiers),
// and alternating between that and <track-size>.
aResult.mIsSubgrid = false;
for (;;) {
AppendGridLineNames(item->mValue, aResult);
item = item->mNext;
nsStyleCoord& min = *aResult.mMinTrackSizingFunctions.AppendElement();
nsStyleCoord& max = *aResult.mMaxTrackSizingFunctions.AppendElement();
SetGridTrackSize(item->mValue, min, max,
aStyleContext, aPresContext, aCanStoreInRuleTree);
if (!item) {
break;
}
item = item->mNext;
MOZ_ASSERT(item, "Expected a eCSSUnit_List of odd length");
nsStyleCoord& min = *aResult.mMinTrackSizingFunctions.AppendElement();
nsStyleCoord& max = *aResult.mMaxTrackSizingFunctions.AppendElement();
SetGridTrackSize(item->mValue, min, max,
aStyleContext, aPresContext, aCanStoreInRuleTree);
item = item->mNext;
MOZ_ASSERT(item, "Expected a eCSSUnit_List of odd length");
}
MOZ_ASSERT(!aResult.mMinTrackSizingFunctions.IsEmpty() &&
aResult.mMinTrackSizingFunctions.Length() ==
aResult.mMaxTrackSizingFunctions.Length() &&
aResult.mMinTrackSizingFunctions.Length() + 1 ==
aResult.mLineNameLists.Length(),
"Inconstistent array lengths for nsStyleGridTemplate");
}
MOZ_ASSERT(!aResult.mMinTrackSizingFunctions.IsEmpty() &&
aResult.mMinTrackSizingFunctions.Length() ==
aResult.mMaxTrackSizingFunctions.Length() &&
aResult.mMinTrackSizingFunctions.Length() + 1 ==
aResult.mLineNameLists.Length(),
"Inconstistent array lengths for nsStyleGridTrackList");
}
}

View File

@ -545,7 +545,11 @@ static inline mozilla::css::Side operator++(mozilla::css::Side& side, int) {
#define NS_STYLE_GRID_AUTO_FLOW_ROW (1 << 2)
#define NS_STYLE_GRID_AUTO_FLOW_DENSE (1 << 3)
// 'subgrid' keyword in grid-template-{columns,rows}
#define NS_STYLE_GRID_TEMPLATE_SUBGRID 0
// CSS Grid <track-breadth> keywords
// Should not overlap with NS_STYLE_GRID_TEMPLATE_SUBGRID
#define NS_STYLE_GRID_TRACK_BREADTH_MAX_CONTENT 1
#define NS_STYLE_GRID_TRACK_BREADTH_MIN_CONTENT 2

View File

@ -1257,7 +1257,7 @@ nsStylePosition::nsStylePosition(void)
// Other members get their default constructors
// which initialize them to representations of their respective initial value.
// mGridTemplateAreas: nullptr for 'none'
// mGridTemplate{Rows,Columns}: empty arrays for 'none'
// mGridTemplate{Rows,Columns}: false and empty arrays for 'none'
// mGrid{Column,Row}{Start,End}: false/0/empty values for 'auto'
}

View File

@ -1157,32 +1157,48 @@ public:
nsRect mImageRegion; // [inherited] the rect to use within an image
};
struct nsStyleGridTrackList {
// http://dev.w3.org/csswg/css-grid/#track-sizing
// This represents either:
// * 'none': all three arrays are empty
// * A <track-list>: mMinTrackSizingFunctions and mMaxTrackSizingFunctions
// are of identical non-zero size,
// and mLineNameLists is one element longer than that.
// (Delimiting N columns requires N+1 lines:
// one before each track, plus one at the very end.)
//
// An omitted <line-names> is still represented in mLineNameLists,
// as an empty sub-array.
//
// A <track-size> specified as a single <track-breadth> is represented
// as identical min and max sizing functions.
//
// The units for nsStyleCoord are:
// * eStyleUnit_Percent represents a <percentage>
// * eStyleUnit_FlexFraction represents a <flex> flexible fraction
// * eStyleUnit_Coord represents a <length>
// * eStyleUnit_Enumerated represents min-content or max-content
// Computed value of the grid-template-columns or grid-columns-rows property
// (but *not* grid-template-areas.)
// http://dev.w3.org/csswg/css-grid/#track-sizing
//
// This represents either:
// * none:
// mIsSubgrid is false, all three arrays are empty
// * <track-list>:
// mIsSubgrid is false,
// mMinTrackSizingFunctions and mMaxTrackSizingFunctions
// are of identical non-zero size,
// and mLineNameLists is one element longer than that.
// (Delimiting N columns requires N+1 lines:
// one before each track, plus one at the very end.)
//
// An omitted <line-names> is still represented in mLineNameLists,
// as an empty sub-array.
//
// A <track-size> specified as a single <track-breadth> is represented
// as identical min and max sizing functions.
//
// The units for nsStyleCoord are:
// * eStyleUnit_Percent represents a <percentage>
// * eStyleUnit_FlexFraction represents a <flex> flexible fraction
// * eStyleUnit_Coord represents a <length>
// * eStyleUnit_Enumerated represents min-content or max-content
// * subgrid <line-name-list>?:
// mIsSubgrid is true,
// mLineNameLists may or may not be empty,
// mMinTrackSizingFunctions and mMaxTrackSizingFunctions are empty.
struct nsStyleGridTemplate {
bool mIsSubgrid;
nsTArray<nsTArray<nsString>> mLineNameLists;
nsTArray<nsStyleCoord> mMinTrackSizingFunctions;
nsTArray<nsStyleCoord> mMaxTrackSizingFunctions;
inline bool operator!=(const nsStyleGridTrackList& aOther) const {
nsStyleGridTemplate()
: mIsSubgrid(false)
{
}
inline bool operator!=(const nsStyleGridTemplate& aOther) const {
return mLineNameLists != aOther.mLineNameLists ||
mMinTrackSizingFunctions != aOther.mMinTrackSizingFunctions ||
mMaxTrackSizingFunctions != aOther.mMaxTrackSizingFunctions;
@ -1298,8 +1314,8 @@ struct nsStylePosition {
// need to have their copy constructor called when we're being copied.
// See nsStylePosition::nsStylePosition(const nsStylePosition& aSource)
// in nsStyleStruct.cpp
nsStyleGridTrackList mGridTemplateColumns;
nsStyleGridTrackList mGridTemplateRows;
nsStyleGridTemplate mGridTemplateColumns;
nsStyleGridTemplate mGridTemplateRows;
// nullptr for 'none'
nsRefPtr<mozilla::css::GridTemplateAreasValue> mGridTemplateAreas;

View File

@ -4880,6 +4880,9 @@ if (SpecialPowers.getBoolPref("layout.css.grid.enabled")) {
// See https://bugzilla.mozilla.org/show_bug.cgi?id=981300
"(none auto subgrid min-content max-content foo) 40px",
"subgrid",
"subgrid () (foo bar)",
],
invalid_values: [
"",
@ -4908,6 +4911,9 @@ if (SpecialPowers.getBoolPref("layout.css.grid.enabled")) {
"maxmin(100px, 20px)",
"minmax(min-content, auto)",
"minmax(min-content, minmax(30px, max-content))",
"subgrid (foo) 40px",
"subgrid (foo 40px)",
"(foo) subgrid",
]
};
gCSSProperties["grid-template-rows"] = {
@ -4954,11 +4960,18 @@ if (SpecialPowers.getBoolPref("layout.css.grid.enabled")) {
"none / none",
],
other_values: [
"subgrid",
// <'grid-template-columns'> / <'grid-template-rows'>
"40px / 100px",
"(foo) 40px (bar) / (baz) 100px (fizz)",
" none/100px",
"40px/none",
"subgrid/40px 20px",
"subgrid (foo) () (bar baz) / 40px 20px",
"40px 20px/subgrid",
"40px 20px/subgrid (foo) () (bar baz)",
"subgrid/subgrid",
"subgrid (foo) () (bar baz)/subgrid (foo) () (bar baz)",
// [ <track-list> / ]? [ <line-names>? <string> <track-size>? <line-names>? ]+
"'fizz'",
"(bar) 'fizz'",
@ -4970,7 +4983,9 @@ if (SpecialPowers.getBoolPref("layout.css.grid.enabled")) {
"(foo) 40px / (bar) 'fizz' 100px (buzz) \n (a) '.' 200px (b)",
],
invalid_values: [
"subgrid", // TODO
"subgrid ()",
"subgrid () / 'fizz'",
"subgrid / 'fizz'",
"(foo) (bar) 40px / 100px",
"40px / (fizz) (buzz) 100px",
"40px / (fizz) (buzz) 'foo'",

View File

@ -82,6 +82,26 @@ var grid_template_test_cases = [
gridTemplateColumns: "(foo) 40px",
gridTemplateRows: "(bar) 100px (buzz a) 200px (b)",
},
{
specified: "subgrid",
gridTemplateColumns: "subgrid",
gridTemplateRows: "subgrid",
},
{
specified: "subgrid / subgrid",
gridTemplateColumns: "subgrid",
gridTemplateRows: "subgrid",
},
{
specified: "subgrid / subgrid (foo)",
gridTemplateColumns: "subgrid",
gridTemplateRows: "subgrid (foo)",
},
{
specified: "subgrid () (foo)/ subgrid (bar",
gridTemplateColumns: "subgrid () (foo)",
gridTemplateRows: "subgrid (bar)",
},
];
grid_test_cases = grid_template_test_cases.concat([