Bug 1273706 - Part 20: Store important and unimportant declarations in one object. r?heycam draft
authorJonathan Chan <jyc@eqv.io>
Thu, 18 Aug 2016 15:30:37 -0700
changeset 402909 7021516f19dc09bc461e0b58d6adc16dbdd44319
parent 402908 a1674b9b7b4c3444b01cc8d28de1caae1c16d712
child 402910 e28f8616e6f6ea0e1ce6a0585c9758afcc498084
push id26775
push userjchan@mozilla.com
push dateThu, 18 Aug 2016 22:38:41 +0000
reviewersheycam
bugs1273706
milestone51.0a1
Bug 1273706 - Part 20: Store important and unimportant declarations in one object. r?heycam Inherited from heycam's patches. This means we have to store more information for variables (in particular, if the variable is important). We now also store the CSSVariableExprContext, which I added in the previous patch, on variable declarations, although in this patch we leave it empty (it's used by a future patch in the series). The eTokenStream, etc. enum values are changed to an enum class, which necessitates some renaming in nsCSSParser. Most importantly, we store invalid variable declarations on a |Declaration|'s |CSSVariableDeclaration|. This allows us to map the declaration that becomes correct after registering properties into the rule data without reparsing everything. We lazily determine which is the correct declaration when someone asks us for information on declarations (e.g. which declaration is at some index in the list of declarations, or when someone asks us to |MapRuleInfoInto|). Because changing registrations might cause which declaration is correct to change, we keep track of which version of the |CSSVariableRegistrations| we last computed the 'used declaration' based on (the 'used declaration' is the most recent declaration with the correct type, considering importance and overriding importance). This necessitates a couple changes: * The constructor for |Declaration| now takes a |RefPtr| to a |CSSVariableRegistrations|, which is used for computing the used declaration. * |Declaration| presents an 'exposed order' that is computed from the actual order. Suppose |--a| is registered with syntax <number>. Then in the declaration: { --a: red; color: black; --a: 50px; display: none; --a: 4.7; } ... the internal order needs to keep track of the positions of *all* of the declarations of |--a|, so that if the registration for |--a| is removed and replaced with a new registration, indexed getters can return |--a| at the correct position, which is expressed in the exposed order. The 'exposed order' system on |Declaration|s comes from heycam's patches. Sorry this is such a long patch. I couldn't find a good way to split it up. MozReview-Commit-ID: 4biir7MJU6h
dom/svg/nsSVGElement.cpp
layout/style/CSSVariableDeclarations.cpp
layout/style/CSSVariableDeclarations.h
layout/style/Declaration.cpp
layout/style/Declaration.h
layout/style/FontFaceSet.cpp
layout/style/StyleAnimationValue.cpp
layout/style/nsCSSParser.cpp
layout/style/nsDOMCSSAttrDeclaration.cpp
layout/style/nsDOMCSSDeclaration.cpp
layout/style/nsDOMCSSDeclaration.h
--- a/dom/svg/nsSVGElement.cpp
+++ b/dom/svg/nsSVGElement.cpp
@@ -51,16 +51,17 @@
 #include "nsSMILMappedAttribute.h"
 #include "SVGMotionSMILAttr.h"
 #include "nsAttrValueOrString.h"
 #include "nsSMILAnimationController.h"
 #include "mozilla/dom/SVGElementBinding.h"
 #include "mozilla/unused.h"
 #include "mozilla/RestyleManagerHandle.h"
 #include "mozilla/RestyleManagerHandleInlines.h"
+#include "mozilla/CSSVariableRegistration.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 // This is needed to ensure correct handling of calls to the
 // vararg-list methods in this file:
 //   nsSVGElement::GetAnimated{Length,Number,Integer}Values
 // See bug 547964 for details:
