From 7eb5d97a49f812f70ec7a513642d4e3db7fdc8f6 Mon Sep 17 00:00:00 2001 From: Cameron McCormack Date: Thu, 12 Dec 2013 13:09:44 +1100 Subject: [PATCH] Bug 773296 - Part 17: Resolve property values that have variable references at computed value time. r=dbaron This re-parses property values at computed value time if they had a specified value that was a token stream. We add a function nsRuleNode::ResolveVariableReferences that looks at all the values in the nsRuleData and calls in to a new nsCSSParser::ParsePropertyWithVariableReferences function if they have a token stream value. We add a nsCSSExpandedDataBlock::MapRuleInfoInto function that will take the re-parsed property value and copy it back into the nsRuleData. nsRuleNode::ResolveVariableReferences returns whether any variables were attempted to be resolved, so that nsRuleNode::WalkRuleTree wil recompute the rule detail in case any became 'inherit'. --- .../en-US/chrome/layout/css.properties | 3 + layout/style/nsCSSDataBlock.cpp | 16 +++ layout/style/nsCSSDataBlock.h | 14 ++ layout/style/nsCSSParser.cpp | 128 ++++++++++++++++++ layout/style/nsCSSParser.h | 23 ++++ layout/style/nsRuleNode.cpp | 60 +++++++- layout/style/nsRuleNode.h | 12 ++ 7 files changed, 255 insertions(+), 1 deletion(-) diff --git a/dom/locales/en-US/chrome/layout/css.properties b/dom/locales/en-US/chrome/layout/css.properties index aa50fe0a8eb6..3d546b49bf6a 100644 --- a/dom/locales/en-US/chrome/layout/css.properties +++ b/dom/locales/en-US/chrome/layout/css.properties @@ -146,3 +146,6 @@ PEExpectedNonnegativeNP=Expected non-negative number or percentage. PEFilterFunctionArgumentsParsingError=Error in parsing arguments for filter function. PEVariableEOF=variable PEVariableEmpty=Expected variable value but found '%1$S'. +PEValueWithVariablesParsingError=Error in parsing value for '%1$S' after substituting variables. +PEValueWithVariablesFallbackInherit=Falling back to 'inherit'. +PEValueWithVariablesFallbackInitial=Falling back to 'initial'. diff --git a/layout/style/nsCSSDataBlock.cpp b/layout/style/nsCSSDataBlock.cpp index 2dd9cb1caacf..a40628791268 100644 --- a/layout/style/nsCSSDataBlock.cpp +++ b/layout/style/nsCSSDataBlock.cpp @@ -545,6 +545,22 @@ nsCSSExpandedDataBlock::DoTransferFromBlock(nsCSSExpandedDataBlock& aFromBlock, return changed; } +void +nsCSSExpandedDataBlock::MapRuleInfoInto(nsCSSProperty aPropID, + nsRuleData* aRuleData) const +{ + MOZ_ASSERT(!nsCSSProps::IsShorthand(aPropID)); + + const nsCSSValue* src = PropertyAt(aPropID); + MOZ_ASSERT(src->GetUnit() != eCSSUnit_Null); + + nsCSSValue* dest = aRuleData->ValueFor(aPropID); + MOZ_ASSERT(dest->GetUnit() == eCSSUnit_TokenStream && + dest->GetTokenStreamValue()->mPropertyID == aPropID); + + MapSinglePropertyInto(aPropID, src, dest, aRuleData); +} + #ifdef DEBUG void nsCSSExpandedDataBlock::DoAssertInitialState() diff --git a/layout/style/nsCSSDataBlock.h b/layout/style/nsCSSDataBlock.h index 42f70939e4ca..e598703cf7de 100644 --- a/layout/style/nsCSSDataBlock.h +++ b/layout/style/nsCSSDataBlock.h @@ -251,6 +251,14 @@ public: bool aMustCallValueAppended, mozilla::css::Declaration* aDeclaration); + /** + * Copies the values for aPropID into the specified aRuleData object. + * + * This is used for copying parsed-at-computed-value-time properties + * that had variable references. aPropID must be a longhand property. + */ + void MapRuleInfoInto(nsCSSProperty aPropID, nsRuleData* aRuleData) const; + void AssertInitialState() { #ifdef DEBUG DoAssertInitialState(); @@ -303,6 +311,12 @@ private: "property out of range"); return &mValues[aProperty]; } + const nsCSSValue* PropertyAt(nsCSSProperty aProperty) const { + NS_ABORT_IF_FALSE(0 <= aProperty && + aProperty < eCSSProperty_COUNT_no_shorthands, + "property out of range"); + return &mValues[aProperty]; + } void SetPropertyBit(nsCSSProperty aProperty) { mPropertiesSet.AddProperty(aProperty); diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 15342e5c9682..5128225460d1 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -42,6 +42,8 @@ #include "nsMediaFeatures.h" #include "nsLayoutUtils.h" #include "mozilla/Preferences.h" +#include "nsRuleData.h" +#include "mozilla/CSSVariableValues.h" using namespace mozilla; @@ -198,6 +200,38 @@ public: nsCSSTokenSerializationType& aFirstToken, nsCSSTokenSerializationType& aLastToken); + /** + * Parses a string as a CSS token stream value for particular property, + * resolving any variable references. The parsed property value is stored + * in the specified nsRuleData object. If aShorthandPropertyID has a value + * other than eCSSProperty_UNKNOWN, this is the property that will be parsed; + * otherwise, aPropertyID will be parsed. Either way, only aPropertyID, + * a longhand property, will be copied over to the rule data. + * + * If the property cannot be parsed, it will be treated as if 'initial' or + * 'inherit' were specified, for non-inherited and inherited properties + * respectively. + * + * @param aPropertyID The ID of the longhand property whose value is to be + * copied to the rule data. + * @param aShorthandPropertyID The ID of the shorthand property to be parsed. + * If a longhand property is to be parsed, aPropertyID is that property, + * and aShorthandPropertyID must be eCSSProperty_UNKNOWN. + * @param aValue The CSS token stream value. + * @param aVariables The set of variable values to use when resolving variable + * references. + * @param aRuleData The rule data object into which parsed property value for + * aPropertyID will be stored. + */ + void ParsePropertyWithVariableReferences(nsCSSProperty aPropertyID, + nsCSSProperty aShorthandPropertyID, + const nsAString& aValue, + const CSSVariableValues* aVariables, + nsRuleData* aRuleData, + nsIURI* aDocURL, + nsIURI* aBaseURL, + nsIPrincipal* aDocPrincipal); + protected: class nsAutoParseCompoundProperty; friend class nsAutoParseCompoundProperty; @@ -2000,6 +2034,83 @@ CSSParserImpl::ResolveVariableValue(const nsAString& aPropertyValue, return valid; } +void +CSSParserImpl::ParsePropertyWithVariableReferences( + nsCSSProperty aPropertyID, + nsCSSProperty aShorthandPropertyID, + const nsAString& aValue, + const CSSVariableValues* aVariables, + nsRuleData* aRuleData, + nsIURI* aDocURL, + nsIURI* aBaseURL, + nsIPrincipal* aDocPrincipal) +{ + mTempData.AssertInitialState(); + + bool valid; + nsString expandedValue; + + // Resolve any variable references in the property value. + { + nsCSSScanner scanner(aValue, 0); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURL); + InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal); + + nsCSSTokenSerializationType firstToken, lastToken; + valid = ResolveValueWithVariableReferences(aVariables, expandedValue, + firstToken, lastToken); + ReleaseScanner(); + } + + nsCSSProperty propertyToParse = + aShorthandPropertyID != eCSSProperty_UNKNOWN ? aShorthandPropertyID : + aPropertyID; + + // Parse the property with that resolved value. + { + nsCSSScanner scanner(expandedValue, 0); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURL); + InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal); + bool parsedOK = ParseProperty(propertyToParse); + if (parsedOK && GetToken(true)) { + REPORT_UNEXPECTED_TOKEN(PEExpectEndValue); + parsedOK = false; + } + if (!parsedOK) { + NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue( + propertyToParse)); + REPORT_UNEXPECTED_P(PEValueWithVariablesParsingError, propName); + if (nsCSSProps::IsInherited(aPropertyID)) { + REPORT_UNEXPECTED(PEValueWithVariablesFallbackInherit); + } else { + REPORT_UNEXPECTED(PEValueWithVariablesFallbackInitial); + } + OUTPUT_ERROR(); + valid = false; + } + ReleaseScanner(); + } + + // If the property could not be parsed with the resolved value, then we + // treat it as if the value were 'initial' or 'inherit', depending on whether + // the property is an inherited property. + if (!valid) { + nsCSSValue defaultValue; + if (nsCSSProps::IsInherited(aPropertyID)) { + defaultValue.SetInheritValue(); + } else { + defaultValue.SetInitialValue(); + } + mTempData.AddLonghandProperty(aPropertyID, defaultValue); + } + + // Copy the property value into the rule data. + mTempData.MapRuleInfoInto(aPropertyID, aRuleData); + + mTempData.ClearProperty(propertyToParse); + mTempData.AssertInitialState(); +} + //---------------------------------------------------------------------- bool @@ -12619,3 +12730,20 @@ nsCSSParser::ResolveVariableValue(const nsAString& aPropertyValue, ResolveVariableValue(aPropertyValue, aVariables, aResult, aFirstToken, aLastToken); } + +void +nsCSSParser::ParsePropertyWithVariableReferences( + nsCSSProperty aPropertyID, + nsCSSProperty aShorthandPropertyID, + const nsAString& aValue, + const CSSVariableValues* aVariables, + nsRuleData* aRuleData, + nsIURI* aDocURL, + nsIURI* aBaseURL, + nsIPrincipal* aDocPrincipal) +{ + static_cast(mImpl)-> + ParsePropertyWithVariableReferences(aPropertyID, aShorthandPropertyID, + aValue, aVariables, aRuleData, aDocURL, + aBaseURL, aDocPrincipal); +} diff --git a/layout/style/nsCSSParser.h b/layout/style/nsCSSParser.h index dda4745b2b13..d4a1d567c51f 100644 --- a/layout/style/nsCSSParser.h +++ b/layout/style/nsCSSParser.h @@ -23,6 +23,7 @@ struct nsCSSSelectorList; class nsMediaList; class nsCSSKeyframeRule; class nsCSSValue; +class nsRuleData; namespace mozilla { class CSSVariableValues; @@ -220,6 +221,28 @@ public: nsCSSTokenSerializationType& aFirstToken, nsCSSTokenSerializationType& aLastToken); + /** + * Parses a string as a CSS token stream value for particular property, + * resolving any variable references. The parsed property value is stored + * in the specified nsRuleData object. If aShorthandPropertyID has a value + * other than eCSSProperty_UNKNOWN, this is the property that will be parsed; + * otherwise, aPropertyID will be parsed. Either way, only aPropertyID, + * a longhand property, will be copied over to the rule data. + * + * If the property cannot be parsed, it will be treated as if 'initial' or + * 'inherit' were specified, for non-inherited and inherited properties + * respectively. + */ + void ParsePropertyWithVariableReferences( + nsCSSProperty aPropertyID, + nsCSSProperty aShorthandPropertyID, + const nsAString& aValue, + const mozilla::CSSVariableValues* aVariables, + nsRuleData* aRuleData, + nsIURI* aDocURL, + nsIURI* aBaseURL, + nsIPrincipal* aDocPrincipal); + protected: // This is a CSSParserImpl*, but if we expose that type name in this // header, we can't put the type definition (in nsCSSParser.cpp) in diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index 13c0dbbe7b6c..92d202dbf732 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -44,6 +44,7 @@ #include "nsIDocument.h" #include "prtime.h" #include "CSSVariableResolver.h" +#include "nsCSSParser.h" #if defined(_MSC_VER) || defined(__MINGW32__) #include @@ -2021,6 +2022,44 @@ private: size_t mCount; }; +/* static */ bool +nsRuleNode::ResolveVariableReferences(const nsStyleStructID aSID, + nsRuleData* aRuleData, + nsStyleContext* aContext) +{ + MOZ_ASSERT(aSID != eStyleStruct_Variables); + MOZ_ASSERT(aRuleData->mSIDs & nsCachedStyleData::GetBitForSID(aSID)); + MOZ_ASSERT(aRuleData->mValueOffsets[aSID] == 0); + + nsCSSParser parser; + bool anyTokenStreams = false; + + // Look at each property in the nsRuleData for the given style struct. + size_t nprops = nsCSSProps::PropertyCountInStruct(aSID); + for (nsCSSValue* value = aRuleData->mValueStorage, + *values_end = aRuleData->mValueStorage + nprops; + value != values_end; value++) { + if (value->GetUnit() != eCSSUnit_TokenStream) { + continue; + } + + const CSSVariableValues* variables = + &aContext->StyleVariables()->mVariables; + nsCSSValueTokenStream* tokenStream = value->GetTokenStreamValue(); + + parser.ParsePropertyWithVariableReferences( + tokenStream->mPropertyID, tokenStream->mShorthandPropertyID, + tokenStream->mTokenStream, variables, aRuleData, + tokenStream->mSheetURI, tokenStream->mBaseURI, + tokenStream->mSheetPrincipal); + + aRuleData->mCanStoreInRuleTree = false; + anyTokenStreams = true; + } + + return anyTokenStreams; +} + const void* nsRuleNode::WalkRuleTree(const nsStyleStructID aSID, nsStyleContext* aContext) @@ -2107,6 +2146,19 @@ nsRuleNode::WalkRuleTree(const nsStyleStructID aSID, ruleNode = ruleNode->mParent; } + bool recomputeDetail = false; + + // If we are computing a style struct other than nsStyleVariables, and + // ruleData has any properties with variable references (nsCSSValues of + // type eCSSUnit_TokenStream), then we need to resolve these. + if (aSID != eStyleStruct_Variables) { + // A property's value might have became 'inherit' after resolving + // variable references. (This happens when an inherited property + // fails to parse its resolved value.) We need to recompute + // |detail| in case this happened. + recomputeDetail = ResolveVariableReferences(aSID, &ruleData, aContext); + } + // If needed, unset the properties that don't have a flag that allows // them to be set for this style context. (For example, only some // properties apply to :first-line and :first-letter.) @@ -2114,9 +2166,13 @@ nsRuleNode::WalkRuleTree(const nsStyleStructID aSID, if (pseudoRestriction) { UnsetPropertiesWithoutFlags(aSID, &ruleData, pseudoRestriction); - // Recompute |detail| based on the restrictions we just applied. + // We need to recompute |detail| based on the restrictions we just applied. // We can adjust |detail| arbitrarily because of the restriction // rule added in nsStyleSet::WalkRestrictionRule. + recomputeDetail = true; + } + + if (recomputeDetail) { detail = CheckSpecifiedProperties(aSID, &ruleData); } @@ -3733,6 +3789,8 @@ nsRuleNode::SetGenericFont(nsPresContext* aPresContext, if (i != 0) ruleData.ValueForFontFamily()->Reset(); + ResolveVariableReferences(eStyleStruct_Font, &ruleData, aContext); + nsRuleNode::SetFont(aPresContext, context, aGenericFontID, &ruleData, &parentFont, aFont, false, dummy); diff --git a/layout/style/nsRuleNode.h b/layout/style/nsRuleNode.h index 1d6a4054140b..d507e8640a0b 100644 --- a/layout/style/nsRuleNode.h +++ b/layout/style/nsRuleNode.h @@ -423,6 +423,18 @@ protected: const void* SetDefaultOnRoot(const nsStyleStructID aSID, nsStyleContext* aContext); + /** + * Resolves any property values in aRuleData for a given style struct that + * have eCSSUnit_TokenStream values, by resolving them against the computed + * variable values on the style context and re-parsing the property. + * + * @return Whether any properties with eCSSUnit_TokenStream values were + * encountered. + */ + static bool ResolveVariableReferences(const nsStyleStructID aSID, + nsRuleData* aRuleData, + nsStyleContext* aContext); + const void* WalkRuleTree(const nsStyleStructID aSID, nsStyleContext* aContext);