Bug 1294863 - Part 2: Make CSS variable registrations changing cause reevaluation of @supports conditions. r?heycam draft
authorJonathan Chan <jyc@eqv.io>
Mon, 15 Aug 2016 00:56:49 -0700
changeset 400620 d26f9df3c80904bb193f6a4e872337f4baf91f7c
parent 400619 f38ddb1bc25a3144e3d5191fe331038df4881b16
child 400621 786bf3376d3f7c3c9f31046f039d85f656ef7f00
push id26211
push userjchan@mozilla.com
push dateMon, 15 Aug 2016 08:07:32 +0000
reviewersheycam
bugs1294863
milestone51.0a1
Bug 1294863 - Part 2: Make CSS variable registrations changing cause reevaluation of @supports conditions. r?heycam We implement this as follows: (1) When parsing @supports conditions, we keep track of whether or not we have seen any variables. We store this in the SupportsRule. (2) In SupportsRule::UseForPresentation, if the condition didn't contain any variables, we can always just return the result of evaluating the condition at parse time. Otherwise, we reevaluate. (3) After registerProperty/unregisterProperty are called, we call nsPresContext::RefreshRuleCascade, which was added in the previous patch. (4) nsCSSRuleProcessor keeps track of the CSS variables registrations generation at which we last refreshed the rule cascade. (5) Previously, we returned early from RefreshRuleCascade if all media feature values were the same. Now we also check that the variable registrations generation is the same as the generation we stored in (4). This causes us to refresh the rule cascade either if a custom property has been registered/unregistered or if a media feature value has changed. This makes checking whether or not we should use supports rules more expensive when they contain variables, but should have no other significant performance impact. It might be possible to parse supports conditions into another format instead of just reevaluating from the string in the future. MozReview-Commit-ID: 7ZKZ2KKm1oW
layout/style/CSS.cpp
layout/style/nsCSSParser.cpp
layout/style/nsCSSRuleProcessor.cpp
layout/style/nsCSSRuleProcessor.h
layout/style/nsCSSRules.cpp
layout/style/nsCSSRules.h
--- a/layout/style/CSS.cpp
+++ b/layout/style/CSS.cpp
@@ -71,16 +71,17 @@ RebuildAllStyleData(const GlobalObject& 
   nsCOMPtr<nsIDocument> doc = win->GetDoc();
   if (!doc) {
     return NS_ERROR_FAILURE;
   }
 
   nsIPresShell* shell;
   nsPresContext* pcx;
   if ((shell = doc->GetShell()) && (pcx = shell->GetPresContext())) {
+    pcx->RefreshRuleCascade();
     pcx->RebuildAllStyleData(NS_STYLE_HINT_REFLOW, eRestyle_Subtree);
     return NS_OK;
   }
 
   return NS_ERROR_FAILURE;
 }
 
 /* static */ bool
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -703,25 +703,26 @@ protected:
                                 nsCSSValue& aValue);
 
   bool ParsePageRule(RuleAppendFunc aAppendFunc, void* aProcessData);
   bool ParseKeyframesRule(RuleAppendFunc aAppendFunc, void* aProcessData);
   already_AddRefed<nsCSSKeyframeRule> ParseKeyframeRule();
   bool ParseKeyframeSelectorList(InfallibleTArray<float>& aSelectorList);
 
   bool ParseSupportsRule(RuleAppendFunc aAppendFunc, void* aProcessData);
-  bool ParseSupportsCondition(bool& aConditionMet);
-  bool ParseSupportsConditionNegation(bool& aConditionMet);
-  bool ParseSupportsConditionInParens(bool& aConditionMet);
+  bool ParseSupportsCondition(bool& aConditionMet, bool& aHasVariables);
+  bool ParseSupportsConditionNegation(bool& aConditionMet, bool& aHasVariables);
+  bool ParseSupportsConditionInParens(bool& aConditionMet, bool& aHasVariables);
   bool ParseSupportsMozBoolPrefName(bool& aConditionMet);
