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
--- 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: