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
--- 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`: <an+b> 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+)");