Bug 978478 part 1: Add support for repeat() in <line-name-list> (CSS Grid) r=dholbert
authorSimon Sapin <simon.sapin@exyr.org>
Mon, 31 Mar 2014 22:49:45 -0700
changeset 194967 f3476de5a68039ddb09ee0886478c9ff99d8902a
parent 194966 9051a06f0268ba529d567e3022a5440fe71b1c60
child 194968 f5c5742ad0041f32448da8ebcf658051f7d29a1b
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs978478
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 978478 part 1: Add support for repeat() in <line-name-list> (CSS Grid) r=dholbert
layout/style/nsCSSParser.cpp
layout/style/test/property_database.js
layout/style/test/test_grid_container_shorthands.html
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -58,16 +58,22 @@ nsCSSProps::kParserVariantTable[eCSSProp
   parsevariant_,
 #include "nsCSSPropList.h"
 #undef CSS_PROP
 };
 
 // Length of the "var-" prefix of custom property names.
 #define VAR_PREFIX_LENGTH 4
 
+// Maximum number of repetitions for the repeat() function
+// in the grid-template-columns and grid-template-rows properties,
+// to limit high memory usage from small stylesheets.
+// Must be a positive integer. Should be large-ish.
+#define GRID_TEMPLATE_MAX_REPETITIONS 10000
+
 // End-of-array marker for mask arguments to ParseBitmaskValues
 #define MASK_END_VALUE  (-1)
 
 MOZ_BEGIN_ENUM_CLASS(CSSParseResult, int32_t)
   // Parsed something successfully:
   Ok,
   // Did not find what we were looking for, but did not consume any token:
   NotFound,
@@ -652,16 +658,17 @@ protected:
   // or set it to a eCSSUnit_List of eCSSUnit_Ident.
   //
   // To parse an optional <line-names> (ie. if not finding an open paren
   // is considered the same as an empty list),
   // treat CSSParseResult::NotFound the same as CSSParseResult::Ok.
   //
   // If aValue is already a eCSSUnit_List, append to that list.
   CSSParseResult ParseGridLineNames(nsCSSValue& aValue);
+  bool ParseGridLineNameListRepeat(nsCSSValueList** aTailPtr);
   bool ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue);
   bool ParseGridTrackBreadth(nsCSSValue& aValue);
   CSSParseResult ParseGridTrackSize(nsCSSValue& aValue);
   bool ParseGridAutoColumnsRows(nsCSSProperty aPropID);
 
   // Assuming a [ <line-names>? ] has already been parsed,
   // parse the rest of a <track-list>.
   //
@@ -7001,36 +7008,103 @@ CSSParserImpl::ParseGridLineNames(nsCSSV
     if (!GetToken(true) || mToken.IsSymbol(')')) {
       return CSSParseResult::Ok;
     }
     item->mNext = new nsCSSValueList;
     item = item->mNext;
   }
 }
 
+// Assuming the 'repeat(' function token has already been consumed,
+// parse the rest of repeat(<positive-integer>, <line-names>+)
+// Append to the linked list whose end is given by |aTailPtr|,
+// and updated |aTailPtr| to point to the new end of the list.
+bool
+CSSParserImpl::ParseGridLineNameListRepeat(nsCSSValueList** aTailPtr)
+{
+  if (!(GetToken(true) &&
+        mToken.mType == eCSSToken_Number &&
+        mToken.mIntegerValid &&
+        mToken.mInteger > 0)) {
+    SkipUntil(')');
+    return false;
+  }
+  int32_t repetitions = std::min(mToken.mInteger,
+                                 GRID_TEMPLATE_MAX_REPETITIONS);
+  if (!ExpectSymbol(',', true)) {
+    SkipUntil(')');
+    return false;
+  }
+
+  // Parse at least one <line-names>
+  nsCSSValueList* tail = *aTailPtr;
+  do {
+    tail->mNext = new nsCSSValueList;
+    tail = tail->mNext;
+    if (ParseGridLineNames(tail->mValue) != CSSParseResult::Ok) {
+      SkipUntil(')');
+      return false;
+    }
+  } while (!ExpectSymbol(')', true));
+  nsCSSValueList* firstRepeatedItem = (*aTailPtr)->mNext;
+  nsCSSValueList* lastRepeatedItem = tail;
+
+  // Our repeated items are already in the target list once,
+  // so they need to be repeated |repetitions - 1| more times.
+  MOZ_ASSERT(repetitions > 0, "Should have only accepted positive integers");
+  while (--repetitions) {
+    nsCSSValueList* repeatedItem = firstRepeatedItem;
+    for (;;) {
+      tail->mNext = new nsCSSValueList;
+      tail = tail->mNext;
+      tail->mValue = repeatedItem->mValue;
+      if (repeatedItem == lastRepeatedItem) {
+        break;
+      }
+      repeatedItem = repeatedItem->mNext;
+    }
+  }
+  *aTailPtr = tail;
+  return true;
+}
+
 // 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) {
+    // First try to parse repeat(<positive-integer>, <line-names>+)
+    if (!GetToken(true)) {
       return true;
     }
-    if (result == CSSParseResult::Error) {
-      return false;
-    }
-    item->mNext = new nsCSSValueList;
-    item = item->mNext;
-    item->mValue = lineNames;
+    if (mToken.mType == eCSSToken_Function &&
+        mToken.mIdent.LowerCaseEqualsLiteral("repeat")) {
+      if (!ParseGridLineNameListRepeat(&item)) {
+        return false;
+      }
+    } else {
+      UngetToken();
+
+      // This was not a repeat() function. Try to parse <line-names>.
+      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)
 {
   if (ParseNonNegativeVariant(aValue,
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -4878,16 +4878,18 @@ if (SpecialPowers.getBoolPref("layout.cs
 			"() 40px (-foo) 2em (bar baz This\ is\ one\ ident)",
 			// TODO bug 978478: "(a) repeat(3, (b) 20px (c) 40px (d)) (e)",
 
 			// See https://bugzilla.mozilla.org/show_bug.cgi?id=981300
 			"(none auto subgrid min-content max-content foo) 40px",
 
 			"subgrid",
 			"subgrid () (foo bar)",
+			"subgrid repeat(1, ())",
+			"subgrid Repeat(4, (a) (b c) () (d))",
 		],
 		invalid_values: [
 			"",
 			"normal",
 			"40ms",
 			"-40px",
 			"-12%",
 			"-2fr",
@@ -4909,16 +4911,26 @@ if (SpecialPowers.getBoolPref("layout.cs
 			"mİnmax(20px, 100px)",
 			"minmax(20px, 100px, 200px)",
 			"maxmin(100px, 20px)",
 			"minmax(min-content, auto)",
 			"minmax(min-content, minmax(30px, max-content))",
 			"subgrid (foo) 40px",
 			"subgrid (foo 40px)",
 			"(foo) subgrid",
+			"subgrid rêpeat(1, ())",
+			"subgrid repeat(0, ())",
+			"subgrid repeat(-3, ())",
+			"subgrid repeat(2.0, ())",
+			"subgrid repeat(2.5, ())",
+			"subgrid repeat(3px, ())",
+			"subgrid repeat(1)",
+			"subgrid repeat(1, )",
+			"subgrid repeat(2, (40px))",
+			"subgrid repeat(2, foo)",
 		]
 	};
 	gCSSProperties["grid-template-rows"] = {
 		domProp: "gridTemplateRows",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		initial_values: gCSSProperties["grid-template-columns"].initial_values,
 		other_values: gCSSProperties["grid-template-columns"].other_values,
@@ -4964,17 +4976,17 @@ if (SpecialPowers.getBoolPref("layout.cs
 			// <'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)",
+			"40px 20px/subgrid  (foo) () repeat(3, (a) (b)) (bar baz)",
 			"subgrid/subgrid",
 			"subgrid (foo) () (bar baz)/subgrid (foo) () (bar baz)",
 			// [ <track-list> / ]? [ <line-names>? <string> <track-size>? <line-names>? ]+
 			"'fizz'",
 			"(bar) 'fizz'",
 			"(foo) 40px / 'fizz'",
 			"(foo) 40px / (bar) 'fizz'",
 			"(foo) 40px / 'fizz' 100px",
--- a/layout/style/test/test_grid_container_shorthands.html
+++ b/layout/style/test/test_grid_container_shorthands.html
@@ -93,16 +93,30 @@ var grid_template_test_cases = [
         gridTemplateRows: "subgrid",
     },
     {
         specified: "subgrid / subgrid (foo)",
         gridTemplateColumns: "subgrid",
         gridTemplateRows: "subgrid (foo)",
     },
     {
+        specified: "subgrid / subgrid (foo) repeat(3, () (a b) (c))",
+        gridTemplateColumns: "subgrid",
+        gridTemplateRows: "subgrid (foo) () (a b) (c) () (a b) (c) () (a b) (c)",
+    },
+    {
+        // https://bugzilla.mozilla.org/show_bug.cgi?id=978478#c1
+        // The number of repetitions is clamped to
+        // #define GRID_TEMPLATE_MAX_REPETITIONS 10000
+        specified: "subgrid / subgrid (foo) repeat(999999999, (a))",
+        gridTemplateColumns: "subgrid",
+        // Array(n + 1).join(s) is a hack for the non-standard s.repeat(n)
+        gridTemplateRows: "subgrid (foo)" + Array(10000 + 1).join(" (a)"),
+    },
+    {
         specified: "subgrid () (foo)/ subgrid (bar",
         gridTemplateColumns: "subgrid () (foo)",
         gridTemplateRows: "subgrid (bar)",
     },
 ];
 
 grid_test_cases = grid_template_test_cases.concat([
     {