Bug 773296 - Part 17: Resolve property values that have variable references at computed value time. r=dbaron
authorCameron McCormack <cam@mcc.id.au>
Thu, 12 Dec 2013 13:09:44 +1100
changeset 160112 0aab83be781eca49e67e388b971ef6348a81e182
parent 160111 c576c10f4a172e56e5cb5048cc2cc14e5a93dad5
child 160113 e44cc02cb44df41932d59c3d0c0a420e85089dbb
push id3938
push usercbook@mozilla.com
push dateThu, 12 Dec 2013 15:09:56 +0000
treeherderfx-team@0cf1107e2791 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs773296
milestone29.0a1
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'.
dom/locales/en-US/chrome/layout/css.properties
layout/style/nsCSSDataBlock.cpp
layout/style/nsCSSDataBlock.h
layout/style/nsCSSParser.cpp
layout/style/nsCSSParser.h
layout/style/nsRuleNode.cpp
layout/style/nsRuleNode.h
--- a/dom/locales/en-US/chrome/layout/css.properties
+++ b/dom/locales/en-US/chrome/layout/css.properties
@@ -141,8 +141,11 @@ PESupportsConditionExpectedNot=Expected 
 PESupportsGroupRuleStart=Expected '{' to begin @supports rule but found '%1$S'.
 PEFilterEOF=filter
 PEExpectedNoneOrURL=Expected 'none' or URL but found '%1$S'.
 PEExpectedNoneOrURLOrFilterFunction=Expected 'none', URL, or filter function but found '%1$S'.
 PEExpectedNonnegativeNP=Expected non-negative number or percentage.
 PEFilterFunctionArgumentsParsingError=Error in parsing arguments for filter function.
 PEVariableEOF=variable
 PEVariableEmpty=Expected variable value but found '%1$S'.