-  bool ParseSupportsConditionInParensInsideParens(bool& aConditionMet);
-  bool ParseSupportsConditionTerms(bool& aConditionMet);
+  bool ParseSupportsConditionInParensInsideParens(bool& aConditionMet, bool& aHasVariables);
+  bool ParseSupportsConditionTerms(bool& aConditionMet, bool& aHasVariables);
   enum SupportsConditionTermOperator { eAnd, eOr };
   bool ParseSupportsConditionTermsAfterOperator(
                                        bool& aConditionMet,
+                                       bool& aHasVariables,
                                        SupportsConditionTermOperator aOperator);
 
   bool ParseCounterStyleRule(RuleAppendFunc aAppendFunc, void* aProcessData);
   bool ParseCounterStyleName(nsAString& aName, bool aForDefinition);
   bool ParseCounterStyleNameValue(nsCSSValue& aValue);
   bool ParseCounterDescriptor(nsCSSCounterStyleRule *aRule);
   bool ParseCounterDescriptorValue(nsCSSCounterDesc aDescID,
                                    nsCSSValue& aValue);
@@ -2458,18 +2459,19 @@ CSSParserImpl::EvaluateSupportsCondition
                                          nsIURI* aBaseURL,
                                          nsIPrincipal* aDocPrincipal)
 {
   nsCSSScanner scanner(aDeclaration, 0);
   css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURL);
   InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
   nsAutoSuppressErrors suppressErrors(this);
 
-  bool conditionMet;
-  bool parsedOK = ParseSupportsCondition(conditionMet) && !GetToken(true);
+  bool conditionMet, hasVariables;
+  bool parsedOK = ParseSupportsCondition(conditionMet, hasVariables) &&
+                  !GetToken(true);
 
   CLEAR_ERROR();
   ReleaseScanner();
 
   return parsedOK && conditionMet;
 }
 
 bool
@@ -4711,17 +4713,18 @@ CSSParserImpl::ParseSupportsRule(RuleApp
 
   mScanner->StartRecording();
 
   uint32_t linenum, colnum;
   if (!GetNextTokenLocation(true, &linenum, &colnum)) {
     return false;
   }
 
-  bool parsed = ParseSupportsCondition(conditionMet);
+  bool hasVariables = false;
+  bool parsed = ParseSupportsCondition(conditionMet, hasVariables);
 
   if (!parsed) {
     mScanner->StopRecording();
     return false;
   }
 
   if (!ExpectSymbol('{', true)) {
     REPORT_UNEXPECTED_TOKEN(PESupportsGroupRuleStart);
@@ -4740,64 +4743,67 @@ CSSParserImpl::ParseSupportsRule(RuleApp
   // Remove spaces from the start and end of the recorded supports condition.
   condition.Trim(" ", true, true, false);
 
   // Record whether we are in a failing @supports, so that property parse
   // errors don't get reported.
   nsAutoFailingSupportsRule failing(this, conditionMet);
 
   RefPtr<css::GroupRule> rule = new CSSSupportsRule(conditionMet, condition,
-                                                      linenum, colnum);
+                                                    hasVariables, linenum,
+                                                    colnum);
   return ParseGroupRule(rule, aAppendFunc, aProcessData);
 }
 
 // supports_condition
 //   : supports_condition_in_parens supports_condition_terms
 //   | supports_condition_negation
 //   ;
 bool
-CSSParserImpl::ParseSupportsCondition(bool& aConditionMet)
+CSSParserImpl::ParseSupportsCondition(bool& aConditionMet, bool& aHasVariables)
 {
   nsAutoInSupportsCondition aisc(this);
 
   if (!GetToken(true)) {
     REPORT_UNEXPECTED_EOF(PESupportsConditionStartEOF2);
     return false;
   }
 
   UngetToken();
 
   mScanner->ClearSeenBadToken();
 
   if (mToken.IsSymbol('(') ||
       mToken.mType == eCSSToken_Function ||
       mToken.mType == eCSSToken_URL ||
       mToken.mType == eCSSToken_Bad_URL) {
-    bool result = ParseSupportsConditionInParens(aConditionMet) &&
-                  ParseSupportsConditionTerms(aConditionMet) &&
-                  !mScanner->SeenBadToken();
+    bool result =
+      ParseSupportsConditionInParens(aConditionMet, aHasVariables) &&
+      ParseSupportsConditionTerms(aConditionMet, aHasVariables) &&
+      !mScanner->SeenBadToken();
     return result;
   }
 
   if (mToken.mType == eCSSToken_Ident &&
       mToken.mIdent.LowerCaseEqualsLiteral("not")) {
-    bool result = ParseSupportsConditionNegation(aConditionMet) &&
+    bool result = ParseSupportsConditionNegation(aConditionMet, aHasVariables) &&
                   !mScanner->SeenBadToken();
     return result;
   }
 
   REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedStart);
   return false;
 }
 
 // supports_condition_negation
 //   : 'not' S+ supports_condition_in_parens
 //   ;
 bool
