Commit 914a1c99 authored by Anders Hartvoll Ruud's avatar Anders Hartvoll Ruud Committed by Commit Bot

[:is/:where] Allow nested :is() in ::cue() and shadow pseudos

However, a nested :is() can not bypass the limitations imposed by
an outer pseudo. So for example, since :host() only allows a
compound selector list, an :is() pseudo inside a :host() can also
only accept a compound selector list.

Bug: 568705
Change-Id: I879c83172386b8732f0c3e3c540c74e8d3eb0ecb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2438387
Commit-Queue: Anders Hartvoll Ruud <andruud@chromium.org>
Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Cr-Commit-Position: refs/heads/master@{#813594}
parent 67aeed68
......@@ -17,6 +17,7 @@
#include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_local_context.h"
#include "third_party/blink/renderer/core/css/parser/css_property_parser.h"
#include "third_party/blink/renderer/core/css/parser/css_selector_parser.h"
#include "third_party/blink/renderer/core/css/parser/css_tokenizer.h"
#include "third_party/blink/renderer/core/css/properties/css_property_ref.h"
#include "third_party/blink/renderer/core/css/properties/longhand.h"
......@@ -159,5 +160,15 @@ const CSSValue* ParseValue(Document& document, String syntax, String value) {
/* is_animation_tainted */ false);
}
CSSSelectorList ParseSelectorList(const String& string) {
auto* context = MakeGarbageCollected<CSSParserContext>(
kHTMLStandardMode, SecureContextMode::kInsecureContext);
auto* sheet = MakeGarbageCollected<StyleSheetContents>(context);
CSSTokenizer tokenizer(string);
const auto tokens = tokenizer.TokenizeToEOF();
CSSParserTokenRange range(tokens);
return CSSSelectorParser::ParseSelector(range, context, sheet);
}
} // namespace css_test_helpers
} // namespace blink
......@@ -6,6 +6,7 @@
#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSS_TEST_HELPERS_H_
#include "base/memory/scoped_refptr.h"
#include "third_party/blink/renderer/core/css/css_selector_list.h"
#include "third_party/blink/renderer/core/css/rule_set.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
......@@ -72,6 +73,8 @@ StyleRuleBase* ParseRule(Document& document, String text);
// https://drafts.css-houdini.org/css-properties-values-api-1/#syntax-strings
const CSSValue* ParseValue(Document&, String syntax, String value);
CSSSelectorList ParseSelectorList(const String&);
} // namespace css_test_helpers
} // namespace blink
......
......@@ -6,6 +6,7 @@
#include "third_party/blink/renderer/core/css/parser/css_selector_parser.h"
#include <memory>
#include "base/auto_reset.h"
#include "base/numerics/safe_conversions.h"
#include "third_party/blink/renderer/core/css/css_selector_list.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
......@@ -143,6 +144,13 @@ CSSSelectorList CSSSelectorParser::ConsumeCompoundSelectorList(
return CSSSelectorList::AdoptSelectorVector(selector_list);
}
CSSSelectorList CSSSelectorParser::ConsumeNestedSelectorList(
CSSParserTokenRange& range) {
if (inside_compound_pseudo_)
return ConsumeCompoundSelectorList(range);
return ConsumeComplexSelectorList(range);
}
namespace {
enum CompoundSelectorFlags {
......@@ -593,7 +601,7 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo(
std::unique_ptr<CSSSelectorList> selector_list =
std::make_unique<CSSSelectorList>();
*selector_list = ConsumeComplexSelectorList(block);
*selector_list = ConsumeNestedSelectorList(block);
if (!selector_list->IsValid() || !block.AtEnd())
return nullptr;
selector->SetSelectorList(std::move(selector_list));
......@@ -610,7 +618,7 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo(
std::unique_ptr<CSSSelectorList> selector_list =
std::make_unique<CSSSelectorList>();
*selector_list = ConsumeComplexSelectorList(block);
*selector_list = ConsumeNestedSelectorList(block);
if (!selector_list->IsValid() || !block.AtEnd())
return nullptr;
selector->SetSelectorList(std::move(selector_list));
......@@ -621,6 +629,7 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo(
case CSSSelector::kPseudoAny:
case CSSSelector::kPseudoCue: {
DisallowPseudoElementsScope scope(this);
base::AutoReset<bool> inside_compound(&inside_compound_pseudo_, true);
std::unique_ptr<CSSSelectorList> selector_list =
std::make_unique<CSSSelectorList>();
......@@ -661,13 +670,12 @@ std::unique_ptr<CSSParserSelector> CSSSelectorParser::ConsumePseudo(
}
case CSSSelector::kPseudoSlotted: {
DisallowPseudoElementsScope scope(this);
base::AutoReset<bool> inside_compound(&inside_compound_pseudo_, true);
std::unique_ptr<CSSParserSelector> inner_selector =
ConsumeCompoundSelector(block);
block.ConsumeWhitespace();
if (!inner_selector || !block.AtEnd() ||
inner_selector->GetPseudoType() == CSSSelector::kPseudoIs ||
inner_selector->GetPseudoType() == CSSSelector::kPseudoWhere)
if (!inner_selector || !block.AtEnd())
return nullptr;
Vector<std::unique_ptr<CSSParserSelector>> selector_vector;
selector_vector.push_back(std::move(inner_selector));
......
......@@ -46,6 +46,9 @@ 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.
CSSSelectorList ConsumeNestedSelectorList(CSSParserTokenRange&);
std::unique_ptr<CSSParserSelector> ConsumeComplexSelector(
CSSParserTokenRange&);
......@@ -96,6 +99,10 @@ class CORE_EXPORT CSSSelectorParser {
// we disallow :is()/:where().
bool disallow_shadow_dom_v0_ = false;
bool disallow_nested_complex_ = false;
// If we're inside a pseudo class that only accepts compound selectors,
// for example :host, inner :is()/:where() pseudo classes are also only
// allowed to contain compound selectors.
bool inside_compound_pseudo_ = false;
class DisallowPseudoElementsScope {
STACK_ALLOCATED();
......
......@@ -6,6 +6,7 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/css/css_selector_list.h"
#include "third_party/blink/renderer/core/css/css_test_helpers.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
#include "third_party/blink/renderer/core/css/parser/css_tokenizer.h"
#include "third_party/blink/renderer/core/css/style_sheet_contents.h"
......@@ -411,18 +412,9 @@ TEST(CSSSelectorParserTest, InvalidPseudoIsArguments) {
":is(:first-letter)",
":is(:first-line)"};
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());
EXPECT_FALSE(css_test_helpers::ParseSelectorList(test_case).IsValid());
}
}
......@@ -449,18 +441,61 @@ TEST(CSSSelectorParserTest, ShadowDomV0WithIsAndWhere) {
// 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());
EXPECT_FALSE(css_test_helpers::ParseSelectorList(test_case).IsValid());
}
}
TEST(CSSSelectorParserTest, NestedSelectorValidity) {
const char* invalid_nesting[] = {
// 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))",
// clang-format on
};
const char* valid_nesting[] = {
// clang-format off
":is(.a, .b)",
":is(.a .b, .c)",
":is(.a :is(.b .c), .d)",
":is(.a :where(.b .c), .d)",
":where(.a :is(.b .c), .d)",
"::slotted(:is(.a))",
"::slotted(:is(div.a))",
"::slotted(:is(.a, .b))",
":host(:is(.a))",
":host(:is(div.a))",
":host(:is(.a, .b))",
":host-context(:is(.a))",
":host-context(:is(div.a))",
":host-context(:is(.a, .b))",
"::cue(:is(.a))",
"::cue(:is(div.a))",
"::cue(:is(.a, .b))",
// clang-format on
};
for (auto* test_case : invalid_nesting) {
SCOPED_TRACE(test_case);
EXPECT_FALSE(css_test_helpers::ParseSelectorList(test_case).IsValid());
}
for (auto* test_case : valid_nesting) {
SCOPED_TRACE(test_case);
EXPECT_TRUE(css_test_helpers::ParseSelectorList(test_case).IsValid());
}
}
......
......@@ -1523,11 +1523,14 @@ RefTestData ref_equal_test_data[] = {
{".a :is(.b, .c)::slotted(.d)", ".a .b::slotted(.d), .a .c::slotted(.d)"},
{".a + :is(.b, .c)::slotted(.d)",
".a + .b::slotted(.d), .a + .c::slotted(.d)"},
{".a::slotted(:is(.b, .c))", ".a::slotted(.b), .a::slotted(.c)"},
{":is(.a, .b)::cue(i)", ".a::cue(i), .b::cue(i)"},
{".a :is(.b, .c)::cue(i)", ".a .b::cue(i), .a .c::cue(i)"},
{".a + :is(.b, .c)::cue(i)", ".a + .b::cue(i), .a + .c::cue(i)"},
{".a::cue(:is(.b, .c))", ".a::cue(.b), .a::cue(.c)"},
{":is(.a, :host + .b, .c) .d", ".a .d, :host + .b .d, .c .d"},
{":is(.a, :host(.b) .c, .d) div", ".a div, :host(.b) .c div, .d div"},
{".a::host(:is(.b, .c))", ".a::host(.b), .a::host(.c)"},
{".a :is(.b, .c)::part(foo)", ".a .b::part(foo), .a .c::part(foo)"},
{":is(.a, .b)::part(foo)", ".a::part(foo), .b::part(foo)"},
{":is(.a, .b) :is(.c, .d)::part(foo)",
......
......@@ -3,7 +3,7 @@ PASS Multiple selectors with combinators
PASS Nested :is
PASS Nested :where
PASS Nested inside :host, without combinators
FAIL Nested inside :host, with combinators assert_not_equals: Nested inside :host, with combinators: :host(:is(div .foo)) got disallowed value ":host(:is(div .foo))"
PASS Nested inside :host, with combinators
PASS Pseudo-classes inside
PASS Pseudo-classes after
PASS Pseudo-elements after
......
<!DOCTYPE html>
<title>:is() inside shadow pseudos</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors-4/#matches">
<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
<link rel="help" href="https://drafts.csswg.org/css-scoping/#slotted-pseudo">
<div class="parent1"><div id="host1" class=a><p class=e>::slotted</p></div></div>
<div class="parent2"><div id="host2" class=b><p class=d>::slotted</p></div></div>
<div class="parent3"><div id="host3" class=c><p class=f>::slotted</p></div></div>
</div>
<script>
let shadow1 = host1.attachShadow({ mode: 'open' });
let shadow2 = host2.attachShadow({ mode: 'open' });
let shadow3 = host3.attachShadow({ mode: 'open' });
const html = `
<style>
* { color: blue; }
:host(:is(.a, .b)) b { color: green; }
:host-context(:is(.parent1, .parent2)) i { color: green; }
::slotted(:is(.e, .f)) { color: green; }
/* The following should not match: */
:host(:is(.z)) b { color: red; }
:host(:is(.a + .b)) b { color: red; }
:host-context(:is(.z)) i { color: red; }
:host-context(:is(.parent1 .parent2)) i { color: red; }
::slotted(:is(.z)) { color: red; }
::slotted(:is(.a > .b)) { color: red; }
</style>
<b>:host</b>
<i>:host-context</i>
<slot></slot>
`;
shadow1.innerHTML = html;
shadow2.innerHTML = html;
shadow3.innerHTML = html;
const getComputedColor = e => getComputedStyle(e).color;
const green = 'rgb(0, 128, 0)';
const blue = 'rgb(0, 0, 255)';
test(function() {
assert_equals(getComputedColor(shadow1.querySelector('b')), green);
assert_equals(getComputedColor(shadow2.querySelector('b')), green);
assert_equals(getComputedColor(shadow3.querySelector('b')), blue);
}, ':is() inside :host()');
test(function() {
assert_equals(getComputedColor(shadow1.querySelector('i')), green);
assert_equals(getComputedColor(shadow2.querySelector('i')), green);
assert_equals(getComputedColor(shadow3.querySelector('i')), blue);
}, ':is() inside :host-context()');
test(function() {
assert_equals(getComputedColor(document.querySelector('.e')), green);
assert_equals(getComputedColor(document.querySelector('.d')), blue);
assert_equals(getComputedColor(document.querySelector('.f')), green);
}, ':is() inside ::slotted()');
</script>
<!DOCTYPE html>
<title>Test that the :is() pseudo-class works in ::cue()</title>
<script src="../media-controls.js"></script>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<style>
video::cue { color: green; }
video::cue(:is(.red, .red2)) { color: red; }
</style>
<video>
<track src="captions-webvtt/styling.vtt" kind="captions" default>
</video>
<script>
async_test(function(t) {
var video = document.querySelector("video");
video.src = "../content/test.ogv";
video.onseeked = t.step_func_done(function() {
var cueNode = textTrackCueElementByIndex(video, 0).firstChild.firstElementChild;
var cueStyle = getComputedStyle(cueNode);
assert_equals(cueStyle.color, "rgb(255, 0, 0)");
cueNode = cueNode.nextElementSibling;
cueStyle = getComputedStyle(cueNode);
assert_equals(cueStyle.color, "rgb(0, 128, 0)");
cueNode = cueNode.nextElementSibling;
cueStyle = getComputedStyle(cueNode);
assert_equals(cueStyle.color, "rgb(255, 0, 0)");
});
video.currentTime = 0.3;
});
</script>
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