Fix issues with parsing of media query lists that have bad queries in them. (Bug 454226) r+sr=bzbarsky
authorL. David Baron <dbaron@dbaron.org>
Sat, 11 Oct 2008 20:49:42 -0400
changeset 20327 53740a23fe93440527a76b8671c8a0d5162f12bb
parent 20326 c7b659c2efbab2d21801d120d81dc0f43a975c16
child 20328 d7338fec726672f07714c7bfc57c765961a4d984
push id2807
push userdbaron@mozilla.com
push dateSun, 12 Oct 2008 00:50:38 +0000
treeherdermozilla-central@53740a23fe93 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs454226
milestone1.9.1b2pre
Fix issues with parsing of media query lists that have bad queries in them. (Bug 454226) r+sr=bzbarsky
layout/style/nsCSSParser.cpp
layout/style/test/test_acid3_test46.html
layout/style/test/test_media_queries.html
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -281,19 +281,20 @@ protected:
   PRBool PushGroup(nsICSSGroupRule* aRule);
   void PopGroup(void);
 
   PRBool ParseRuleSet(RuleAppendFunc aAppendFunc, void* aProcessData);
   PRBool ParseAtRule(RuleAppendFunc aAppendFunc, void* aProcessData);
   PRBool ParseCharsetRule(RuleAppendFunc aAppendFunc, void* aProcessData);
   PRBool ParseImportRule(RuleAppendFunc aAppendFunc, void* aProcessData);
   PRBool GatherURL(nsString& aURL);
-  // Callers must clear or throw out aMedia if GatherMedia returns false.
   PRBool GatherMedia(nsMediaList* aMedia,
                      PRUnichar aStopSymbol);
+  PRBool ParseMediaQuery(PRUnichar aStopSymbol, nsMediaQuery **aQuery,
+                         PRBool *aParsedSomething, PRBool *aHitStop);
   PRBool ParseMediaQueryExpression(nsMediaQuery* aQuery);
   PRBool ProcessImport(const nsString& aURLSpec,
                        nsMediaList* aMedia,
                        RuleAppendFunc aAppendFunc,
                        void* aProcessData);
   PRBool ParseGroupRule(nsICSSGroupRule* aRule, RuleAppendFunc aAppendFunc,
                         void* aProcessData);
   PRBool ParseMediaRule(RuleAppendFunc aAppendFunc, void* aProcessData);
@@ -1459,141 +1460,162 @@ CSSParserImpl::GatherURL(nsString& aURL)
     aURL = mToken.mIdent;
     if (ExpectSymbol(')', PR_TRUE)) {
       return PR_TRUE;
     }
   }
   return PR_FALSE;
 }
 
-// Callers must clear or throw out aMedia if GatherMedia returns false.
 PRBool
