Bug 1364009 - Don't allow comments/spaces between signs,numbers,and `n` in an+b syntax for nth-child; r?dbaron draft
authorManish Goregaokar <manishearth@gmail.com>
Thu, 01 Jun 2017 15:54:14 -0700
changeset 588684 f87a563ec4853d28a8d190c371b13e39df790604
parent 587906 b138d2f271fdb598bf8a66c2dcb7fe391ca2a96f
child 631645 efd366ef9d56091d8a2c4adcbf3dcabbf803269b
push id62114
push userbmo:manishearth@gmail.com
push dateSat, 03 Jun 2017 22:01:51 +0000
reviewersdbaron
bugs1364009
milestone55.0a1
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+)");