Fix skipping of @-rules that are inside blocks to conform to new rule in CSS 2.1, which says that we must look for end-of-outer-block in addition to a semicolon or a block. (Bug 604175) r=bzbarsky
authorL. David Baron <dbaron@dbaron.org>
Thu, 03 Mar 2011 13:18:42 -0800
changeset 63565 b6a3ab183a8f5d8e2cd97aa52711a607cf1a7961
parent 63564 60cef935a89cf1533f7453207632d8b48af9d58f
child 63566 fa751027e9b8d529dd0e1b3a4f38cf2d98066f6b
push idunknown
push userunknown
push dateunknown
reviewersbzbarsky
bugs604175, 20110111
milestone2.0b13pre
Fix skipping of @-rules that are inside blocks to conform to new rule in CSS 2.1, which says that we must look for end-of-outer-block in addition to a semicolon or a block. (Bug 604175) r=bzbarsky This fixes http://test.csswg.org/suites/css2.1/20110111/html4/at-rule-013.htm Needed to help CSS 2.1 meet Proposed Recommendation entrance criteria.
layout/reftests/css-parsing/at-rule-013-ref.html
layout/reftests/css-parsing/at-rule-013.html
layout/reftests/css-parsing/reftest.list
layout/style/nsCSSParser.cpp
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-parsing/at-rule-013-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<!--
+    from the CSS 2.1 test suite,
+    http://test.csswg.org/suites/css2.1/20110111/html4/at-rule-013.htm
+
+    See ../css3-namespace/LICENSE .
+  -->
+<html>
+ <head>
+  <title>CSS Test: Ignoring at-rules inside @media blocks</title>
+  <link rel="author" title="L. David Baron" href="http://dbaron.org/">
+  <style type="text/css">
+    p {
+      color: green;
+      background: transparent;
+    }
+  </style>
+ </head>
+ <body>
+  <p>This sentence must be green.</p>
+  <p>This sentence must be green.</p>
+  <p>This sentence must be green.</p>
+  <p>This sentence must be green.</p>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-parsing/at-rule-013.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<!--
+    from the CSS 2.1 test suite,
+    http://test.csswg.org/suites/css2.1/20110111/html4/at-rule-013.htm
+
+    See ../css3-namespace/LICENSE .
+  -->
+<html>
+ <head>
+  <title>CSS Test: Ignoring at-rules inside @media blocks</title>
+  <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact">
+  <link rel="help" href="http://www.w3.org/TR/CSS21/syndata.html#parsing-errors">
+  <link rel="help" href="http://www.w3.org/TR/CSS21/media.html#at-media-rule">
+  <meta name="flags" content="invalid">
+  <meta name="assert" content="At-rules inside @media blocks are ignored up to         up to the end of the block that contains the invalid at-keyword, or up         to and including the next semicolon (;) or up to and including the next         block ({...}), whichever comes first.">
+  <style type="text/css">
+    p {
+      color: red;
+      background: red;
+    }
+    @media all {
+      #semicolon { background: transparent; }
+      @foo ] & | # $ % test-token \
+       [; # { background: red; } ]
+       (; #semicolon { background: red; } } } } )
+       '; #semicolon { background: red; } } } }',
+       "; #semicolon { background: red; }' } } }"
+      ;
+      #semicolon { color: green; }
+    }
+    @media all {
+      #block { background: transparent; }
+      @foo ] & | # $ % test-token \
+       [; #block { background: red; } ]
+       (; #block { background: red; } )
+       '; #block { background: red; }',
+       "; #block { background: red; }'"
+       {; #block { background: red; }
+          #block { background: red; } }
+      #block { color: green; }
+    }
+    @media all {
+      #eob { background: transparent; }
+      @import "support/import-red.css"
+    }
+    #eob {
+      color: green;
+    }
+    @media all {
+      #eob-complex { background: transparent; }
+      @import "support/import-red.css"
+       [; #eob-complex { background: red; } ]
+       (; #eob-complex { background: red; } )
+       '; #eob-complex { background: red; }',
+       "; #eob-complex { background: red; }'"
+    }
+    #eob-complex {
+      color: green;
+    }
+  </style>
+ </head>
+ <body>
+  <p id="semicolon">This sentence must be green.</p>
+  <p id="block">This sentence must be green.</p>
+  <p id="eob">This sentence must be green.</p>
+  <p id="eob-complex">This sentence must be green.</p>
+ </body>
+</html>
--- a/layout/reftests/css-parsing/reftest.list
+++ b/layout/reftests/css-parsing/reftest.list
@@ -1,2 +1,3 @@
+== at-rule-013.html at-rule-013-ref.html
 == invalid-url-handling.xhtml invalid-url-handling-ref.xhtml
 == pseudo-elements-1.html pseudo-elements-1-ref.html
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -305,17 +305,17 @@ protected:
 
   PRBool ExpectSymbol(PRUnichar aSymbol, PRBool aSkipWS);
   PRBool ExpectEndProperty();
   PRBool CheckEndProperty();
   nsSubstring* NextIdent();
   void SkipUntil(PRUnichar aStopSymbol);
   void SkipUntilOneOf(const PRUnichar* aStopSymbolChars);
   void SkipRuleSet(PRBool aInsideBraces);
-  PRBool SkipAtRule();
+  PRBool SkipAtRule(PRBool aInsideBlock);
   PRBool SkipDeclaration(PRBool aCheckForBraces);
 
   PRBool PushGroup(nsICSSGroupRule* aRule);
   void PopGroup(void);
 
   PRBool ParseRuleSet(RuleAppendFunc aAppendFunc, void* aProcessData,
                       PRBool aInsideBraces = PR_FALSE);
   PRBool ParseAtRule(RuleAppendFunc aAppendFunc, void* aProcessData);
@@ -1429,28 +1429,33 @@ CSSParserImpl::NextIdent()
   if (eCSSToken_Ident != mToken.mType) {
     UngetToken();
     return nsnull;
   }
   return &mToken.mIdent;
 }
 
 PRBool
-CSSParserImpl::SkipAtRule()
+CSSParserImpl::SkipAtRule(PRBool aInsideBlock)
 {
   for (;;) {
     if (!GetToken(PR_TRUE)) {
       REPORT_UNEXPECTED_EOF(PESkipAtRuleEOF);
       return PR_FALSE;
     }
     if (eCSSToken_Symbol == mToken.mType) {
       PRUnichar symbol = mToken.mSymbol;
       if (symbol == ';') {
         break;
       }
+      if (aInsideBlock && symbol == '}') {
+        // The closing } doesn't belong to us.
+        UngetToken();
+        break;
+      }
       if (symbol == '{') {
         SkipUntil('}');
         break;
       } else if (symbol == '(') {
         SkipUntil(')');
       } else if (symbol == '[') {
         SkipUntil(']');
       }
@@ -1460,16 +1465,22 @@ CSSParserImpl::SkipAtRule()
   }
   return PR_TRUE;
 }
 
 PRBool
 CSSParserImpl::ParseAtRule(RuleAppendFunc aAppendFunc,
                            void* aData)
 {
+  // If we ever allow nested at-rules, we need to be very careful about
+  // the error handling rules in the CSS spec.  In particular, we need
+  // to pass in to ParseAtRule whether we're inside a block, we need to
+  // ensure that all the individual at-rule parsing functions terminate
+  // immediately when they hit a '}', and then we need to pass whether
+  // we're inside a block to SkipAtRule below.
   nsCSSSection newSection;
   PRBool (CSSParserImpl::*parseFunc)(RuleAppendFunc, void*);
 
   if ((mSection <= eCSSSection_Charset) &&
       (mToken.mIdent.LowerCaseEqualsLiteral("charset"))) {
     parseFunc = &CSSParserImpl::ParseCharsetRule;
     newSection = eCSSSection_Import;  // only one charset allowed
 
@@ -1500,23 +1511,23 @@ CSSParserImpl::ParseAtRule(RuleAppendFun
     newSection = eCSSSection_General;
 
   } else {
     if (!NonMozillaVendorIdentifier(mToken.mIdent)) {
       REPORT_UNEXPECTED_TOKEN(PEUnknownAtRule);
       OUTPUT_ERROR();
     }
     // Skip over unsupported at rule, don't advance section
-    return SkipAtRule();
+    return SkipAtRule(PR_FALSE);
   }
 
   if (!(this->*parseFunc)(aAppendFunc, aData)) {
     // Skip over invalid at rule, don't advance section
     OUTPUT_ERROR();
-    return SkipAtRule();
+    return SkipAtRule(PR_FALSE);
   }
 
   mSection = newSection;
   return PR_TRUE;
 }
 
 PRBool
 CSSParserImpl::ParseCharsetRule(RuleAppendFunc aAppendFunc,
@@ -1967,17 +1978,17 @@ CSSParserImpl::ParseGroupRule(nsICSSGrou
       REPORT_UNEXPECTED_EOF(PEGroupRuleEOF);
       break;
     }
     if (mToken.IsSymbol('}')) { // done!
       UngetToken();
       break;
     }
     if (eCSSToken_AtKeyword == mToken.mType) {
-      SkipAtRule(); // group rules cannot contain @rules
+      SkipAtRule(PR_TRUE); // group rules cannot contain @rules
       continue;
     }
     UngetToken();
     ParseRuleSet(AppendRuleToSheet, this, PR_TRUE);
   }
   PopGroup();
 
   if (!ExpectSymbol('}', PR_TRUE)) {