-CSSParserImpl::GatherMedia(nsMediaList* aMedia,
-                           PRUnichar aStopSymbol)
-{
+CSSParserImpl::ParseMediaQuery(PRUnichar aStopSymbol,
+                               nsMediaQuery **aQuery,
+                               PRBool *aParsedSomething,
+                               PRBool *aHitStop)
+{
+  *aQuery = nsnull;
+  *aParsedSomething = PR_FALSE;
+  *aHitStop = PR_FALSE;
+
   // "If the comma-separated list is the empty list it is assumed to
   // specify the media query 'all'."  (css3-mediaqueries, section
   // "Media Queries")
   if (!GetToken(PR_TRUE)) {
+    *aHitStop = PR_TRUE;
     // expected termination by EOF
     if (aStopSymbol == PRUnichar(0))
       return PR_TRUE;
 
-    // unexpected termination by EOF; if we were looking for a
-    // semicolon, return true anyway, for the same reason this is
-    // done by ExpectSymbol().
+    // unexpected termination by EOF
     REPORT_UNEXPECTED_EOF(PEGatherMediaEOF);
-    return aStopSymbol == PRUnichar(';');
+    return PR_TRUE;
   }
 
   if (eCSSToken_Symbol == mToken.mType &&
       mToken.mSymbol == aStopSymbol) {
+    *aHitStop = PR_TRUE;
     UngetToken();
     return PR_TRUE;
   }
   UngetToken();
-  aMedia->SetNonEmpty();
+
+  *aParsedSomething = PR_TRUE;
+
+  nsAutoPtr<nsMediaQuery> query(new nsMediaQuery);
+  if (!query) {
+    mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
+    return PR_FALSE;
+  }
+
+  if (ExpectSymbol('(', PR_TRUE)) {
+    // we got an expression without a media type
+    UngetToken(); // so ParseMediaQueryExpression can handle it
+    query->SetType(nsGkAtoms::all);
+    query->SetTypeOmitted();
+    // Just parse the first expression here.
+    if (!ParseMediaQueryExpression(query)) {
+      OUTPUT_ERROR();
+      query->SetHadUnknownExpression();
+    }
+  } else {
+    nsCOMPtr<nsIAtom> mediaType;
+    PRBool gotNotOrOnly = PR_FALSE;
+    for (;;) {
+      if (!GetToken(PR_TRUE)) {
+        REPORT_UNEXPECTED_EOF(PEGatherMediaEOF);
+        return PR_FALSE;
+      }
+      if (eCSSToken_Ident != mToken.mType) {
+        REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotIdent);
+        UngetToken();
+        return PR_FALSE;
+      }
+      // case insensitive from CSS - must be lower cased
+      ToLowerCase(mToken.mIdent);
+      mediaType = do_GetAtom(mToken.mIdent);
+      if (gotNotOrOnly ||
+          (mediaType != nsGkAtoms::_not && mediaType != nsGkAtoms::only))
+        break;
+      gotNotOrOnly = PR_TRUE;
+      if (mediaType == nsGkAtoms::_not)
+        query->SetNegated();
+      else
+        query->SetHasOnly();
+    }
+    query->SetType(mediaType);
+  }
 
   for (;;) {
-    // We want to still have |query| after we transfer ownership from
-    // |queryHolder| to |aMedia|.
-    nsMediaQuery *query;
-    {
-      nsAutoPtr<nsMediaQuery> queryHolder(new nsMediaQuery);
-      if (!queryHolder) {
-        mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
+    if (!GetToken(PR_TRUE)) {
+      *aHitStop = PR_TRUE;
+      // expected termination by EOF
+      if (aStopSymbol == PRUnichar(0))
+        break;
+
+      // unexpected termination by EOF
+      REPORT_UNEXPECTED_EOF(PEGatherMediaEOF);
+      break;
+    }
+
+    if (eCSSToken_Symbol == mToken.mType &&
+        mToken.mSymbol == aStopSymbol) {
+      *aHitStop = PR_TRUE;
+      UngetToken();
+      break;
+    }
+    if (eCSSToken_Symbol == mToken.mType && mToken.mSymbol == ',') {
+      // Done with the expressions for this query
+      break;
+    }
+    if (eCSSToken_Ident != mToken.mType ||
+        !mToken.mIdent.LowerCaseEqualsLiteral("and")) {
+      REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotComma);
+      UngetToken();
+      return PR_FALSE;
+    }
+    if (!ParseMediaQueryExpression(query)) {
+      OUTPUT_ERROR();
+      query->SetHadUnknownExpression();
+    }
+  }
+  *aQuery = query.forget();
+  return PR_TRUE;
+}
+
+// Returns false only when there is a low-level error in the scanner
+// (out-of-memory).
+PRBool
+CSSParserImpl::GatherMedia(nsMediaList* aMedia,
+                           PRUnichar aStopSymbol)
+{
+  for (;;) {
+    nsAutoPtr<nsMediaQuery> query;
+    PRBool parsedSomething, hitStop;
+    if (!ParseMediaQuery(aStopSymbol, getter_Transfers(query),
+                         &parsedSomething, &hitStop)) {
+      if (NS_FAILED(mScanner.GetLowLevelError())) {
         return PR_FALSE;
       }
-      query = queryHolder;
-
-      // In terms of error handling, it doesn't really matter when we
-      // append this, since aMedia's contents get dropped entirely
-      // whenever there is an error.
-      nsresult rv = aMedia->AppendQuery(queryHolder);
+      SkipUntil(',');
+    }
+    if (parsedSomething) {
+      aMedia->SetNonEmpty();
+    }
+    if (query) {
+      nsresult rv = aMedia->AppendQuery(query);
       if (NS_FAILED(rv)) {
         mScanner.SetLowLevelError(rv);
         return PR_FALSE;
       }
-      NS_ASSERTION(!queryHolder, "ownership should have been transferred");
-    }
-
-    if (ExpectSymbol('(', PR_TRUE)) {
-      // we got an expression without a media type
-      UngetToken(); // so ParseMediaQueryExpression can handle it
-      query->SetType(nsGkAtoms::all);
-      query->SetTypeOmitted();
-      // Just parse the first expression here.
-      if (!ParseMediaQueryExpression(query)) {
-        OUTPUT_ERROR();
-        query->SetHadUnknownExpression();
-      }
-    } else {
-      nsCOMPtr<nsIAtom> mediaType;
-      PRBool gotNotOrOnly = PR_FALSE;
-      for (;;) {
-        if (!GetToken(PR_TRUE)) {
-          REPORT_UNEXPECTED_EOF(PEGatherMediaEOF);
-          return PR_FALSE;
-        }
-        if (eCSSToken_Ident != mToken.mType) {
-          REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotIdent);
-          UngetToken();
-          return PR_FALSE;
-        }
-        // case insensitive from CSS - must be lower cased
-        ToLowerCase(mToken.mIdent);
-        mediaType = do_GetAtom(mToken.mIdent);
-        if (gotNotOrOnly ||
-            (mediaType != nsGkAtoms::_not && mediaType != nsGkAtoms::only))
-          break;
-        gotNotOrOnly = PR_TRUE;
-        if (mediaType == nsGkAtoms::_not)
-          query->SetNegated();
-        else
-          query->SetHasOnly();
-      }
-      query->SetType(mediaType);
-    }
-
-    for (;;) {
-      if (!GetToken(PR_TRUE)) {
-        // expected termination by EOF
-        if (aStopSymbol == PRUnichar(0))
-          return PR_TRUE;
-
-        // unexpected termination by EOF; if we were looking for a
-        // semicolon, return true anyway, for the same reason this is
-        // done by ExpectSymbol().
-        REPORT_UNEXPECTED_EOF(PEGatherMediaEOF);
-        return aStopSymbol == PRUnichar(';');
-      }
-
-      if (eCSSToken_Symbol == mToken.mType &&
-          mToken.mSymbol == aStopSymbol) {
-        UngetToken();
-        return PR_TRUE;
-      }
-      if (eCSSToken_Symbol == mToken.mType && mToken.mSymbol == ',') {
-        // Done with the expressions for this query
-        break;
-      }
-      if (eCSSToken_Ident != mToken.mType ||
-          !mToken.mIdent.LowerCaseEqualsLiteral("and")) {
-        REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotComma);
-        UngetToken();
-        return PR_FALSE;
-      }
-      if (!ParseMediaQueryExpression(query)) {
-        OUTPUT_ERROR();
-        query->SetHadUnknownExpression();
-      }
-    }
-  }
-  NS_NOTREACHED("unreachable code");
-  return PR_FALSE; // keep the compiler happy
+    }
+    if (hitStop) {
+      break;
+    }
+  }
+  return PR_TRUE;
 }
 
 PRBool
 CSSParserImpl::ParseMediaQueryExpression(nsMediaQuery* aQuery)
 {
   if (!ExpectSymbol('(', PR_TRUE)) {
     REPORT_UNEXPECTED_TOKEN(PEMQExpectedExpressionStart);
     return PR_FALSE;
--- a/layout/style/test/test_acid3_test46.html
+++ b/layout/style/test/test_acid3_test46.html
@@ -104,17 +104,17 @@ extracted from the test framework there 
   check('l', true);
   check('m', true);
   check('n', true);
   check('o', true);
   check('p', false);
   check('q', false);
   check('r', false);
   check('s', false);
-  check('t', false); // 20
+  check('t', true); // 20 - false in old spec
   check('u', false);
   check('v', true);
   check('w', true);
   check('x', true);
   // here the viewport is 0x0
   check('y1', false); // 25
   check('y2', false);
   check('y3', false);
--- a/layout/style/test/test_media_queries.html
+++ b/layout/style/test/test_media_queries.html
@@ -416,16 +416,31 @@ function run() {
   should_not_apply("(orientation:");
   should_apply("all,(orientation:");
   should_not_apply("(orientation:,all");
   should_apply("not all and (grid");
   should_not_apply("only all and (grid");
   should_not_apply("(grid");
   should_apply("all,(grid");
   should_not_apply("(grid,all");
+  // bug 454226
+  should_apply(",all");
+  should_apply("all,");
+  should_apply(",all,");
+  should_apply("all,badmedium");
+  should_apply("badmedium,all");
+  should_not_apply(",badmedium,");
+  should_apply("all,(badexpression)");
+  should_apply("(badexpression),all");
+  should_not_apply("(badexpression),badmedium");
+  should_not_apply("badmedium,(badexpression)");
+  should_apply("all,[badsyntax]");
+  should_apply("[badsyntax],all");
+  should_not_apply("badmedium,[badsyntax]");
+  should_not_apply("[badsyntax],badmedium");
 
   SimpleTest.finish();
 }
 
 </script>
 </pre>
 </body>
 </html>