Commit 7f276c9a authored by Eric Willigers's avatar Eric Willigers Committed by Commit Bot

CSS: Use count :not with selector lists

CSS Selectors 4 allows :not to accept selector lists.

There is a proposal for invalid complex selectors in the list to
be ignored (instead of causing the whole selector to fail parsing.

This would affect :is :where :nth-child :nth-last-child, :has and :not,
with the main compatibility risk being :not.

https://github.com/w3c/csswg-drafts/issues/3264

We add use counters to estimate the Web compatibilty risk.

BUG=568705

Change-Id: I708d48d626a6e61cc8c1076d40d2c33bb19496e3
Reviewed-on: https://chromium-review.googlesource.com/c/1354751Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Commit-Queue: Eric Willigers <ericwilligers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#612177}
parent 1378cfd0
......@@ -2094,6 +2094,9 @@ enum WebFeature {
kFlexboxSingleLineAlignContent = 2642,
kSignedExchangeInnerResponseInMainFrame = 2643,
kSignedExchangeInnerResponseInSubFrame = 2644,
kCSSSelectorNotWithValidList = 2645,
kCSSSelectorNotWithInvalidList = 2646,
kCSSSelectorNotWithPartiallyValidList = 2647,
// Add new features immediately above this line. Don't change assigned
// numbers of any item, and don't reuse removed slots.
......
......@@ -487,6 +487,44 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumeAttribute(
return selector;
}
void CSSSelectorParser::CountRejectedNot(CSSParserTokenRange& range) {
bool exists_valid = false;
bool exists_invalid = false;
do {
if (exists_valid || exists_invalid) {
DCHECK(range.Peek().GetType() == kCommaToken);
range.ConsumeIncludingWhitespace();
}
// else we are parsing the first complex selector
failed_parsing_ = false;
bool consumed_invalid = !ConsumeComplexSelector(range) || failed_parsing_;
range.ConsumeWhitespace();
while (!range.AtEnd() && range.Peek().GetType() != kCommaToken) {
consumed_invalid = true;
range.ConsumeIncludingWhitespace();
}
if (consumed_invalid)
exists_invalid = true;
else
exists_valid = true;
} while (!range.AtEnd());
WebFeature feature;
if (exists_valid) {
if (exists_invalid)
feature = WebFeature::kCSSSelectorNotWithPartiallyValidList;
else
feature = WebFeature::kCSSSelectorNotWithValidList;
} else {
feature = WebFeature::kCSSSelectorNotWithInvalidList;
}
context_->Count(feature);
failed_parsing_ = true;
}
std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo(
CSSParserTokenRange& range) {
DCHECK_EQ(range.Peek().GetType(), kColonToken);
......@@ -577,11 +615,17 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo(
return selector;
}
case CSSSelector::kPseudoNot: {
CSSParserTokenRange fallback_block = block;
std::unique_ptr<CSSParserSelector> inner_selector =
ConsumeCompoundSelector(block);
block.ConsumeWhitespace();
if (!inner_selector || !inner_selector->IsSimple() || !block.AtEnd())
if (!inner_selector || !inner_selector->IsSimple() ||
inner_selector->Relation() != CSSSelector::kSubSelector ||
!block.AtEnd()) {
CountRejectedNot(fallback_block);
return nullptr;
}
Vector<std::unique_ptr<CSSParserSelector>> selector_vector;
selector_vector.push_back(std::move(inner_selector));
selector->AdoptSelectorVector(selector_vector);
......
......@@ -80,6 +80,7 @@ class CORE_EXPORT CSSSelectorParser {
SplitCompoundAtImplicitShadowCrossingCombinator(
std::unique_ptr<CSSParserSelector> compound_selector);
void RecordUsageAndDeprecations(const CSSSelectorList&);
void CountRejectedNot(CSSParserTokenRange&);
Member<const CSSParserContext> context_;
Member<const StyleSheetContents> style_sheet_;
......
......@@ -771,4 +771,88 @@ TEST(CSSSelectorParserTest, ImplicitShadowCrossingCombinators) {
}
}
TEST(CSSSelectorParserTest, UseCountRejectedNot) {
auto ExpectCount = [](const char* selector, WebFeature feature) {
std::unique_ptr<DummyPageHolder> dummy_holder =
DummyPageHolder::Create(IntSize(500, 500));
Document* doc = &dummy_holder->GetDocument();
Page::InsertOrdinaryPageForTesting(&dummy_holder->GetPage());
CSSParserContext* context = CSSParserContext::Create(
kHTMLStandardMode, SecureContextMode::kSecureContext,
CSSParserContext::kLiveProfile, doc);
StyleSheetContents* sheet = StyleSheetContents::Create(context);
EXPECT_FALSE(UseCounter::IsCounted(*doc, feature));
CSSTokenizer tokenizer(selector);
const auto tokens = tokenizer.TokenizeToEOF();
CSSParserTokenRange range(tokens);
CSSSelectorParser::ParseSelector(range, context, sheet);
bool result = UseCounter::IsCounted(*doc, feature);
EXPECT_TRUE(result);
};
ExpectCount(":not(:nonsense :gibberish)",
WebFeature::kCSSSelectorNotWithInvalidList);
ExpectCount(":not(:nonsense :gibberish, .a)",
WebFeature::kCSSSelectorNotWithPartiallyValidList);
ExpectCount(":not()", WebFeature::kCSSSelectorNotWithInvalidList);
ExpectCount(":not(,)", WebFeature::kCSSSelectorNotWithInvalidList);
ExpectCount(":not(,,)", WebFeature::kCSSSelectorNotWithInvalidList);
ExpectCount(":not(* .a)", WebFeature::kCSSSelectorNotWithValidList);
ExpectCount(":not(:nonsense)", WebFeature::kCSSSelectorNotWithInvalidList);
ExpectCount(":not(* * .previouslyFailed)",
WebFeature::kCSSSelectorNotWithValidList);
ExpectCount(":not(* :nonsense)", WebFeature::kCSSSelectorNotWithInvalidList);
ExpectCount(":not(:nonsense *)", WebFeature::kCSSSelectorNotWithInvalidList);
ExpectCount(":not(*,)", WebFeature::kCSSSelectorNotWithPartiallyValidList);
ExpectCount(":not(*,,)", WebFeature::kCSSSelectorNotWithPartiallyValidList);
ExpectCount(":not(:nonsense ,)", WebFeature::kCSSSelectorNotWithInvalidList);
ExpectCount(":not(:nonsense,,)", WebFeature::kCSSSelectorNotWithInvalidList);
ExpectCount(":not(*, *)", WebFeature::kCSSSelectorNotWithValidList);
ExpectCount(":not(*, :nonsense)",
WebFeature::kCSSSelectorNotWithPartiallyValidList);
ExpectCount(":not(* , * *)", WebFeature::kCSSSelectorNotWithValidList);
ExpectCount(":not(*, * :nonsense)",
WebFeature::kCSSSelectorNotWithPartiallyValidList);
ExpectCount(":not(:nonsense,*)",
WebFeature::kCSSSelectorNotWithPartiallyValidList);
ExpectCount(":not(:nonsense , :nonsense)",
WebFeature::kCSSSelectorNotWithInvalidList);
ExpectCount(":not(:nonsense, * *)",
WebFeature::kCSSSelectorNotWithPartiallyValidList);
ExpectCount(":not(:nonsense, * :nonsense )",
WebFeature::kCSSSelectorNotWithInvalidList);
ExpectCount(":not(*, :not(* * :nonsense))",
WebFeature::kCSSSelectorNotWithPartiallyValidList);
ExpectCount(":not(:not(* * :nonsense) , *)",
WebFeature::kCSSSelectorNotWithPartiallyValidList);
ExpectCount(":not(.a || :nonsense, *)",
WebFeature::kCSSSelectorNotWithPartiallyValidList);
}
TEST(CSSSelectorParserTest, SimpleNotNeverCounted) {
// :not with a simple selector from CSS Selectors 3 is not counted.
std::unique_ptr<DummyPageHolder> dummy_holder =
DummyPageHolder::Create(IntSize(500, 500));
Document* doc = &dummy_holder->GetDocument();
Page::InsertOrdinaryPageForTesting(&dummy_holder->GetPage());
CSSParserContext* context = CSSParserContext::Create(
kHTMLStandardMode, SecureContextMode::kSecureContext,
CSSParserContext::kLiveProfile, doc);
StyleSheetContents* sheet = StyleSheetContents::Create(context);
CSSTokenizer tokenizer(":not(*)");
const auto tokens = tokenizer.TokenizeToEOF();
CSSParserTokenRange range(tokens);
CSSSelectorParser::ParseSelector(range, context, sheet);
EXPECT_FALSE(
UseCounter::IsCounted(*doc, WebFeature::kCSSSelectorNotWithValidList));
EXPECT_FALSE(
UseCounter::IsCounted(*doc, WebFeature::kCSSSelectorNotWithInvalidList));
EXPECT_FALSE(UseCounter::IsCounted(
*doc, WebFeature::kCSSSelectorNotWithPartiallyValidList));
}
} // namespace blink
......@@ -20881,6 +20881,9 @@ Called by update_net_error_codes.py.-->
<int value="2642" label="FlexboxSingleLineAlignContent"/>
<int value="2643" label="SignedExchangeInnerResponseInMainFrame"/>
<int value="2644" label="SignedExchangeInnerResponseInSubFrame"/>
<int value="2645" label="CSSSelectorNotWithValidList"/>
<int value="2646" label="CSSSelectorNotWithInvalidList"/>
<int value="2647" label="CSSSelectorNotWithPartiallyValidList"/>
</enum>
<enum name="FeaturePolicyFeature">
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