Bug 1364009 - Don't allow comments/spaces between signs,numbers,and `n` in an+b syntax for nth-child; r=dbaron
authorManish Goregaokar <manishearth@gmail.com>
Thu, 01 Jun 2017 15:54:14 -0700
changeset 410314 c3ac266d12a8c08eb4342030b55a07f28c8a839b
parent 410313 64c3afa3fabf81d5246676bccd559ff8db1be66e
child 410315 690711aa482c25892b669110effe9065ef39b589
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs1364009
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1364009 - Don't allow comments/spaces between signs,numbers,and `n` in an+b syntax for nth-child; r=dbaron In the an+b syntax, this continues to allow comments and spaces like so: ` an + b `. It does not allow `a n+b`, or `- an+b` or `+ an+b` (and the same for the `an-b` form). Similarly, it does not allow `- b` or `+ b`. Additionally, it *does* allow `+/*comment*/n+b` or `-/*comment*/n+b`, but not `+ n+b` or `-n+b`. This is specced; in this one case we parse two tokens but do not allow whitespace in between. MozReview-Commit-ID: INzFGeMPeK7
layout/reftests/css-selectors/nth-child-1.html
layout/reftests/css-selectors/nth-child-2.html
layout/reftests/css-selectors/nth-child-ref.html
layout/reftests/css-selectors/reftest.list
layout/style/nsCSSParser.cpp
layout/style/test/stylo-failures.md
layout/style/test/test_selectors.html
--- a/layout/reftests/css-selectors/nth-child-1.html
+++ b/layout/reftests/css-selectors/nth-child-1.html
@@ -1,42 +1,40 @@
 <!DOCTYPE HTML>
 <html><head>
     <meta charset="utf-8">
     <title>Tests :nth-child(An+B) matching</title>
     <style type="text/css">
 
-    div :nth-child(+/**/3n-2)  { color:white; }
     div :nth-child(+3n/**/-2)  { background-color:black; }
     div :nth-child(+3n/**/-2)  { font-size:12px; }
     div :nth-child(+3n-/**/2)  { text-decoration: underline; }
     div :nth-child(+3n-2/**/)  { border-left-width: 1px; }
-    div :nth-child(+3/**/n-2) { border-right-width: 1px; }
     div :nth-child(+3n/**/-2) { border-top-width: 1px; }
     div :nth-child(+3n/**/-2) { border-bottom-width: 1px; }
-    div :nth-child(+3n-/**/2) { border-style: solid; }
-    div :nth-child(+3n-2/**/) { border-color: blue; }
+    div :nth-child(+3n-/**/2) { border-right-width: 1px; }
+    div :nth-child(+3n-2/**/) { border-style: solid; border-color: blue;}
 
     /* valid but will not match anything */
-    div :nth-child(-/**/n-2)  { color:red; }
     div :nth-child(-n/**/-2)  { color:red; }
     div :nth-child(-n/**/-2)  { color:red; }
     div :nth-child(-n-/**/2)  { color:red; }
     div :nth-child(-n-2/**/)  { color:red; }
-    div :nth-child(-1/**/n-2) { color:red; }
     div :nth-child(-1n/**/-2) { color:red; }
     div :nth-child(-1n/**/-2) { color:red; }
     div :nth-child(-1n-/**/2) { color:red; }
     div :nth-child(-1n-2/**/) { color:red; }
 
     /* invalid */
     div :nth-child(-/**/ n-2) { color:red; }
     div :nth-child(- /**/n-2) { color:red; }
     div :nth-child(+/**/ n-2) { color:red; }
     div :nth-child(+ /**/n-2) { color:red; }
+    div :nth-child(+3/**/n-2) { color:red; }
+    div :nth-child(-/**/n-2) {color: red;}
 
     </style>
 </head>
 <body>
 
 <div><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x></div>
 
 </body>
--- a/layout/reftests/css-selectors/nth-child-2.html
+++ b/layout/reftests/css-selectors/nth-child-2.html
@@ -1,42 +1,40 @@
 <!DOCTYPE HTML>
 <html><head>
     <meta charset="utf-8">
     <title>Tests :nth-child(An+B) matching</title>
     <style type="text/css">
 