-CSSParserImpl::ParseSupportsConditionNegation(bool& aConditionMet)
+CSSParserImpl::ParseSupportsConditionNegation(bool& aConditionMet,
+                                              bool& aHasVariables)
 {
   if (!GetToken(true)) {
     REPORT_UNEXPECTED_EOF(PESupportsConditionNotEOF);
     return false;
   }
 
   if (mToken.mType != eCSSToken_Ident ||
       !mToken.mIdent.LowerCaseEqualsLiteral("not")) {
@@ -4805,31 +4811,32 @@ CSSParserImpl::ParseSupportsConditionNeg
     return false;
   }
 
   if (!RequireWhitespace()) {
     REPORT_UNEXPECTED(PESupportsWhitespaceRequired);
     return false;
   }
 
-  if (ParseSupportsConditionInParens(aConditionMet)) {
+  if (ParseSupportsConditionInParens(aConditionMet, aHasVariables)) {
     aConditionMet = !aConditionMet;
     return true;
   }
 
   return false;
 }
 
 // supports_condition_in_parens
 //   : '(' S* supports_condition_in_parens_inside_parens ')' S*
 //   | supports_condition_pref
 //   | general_enclosed
 //   ;
 bool
-CSSParserImpl::ParseSupportsConditionInParens(bool& aConditionMet)
+CSSParserImpl::ParseSupportsConditionInParens(bool& aConditionMet,
+                                              bool& aHasVariables)
 {
   if (!GetToken(true)) {
     REPORT_UNEXPECTED_EOF(PESupportsConditionInParensStartEOF);
     return false;
   }
 
   if (mToken.mType == eCSSToken_URL) {
     aConditionMet = false;
@@ -4853,17 +4860,18 @@ CSSParserImpl::ParseSupportsConditionInP
   }
 
   if (!mToken.IsSymbol('(')) {
     REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedOpenParenOrFunction);
     UngetToken();
     return false;
   }
 
-  if (!ParseSupportsConditionInParensInsideParens(aConditionMet)) {
+  if (!ParseSupportsConditionInParensInsideParens(aConditionMet,
+                                                  aHasVariables)) {
     if (!SkipUntil(')')) {
       REPORT_UNEXPECTED_EOF(PESupportsConditionInParensEOF);
       return false;
     }
     aConditionMet = false;
     return true;
   }
 
@@ -4903,17 +4911,18 @@ CSSParserImpl::ParseSupportsMozBoolPrefN
 }
 
 // supports_condition_in_parens_inside_parens
 //   : core_declaration
 //   | supports_condition_negation
 //   | supports_condition_in_parens supports_condition_terms
 //   ;
 bool
-CSSParserImpl::ParseSupportsConditionInParensInsideParens(bool& aConditionMet)
+CSSParserImpl::ParseSupportsConditionInParensInsideParens(bool& aConditionMet,
+                                                          bool& aHasVariables)
 {
   if (!GetToken(true)) {
     return false;
   }
 
   if (mToken.mType == eCSSToken_Ident) {
     if (!mToken.mIdent.LowerCaseEqualsLiteral("not")) {
       nsAutoString propertyName = mToken.mIdent;
@@ -4932,16 +4941,17 @@ CSSParserImpl::ParseSupportsConditionInP
         UngetToken();
       } else if (propID == eCSSPropertyExtra_variable) {
         if (ExpectSymbol(')', false)) {
           UngetToken();
           return false;
         }
 
         aConditionMet = SupportsVariableDeclaration(propertyName, false);
+        aHasVariables = true;
 
         if (!aConditionMet) {
           SkipUntil(')');
           UngetToken();
         }
       } else {
         if (ExpectSymbol(')', true)) {
           UngetToken();
@@ -4955,70 +4965,74 @@ CSSParserImpl::ParseSupportsConditionInP
         }
         mTempData.ClearProperty(propID);
         mTempData.AssertInitialState();
       }
       return true;
     }
 
     UngetToken();
-    return ParseSupportsConditionNegation(aConditionMet);
+    return ParseSupportsConditionNegation(aConditionMet, aHasVariables);
   }
 
   UngetToken();
-  return ParseSupportsConditionInParens(aConditionMet) &&
-         ParseSupportsConditionTerms(aConditionMet);
+  return ParseSupportsConditionInParens(aConditionMet, aHasVariables) &&
+         ParseSupportsConditionTerms(aConditionMet, aHasVariables);
 }
 
 // supports_condition_terms
 //   : S+ 'and' supports_condition_terms_after_operator('and')
 //   | S+ 'or' supports_condition_terms_after_operator('or')
 //   |
 //   ;
 bool