+PEValueWithVariablesParsingError=Error in parsing value for '%1$S' after substituting variables.
+PEValueWithVariablesFallbackInherit=Falling back to 'inherit'.
+PEValueWithVariablesFallbackInitial=Falling back to 'initial'.
--- a/layout/style/nsCSSDataBlock.cpp
+++ b/layout/style/nsCSSDataBlock.cpp
@@ -540,16 +540,32 @@ nsCSSExpandedDataBlock::DoTransferFromBl
    * Save needless copying and allocation by calling the destructor in
    * the destination, copying memory directly, and then using placement
    * new.
    */
   changed |= MoveValue(aFromBlock.PropertyAt(aPropID), PropertyAt(aPropID));
   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()
 {
     mPropertiesSet.AssertIsEmpty("not initial state");
     mPropertiesImportant.AssertIsEmpty("not initial state");
 
     for (uint32_t i = 0; i < eCSSProperty_COUNT_no_shorthands; ++i) {
--- a/layout/style/nsCSSDataBlock.h
+++ b/layout/style/nsCSSDataBlock.h
@@ -246,16 +246,24 @@ public:
      */
     bool TransferFromBlock(nsCSSExpandedDataBlock& aFromBlock,
                              nsCSSProperty aPropID,
                              bool aIsImportant,
                              bool aOverrideImportant,
                              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();
 #endif
     }
 
 private:
     /**
@@ -298,16 +306,22 @@ private:
      * property |aProperty|.
      */
     nsCSSValue* PropertyAt(nsCSSProperty aProperty) {
         NS_ABORT_IF_FALSE(0 <= aProperty &&
                           aProperty < eCSSProperty_COUNT_no_shorthands,
                           "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);
     }
 
     void ClearPropertyBit(nsCSSProperty aProperty) {
         mPropertiesSet.RemoveProperty(aProperty);
     }
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -37,16 +37,18 @@
 #include "nsIPrincipal.h"
 #include "prprf.h"
 #include "nsContentUtils.h"
 #include "nsAutoPtr.h"
 #include "CSSCalc.h"
 #include "nsMediaFeatures.h"
 #include "nsLayoutUtils.h"
 #include "mozilla/Preferences.h"
+#include "nsRuleData.h"
+#include "mozilla/CSSVariableValues.h"
 
 using namespace mozilla;
 
 const uint32_t
 nsCSSProps::kParserVariantTable[eCSSProperty_COUNT_no_shorthands] = {
 #define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \
                  stylestruct_, stylestructoffset_, animtype_)                 \
   parsevariant_,
@@ -193,16 +195,48 @@ public:
    *   substitution succeeded.
    */
   bool ResolveVariableValue(const nsAString& aPropertyValue,
                             const CSSVariableValues* aVariables,
                             nsString& aResult,
                             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;
 
   class nsAutoFailingSupportsRule;
   friend class nsAutoFailingSupportsRule;
 
   class nsAutoSuppressErrors;
@@ -1995,16 +2029,93 @@ CSSParserImpl::ResolveVariableValue(cons
 
   bool valid = ResolveValueWithVariableReferences(aVariables, aResult,
                                                   aFirstToken, aLastToken);
 
   ReleaseScanner();
   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
 CSSParserImpl::GetToken(bool aSkipWS)
 {
   if (mHavePushBack) {
     mHavePushBack = false;
     if (!aSkipWS || mToken.mType != eCSSToken_Whitespace) {
@@ -12614,8 +12725,25 @@ nsCSSParser::ResolveVariableValue(const 
                                   nsString& aResult,
                                   nsCSSTokenSerializationType& aFirstToken,
                                   nsCSSTokenSerializationType& aLastToken)
 {
   return static_cast<CSSParserImpl*>(mImpl)->
     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<CSSParserImpl*>(mImpl)->
+    ParsePropertyWithVariableReferences(aPropertyID, aShorthandPropertyID,
+                                        aValue, aVariables, aRuleData, aDocURL,
+                                        aBaseURL, aDocPrincipal);
+}
--- a/layout/style/nsCSSParser.h
+++ b/layout/style/nsCSSParser.h
@@ -18,16 +18,17 @@
 
 class nsCSSStyleSheet;
 class nsIPrincipal;
 class nsIURI;
 struct nsCSSSelectorList;
 class nsMediaList;
 class nsCSSKeyframeRule;
 class nsCSSValue;
+class nsRuleData;
 
 namespace mozilla {
 class CSSVariableValues;
 namespace css {
 class Rule;
 class Declaration;
 class Loader;
 class StyleRule;
@@ -215,16 +216,38 @@ public:
    * using the values in aVariables.
    */
   bool ResolveVariableValue(const nsAString& aPropertyValue,
                             const mozilla::CSSVariableValues* aVariables,
                             nsString& aResult,
                             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
   // the anonymous namespace.
   void* mImpl;
 };
 
 #endif /* nsCSSParser_h___ */
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -39,16 +39,17 @@
 #include "nsContentUtils.h"
 #include "CSSCalc.h"
 #include "nsPrintfCString.h"
 #include "nsRenderingContext.h"
 #include "nsStyleUtil.h"
 #include "nsIDocument.h"
 #include "prtime.h"
 #include "CSSVariableResolver.h"
+#include "nsCSSParser.h"
 
 #if defined(_MSC_VER) || defined(__MINGW32__)
 #include <malloc.h>
 #ifdef _MSC_VER
 #define alloca _alloca
 #endif
 #endif
 #ifdef SOLARIS
@@ -2016,16 +2017,54 @@ struct AutoCSSValueArray {
 
   nsCSSValue* get() { return mArray; }
 
 private:
   nsCSSValue *mArray;
   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)
 {
   // use placement new[] on the result of alloca() to allocate a
   // variable-sized stack array, including execution of constructors,
   // and use an RAII class to run the destructors too.
   size_t nprops = nsCSSProps::PropertyCountInStruct(aSID);
@@ -2102,26 +2141,43 @@ nsRuleNode::WalkRuleTree(const nsStyleSt
       break; // We don't need to examine any more rules.  All properties
              // have been fully specified.
 
     // Climb up to the next rule in the tree (a less specific rule).
     rootNode = ruleNode;
     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.)
   uint32_t pseudoRestriction = GetPseudoRestriction(aContext);
   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);
   }
 
   NS_ASSERTION(!startStruct || (detail != eRuleFullReset &&
                                 detail != eRuleFullMixed &&
                                 detail != eRuleFullInherited),
                "can't have start struct and be fully specified");
 
@@ -3728,16 +3784,18 @@ nsRuleNode::SetGenericFont(nsPresContext
 
     // Compute the delta from the information that the rules specified
 
     // Avoid unnecessary operations in SetFont().  But we care if it's
     // the final value that we're computing.
     if (i != 0)
       ruleData.ValueForFontFamily()->Reset();
 
+    ResolveVariableReferences(eStyleStruct_Font, &ruleData, aContext);
+
     nsRuleNode::SetFont(aPresContext, context,
                         aGenericFontID, &ruleData, &parentFont, aFont,
                         false, dummy);
 
     parentFont = *aFont;
   }
 }
 
--- a/layout/style/nsRuleNode.h
+++ b/layout/style/nsRuleNode.h
@@ -418,16 +418,28 @@ protected:
   void DestroyInternal(nsRuleNode ***aDestroyQueueTail);
   void PropagateDependentBit(nsStyleStructID aSID, nsRuleNode* aHighestNode,
                              void* aStruct);
   void PropagateNoneBit(uint32_t aBit, nsRuleNode* aHighestNode);
 
   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);
 
   const void*
     ComputeDisplayData(void* aStartStruct,
                        const nsRuleData* aRuleData,
                        nsStyleContext* aContext, nsRuleNode* aHighestNode,
                        RuleDetail aRuleDetail,