Bug 773296 - Part 13: Parse properties that use variable references and store them as eCSSUnit_TokenStream values. r=dbaron
authorCameron McCormack <cam@mcc.id.au>
Thu, 12 Dec 2013 13:09:43 +1100
changeset 160107 27256a46adbbec1f866c38502a426bd80bc40f1d
parent 160106 88fd50578a763adeaefbf1214e3e4c5cf75598ac
child 160108 39833de60ed769bedb8bd05ae272059ebeb8488c
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 13: Parse properties that use variable references and store them as eCSSUnit_TokenStream values. r=dbaron This adds functionality to nsCSSParser to recognise properties with variable references and store their recorded token stream as an eCSSUnit_TokenStream nsCSSValue.
layout/style/nsCSSParser.cpp
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -5475,30 +5475,31 @@ CSSParserImpl::ParseDeclaration(css::Dec
   if (customProperty) {
     if (!ParseVariableDeclaration(&variableType, variableValue)) {
       REPORT_UNEXPECTED_P(PEValueParsingError, propertyName);
       REPORT_UNEXPECTED(PEDeclDropped);
       OUTPUT_ERROR();
       return false;
     }
   } else {
-    // Map property name to its ID and then parse the property
+    // Map property name to its ID.
     propID = nsCSSProps::LookupProperty(propertyName, nsCSSProps::eEnabled);
     if (eCSSProperty_UNKNOWN == propID ||
         (aContext == eCSSContext_Page &&
          !nsCSSProps::PropHasFlags(propID,
                                    CSS_PROPERTY_APPLIES_TO_PAGE_RULE))) { // unknown property
       if (!NonMozillaVendorIdentifier(propertyName)) {
         REPORT_UNEXPECTED_P(PEUnknownProperty, propertyName);
         REPORT_UNEXPECTED(PEDeclDropped);
         OUTPUT_ERROR();
       }
       return false;
     }
-    if (! ParseProperty(propID)) {
+    // Then parse the property.
+    if (!ParseProperty(propID)) {
       // XXX Much better to put stuff in the value parsers instead...
       REPORT_UNEXPECTED_P(PEValueParsingError, propertyName);
       REPORT_UNEXPECTED(PEDeclDropped);
       OUTPUT_ERROR();
       mTempData.ClearProperty(propID);
       mTempData.AssertInitialState();
       return false;
     }
@@ -7202,22 +7203,31 @@ CSSParserImpl::ParseProperty(nsCSSProper
 
   if (mNavQuirkMode) {
     mHashlessColorQuirk =
       nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_HASHLESS_COLOR_QUIRK);
     mUnitlessLengthQuirk =
       nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_UNITLESS_LENGTH_QUIRK);
   }
 
+  // Save the current input state so that we can restore it later if we
+  // have to re-parse the property value as a variable-reference-containing
+  // token stream.
+  CSSParserInputState stateBeforeProperty;
+  SaveInputState(stateBeforeProperty);
+  mScanner->ClearSeenVariableReference();
+
   NS_ASSERTION(aPropID < eCSSProperty_COUNT, "index out of range");
+  bool allowVariables = true;
   bool result;
   switch (nsCSSProps::PropertyParseType(aPropID)) {
     case CSS_PROPERTY_PARSE_INACCESSIBLE: {
       // The user can't use these
       REPORT_UNEXPECTED(PEInaccessibleProperty2);
+      allowVariables = false;
       result = false;
       break;
     }
     case CSS_PROPERTY_PARSE_FUNCTION: {
       result = ParsePropertyByFunction(aPropID);
       break;
     }
     case CSS_PROPERTY_PARSE_VALUE: {
@@ -7234,23 +7244,141 @@ CSSParserImpl::ParseProperty(nsCSSProper
       break;
     }
     case CSS_PROPERTY_PARSE_VALUE_LIST: {
       result = ParseValueList(aPropID);
       break;
     }
     default: {
       result = false;
+      allowVariables = false;
       NS_ABORT_IF_FALSE(false,
                         "Property's flags field in nsCSSPropList.h is missing "
                         "one of the CSS_PROPERTY_PARSE_* constants");
       break;
     }
   }
 
+  bool seenVariable = mScanner->SeenVariableReference() ||
+    (stateBeforeProperty.mHavePushBack &&
+     stateBeforeProperty.mToken.mType == eCSSToken_Function &&
+     stateBeforeProperty.mToken.mIdent.LowerCaseEqualsLiteral("var"));
+  bool parseAsTokenStream;
+
+  if (!result && allowVariables) {
+    parseAsTokenStream = true;
+    if (!seenVariable) {
+      // We might have stopped parsing the property before its end and before
+      // finding a variable reference.  Keep checking until the end of the
+      // property.
+      CSSParserInputState stateAtError;
+      SaveInputState(stateAtError);
+
+      const PRUnichar stopChars[] = { ';', '!', '}', ')', 0 };
+      SkipUntilOneOf(stopChars);
+      UngetToken();
+      parseAsTokenStream = mScanner->SeenVariableReference();
+
+      if (!parseAsTokenStream) {
+        // If we parsed to the end of the propery and didn't find any variable
+        // references, then the real position we want to report the error at
+        // is |stateAtError|.
+        RestoreSavedInputState(stateAtError);
+      }
+    }
+  } else {
+    parseAsTokenStream = false;
+  }
+
+  if (parseAsTokenStream) {
+    // Go back to the start of the property value and parse it to make sure
+    // its variable references are syntactically valid and is otherwise
+    // balanced.
+    RestoreSavedInputState(stateBeforeProperty);
+
+    if (!mInSupportsCondition) {
+      mScanner->StartRecording();
+    }
+
+    CSSVariableDeclarations::Type type;
+    bool dropBackslash;
+    nsString impliedCharacters;
+    nsCSSValue value;
+    if (ParseValueWithVariables(&type, &dropBackslash, impliedCharacters,
+                                nullptr, nullptr)) {
+      MOZ_ASSERT(type == CSSVariableDeclarations::eTokenStream,
+                 "a non-custom property reparsed since it contained variable "
+                 "references should not have been 'initial' or 'inherit'");
+
+      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) {
+          MOZ_ASSERT(!propertyValue.IsEmpty() &&
+                     propertyValue[propertyValue.Length() - 1] == '\\');
+          propertyValue.Truncate(propertyValue.Length() - 1);
+        }
+        propertyValue.Append(impliedCharacters);
+      }
+
+      if (mHavePushBack) {
+        // If we came to the end of a property value that had a variable
+        // reference and a token was pushed back, then it would have been
+        // ended by '!', ')', ';', ']' or '}'.  We should remove it from the
+        // recorded property value.
+        MOZ_ASSERT(mToken.IsSymbol('!') ||
+                   mToken.IsSymbol(')') ||
+                   mToken.IsSymbol(';') ||
+                   mToken.IsSymbol(']') ||
+                   mToken.IsSymbol('}'));
+        if (!mInSupportsCondition) {
+          MOZ_ASSERT(!propertyValue.IsEmpty());
+          MOZ_ASSERT(propertyValue[propertyValue.Length() - 1] ==
+                     mToken.mSymbol);
+          propertyValue.Truncate(propertyValue.Length() - 1);
+        }
+      }
+
+      if (!mInSupportsCondition) {
+        if (nsCSSProps::IsShorthand(aPropID)) {
+          // If this is a shorthand property, we store the token stream on each
+          // of its corresponding longhand properties.
+          CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aPropID) {
+            nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream;
+            tokenStream->mPropertyID = *p;
+            tokenStream->mShorthandPropertyID = aPropID;
+            tokenStream->mTokenStream = propertyValue;
+            tokenStream->mBaseURI = mBaseURI;
+            tokenStream->mSheetURI = mSheetURI;
+            tokenStream->mSheetPrincipal = mSheetPrincipal;
+            value.SetTokenStreamValue(tokenStream);
+            AppendValue(*p, value);
+          }
+        } else {
+          nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream;
+          tokenStream->mPropertyID = aPropID;
+          tokenStream->mTokenStream = propertyValue;
+          tokenStream->mBaseURI = mBaseURI;
+          tokenStream->mSheetURI = mSheetURI;
+          tokenStream->mSheetPrincipal = mSheetPrincipal;
+          value.SetTokenStreamValue(tokenStream);
+          AppendValue(aPropID, value);
+        }
+      }
+      result = true;
+    } else {
+      if (!mInSupportsCondition) {
+        mScanner->StopRecording();
+      }
+    }
+  }
+
   if (mNavQuirkMode) {
     mHashlessColorQuirk = false;
     mUnitlessLengthQuirk = false;
   }
 
   return result;
 }