@@ -1190,17 +1191,18 @@ MappedAttrParser::~MappedAttrParser()
              "into a style rule (and had its pointer cleared)");
 }
 
 void
 MappedAttrParser::ParseMappedAttrValue(nsIAtom* aMappedAttrName,
                                        const nsAString& aMappedAttrValue)
 {
   if (!mDecl) {
-    mDecl = new css::Declaration();
+    nsIDocument* doc = mElement->OwnerDoc();
+    mDecl = new css::Declaration(CSSVariableRegistrationsOfDocument(doc));
     mDecl->InitializeEmpty();
   }
 
   // Get the nsCSSPropertyID ID for our mapped attribute.
   nsCSSPropertyID propertyID =
     nsCSSProps::LookupProperty(nsDependentAtomString(aMappedAttrName),
                                CSSEnabledState::eForAllContent);
   if (propertyID != eCSSProperty_UNKNOWN) {
--- a/layout/style/CSSVariableDeclarations.cpp
+++ b/layout/style/CSSVariableDeclarations.cpp
@@ -3,193 +3,444 @@
 /* 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 */
 
 #include "CSSVariableDeclarations.h"
 
-#include "CSSVariableResolver.h"
-#include "nsCSSScanner.h"
+#include "mozilla/CSSVariableRegistration.h"
+#include "mozilla/CSSVariableResolver.h"
+#include "nsCSSParser.h"
 #include "nsRuleData.h"
 
-// These three special string values are used to represent specified values of
-// 'initial', 'inherit' and 'unset'.  (Note that none of these are valid
-// variable values.)
-#define INITIAL_VALUE "!"
-#define INHERIT_VALUE ";"
-#define UNSET_VALUE   ")"
+#ifdef CSSVAR_DEBUG
+#define CSSVAR_PRINTF(...) fprintf(stderr, __VA_ARGS__)
+#else
+#define CSSVAR_PRINTF(...)
+#endif
 
 namespace mozilla {
 
-CSSVariableDeclarations::CSSVariableDeclarations()
+CSSVariableDeclarations::CSSVariableDeclarations(
+    const CSSVariableRegistrations* aRegistrations)
+  : mRegistrations(aRegistrations)
+  , mTotalGeneration(-1)
+  , mHasImportant(false)
+  , mImmutable(false)
+  , mNeverComputed(true)
 {
   MOZ_COUNT_CTOR(CSSVariableDeclarations);
 }
 
 CSSVariableDeclarations::CSSVariableDeclarations(const CSSVariableDeclarations& aOther)
+  : mRegistrations(aOther.mRegistrations)
+  , mDeclarations(aOther.mDeclarations)
+  , mUsedDeclarations(aOther.mUsedDeclarations)
+  , mVariableGenerations(aOther.mVariableGenerations)
+  , mTotalGeneration(aOther.mTotalGeneration)
+  , mVariableNames(aOther.mVariableNames)
+  , mHasImportant(aOther.mHasImportant)
+  , mImmutable(false)
+  , mNeverComputed(true)
 {
   MOZ_COUNT_CTOR(CSSVariableDeclarations);
-  CopyVariablesFrom(aOther);
+  mVariableIDs.Clear();
+  for (auto iter = aOther.mVariableIDs.ConstIter(); !iter.Done(); iter.Next()) {
+    mVariableIDs.Put(iter.Key(), iter.UserData());
+  }
 }
 
 #ifdef DEBUG
 CSSVariableDeclarations::~CSSVariableDeclarations()
 {
   MOZ_COUNT_DTOR(CSSVariableDeclarations);
 }
 #endif
 
 CSSVariableDeclarations&
 CSSVariableDeclarations::operator=(const CSSVariableDeclarations& aOther)
 {
   if (this == &aOther) {
     return *this;
   }
 
-  mVariables.Clear();
-  CopyVariablesFrom(aOther);
+  mRegistrations = aOther.mRegistrations;
+  mDeclarations = aOther.mDeclarations;
+  mUsedDeclarations = aOther.mUsedDeclarations;
+  mVariableGenerations = aOther.mVariableGenerations;
+  mTotalGeneration = aOther.mTotalGeneration;
+  mVariableNames = aOther.mVariableNames;
+
+  mVariableIDs.Clear();
+  for (auto iter = aOther.mVariableIDs.ConstIter(); !iter.Done(); iter.Next()) {
+    mVariableIDs.Put(iter.Key(), iter.UserData());
+  }
+
+  mHasImportant = aOther.mHasImportant;
+  mImmutable = false;
+  mNeverComputed = true;
   return *this;
 }
 
-void
-CSSVariableDeclarations::CopyVariablesFrom(const CSSVariableDeclarations& aOther)
-{
-  for (auto iter = aOther.mVariables.ConstIter(); !iter.Done(); iter.Next()) {
-    mVariables.Put(iter.Key(), iter.UserData());
-  }
-}
 
 bool
 CSSVariableDeclarations::Has(const nsAString& aName) const
 {
-  nsString value;
-  return mVariables.Get(aName, &value);
+  size_t id;
+  if (!mVariableIDs.Get(aName, &id)) {
+    return false;
+  }
+  RecomputeSingleValidity(aName);
+  return mUsedDeclarations[id] != (size_t) -1;
+}
+
+bool
+CSSVariableDeclarations::Get(const nsAString& aName, Type& aType,
+                             nsAString& aExpr, bool& aImportant,
+                             CSSVariableExprContext& aContext) const
+{
+  RecomputeSingleValidity(aName);
+  size_t id;
+  if (!mVariableIDs.Get(aName, &id) || mUsedDeclarations[id] == (size_t) -1) {
+    return false;
+  }
+  const Declaration& decl = mDeclarations[mUsedDeclarations[id]];
+  aType = decl.mType;
+  aExpr = decl.mExpr;
+  aImportant = decl.mImportant;
+  aContext = decl.mContext;
+  return true;
 }
 
 bool
-CSSVariableDeclarations::Get(const nsAString& aName,
-                             Type& aType,
-                             nsString& aTokenStream) const
+CSSVariableDeclarations::IsUsedDecl(size_t aID) 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 if (value.EqualsLiteral(UNSET_VALUE)) {
-    aType = eUnset;
-    aTokenStream.Truncate();
-  } else {
-    aType = eTokenStream;
-    aTokenStream = value;
-  }
-  return true;
+  RecomputeValidities();
+  const Declaration& decl = mDeclarations[aID];
+  return aID == mUsedDeclarations[decl.mVarID];
 }
 
 void
-CSSVariableDeclarations::PutTokenStream(const nsAString& aName,
-                                        const nsString& aTokenStream)
+CSSVariableDeclarations::GetDeclName(size_t aID, nsAString& aName) const
 {
-  MOZ_ASSERT(!aTokenStream.EqualsLiteral(INITIAL_VALUE) &&
-             !aTokenStream.EqualsLiteral(INHERIT_VALUE) &&
-             !aTokenStream.EqualsLiteral(UNSET_VALUE));
-  mVariables.Put(aName, aTokenStream);
+  // Don't need to recompute validities because all of the declarations for a
+  // given variable are, of course, for that variable.
+  const Declaration& decl = mDeclarations[aID];
+  aName = mVariableNames[decl.mVarID];
 }
 
-void
-CSSVariableDeclarations::PutInitial(const nsAString& aName)
+static bool
+ComputeValidity(const CSSVariableRegistrations::Data& aRegs,
+                const nsAString& aName, CSSVariableDeclarations::Type aType,
+                const nsAString& aExpr, nsCSSValue& aValue,
+                CSSValueType& aValueType, const CSSVariableExprContext& aContext,
+                nsCSSTokenSerializationType& aFirstToken,
+                nsCSSTokenSerializationType& aLastToken, bool& aParsed)
 {
-  mVariables.Put(aName, NS_LITERAL_STRING(INITIAL_VALUE));
+  nsCSSParser parser;
+  CSSVariableRegistration* reg;
+  // So far, we've parsed the name and the value.
+  // Then if this custom property isn't registered or the value is one one of the
+  // CSS-wide keywords, there's no concept of an 'illegal' value that we
+  // ignore, so the last declaration wins.
+  if (aRegs.Get(aName, &reg) &&
+      !(aType == CSSVariableDeclarations::Type::Initial ||
+        aType == CSSVariableDeclarations::Type::Unset ||
+        aType == CSSVariableDeclarations::Type::Inherit)) {
+    // If the custom property is registered and has a value, we need to type
+    // it against the registered syntax. NB: if the value (really an
+    // expression) contains a variable reference:
+    //   If a property contains one or more var() functions, and those functions
+    //   are syntactically valid, the entire property’s grammar must be assumed
+    //   to be valid at parse time. It is only syntax-checked at computed-value
+    //   time, after var() functions have been substituted.
+    //   -- CSS Variables
+    bool hasVariables = false;
+    bool parsed = parser.EnumerateVariableReferences(
+                    aExpr,
+                    [](const nsAString& aName, void* aData) {
+                      *static_cast<bool*>(aData) = true;
+                    },
+                    &hasVariables);
+
+    if (!parsed) {
+      // If EnumerateVariableReferences couldn't parse it, it's definitely
+      // invalid.
+      // TODO(jyc) Should we even keep it around at this point? Actually, can
+      // this even happen? I think a parse error would be caught earlier.
+      return false;
+    } else if (hasVariables) {
+      // If it has variables, we have to just add it anyways.
+      return true;
+    } else {
+      if (!parser.ParseTypedValue(reg->mSyntax, aExpr, aContext.mSheetURI,
+                                  aContext.mBaseURI, aContext.mSheetPrincipal,
+                                  aValue, aValueType)) {
+        return false;
+      }
+      // We use ResolveVariableValue only to find the first and last token here.
+      // In the future, ResolveVariableValue could be refactored so that we can
+      // separate the code for conversion between token types and the code for
+      // variable expansion.
+      // It might seem excessive parsing three times here. But unless
+      // nsCSSParser is restructured, we certainly need the first two, and
+      // using three here prevents us from using five total for properly typed
+      // declarations (2 to enumerate then parse here and 3 in
+      // CSSVariableResolver to enumerate, resolve, then parse).
+      nsAutoString result;
+      DebugOnly<bool> ok = 
+        parser.ResolveVariableValue(aExpr, nullptr, result, aFirstToken,
+                                    aLastToken);
+      MOZ_ASSERT(ok);
+      aParsed = true;
+      return true;
+    }
+  }
+  // So now, we must be in at least one of the following cases:
+  // 1) aName is not a registered property
+  // 2) aType is initial, unset, or inherit
+  return true;
 }
 
-void
-CSSVariableDeclarations::PutInherit(const nsAString& aName)
+size_t
+CSSVariableDeclarations::Add(const nsAString& aName, Type aType,
+                             const nsAString& aExpr, bool aImportant,
+                             bool aOverrideImportant,
+                             CSSVariableExprContext aContext)
 {
-  mVariables.Put(aName, NS_LITERAL_STRING(INHERIT_VALUE));
-}
+  size_t varID;
+  if (!mVariableIDs.Get(aName, &varID)) {
+    // Don't use |mVariableIDs|, as variables might have been removed.
+    // |mVariableNames|, on the other hand, is only appended to.
+    varID = mVariableNames.Length();
+    mVariableIDs.Put(aName, varID);
+    mUsedDeclarations.AppendElement(-1);
+    if (mRegistrations) {
+      mVariableGenerations.AppendElement(mRegistrations->mGeneration);
+    }
+    mVariableNames.AppendElement(aName);
+  }
 
-void
-CSSVariableDeclarations::PutUnset(const nsAString& aName)
-{
-  mVariables.Put(aName, NS_LITERAL_STRING(UNSET_VALUE));
+  size_t declID = mDeclarations.Length();
+  mDeclarations.AppendElement(Declaration(varID, aType, aExpr, aImportant,
+                                          aOverrideImportant, aContext));
+  if (!mRegistrations) {
+    if (DeclPriorityGte(declID, mUsedDeclarations[varID])) {
+      mUsedDeclarations[varID] = declID;
+      CSSVAR_PRINTF("mUsedDeclarations[%zu] = %zu\n", varID, declID);
+      mHasImportant = mHasImportant || aImportant;
+    }
+  } else {
+    CSSVAR_PRINTF("Triggering a recompute.\n");
+    // Trigger a recompute.
+    mTotalGeneration = -1;
+    mVariableGenerations[varID] = -1;
+    CSSVAR_PRINTF("mUsedDeclarations[%zu] = %zu\n", varID, declID);
+  }
+  return declID;
 }
 
 void
 CSSVariableDeclarations::Remove(const nsAString& aName)
 {
-  mVariables.Remove(aName);
+  mVariableIDs.Remove(aName);
+  // TODO(jyc) We don't clean up the garbage declarations. Should we? Doesn't
+  // seem like removing properties completely is a very common thing.
+  // We also don't remove mHasImportant, but we didn't remove it before either.
 }
 
 void
-CSSVariableDeclarations::MapRuleInfoInto(nsRuleData* aRuleData)
+CSSVariableDeclarations::MapRuleInfoInto(nsRuleData* aRuleData, bool aImportant) const
 {
   if (!(aRuleData->mSIDs & NS_STYLE_INHERIT_BIT(Variables))) {
     return;
   }
+  RecomputeValidities();
+
+  CSSVAR_PRINTF("CSSVariableDeclarations::MapRuleInfoInto\n");
 
   if (!aRuleData->mVariables) {
-    aRuleData->mVariables = new CSSVariableDeclarations(*this);
-  } else {
-    for (auto iter = mVariables.Iter(); !iter.Done(); iter.Next()) {
-      nsDataHashtable<nsStringHashKey, nsString>& variables =
-        aRuleData->mVariables->mVariables;
-      const nsAString& aName = iter.Key();
-      if (!variables.Contains(aName)) {
-        variables.Put(aName, iter.UserData());
-      }
+    aRuleData->mVariables = new CSSVariableDeclarations(nullptr);
+  }
+
+  for (auto iter = mVariableIDs.ConstIter(); !iter.Done(); iter.Next()) {
+    const nsAString& name = iter.Key();
+    size_t varID = iter.UserData();
+    CSSVAR_PRINTF("Considering mapping variable %s.\n", NS_ConvertUTF16toUTF8(name).get());
+    if (aRuleData->mVariables->Has(name)) {
+      // MapRuleInfoInto is called on all the applicable rules (ours is a parent
+      // Declaration) in order of *decreasing* specificity.
+      // If there's already an entry, it was put by a more specific applicable
+      // rule.
+      CSSVAR_PRINTF("We already have a variable with that name. Skipping...\n");
+      continue;
     }
+    size_t declID = mUsedDeclarations[varID];
+    if (declID == (size_t) -1) {
+      CSSVAR_PRINTF("No usable declarations. Skipping.\n");
+      continue;
+    }
+    const Declaration& decl = mDeclarations[declID];
+    if (decl.mImportant != aImportant) {
+      CSSVAR_PRINTF("Importance doesn't match. Skipping...\n");
+      continue;
+    }
+    CSSVAR_PRINTF("Adding!\n");
+    aRuleData->mVariables->Add(name, decl.mType, decl.mExpr, decl.mImportant,
+                               decl.mOverrideImportant, decl.mContext);
   }
 }
 
 void
-CSSVariableDeclarations::AddVariablesToResolver(
-                                           CSSVariableResolver* aResolver) const
+CSSVariableDeclarations::AddVariablesToResolver(CSSVariableResolver* aResolver) const
 {
-  for (auto iter = mVariables.ConstIter(); !iter.Done(); iter.Next()) {
+  CSSVAR_PRINTF("CSSVariableDeclarations::AddVariablesToResolver\n");
+  RecomputeValidities();
+  for (auto iter = mVariableIDs.ConstIter(); !iter.Done(); iter.Next()) {
     const nsAString& name = iter.Key();
-    nsString value = iter.UserData();
-    if (value.EqualsLiteral(INITIAL_VALUE)) {
+    CSSVAR_PRINTF("Considering adding variable %s.\n", NS_ConvertUTF16toUTF8(name).get());
+    size_t varID = iter.UserData();
+    size_t declID = mUsedDeclarations[varID];
+    if (declID == (size_t) -1) {
+      CSSVAR_PRINTF("Not adding because no usable declaration.\n");
+      continue;
+    }
+    const Declaration& decl = mDeclarations[declID];
+    switch (decl.mType) {
+    case Type::Initial:
       // Values of 'initial' are treated the same as an invalid value in the
       // variable resolver.
       aResolver->Put(name, EmptyString(),
                      eCSSTokenSerialization_Nothing,
                      eCSSTokenSerialization_Nothing,
                      false);
-    } else if (value.EqualsLiteral(INHERIT_VALUE) ||
-               value.EqualsLiteral(UNSET_VALUE)) {
-      // Values of 'inherit' and 'unset' don't need any handling, since it means
-      // we just need to keep whatever value is currently in the resolver.  This
-      // is because the specified variable declarations already have only the
-      // winning declaration for the variable and no longer have any of the
-      // others.
-    } else {
+      break;
+    case Type::Inherit:
+      break;
+    case Type::Unset:
+      break;
+    case Type::TokenStream:
       // At this point, we don't know what token types are at the start and end
       // of the specified variable value.  These will be determined later during
       // the resolving process.
-      aResolver->Put(name, value,
+      aResolver->Put(name, decl.mExpr,
                      eCSSTokenSerialization_Nothing,
                      eCSSTokenSerialization_Nothing,
                      false);
+      break;
     }
   }
 }
 
+CSSVariableDeclarations::Iterator
+CSSVariableDeclarations::Iter() const
+{
+  return mVariableIDs.ConstIter();
+}
+
 size_t
-CSSVariableDeclarations::SizeOfIncludingThis(
-                                      mozilla::MallocSizeOf aMallocSizeOf) const
+CSSVariableDeclarations::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
+  // TODO(jyc) verify
   size_t n = aMallocSizeOf(this);
-  n += mVariables.ShallowSizeOfExcludingThis(aMallocSizeOf);
-  for (auto iter = mVariables.ConstIter(); !iter.Done(); iter.Next()) {
+  n += mDeclarations.ShallowSizeOfExcludingThis(aMallocSizeOf);
+  for (const auto& decl : mDeclarations) {
+    n += aMallocSizeOf(&decl);
+    n += decl.mExpr.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+  }
+  n += mUsedDeclarations.ShallowSizeOfExcludingThis(aMallocSizeOf);
+  n += sizeof(size_t) * mUsedDeclarations.Length();
+  n += mVariableGenerations.ShallowSizeOfExcludingThis(aMallocSizeOf);
+  n += sizeof(uint32_t) * mVariableGenerations.Length();
+  for (auto iter = mVariableIDs.ConstIter(); !iter.Done(); iter.Next()) {
     n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
-    n += iter.Data().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+    n += sizeof(size_t);
+  }
+  if (mRegistrations) {
+    n += aMallocSizeOf(mRegistrations);
   }
   return n;
 }
 
+bool
+CSSVariableDeclarations::DeclPriorityGte(size_t aFirst, size_t aSecond) const
+{
+  if (aSecond == (size_t) -1) {
+    return true;
+  }
+  const Declaration& first = mDeclarations[aFirst];
+  const Declaration& second = mDeclarations[aSecond];
+  int nFirst = 0;
+  if (first.mImportant) nFirst += 1;
+  if (first.mOverrideImportant) nFirst += 2;
+  int nSecond = 0;
+  if (second.mImportant) nSecond += 1;
+  if (second.mOverrideImportant) nSecond += 2;
+
+  // I = important
+  // O = override important
+  // IO = important + override important
+  // down = first, across = second
+  //     _  I  O IO
+  //  _  ≥  <  <  <
+  //  I  ≥  ≥  <  <
+  //  O  ≥  ≥  ≥  <
+  // IO  ≥  ≥  ≥  ≥
+
+  return nFirst >= nSecond;
+}
+
+void
+CSSVariableDeclarations::RecomputeSingleValidity(const nsAString& aName) const
+{
+  if (!mRegistrations || (mImmutable && !mNeverComputed)) {
+    return;
+  }
+  mNeverComputed = false;
+  size_t varID = mVariableIDs.Get(aName);
+  if (mVariableGenerations[varID] == mRegistrations->mGeneration) {
+    return;
+  }
+  for (size_t declID = 0; declID < mDeclarations.Length(); declID++) {
+    const Declaration& decl = mDeclarations[declID];
+    if (decl.mVarID != varID) {
+      continue;
+    }
+    bool valid = ComputeValidity(mRegistrations->mData, aName, decl.mType,
+                                 decl.mExpr, decl.mValue, decl.mValueType,
+                                 decl.mContext, decl.mFirstToken,
+                                 decl.mLastToken, decl.mParsed);
+    if (valid && DeclPriorityGte(declID, mUsedDeclarations[varID])) {
+      mUsedDeclarations[varID] = declID;
+      mHasImportant = mHasImportant || decl.mImportant;
+    }
+  }
+  mVariableGenerations[varID] = mRegistrations->mGeneration;
+}
+
+void
+CSSVariableDeclarations::RecomputeValidities() const
+{
+  if (!mRegistrations || mTotalGeneration == mRegistrations->mGeneration ||
+      (mImmutable && !mNeverComputed)) {
+    return;
+  }
+  mNeverComputed = false;
+  for (size_t declID = 0; declID < mDeclarations.Length(); declID++) {
+    const Declaration& decl = mDeclarations[declID];
+    size_t varID = decl.mVarID;
+    bool valid = ComputeValidity(mRegistrations->mData, mVariableNames[varID],
+                                 decl.mType, decl.mExpr, decl.mValue,
+                                 decl.mValueType, decl.mContext,
+                                 decl.mFirstToken, decl.mLastToken,
+                                 decl.mParsed);
+    CSSVAR_PRINTF("declID=%zu, varID=%zu, valid=%d\n", declID, varID, valid);
+    if (valid && DeclPriorityGte(declID, mUsedDeclarations[varID])) {
+      mUsedDeclarations[varID] = declID;
+      mHasImportant = mHasImportant || decl.mImportant;
+    }
+  }
+  mTotalGeneration = mRegistrations->mGeneration;
+}
+
 } // namespace mozilla
--- a/layout/style/CSSVariableDeclarations.h
+++ b/layout/style/CSSVariableDeclarations.h
@@ -3,29 +3,35 @@
  * 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 "mozilla/CSSVariableRegistrations.h"
+#include "mozilla/CSSVariableSyntax.h"
+
+#include "nsCSSScanner.h"
+#include "nsCSSValue.h"
 #include "nsDataHashtable.h"
 
 namespace mozilla {
 class CSSVariableResolver;
 } // namespace mozilla
 struct nsRuleData;
 
 namespace mozilla {
 
 class CSSVariableDeclarations
 {
 public:
-  CSSVariableDeclarations();
+  explicit CSSVariableDeclarations(const CSSVariableRegistrations*
+                                   aRegistrations);
   CSSVariableDeclarations(const CSSVariableDeclarations& aOther);
 #ifdef DEBUG
   ~CSSVariableDeclarations();
 #endif
   CSSVariableDeclarations& operator=(const CSSVariableDeclarations& aOther);
 
   /**
    * Returns whether this set of variable declarations includes a variable
@@ -34,106 +40,148 @@ public:
    * @param aName The variable name (not including any "--" 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'
-    eUnset         // 'unset'
+  enum class Type : uint8_t {
+    Initial,      // 'initial'
+    Inherit,      // 'inherit'
+    Unset,        // 'unset'
+    TokenStream,  // a stream of CSS tokens (the default type for variables)
   };
 
-  /**
-   * Gets the value of a variable in this set of variable declarations.
-   *
-   * @param aName The variable name (not including any "--" 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', 'inherit' or 'unset', 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 "--" prefix that would
-   *   be part of the custom property name) whose value is to be set.
-   */
-  void PutInitial(const nsAString& aName);
+  bool Get(const nsAString& aName, Type& aType, nsAString& aExpr,
+           bool& aImportant, CSSVariableExprContext& aContext) const;
 
-  /**
-   * 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 "--" prefix that would
-   *   be part of the custom property name) whose value is to be set.
-   */
-  void PutInherit(const nsAString& aName);
+  bool IsUsedDecl(size_t aID) const;
+  void GetDeclName(size_t aID, nsAString& aName) const;
 
-  /**
-   * Adds or modifies an existing entry in this set of variable declarations
-   * to have the value 'unset'.
-   *
-   * @param aName The variable name (not including any "--" prefix that would
-   *   be part of the custom property name) whose value is to be set.
-   */
-  void PutUnset(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 "--" 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);
+  size_t Add(const nsAString& aName, Type aType, const nsAString& aExpr,
+             bool aImportant, bool aOverrideImportant,
+             CSSVariableExprContext aContext);
 
   /**
    * Removes an entry in this set of variable declarations.
    *
    * @param aName The variable name (not including any "--" 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.
+   * Returns the number of variable declarations.
+   * Double-counts multiple declarations of the same variable.
    */
-  uint32_t Count() const { return mVariables.Count(); }
+  uint32_t DeclCount() const { return mDeclarations.Length(); }
+
+  /**
+   * Returns the number of variables declared in this set of variable declarations.
+   * Does not double-count multiple declarations of the same variable.
+   */
+  uint32_t Count() const { return mVariableIDs.Count(); }
 
   /**
    * Copies each variable value from this object into aRuleData, unless that
-   * variable already exists on aRuleData.
+   * variable already exists on aRuleData.  Only values whose importance
+   * match aImportant are copied.
    */
-  void MapRuleInfoInto(nsRuleData* aRuleData);
+  void MapRuleInfoInto(nsRuleData* aRuleData, bool aImportant) const;
 
   /**
    * Copies the variables from this object into aResolver, marking them as
    * specified values.
    */
   void AddVariablesToResolver(CSSVariableResolver* aResolver) const;
 
+  bool HasImportant() const {
+    RecomputeValidities();
+    return mHasImportant;
+  }
+
+  typedef nsDataHashtable<nsStringHashKey, size_t>::Iterator Iterator;
+  Iterator Iter() const;
+
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
+  void SetImmutable() const { mImmutable = true; }
+
 private:
-  /**
-   * Adds all the variable declarations from aOther into this object.
-   */
-  void CopyVariablesFrom(const CSSVariableDeclarations& aOther);
+  bool DeclPriorityGte(size_t aFirst, size_t aSecond) const;
+
+  void RecomputeSingleValidity(const nsAString& aName) const;
+  void RecomputeValidities() const;
+
+  struct Declaration
+  {
+    Declaration(size_t aVarID, Type aType, const nsAString& aExpr,
+                bool aImportant, bool aOverrideImportant,
+                CSSVariableExprContext aContext)
+      : mVarID(aVarID)
+      , mType(aType)
+      , mExpr(aExpr)
+      , mImportant(aImportant)
+      , mOverrideImportant(aOverrideImportant)
+      , mContext(aContext)
+      , mParsed(false)
+    { }
+
+    size_t mVarID;
+    Type mType;
+    nsString mExpr;
+    bool mImportant;
+    bool mOverrideImportant;
+    CSSVariableExprContext mContext;
 
-  nsDataHashtable<nsStringHashKey, nsString> mVariables;
+    // These member variables are only valid if this is a registered custom
+    // property and the type is correct or was correct at some point.
+    // Just because these are present doesn't mean that we can use them as-is:
+    // this same declaration may parse as a different type given a different
+    // syntax.
+    mutable nsCSSValue mValue;
+    mutable CSSValueType mValueType;
+    mutable nsCSSTokenSerializationType mFirstToken;
+    mutable nsCSSTokenSerializationType mLastToken;
+    mutable bool mParsed;
+  };
+
+  RefPtr<const CSSVariableRegistrations> mRegistrations;
+
+  // Declaration ID -> |Declaration|
+  // The order corresponds to the declaration order.
+  nsTArray<Declaration> mDeclarations;
+  // Variable ID -> Declaration ID
+  // (or -1 = 0xFFF... if there is no valid declaration)
+  mutable nsTArray<size_t> mUsedDeclarations;
+  // Variable ID -> mRegistrations.mGeneration at the last time we recomputed the
+  //                valid declaration
+  mutable nsTArray<uint32_t> mVariableGenerations;
+  // mRegistrations.mGeneration at the last time we recomputed all the valid
+  // declarations
+  mutable uint32_t mTotalGeneration;
+  // variable name (without --) -> Variable ID
+  nsDataHashtable<nsStringHashKey, size_t> mVariableIDs;
+  // Variable ID -> variable name
+  nsTArray<nsString> mVariableNames;
+
+  // Currently, removing a variable removes its name -> ID mapping in
+  // |mVariableIDs| but doesn't touch anything else (in particular,
+  // |mVariableNames| still holds the old variable name).
+  // That means that mVariableIDs is not injective if a specific variable name
+  // is removed and then added back.
+  // This seems to be the simplest thing to do for now, even though it might
+  // waste space (as declarations and other info relevant to the variable rior
+  // to the removal are kept around, but refer to the old variable ID).
+
+  mutable bool mHasImportant;
+  mutable bool mImmutable;
+  // If |mImmutable| but |mNeverComputed|, that means we can and should
+  // recompute validities. The user has never read them before (causing a
+  // compute), so this will not cause the data to change from under them.
+  mutable bool mNeverComputed;
 };
 
 } // namespace mozilla
 
 #endif
--- a/layout/style/Declaration.cpp
+++ b/layout/style/Declaration.cpp
@@ -3,22 +3,21 @@
  * 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/. */
 
 /*
  * representation of a declaration block (or style attribute) in a CSS
  * stylesheet
  */
 
+#include "gfxFontConstants.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/MemoryReporting.h"
-
 #include "mozilla/css/Declaration.h"
 #include "nsPrintfCString.h"
-#include "gfxFontConstants.h"
 #include "nsStyleUtil.h"
 
 namespace mozilla {
 namespace css {
 
 NS_IMPL_QUERY_INTERFACE(ImportantStyleData, nsIStyleRule)
 NS_IMPL_ADDREF_USING_AGGREGATOR(ImportantStyleData, Declaration())
 NS_IMPL_RELEASE_USING_AGGREGATOR(ImportantStyleData, Declaration())
@@ -45,34 +44,34 @@ ImportantStyleData::List(FILE* out, int3
     str.AppendLiteral("  ");
   }
 
   str.AppendLiteral("! important rule\n");
   fprintf_stderr(out, "%s", str.get());
 }
 #endif
 
-Declaration::Declaration()
-  : mImmutable(false)
+Declaration::Declaration(const CSSVariableRegistrations* aRegistrations)
+  : mRegistrations(aRegistrations)
+  , mExposedOrderDirty(false)
+  , mImmutable(false)
 {
   mContainer.mRaw = uintptr_t(0);
 }
 
 Declaration::Declaration(const Declaration& aCopy)
   : mOrder(aCopy.mOrder),
-    mVariableOrder(aCopy.mVariableOrder),
+    mExposedOrder(aCopy.mExposedOrder),
     mData(aCopy.mData ? aCopy.mData->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),
+    mExposedOrderDirty(aCopy.mExposedOrderDirty),
     mImmutable(false)
 {
   mContainer.mRaw = uintptr_t(0);
 }
 
 Declaration::~Declaration()
 {
 }
@@ -92,52 +91,55 @@ NS_IMPL_ADDREF(Declaration)
 NS_IMPL_RELEASE(Declaration)
 
 /* virtual */ void
 Declaration::MapRuleInfoInto(nsRuleData* aRuleData)
 {
   MOZ_ASSERT(mData, "must call only while compressed");
   mData->MapRuleInfoInto(aRuleData);
   if (mVariables) {
-    mVariables->MapRuleInfoInto(aRuleData);
+    mVariables->MapRuleInfoInto(aRuleData, false);
   }
 }
 
 /* virtual */ bool
 Declaration::MightMapInheritedStyleData()
 {
   MOZ_ASSERT(mData, "must call only while compressed");
   if (mVariables && mVariables->Count() != 0) {
+    // Some custom properties might be inherited. All variables are.
     return true;
   }
   return mData->HasInheritedStyleData();
 }
 
 bool
 Declaration::MapsImportantInheritedStyleData() const
 {
   MOZ_ASSERT(mData, "must call only while compressed");
   MOZ_ASSERT(HasImportantData(), "must only be called for Declarations with "
                                  "important data");
-  if (mImportantVariables && mImportantVariables->Count() != 0) {
+  if (mVariables && mVariables->Count() != 0 && mVariables->HasImportant()) {
+    // Some custom properties might be inherited. All variables are.
     return true;
   }
   return mImportantData ? mImportantData->HasInheritedStyleData() : false;
 }
 
 void
 Declaration::ValueAppended(nsCSSPropertyID aProperty)
 {
   MOZ_ASSERT(!mData && !mImportantData,
              "should only be called while expanded");
   MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
              "shorthands forbidden");
   // order IS important for CSS, so remove and add to the end
   mOrder.RemoveElement(static_cast<uint32_t>(aProperty));
   mOrder.AppendElement(static_cast<uint32_t>(aProperty));
+  mExposedOrderDirty = true;
 }
 
 void
 Declaration::RemoveProperty(nsCSSPropertyID aProperty)
 {
   MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT);
 
   nsCSSExpandedDataBlock data;
@@ -149,16 +151,17 @@ Declaration::RemoveProperty(nsCSSPropert
                                          CSSEnabledState::eForAllContent) {
       data.ClearLonghandProperty(*p);
       mOrder.RemoveElement(static_cast<uint32_t>(*p));
     }
   } else {
     data.ClearLonghandProperty(aProperty);
     mOrder.RemoveElement(static_cast<uint32_t>(aProperty));
   }
+  mExposedOrderDirty = true;
 
   CompressFrom(&data);
 }
 
 bool
 Declaration::HasProperty(nsCSSPropertyID aProperty) const
 {
   MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_no_shorthands,
@@ -467,16 +470,29 @@ Declaration::GetImageLayerPositionValue(
       }
       return;
     }
     aValue.Append(char16_t(','));
     aValue.Append(char16_t(' '));
   }
 }
 
+bool
+Declaration::HasVariables() const
+{
+  return !!mVariables;
+}
+
+CSSVariableDeclarations::Iterator
+Declaration::IterVariables() const
+{
+  MOZ_ASSERT(mVariables);
+  return mVariables->Iter();
+}
+
 void
 Declaration::GetValue(nsCSSPropertyID aProperty, nsAString& aValue,
                       nsCSSValue::Serialization aSerialization) const
 {
   aValue.Truncate(0);
 
   // simple properties are easy.
   if (!nsCSSProps::IsShorthand(aProperty)) {
@@ -1500,50 +1516,42 @@ Declaration::AppendVariableAndValueToStr
 {
   nsAutoString localName;
   localName.AppendLiteral("--");
   localName.Append(aName);
   nsStyleUtil::AppendEscapedCSSIdent(localName, aResult);
   CSSVariableDeclarations::Type type;
   nsString value;
   bool important;
+  CSSVariableExprContext context;
 
-  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;
-  }
+  DebugOnly<bool> found = mVariables->Get(aName, type, value, important, context);
+  MOZ_ASSERT(found);
 
   switch (type) {
-    case CSSVariableDeclarations::eTokenStream:
+    case CSSVariableDeclarations::Type::TokenStream:
       if (value.IsEmpty()) {
         aResult.Append(':');
       } else {
         aResult.AppendLiteral(": ");
         aResult.Append(value);
       }
       break;
 
-    case CSSVariableDeclarations::eInitial:
+    case CSSVariableDeclarations::Type::Initial:
       aResult.AppendLiteral("initial");
       break;
 
-    case CSSVariableDeclarations::eInherit:
+    case CSSVariableDeclarations::Type::Inherit:
       aResult.AppendLiteral("inherit");
       break;
 
-    case CSSVariableDeclarations::eUnset:
+    case CSSVariableDeclarations::Type::Unset:
       aResult.AppendLiteral("unset");
       break;
-
-    default:
-      MOZ_ASSERT(false, "unexpected variable value type");
   }
 
   if (important) {
     aResult.AppendLiteral("! important");
   }
   aResult.AppendLiteral("; ");
 }
 
@@ -1558,25 +1566,29 @@ Declaration::ToString(nsAString& aString
     GetValueIsImportant(eCSSProperty__x_system_font) ? mImportantData : mData;
   const nsCSSValue *systemFont =
     systemFontData->ValueFor(eCSSProperty__x_system_font);
   const bool haveSystemFont = systemFont &&
                                 systemFont->GetUnit() != eCSSUnit_None &&
                                 systemFont->GetUnit() != eCSSUnit_Null;
   bool didSystemFont = false;
 
-  int32_t count = mOrder.Length();
+  UpdateExposedOrder();
+  int32_t count = mExposedOrder.Length();
   int32_t index;
   AutoTArray<nsCSSPropertyID, 16> shorthandsUsed;
   for (index = 0; index < count; index++) {
     nsCSSPropertyID property = GetPropertyAt(index);
 
     if (property == eCSSPropertyExtra_variable) {
-      uint32_t variableIndex = mOrder[index] - eCSSProperty_COUNT;
-      AppendVariableAndValueToString(mVariableOrder[variableIndex], aString);
+      MOZ_ASSERT(mVariables);
+      uint32_t declID = mExposedOrder[index] - eCSSProperty_COUNT;
+      nsAutoString varName;
+      mVariables->GetDeclName(declID, varName);
+      AppendVariableAndValueToString(varName, aString);
       continue;
     }
 
     if (!nsCSSProps::IsEnabled(property, CSSEnabledState::eForAllContent)) {
       continue;
     }
     bool doneProperty = false;
 
@@ -1685,17 +1697,18 @@ Declaration::List(FILE* out, int32_t aIn
   fprintf_stderr(out, "%s", str.get());
 }
 #endif
 
 bool
 Declaration::GetNthProperty(uint32_t aIndex, nsAString& aReturn) const
 {
   aReturn.Truncate();
-  if (aIndex < mOrder.Length()) {
+  UpdateExposedOrder();
+  if (aIndex < mExposedOrder.Length()) {
     nsCSSPropertyID property = GetPropertyAt(aIndex);
     if (property == eCSSPropertyExtra_variable) {
       GetCustomPropertyNameAt(aIndex, aReturn);
       return true;
     }
     if (0 <= property) {
       AppendASCIItoUTF16(nsCSSProps::GetStringValue(property), aReturn);
       return true;
@@ -1719,149 +1732,152 @@ Declaration::EnsureMutable()
   if (!IsMutable()) {
     result = new Declaration(*this);
   } else {
     result = this;
   }
   return result.forget();
 }
 
+void
+Declaration::SetImmutable() const
+{
+  mImmutable = true;
+  if (mVariables) {
+    mVariables->SetImmutable();
+  }
+}
+
 size_t
 Declaration::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
   size_t n = aMallocSizeOf(this);
   n += mOrder.ShallowSizeOfExcludingThis(aMallocSizeOf);
+  n += mExposedOrder.ShallowSizeOfExcludingThis(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;
 }
 
 void
 Declaration::GetVariableDeclaration(const nsAString& aName,
                                     nsAString& aValue) const
 {
   aValue.Truncate();
 
   CSSVariableDeclarations::Type type;
   nsString value;
+  bool important;
+  CSSVariableExprContext context;
 
-  if ((mImportantVariables && mImportantVariables->Get(aName, type, value)) ||
-      (mVariables && mVariables->Get(aName, type, value))) {
+  if (mVariables && mVariables->Get(aName, type, value, important, context)) {
     switch (type) {
-      case CSSVariableDeclarations::eTokenStream:
+      case CSSVariableDeclarations::Type::TokenStream:
         aValue.Append(value);
         break;
 
-      case CSSVariableDeclarations::eInitial:
+      case CSSVariableDeclarations::Type::Initial:
         aValue.AppendLiteral("initial");
         break;
 
-      case CSSVariableDeclarations::eInherit:
+      case CSSVariableDeclarations::Type::Inherit:
         aValue.AppendLiteral("inherit");
         break;
 
-      case CSSVariableDeclarations::eUnset:
+      case CSSVariableDeclarations::Type::Unset:
         aValue.AppendLiteral("unset");
         break;
-
-      default:
-        MOZ_ASSERT(false, "unexpected variable value type");
     }
   }
 }
 
 void
 Declaration::AddVariableDeclaration(const nsAString& aName,
                                     CSSVariableDeclarations::Type aType,
-                                    const nsString& aValue,
+                                    const nsAString& aValue,
                                     bool aIsImportant,
-                                    bool aOverrideImportant)
+                                    bool aOverrideImportant,
+                                    CSSVariableExprContext aContext)
 {
   MOZ_ASSERT(IsMutable());
 
-  nsTArray<nsString>::index_type index = mVariableOrder.IndexOf(aName);
-  if (index == nsTArray<nsString>::NoIndex) {
-    index = mVariableOrder.Length();
-    mVariableOrder.AppendElement(aName);
-  }
+  if (!mVariables) {
+    mVariables = new CSSVariableDeclarations(mRegistrations);
+  } 
 
-  if (!aIsImportant && !aOverrideImportant &&
-      mImportantVariables && mImportantVariables->Has(aName)) {
+  // Hope we don't get too many declarations...
+  size_t szDeclID = mVariables->Add(aName, aType, aValue, aIsImportant,
+                                    aOverrideImportant, aContext);
+  if (szDeclID > (uint32_t) -1) {
+    // Wow. That's a lot of declarations.
     return;
   }
-
-  CSSVariableDeclarations* variables;
-  if (aIsImportant) {
-    if (mVariables) {
-      mVariables->Remove(aName);
-    }
-    if (!mImportantVariables) {
-      mImportantVariables = new CSSVariableDeclarations;
-    }
-    variables = mImportantVariables;
-  } else {
-    if (mImportantVariables) {
-      mImportantVariables->Remove(aName);
-    }
-    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;
-
-    case CSSVariableDeclarations::eUnset:
-      MOZ_ASSERT(aValue.IsEmpty());
-      variables->PutUnset(aName);
-      break;
-
-    default:
-      MOZ_ASSERT(false, "unexpected aType value");
-  }
-
-  uint32_t propertyIndex = index + eCSSProperty_COUNT;
-  mOrder.RemoveElement(propertyIndex);
-  mOrder.AppendElement(propertyIndex);
+  uint32_t declID = (uint32_t) szDeclID;
+  mOrder.AppendElement(eCSSProperty_COUNT + declID);
+  mExposedOrderDirty = true;
 }
 
 void
 Declaration::RemoveVariableDeclaration(const nsAString& aName)
 {
+  MOZ_ASSERT(IsMutable());
+
   if (mVariables) {
     mVariables->Remove(aName);
   }
-  if (mImportantVariables) {
-    mImportantVariables->Remove(aName);
+  AutoTArray<uint32_t, 8> oldOrder = mOrder;
+  mOrder.Clear();
+  for (uint32_t id : oldOrder) {
+    if (id >= eCSSProperty_COUNT) {
+      uint32_t declID = id - eCSSProperty_COUNT;
+      nsAutoString declName;
+      mVariables->GetDeclName(declID, declName);
+      if (declName.Equals(aName)) {
+        continue;
+      }
+    }
+    mOrder.AppendElement(id);
   }
-  nsTArray<nsString>::index_type index = mVariableOrder.IndexOf(aName);
-  if (index != nsTArray<nsString>::NoIndex) {
-    mOrder.RemoveElement(index + eCSSProperty_COUNT);
-  }
+  mExposedOrderDirty = true;
 }
 
 bool
 Declaration::GetVariableValueIsImportant(const nsAString& aName) const
 {
-  return mImportantVariables && mImportantVariables->Has(aName);
+  CSSVariableDeclarations::Type type;
+  nsString value;
+  bool important;
+  CSSVariableExprContext context;
+  return mVariables ? mVariables->Get(aName, type, value, important, context) && important
+                    : false;
+}
+
+void
+Declaration::UpdateExposedOrder() const
+{
+  if (!mExposedOrderDirty) {
+    return;
+  }
+
+  mExposedOrder.Clear();
+  for (size_t i = 0; i < mOrder.Length(); i++) {
+    if (mOrder[i] < eCSSProperty_COUNT) {
+      // Not a custom property. Just pass it along.
+      mExposedOrder.AppendElement(mOrder[i]);
+    } else {
+      // A custom property. Check if it's the valid declaration for its
+      // variable and if the variable it was added for wasn't removed.
+      MOZ_ASSERT(mVariables);
+      uint32_t declID = mOrder[i] - eCSSProperty_COUNT;
+      if (mVariables->IsUsedDecl(declID)) {
+        mExposedOrder.AppendElement(mOrder[i]);
+      }
+    }
+  }
+
+  mExposedOrderDirty = false;
 }
 
 } // namespace css
 } // namespace mozilla
--- a/layout/style/Declaration.h
+++ b/layout/style/Declaration.h
@@ -14,17 +14,18 @@
 // 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 "mozilla/CSSVariableDeclarations.h"
+#include "mozilla/CSSVariableRegistrations.h"
 #include "nsCSSDataBlock.h"
 #include "nsCSSPropertyID.h"
 #include "nsCSSProps.h"
 #include "nsIStyleRule.h"
 #include "nsStringFwd.h"
 #include "nsTArray.h"
 #include <stdio.h>
 
@@ -81,18 +82,22 @@ private:
 // |EnsureMutable|.
 
 class Declaration final : public nsIStyleRule {
 public:
   /**
    * Construct an |Declaration| that is in an invalid state (null
    * |mData|) and cannot be used until its |CompressFrom| method or
    * |InitializeEmpty| method is called.
+   *
+   * |aRegistrations| is either a pointer to the custom property registrations
+   * of the owning window, or null. If it is null, variable declarations in
+   * this |Declaration| will not make use of types.
    */
-  Declaration();
+  explicit Declaration(const CSSVariableRegistrations* aRegistrations);
 
   Declaration(const Declaration& aCopy);
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_CSS_DECLARATION_IMPL_CID)
 
   NS_DECL_ISUPPORTS
 
 private:
@@ -117,37 +122,39 @@ public:
   void RemoveProperty(nsCSSPropertyID aProperty);
 
   bool HasProperty(nsCSSPropertyID aProperty) const;
 
   void GetValue(nsCSSPropertyID aProperty, nsAString& aValue) const;
   void GetAuthoredValue(nsCSSPropertyID aProperty, nsAString& aValue) const;
 
   bool HasImportantData() const {
-    return mImportantData || mImportantVariables;
+    return mImportantData ||
+           (mVariables && mVariables->HasImportant());
   }
   bool GetValueIsImportant(nsCSSPropertyID aProperty) const;
   bool GetValueIsImportant(const nsAString& aProperty) const;
 
   /**
-   * Adds a custom property declaration to this object.
+   * Puts a custom property declaration to this object.
    *
    * @param aName The variable name (i.e., without the "--" 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.
    * @param aOverrideImportant When aIsImportant is false, whether an
    *   existing !important declaration will be overridden.
    */
   void AddVariableDeclaration(const nsAString& aName,
                               CSSVariableDeclarations::Type aType,
-                              const nsString& aValue,
+                              const nsAString& aValue,
                               bool aIsImportant,
-                              bool aOverrideImportant);
+                              bool aOverrideImportant,
+                              CSSVariableExprContext aContext);
 
   /**
    * Removes a custom property declaration from this object.
    *
    * @param aName The variable name (i.e., without the "--" prefix).
    */
   void RemoveVariableDeclaration(const nsAString& aName);
 
@@ -163,18 +170,21 @@ public:
   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;
 
+  void UpdateExposedOrder() const;
+
   uint32_t Count() const {
-    return mOrder.Length();
+    UpdateExposedOrder();
+    return mExposedOrder.Length();
   }
 
   // Returns whether we actually had a property at aIndex
   bool GetNthProperty(uint32_t aIndex, nsAString& aReturn) const;
 
   void ToString(nsAString& aString) const;
 
   nsCSSCompressedDataBlock* GetNormalBlock() const { return mData; }
@@ -188,16 +198,17 @@ public:
   /**
    * Transfer all of the state from |aExpandedData| into this declaration.
    * After calling, |aExpandedData| should be in its initial state.
    * Callers must make sure mOrder is updated as necessary.
    */
   void CompressFrom(nsCSSExpandedDataBlock *aExpandedData) {
     MOZ_ASSERT(!mData, "oops");
     MOZ_ASSERT(!mImportantData, "oops");
+    // Compress ignores custom properties.
     aExpandedData->Compress(getter_Transfers(mData),
                             getter_Transfers(mImportantData),
                             mOrder);
     aExpandedData->AssertInitialState();
   }
 
   /**
    * Transfer all of the state from this declaration into
@@ -211,23 +222,22 @@ public:
     aExpandedData->AssertInitialState();
 
     MOZ_ASSERT(mData, "oops");
     aExpandedData->Expand(mData.forget(), mImportantData.forget());
   }
 
   void MapImportantRuleInfoInto(nsRuleData *aRuleData) const {
     MOZ_ASSERT(mData, "called while expanded");
-    MOZ_ASSERT(mImportantData || mImportantVariables,
-               "must have important data or variables");
+    MOZ_ASSERT(HasImportantData(), "must have important data or variables");
     if (mImportantData) {
       mImportantData->MapRuleInfoInto(aRuleData);
     }
-    if (mImportantVariables) {
-      mImportantVariables->MapRuleInfoInto(aRuleData);
+    if (mVariables) {
+      mVariables->MapRuleInfoInto(aRuleData, true);
     }
   }
 
   bool MapsImportantInheritedStyleData() const;
 
   /**
    * Attempt to replace the value for |aProperty| stored in this
    * declaration with the matching value from |aFromBlock|.
@@ -290,30 +300,30 @@ public:
   void AssertMutable() const {
     MOZ_ASSERT(IsMutable(), "someone forgot to call EnsureMutable");
   }
 
   /**
    * Mark this declaration as unmodifiable.  It's 'const' so it can
    * be called from ToString.
    */
-  void SetImmutable() const { mImmutable = true; }
+  void SetImmutable() const;
 
   /**
    * 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();
+    mExposedOrder.Clear();
+    mExposedOrderDirty = false;
   }
 
   void SetOwningRule(Rule* aRule) {
     MOZ_ASSERT(!mContainer.mOwningRule || !aRule,
                "should never overwrite one rule with another");
     mContainer.mOwningRule = aRule;
   }
 
@@ -382,63 +392,73 @@ private:
 
 public:
   /**
    * Returns the property at the given index in the ordered list of
    * declarations.  For custom properties, eCSSPropertyExtra_variable
    * is returned.
    */
   nsCSSPropertyID GetPropertyAt(uint32_t aIndex) const {
-    uint32_t value = mOrder[aIndex];
+    UpdateExposedOrder();
+    uint32_t value = mExposedOrder[aIndex];
     if (value >= eCSSProperty_COUNT) {
       return eCSSPropertyExtra_variable;
     }
     return nsCSSPropertyID(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);
-    uint32_t variableIndex = mOrder[aIndex] - eCSSProperty_COUNT;
+    UpdateExposedOrder();
+    MOZ_ASSERT(mExposedOrder[aIndex] >= eCSSProperty_COUNT);
+    MOZ_ASSERT(mVariables);
+    uint32_t declID = mOrder[aIndex] - eCSSProperty_COUNT;
     aResult.Truncate();
     aResult.AppendLiteral("--");
-    aResult.Append(mVariableOrder[variableIndex]);
+    nsAutoString varName;
+    mVariables->GetDeclName(declID, varName);
+    aResult.Append(varName);
   }
 
+  bool HasVariables() const;
+
+  /**
+   * Returns an iterator over the names of variable in this declaration
+   * (without the leading --).
+   *
+   * It is an error to call this if |!HasVariables()|.
+   */
+  CSSVariableDeclarations::Iterator IterVariables() const;
+
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
 private:
   // The order of properties in this declaration.  Longhand properties are
   // represented by their nsCSSPropertyID value, and each custom property (--*)
   // 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.
   AutoTArray<uint32_t, 8> mOrder;
-
-  // variable names of custom properties found in mOrder
-  nsTArray<nsString> mVariableOrder;
+  mutable AutoTArray<uint32_t, 8> mExposedOrder;
 
   // 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;
-
   union {
     // We only ever have one of these since we have an
     // nsHTMLCSSStyleSheet only for style attributes, and style
     // attributes never have an owning rule.
 
     // It's an nsHTMLCSSStyleSheet if the low bit is set.
 
     uintptr_t mRaw;
@@ -449,16 +469,23 @@ private:
     // The nsHTMLCSSStyleSheet that is responsible for this declaration.
     // Only non-null for style attributes.
     nsHTMLCSSStyleSheet* mHTMLCSSStyleSheet;
   } mContainer;
 
   friend class ImportantStyleData;
   ImportantStyleData mImportantStyleData;
 
+  // Custom property registrations of the owning window.
+  // TODO(jyc) Do we change this to the owning document?
+  // We do not own this. Only used by |mVariables|.
+  RefPtr<const CSSVariableRegistrations> mRegistrations;
+
+  mutable bool mExposedOrderDirty;
+
   // set when declaration put in the rule tree;
   // also by ToString (hence the 'mutable').
   mutable bool mImmutable;
 };
 
 inline ::mozilla::css::Declaration*
 ImportantStyleData::Declaration()
 {
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -172,17 +172,19 @@ FontFaceSet::ParseFontShorthandForMatchi
                             const nsAString& aFont,
                             RefPtr<FontFamilyListRefCnt>& aFamilyList,
                             uint32_t& aWeight,
                             int32_t& aStretch,
                             uint8_t& aStyle,
                             ErrorResult& aRv)
 {
   // Parse aFont as a 'font' property value.
-  RefPtr<Declaration> declaration = new Declaration;
+  // Null custom property registrations is OK because we're only using this for
+  // the font property.
+  RefPtr<Declaration> declaration = new Declaration(nullptr);
   declaration->InitializeEmpty();
 
   bool changed = false;
   nsCSSParser parser;
   parser.ParseProperty(eCSSProperty_font,
                        aFont,
                        mDocument->GetDocumentURI(),
                        mDocument->GetDocumentURI(),
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -2794,17 +2794,19 @@ StyleAnimationValue::AddWeighted(nsCSSPr
 
 already_AddRefed<css::StyleRule>
 BuildStyleRule(nsCSSPropertyID aProperty,
                dom::Element* aTargetElement,
                const nsAString& aSpecifiedValue,
                bool aUseSVGMode)
 {
   // Set up an empty CSS Declaration
-  RefPtr<css::Declaration> declaration(new css::Declaration());
+  // Custom property registrations can be null beacuse we should have that the
+  // specified value (that we're trying to animate) has the correct type.
+  RefPtr<css::Declaration> declaration(new css::Declaration(nullptr));
   declaration->InitializeEmpty();
 
   bool changed; // ignored, but needed as outparam for ParseProperty
   nsIDocument* doc = aTargetElement->OwnerDoc();
   nsCOMPtr<nsIURI> baseURI = aTargetElement->GetBaseURI();
   nsCSSParser parser(doc->CSSLoader());
 
   nsCSSPropertyID propertyToCheck = nsCSSProps::IsShorthand(aProperty) ?
@@ -2837,17 +2839,19 @@ BuildStyleRule(nsCSSPropertyID aProperty
              "Should be a longhand property");
 
   // Check if longhand failed to parse correctly.
   if (aSpecifiedValue.GetUnit() == eCSSUnit_Null) {
     return nullptr;
   }
 
   // Set up an empty CSS Declaration
-  RefPtr<css::Declaration> declaration(new css::Declaration());
+  // Custom property registrations can be null beacuse we should have that the
+  // specified value (that we're trying to animate) has the correct type.
+  RefPtr<css::Declaration> declaration(new css::Declaration(nullptr));
   declaration->InitializeEmpty();
 
   // Add our longhand value
   nsCSSExpandedDataBlock block;
   declaration->ExpandTo(&block);
   block.AddLonghandProperty(aProperty, aSpecifiedValue);
   declaration->ValueAppended(aProperty);
   declaration->CompressFrom(&block);
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -1924,17 +1924,18 @@ CSSParserImpl::ParseLonghandProperty(con
                                      nsIURI* aSheetURL,
                                      nsIURI* aBaseURL,
                                      nsIPrincipal* aSheetPrincipal,
                                      nsCSSValue& aValue)
 {
   MOZ_ASSERT(aPropID < eCSSProperty_COUNT_no_shorthands,
              "ParseLonghandProperty must only take a longhand property");
 
-  RefPtr<css::Declaration> declaration = new css::Declaration;
+  RefPtr<css::Declaration> declaration =
+    new css::Declaration(GetVariableRegistrations());
   declaration->InitializeEmpty();
 
   bool changed;
   ParseProperty(aPropID, aPropValue, aSheetURL, aBaseURL, aSheetPrincipal,
                 declaration, &changed,
                 /* aIsImportant */ false,
                 /* aIsSVGMode */ false);
 
@@ -1945,17 +1946,19 @@ CSSParserImpl::ParseLonghandProperty(con
   }
 }
 
 bool
 CSSParserImpl::ParseTransformProperty(const nsAString& aPropValue,
                                       bool aDisallowRelativeValues,
                                       nsCSSValue& aValue)
 {
-  RefPtr<css::Declaration> declaration = new css::Declaration();
+  // OK to have custom property registrations as null because we're only
+  // parsing the transform property, not custom property registrations.
+  RefPtr<css::Declaration> declaration = new css::Declaration(nullptr);
   declaration->InitializeEmpty();
 
   mData.AssertInitialState();
   mTempData.AssertInitialState();
 
   nsCSSScanner scanner(aPropValue, 0);
   css::ErrorReporter reporter(scanner, mSheet, mChildLoader, nullptr);
   InitScanner(scanner, reporter, nullptr, nullptr, nullptr);
@@ -2106,17 +2109,20 @@ CSSParserImpl::ParseVariable(const nsASt
   if (!parsedOK) {
     REPORT_UNEXPECTED_P(PEValueParsingError, NS_LITERAL_STRING("--") +
                                              aVariableName);
     REPORT_UNEXPECTED(PEDeclDropped);
     OUTPUT_ERROR();
   } else {
     CLEAR_ERROR();
     aDeclaration->AddVariableDeclaration(aVariableName, variableType,
-                                         variableValue, aIsImportant, true);
+                                         variableValue, aIsImportant, true,
+                                         CSSVariableExprContext(mSheetURI,
+                                                             mBaseURI,
+                                                             mSheetPrincipal));
     *aChanged = true;
   }
 
   mTempData.AssertInitialState();
 
   ReleaseScanner();
 }
 
@@ -3237,17 +3243,17 @@ CSSParserImpl::ParseTypedValue(const CSS
   CSSVariableSyntax::Type syntaxType = aSyntax.GetType();
 
   if (syntaxType == CSSVariableSyntax::Type::Anything) {
     CSSVariableDeclarations::Type type;
     nsString result;
     if (!ParseVariableDeclaration(&type, aAllowVariableReferences, result)) {
       return false;
     }
-    if (type != CSSVariableDeclarations::Type::eTokenStream) {
+    if (type != CSSVariableDeclarations::Type::TokenStream) {
       return false;
     }
     nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream;
     // Can't use eCSSPropertyExtra_UNKNOWN because that would cause
     // KeyframeUtils::IsInvalidValuePair to think these values were invalid.
     tokenStream->mPropertyID = eCSSPropertyExtra_variable;
     tokenStream->mTokenStream = result;
     tokenStream->mBaseURI = mBaseURI;
@@ -6836,17 +6842,18 @@ CSSParserImpl::ParseDeclarationBlock(uin
 
   if (checkForBraces) {
     if (!ExpectSymbol('{', true)) {
       REPORT_UNEXPECTED_TOKEN(PEBadDeclBlockStart);
       OUTPUT_ERROR();
       return nullptr;
     }
   }
-  RefPtr<css::Declaration> declaration = new css::Declaration();
+  RefPtr<css::Declaration> declaration =
+    new css::Declaration(GetVariableRegistrations());
   mData.AssertInitialState();
   for (;;) {
     bool changed = false;
     if (!ParseDeclaration(declaration, aFlags, true, &changed, aContext)) {
       if (!SkipDeclaration(checkForBraces)) {
         break;
       }
       if (checkForBraces) {
@@ -7635,17 +7642,20 @@ CSSParserImpl::ParseDeclaration(css::Dec
   }
 
   if (customProperty) {
     MOZ_ASSERT(Substring(propertyName, 0,
                          CSS_CUSTOM_NAME_PREFIX_LENGTH).EqualsLiteral("--"));
     // remove '--'
     nsDependentString varName(propertyName, CSS_CUSTOM_NAME_PREFIX_LENGTH);
     aDeclaration->AddVariableDeclaration(varName, variableType, variableValue,
-                                         status == ePriority_Important, false);
+                                         status == ePriority_Important, false,
+                                         CSSVariableExprContext(mSheetURI,
+                                                             mBaseURI,
+                                                             mSheetPrincipal));
   } else {
     *aChanged |= mData.TransferFromBlock(mTempData, propID, EnabledState(),
                                          status == ePriority_Important,
                                          false, aMustCallValueAppended,
                                          aDeclaration, GetDocument());
   }
 
   return true;
@@ -11637,19 +11647,20 @@ CSSParserImpl::ParseProperty(nsCSSProper
     }
 
     CSSVariableDeclarations::Type type;
     bool dropBackslash;
     nsString impliedCharacters;
     nsCSSValue value;
     if (ParseValueWithVariables(&type, &dropBackslash, impliedCharacters,
                                 nullptr, nullptr)) {
-      MOZ_ASSERT(type == CSSVariableDeclarations::eTokenStream,
+      MOZ_ASSERT(type == CSSVariableDeclarations::Type::TokenStream,
                  "a non-custom property reparsed since it contained variable "
-                 "references should not have been 'initial' or 'inherit'");
+                 "references should not have been 'initial', 'inherit' or "
+                 "'unset'");
 
       nsString propertyValue;
 
       if (!mInSupportsCondition) {
         // If we are in an @supports condition, we don't need to store the
         // actual token stream on the nsCSSValue.
         mScanner->StopRecording(propertyValue);
         if (dropBackslash) {
@@ -17497,33 +17508,33 @@ CSSParserImpl::ParseVariableDeclaration(
       gotDisallowedVariableReference) {
     if (!mInSupportsCondition) {
       mScanner->StopRecording();
     }
     return false;
   }
 
   if (!mInSupportsCondition) {
-    if (type == CSSVariableDeclarations::eTokenStream) {
+    if (type == CSSVariableDeclarations::Type::TokenStream) {
       // This was indeed a token stream value, so store it in variableValue.
       mScanner->StopRecording(variableValue);
       if (dropBackslash) {
         MOZ_ASSERT(!variableValue.IsEmpty() &&
                    variableValue[variableValue.Length() - 1] == '\\');
         variableValue.Truncate(variableValue.Length() - 1);
       }
       variableValue.Append(impliedCharacters);
     } else {
       // This was either 'inherit' or 'initial'; we don't need the recorded
       // input.
       mScanner->StopRecording();
     }
   }
 
-  if (mHavePushBack && type == CSSVariableDeclarations::eTokenStream) {
+  if (mHavePushBack && type == CSSVariableDeclarations::Type::TokenStream) {
     // 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('}'));
@@ -17679,37 +17690,37 @@ CSSParserImpl::ParseValueWithVariables(C
     REPORT_UNEXPECTED_TOKEN(PEVariableEmpty);
     return false;
   }
 
   if (mToken.mType == eCSSToken_Whitespace) {
     if (!GetToken(true)) {
       // Variable value was white space only.  This is valid.
       MOZ_ASSERT(!BackslashDropped());
-      *aType = CSSVariableDeclarations::eTokenStream;
+      *aType = CSSVariableDeclarations::Type::TokenStream;
       *aDropBackslash = false;
       AppendImpliedEOFCharacters(aImpliedCharacters);
       return true;
     }
   }
 
   // Look for 'initial', 'inherit' or 'unset' as the first non-white space
   // token.
-  CSSVariableDeclarations::Type type = CSSVariableDeclarations::eTokenStream;
+  CSSVariableDeclarations::Type type = CSSVariableDeclarations::Type::TokenStream;
   if (mToken.mType == eCSSToken_Ident) {
     if (mToken.mIdent.LowerCaseEqualsLiteral("initial")) {
-      type = CSSVariableDeclarations::eInitial;
+      type = CSSVariableDeclarations::Type::Initial;
     } else if (mToken.mIdent.LowerCaseEqualsLiteral("inherit")) {
-      type = CSSVariableDeclarations::eInherit;
+      type = CSSVariableDeclarations::Type::Inherit;
     } else if (mToken.mIdent.LowerCaseEqualsLiteral("unset")) {
-      type = CSSVariableDeclarations::eUnset;
-    }
-  }
-
-  if (type != CSSVariableDeclarations::eTokenStream) {
+      type = CSSVariableDeclarations::Type::Unset;
+    }
+  }
+
+  if (type != CSSVariableDeclarations::Type::TokenStream) {
     if (!GetToken(true)) {
       // Variable value was 'initial' or 'inherit' followed by EOF.
       MOZ_ASSERT(!BackslashDropped());
       *aType = type;
       *aDropBackslash = false;
       AppendImpliedEOFCharacters(aImpliedCharacters);
       return true;
     }
@@ -17738,33 +17749,33 @@ CSSParserImpl::ParseValueWithVariables(C
           stack.AppendElement(']');
         } else if (mToken.mSymbol == '{') {
           stack.AppendElement('}');
         } else if (mToken.mSymbol == ';' ||
                    mToken.mSymbol == '!') {
           if (stack.IsEmpty()) {
             UngetToken();
             MOZ_ASSERT(!BackslashDropped());
-            *aType = CSSVariableDeclarations::eTokenStream;
+            *aType = CSSVariableDeclarations::Type::TokenStream;
             *aDropBackslash = false;
             return true;
           } else if (!references.IsEmpty() &&
                      references.LastElement() == stack.Length() - 1) {
             REPORT_UNEXPECTED_TOKEN(PEInvalidVariableTokenFallback);
             SkipUntilAllOf(stack);
             return false;
           }
         } else if (mToken.mSymbol == ')' ||
                    mToken.mSymbol == ']' ||
                    mToken.mSymbol == '}') {
           for (;;) {
             if (stack.IsEmpty()) {
               UngetToken();
               MOZ_ASSERT(!BackslashDropped());
-              *aType = CSSVariableDeclarations::eTokenStream;
+              *aType = CSSVariableDeclarations::Type::TokenStream;
               *aDropBackslash = false;
               return true;
             }
             char16_t c = stack.LastElement();
             stack.TruncateLength(stack.Length() - 1);
             if (!references.IsEmpty() &&
                 references.LastElement() == stack.Length()) {
               references.TruncateLength(references.Length() - 1);
--- a/layout/style/nsDOMCSSAttrDeclaration.cpp
+++ b/layout/style/nsDOMCSSAttrDeclaration.cpp
@@ -12,16 +12,17 @@
 #include "mozilla/dom/Element.h"
 #include "nsIDocument.h"
 #include "nsIDOMMutationEvent.h"
 #include "nsIURI.h"
 #include "nsNodeUtils.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsIFrame.h"
 #include "ActiveLayerTracker.h"
+#include "mozilla/CSSVariableRegistration.h"
 
 using namespace mozilla;
 
 nsDOMCSSAttributeDeclaration::nsDOMCSSAttributeDeclaration(dom::Element* aElement,
                                                            bool aIsSMILOverride)
   : mElement(aElement)
   , mIsSMILOverride(aIsSMILOverride)
 {
@@ -121,18 +122,22 @@ nsDOMCSSAttributeDeclaration::GetCSSDecl
   if (declaration) {
     return declaration;
   }
 
   if (aOperation != eOperation_Modify) {
     return nullptr;
   }
 
+  CSSParsingEnvironment env;
+  GetCSSParsingEnvironment(env);
+
   // cannot fail
-  RefPtr<css::Declaration> decl = new css::Declaration();
+  RefPtr<css::Declaration> decl =
+    new css::Declaration(CSSVariableRegistrationsOfDocument(env.mDocument));
   decl->InitializeEmpty();
 
   // this *can* fail (inside SetAttrAndNotify, at least).
   nsresult rv;
   if (mIsSMILOverride)
     rv = mElement->SetSMILOverrideStyleDeclaration(decl, false);
   else
     rv = mElement->SetInlineStyleDeclaration(decl, nullptr, false);
@@ -149,16 +154,17 @@ nsDOMCSSAttributeDeclaration::GetCSSPars
 {
   NS_ASSERTION(mElement, "Something is severely broken -- there should be an Element here!");
 
   nsIDocument* doc = mElement->OwnerDoc();
   aCSSParseEnv.mSheetURI = doc->GetDocumentURI();
   aCSSParseEnv.mBaseURI = mElement->GetBaseURI();
   aCSSParseEnv.mPrincipal = mElement->NodePrincipal();
   aCSSParseEnv.mCSSLoader = doc->CSSLoader();
+  aCSSParseEnv.mDocument = doc;
 }
 
 NS_IMETHODIMP
 nsDOMCSSAttributeDeclaration::GetParentRule(nsIDOMCSSRule **aParent)
 {
   NS_ENSURE_ARG_POINTER(aParent);
 
   *aParent = nullptr;
--- a/layout/style/nsDOMCSSDeclaration.cpp
+++ b/layout/style/nsDOMCSSDeclaration.cpp
@@ -15,16 +15,17 @@
 #include "nsCSSProps.h"
 #include "nsCOMPtr.h"
 #include "mozAutoDocUpdate.h"
 #include "nsIURI.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "nsContentUtils.h"
 #include "nsQueryObject.h"
 #include "mozilla/layers/ScrollLinkedEffectDetector.h"
+#include "mozilla/CSSVariableRegistration.h"
 
 using namespace mozilla;
 
 nsDOMCSSDeclaration::~nsDOMCSSDeclaration()
 {
 }
 
 /* virtual */ JSObject*
@@ -143,17 +144,20 @@ nsDOMCSSDeclaration::SetCssText(const ns
 
   // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to
   // Attribute setting code, which leads in turn to BeginUpdate.  We
   // need to start the update now so that the old rule doesn't get used
   // between when we mutate the declaration and when we set the new
   // rule (see stack in bug 209575).
   mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true);
 
-  RefPtr<css::Declaration> decl(new css::Declaration());
+  const CSSVariableRegistrations* regs =
+    env.mDocument ? CSSVariableRegistrationsOfDocument(env.mDocument)
+                  : nullptr;
+  RefPtr<css::Declaration> decl(new css::Declaration(regs));
   decl->InitializeEmpty();
   nsCSSParser cssParser(env.mCSSLoader);
   bool changed;
   nsresult result = cssParser.ParseDeclarations(aCssText, env.mSheetURI,
                                                 env.mBaseURI,
                                                 env.mPrincipal, decl, &changed);
   if (NS_FAILED(result) || !changed) {
     return result;
@@ -311,24 +315,26 @@ nsDOMCSSDeclaration::RemoveProperty(cons
 
 /* static */ void
 nsDOMCSSDeclaration::GetCSSParsingEnvironmentForRule(css::Rule* aRule,
                                                      CSSParsingEnvironment& aCSSParseEnv)
 {
   CSSStyleSheet* sheet = aRule ? aRule->GetStyleSheet() : nullptr;
   if (!sheet) {
     aCSSParseEnv.mPrincipal = nullptr;
+    aCSSParseEnv.mDocument = nullptr;
     return;
   }
 
   nsIDocument* document = sheet->GetOwningDocument();
   aCSSParseEnv.mSheetURI = sheet->GetSheetURI();
   aCSSParseEnv.mBaseURI = sheet->GetBaseURI();
   aCSSParseEnv.mPrincipal = sheet->Principal();
   aCSSParseEnv.mCSSLoader = document ? document->CSSLoader() : nullptr;
+  aCSSParseEnv.mDocument = document;
 }
 
 nsresult
 nsDOMCSSDeclaration::ParsePropertyValue(const nsCSSPropertyID aPropID,
                                         const nsAString& aPropValue,
                                         bool aIsImportant)
 {
   css::Declaration* olddecl = GetCSSDeclaration(eOperation_Modify);
--- a/layout/style/nsDOMCSSDeclaration.h
+++ b/layout/style/nsDOMCSSDeclaration.h
@@ -128,27 +128,30 @@ protected:
 
   // Information neded to parse a declaration.  We need the mSheetURI
   // for error reporting, mBaseURI to resolve relative URIs,
   // mPrincipal for subresource loads, and mCSSLoader for determining
   // whether we're in quirks mode.  mBaseURI needs to be a strong
   // pointer because of xml:base possibly creating base URIs on the
   // fly.  This is why we don't use CSSParsingEnvironment as a return
   // value, to avoid multiple-refcounting of mBaseURI.
+  // |mDocument| is used when creating new declarations so that we handle
+  // custom properties appropriately.
   struct CSSParsingEnvironment {
     nsIURI* MOZ_UNSAFE_REF("user of CSSParsingEnviroment must hold an owning "
                            "reference; reference counting here has unacceptable "
                            "performance overhead (see bug 649163)") mSheetURI;
     nsCOMPtr<nsIURI> mBaseURI;
     nsIPrincipal* MOZ_UNSAFE_REF("user of CSSParsingEnviroment must hold an owning "
                                  "reference; reference counting here has unacceptable "
                                  "performance overhead (see bug 649163)") mPrincipal;
     mozilla::css::Loader* MOZ_UNSAFE_REF("user of CSSParsingEnviroment must hold an owning "
                                          "reference; reference counting here has unacceptable "
                                          "performance overhead (see bug 649163)") mCSSLoader;
+    nsIDocument* mDocument;
   };
 
   // On failure, mPrincipal should be set to null in aCSSParseEnv.
   // If mPrincipal is null, the other members may not be set to
   // anything meaningful.
   virtual void GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv) = 0;
 
   // An implementation for GetCSSParsingEnvironment for callers wrapping