-    div :nth-child(+/**/3N-2)  { color:white; }
     div :nth-child(+3N/**/-2)  { background-color:black; }
     div :nth-child(+3N/**/-2)  { font-size:12px; }
     div :nth-child(+3N-/**/2)  { text-decoration: underline; }
     div :nth-child(+3N-2/**/)  { border-left-width: 1px; }
-    div :nth-child(+3/**/N-2) { border-right-width: 1px; }
     div :nth-child(+3N/**/-2) { border-top-width: 1px; }
     div :nth-child(+3N/**/-2) { border-bottom-width: 1px; }
-    div :nth-child(+3N-/**/2) { border-style: solid; }
-    div :nth-child(+3N-2/**/) { border-color: blue; }
+    div :nth-child(+3N-/**/2) { border-right-width: 1px; }
+    div :nth-child(+3N-2/**/) { border-style: solid; border-color: blue;}
 
     /* valid but will not match anything */
-    div :nth-child(-/**/N-2)  { color:red; }
     div :nth-child(-N/**/-2)  { color:red; }
     div :nth-child(-N/**/-2)  { color:red; }
     div :nth-child(-N-/**/2)  { color:red; }
     div :nth-child(-N-2/**/)  { color:red; }
-    div :nth-child(-1/**/N-2) { color:red; }
     div :nth-child(-1N/**/-2) { color:red; }
     div :nth-child(-1N/**/-2) { color:red; }
     div :nth-child(-1N-/**/2) { color:red; }
     div :nth-child(-1N-2/**/) { color:red; }
 
     /* invalid */
     div :nth-child(-/**/ N-2) { color:red; }
     div :nth-child(- /**/N-2) { color:red; }
     div :nth-child(+/**/ N-2) { color:red; }
     div :nth-child(+ /**/N-2) { color:red; }
+    div :nth-child(+3/**/N-2) { color:red; }
+    div :nth-child(-/**/N-2) {color: red;}
 
     </style>
 </head>
 <body>
 
 <div><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x></div>
 
 </body>
--- a/layout/reftests/css-selectors/nth-child-ref.html
+++ b/layout/reftests/css-selectors/nth-child-ref.html
@@ -1,24 +1,22 @@
 <!DOCTYPE HTML>
 <html><head>
     <meta charset="utf-8">
     <title>Tests :nth-child(An+B) matching</title>
     <style type="text/css">
 
-    x { color:white; }
     x { background-color:black; }
     x { font-size:12px; }
     x { text-decoration: underline; }
     x { border-left-width: 1px; }
     x { border-right-width: 1px; }
     x { border-top-width: 1px; }
     x { border-bottom-width: 1px; }
-    x { border-style: solid; }
-    x { border-color: blue; }
+    x { border-style: solid; border-color: blue;}
 
     </style>
 </head>
 <body>
 
 <div><x>x</x><y>x</y><y>x</y><x>x</x><y>x</y><y>x</y><x>x</x><y>x</y><y>x</y><x>x</x><y>x</y></div>
 
 </body>
--- a/layout/reftests/css-selectors/reftest.list
+++ b/layout/reftests/css-selectors/reftest.list
@@ -1,6 +1,6 @@
 == state-dependent-in-any.html state-dependent-in-any-ref.html
 == attr-case-insensitive-1.html attr-case-insensitive-1-ref.html
 == sibling-combinators-on-anon-content-1.xhtml sibling-combinators-on-anon-content-ref.xhtml
 == sibling-combinators-on-anon-content-2.xhtml sibling-combinators-on-anon-content-ref.xhtml
-fails-if(styloVsGecko||stylo) == nth-child-1.html nth-child-ref.html
-fails-if(styloVsGecko||stylo) == nth-child-2.html nth-child-ref.html
+== nth-child-1.html nth-child-ref.html
+== nth-child-2.html nth-child-ref.html
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -6326,161 +6326,148 @@ CSSParserImpl::ParsePseudoClassWithIdent
   return eSelectorParsingStatus_Continue;
 }
 
 CSSParserImpl::nsSelectorParsingStatus
 CSSParserImpl::ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
                                               CSSPseudoClassType aType)
 {
   int32_t numbers[2] = { 0, 0 };
-  int32_t sign[2] = { 1, 1 };
-  bool hasSign[2] = { false, false };
   bool lookForB = true;
+  bool onlyN = false;
+  int hasSign = false;
+  int sign = 1;
 
   // Follow the whitespace rules as proposed in
   // http://lists.w3.org/Archives/Public/www-style/2008Mar/0121.html
 
   if (! GetToken(true)) {
     REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
     return eSelectorParsingStatus_Error;
   }
 
-  if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) {
-    hasSign[0] = true;
-    if (mToken.IsSymbol('-')) {
-      sign[0] = -1;
-    }
-    if (! GetToken(false)) {
-      REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
-      return eSelectorParsingStatus_Error;
-    }
-  }
-
   // A helper function that checks if the token starts with literal string
   // |aStr| using a case-insensitive match.
   auto TokenBeginsWith = [this] (const nsLiteralString& aStr) {
     return StringBeginsWith(mToken.mIdent, aStr,
                             nsASCIICaseInsensitiveStringComparator());
   };
 
+  if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) {
+    // This can only be +n or -n, since +an, -an, +a, -a will all
+    // parse a number as the first token.
+    numbers[0] = mToken.IsSymbol('+') ? 1 : -1;
+    onlyN = true;
+
+    // consume the `n`
+    // We do not allow whitespace here
+    // https://drafts.csswg.org/css-syntax-3/#the-anb-type
+    if (! GetToken(false)) {
+      REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
+      return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+    }
+  }
+
   if (eCSSToken_Ident == mToken.mType || eCSSToken_Dimension == mToken.mType) {
     // The CSS tokenization doesn't handle :nth-child() containing - well:
     //   2n-1 is a dimension
     //   n-1 is an identifier
     // The easiest way to deal with that is to push everything from the
     // minus on back onto the scanner's pushback buffer.
     uint32_t truncAt = 0;
     if (TokenBeginsWith(NS_LITERAL_STRING("n-"))) {
       truncAt = 1;
-    } else if (TokenBeginsWith(NS_LITERAL_STRING("-n-")) && !hasSign[0]) {
+    } else if (TokenBeginsWith(NS_LITERAL_STRING("-n-"))) {
       truncAt = 2;
     }
     if (truncAt != 0) {
       mScanner->Backup(mToken.mIdent.Length() - truncAt);
       mToken.mIdent.Truncate(truncAt);
     }
   }
 
-  if (eCSSToken_Ident == mToken.mType) {
-    if (mToken.mIdent.LowerCaseEqualsLiteral("odd") && !hasSign[0]) {
-      numbers[0] = 2;
-      numbers[1] = 1;
+  if (onlyN) {
+    // If we parsed a + or -, check that the truncated
+    // token is an "n"
+    if (eCSSToken_Ident != mToken.mType || !mToken.mIdent.LowerCaseEqualsLiteral("n")) {
+      REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
+      return eSelectorParsingStatus_Error;
+    }
+  } else {
+    if (eCSSToken_Ident == mToken.mType) {
+      if (mToken.mIdent.LowerCaseEqualsLiteral("odd")) {
+        numbers[0] = 2;
+        numbers[1] = 1;
+        lookForB = false;
+      }
+      else if (mToken.mIdent.LowerCaseEqualsLiteral("even")) {
+        numbers[0] = 2;
+        numbers[1] = 0;
+        lookForB = false;
+      }
+      else if (mToken.mIdent.LowerCaseEqualsLiteral("n")) {
+          numbers[0] = 1;
+      }
+      else if (mToken.mIdent.LowerCaseEqualsLiteral("-n")) {
+        numbers[0] = -1;
+      }
+      else {
+        REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
+        return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+      }
+    }
+    else if (eCSSToken_Number == mToken.mType) {
+      if (!mToken.mIntegerValid) {
+        REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
+        return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+      }
+
+      numbers[1] = mToken.mInteger;
       lookForB = false;
     }
-    else if (mToken.mIdent.LowerCaseEqualsLiteral("even") && !hasSign[0]) {
-      numbers[0] = 2;
-      numbers[1] = 0;
-      lookForB = false;
-    }
-    else if (mToken.mIdent.LowerCaseEqualsLiteral("n")) {
-      numbers[0] = sign[0];
-    }
-    else if (mToken.mIdent.LowerCaseEqualsLiteral("-n") && !hasSign[0]) {
-      numbers[0] = -1;
-    }
+    else if (eCSSToken_Dimension == mToken.mType) {
+      if (!mToken.mIntegerValid || !mToken.mIdent.LowerCaseEqualsLiteral("n")) {
+        REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
+        return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+      }
+      numbers[0] = mToken.mInteger;
+    }
+    // XXX If it's a ')', is that valid?  (as 0n+0)
     else {
       REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
-      return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
-    }
-  }
-  else if (eCSSToken_Number == mToken.mType) {
-    if (!mToken.mIntegerValid) {
-      REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
-      return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
-    }
-    // for +-an case
-    if (mToken.mHasSign && hasSign[0]) {
-      REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
+      UngetToken();
       return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
     }
-    int32_t intValue = mToken.mInteger * sign[0];
-    // for -a/**/n case
-    if (! GetToken(false)) {
-      numbers[1] = intValue;
-      lookForB = false;
-    }
-    else {
-      if (eCSSToken_Ident == mToken.mType && mToken.mIdent.LowerCaseEqualsLiteral("n")) {
-        numbers[0] = intValue;
-      }
-      else if (eCSSToken_Ident == mToken.mType && TokenBeginsWith(NS_LITERAL_STRING("n-"))) {
-        numbers[0] = intValue;
-        mScanner->Backup(mToken.mIdent.Length() - 1);
-      }
-      else {
-        UngetToken();
-        numbers[1] = intValue;
-        lookForB = false;
-      }
-    }
-  }
-  else if (eCSSToken_Dimension == mToken.mType) {
-    if (!mToken.mIntegerValid || !mToken.mIdent.LowerCaseEqualsLiteral("n")) {
-      REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
-      return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
-    }
-    // for +-an case
-    if ( mToken.mHasSign && hasSign[0] ) {
-      REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
-      return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
-    }
-    numbers[0] = mToken.mInteger * sign[0];
-  }
-  // XXX If it's a ')', is that valid?  (as 0n+0)
-  else {
-    REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
-    UngetToken();
-    return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
   }
 
   if (! GetToken(true)) {
     REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
     return eSelectorParsingStatus_Error;
   }
   if (lookForB && !mToken.IsSymbol(')')) {
     // The '+' or '-' sign can optionally be separated by whitespace.
     // If it is separated by whitespace from what follows it, it appears
     // as a separate token rather than part of the number token.
     if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) {
-      hasSign[1] = true;
+      hasSign = true;
       if (mToken.IsSymbol('-')) {
-        sign[1] = -1;
+        sign = -1;
       }
       if (! GetToken(true)) {
         REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
         return eSelectorParsingStatus_Error;
       }
     }
     if (eCSSToken_Number != mToken.mType ||
-        !mToken.mIntegerValid || mToken.mHasSign == hasSign[1]) {
+        !mToken.mIntegerValid || mToken.mHasSign == hasSign) {
       REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
       UngetToken();
       return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
     }
-    numbers[1] = mToken.mInteger * sign[1];
+    numbers[1] = mToken.mInteger * sign;
     if (! GetToken(true)) {
       REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
       return eSelectorParsingStatus_Error;
     }
   }
   if (!mToken.IsSymbol(')')) {
     REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose);
     return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -135,15 +135,15 @@ to mochitest command.
     * test_property_syntax_errors.html `-webkit-gradient` [20]
 * test_specified_value_serialization.html `-webkit-radial-gradient`: bug 1367299 [1]
 * test_variables.html `var(--var6)`: irrelevant test for stylo bug 1367306 [1]
 
 ## Unknown / Unsure
 
 * test_selectors_on_anonymous_content.html: xbl and :nth-child [1]
 * test_parse_rule.html `rgb(0, 128, 0)`: color properties not getting computed [5]
-* test_selectors.html `:nth-child`: &lt;an+b&gt; parsing difference bug 1364009 [14]
+* test_selectors.html `:nth-child`: https://github.com/servo/rust-cssparser/issues/153 [4]
 
 ## Ignore
 
 * Ignore for now since should be mostly identical to test_value_storage.html
   * test_value_cloning.html [*]
   * test_value_computation.html [*]
--- a/layout/style/test/test_selectors.html
+++ b/layout/style/test/test_selectors.html
@@ -642,93 +642,93 @@ function run() {
     test_balanced_unparseable(":nth-child(2n+/**/-/**/2)");
     test_balanced_unparseable(":nth-child(2n-/**/+/**/2)");
     test_balanced_unparseable(":nth-child(2n-/**/-/**/2)");
     test_parseable(":nth-child(+/**/n+2)");
     test_parseable(":nth-child(+n/**/+2)");
     test_parseable(":nth-child(+n/**/+2)");
     test_parseable(":nth-child(+n+/**/2)");
     test_parseable(":nth-child(+n+2/**/)");
-    test_parseable(":nth-child(+1/**/n+2)");
+    test_balanced_unparseable(":nth-child(+1/**/n+2)");
     test_parseable(":nth-child(+1n/**/+2)");
     test_parseable(":nth-child(+1n/**/+2)");
     test_parseable(":nth-child(+1n+/**/2)");
     test_parseable(":nth-child(+1n+2/**/)");
     test_parseable(":nth-child(-/**/n+2)");
     test_parseable(":nth-child(-n/**/+2)");
     test_parseable(":nth-child(-n/**/+2)");
     test_parseable(":nth-child(-n+/**/2)");
     test_parseable(":nth-child(-n+2/**/)");
-    test_parseable(":nth-child(-1/**/n+2)");
+    test_balanced_unparseable(":nth-child(-1/**/n+2)");
     test_parseable(":nth-child(-1n/**/+2)");
     test_parseable(":nth-child(-1n/**/+2)");
     test_parseable(":nth-child(-1n+/**/2)");
     test_parseable(":nth-child(-1n+2/**/)");
     test_balanced_unparseable(":nth-child(-/**/ n+2)");
     test_balanced_unparseable(":nth-child(- /**/n+2)");
     test_balanced_unparseable(":nth-child(+/**/ n+2)");
     test_balanced_unparseable(":nth-child(+ /**/n+2)");
     test_parseable(":nth-child(+/**/n-2)");
     test_parseable(":nth-child(+n/**/-2)");
     test_parseable(":nth-child(+n/**/-2)");
     test_parseable(":nth-child(+n-/**/2)");
     test_parseable(":nth-child(+n-2/**/)");
-    test_parseable(":nth-child(+1/**/n-2)");
+    test_balanced_unparseable(":nth-child(+1/**/n-2)");
     test_parseable(":nth-child(+1n/**/-2)");
     test_parseable(":nth-child(+1n/**/-2)");
     test_parseable(":nth-child(+1n-/**/2)");
     test_parseable(":nth-child(+1n-2/**/)");
     test_parseable(":nth-child(-/**/n-2)");
     test_parseable(":nth-child(-n/**/-2)");
     test_parseable(":nth-child(-n/**/-2)");
     test_parseable(":nth-child(-n-/**/2)");
     test_parseable(":nth-child(-n-2/**/)");
-    test_parseable(":nth-child(-1/**/n-2)");
+    test_balanced_unparseable(":nth-child(-1/**/n-2)");
     test_parseable(":nth-child(-1n/**/-2)");
     test_parseable(":nth-child(-1n/**/-2)");
     test_parseable(":nth-child(-1n-/**/2)");
     test_parseable(":nth-child(-1n-2/**/)");
     test_balanced_unparseable(":nth-child(-/**/ n-2)");
     test_balanced_unparseable(":nth-child(- /**/n-2)");
     test_balanced_unparseable(":nth-child(+/**/ n-2)");
     test_balanced_unparseable(":nth-child(+ /**/n-2)");
     test_parseable(":nth-child(+/**/N-2)");
     test_parseable(":nth-child(+N/**/-2)");
     test_parseable(":nth-child(+N/**/-2)");
     test_parseable(":nth-child(+N-/**/2)");
     test_parseable(":nth-child(+N-2/**/)");
-    test_parseable(":nth-child(+1/**/N-2)");
+    test_balanced_unparseable(":nth-child(+1/**/N-2)");
     test_parseable(":nth-child(+1N/**/-2)");
     test_parseable(":nth-child(+1N/**/-2)");
     test_parseable(":nth-child(+1N-/**/2)");
     test_parseable(":nth-child(+1N-2/**/)");
     test_parseable(":nth-child(-/**/N-2)");
     test_parseable(":nth-child(-N/**/-2)");
     test_parseable(":nth-child(-N/**/-2)");
     test_parseable(":nth-child(-N-/**/2)");
     test_parseable(":nth-child(-N-2/**/)");
-    test_parseable(":nth-child(-1/**/N-2)");
+    test_balanced_unparseable(":nth-child(-1/**/N-2)");
     test_parseable(":nth-child(-1N/**/-2)");
     test_parseable(":nth-child(-1N/**/-2)");
     test_parseable(":nth-child(-1N-/**/2)");
     test_parseable(":nth-child(-1N-2/**/)");
     test_balanced_unparseable(":nth-child(-/**/ N-2)");
     test_balanced_unparseable(":nth-child(- /**/N-2)");
     test_balanced_unparseable(":nth-child(+/**/ N-2)");
     test_balanced_unparseable(":nth-child(+ /**/N-2)");
     test_parseable(":nth-child( +n + 1 )");
     test_parseable(":nth-child( +/**/n + 1 )");
-    test_parseable(":nth-child( -/**/2/**/n/**/+/**/4 )");
-    test_balanced_unparseable(":nth-child( -/**/ 2/**/n/**/+/**/4 )");
-    test_balanced_unparseable(":nth-child( -/**/2 /**/n/**/+/**/4 )");
-    test_balanced_unparseable(":nth-child( -/**/2/**/ n/**/+/**/4 )");
-    test_parseable(":nth-child( -/**/2/**/n /**/+/**/4 )");
-    test_parseable(":nth-child( -/**/2/**/n/**/ +/**/4 )");
-    test_parseable(":nth-child(+1/**/n-1)");
-    test_parseable(":nth-child(1/**/n-1)");
+    test_balanced_unparseable(":nth-child( -/**/2/**/n/**/+/**/4 )");
+    test_parseable(":nth-child( -2n/**/ + /**/4 )");
+    test_parseable(":nth-child( -2n/**/+/**/4 )");
+    test_parseable(":nth-child( -2n  /**/+/**/4 )");
+    test_parseable(":nth-child( -/**/n  /**/+ /**/ 4 )");
+    test_parseable(":nth-child( +/**/n  /**/+ /**/ 4 )");
+    test_balanced_unparseable(":nth-child(+1/**/n-1)");
+    test_balanced_unparseable(":nth-child(1/**/n-1)");
     // bug 876570
     test_balanced_unparseable(":nth-child(+2n-)");
     test_balanced_unparseable(":nth-child(+n-)");
     test_balanced_unparseable(":nth-child(-2n-)");
     test_balanced_unparseable(":nth-child(-n-)");
     test_balanced_unparseable(":nth-child(2n-)");
     test_balanced_unparseable(":nth-child(n-)");
     test_balanced_unparseable(":nth-child(+2n+)");