Commit 26fd0dee authored by Anders Hartvoll Ruud's avatar Anders Hartvoll Ruud Committed by Commit Bot

[:is/:where] Parse as <forgiving-selector-list>

This CL aligns the parsing behavior for :is() and :where() with
<forgiving-selector-list> [1]. Parsing as a forgiving selector list
means that individual selectors that are invalid will just be dropped;
they won't invalidate the entire pseudo.

This also adds support for empty :is()/:where() pseudos, which are
valid, but do not match anything [2].

[1] https://drafts.csswg.org/selectors/#typedef-forgiving-selector-list
[2] https://drafts.csswg.org/selectors/#matches

Bug: 568705
Change-Id: I1b8f7d34e28d10927b1199ae2ed6ef8c2936e828
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2450012Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Commit-Queue: Anders Hartvoll Ruud <andruud@chromium.org>
Cr-Commit-Position: refs/heads/master@{#815253}
parent 57ee92af
......@@ -19,6 +19,23 @@
namespace blink {
namespace {
CSSParserTokenRange ConsumeNestedArgument(CSSParserTokenRange& range) {
const CSSParserToken& first = range.Peek();
while (!range.AtEnd() && range.Peek().GetType() != kCommaToken) {
const CSSParserToken& token = range.Peek();
if (token.GetBlockType() == CSSParserToken::kBlockStart) {
range.ConsumeBlock();
continue;
}
range.Consume();
}
return range.MakeSubRange(&first, &range.Peek());
}
} // namespace
// static
CSSSelectorList CSSSelectorParser::ParseSelector(
CSSParserTokenRange range,
......@@ -147,8 +164,52 @@ CSSSelectorList CSSSelectorParser::ConsumeCompoundSelectorList(
CSSSelectorList CSSSelectorParser::ConsumeNestedSelectorList(
CSSParserTokenRange& range) {
if (inside_compound_pseudo_)
return ConsumeCompoundSelectorList(range);
return ConsumeComplexSelectorList(range);
return ConsumeForgivingCompoundSelectorList(range);
return ConsumeForgivingComplexSelectorList(range);
}
CSSSelectorList CSSSelectorParser::ConsumeForgivingComplexSelectorList(
CSSParserTokenRange& range) {
Vector<std::unique_ptr<CSSParserSelector>> selector_list;
while (!range.AtEnd()) {
base::AutoReset<bool> reset_failure(&failed_parsing_, false);
CSSParserTokenRange argument = ConsumeNestedArgument(range);
std::unique_ptr<CSSParserSelector> selector =
ConsumeComplexSelector(argument);
if (selector && !failed_parsing_ && argument.AtEnd())
selector_list.push_back(std::move(selector));
if (range.Peek().GetType() != kCommaToken)
break;
range.ConsumeIncludingWhitespace();
}
if (selector_list.IsEmpty())
return CSSSelectorList();
return CSSSelectorList::AdoptSelectorVector(selector_list);
}
CSSSelectorList CSSSelectorParser::ConsumeForgivingCompoundSelectorList(
CSSParserTokenRange& range) {
Vector<std::unique_ptr<CSSParserSelector>> selector_list;
while (!range.AtEnd()) {
base::AutoReset<bool> reset_failure(&failed_parsing_, false);
CSSParserTokenRange argument = ConsumeNestedArgument(range);
std::unique_ptr<CSSParserSelector> selector =
ConsumeCompoundSelector(argument);
if (selector && !failed_parsing_ && argument.AtEnd())
selector_list.push_back(std::move(selector));
if (range.Peek().GetType() != kCommaToken)
break;
range.ConsumeIncludingWhitespace();
}
if (selector_list.IsEmpty())
return CSSSelectorList();
return CSSSelectorList::AdoptSelectorVector(selector_list);
}
namespace {
......@@ -603,7 +664,7 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo(
std::unique_ptr<CSSSelectorList> selector_list =
std::make_unique<CSSSelectorList>();
*selector_list = ConsumeNestedSelectorList(block);
if (!selector_list->IsValid() || !block.AtEnd())
if (!block.AtEnd())
return nullptr;
selector->SetSelectorList(std::move(selector_list));
return selector;
......@@ -620,7 +681,7 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo(
std::unique_ptr<CSSSelectorList> selector_list =
std::make_unique<CSSSelectorList>();
*selector_list = ConsumeNestedSelectorList(block);
if (!selector_list->IsValid() || !block.AtEnd())
if (!block.AtEnd())
return nullptr;
selector->SetSelectorList(std::move(selector_list));
return selector;
......
......@@ -46,9 +46,12 @@ class CORE_EXPORT CSSSelectorParser {
CSSSelectorList ConsumeComplexSelectorList(CSSParserTokenStream&,
CSSParserObserver*);
CSSSelectorList ConsumeCompoundSelectorList(CSSParserTokenRange&);
// Consumes a complex selector list if inside_compound_pseudo_ is false,
// otherwise consumes a compound selector list.
// Consumes a forgiving complex selector list if inside_compound_pseudo_ is
// false, otherwise consumes a forgiving compound selector list.
CSSSelectorList ConsumeNestedSelectorList(CSSParserTokenRange&);
// https://drafts.csswg.org/selectors/#typedef-forgiving-selector-list
CSSSelectorList ConsumeForgivingComplexSelectorList(CSSParserTokenRange&);
CSSSelectorList ConsumeForgivingCompoundSelectorList(CSSParserTokenRange&);
std::unique_ptr<CSSParserSelector> ConsumeComplexSelector(
CSSParserTokenRange&);
......
......@@ -413,27 +413,27 @@ TEST(CSSSelectorParserTest, InternalPseudo) {
// https://drafts.csswg.org/selectors-4/#matches
static const SelectorTestCase invalid_pseudo_is_argments_data[] = {
// clang-format off
{":is(::-webkit-progress-bar)", ""},
{":is(::-webkit-progress-value)", ""},
{":is(::-webkit-slider-runnable-track)", ""},
{":is(::-webkit-slider-thumb)", ""},
{":is(::after)", ""},
{":is(::backdrop)", ""},
{":is(::before)", ""},
{":is(::cue)", ""},
{":is(::first-letter)", ""},
{":is(::first-line)", ""},
{":is(::grammar-error)", ""},
{":is(::marker)", ""},
{":is(::placeholder)", ""},
{":is(::selection)", ""},
{":is(::slotted)", ""},
{":is(::spelling-error)", ""},
{":is(:after)", ""},
{":is(:before)", ""},
{":is(:cue)", ""},
{":is(:first-letter)", ""},
{":is(:first-line)", ""},
{":is(::-webkit-progress-bar)", ":is()"},
{":is(::-webkit-progress-value)", ":is()"},
{":is(::-webkit-slider-runnable-track)", ":is()"},
{":is(::-webkit-slider-thumb)", ":is()"},
{":is(::after)", ":is()"},
{":is(::backdrop)", ":is()"},
{":is(::before)", ":is()"},
{":is(::cue)", ":is()"},
{":is(::first-letter)", ":is()"},
{":is(::first-line)", ":is()"},
{":is(::grammar-error)", ":is()"},
{":is(::marker)", ":is()"},
{":is(::placeholder)", ":is()"},
{":is(::selection)", ":is()"},
{":is(::slotted)", ":is()"},
{":is(::spelling-error)", ":is()"},
{":is(:after)", ":is()"},
{":is(:before)", ":is()"},
{":is(:cue)", ":is()"},
{":is(:first-letter)", ":is()"},
{":is(:first-line)", ":is()"},
// clang-format on
};
......@@ -446,11 +446,11 @@ INSTANTIATE_TEST_SUITE_P(InvalidPseudoIsArguments,
static const SelectorTestCase shadow_v0_with_is_where_data[] = {
// clang-format off
{":is(.a) ::content", ""},
{":is(.a /deep/ .b)", ""},
{":is(::content)", ""},
{":is(::shadow)", ""},
{":is(::content .a)", ""},
{":is(::shadow .b)", ""},
{":is(.a /deep/ .b)", ":is()"},
{":is(::content)", ":is()"},
{":is(::shadow)", ":is()"},
{":is(::content .a)", ":is()"},
{":is(::shadow .b)", ":is()"},
{":is(.a)::shadow", ""},
{":is(.a) ::content", ""},
{":is(.a) ::shadow", ""},
......@@ -458,7 +458,7 @@ static const SelectorTestCase shadow_v0_with_is_where_data[] = {
{"::shadow :is(.a)", ""},
{":is(.a) /deep/ .b", ""},
{":.a /deep/ :is(.b)", ""},
{":where(.a /deep/ .b)", ""},
{":where(.a /deep/ .b)", ":where()"},
{":where(.a) ::shadow", ""},
// clang-format on
};
......@@ -470,18 +470,18 @@ INSTANTIATE_TEST_SUITE_P(ShadowDomV0WithIsAndWhere,
static const SelectorTestCase is_where_nesting_data[] = {
// clang-format off
// These pseudos only accept compound selectors:
{"::slotted(:is(.a .b))", ""},
{"::slotted(:is(.a + .b))", ""},
{"::slotted(:is(.a, .b + .c))", ""},
{":host(:is(.a .b))", ""},
{":host(:is(.a + .b))", ""},
{":host(:is(.a, .b + .c))", ""},
{":host-context(:is(.a .b))", ""},
{":host-context(:is(.a + .b))", ""},
{":host-context(:is(.a, .b + .c))", ""},
{"::cue(:is(.a .b))", ""},
{"::cue(:is(.a + .b))", ""},
{"::cue(:is(.a, .b + .c))", ""},
{"::slotted(:is(.a .b))", "::slotted(:is())"},
{"::slotted(:is(.a + .b))", "::slotted(:is())"},
{"::slotted(:is(.a, .b + .c))", "::slotted(:is(.a))"},
{":host(:is(.a .b))", ":host(:is())"},
{":host(:is(.a + .b))", ":host(:is())"},
{":host(:is(.a, .b + .c))", ":host(:is(.a))"},
{":host-context(:is(.a .b))", ":host-context(:is())"},
{":host-context(:is(.a + .b))", ":host-context(:is())"},
{":host-context(:is(.a, .b + .c))", ":host-context(:is(.a))"},
{"::cue(:is(.a .b))", "::cue(:is())"},
{"::cue(:is(.a + .b))", "::cue(:is())"},
{"::cue(:is(.a, .b + .c))", "::cue(:is(.a))"},
// Valid selectors:
{":is(.a, .b)"},
......@@ -508,6 +508,33 @@ INSTANTIATE_TEST_SUITE_P(NestedSelectorValidity,
SelectorParseTest,
testing::ValuesIn(is_where_nesting_data));
static const SelectorTestCase is_where_forgiving_data[] = {
// clang-format off
{":is():where()"},
{":is(.a, .b):where(.c)"},
{":is(.a, :unknown, .b)", ":is(.a, .b)"},
{":where(.a, :unknown, .b)", ":where(.a, .b)"},
{":is(.a, :unknown)", ":is(.a)"},
{":is(:unknown, .a)", ":is(.a)"},
{":is(:unknown)", ":is()"},
{":is(:unknown, :where(.a))", ":is(:where(.a))"},
{":is(:unknown, :where(:unknown))", ":is(:where())"},
{":is(.a, :is(.b, :unknown), .c)", ":is(.a, :is(.b), .c)"},
{":host(:is(.a, .b + .c, .d))", ":host(:is(.a, .d))"},
{":is(,, ,, )", ":is()"},
{":is(.a,,,,)", ":is(.a)"},
{":is(,,.a,,)", ":is(.a)"},
{":is(,,,,.a)", ":is(.a)"},
{":is(@x {,.b,}, .a)", ":is(.a)"},
{":is({,.b,} @x, .a)", ":is(.a)"},
{":is((@x), .a)", ":is(.a)"},
{":is((.b), .a)", ":is(.a)"},
// clang-format on
};
INSTANTIATE_TEST_SUITE_P(IsWhereForgiving,
SelectorParseTest,
testing::ValuesIn(is_where_forgiving_data));
namespace {
const auto TagLocalName = [](const CSSSelector* selector) {
......
This is a testharness.js-based test.
FAIL CSS Selectors: :is() and :where() error recovery assert_not_equals: Should've parsed got disallowed value "random-selector"
Harness: the test ran to completion.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment