Bug 976787 part 1: Add the grid-template-{columns,rows} properties to the style system. r=dholbert
authorSimon Sapin <simon.sapin@exyr.org>
Mon, 10 Mar 2014 15:54:13 -0700
changeset 191104 96f8a98c9228031bc43325842ad475ba38c5369e
parent 191103 681b3c6bcf49f03b780a7e3c2e2a5db063c09c7d
child 191105 a2cb9d6adebc91198718041e04dd7a87240f62c3
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs976787
milestone30.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 976787 part 1: Add the grid-template-{columns,rows} properties to the style system. r=dholbert
layout/style/nsCSSKeywordList.h
layout/style/nsCSSParser.cpp
layout/style/nsCSSPropList.h
layout/style/nsCSSProps.cpp
layout/style/nsCSSProps.h
layout/style/nsCSSValue.cpp
layout/style/nsCSSValue.h
layout/style/nsComputedDOMStyle.cpp
layout/style/nsComputedDOMStyle.h
layout/style/nsComputedDOMStylePropertyList.h
layout/style/nsRuleNode.cpp
layout/style/nsStyleConsts.h
layout/style/nsStyleCoord.cpp
layout/style/nsStyleCoord.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/test/property_database.js
--- a/layout/style/nsCSSKeywordList.h
+++ b/layout/style/nsCSSKeywordList.h
@@ -371,21 +371,24 @@ CSS_KEY(lowercase, lowercase)
 CSS_KEY(ltr, ltr)
 CSS_KEY(luminance, luminance)
 CSS_KEY(luminosity, luminosity)
 CSS_KEY(manual, manual)
 CSS_KEY(margin-box, margin_box)
 CSS_KEY(markers, markers)
 CSS_KEY(matrix, matrix)
 CSS_KEY(matrix3d, matrix3d)
+CSS_KEY(max-content, max_content)
 CSS_KEY(medium, medium)
 CSS_KEY(menu, menu)
 CSS_KEY(menutext, menutext)
 CSS_KEY(message-box, message_box)
 CSS_KEY(middle, middle)
+CSS_KEY(min-content, min_content)
+CSS_KEY(minmax, minmax)
 CSS_KEY(mix, mix)
 CSS_KEY(mm, mm)
 CSS_KEY(monospace, monospace)
 CSS_KEY(move, move)
 CSS_KEY(ms, ms)
 CSS_KEY(multiply, multiply)
 CSS_KEY(n-resize, n_resize)
 CSS_KEY(narrower, narrower)
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -626,16 +626,22 @@ protected:
   bool ParseCalcTerm(nsCSSValue& aValue, int32_t& aVariantMask);
   bool RequireWhitespace();
 
   // For "flex" shorthand property, defined in CSS Flexbox spec
   bool ParseFlex();
   // For "flex-flow" shorthand property, defined in CSS Flexbox spec
   bool ParseFlexFlow();
 
+  // CSS Grid
+  bool ParseGridLineNames(nsCSSValue& aValue);
+  bool ParseGridTrackBreadth(nsCSSValue& aValue);
+  bool ParseGridTrackSize(nsCSSValue& aValue);
+  bool ParseGridTrackList(nsCSSProperty aPropID);
+
   // for 'clip' and '-moz-image-region'
   bool ParseRect(nsCSSProperty aPropID);
   bool ParseColumns();
   bool ParseContent();
   bool ParseCounterData(nsCSSProperty aPropID);
   bool ParseCursor();
   bool ParseFont();
   bool ParseFontSynthesis(nsCSSValue& aValue);
@@ -776,16 +782,34 @@ protected:
                     int32_t aVariantMask,
                     const KTableValue aKeywordTable[]);
   bool ParseNonNegativeVariant(nsCSSValue& aValue,
                                int32_t aVariantMask,
                                const KTableValue aKeywordTable[]);
   bool ParseOneOrLargerVariant(nsCSSValue& aValue,
                                int32_t aVariantMask,
                                const KTableValue aKeywordTable[]);
+
+  // http://dev.w3.org/csswg/css-values/#custom-idents
+  // Parse an identifier that is none of:
+  // * a CSS-wide keyword
+  // * "default"
+  // * a keyword in |aExcludedKeywords|
+  // * a keyword in |aPropertyKTable|
+  //
+  // |aExcludedKeywords| is an array of nsCSSKeyword
+  // that ends with a eCSSKeyword_UNKNOWN marker.
+  //
+  // |aPropertyKTable| can be used if some of the keywords to exclude
+  // also appear in an existing nsCSSProps::KTableValue,
+  // to avoid duplicating them.
+  bool ParseCustomIdent(nsCSSValue& aValue,
+                        const nsAutoString& aIdentValue,
+                        const nsCSSKeyword aExcludedKeywords[] = nullptr,
+                        const nsCSSProps::KTableValue aPropertyKTable[] = nullptr);
   bool ParseCounter(nsCSSValue& aValue);
   bool ParseAttr(nsCSSValue& aValue);
   bool SetValueToURL(nsCSSValue& aValue, const nsString& aURL);
   bool TranslateDimension(nsCSSValue& aValue, int32_t aVariantMask,
                             float aNumber, const nsString& aUnit);
   bool ParseImageOrientation(nsCSSValue& aAngle);
   bool ParseImageRect(nsCSSValue& aImage);
   bool ParseElement(nsCSSValue& aValue);
@@ -6154,16 +6178,19 @@ CSSParserImpl::ParseVariant(nsCSSValue& 
           aValue.SetAutoValue();
           return true;
         }
       }
       if ((aVariantMask & VARIANT_INHERIT) != 0) {
         // XXX Should we check IsParsingCompoundProperty, or do all
         // callers handle it?  (Not all callers set it, though, since
         // they want the quirks that are disabled by setting it.)
+
+        // IMPORTANT: If new keywords are added here,
+        // they probably need to be added in ParseCustomIdent as well.
         if (eCSSKeyword_inherit == keyword) {
           aValue.SetInheritValue();
           return true;
         }
         else if (eCSSKeyword_initial == keyword) {
           aValue.SetInitialValue();
           return true;
         }
@@ -6387,16 +6414,50 @@ CSSParserImpl::ParseVariant(nsCSSValue& 
     // calc() currently allows only lengths and percents inside it.
     return ParseCalc(aValue, aVariantMask & VARIANT_LP);
   }
 
   UngetToken();
   return false;
 }
 
+bool
+CSSParserImpl::ParseCustomIdent(nsCSSValue& aValue,
+                                const nsAutoString& aIdentValue,
+                                const nsCSSKeyword aExcludedKeywords[],
+                                const nsCSSProps::KTableValue aPropertyKTable[])
+{
+  nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aIdentValue);
+  if (keyword == eCSSKeyword_UNKNOWN) {
+    // Fast path for identifiers that are not known CSS keywords:
+    aValue.SetStringValue(mToken.mIdent, eCSSUnit_Ident);
+    return true;
+  }
+  if (keyword == eCSSKeyword_inherit ||
+      keyword == eCSSKeyword_initial ||
+      keyword == eCSSKeyword_unset ||
+      keyword == eCSSKeyword_default ||
+      (aPropertyKTable &&
+        nsCSSProps::FindIndexOfKeyword(keyword, aPropertyKTable) >= 0)) {
+    return false;
+  }
+  if (aExcludedKeywords) {
+    for (uint32_t i = 0;; i++) {
+      nsCSSKeyword excludedKeyword = aExcludedKeywords[i];
+      if (excludedKeyword == eCSSKeyword_UNKNOWN) {
+        break;
+      }
+      if (excludedKeyword == keyword) {
+        return false;
+      }
+    }
+  }
+  aValue.SetStringValue(mToken.mIdent, eCSSUnit_Ident);
+  return true;
+}
 
 bool
 CSSParserImpl::ParseCounter(nsCSSValue& aValue)
 {
   nsCSSUnit unit = (mToken.mIdent.LowerCaseEqualsLiteral("counter") ?
                     eCSSUnit_Counter : eCSSUnit_Counters);
 
   // A non-iterative for loop to break out when an error occurs.
@@ -6803,16 +6864,146 @@ CSSParserImpl::ParseFlexFlow()
 
   // Store these values and declare success!
   for (size_t i = 0; i < numProps; i++) {
     AppendValue(kFlexFlowSubprops[i], values[i]);
   }
   return true;
 }
 
+// Parse an optional <line-names> expression.
+// If successful, leaves aValue with eCSSUnit_Null for the empty list,
+// or sets it to a eCSSUnit_List of eCSSUnit_Ident.
+// Not finding an open paren is considered the same as an empty list.
+
+// aPropertyKeywords contains additional keywords for the 'grid' shorthand.
+bool
+CSSParserImpl::ParseGridLineNames(nsCSSValue& aValue)
+{
+  MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Null,
+             "Unexpected unit, aValue should not be initialized yet");
+  if (!GetToken(true)) {
+    return true;
+  }
+  if (!mToken.IsSymbol('(')) {
+    UngetToken();
+    return true;
+  }
+  if (!GetToken(true) || mToken.IsSymbol(')')) {
+    return true;
+  }
+  // 'return true' so far keeps eCSSUnit_Null, to represent an empty list.
+
+  nsCSSValueList* item = aValue.SetListValue();
+  for (;;) {
+    if (!(eCSSToken_Ident == mToken.mType &&
+          ParseCustomIdent(item->mValue, mToken.mIdent))) {
+      UngetToken();
+      SkipUntil(')');
+      return false;
+    }
+    if (!GetToken(true) || mToken.IsSymbol(')')) {
+      return true;
+    }
+    item->mNext = new nsCSSValueList;
+    item = item->mNext;
+  }
+}
+
+// Parse a <track-breadth>
+bool
+CSSParserImpl::ParseGridTrackBreadth(nsCSSValue& aValue)
+{
+  if (ParseNonNegativeVariant(aValue,
+                              VARIANT_LPCALC | VARIANT_KEYWORD,
+                              nsCSSProps::kGridTrackBreadthKTable)) {
+    return true;
+  }
+
+  // Attempt to parse <flex> (a dimension with the "fr" unit)
+  if (!GetToken(true)) {
+    return false;
+  }
+  if (!(eCSSToken_Dimension == mToken.mType &&
+        mToken.mIdent.LowerCaseEqualsLiteral("fr") &&
+        mToken.mNumber >= 0)) {
+    UngetToken();
+    return false;
+  }
+  aValue.SetFloatValue(mToken.mNumber, eCSSUnit_FlexFraction);
+  return true;
+}
+
+// Parse a <track-size>
+bool
+CSSParserImpl::ParseGridTrackSize(nsCSSValue& aValue)
+{
+  // Attempt to parse 'auto' or a single <track-breadth>
+  if (ParseGridTrackBreadth(aValue) ||
+      ParseVariant(aValue, VARIANT_AUTO, nullptr)) {
+    return true;
+  }
+
+  // Attempt to parse a minmax() function
+  if (!GetToken(true)) {
+    return false;
+  }
+  if (!(eCSSToken_Function == mToken.mType &&
+        mToken.mIdent.LowerCaseEqualsLiteral("minmax"))) {
+    UngetToken();
+    return false;
+  }
+  nsCSSValue::Array* func = aValue.InitFunction(eCSSKeyword_minmax, 2);
+  if (ParseGridTrackBreadth(func->Item(1)) &&
+      ExpectSymbol(',', true) &&
+      ParseGridTrackBreadth(func->Item(2)) &&
+      ExpectSymbol(')', true)) {
+    return true;
+  }
+  SkipUntil(')');
+  return false;
+}
+
+bool
+CSSParserImpl::ParseGridTrackList(nsCSSProperty aPropID)
+{
+  nsCSSValue value;
+  if (ParseVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) {
+    AppendValue(aPropID, value);
+    return true;
+  }
+  // FIXME: add subgrid
+
+  // |value| will be a list of odd length >= 3,
+  // starting with a <line-names> (which is itself a list)
+  // and alternating between that and <track-size>
+  nsCSSValueList* item = value.SetListValue();
+  if (!ParseGridLineNames(item->mValue)) {
+    return false;
+  }
+  do {
+    item->mNext = new nsCSSValueList;
+    item = item->mNext;
+    // FIXME: add repeat()
+    if (!ParseGridTrackSize(item->mValue)) {
+      return false;
+    }
+    item->mNext = new nsCSSValueList;
+    item = item->mNext;
+    if (!ParseGridLineNames(item->mValue)) {
+      return false;
+    }
+  } while (!CheckEndProperty());
+  MOZ_ASSERT(value.GetListValue() && value.GetListValue()->mNext &&
+             value.GetListValue()->mNext->mNext,
+             "<track-list> should have a minimum length of 3");
+  AppendValue(aPropID, value);
+  return true;
+}
+
 // <color-stop> : <color> [ <percentage> | <length> ]?
 bool
 CSSParserImpl::ParseColorStop(nsCSSValueGradient* aGradient)
 {
   nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement();
   if (!ParseVariant(stop->mColor, VARIANT_COLOR, nullptr)) {
     return false;
   }
@@ -7817,16 +8008,19 @@ CSSParserImpl::ParsePropertyByFunction(n
   case eCSSProperty_filter:
     return ParseFilter();
   case eCSSProperty_flex:
     return ParseFlex();
   case eCSSProperty_flex_flow:
     return ParseFlexFlow();
   case eCSSProperty_font:
     return ParseFont();
+  case eCSSProperty_grid_template_columns:
+  case eCSSProperty_grid_template_rows:
+    return ParseGridTrackList(aPropID);
   case eCSSProperty_image_region:
     return ParseRect(eCSSProperty_image_region);
   case eCSSProperty_list_style:
     return ParseListStyle();
   case eCSSProperty_margin:
     return ParseMargin();
   case eCSSProperty_margin_end:
     return ParseDirectionalBoxProperty(eCSSProperty_margin_end,
@@ -9794,35 +9988,31 @@ CSSParserImpl::ParseContent()
   }
   AppendValue(eCSSProperty_content, value);
   return true;
 }
 
 bool
 CSSParserImpl::ParseCounterData(nsCSSProperty aPropID)
 {
+  static const nsCSSKeyword kCounterDataKTable[] = {
+    eCSSKeyword_none,
+    eCSSKeyword_UNKNOWN
+  };
   nsCSSValue value;
   if (!ParseVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) {
     if (!GetToken(true) || mToken.mType != eCSSToken_Ident) {
       return false;
     }
 
     nsCSSValuePairList *cur = value.SetPairListValue();
     for (;;) {
-      // check for "none", "default" and the CSS-wide keywords,
-      // which can't be used as counter names
-      nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
-      if (keyword == eCSSKeyword_inherit ||
-          keyword == eCSSKeyword_default ||
-          keyword == eCSSKeyword_none ||
-          keyword == eCSSKeyword_unset ||
-          keyword == eCSSKeyword_initial) {
-        return false;
-      }
-      cur->mXValue.SetStringValue(mToken.mIdent, eCSSUnit_Ident);
+      if (!ParseCustomIdent(cur->mXValue, mToken.mIdent, kCounterDataKTable)) {
+        return false;
+      }
       if (!GetToken(true)) {
         break;
       }
       if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid) {
         cur->mYValue.SetIntValue(mToken.mInteger, eCSSUnit_Integer);
       } else {
         UngetToken();
       }
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -2004,16 +2004,40 @@ CSS_PROP_UIRESET(
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_NONNEGATIVE,
     "",
     VARIANT_HI,
     nullptr,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None) // bug 58646
 CSS_PROP_POSITION(
+    grid-template-columns,
+    grid_template_columns,
+    GridTemplateColumns,
+    CSS_PROPERTY_PARSE_FUNCTION |
+        CSS_PROPERTY_STORES_CALC |
+        CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+    "layout.css.grid.enabled",
+    0,
+    kGridTrackBreadthKTable,
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_None)
+CSS_PROP_POSITION(
+    grid-template-rows,
+    grid_template_rows,
+    GridTemplateRows,
+    CSS_PROPERTY_PARSE_FUNCTION |
+        CSS_PROPERTY_STORES_CALC |
+        CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+    "layout.css.grid.enabled",
+    0,
+    kGridTrackBreadthKTable,
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_None)
+CSS_PROP_POSITION(
     height,
     height,
     Height,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_NONNEGATIVE |
         CSS_PROPERTY_STORES_CALC |
         CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
         CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -1246,16 +1246,22 @@ const KTableValue nsCSSProps::kFontVaria
 const KTableValue nsCSSProps::kFontWeightKTable[] = {
   eCSSKeyword_normal, NS_STYLE_FONT_WEIGHT_NORMAL,
   eCSSKeyword_bold, NS_STYLE_FONT_WEIGHT_BOLD,
   eCSSKeyword_bolder, NS_STYLE_FONT_WEIGHT_BOLDER,
   eCSSKeyword_lighter, NS_STYLE_FONT_WEIGHT_LIGHTER,
   eCSSKeyword_UNKNOWN,-1
 };
 
+const KTableValue nsCSSProps::kGridTrackBreadthKTable[] = {
+  eCSSKeyword_min_content, NS_STYLE_GRID_TRACK_BREADTH_MIN_CONTENT,
+  eCSSKeyword_max_content, NS_STYLE_GRID_TRACK_BREADTH_MAX_CONTENT,
+  eCSSKeyword_UNKNOWN,-1
+};
+
 const KTableValue nsCSSProps::kImageOrientationKTable[] = {
   eCSSKeyword_flip, NS_STYLE_IMAGE_ORIENTATION_FLIP,
   eCSSKeyword_from_image, NS_STYLE_IMAGE_ORIENTATION_FROM_IMAGE,
   eCSSKeyword_UNKNOWN,-1
 };
 
 const KTableValue nsCSSProps::kImageOrientationFlipKTable[] = {
   eCSSKeyword_flip, NS_STYLE_IMAGE_ORIENTATION_FLIP,
--- a/layout/style/nsCSSProps.h
+++ b/layout/style/nsCSSProps.h
@@ -567,16 +567,17 @@ public:
   static const KTableValue kFontVariantAlternatesKTable[];
   static const KTableValue kFontVariantAlternatesFuncsKTable[];
   static const KTableValue kFontVariantCapsKTable[];
   static const KTableValue kFontVariantEastAsianKTable[];
   static const KTableValue kFontVariantLigaturesKTable[];
   static const KTableValue kFontVariantNumericKTable[];
   static const KTableValue kFontVariantPositionKTable[];
   static const KTableValue kFontWeightKTable[];
+  static const KTableValue kGridTrackBreadthKTable[];
   static const KTableValue kImageOrientationKTable[];
   static const KTableValue kImageOrientationFlipKTable[];
   static const KTableValue kIMEModeKTable[];
   static const KTableValue kLineHeightKTable[];
   static const KTableValue kListStylePositionKTable[];
   static const KTableValue kListStyleKTable[];
   static const KTableValue kMaskTypeKTable[];
   static const KTableValue kMathVariantKTable[];
--- a/layout/style/nsCSSValue.cpp
+++ b/layout/style/nsCSSValue.cpp
@@ -1393,16 +1393,18 @@ nsCSSValue::AppendToString(nsCSSProperty
     case eCSSUnit_Radian:       aResult.AppendLiteral("rad");  break;
     case eCSSUnit_Turn:         aResult.AppendLiteral("turn");  break;
 
     case eCSSUnit_Hertz:        aResult.AppendLiteral("Hz");   break;
     case eCSSUnit_Kilohertz:    aResult.AppendLiteral("kHz");  break;
 
     case eCSSUnit_Seconds:      aResult.Append(char16_t('s'));    break;
     case eCSSUnit_Milliseconds: aResult.AppendLiteral("ms");   break;
+
+    case eCSSUnit_FlexFraction: aResult.AppendLiteral("fr");   break;
   }
 }
 
 size_t
 nsCSSValue::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
   size_t n = 0;
 
@@ -1549,16 +1551,17 @@ nsCSSValue::SizeOfExcludingThis(mozilla:
     case eCSSUnit_Degree:
     case eCSSUnit_Grad:
     case eCSSUnit_Turn:
     case eCSSUnit_Radian:
     case eCSSUnit_Hertz:
     case eCSSUnit_Kilohertz:
     case eCSSUnit_Seconds:
     case eCSSUnit_Milliseconds:
+    case eCSSUnit_FlexFraction:
       break;
 
     default:
       NS_ABORT_IF_FALSE(false, "bad nsCSSUnit");
       break;
   }
 
   return n;
@@ -1589,34 +1592,83 @@ nsCSSValueList::Clone() const
 void
 nsCSSValueList::CloneInto(nsCSSValueList* aList) const
 {
     NS_ASSERTION(!aList->mNext, "Must be an empty list!");
     aList->mValue = mValue;
     aList->mNext = mNext ? mNext->Clone() : nullptr;
 }
 
-void
-nsCSSValueList::AppendToString(nsCSSProperty aProperty, nsAString& aResult,
-                               nsCSSValue::Serialization aSerialization) const
+static void
+AppendValueListToString(const nsCSSValueList* val,
+                        nsCSSProperty aProperty, nsAString& aResult,
+                        nsCSSValue::Serialization aSerialization)
 {
-  const nsCSSValueList* val = this;
   for (;;) {
     val->mValue.AppendToString(aProperty, aResult, aSerialization);
     val = val->mNext;
     if (!val)
       break;
 
     if (nsCSSProps::PropHasFlags(aProperty,
                                  CSS_PROPERTY_VALUE_LIST_USES_COMMAS))
       aResult.Append(char16_t(','));
     aResult.Append(char16_t(' '));
   }
 }
 
+static void
+AppendGridTemplateToString(const nsCSSValueList* val,
+                           nsCSSProperty aProperty, nsAString& aResult,
+                           nsCSSValue::Serialization aSerialization)
+{
+  // This is called for the "list" that's the top-level value of the property.
+  for (;;) {
+    bool addSpaceSpearator = true;
+    nsCSSUnit unit = val->mValue.GetUnit();
+
+    if (unit == eCSSUnit_Null) {
+      // Empty or omitted <line-names>. Serializes to nothing.
+      addSpaceSpearator = false;  // Avoid a double space.
+
+    } else if (unit == eCSSUnit_List || unit == eCSSUnit_ListDep) {
+      // Non-empty <line-names>
+      aResult.AppendLiteral("(");
+      AppendValueListToString(val->mValue.GetListValue(), aProperty,
+                              aResult, aSerialization);
+      aResult.AppendLiteral(")");
+
+    } else {
+      // <track-size>
+      val->mValue.AppendToString(aProperty, aResult, aSerialization);
+    }
+
+    val = val->mNext;
+    if (!val) {
+      break;
+    }
+
+    if (addSpaceSpearator) {
+      aResult.Append(char16_t(' '));
+    }
+  }
+}
+
+void
+nsCSSValueList::AppendToString(nsCSSProperty aProperty, nsAString& aResult,
+                               nsCSSValue::Serialization aSerialization) const
+{
+  if (aProperty == eCSSProperty_grid_template_columns ||
+      aProperty == eCSSProperty_grid_template_rows) {
+    AppendGridTemplateToString(this, aProperty, aResult, aSerialization);
+  } else {
+    AppendValueListToString(this, aProperty, aResult, aSerialization);
+  }
+}
+
 bool
 nsCSSValueList::operator==(const nsCSSValueList& aOther) const
 {
   if (this == &aOther)
     return true;
 
   const nsCSSValueList *p1 = this, *p2 = &aOther;
   for ( ; p1 && p2; p1 = p1->mNext, p2 = p2->mNext) {
--- a/layout/style/nsCSSValue.h
+++ b/layout/style/nsCSSValue.h
@@ -253,17 +253,20 @@ enum nsCSSUnit {
   eCSSUnit_Turn         = 1003,    // (float) 1 per circle
 
   // Frequency units
   eCSSUnit_Hertz        = 2000,    // (float) 1/seconds
   eCSSUnit_Kilohertz    = 2001,    // (float) 1000 Hertz
 
   // Time units
   eCSSUnit_Seconds      = 3000,    // (float) Standard time
-  eCSSUnit_Milliseconds = 3001     // (float) 1/1000 second
+  eCSSUnit_Milliseconds = 3001,    // (float) 1/1000 second
+
+  // Flexible fraction (CSS Grid)
+  eCSSUnit_FlexFraction = 4000     // (float) Fraction of free space
 };
 
 struct nsCSSValueGradient;
 struct nsCSSValuePair;
 struct nsCSSValuePair_heap;
 struct nsCSSValueTokenStream;
 struct nsCSSRect;
 struct nsCSSRect_heap;
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -2266,16 +2266,116 @@ nsComputedDOMStyle::DoGetBackgroundSize(
         break;
       }
     }
   }
 
   return valueList;
 }
 
+// aLineNames must not be empty
+CSSValue*
+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;
+    }
+    lineNamesString.AppendLiteral(" ");
+  }
+  lineNamesString.AppendLiteral(")");
+  val->SetString(lineNamesString);
+  return val;
+}
+
+CSSValue*
+nsComputedDOMStyle::GetGridTrackSize(const nsStyleCoord& aMinValue,
+                                     const nsStyleCoord& aMaxValue)
+{
+  // FIXME bug 978212: If we have frame, every <track-size> should
+  // be resolved into 'px' here, based on layout results.
+  if (aMinValue == aMaxValue) {
+    nsROCSSPrimitiveValue *val = new nsROCSSPrimitiveValue;
+    SetValueToCoord(val, aMinValue, true,
+                    nullptr, nsCSSProps::kGridTrackBreadthKTable);
+    return val;
+  }
+
+  nsROCSSPrimitiveValue *val = new nsROCSSPrimitiveValue;
+  nsAutoString argumentStr, minmaxStr;
+  minmaxStr.AppendLiteral("minmax(");
+
+  SetValueToCoord(val, aMinValue, true,
+                  nullptr, nsCSSProps::kGridTrackBreadthKTable);
+  val->GetCssText(argumentStr);
+  minmaxStr.Append(argumentStr);
+
+  minmaxStr.AppendLiteral(", ");
+
+  SetValueToCoord(val, aMaxValue, true,
+                  nullptr, nsCSSProps::kGridTrackBreadthKTable);
+  val->GetCssText(argumentStr);
+  minmaxStr.Append(argumentStr);
+
+  minmaxStr.Append(char16_t(')'));
+  val->SetString(minmaxStr);
+  return val;
+}
+
+CSSValue*
+nsComputedDOMStyle::GetGridTrackList(const nsStyleGridTrackList& aTrackList)
+{
+  uint32_t numSizes = aTrackList.mMinTrackSizingFunctions.Length();
+  MOZ_ASSERT(aTrackList.mMaxTrackSizingFunctions.Length() == numSizes,
+             "Different number of min and max track sizing functions");
+  // An empty <track-list> is represented as "none" in syntax.
+  if (numSizes == 0) {
+    nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue;
+    val->SetIdent(eCSSKeyword_none);
+    return val;
+  }
+
+  nsDOMCSSValueList* valueList = GetROCSSValueList(false);
+  // Delimiting N tracks requires N+1 lines:
+  // one before each track, plus one at the very end.
+  MOZ_ASSERT(aTrackList.mLineNameLists.Length() == numSizes + 1,
+             "Unexpected number of line name lists");
+  for (uint32_t i = 0;; i++) {
+    const nsTArray<nsString>& lineNames = aTrackList.mLineNameLists[i];
+    if (!lineNames.IsEmpty()) {
+      valueList->AppendCSSValue(GetGridLineNames(aTrackList.mLineNameLists[i]));
+    }
+    if (i == numSizes) {
+      break;
+    }
+    valueList->AppendCSSValue(GetGridTrackSize(aTrackList.mMinTrackSizingFunctions[i],
+                                               aTrackList.mMaxTrackSizingFunctions[i]));
+  }
+
+  return valueList;
+}
+
+CSSValue*
+nsComputedDOMStyle::DoGetGridTemplateColumns()
+{
+  return GetGridTrackList(StylePosition()->mGridTemplateColumns);
+}
+
+CSSValue*
+nsComputedDOMStyle::DoGetGridTemplateRows()
+{
+  return GetGridTrackList(StylePosition()->mGridTemplateRows);
+}
+
 CSSValue*
 nsComputedDOMStyle::DoGetPaddingTop()
 {
   return GetPaddingWidthFor(NS_SIDE_TOP);
 }
 
 CSSValue*
 nsComputedDOMStyle::DoGetPaddingBottom()
@@ -4397,16 +4497,24 @@ nsComputedDOMStyle::SetValueToCoord(nsRO
     case eStyleUnit_Radian:
       aValue->SetRadian(aCoord.GetAngleValue());
       break;
 
     case eStyleUnit_Turn:
       aValue->SetTurn(aCoord.GetAngleValue());
       break;
 
+    case eStyleUnit_FlexFraction: {
+      nsAutoString tmpStr;
+      nsStyleUtil::AppendCSSNumber(aCoord.GetFlexFractionValue(), tmpStr);
+      tmpStr.AppendLiteral("fr");
+      aValue->SetString(tmpStr);
+      break;
+    }
+
     default:
       NS_ERROR("Can't handle this unit");
       break;
   }
 }
 
 nscoord
 nsComputedDOMStyle::StyleCoordToNSCoord(const nsStyleCoord& aCoord,
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -184,16 +184,21 @@ private:
   mozilla::dom::CSSValue* GetBorderWidthFor(mozilla::css::Side aSide);
 
   mozilla::dom::CSSValue* GetBorderColorFor(mozilla::css::Side aSide);
 
   mozilla::dom::CSSValue* GetMarginWidthFor(mozilla::css::Side aSide);
 
   mozilla::dom::CSSValue* GetSVGPaintFor(bool aFill);
 
+  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);
+
   bool GetLineHeightCoord(nscoord& aCoord);
 
   mozilla::dom::CSSValue* GetCSSShadowArray(nsCSSShadowArray* aArray,
                                             const nscolor& aDefaultColor,
                                             bool aIsBoxShadow);
 
   mozilla::dom::CSSValue* GetBackgroundList(uint8_t nsStyleBackground::Layer::* aMember,
                                             uint32_t nsStyleBackground::* aCount,
@@ -252,16 +257,20 @@ private:
   mozilla::dom::CSSValue* DoGetFontVariantAlternates();
   mozilla::dom::CSSValue* DoGetFontVariantCaps();
   mozilla::dom::CSSValue* DoGetFontVariantEastAsian();
   mozilla::dom::CSSValue* DoGetFontVariantLigatures();
   mozilla::dom::CSSValue* DoGetFontVariantNumeric();
   mozilla::dom::CSSValue* DoGetFontVariantPosition();
   mozilla::dom::CSSValue* DoGetFontWeight();
 
+  /* Grid properties */
+  mozilla::dom::CSSValue* DoGetGridTemplateColumns();
+  mozilla::dom::CSSValue* DoGetGridTemplateRows();
+
   /* Background properties */
   mozilla::dom::CSSValue* DoGetBackgroundAttachment();
   mozilla::dom::CSSValue* DoGetBackgroundColor();
   mozilla::dom::CSSValue* DoGetBackgroundImage();
   mozilla::dom::CSSValue* DoGetBackgroundPosition();
   mozilla::dom::CSSValue* DoGetBackgroundRepeat();
   mozilla::dom::CSSValue* DoGetBackgroundClip();
   mozilla::dom::CSSValue* DoGetBackgroundInlinePolicy();
--- a/layout/style/nsComputedDOMStylePropertyList.h
+++ b/layout/style/nsComputedDOMStylePropertyList.h
@@ -125,16 +125,18 @@ COMPUTED_STYLE_PROP(font_synthesis,     
 COMPUTED_STYLE_PROP(font_variant,                  FontVariant)
 COMPUTED_STYLE_PROP(font_variant_alternates,       FontVariantAlternates)
 COMPUTED_STYLE_PROP(font_variant_caps,             FontVariantCaps)
 COMPUTED_STYLE_PROP(font_variant_east_asian,       FontVariantEastAsian)
 COMPUTED_STYLE_PROP(font_variant_ligatures,        FontVariantLigatures)
 COMPUTED_STYLE_PROP(font_variant_numeric,          FontVariantNumeric)
 COMPUTED_STYLE_PROP(font_variant_position,         FontVariantPosition)
 COMPUTED_STYLE_PROP(font_weight,                   FontWeight)
+COMPUTED_STYLE_PROP(grid_template_columns,         GridTemplateColumns)
+COMPUTED_STYLE_PROP(grid_template_rows,            GridTemplateRows)
 COMPUTED_STYLE_PROP(height,                        Height)
 COMPUTED_STYLE_PROP(image_orientation,             ImageOrientation)
 COMPUTED_STYLE_PROP(ime_mode,                      IMEMode)
 COMPUTED_STYLE_PROP(justify_content,               JustifyContent)
 COMPUTED_STYLE_PROP(left,                          Left)
 COMPUTED_STYLE_PROP(letter_spacing,                LetterSpacing)
 COMPUTED_STYLE_PROP(line_height,                   LineHeight)
 //// COMPUTED_STYLE_PROP(list_style,               ListStyle)
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -678,17 +678,18 @@ GetFloatFromBoxPosition(int32_t aEnumVal
 #define SETCOORD_UNSET_INITIAL          0x00800000
 
 #define SETCOORD_LP     (SETCOORD_LENGTH | SETCOORD_PERCENT)
 #define SETCOORD_LH     (SETCOORD_LENGTH | SETCOORD_INHERIT)
 #define SETCOORD_AH     (SETCOORD_AUTO | SETCOORD_INHERIT)
 #define SETCOORD_LAH    (SETCOORD_AUTO | SETCOORD_LENGTH | SETCOORD_INHERIT)
 #define SETCOORD_LPH    (SETCOORD_LP | SETCOORD_INHERIT)
 #define SETCOORD_LPAH   (SETCOORD_LP | SETCOORD_AH)
-#define SETCOORD_LPEH   (SETCOORD_LP | SETCOORD_ENUMERATED | SETCOORD_INHERIT)
+#define SETCOORD_LPE    (SETCOORD_LP | SETCOORD_ENUMERATED)
+#define SETCOORD_LPEH   (SETCOORD_LPE | SETCOORD_INHERIT)
 #define SETCOORD_LPAEH  (SETCOORD_LPAH | SETCOORD_ENUMERATED)
 #define SETCOORD_LPO    (SETCOORD_LP | SETCOORD_NONE)
 #define SETCOORD_LPOH   (SETCOORD_LPH | SETCOORD_NONE)
 #define SETCOORD_LPOEH  (SETCOORD_LPOH | SETCOORD_ENUMERATED)
 #define SETCOORD_LE     (SETCOORD_LENGTH | SETCOORD_ENUMERATED)
 #define SETCOORD_LEH    (SETCOORD_LE | SETCOORD_INHERIT)
 #define SETCOORD_IA     (SETCOORD_INTEGER | SETCOORD_AUTO)
 #define SETCOORD_LAE    (SETCOORD_LENGTH | SETCOORD_AUTO | SETCOORD_ENUMERATED)
@@ -7057,16 +7058,130 @@ nsRuleNode::ComputeListData(void* aStart
 
   default:
     NS_ABORT_IF_FALSE(false, "unrecognized image-region unit");
   }
 
   COMPUTE_END_INHERITED(List, list)
 }
 
+static void
+SetGridTrackBreadth(const nsCSSValue& aValue,
+                    nsStyleCoord& aResult,
+                    nsStyleContext* aStyleContext,
+                    nsPresContext* aPresContext,
+                    bool& aCanStoreInRuleTree)
+{
+  nsCSSUnit unit = aValue.GetUnit();
+  if (unit == eCSSUnit_FlexFraction) {
+    aResult.SetFlexFractionValue(aValue.GetFloatValue());
+  } else {
+    MOZ_ASSERT(unit != eCSSUnit_Inherit && unit != eCSSUnit_Unset,
+               "Unexpected value that would use dummyParentCoord");
+    const nsStyleCoord dummyParentCoord;
+    SetCoord(aValue, aResult, dummyParentCoord,
+             SETCOORD_LPE | SETCOORD_STORE_CALC,
+             aStyleContext, aPresContext, aCanStoreInRuleTree);
+  }
+}
+
+static void
+SetGridTrackList(const nsCSSValue& aValue,
+                 nsStyleGridTrackList& aResult,
+                 const nsStyleGridTrackList& aParentValue,
+                 nsStyleContext* aStyleContext,
+                 nsPresContext* aPresContext,
+                 bool& aCanStoreInRuleTree)
+
+{
+  switch (aValue.GetUnit()) {
+  case eCSSUnit_Null:
+    break;
+
+  case eCSSUnit_Inherit:
+    aCanStoreInRuleTree = false;
+    aResult.mLineNameLists = aParentValue.mLineNameLists;
+    aResult.mMinTrackSizingFunctions = aParentValue.mMinTrackSizingFunctions;
+    aResult.mMaxTrackSizingFunctions = aParentValue.mMaxTrackSizingFunctions;
+    break;
+
+  case eCSSUnit_Initial:
+  case eCSSUnit_Unset:
+  case eCSSUnit_None:
+    aResult.mLineNameLists.Clear();
+    aResult.mMinTrackSizingFunctions.Clear();
+    aResult.mMaxTrackSizingFunctions.Clear();
+    break;
+
+  default:
+    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);
+      }
+      item = item->mNext;
+
+      if (!item) {
+        break;
+      }
+
+      // Compute a <track-size> value
+      nsStyleCoord minSizingFunction;
+      nsStyleCoord maxSizingFunction;
+      if (item->mValue.GetUnit() == eCSSUnit_Function) {
+        // A minmax() function.
+        nsCSSValue::Array* func = item->mValue.GetArrayValue();
+        NS_ASSERTION(func->Item(0).GetKeywordValue() == eCSSKeyword_minmax,
+                     "Expected minmax(), got another function name.");
+        SetGridTrackBreadth(func->Item(1), minSizingFunction,
+                            aStyleContext, aPresContext, aCanStoreInRuleTree);
+        SetGridTrackBreadth(func->Item(2), maxSizingFunction,
+                            aStyleContext, aPresContext, aCanStoreInRuleTree);
+      } else if (item->mValue.GetUnit() == eCSSUnit_Auto) {
+        // 'auto' computes to 'minmax(min-content, max-content)'
+        minSizingFunction.SetIntValue(NS_STYLE_GRID_TRACK_BREADTH_MIN_CONTENT,
+                                      eStyleUnit_Enumerated);
+        maxSizingFunction.SetIntValue(NS_STYLE_GRID_TRACK_BREADTH_MAX_CONTENT,
+                                      eStyleUnit_Enumerated);
+      } else {
+        // A single <track-breadth>,
+        // specifies identical min and max sizing functions.
+        SetGridTrackBreadth(item->mValue, minSizingFunction,
+                            aStyleContext, aPresContext, aCanStoreInRuleTree);
+        maxSizingFunction = minSizingFunction;
+      }
+
+      aResult.mMinTrackSizingFunctions.AppendElement(minSizingFunction);
+      aResult.mMaxTrackSizingFunctions.AppendElement(maxSizingFunction);
+      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 nsStyleGridTrackList");
+  }
+}
+
 const void*
 nsRuleNode::ComputePositionData(void* aStartStruct,
                                 const nsRuleData* aRuleData,
                                 nsStyleContext* aContext,
                                 nsRuleNode* aHighestNode,
                                 const RuleDetail aRuleDetail,
                                 const bool aCanStoreInRuleTree)
 {
@@ -7230,16 +7345,26 @@ nsRuleNode::ComputePositionData(void* aS
 
   // justify-content: enum, inherit, initial
   SetDiscrete(*aRuleData->ValueForJustifyContent(),
               pos->mJustifyContent, canStoreInRuleTree,
               SETDSC_ENUMERATED | SETDSC_UNSET_INITIAL,
               parentPos->mJustifyContent,
               NS_STYLE_JUSTIFY_CONTENT_FLEX_START, 0, 0, 0, 0);
 
+  // grid-template-columns
+  SetGridTrackList(*aRuleData->ValueForGridTemplateColumns(),
+                   pos->mGridTemplateColumns, parentPos->mGridTemplateColumns,
+                   aContext, mPresContext, canStoreInRuleTree);
+
+  // grid-template-rows
+  SetGridTrackList(*aRuleData->ValueForGridTemplateRows(),
+                   pos->mGridTemplateRows, parentPos->mGridTemplateRows,
+                   aContext, mPresContext, canStoreInRuleTree);
+
   // z-index
   const nsCSSValue* zIndexValue = aRuleData->ValueForZIndex();
   if (! SetCoord(*zIndexValue, pos->mZIndex, parentPos->mZIndex,
                  SETCOORD_IA | SETCOORD_INITIAL_AUTO | SETCOORD_UNSET_INITIAL,
                  aContext, nullptr, canStoreInRuleTree)) {
     if (eCSSUnit_Inherit == zIndexValue->GetUnit()) {
       // handle inherit, because it's ok to inherit 'auto' here
       canStoreInRuleTree = false;
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -534,16 +534,20 @@ static inline mozilla::css::Side operato
 #define NS_STYLE_FONT_DESKTOP                   10
 #define NS_STYLE_FONT_INFO                      11
 #define NS_STYLE_FONT_DIALOG                    12
 #define NS_STYLE_FONT_BUTTON                    13
 #define NS_STYLE_FONT_PULL_DOWN_MENU            14
 #define NS_STYLE_FONT_LIST                      15
 #define NS_STYLE_FONT_FIELD                     16
 
+// CSS Grid <track-breadth> keywords
+#define NS_STYLE_GRID_TRACK_BREADTH_MAX_CONTENT 1
+#define NS_STYLE_GRID_TRACK_BREADTH_MIN_CONTENT 2
+
 // defaults per MathML spec
 #define NS_MATHML_DEFAULT_SCRIPT_SIZE_MULTIPLIER 0.71f
 #define NS_MATHML_DEFAULT_SCRIPT_MIN_SIZE_PT 8
 
 // See nsStyleFont
 #define NS_MATHML_MATHVARIANT_NONE                     0
 #define NS_MATHML_MATHVARIANT_NORMAL                   1
 #define NS_MATHML_MATHVARIANT_BOLD                     2
--- a/layout/style/nsStyleCoord.cpp
+++ b/layout/style/nsStyleCoord.cpp
@@ -58,16 +58,17 @@ bool nsStyleCoord::operator==(const nsSt
     case eStyleUnit_None:
       return true;
     case eStyleUnit_Percent:
     case eStyleUnit_Factor:
     case eStyleUnit_Degree:
     case eStyleUnit_Grad:
     case eStyleUnit_Radian:
     case eStyleUnit_Turn:
+    case eStyleUnit_FlexFraction:
       return mValue.mFloat == aOther.mValue.mFloat;
     case eStyleUnit_Coord:
     case eStyleUnit_Integer:
     case eStyleUnit_Enumerated:
       return mValue.mInt == aOther.mValue.mInt;
     case eStyleUnit_Calc:
       return *this->GetCalcValue() == *aOther.GetCalcValue();
   }
@@ -86,16 +87,17 @@ uint32_t nsStyleCoord::HashValue(uint32_
     case eStyleUnit_None:
       return mozilla::AddToHash(aHash, true);
     case eStyleUnit_Percent:
     case eStyleUnit_Factor:
     case eStyleUnit_Degree:
     case eStyleUnit_Grad:
     case eStyleUnit_Radian:
     case eStyleUnit_Turn:
+    case eStyleUnit_FlexFraction:
       return mozilla::AddToHash(aHash, mValue.mFloat);
     case eStyleUnit_Coord:
     case eStyleUnit_Integer:
     case eStyleUnit_Enumerated:
       return mozilla::AddToHash(aHash, mValue.mInt);
     case eStyleUnit_Calc:
       Calc* calcValue = GetCalcValue();
       aHash = mozilla::AddToHash(aHash, calcValue->mLength);
@@ -155,16 +157,22 @@ void nsStyleCoord::SetAngleValue(float a
     mUnit = aUnit;
     mValue.mFloat = aValue;
   } else {
     NS_NOTREACHED("not an angle value");
     Reset();
   }
 }
 
+void nsStyleCoord::SetFlexFractionValue(float aValue)
+{
+  mUnit = eStyleUnit_FlexFraction;
+  mValue.mFloat = aValue;
+}
+
 void nsStyleCoord::SetCalcValue(Calc* aValue)
 {
   mUnit = eStyleUnit_Calc;
   mValue.mPointer = aValue;
 }
 
 void nsStyleCoord::SetNormalValue()
 {
--- a/layout/style/nsStyleCoord.h
+++ b/layout/style/nsStyleCoord.h
@@ -17,16 +17,17 @@ enum nsStyleUnit {
   eStyleUnit_Auto         = 2,      // (no value)
   eStyleUnit_None         = 3,      // (no value)
   eStyleUnit_Percent      = 10,     // (float) 1.0 == 100%
   eStyleUnit_Factor       = 11,     // (float) a multiplier
   eStyleUnit_Degree       = 12,     // (float) angle in degrees
   eStyleUnit_Grad         = 13,     // (float) angle in grads
   eStyleUnit_Radian       = 14,     // (float) angle in radians
   eStyleUnit_Turn         = 15,     // (float) angle in turns
+  eStyleUnit_FlexFraction = 16,     // (float) <flex> in fr units
   eStyleUnit_Coord        = 20,     // (nscoord) value is twips
   eStyleUnit_Integer      = 30,     // (int) value is simple integer
   eStyleUnit_Enumerated   = 32,     // (int) value has enumerated meaning
 
   // The following are allocated types.  They are weak pointers to
   // values allocated by nsStyleContext::Alloc.
   eStyleUnit_Calc         = 40      // (Calc*) calc() toplevel; always present
                                     // to distinguish 50% from calc(50%), etc.
@@ -124,26 +125,28 @@ public:
   }
 
   nscoord     GetCoordValue() const;
   int32_t     GetIntValue() const;
   float       GetPercentValue() const;
   float       GetFactorValue() const;
   float       GetAngleValue() const;
   double      GetAngleValueInRadians() const;
+  float       GetFlexFractionValue() const;
   Calc*       GetCalcValue() const;
   void        GetUnionValue(nsStyleUnion& aValue) const;
   uint32_t    HashValue(uint32_t aHash) const;
 
   void  Reset();  // sets to null
   void  SetCoordValue(nscoord aValue);
   void  SetIntValue(int32_t aValue, nsStyleUnit aUnit);
   void  SetPercentValue(float aValue);
   void  SetFactorValue(float aValue);
   void  SetAngleValue(float aValue, nsStyleUnit aUnit);
+  void  SetFlexFractionValue(float aValue);
   void  SetNormalValue();
   void  SetAutoValue();
   void  SetNoneValue();
   void  SetCalcValue(Calc* aValue);
 
 private:
   nsStyleUnit   mUnit;
   nsStyleUnion  mValue;
@@ -295,16 +298,25 @@ inline float nsStyleCoord::GetAngleValue
   NS_ASSERTION(mUnit >= eStyleUnit_Degree &&
                mUnit <= eStyleUnit_Turn, "not an angle value");
   if (mUnit >= eStyleUnit_Degree && mUnit <= eStyleUnit_Turn) {
     return mValue.mFloat;
   }
   return 0.0f;
 }
 
+inline float nsStyleCoord::GetFlexFractionValue() const
+{
+  NS_ASSERTION(mUnit == eStyleUnit_FlexFraction, "not a fr value");
+  if (mUnit == eStyleUnit_FlexFraction) {
+    return mValue.mFloat;
+  }
+  return 0.0f;
+}
+
 inline nsStyleCoord::Calc* nsStyleCoord::GetCalcValue() const
 {
   NS_ASSERTION(IsCalcUnit(), "not a pointer value");
   if (IsCalcUnit()) {
     return static_cast<Calc*>(mValue.mPointer);
   }
   return nullptr;
 }
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1207,18 +1207,18 @@ bool nsStyleSVGPaint::operator==(const n
     return mPaint.mColor == aOther.mPaint.mColor;
   return true;
 }
 
 
 // --------------------
 // nsStylePosition
 //
-nsStylePosition::nsStylePosition(void) 
-{ 
+nsStylePosition::nsStylePosition(void)
+{
   MOZ_COUNT_CTOR(nsStylePosition);
   // positioning values not inherited
   nsStyleCoord  autoCoord(eStyleUnit_Auto);
   mOffset.SetLeft(autoCoord);
   mOffset.SetTop(autoCoord);
   mOffset.SetRight(autoCoord);
   mOffset.SetBottom(autoCoord);
   mWidth.SetAutoValue();
@@ -1234,27 +1234,44 @@ nsStylePosition::nsStylePosition(void)
   mAlignSelf = NS_STYLE_ALIGN_SELF_AUTO;
   mFlexDirection = NS_STYLE_FLEX_DIRECTION_ROW;
   mFlexWrap = NS_STYLE_FLEX_WRAP_NOWRAP;
   mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_FLEX_START;
   mOrder = NS_STYLE_ORDER_INITIAL;
   mFlexGrow = 0.0f;
   mFlexShrink = 1.0f;
   mZIndex.SetAutoValue();
+  // mGridTemplateRows and mGridTemplateColumns get their default constructors
+  // which initializes them to empty arrays,
+  // which represent the property’s initial value 'none'
 }
 
-nsStylePosition::~nsStylePosition(void) 
-{ 
+nsStylePosition::~nsStylePosition(void)
+{
   MOZ_COUNT_DTOR(nsStylePosition);
 }
 
 nsStylePosition::nsStylePosition(const nsStylePosition& aSource)
+  : mGridTemplateColumns(aSource.mGridTemplateColumns)
+  , mGridTemplateRows(aSource.mGridTemplateRows)
 {
   MOZ_COUNT_CTOR(nsStylePosition);
-  memcpy((nsStylePosition*)this, &aSource, sizeof(nsStylePosition));
+  // If you add any memcpy'able member vars,
+  // they should be declared before mGridTemplateColumns.
+  // If you add any non-memcpy'able member vars,
+  // they should be declared after mGridTemplateColumns,
+  // and you should invoke their copy constructor in the init list above
+  // and update this static-assert to include their "sizeof()"
+  static_assert(sizeof(nsStylePosition) ==
+                offsetof(nsStylePosition, mGridTemplateColumns) +
+                sizeof(mGridTemplateColumns) + sizeof(mGridTemplateRows),
+                "Unexpected size or offset in nsStylePosition");
+  memcpy((nsStylePosition*) this,
+         &aSource,
+         offsetof(nsStylePosition, mGridTemplateColumns));
 }
 
 static bool
 IsAutonessEqual(const nsStyleSides& aSides1, const nsStyleSides& aSides2)
 {
   NS_FOR_CSS_SIDES(side) {
     if ((aSides1.GetUnit(side) == eStyleUnit_Auto) !=
         (aSides2.GetUnit(side) == eStyleUnit_Auto)) {
@@ -1294,16 +1311,23 @@ nsChangeHint nsStylePosition::CalcDiffer
   //   impacts their sizing/positioning and hence impacts the container's size.
   if (mAlignItems != aOther.mAlignItems ||
       mFlexDirection != aOther.mFlexDirection ||
       mFlexWrap != aOther.mFlexWrap) {
     return NS_CombineHint(hint, nsChangeHint_AllReflowHints);
   }
 
 
+  // Properties that apply to grid containers:
+  if (mGridTemplateColumns != aOther.mGridTemplateColumns ||
+      mGridTemplateRows != aOther.mGridTemplateRows) {
+    return NS_CombineHint(hint, nsChangeHint_AllReflowHints);
+  }
+
+
   // Changing justify-content on a flexbox might affect the positioning of its
   // children, but it won't affect any sizing.
   if (mJustifyContent != aOther.mJustifyContent) {
     NS_UpdateHint(hint, nsChangeHint_NeedReflow);
   }
 
   // Properties that apply only to multi-line flex containers:
   // 'align-content' can change the positioning & sizing of a multi-line flex
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -1147,16 +1147,48 @@ struct nsStyleList {
   uint8_t   mListStylePosition;         // [inherited]
 private:
   nsRefPtr<imgRequestProxy> mListStyleImage; // [inherited]
   nsStyleList& operator=(const nsStyleList& aOther) MOZ_DELETE;
 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
+  nsTArray<nsTArray<nsString>> mLineNameLists;
+  nsTArray<nsStyleCoord> mMinTrackSizingFunctions;
+  nsTArray<nsStyleCoord> mMaxTrackSizingFunctions;
+
+  inline bool operator!=(const nsStyleGridTrackList& aOther) const {
+    return mLineNameLists != aOther.mLineNameLists ||
+           mMinTrackSizingFunctions != aOther.mMinTrackSizingFunctions ||
+           mMaxTrackSizingFunctions != aOther.mMaxTrackSizingFunctions;
+  }
+};
+
 struct nsStylePosition {
   nsStylePosition(void);
   nsStylePosition(const nsStylePosition& aOther);
   ~nsStylePosition(void);
 
   void* operator new(size_t sz, nsPresContext* aContext) CPP_THROW_NEW {
     return aContext->AllocateFromShell(sz);
   }
@@ -1191,16 +1223,22 @@ struct nsStylePosition {
   uint8_t       mAlignSelf;             // [reset] see nsStyleConsts.h
   uint8_t       mFlexDirection;         // [reset] see nsStyleConsts.h
   uint8_t       mFlexWrap;              // [reset] see nsStyleConsts.h
   uint8_t       mJustifyContent;        // [reset] see nsStyleConsts.h
   int32_t       mOrder;                 // [reset] integer
   float         mFlexGrow;              // [reset] float
   float         mFlexShrink;            // [reset] float
   nsStyleCoord  mZIndex;                // [reset] integer, auto
+  // NOTE: Fields so far can be memcpy()'ed, while following fields
+  // 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;
 
   bool WidthDependsOnContainer() const
     { return WidthCoordDependsOnContainer(mWidth); }
   bool MinWidthDependsOnContainer() const
     { return WidthCoordDependsOnContainer(mMinWidth); }
   bool MaxWidthDependsOnContainer() const
     { return WidthCoordDependsOnContainer(mMaxWidth); }
 
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -4787,16 +4787,74 @@ if (SpecialPowers.getBoolPref("layout.cs
 			"sepia(10px)",
 			"sepia(-1)",
 		]
 	};
 }
 
 if (SpecialPowers.getBoolPref("layout.css.grid.enabled")) {
 	gCSSProperties["display"].other_values.push("grid", "inline-grid");
+	gCSSProperties["grid-template-columns"] = {
+		domProp: "gridTemplateColumns",
+		inherited: false,
+		type: CSS_TYPE_LONGHAND,
+		initial_values: [ "none" ],
+		other_values: [
+			"auto",
+			"40px",
+			"2.5fr",
+			"(normal) 40px () auto ( ) 12%",
+			"(foo) 40px min-content ( bar ) calc(20px + 10%) max-content",
+			"40px min-content calc(20px + 10%) max-content",
+			"m\\69nmax(20px, 4Fr)",
+			"40px MinMax(min-content, calc(20px + 10%)) max-content",
+			"40px 2em",
+			"() 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",
+		],
+		invalid_values: [
+			"",
+			"normal",
+			"40ms",
+			"-40px",
+			"-12%",
+			"-2fr",
+			"(foo)",
+			"(inherit) 40px",
+			"(initial) 40px",
+			"(unset) 40px",
+			"(default) 40px",
+			"(6%) 40px",
+			"(5th) 40px",
+			"(foo() bar) 40px",
+			"(foo)) 40px",
+			"(foo] 40px",
+			"[foo] 40px",
+			"(foo) (bar) 40px",
+			"40px (foo) (bar)",
+			"minmax()",
+			"minmax(20px)",
+			"mİnmax(20px, 100px)",
+			"minmax(20px, 100px, 200px)",
+			"maxmin(100px, 20px)",
+			"minmax(min-content, auto)",
+			"minmax(min-content, minmax(30px, max-content))",
+		]
+	};
+	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,
+		invalid_values: gCSSProperties["grid-template-columns"].invalid_values
+	};
 }
 
 if (SpecialPowers.getBoolPref("layout.css.image-orientation.enabled")) {
 	gCSSProperties["image-orientation"] = {
 		domProp: "imageOrientation",
 		inherited: true,
 		type: CSS_TYPE_LONGHAND,
 		initial_values: [