Bug 773296 - Part 2: Parse CSS variable declarations and store them on Declaration objects. p=ebassi,heycam r=dbaron
authorCameron McCormack <cam@mcc.id.au>
Thu, 12 Dec 2013 13:09:40 +1100
changeset 160018 e6e4e9b6cf6a1a84b4c56882a10dffb908884150
parent 160017 6ceff347849df2add64f8a0bc680757f1e95c66c
child 160019 7069d52b1e601f6bc55b4609e055233b7f5f734d
push id37483
push usercmccormack@mozilla.com
push dateThu, 12 Dec 2013 02:11:02 +0000
treeherdermozilla-inbound@1390f263b8ce [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs773296
milestone29.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 773296 - Part 2: Parse CSS variable declarations and store them on Declaration objects. p=ebassi,heycam r=dbaron Patch co-authored by Emmanuele Bassi <ebassi@gmail.com> This defines a CSSVariableDeclarations class that holds a set of variable declarations. This is at the specified value stage, so values can either be 'initial', 'inherit' or a token stream (which is what you normally have). The variables are stored in a hash table. Although it's a bit of a hack, we store 'initial' and 'inherit' using special string values that can't be valid token streams (we use "!" and ";"). Declaration objects now can have two CSSVariableDeclarations objects on them, to store normal and !important variable declarations. So that we keep preserving the order of declarations on the object, we inflate mOrder to store uint32_ts, where values from eCSSProperty_COUNT onwards represent custom properties. mVariableOrder stores the names of the variables corresponding to those entries in mOrder. We also add a new nsCSSProperty value, eCSSPropertyExtra_variable, which is used to represent any custom property name. nsCSSProps::LookupProperty can return this value. The changes to nsCSSParser are straightforward. Custom properties are parsed and checked for syntactic validity (e.g. "var(a,)" being invalid) and stored on the Declaration. We use nsCSSScanner's recording ability to grab the unparsed CSS string corresponding to the variable's value.
dom/locales/en-US/chrome/layout/css.properties
layout/style/CSSVariableDeclarations.cpp
layout/style/CSSVariableDeclarations.h
layout/style/Declaration.cpp
layout/style/Declaration.h
layout/style/moz.build
layout/style/nsAnimationManager.cpp
layout/style/nsCSSParser.cpp
layout/style/nsCSSProperty.h
layout/style/nsCSSProps.cpp
layout/style/nsCSSProps.h
--- a/dom/locales/en-US/chrome/layout/css.properties
+++ b/dom/locales/en-US/chrome/layout/css.properties
@@ -139,8 +139,10 @@ PESupportsConditionExpectedCloseParen=Ex
 PESupportsConditionExpectedStart2=Expected 'not', '(', or function while parsing supports condition but found '%1$S'.
 PESupportsConditionExpectedNot=Expected 'not' while parsing supports condition but found '%1$S'.
 PESupportsGroupRuleStart=Expected '{' to begin @supports rule but found '%1$S'.
 PEFilterEOF=filter
 PEExpectedNoneOrURL=Expected 'none' or URL but found '%1$S'.
 PEExpectedNoneOrURLOrFilterFunction=Expected 'none', URL, or filter function but found '%1$S'.
 PEExpectedNonnegativeNP=Expected non-negative number or percentage.
 PEFilterFunctionArgumentsParsingError=Error in parsing arguments for filter function.
+PEVariableEOF=variable
+PEVariableEmpty=Expected variable value but found '%1$S'.
new file mode 100644
--- /dev/null
+++ b/layout/style/CSSVariableDeclarations.cpp
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CSSVariableDeclarations.h"
+
+// These two special string values are used to represent specified values of
+// 'initial' and 'inherit'.  (Note that neither is a valid variable value.)
+#define INITIAL_VALUE "!"
+#define INHERIT_VALUE ";"
+
+namespace mozilla {
+
+CSSVariableDeclarations::CSSVariableDeclarations()
+{
+  MOZ_COUNT_CTOR(CSSVariableDeclarations);
+}
+
+CSSVariableDeclarations::CSSVariableDeclarations(const CSSVariableDeclarations& aOther)
+{
+  MOZ_COUNT_CTOR(CSSVariableDeclarations);
+  CopyVariablesFrom(aOther);
+}
+
+#ifdef DEBUG
+CSSVariableDeclarations::~CSSVariableDeclarations()
+{
+  MOZ_COUNT_DTOR(CSSVariableDeclarations);
+}
+#endif
+
+CSSVariableDeclarations&
+CSSVariableDeclarations::operator=(const CSSVariableDeclarations& aOther)
+{
+  mVariables.Clear();
+  CopyVariablesFrom(aOther);
+  return *this;
+}
+
+/* static */ PLDHashOperator
+CSSVariableDeclarations::EnumerateVariableForCopy(const nsAString& aName,
+                                                  nsString aValue,
+                                                  void* aData)
+{
+  CSSVariableDeclarations* variables = static_cast<CSSVariableDeclarations*>(aData);
+  variables->mVariables.Put(aName, aValue);
+  return PL_DHASH_NEXT;
+}
+
+void
+CSSVariableDeclarations::CopyVariablesFrom(const CSSVariableDeclarations& aOther)
+{
+  aOther.mVariables.EnumerateRead(EnumerateVariableForCopy, this);
+}
+
+bool
+CSSVariableDeclarations::Has(const nsAString& aName) const
+{
+  nsString value;
+  return mVariables.Get(aName, &value);
+}
+
+bool
+CSSVariableDeclarations::Get(const nsAString& aName,
+                             Type& aType,
+                             nsString& aTokenStream) const
+{
+  nsString value;
+  if (!mVariables.Get(aName, &value)) {
+    return false;
+  }
+  if (value.EqualsLiteral(INITIAL_VALUE)) {
+    aType = eInitial;
+    aTokenStream.Truncate();
+  } else if (value.EqualsLiteral(INHERIT_VALUE)) {
+    aType = eInitial;
+    aTokenStream.Truncate();
+  } else {
+    aType = eTokenStream;
+    aTokenStream = value;
+  }
+  return true;
+}
+
+void
+CSSVariableDeclarations::PutTokenStream(const nsAString& aName,
+                                        const nsString& aTokenStream)
+{
+  MOZ_ASSERT(!aTokenStream.EqualsLiteral(INITIAL_VALUE) &&
+             !aTokenStream.EqualsLiteral(INHERIT_VALUE));
+  mVariables.Put(aName, aTokenStream);
+}
+
+void
+CSSVariableDeclarations::PutInitial(const nsAString& aName)
+{
+  mVariables.Put(aName, NS_LITERAL_STRING(INITIAL_VALUE));
+}
+
+void
+CSSVariableDeclarations::PutInherit(const nsAString& aName)
+{
+  mVariables.Put(aName, NS_LITERAL_STRING(INHERIT_VALUE));
+}
+
+void
+CSSVariableDeclarations::Remove(const nsAString& aName)
+{
+  mVariables.Remove(aName);
+}
+
+static size_t
+SizeOfTableEntry(const nsAString& aKey,
+                 const nsString& aValue,
+                 MallocSizeOf aMallocSizeOf,
+                 void* aUserArg)
+{
+  size_t n = 0;
+  n += aKey.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+  n += aValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+  return n;
+}
+
+size_t
+CSSVariableDeclarations::SizeOfIncludingThis(
+                                      mozilla::MallocSizeOf aMallocSizeOf) const
+{
+  size_t n = aMallocSizeOf(this);
+  n += mVariables.SizeOfExcludingThis(SizeOfTableEntry, aMallocSizeOf);
+  return n;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/style/CSSVariableDeclarations.h
@@ -0,0 +1,115 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* CSS Custom Property assignments for a Declaration at a given priority */
+
+#ifndef mozilla_CSSVariableDeclarations_h
+#define mozilla_CSSVariableDeclarations_h
+
+#include "nsDataHashtable.h"
+
+namespace mozilla {
+
+class CSSVariableDeclarations
+{
+public:
+  CSSVariableDeclarations();
+  CSSVariableDeclarations(const CSSVariableDeclarations& aOther);
+#ifdef DEBUG
+  ~CSSVariableDeclarations();
+#endif
+  CSSVariableDeclarations& operator=(const CSSVariableDeclarations& aOther);
+
+  /**
+   * Returns whether this set of variable declarations includes a variable
+   * with a given name.
+   *
+   * @param aName The variable name (not including any "var-" prefix that would
+   *   be part of the custom property name).
+   */
+  bool Has(const nsAString& aName) const;
+
+  /**
+   * Represents the type of a variable value.
+   */
+  enum Type {
+    eTokenStream,  // a stream of CSS tokens (the usual type for variables)
+    eInitial,      // 'initial'
+    eInherit       // 'inherit'
+  };
+
+  /**
+   * Gets the value of a variable in this set of variable declarations.
+   *
+   * @param aName The variable name (not including any "var-" prefix that would
+   *   be part of the custom property name).
+   * @param aType Out parameter into which the type of the variable value will
+   *   be stored.
+   * @param aValue Out parameter into which the value of the variable will
+   *   be stored.  If the variable is 'initial' or 'inherit', this will be
+   *   the empty string.
+   * @return Whether a variable with the given name was found.  When false
+   *   is returned, aType and aValue will not be modified.
+   */
+  bool Get(const nsAString& aName, Type& aType, nsString& aValue) const;
+
+  /**
+   * Adds or modifies an existing entry in this set of variable declarations
+   * to have the value 'initial'.
+   *
+   * @param aName The variable name (not including any "var-" prefix that would
+   *   be part of the custom property name) whose value is to be set.
+   */
+  void PutInitial(const nsAString& aName);
+
+  /**
+   * Adds or modifies an existing entry in this set of variable declarations
+   * to have the value 'inherit'.
+   *
+   * @param aName The variable name (not including any "var-" prefix that would
+   *   be part of the custom property name) whose value is to be set.
+   */
+  void PutInherit(const nsAString& aName);
+
+  /**
+   * Adds or modifies an existing entry in this set of variable declarations
+   * to have a token stream value.
+   *
+   * @param aName The variable name (not including any "var-" prefix that would
+   *   be part of the custom property name) whose value is to be set.
+   * @param aTokenStream The CSS token stream.
+   */
+  void PutTokenStream(const nsAString& aName, const nsString& aTokenStream);
+
+  /**
+   * Removes an entry in this set of variable declarations.
+   *
+   * @param aName The variable name (not including any "var-" prefix that would
+   *   be part of the custom property name) whose entry is to be removed.
+   */
+  void Remove(const nsAString& aName);
+
+  /**
+   * Returns the number of entries in this set of variable declarations.
+   */
+  uint32_t Count() const { return mVariables.Count(); }
+
+  size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+private:
+  /**
+   * Adds all the variable declarations from aOther into this object.
+   */
+  void CopyVariablesFrom(const CSSVariableDeclarations& aOther);
+  static PLDHashOperator EnumerateVariableForCopy(const nsAString& aName,
+                                                  nsString aValue,
+                                                  void* aData);
+
+  nsDataHashtable<nsStringHashKey, nsString> mVariables;
+};
+
+} // namespace mozilla
+
+#endif
--- a/layout/style/Declaration.cpp
+++ b/layout/style/Declaration.cpp
@@ -13,32 +13,34 @@
 
 #include "mozilla/css/Declaration.h"
 #include "nsPrintfCString.h"
 #include "gfxFontConstants.h"
 
 namespace mozilla {
 namespace css {
 
-// check that we can fit all the CSS properties into a uint16_t
-// for the mOrder array
-static_assert(eCSSProperty_COUNT_no_shorthands - 1 <= UINT16_MAX,
-              "CSS longhand property numbers no longer fit in a uint16_t");
-
 Declaration::Declaration()
   : mImmutable(false)
 {
   MOZ_COUNT_CTOR(mozilla::css::Declaration);
 }
 
 Declaration::Declaration(const Declaration& aCopy)
   : mOrder(aCopy.mOrder),
+    mVariableOrder(aCopy.mVariableOrder),
     mData(aCopy.mData ? aCopy.mData->Clone() : nullptr),
-    mImportantData(aCopy.mImportantData
-                   ? aCopy.mImportantData->Clone() : nullptr),
+    mImportantData(aCopy.mImportantData ?
+                     aCopy.mImportantData->Clone() : nullptr),
+    mVariables(aCopy.mVariables ?
+        new CSSVariableDeclarations(*aCopy.mVariables) :
+        nullptr),
+    mImportantVariables(aCopy.mImportantVariables ?
+        new CSSVariableDeclarations(*aCopy.mImportantVariables) :
+        nullptr),
     mImmutable(false)
 {
   MOZ_COUNT_CTOR(mozilla::css::Declaration);
 }
 
 Declaration::~Declaration()
 {
   MOZ_COUNT_DTOR(mozilla::css::Declaration);
@@ -47,35 +49,37 @@ Declaration::~Declaration()
 void
 Declaration::ValueAppended(nsCSSProperty aProperty)
 {
   NS_ABORT_IF_FALSE(!mData && !mImportantData,
                     "should only be called while expanded");
   NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty),
                     "shorthands forbidden");
   // order IS important for CSS, so remove and add to the end
-  mOrder.RemoveElement(aProperty);
-  mOrder.AppendElement(aProperty);
+  mOrder.RemoveElement(static_cast<uint32_t>(aProperty));
+  mOrder.AppendElement(static_cast<uint32_t>(aProperty));
 }
 
 void
 Declaration::RemoveProperty(nsCSSProperty aProperty)
 {
+  MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT);
+
   nsCSSExpandedDataBlock data;
   ExpandTo(&data);
   NS_ABORT_IF_FALSE(!mData && !mImportantData, "Expand didn't null things out");
 
   if (nsCSSProps::IsShorthand(aProperty)) {
     CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty) {
       data.ClearLonghandProperty(*p);
-      mOrder.RemoveElement(*p);
+      mOrder.RemoveElement(static_cast<uint32_t>(*p));
     }
   } else {
     data.ClearLonghandProperty(aProperty);
-    mOrder.RemoveElement(aProperty);
+    mOrder.RemoveElement(static_cast<uint32_t>(aProperty));
   }
 
   CompressFrom(&data);
 }
 
 bool
 Declaration::HasProperty(nsCSSProperty aProperty) const
 {
@@ -878,23 +882,29 @@ Declaration::GetValue(nsCSSProperty aPro
       // so serialize as the empty string.
       break;
     default:
       NS_ABORT_IF_FALSE(false, "no other shorthands");
       break;
   }
 }
 
+// Length of the "var-" prefix of custom property names.
+#define VAR_PREFIX_LENGTH 4
+
 bool
 Declaration::GetValueIsImportant(const nsAString& aProperty) const
 {
   nsCSSProperty propID = nsCSSProps::LookupProperty(aProperty, nsCSSProps::eAny);
   if (propID == eCSSProperty_UNKNOWN) {
     return false;
   }
+  if (propID == eCSSPropertyExtra_variable) {
+    return GetVariableValueIsImportant(Substring(aProperty, VAR_PREFIX_LENGTH));
+  }
   return GetValueIsImportant(propID);
 }
 
 bool
 Declaration::GetValueIsImportant(nsCSSProperty aProperty) const
 {
   if (!mImportantData)
     return false;
@@ -935,16 +945,63 @@ Declaration::AppendPropertyAndValueToStr
     aResult.Append(aValue);
   if (GetValueIsImportant(aProperty)) {
     aResult.AppendLiteral(" ! important");
   }
   aResult.AppendLiteral("; ");
 }
 
 void
+Declaration::AppendVariableAndValueToString(const nsAString& aName,
+                                            nsAString& aResult) const
+{
+  aResult.AppendLiteral("var-");
+  aResult.Append(aName);
+  CSSVariableDeclarations::Type type;
+  nsString value;
+  bool important;
+
+  if (mImportantVariables && mImportantVariables->Get(aName, type, value)) {
+    important = true;
+  } else {
+    MOZ_ASSERT(mVariables);
+    MOZ_ASSERT(mVariables->Has(aName));
+    mVariables->Get(aName, type, value);
+    important = false;
+  }
+
+  switch (type) {
+    case CSSVariableDeclarations::eTokenStream:
+      if (value.IsEmpty()) {
+        aResult.Append(':');
+      } else {
+        aResult.AppendLiteral(": ");
+        aResult.Append(value);
+      }
+      break;
+
+    case CSSVariableDeclarations::eInitial:
+      aResult.AppendLiteral("initial");
+      break;
+
+    case CSSVariableDeclarations::eInherit:
+      aResult.AppendLiteral("inherit");
+      break;
+
+    default:
+      MOZ_ASSERT(false, "unexpected variable value type");
+  }
+
+  if (important) {
+    aResult.AppendLiteral("! important");
+  }
+  aResult.AppendLiteral("; ");
+}
+
+void
 Declaration::ToString(nsAString& aString) const
 {
   // Someone cares about this declaration's contents, so don't let it
   // change from under them.  See e.g. bug 338679.
   SetImmutable();
 
   nsCSSCompressedDataBlock *systemFontData =
     GetValueIsImportant(eCSSProperty__x_system_font) ? mImportantData : mData;
@@ -954,17 +1011,24 @@ Declaration::ToString(nsAString& aString
                                 systemFont->GetUnit() != eCSSUnit_None &&
                                 systemFont->GetUnit() != eCSSUnit_Null;
   bool didSystemFont = false;
 
   int32_t count = mOrder.Length();
   int32_t index;
   nsAutoTArray<nsCSSProperty, 16> shorthandsUsed;
   for (index = 0; index < count; index++) {
-    nsCSSProperty property = OrderValueAt(index);
+    nsCSSProperty property = GetPropertyAt(index);
+
+    if (property == eCSSPropertyExtra_variable) {
+      uint32_t variableIndex = mOrder[index] - eCSSProperty_COUNT;
+      AppendVariableAndValueToString(mVariableOrder[variableIndex], aString);
+      continue;
+    }
+
     if (!nsCSSProps::IsEnabled(property)) {
       continue;
     }
     bool doneProperty = false;
 
     // If we already used this property in a shorthand, skip it.
     if (shorthandsUsed.Length() > 0) {
       for (const nsCSSProperty *shorthands =
@@ -1053,17 +1117,21 @@ Declaration::List(FILE* out, int32_t aIn
 }
 #endif
 
 bool
 Declaration::GetNthProperty(uint32_t aIndex, nsAString& aReturn) const
 {
   aReturn.Truncate();
   if (aIndex < mOrder.Length()) {
-    nsCSSProperty property = OrderValueAt(aIndex);
+    nsCSSProperty property = GetPropertyAt(aIndex);
+    if (property == eCSSPropertyExtra_variable) {
+      GetCustomPropertyNameAt(aIndex, aReturn);
+      return true;
+    }
     if (0 <= property) {
       AppendASCIItoUTF16(nsCSSProps::GetStringValue(property), aReturn);
       return true;
     }
   }
   return false;
 }
 
@@ -1087,13 +1155,135 @@ Declaration::EnsureMutable()
 
 size_t
 Declaration::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
   size_t n = aMallocSizeOf(this);
   n += mOrder.SizeOfExcludingThis(aMallocSizeOf);
   n += mData          ? mData         ->SizeOfIncludingThis(aMallocSizeOf) : 0;
   n += mImportantData ? mImportantData->SizeOfIncludingThis(aMallocSizeOf) : 0;
+  if (mVariables) {
+    n += mVariables->SizeOfIncludingThis(aMallocSizeOf);
+  }
+  if (mImportantVariables) {
+    n += mImportantVariables->SizeOfIncludingThis(aMallocSizeOf);
+  }
   return n;
 }
 
+bool
+Declaration::HasVariableDeclaration(const nsAString& aName) const
+{
+  return (mVariables && mVariables->Has(aName)) ||
+         (mImportantVariables && mImportantVariables->Has(aName));
+}
+
+void
+Declaration::GetVariableDeclaration(const nsAString& aName,
+                                    nsAString& aValue) const
+{
+  aValue.Truncate();
+
+  CSSVariableDeclarations::Type type;
+  nsString value;
+
+  if ((mImportantVariables && mImportantVariables->Get(aName, type, value)) ||
+      (mVariables && mVariables->Get(aName, type, value))) {
+    switch (type) {
+      case CSSVariableDeclarations::eTokenStream:
+        aValue.Append(value);
+        break;
+
+      case CSSVariableDeclarations::eInitial:
+        aValue.AppendLiteral("initial");
+        break;
+
+      case CSSVariableDeclarations::eInherit:
+        aValue.AppendLiteral("inherit");
+        break;
+
+      default:
+        MOZ_ASSERT(false, "unexpected variable value type");
+    }
+  }
+}
+
+void
+Declaration::AddVariableDeclaration(const nsAString& aName,
+                                    CSSVariableDeclarations::Type aType,
+                                    const nsString& aValue,
+                                    bool aIsImportant)
+{
+  MOZ_ASSERT(IsMutable());
+
+  nsTArray<nsString>::index_type index = mVariableOrder.IndexOf(aName);
+  if (index == nsTArray<nsString>::NoIndex) {
+    index = mVariableOrder.Length();
+    mVariableOrder.AppendElement(aName);
+  }
+
+  if (!aIsImportant && mImportantVariables && mImportantVariables->Has(aName)) {
+    return;
+  }
+
+  CSSVariableDeclarations* variables;
+  if (aIsImportant) {
+    if (mVariables) {
+      mVariables->Remove(aName);
+    }
+    if (!mImportantVariables) {
+      mImportantVariables = new CSSVariableDeclarations;
+    }
+    variables = mImportantVariables;
+  } else {
+    if (!mVariables) {
+      mVariables = new CSSVariableDeclarations;
+    }
+    variables = mVariables;
+  }
+
+  switch (aType) {
+    case CSSVariableDeclarations::eTokenStream:
+      variables->PutTokenStream(aName, aValue);
+      break;
+
+    case CSSVariableDeclarations::eInitial:
+      MOZ_ASSERT(aValue.IsEmpty());
+      variables->PutInitial(aName);
+      break;
+
+    case CSSVariableDeclarations::eInherit:
+      MOZ_ASSERT(aValue.IsEmpty());
+      variables->PutInherit(aName);
+      break;
+
+    default:
+      MOZ_ASSERT("unexpected aType value");
+  }
+
+  uint32_t propertyIndex = index + eCSSProperty_COUNT;
+  mOrder.RemoveElement(propertyIndex);
+  mOrder.AppendElement(propertyIndex);
+}
+
+void
+Declaration::RemoveVariableDeclaration(const nsAString& aName)
+{
+  if (mVariables) {
+    mVariables->Remove(aName);
+  }
+  if (mImportantVariables) {
+    mImportantVariables->Remove(aName);
+  }
+  nsTArray<nsString>::index_type index = mVariableOrder.IndexOf(aName);
+  if (index != nsTArray<nsString>::NoIndex) {
+    mOrder.RemoveElement(index + eCSSProperty_COUNT);
+  }
+}
+
+bool
+Declaration::GetVariableValueIsImportant(const nsAString& aName) const
+{
+  return mImportantVariables && mImportantVariables->Has(aName);
+}
+
 } // namespace mozilla::css
 } // namespace mozilla
--- a/layout/style/Declaration.h
+++ b/layout/style/Declaration.h
@@ -6,25 +6,25 @@
 /*
  * representation of a declaration block (or style attribute) in a CSS
  * stylesheet
  */
 
 #ifndef mozilla_css_Declaration_h
 #define mozilla_css_Declaration_h
 
-#include "mozilla/Attributes.h"
-#include "mozilla/MemoryReporting.h"
-
 // This header is in EXPORTS because it's used in several places in content/,
 // but it's not really a public interface.
 #ifndef MOZILLA_INTERNAL_API
 #error "This file should only be included within libxul"
 #endif
 
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "CSSVariableDeclarations.h"
 #include "nsCSSDataBlock.h"
 #include "nsCSSProperty.h"
 #include "nsCSSProps.h"
 #include "nsStringFwd.h"
 #include "nsTArray.h"
 #include <stdio.h>
 
 namespace mozilla {
@@ -63,16 +63,62 @@ public:
   bool HasProperty(nsCSSProperty aProperty) const;
 
   void GetValue(nsCSSProperty aProperty, nsAString& aValue) const;
 
   bool HasImportantData() const { return mImportantData != nullptr; }
   bool GetValueIsImportant(nsCSSProperty aProperty) const;
   bool GetValueIsImportant(const nsAString& aProperty) const;
 
+  /**
+   * Adds a custom property declaration to this object.
+   *
+   * @param aName The variable name (i.e., without the "var-" prefix).
+   * @param aType The type of value the variable has.
+   * @param aValue The value of the variable, if aType is
+   *   CSSVariableDeclarations::eTokenStream.
+   * @param aIsImportant Whether the declaration is !important.
+   */
+  void AddVariableDeclaration(const nsAString& aName,
+                              CSSVariableDeclarations::Type aType,
+                              const nsString& aValue,
+                              bool aIsImportant);
+
+  /**
+   * Removes a custom property declaration from this object.
+   *
+   * @param aName The variable name (i.e., without the "var-" prefix).
+   */
+  void RemoveVariableDeclaration(const nsAString& aName);
+
+  /**
+   * Returns whether a custom property declaration for a variable with
+   * a given name exists on this object.
+   *
+   * @param aName The variable name (i.e., without the "var-" prefix).
+   */
+  bool HasVariableDeclaration(const nsAString& aName) const;
+
+  /**
+   * Gets the string value for a custom property declaration of a variable
+   * with a given name.
+   *
+   * @param aName The variable name (i.e., without the "var-" prefix).
+   * @param aValue Out parameter into which the variable's value will be
+   *   stored.  If the value is 'initial' or 'inherit', that exact string
+   *   will be stored in aValue.
+   */
+  void GetVariableDeclaration(const nsAString& aName, nsAString& aValue) const;
+
+  /**
+   * Returns whether the custom property declaration for a variable with
+   * the given name was !important.
+   */
+  bool GetVariableValueIsImportant(const nsAString& aName) const;
+
   uint32_t Count() const {
     return mOrder.Length();
   }
 
   // Returns whether we actually had a property at aIndex
   bool GetNthProperty(uint32_t aIndex, nsAString& aReturn) const;
 
   void ToString(nsAString& aString) const;
@@ -199,17 +245,20 @@ public:
   /**
    * Clear the data, in preparation for its replacement with entirely
    * new data by a call to |CompressFrom|.
    */
   void ClearData() {
     AssertMutable();
     mData = nullptr;
     mImportantData = nullptr;
+    mVariables = nullptr;
+    mImportantVariables = nullptr;
     mOrder.Clear();
+    mVariableOrder.Clear();
   }
 
 #ifdef DEBUG
   void List(FILE* out = stdout, int32_t aIndent = 0) const;
 #endif
 
 private:
   Declaration& operator=(const Declaration& aCopy) MOZ_DELETE;
@@ -217,34 +266,74 @@ private:
 
   static void AppendImportanceToString(bool aIsImportant, nsAString& aString);
   // return whether there was a value in |aValue| (i.e., it had a non-null unit)
   bool AppendValueToString(nsCSSProperty aProperty, nsAString& aResult) const;
   // Helper for ToString with strange semantics regarding aValue.
   void AppendPropertyAndValueToString(nsCSSProperty aProperty,
                                       nsAutoString& aValue,
                                       nsAString& aResult) const;
+  // helper for ToString that serializes a custom property declaration for
+  // a variable with the specified name
+  void AppendVariableAndValueToString(const nsAString& aName,
+                                      nsAString& aResult) const;
 
 public:
-  nsCSSProperty OrderValueAt(uint32_t aValue) const {
-    return nsCSSProperty(mOrder.ElementAt(aValue));
+  /**
+   * Returns the property at the given index in the ordered list of
+   * declarations.  For custom properties, eCSSPropertyExtra_variable
+   * is returned.
+   */
+  nsCSSProperty GetPropertyAt(uint32_t aIndex) const {
+    uint32_t value = mOrder[aIndex];
+    if (value >= eCSSProperty_COUNT) {
+      return eCSSPropertyExtra_variable;
+    }
+    return nsCSSProperty(value);
+  }
+
+  /**
+   * Gets the name of the custom property at the given index in the ordered
+   * list of declarations.
+   */
+  void GetCustomPropertyNameAt(uint32_t aIndex, nsAString& aResult) const {
+    MOZ_ASSERT(mOrder[aIndex] >= eCSSProperty_COUNT);
+    aResult.Truncate();
+    aResult.AppendLiteral("var-");
+    aResult.Append(mVariableOrder[aIndex]);
   }
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
 private:
-  nsAutoTArray<uint16_t, 8> mOrder;
+  // The order of properties in this declaration.  Longhand properties are
+  // represented by their nsCSSProperty value, and each custom property (var-*)
+  // is represented by a value that begins at eCSSProperty_COUNT.
+  //
+  // Subtracting eCSSProperty_COUNT from those values that represent custom
+  // properties results in an index into mVariableOrder, which identifies the
+  // specific variable the custom property declaration is for.
+  nsAutoTArray<uint32_t, 8> mOrder;
+
+  // variable names of custom properties found in mOrder
+  nsTArray<nsString> mVariableOrder;
 
   // never null, except while expanded, or before the first call to
   // InitializeEmpty or CompressFrom.
   nsAutoPtr<nsCSSCompressedDataBlock> mData;
 
   // may be null
   nsAutoPtr<nsCSSCompressedDataBlock> mImportantData;
 
+  // may be null
+  nsAutoPtr<CSSVariableDeclarations> mVariables;
+
+  // may be null
+  nsAutoPtr<CSSVariableDeclarations> mImportantVariables;
+
   // set by style rules when |RuleMatched| is called;
   // also by ToString (hence the 'mutable').
   mutable bool mImmutable;
 };
 
 } // namespace css
 } // namespace mozilla
 
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -52,16 +52,20 @@ EXPORTS += [
     'nsStyleSet.h',
     'nsStyleStruct.h',
     'nsStyleStructFwd.h',
     'nsStyleStructInlines.h',
     'nsStyleTransformMatrix.h',
     'nsStyleUtil.h',
 ]
 
+EXPORTS.mozilla += [
+    'CSSVariableDeclarations.h',
+]
+
 EXPORTS.mozilla.dom += [
     'CSS.h',
     'CSSValue.h',
 ]
 
 EXPORTS.mozilla.css += [
     'Declaration.h',
     'ErrorReporter.h',
@@ -72,16 +76,17 @@ EXPORTS.mozilla.css += [
     'NameSpaceRule.h',
     'Rule.h',
     'StyleRule.h',
 ]
 
 UNIFIED_SOURCES += [
     'AnimationCommon.cpp',
     'CSS.cpp',
+    'CSSVariableDeclarations.cpp',
     'Declaration.cpp',
     'ErrorReporter.cpp',
     'ImageLoader.cpp',
     'Loader.cpp',
     'nsAnimationManager.cpp',
     'nsComputedDOMStyle.cpp',
     'nsCSSAnonBoxes.cpp',
     'nsCSSDataBlock.cpp',
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -816,17 +816,21 @@ nsAnimationManager::BuildAnimations(nsSt
     // are using.
     nsCSSPropertySet properties;
 
     for (uint32_t kfIdx = 0, kfEnd = sortedKeyframes.Length();
          kfIdx != kfEnd; ++kfIdx) {
       css::Declaration *decl = sortedKeyframes[kfIdx].mRule->Declaration();
       for (uint32_t propIdx = 0, propEnd = decl->Count();
            propIdx != propEnd; ++propIdx) {
-        properties.AddProperty(decl->OrderValueAt(propIdx));
+        nsCSSProperty prop = decl->GetPropertyAt(propIdx);
+        if (prop != eCSSPropertyExtra_variable) {
+          // CSS Variables are not animatable
+          properties.AddProperty(prop);
+        }
       }
     }
 
     for (nsCSSProperty prop = nsCSSProperty(0);
          prop < eCSSProperty_COUNT_no_shorthands;
          prop = nsCSSProperty(prop + 1)) {
       if (!properties.HasProperty(prop) ||
           nsCSSProps::kAnimTypeTable[prop] == eStyleAnimType_None) {
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -49,16 +49,19 @@ const uint32_t
 nsCSSProps::kParserVariantTable[eCSSProperty_COUNT_no_shorthands] = {
 #define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \
                  stylestruct_, stylestructoffset_, animtype_)                 \
   parsevariant_,
 #include "nsCSSPropList.h"
 #undef CSS_PROP
 };
 
+// Length of the "var-" prefix of custom property names.
+#define VAR_PREFIX_LENGTH 4
+
 namespace {
 
 // Rule processing function
 typedef void (* RuleAppendFunc) (css::Rule* aRule, void* aData);
 static void AssignRuleToPointer(css::Rule* aRule, void* aPointer);
 static void AppendRuleToSheet(css::Rule* aRule, void* aParser);
 
 // Your basic top-down recursive descent style parser
@@ -255,16 +258,20 @@ protected:
   bool ExpectSymbol(PRUnichar aSymbol, bool aSkipWS);
   bool ExpectEndProperty();
   bool CheckEndProperty();
   nsSubstring* NextIdent();
 
   // returns true when the stop symbol is found, and false for EOF
   bool SkipUntil(PRUnichar aStopSymbol);
   void SkipUntilOneOf(const PRUnichar* aStopSymbolChars);
+  // For each character in aStopSymbolChars from the end of the array
+  // to the start, calls SkipUntil with that character.
+  typedef nsAutoTArray<PRUnichar, 16> StopSymbolCharStack;
+  void SkipUntilAllOf(const StopSymbolCharStack& aStopSymbolChars);
 
   void SkipRuleSet(bool aInsideBraces);
   bool SkipAtRule(bool aInsideBlock);
   bool SkipDeclaration(bool aCheckForBraces);
 
   void PushGroup(css::GroupRule* aRule);
   void PopGroup();
 
@@ -538,16 +545,42 @@ protected:
   bool ParseAnimation();
 
   bool ParsePaint(nsCSSProperty aPropID);
   bool ParseDasharray();
   bool ParseMarker();
   bool ParsePaintOrder();
   bool ParseAll();
 
+  /**
+   * Parses a variable value from a custom property declaration.
+   *
+   * @param aType Out parameter into which will be stored the type of variable
+   *   value, indicating whether the parsed value was a token stream or one of
+   *   the CSS-wide keywords.
+   * @param aValue Out parameter into which will be stored the token stream
+   *   as a string, if the parsed custom property did take a token stream.
+   * @return Whether parsing succeeded.
+   */
+  bool ParseVariableDeclaration(CSSVariableDeclarations::Type* aType,
+                                nsString& aValue);
+
+  /**
+   * Parses a CSS variable value.  This could be 'initial', 'inherit'
+   * or a token stream, which may or may not include variable references.
+   *
+   * @param aType Out parameter into which the type of the variable value
+   *   will be stored.
+   * @param aClosingChars Out parameter appended to which will be any
+   *   closing characters that were implied when encountering EOF.
+   * @return Whether parsing succeeded.
+   */
+  bool ParseValueWithVariables(CSSVariableDeclarations::Type* aType,
+                               nsString& aClosingChars);
+
   // Reused utility parsing routines
   void AppendValue(nsCSSProperty aPropID, const nsCSSValue& aValue);
   bool ParseBoxProperties(const nsCSSProperty aPropIDs[]);
   bool ParseGroupedBoxProperty(int32_t aVariantMask,
                                nsCSSValue& aValue);
   bool ParseDirectionalBoxProperty(nsCSSProperty aProperty,
                                      int32_t aSourceType);
   bool ParseBoxCornerRadius(const nsCSSProperty aPropID);
@@ -2970,16 +3003,25 @@ CSSParserImpl::SkipUntilOneOf(const PRUn
       }
     } else if (eCSSToken_Function == tk->mType ||
                eCSSToken_Bad_URL == tk->mType) {
       SkipUntil(')');
     }
   }
 }
 
+void
+CSSParserImpl::SkipUntilAllOf(const StopSymbolCharStack& aStopSymbolChars)
+{
+  uint32_t i = aStopSymbolChars.Length();
+  while (i--) {
+    SkipUntil(aStopSymbolChars[i]);
+  }
+}
+
 bool
 CSSParserImpl::SkipDeclaration(bool aCheckForBraces)
 {
   nsCSSToken* tk = &mToken;
   for (;;) {
     if (!GetToken(true)) {
       if (aCheckForBraces) {
         REPORT_UNEXPECTED_EOF(PESkipDeclBraceEOF);
@@ -4740,47 +4782,67 @@ CSSParserImpl::ParseDeclaration(css::Dec
     UngetToken();
     return false;
   }
 
   // Don't report property parse errors if we're inside a failing @supports
   // rule.
   nsAutoSuppressErrors suppressErrors(this, mInFailingSupportsRule);
 
-  // Map property name to its ID and then parse the property
-  nsCSSProperty propID = nsCSSProps::LookupProperty(propertyName,
-                                                    nsCSSProps::eEnabled);
-  if (eCSSProperty_UNKNOWN == propID ||
-     (aContext == eCSSContext_Page &&
-      !nsCSSProps::PropHasFlags(propID, CSS_PROPERTY_APPLIES_TO_PAGE_RULE))) { // unknown property
-    if (!NonMozillaVendorIdentifier(propertyName)) {
-      REPORT_UNEXPECTED_P(PEUnknownProperty, propertyName);
+  // Information about a parsed non-custom property.
+  nsCSSProperty propID;
+
+  // Information about a parsed custom property.
+  CSSVariableDeclarations::Type variableType;
+  nsString variableValue;
+
+  // Check if the property name is a custom property.
+  bool customProperty = nsLayoutUtils::CSSVariablesEnabled() &&
+                        nsCSSProps::IsCustomPropertyName(propertyName) &&
+                        aContext == eCSSContext_General;
+
+  if (customProperty) {
+    if (!ParseVariableDeclaration(&variableType, variableValue)) {
+      REPORT_UNEXPECTED_P(PEValueParsingError, propertyName);
       REPORT_UNEXPECTED(PEDeclDropped);
       OUTPUT_ERROR();
-    }
-
-    return false;
-  }
-  if (! ParseProperty(propID)) {
-    // XXX Much better to put stuff in the value parsers instead...
-    REPORT_UNEXPECTED_P(PEValueParsingError, propertyName);
-    REPORT_UNEXPECTED(PEDeclDropped);
-    OUTPUT_ERROR();
-    mTempData.ClearProperty(propID);
-    mTempData.AssertInitialState();
-    return false;
-  }
+      return false;
+    }
+  } else {
+    // Map property name to its ID and then parse the property
+    propID = nsCSSProps::LookupProperty(propertyName, nsCSSProps::eEnabled);
+    if (eCSSProperty_UNKNOWN == propID ||
+        (aContext == eCSSContext_Page &&
+         !nsCSSProps::PropHasFlags(propID,
+                                   CSS_PROPERTY_APPLIES_TO_PAGE_RULE))) { // unknown property
+      if (!NonMozillaVendorIdentifier(propertyName)) {
+        REPORT_UNEXPECTED_P(PEUnknownProperty, propertyName);
+        REPORT_UNEXPECTED(PEDeclDropped);
+        OUTPUT_ERROR();
+      }
+      return false;
+    }
+    if (! ParseProperty(propID)) {
+      // XXX Much better to put stuff in the value parsers instead...
+      REPORT_UNEXPECTED_P(PEValueParsingError, propertyName);
+      REPORT_UNEXPECTED(PEDeclDropped);
+      OUTPUT_ERROR();
+      mTempData.ClearProperty(propID);
+      mTempData.AssertInitialState();
+      return false;
+    }
+  }
+
   CLEAR_ERROR();
 
   // Look for "!important".
   PriorityParsingStatus status;
   if ((aFlags & eParseDeclaration_AllowImportant) != 0) {
     status = ParsePriority();
-  }
-  else {
+  } else {
     status = ePriority_None;
   }
 
   // Look for a semicolon or close brace.
   if (status != ePriority_Error) {
     if (!GetToken(true)) {
       // EOF is always ok
     } else if (mToken.IsSymbol(';')) {
@@ -4800,25 +4862,36 @@ CSSParserImpl::ParseDeclaration(css::Dec
   if (status == ePriority_Error) {
     if (checkForBraces) {
       REPORT_UNEXPECTED_TOKEN(PEBadDeclOrRuleEnd2);
     } else {
       REPORT_UNEXPECTED_TOKEN(PEBadDeclEnd);
     }
     REPORT_UNEXPECTED(PEDeclDropped);
     OUTPUT_ERROR();
-    mTempData.ClearProperty(propID);
+    if (!customProperty) {
+      mTempData.ClearProperty(propID);
+    }
     mTempData.AssertInitialState();
     return false;
   }
 
-  *aChanged |= mData.TransferFromBlock(mTempData, propID,
-                                       status == ePriority_Important,
-                                       false, aMustCallValueAppended,
-                                       aDeclaration);
+  if (customProperty) {
+    MOZ_ASSERT(Substring(propertyName,
+                         0, VAR_PREFIX_LENGTH).EqualsLiteral("var-"));
+    nsDependentString varName(propertyName, VAR_PREFIX_LENGTH); // remove 'var-'
+    aDeclaration->AddVariableDeclaration(varName, variableType, variableValue,
+                                         status == ePriority_Important);
+  } else {
+    *aChanged |= mData.TransferFromBlock(mTempData, propID,
+                                         status == ePriority_Important,
+                                         false, aMustCallValueAppended,
+                                         aDeclaration);
+  }
+
   return true;
 }
 
 static const nsCSSProperty kBorderTopIDs[] = {
   eCSSProperty_border_top_width,
   eCSSProperty_border_top_style,
   eCSSProperty_border_top_color
 };
@@ -6432,16 +6505,17 @@ static const nsCSSProperty kOutlineRadiu
 bool
 CSSParserImpl::ParseProperty(nsCSSProperty aPropID)
 {
   // Can't use AutoRestore<bool> because it's a bitfield.
   NS_ABORT_IF_FALSE(!mHashlessColorQuirk,
                     "hashless color quirk should not be set");
   NS_ABORT_IF_FALSE(!mUnitlessLengthQuirk,
                     "unitless length quirk should not be set");
+
   if (mNavQuirkMode) {
     mHashlessColorQuirk =
       nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_HASHLESS_COLOR_QUIRK);
     mUnitlessLengthQuirk =
       nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_UNITLESS_LENGTH_QUIRK);
   }
 
   NS_ASSERTION(aPropID < eCSSProperty_COUNT, "index out of range");
@@ -11214,16 +11288,238 @@ CSSParserImpl::ParseAll()
   }
 
   CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, eCSSProperty_all) {
     AppendValue(*p, value);
   }
   return true;
 }
 
+bool
+CSSParserImpl::ParseVariableDeclaration(CSSVariableDeclarations::Type* aType,
+                                        nsString& aValue)
+{
+  CSSVariableDeclarations::Type type;
+  nsString variableValue;
+  nsString closingBrackets;
+
+  // Record the token stream while parsing a variable value.
+  mScanner->StartRecording();
+  if (!ParseValueWithVariables(&type, closingBrackets)) {
+    mScanner->StopRecording();
+    return false;
+  }
+
+  if (type == CSSVariableDeclarations::eTokenStream) {
+    // This was indeed a token stream value, so store it in variableValue.
+    mScanner->StopRecording(variableValue);
+    variableValue.Append(closingBrackets);
+  } else {
+    // This was either 'inherit' or 'initial'; we don't need the recorded input.
+    mScanner->StopRecording();
+  }
+
+  if (mHavePushBack && type == CSSVariableDeclarations::eTokenStream) {
+    // If we came to the end of a valid variable declaration and a token was
+    // pushed back, then it would have been ended by '!', ')', ';', ']' or '}'.
+    // We need to remove it from the recorded variable value.
+    MOZ_ASSERT(mToken.IsSymbol('!') ||
+               mToken.IsSymbol(')') ||
+               mToken.IsSymbol(';') ||
+               mToken.IsSymbol(']') ||
+               mToken.IsSymbol('}'));
+    MOZ_ASSERT(!variableValue.IsEmpty());
+    MOZ_ASSERT(variableValue[variableValue.Length() - 1] == mToken.mSymbol);
+    variableValue.Truncate(variableValue.Length() - 1);
+  }
+
+  *aType = type;
+  aValue = variableValue;
+  return true;
+}
+
+bool
+CSSParserImpl::ParseValueWithVariables(CSSVariableDeclarations::Type* aType,
+                                       nsString& aClosingChars)
+{
+  // A property value is invalid if it contains variable references and also:
+  //
+  //   * has unbalanced parens, brackets or braces
+  //   * has any BAD_STRING or BAD_URL tokens
+  //   * has any ';' or '!' tokens at the top level of a variable reference's
+  //     fallback
+  //
+  // If the property is a custom property (i.e. a variable declaration), then
+  // it is also invalid if it consists of no tokens, such as:
+  //
+  //   var-invalid:;
+  //
+  // Note that is valid for a custom property to have a value that consists
+  // solely of white space, such as:
+  //
+  //   var-valid: ;
+
+  // Stack of closing characters for currently open constructs.
+  StopSymbolCharStack stack;
+
+  // Indexes into ')' characters in |stack| that correspond to "var(".  This
+  // is used to stop parsing when we encounter a '!' or ';' at the top level
+  // of a variable reference's fallback.
+  nsAutoTArray<uint32_t, 16> references;
+
+  if (!GetToken(false)) {
+    // Variable value was empty since we reached EOF.
+    REPORT_UNEXPECTED_EOF(PEVariableEOF);
+    return false;
+  }
+
+  if (mToken.mType == eCSSToken_Symbol &&
+      (mToken.mSymbol == '!' ||
+       mToken.mSymbol == ')' ||
+       mToken.mSymbol == ';' ||
+       mToken.mSymbol == ']' ||
+       mToken.mSymbol == '}')) {
+    // Variable value was empty since we reached the end of the construct.
+    UngetToken();
+    REPORT_UNEXPECTED_TOKEN(PEVariableEmpty);
+    return false;
+  }
+
+  if (mToken.mType == eCSSToken_Whitespace) {
+    if (!GetToken(true)) {
+      // Variable value was white space only.  This is valid.
+      *aType = CSSVariableDeclarations::eTokenStream;
+      return true;
+    }
+  }
+
+  // Look for 'initial' or 'inherit' as the first non-white space token.
+  CSSVariableDeclarations::Type type = CSSVariableDeclarations::eTokenStream;
+  if (mToken.mType == eCSSToken_Ident) {
+    if (mToken.mIdent.LowerCaseEqualsLiteral("initial")) {
+      type = CSSVariableDeclarations::eInitial;
+    } else if (mToken.mIdent.LowerCaseEqualsLiteral("inherit")) {
+      type = CSSVariableDeclarations::eInherit;
+    }
+  }
+
+  if (type != CSSVariableDeclarations::eTokenStream) {
+    if (!GetToken(true)) {
+      // Variable value was 'initial' or 'inherit' followed by EOF.
+      *aType = type;
+      return true;
+    }
+    UngetToken();
+    if (mToken.mType == eCSSToken_Symbol &&
+        (mToken.mSymbol == '!' ||
+         mToken.mSymbol == ')' ||
+         mToken.mSymbol == ';' ||
+         mToken.mSymbol == ']' ||
+         mToken.mSymbol == '}')) {
+      // Variable value was 'initial' or 'inherit' followed by the end
+      // of the declaration.
+      *aType = type;
+      return true;
+    }
+  }
+
+  do {
+    switch (mToken.mType) {
+      case eCSSToken_Symbol:
+        if (mToken.mSymbol == '(') {
+          stack.AppendElement(')');
+        } else if (mToken.mSymbol == '[') {
+          stack.AppendElement(']');
+        } else if (mToken.mSymbol == '{') {
+          stack.AppendElement('}');
+        } else if (mToken.mSymbol == ';' ||
+                   mToken.mSymbol == '!') {
+          if (stack.IsEmpty()) {
+            UngetToken();
+            *aType = CSSVariableDeclarations::eTokenStream;
+            return true;
+          } else if (!references.IsEmpty() &&
+                     references.LastElement() == stack.Length() - 1) {
+            SkipUntilAllOf(stack);
+            return false;
+          }
+        } else if (mToken.mSymbol == ')' ||
+                   mToken.mSymbol == ']' ||
+                   mToken.mSymbol == '}') {
+          for (;;) {
+            if (stack.IsEmpty()) {
+              UngetToken();
+              *aType = CSSVariableDeclarations::eTokenStream;
+              return true;
+            }
+            PRUnichar c = stack.LastElement();
+            stack.TruncateLength(stack.Length() - 1);
+            if (!references.IsEmpty() &&
+                references.LastElement() == stack.Length()) {
+              references.TruncateLength(references.Length() - 1);
+            }
+            if (mToken.mSymbol == c) {
+              break;
+            }
+          }
+        }
+        break;
+
+      case eCSSToken_Function:
+        if (mToken.mIdent.LowerCaseEqualsLiteral("var")) {
+          if (GetToken(true)) {
+            if (mToken.mType != eCSSToken_Ident) {
+              UngetToken();
+              SkipUntil(')');
+              SkipUntilAllOf(stack);
+              return false;
+            }
+          }
+          if (ExpectSymbol(',', true)) {
+            if (ExpectSymbol(')', false)) {
+              // Comma must be followed by at least one fallback token.
+              SkipUntilAllOf(stack);
+              return false;
+            }
+            references.AppendElement(stack.Length());
+            stack.AppendElement(')');
+          } else if (!ExpectSymbol(')', true)) {
+            UngetToken();
+            SkipUntilAllOf(stack);
+            return false;
+          }
+        } else {
+          stack.AppendElement(')');
+        }
+        break;
+
+      case eCSSToken_Bad_String:
+        SkipUntilAllOf(stack);
+        return false;
+
+      case eCSSToken_Bad_URL:
+        SkipUntil(')');
+        SkipUntilAllOf(stack);
+        return false;
+
+      default:
+        break;
+    }
+  } while (GetToken(true));
+
+  // Append any implied closing characters.
+  uint32_t i = stack.Length();
+  while (i--) {
+    aClosingChars.Append(stack[i]);
+  }
+
+  *aType = type;
+  return true;
+}
+
 } // anonymous namespace
 
 // Recycling of parser implementation objects
 
 static CSSParserImpl* gFreeList = nullptr;
 
 nsCSSParser::nsCSSParser(mozilla::css::Loader* aLoader,
                          nsCSSStyleSheet* aSheet)
--- a/layout/style/nsCSSProperty.h
+++ b/layout/style/nsCSSProperty.h
@@ -51,17 +51,20 @@ enum nsCSSProperty {
 
   // Extra values for use in the values of the 'transition-property'
   // property.
   eCSSPropertyExtra_no_properties,
   eCSSPropertyExtra_all_properties,
 
   // Extra dummy values for nsCSSParser internal use.
   eCSSPropertyExtra_x_none_value,
-  eCSSPropertyExtra_x_auto_value
+  eCSSPropertyExtra_x_auto_value,
+
+  // Extra value to represent custom properties (var-*).
+  eCSSPropertyExtra_variable
 };
 
 // The "descriptors" that can appear in a @font-face rule.
 // They have the syntax of properties but different value rules.
 enum nsCSSFontDesc {
   eCSSFontDesc_UNKNOWN = -1,
 #define CSS_FONT_DESC(name_, method_) eCSSFontDesc_##method_,
 #include "nsCSSFontDescList.h"
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -7,27 +7,25 @@
  * methods for dealing with CSS properties and tables of the keyword
  * values they accept
  */
 
 #include "mozilla/ArrayUtils.h"
 
 #include "nsCSSProps.h"
 #include "nsCSSKeywords.h"
+#include "nsLayoutUtils.h"
 #include "nsStyleConsts.h"
 #include "nsIWidget.h"
 #include "nsThemeConstants.h"  // For system widget appearance types
 
 #include "mozilla/LookAndFeel.h" // for system colors
 
 #include "nsString.h"
 #include "nsStaticNameTable.h"
-#include "nsStyleConsts.h"
-#include "gfxFontConstants.h"
-#include "nsStyleStruct.h"
 
 #include "mozilla/Preferences.h"
 
 using namespace mozilla;
 
 // required to make the symbol external, so that TestCSSPropertyLookup.cpp can link with it
 extern const char* const kCSSRawProperties[];
 
@@ -343,22 +341,45 @@ nsCSSProps::ReleaseTable(void)
     delete gFontDescTable;
     gFontDescTable = nullptr;
 
     delete [] gShorthandsContainingPool;
     gShorthandsContainingPool = nullptr;
   }
 }
 
+// Length of the "var-" custom property name prefix.
+#define VAR_PREFIX_LENGTH 4
+
+/* static */ bool
+nsCSSProps::IsCustomPropertyName(const nsACString& aProperty)
+{
+  // Custom properties must have at least one character after the "var-" prefix.
+  return aProperty.Length() >= (VAR_PREFIX_LENGTH + 1) &&
+         StringBeginsWith(aProperty, NS_LITERAL_CSTRING("var-"));
+}
+
+/* static */ bool
+nsCSSProps::IsCustomPropertyName(const nsAString& aProperty)
+{
+  return aProperty.Length() >= (VAR_PREFIX_LENGTH + 1) &&
+         StringBeginsWith(aProperty, NS_LITERAL_STRING("var-"));
+}
+
 nsCSSProperty
 nsCSSProps::LookupProperty(const nsACString& aProperty,
                            EnabledState aEnabled)
 {
   NS_ABORT_IF_FALSE(gPropertyTable, "no lookup table, needs addref");
 
+  if (nsLayoutUtils::CSSVariablesEnabled() &&
+      IsCustomPropertyName(aProperty)) {
+    return eCSSPropertyExtra_variable;
+  }
+
   nsCSSProperty res = nsCSSProperty(gPropertyTable->Lookup(aProperty));
   // Check eCSSAliasCount against 0 to make it easy for the
   // compiler to optimize away the 0-aliases case.
   if (eCSSAliasCount != 0 && res >= eCSSProperty_COUNT) {
     static_assert(eCSSProperty_UNKNOWN < eCSSProperty_COUNT,
                   "assuming eCSSProperty_UNKNOWN doesn't hit this code");
     if (IsEnabled(res) || aEnabled == eAny) {
       res = gAliases[res - eCSSProperty_COUNT];
@@ -372,16 +393,21 @@ nsCSSProps::LookupProperty(const nsACStr
     res = eCSSProperty_UNKNOWN;
   }
   return res;
 }
 
 nsCSSProperty
 nsCSSProps::LookupProperty(const nsAString& aProperty, EnabledState aEnabled)
 {
+  if (nsLayoutUtils::CSSVariablesEnabled() &&
+      IsCustomPropertyName(aProperty)) {
+    return eCSSPropertyExtra_variable;
+  }
+
   // This is faster than converting and calling
   // LookupProperty(nsACString&).  The table will do its own
   // converting and avoid a PromiseFlatCString() call.
   NS_ABORT_IF_FALSE(gPropertyTable, "no lookup table, needs addref");
   nsCSSProperty res = nsCSSProperty(gPropertyTable->Lookup(aProperty));
   // Check eCSSAliasCount against 0 to make it easy for the
   // compiler to optimize away the 0-aliases case.
   if (eCSSAliasCount != 0 && res >= eCSSProperty_COUNT) {
--- a/layout/style/nsCSSProps.h
+++ b/layout/style/nsCSSProps.h
@@ -246,20 +246,28 @@ public:
   static void AddRefTable(void);
   static void ReleaseTable(void);
 
   // Given a property string, return the enum value
   enum EnabledState {
     eEnabled,
     eAny
   };
+  // Looks up the property with name aProperty and returns its corresponding
+  // nsCSSProperty value.  If aProperty is the name of a custom property,
+  // then eCSSPropertyExtra_variable will be returned.
   static nsCSSProperty LookupProperty(const nsAString& aProperty,
                                       EnabledState aEnabled);
   static nsCSSProperty LookupProperty(const nsACString& aProperty,
                                       EnabledState aEnabled);
+  // Returns whether aProperty is a custom property name, i.e. begins with
+  // "var-" and has at least one more character.  This assumes that
+  // the CSS Variables pref has been enabled.
+  static bool IsCustomPropertyName(const nsAString& aProperty);
+  static bool IsCustomPropertyName(const nsACString& aProperty);
 
   static inline bool IsShorthand(nsCSSProperty aProperty) {
     NS_ABORT_IF_FALSE(0 <= aProperty && aProperty < eCSSProperty_COUNT,
                  "out of range");
     return (aProperty >= eCSSProperty_COUNT_no_shorthands);
   }
 
   // Same but for @font-face descriptors