Commit cf262a36 authored by Anders Hartvoll Ruud's avatar Anders Hartvoll Ruud Committed by Commit Bot

[:is/:where] Disallow mixing of :is/:where and ShadowDOM V0

During selector parsing, whenever we encounter :is or :where, we set a
flag which disallows ShadowDOM v0 selector features (/deep/, ::content
and ::shadow). And conversely, whenever we encounter a ShadowDOM v0
feature, we disallow :is and :where.

Bug: 568705
Change-Id: If11d20b4fe93c82519e2a6ee780e68f62d3f4789
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2438989
Commit-Queue: Anders Hartvoll Ruud <andruud@chromium.org>
Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Cr-Commit-Position: refs/heads/master@{#812767}
parent 1709ec5f
...@@ -554,7 +554,14 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo( ...@@ -554,7 +554,14 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo(
return nullptr; return nullptr;
} }
break; break;
default:; case CSSSelector::kPseudoShadow:
case CSSSelector::kPseudoContent:
if (disallow_shadow_dom_v0_)
return nullptr;
disallow_nested_complex_ = true;
break;
default:
break;
} }
} }
...@@ -578,6 +585,9 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo( ...@@ -578,6 +585,9 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo(
case CSSSelector::kPseudoIs: { case CSSSelector::kPseudoIs: {
if (!RuntimeEnabledFeatures::CSSPseudoIsEnabled()) if (!RuntimeEnabledFeatures::CSSPseudoIsEnabled())
break; break;
if (disallow_nested_complex_)
return nullptr;
disallow_shadow_dom_v0_ = true;
DisallowPseudoElementsScope scope(this); DisallowPseudoElementsScope scope(this);
...@@ -592,6 +602,9 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo( ...@@ -592,6 +602,9 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo(
case CSSSelector::kPseudoWhere: { case CSSSelector::kPseudoWhere: {
if (!RuntimeEnabledFeatures::CSSPseudoWhereEnabled()) if (!RuntimeEnabledFeatures::CSSPseudoWhereEnabled())
break; break;
if (disallow_nested_complex_)
return nullptr;
disallow_shadow_dom_v0_ = true;
DisallowPseudoElementsScope scope(this); DisallowPseudoElementsScope scope(this);
...@@ -723,6 +736,10 @@ CSSSelector::RelationType CSSSelectorParser::ConsumeCombinator( ...@@ -723,6 +736,10 @@ CSSSelector::RelationType CSSSelectorParser::ConsumeCombinator(
const CSSParserToken& slash = range.ConsumeIncludingWhitespace(); const CSSParserToken& slash = range.ConsumeIncludingWhitespace();
if (slash.GetType() != kDelimiterToken || slash.Delimiter() != '/') if (slash.GetType() != kDelimiterToken || slash.Delimiter() != '/')
failed_parsing_ = true; failed_parsing_ = true;
if (disallow_shadow_dom_v0_)
failed_parsing_ = true;
else
disallow_nested_complex_ = true;
return context_->IsLiveProfile() ? CSSSelector::kShadowDeepAsDescendant return context_->IsLiveProfile() ? CSSSelector::kShadowDeepAsDescendant
: CSSSelector::kShadowDeep; : CSSSelector::kShadowDeep;
} }
......
...@@ -90,6 +90,12 @@ class CORE_EXPORT CSSSelectorParser { ...@@ -90,6 +90,12 @@ class CORE_EXPORT CSSSelectorParser {
bool failed_parsing_ = false; bool failed_parsing_ = false;
bool disallow_pseudo_elements_ = false; bool disallow_pseudo_elements_ = false;
// We don't allow mixing ShadowDOMv0 features and nested complex selectors,
// such as :is(). When :is() or :where() is encountered, ShadowDOM V0 features
// are disallowed, and whenever /deep/, ::content or ::shadow is encountered
// we disallow :is()/:where().
bool disallow_shadow_dom_v0_ = false;
bool disallow_nested_complex_ = false;
class DisallowPseudoElementsScope { class DisallowPseudoElementsScope {
STACK_ALLOCATED(); STACK_ALLOCATED();
......
...@@ -426,6 +426,44 @@ TEST(CSSSelectorParserTest, InvalidPseudoIsArguments) { ...@@ -426,6 +426,44 @@ TEST(CSSSelectorParserTest, InvalidPseudoIsArguments) {
} }
} }
TEST(CSSSelectorParserTest, ShadowDomV0WithIsAndWhere) {
// To reduce complexity, ShadowDOM v0 features are not supported in
// combination with :is/:where.
const char* test_cases[] = {
// clang-format off
":is(.a) ::content",
":is(.a /deep/ .b)",
":is(::content)",
":is(::shadow)",
":is(::content .a)",
":is(::shadow .b)",
":is(.a)::shadow",
":is(.a) ::content",
":is(.a) ::shadow",
"::content :is(.a)",
"::shadow :is(.a)",
":is(.a) /deep/ .b",
":.a /deep/ :is(.b)",
":where(.a /deep/ .b)",
":where(.a) ::shadow",
// clang-format on
};
auto* context = MakeGarbageCollected<CSSParserContext>(
kHTMLStandardMode, SecureContextMode::kInsecureContext);
auto* sheet = MakeGarbageCollected<StyleSheetContents>(context);
for (auto* test_case : test_cases) {
SCOPED_TRACE(test_case);
CSSTokenizer tokenizer(test_case);
const auto tokens = tokenizer.TokenizeToEOF();
CSSParserTokenRange range(tokens);
CSSSelectorList list =
CSSSelectorParser::ParseSelector(range, context, sheet);
EXPECT_FALSE(list.IsValid());
}
}
namespace { namespace {
const auto TagLocalName = [](const CSSSelector* selector) { const auto TagLocalName = [](const CSSSelector* selector) {
......
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