-CSSParserImpl::ParseSupportsConditionTerms(bool& aConditionMet)
+CSSParserImpl::ParseSupportsConditionTerms(bool& aConditionMet,
+                                           bool& aHasVariables)
 {
   if (!RequireWhitespace() || !GetToken(false)) {
     return true;
   }
 
   if (mToken.mType != eCSSToken_Ident) {
     UngetToken();
     return true;
   }
 
   if (mToken.mIdent.LowerCaseEqualsLiteral("and")) {
-    return ParseSupportsConditionTermsAfterOperator(aConditionMet, eAnd);
+    return ParseSupportsConditionTermsAfterOperator(aConditionMet,
+                                                    aHasVariables, eAnd);
   }
 
   if (mToken.mIdent.LowerCaseEqualsLiteral("or")) {
-    return ParseSupportsConditionTermsAfterOperator(aConditionMet, eOr);
+    return ParseSupportsConditionTermsAfterOperator(aConditionMet,
+                                                    aHasVariables, eOr);
   }
 
   UngetToken();
   return true;
 }
 
 // supports_condition_terms_after_operator(operator)
 //   : S+ supports_condition_in_parens ( <operator> supports_condition_in_parens )*
 //   ;
 bool
 CSSParserImpl::ParseSupportsConditionTermsAfterOperator(
                          bool& aConditionMet,
+                         bool& aHasVariables,
                          CSSParserImpl::SupportsConditionTermOperator aOperator)
 {
   if (!RequireWhitespace()) {
     REPORT_UNEXPECTED(PESupportsWhitespaceRequired);
     return false;
   }
 
   const char* token = aOperator == eAnd ? "and" : "or";
   for (;;) {
     bool termConditionMet = false;
-    if (!ParseSupportsConditionInParens(termConditionMet)) {
+    if (!ParseSupportsConditionInParens(termConditionMet, aHasVariables)) {
       return false;
     }
     aConditionMet = aOperator == eAnd ? aConditionMet && termConditionMet :
                                         aConditionMet || termConditionMet;
 
     if (!GetToken(true)) {
       return true;
     }
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -45,16 +45,17 @@
 #include "nsTArray.h"
 #include "nsContentUtils.h"
 #include "nsIMediaList.h"
 #include "nsCSSRules.h"
 #include "nsStyleSet.h"
 #include "mozilla/dom/Element.h"
 #include "nsNthIndexCache.h"
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/CSSVariableRegistrations.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/Likely.h"
 #include "mozilla/TypedEnumBits.h"
 #include "RuleProcessorCache.h"
 #include "nsIDOMMutationEvent.h"
 #include "nsIMozBrowserFrame.h"
@@ -1003,16 +1004,17 @@ nsCSSRuleProcessor::nsCSSRuleProcessor(s
                        : UniquePtr<nsMediaQueryResultCacheKey>())
   , mLastPresContext(nullptr)
   , mScopeElement(aScopeElement)
   , mStyleSetRefCnt(0)
   , mSheetType(aSheetType)
   , mIsShared(aIsShared)
   , mMustGatherDocumentRules(aIsShared)
   , mInRuleProcessorCache(false)
+  , mVariablesGeneration(-1)
 #ifdef DEBUG
   , mDocumentRulesAndCacheKeyValid(false)
 #endif
 {
   NS_ASSERTION(!!mScopeElement == (aSheetType == SheetType::ScopedDoc),
                "aScopeElement must be specified iff aSheetType is "
                "eScopedDocSheet");
   for (sheet_array_type::size_type i = mSheets.Length(); i-- != 0; ) {
@@ -3765,28 +3767,49 @@ nsCSSRuleProcessor::GetRuleCascade(nsPre
 nsCSSRuleProcessor::RefreshRuleCascade(nsPresContext* aPresContext)
 {
   // Having RuleCascadeData objects be per-medium (over all variation
   // caused by media queries, handled through mCacheKey) works for now
   // since nsCSSRuleProcessor objects are per-document.  (For a given
   // set of stylesheets they can vary based on medium (@media) or
   // document (@-moz-document).)
 
-  for (RuleCascadeData **cascadep = &mRuleCascades, *cascade;
-       (cascade = *cascadep); cascadep = &cascade->mNext) {
-    if (cascade->mCacheKey.Matches(aPresContext)) {
-      // Ensure that the current one is always mRuleCascades.
-      *cascadep = cascade->mNext;
-      cascade->mNext = mRuleCascades;
-      mRuleCascades = cascade;
-
-      return;
+
+  // We don't want to refresh if everything is the same as when we last
+  // calculated the cascade.
+  // The two things (here) that might differ:
+  // (1) media feature values (in @media rule conditions)
+  // (2) CSS variable registrations (in @supports rule conditions)
+  // We keep track of (1) in the RuleCascadeData::mCacheKeys, and keep track of
+  // (2) in mVariablesGeneration.
+
+  // Check if variable registrations have changed.
+  nsIDocument* doc = aPresContext->Document();
+  const CSSVariableRegistrations* registrations =
+    CSSVariableRegistrationsOfDocument(doc);
+  MOZ_ASSERT(registrations);
+
+  if (registrations->mGeneration == mVariablesGeneration) {
+    // Check if media feature values are the same.
+    for (RuleCascadeData **cascadep = &mRuleCascades, *cascade;
+         (cascade = *cascadep); cascadep = &cascade->mNext) {
+      if (cascade->mCacheKey.Matches(aPresContext)) {
+        // Ensure that the current one is always mRuleCascades.
+        *cascadep = cascade->mNext;
+        cascade->mNext = mRuleCascades;
+        mRuleCascades = cascade;
+
+        return;
+      }
     }
   }
 
+  // Update so we don't recompute unnecessarily next time.
+  mVariablesGeneration = registrations->mGeneration;
+
   // We're going to make a new rule cascade; this means that we should
   // now stop using the previous cache key that we're holding on to from
   // the last time we had rule cascades.
   mPreviousCacheKey = nullptr;
 
   if (mSheets.Length() != 0) {
     nsAutoPtr<RuleCascadeData> newCascade(
       new RuleCascadeData(aPresContext->Medium(),
--- a/layout/style/nsCSSRuleProcessor.h
+++ b/layout/style/nsCSSRuleProcessor.h
@@ -275,16 +275,18 @@ private:
   // Whether we need to build up mDocumentCacheKey and mDocumentRules as
   // we build a RuleCascadeData.  Is true only for shared rule processors
   // and only before we build the first RuleCascadeData.  See comment in
   // RefreshRuleCascade for why.
   bool mMustGatherDocumentRules;
 
   bool mInRuleProcessorCache;
 
+  uint32_t mVariablesGeneration;
+
 #ifdef DEBUG
   bool mDocumentRulesAndCacheKeyValid;
 #endif
 
 #ifdef XP_WIN
   static uint8_t sWinThemeId;
 #endif
 };
--- a/layout/style/nsCSSRules.cpp
+++ b/layout/style/nsCSSRules.cpp
@@ -32,16 +32,17 @@
 #include "mozilla/css/Declaration.h"
 #include "nsCSSParser.h"
 #include "nsDOMClassInfoID.h"
 #include "mozilla/dom/CSSStyleDeclarationBinding.h"
 #include "StyleRule.h"
 #include "nsFont.h"
 #include "nsIURI.h"
 #include "mozAutoDocUpdate.h"
+#include "mozilla/CSSVariableRegistrations.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 #define IMPL_STYLE_RULE_INHERIT_GET_DOM_RULE_WEAK(class_, super_) \
   /* virtual */ nsIDOMCSSRule* class_::GetDOMRule()               \
   { return this; }                                                \
   /* virtual */ nsIDOMCSSRule* class_::GetExistingDOMRule()       \
@@ -2661,31 +2662,34 @@ nsCSSPageRule::SizeOfIncludingThis(Mallo
 {
   return aMallocSizeOf(this);
 }
 
 namespace mozilla {
 
 CSSSupportsRule::CSSSupportsRule(bool aConditionMet,
                                  const nsString& aCondition,
+                                 bool aHasVariables,
                                  uint32_t aLineNumber, uint32_t aColumnNumber)
   : css::GroupRule(aLineNumber, aColumnNumber)
   , mUseGroup(aConditionMet)
   , mCondition(aCondition)
+  , mHasVariables(aHasVariables)
 {
 }
 
 CSSSupportsRule::~CSSSupportsRule()
 {
 }
 
 CSSSupportsRule::CSSSupportsRule(const CSSSupportsRule& aCopy)
   : css::GroupRule(aCopy),
     mUseGroup(aCopy.mUseGroup),
-    mCondition(aCopy.mCondition)
+    mCondition(aCopy.mCondition),
+    mHasVariables(aCopy.mHasVariables)
 {
 }
 
 #ifdef DEBUG
 /* virtual */ void
 CSSSupportsRule::List(FILE* out, int32_t aIndent) const
 {
   nsAutoCString indentStr;
@@ -2712,19 +2716,29 @@ CSSSupportsRule::GetType() const
 CSSSupportsRule::Clone() const
 {
   RefPtr<css::Rule> clone = new CSSSupportsRule(*this);
   return clone.forget();
 }
 
 /* virtual */ bool
 CSSSupportsRule::UseForPresentation(nsPresContext* aPresContext,
-                                   nsMediaQueryResultCacheKey& aKey)
+                                    nsMediaQueryResultCacheKey& aKey)
 {
-  return mUseGroup;
+  if (!mHasVariables) {
+    return mUseGroup;
+  }
+
+  nsCSSParser parser;
+  nsIDocument* doc = aPresContext->Document();
+  nsIURI* docURI = doc->GetDocumentURI();
+  RefPtr<nsIURI> baseURI = doc->GetBaseURI();
+  nsIPrincipal* principal = doc->NodeInfoManager()->DocumentPrincipal();
+  parser.SetVariableRegistrations(CSSVariableRegistrationsOfDocument(doc));
+  return parser.EvaluateSupportsCondition(mCondition, docURI, baseURI, principal);
 }
 
 NS_IMPL_ADDREF_INHERITED(CSSSupportsRule, css::GroupRule)
 NS_IMPL_RELEASE_INHERITED(CSSSupportsRule, css::GroupRule)
 
 // QueryInterface implementation for CSSSupportsRule
 NS_INTERFACE_MAP_BEGIN(CSSSupportsRule)
   NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
--- a/layout/style/nsCSSRules.h
+++ b/layout/style/nsCSSRules.h
@@ -549,17 +549,18 @@ private:
 
 namespace mozilla {
 
 class CSSSupportsRule : public css::GroupRule,
                         public nsIDOMCSSSupportsRule
 {
 public:
   CSSSupportsRule(bool aConditionMet, const nsString& aCondition,
-                  uint32_t aLineNumber, uint32_t aColumnNumber);
+                  bool aHasVariables, uint32_t aLineNumber,
+                  uint32_t aColumnNumber);
   CSSSupportsRule(const CSSSupportsRule& aCopy);
 
   // Rule methods
 #ifdef DEBUG
   virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
 #endif
   virtual int32_t GetType() const override;
   virtual already_AddRefed<mozilla::css::Rule> Clone() const override;
@@ -590,16 +591,21 @@ public:
 
   virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
 
 protected:
   virtual ~CSSSupportsRule();
 
   bool mUseGroup;
   nsString mCondition;
+
+  // Keep track of whether or not mCondition contains any variable references.
+  // If it does, we will need to re-evaluate mCondition when UseForPresentation
+  // is called, as variable registrations might change.
+  bool mHasVariables;
 };
 
 } // namespace mozilla
 
 class nsCSSCounterStyleRule final : public mozilla::css::Rule,
                                     public nsIDOMCSSCounterStyleRule
 {
 public: