mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-06 08:10:02 +00:00
LibWeb/CSS: Parse the ::slotted pseudo-element
This commit is contained in:
parent
0151a088ad
commit
9054ff29f0
Notes:
github-actions[bot]
2025-07-15 12:55:25 +00:00
Author: https://github.com/shannonbooth
Commit: 9054ff29f0
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5414
Reviewed-by: https://github.com/AtkinsSJ ✅
10 changed files with 141 additions and 21 deletions
|
@ -466,6 +466,20 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case PseudoElementMetadata::ParameterType::CompoundSelector: {
|
||||||
|
auto compound_selector_or_error = parse_compound_selector(function_tokens);
|
||||||
|
if (compound_selector_or_error.is_error() || !compound_selector_or_error.value().has_value()) {
|
||||||
|
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse ::{}() parameter as a compound selector", pseudo_name);
|
||||||
|
return ParseError::SyntaxError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto compound_selector = compound_selector_or_error.release_value().release_value();
|
||||||
|
compound_selector.combinator = Selector::Combinator::None;
|
||||||
|
|
||||||
|
Vector compound_selectors { move(compound_selector) };
|
||||||
|
value = Selector::create(move(compound_selectors));
|
||||||
|
break;
|
||||||
|
}
|
||||||
case PseudoElementMetadata::ParameterType::PTNameSelector: {
|
case PseudoElementMetadata::ParameterType::PTNameSelector: {
|
||||||
// <pt-name-selector> = '*' | <custom-ident>
|
// <pt-name-selector> = '*' | <custom-ident>
|
||||||
// https://drafts.csswg.org/css-view-transitions-1/#typedef-pt-name-selector
|
// https://drafts.csswg.org/css-view-transitions-1/#typedef-pt-name-selector
|
||||||
|
|
|
@ -110,6 +110,11 @@
|
||||||
"slider-track": {
|
"slider-track": {
|
||||||
"spec": "https://drafts.csswg.org/css-forms-1/#selectordef-slider-track"
|
"spec": "https://drafts.csswg.org/css-forms-1/#selectordef-slider-track"
|
||||||
},
|
},
|
||||||
|
"slotted": {
|
||||||
|
"spec": "https://drafts.csswg.org/css-scoping/#slotted-pseudo",
|
||||||
|
"type": "function",
|
||||||
|
"function-syntax": "<compound-selector>"
|
||||||
|
},
|
||||||
"view-transition": {
|
"view-transition": {
|
||||||
"spec": "https://drafts.csswg.org/css-view-transitions-1/#selectordef-view-transition",
|
"spec": "https://drafts.csswg.org/css-view-transitions-1/#selectordef-view-transition",
|
||||||
"is-pseudo-root": true
|
"is-pseudo-root": true
|
||||||
|
|
|
@ -294,6 +294,11 @@ String Selector::PseudoElementSelector::serialize() const
|
||||||
}
|
}
|
||||||
|
|
||||||
m_value.visit(
|
m_value.visit(
|
||||||
|
[&builder](NonnullRefPtr<Selector> const& compund_selector) {
|
||||||
|
builder.append('(');
|
||||||
|
builder.append(compund_selector->serialize());
|
||||||
|
builder.append(')');
|
||||||
|
},
|
||||||
[&builder](PTNameSelector const& pt_name_selector) {
|
[&builder](PTNameSelector const& pt_name_selector) {
|
||||||
builder.append('(');
|
builder.append('(');
|
||||||
if (pt_name_selector.is_universal)
|
if (pt_name_selector.is_universal)
|
||||||
|
|
|
@ -31,7 +31,7 @@ public:
|
||||||
FlyString value {};
|
FlyString value {};
|
||||||
};
|
};
|
||||||
|
|
||||||
using Value = Variant<Empty, PTNameSelector>;
|
using Value = Variant<Empty, PTNameSelector, NonnullRefPtr<Selector>>;
|
||||||
|
|
||||||
explicit PseudoElementSelector(PseudoElement type, Value value = {})
|
explicit PseudoElementSelector(PseudoElement type, Value value = {})
|
||||||
: m_type(type)
|
: m_type(type)
|
||||||
|
@ -60,10 +60,13 @@ public:
|
||||||
|
|
||||||
PTNameSelector const& pt_name_selector() const { return m_value.get<PTNameSelector>(); }
|
PTNameSelector const& pt_name_selector() const { return m_value.get<PTNameSelector>(); }
|
||||||
|
|
||||||
|
// NOTE: This can't (currently) be a CompoundSelector due to cyclic dependencies.
|
||||||
|
Selector const& compound_selector() const { return m_value.get<NonnullRefPtr<Selector>>(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PseudoElement m_type;
|
PseudoElement m_type;
|
||||||
String m_name;
|
String m_name;
|
||||||
Variant<Empty, PTNameSelector> m_value;
|
Value m_value;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SimpleSelector {
|
struct SimpleSelector {
|
||||||
|
|
|
@ -613,6 +613,7 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector, int in
|
||||||
|
|
||||||
switch (pseudo_element_metadata.parameter_type) {
|
switch (pseudo_element_metadata.parameter_type) {
|
||||||
case CSS::PseudoElementMetadata::ParameterType::None:
|
case CSS::PseudoElementMetadata::ParameterType::None:
|
||||||
|
case CSS::PseudoElementMetadata::ParameterType::CompoundSelector:
|
||||||
break;
|
break;
|
||||||
case CSS::PseudoElementMetadata::ParameterType::PTNameSelector: {
|
case CSS::PseudoElementMetadata::ParameterType::PTNameSelector: {
|
||||||
auto const& [is_universal, value] = pseudo_element.pt_name_selector();
|
auto const& [is_universal, value] = pseudo_element.pt_name_selector();
|
||||||
|
|
|
@ -1286,7 +1286,8 @@ GC::Ref<CSS::CSSStyleDeclaration> Window::get_computed_style(DOM::Element& eleme
|
||||||
auto type = parse_pseudo_element_selector(CSS::Parser::ParsingParams(associated_document()), pseudo_element.value());
|
auto type = parse_pseudo_element_selector(CSS::Parser::ParsingParams(associated_document()), pseudo_element.value());
|
||||||
|
|
||||||
// 2. If type is failure, or is a ::slotted() or ::part() pseudo-element, let obj be null.
|
// 2. If type is failure, or is a ::slotted() or ::part() pseudo-element, let obj be null.
|
||||||
if (!type.has_value()) {
|
// FIXME: Handle ::part() here too when we support it.
|
||||||
|
if (!type.has_value() || type.value().type() == CSS::PseudoElement::Slotted) {
|
||||||
object = {};
|
object = {};
|
||||||
}
|
}
|
||||||
// 3. Otherwise let obj be the given pseudo-element of elt.
|
// 3. Otherwise let obj be the given pseudo-element of elt.
|
||||||
|
|
|
@ -92,6 +92,7 @@ bool pseudo_element_supports_property(PseudoElement, PropertyID);
|
||||||
struct PseudoElementMetadata {
|
struct PseudoElementMetadata {
|
||||||
enum class ParameterType {
|
enum class ParameterType {
|
||||||
None,
|
None,
|
||||||
|
CompoundSelector,
|
||||||
PTNameSelector,
|
PTNameSelector,
|
||||||
} parameter_type;
|
} parameter_type;
|
||||||
bool is_valid_as_function;
|
bool is_valid_as_function;
|
||||||
|
@ -515,6 +516,8 @@ PseudoElementMetadata pseudo_element_metadata(PseudoElement pseudo_element)
|
||||||
auto const& function_syntax = pseudo_element.get_string("function-syntax"sv).value();
|
auto const& function_syntax = pseudo_element.get_string("function-syntax"sv).value();
|
||||||
if (function_syntax == "<pt-name-selector>"sv) {
|
if (function_syntax == "<pt-name-selector>"sv) {
|
||||||
parameter_type = "PTNameSelector"_string;
|
parameter_type = "PTNameSelector"_string;
|
||||||
|
} else if (function_syntax == "<compound-selector>"sv) {
|
||||||
|
parameter_type = "CompoundSelector"_string;
|
||||||
} else {
|
} else {
|
||||||
warnln("Unrecognized pseudo-element parameter type: `{}`", function_syntax);
|
warnln("Unrecognized pseudo-element parameter type: `{}`", function_syntax);
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
Harness status: OK
|
||||||
|
|
||||||
|
Found 32 tests
|
||||||
|
|
||||||
|
13 Pass
|
||||||
|
19 Fail
|
||||||
|
Pass "::slotted" should be an invalid selector
|
||||||
|
Pass "::slotted()" should be an invalid selector
|
||||||
|
Fail "::slotted(*).class" should be an invalid selector
|
||||||
|
Pass "::slotted(*)#id {}" should be an invalid selector
|
||||||
|
Fail "::slotted(*)[attr]" should be an invalid selector
|
||||||
|
Fail "::slotted(*):host" should be an invalid selector
|
||||||
|
Fail "::slotted(*):host(div)" should be an invalid selector
|
||||||
|
Fail "::slotted(*):hover" should be an invalid selector
|
||||||
|
Fail "::slotted(*):read-only" should be an invalid selector
|
||||||
|
Fail "::slotted(*)::slotted(*)" should be an invalid selector
|
||||||
|
Fail "::slotted(*)::before::slotted(*)" should be an invalid selector
|
||||||
|
Fail "::slotted(*) span" should be an invalid selector
|
||||||
|
Pass "::slotted(*)" should be a valid selector
|
||||||
|
Pass "::slotted(div)" should be a valid selector
|
||||||
|
Pass "::slotted([attr]:hover)" should be a valid selector
|
||||||
|
Pass "::slotted(:not(.a))" should be a valid selector
|
||||||
|
Fail "::slotted(*):is()" should be a valid selector
|
||||||
|
Fail "::slotted(*):is(:hover)" should be a valid selector
|
||||||
|
Fail "::slotted(*):is(#id)" should be a valid selector
|
||||||
|
Fail "::slotted(*):where()" should be a valid selector
|
||||||
|
Fail "::slotted(*):where(:hover)" should be a valid selector
|
||||||
|
Fail "::slotted(*):where(#id)" should be a valid selector
|
||||||
|
Fail "::slotted(*):where(::before)" should be a valid selector
|
||||||
|
Pass "::slotted(*)::before" should be a valid selector
|
||||||
|
Pass "::slotted(*)::after" should be a valid selector
|
||||||
|
Pass "::slotted(*)::details-content" should be a valid selector
|
||||||
|
Pass "::slotted(*)::file-selector-button" should be a valid selector
|
||||||
|
Pass "::slotted(*)::placeholder" should be a valid selector
|
||||||
|
Pass "::slotted(*)::marker" should be a valid selector
|
||||||
|
Fail "::slotted(*)::first-line" should be an invalid selector
|
||||||
|
Fail "::slotted(*)::first-letter" should be an invalid selector
|
||||||
|
Fail "::slotted(*)::selection" should be an invalid selector
|
|
@ -2,8 +2,7 @@ Harness status: OK
|
||||||
|
|
||||||
Found 1975 tests
|
Found 1975 tests
|
||||||
|
|
||||||
1959 Pass
|
1975 Pass
|
||||||
16 Fail
|
|
||||||
Pass Selectors-API Test Suite: HTML
|
Pass Selectors-API Test Suite: HTML
|
||||||
Pass Document supports querySelector
|
Pass Document supports querySelector
|
||||||
Pass Document supports querySelectorAll
|
Pass Document supports querySelectorAll
|
||||||
|
@ -819,10 +818,10 @@ Pass Document.querySelector: Syntax, group of selectors separator, whitespace be
|
||||||
,#group strong
|
,#group strong
|
||||||
Pass Document.querySelectorAll: Syntax, group of selectors separator, no whitespace: #group em,#group strong
|
Pass Document.querySelectorAll: Syntax, group of selectors separator, no whitespace: #group em,#group strong
|
||||||
Pass Document.querySelector: Syntax, group of selectors separator, no whitespace: #group em,#group strong
|
Pass Document.querySelector: Syntax, group of selectors separator, no whitespace: #group em,#group strong
|
||||||
Fail Document.querySelectorAll: Slotted selector: ::slotted(foo)
|
Pass Document.querySelectorAll: Slotted selector: ::slotted(foo)
|
||||||
Fail Document.querySelector: Slotted selector: ::slotted(foo)
|
Pass Document.querySelector: Slotted selector: ::slotted(foo)
|
||||||
Fail Document.querySelectorAll: Slotted selector (no matching closing paren): ::slotted(foo
|
Pass Document.querySelectorAll: Slotted selector (no matching closing paren): ::slotted(foo
|
||||||
Fail Document.querySelector: Slotted selector (no matching closing paren): ::slotted(foo
|
Pass Document.querySelector: Slotted selector (no matching closing paren): ::slotted(foo
|
||||||
Pass Detached Element.querySelectorAll: Type selector, matching html element: html
|
Pass Detached Element.querySelectorAll: Type selector, matching html element: html
|
||||||
Pass Detached Element.querySelector: Type selector, matching html element: html
|
Pass Detached Element.querySelector: Type selector, matching html element: html
|
||||||
Pass Detached Element.querySelectorAll: Type selector, matching body element: body
|
Pass Detached Element.querySelectorAll: Type selector, matching body element: body
|
||||||
|
@ -1249,10 +1248,10 @@ Pass Detached Element.querySelector: Syntax, group of selectors separator, white
|
||||||
,#group strong
|
,#group strong
|
||||||
Pass Detached Element.querySelectorAll: Syntax, group of selectors separator, no whitespace: #group em,#group strong
|
Pass Detached Element.querySelectorAll: Syntax, group of selectors separator, no whitespace: #group em,#group strong
|
||||||
Pass Detached Element.querySelector: Syntax, group of selectors separator, no whitespace: #group em,#group strong
|
Pass Detached Element.querySelector: Syntax, group of selectors separator, no whitespace: #group em,#group strong
|
||||||
Fail Detached Element.querySelectorAll: Slotted selector: ::slotted(foo)
|
Pass Detached Element.querySelectorAll: Slotted selector: ::slotted(foo)
|
||||||
Fail Detached Element.querySelector: Slotted selector: ::slotted(foo)
|
Pass Detached Element.querySelector: Slotted selector: ::slotted(foo)
|
||||||
Fail Detached Element.querySelectorAll: Slotted selector (no matching closing paren): ::slotted(foo
|
Pass Detached Element.querySelectorAll: Slotted selector (no matching closing paren): ::slotted(foo
|
||||||
Fail Detached Element.querySelector: Slotted selector (no matching closing paren): ::slotted(foo
|
Pass Detached Element.querySelector: Slotted selector (no matching closing paren): ::slotted(foo
|
||||||
Pass Fragment.querySelectorAll: Type selector, matching html element: html
|
Pass Fragment.querySelectorAll: Type selector, matching html element: html
|
||||||
Pass Fragment.querySelector: Type selector, matching html element: html
|
Pass Fragment.querySelector: Type selector, matching html element: html
|
||||||
Pass Fragment.querySelectorAll: Type selector, matching body element: body
|
Pass Fragment.querySelectorAll: Type selector, matching body element: body
|
||||||
|
@ -1679,10 +1678,10 @@ Pass Fragment.querySelector: Syntax, group of selectors separator, whitespace be
|
||||||
,#group strong
|
,#group strong
|
||||||
Pass Fragment.querySelectorAll: Syntax, group of selectors separator, no whitespace: #group em,#group strong
|
Pass Fragment.querySelectorAll: Syntax, group of selectors separator, no whitespace: #group em,#group strong
|
||||||
Pass Fragment.querySelector: Syntax, group of selectors separator, no whitespace: #group em,#group strong
|
Pass Fragment.querySelector: Syntax, group of selectors separator, no whitespace: #group em,#group strong
|
||||||
Fail Fragment.querySelectorAll: Slotted selector: ::slotted(foo)
|
Pass Fragment.querySelectorAll: Slotted selector: ::slotted(foo)
|
||||||
Fail Fragment.querySelector: Slotted selector: ::slotted(foo)
|
Pass Fragment.querySelector: Slotted selector: ::slotted(foo)
|
||||||
Fail Fragment.querySelectorAll: Slotted selector (no matching closing paren): ::slotted(foo
|
Pass Fragment.querySelectorAll: Slotted selector (no matching closing paren): ::slotted(foo
|
||||||
Fail Fragment.querySelector: Slotted selector (no matching closing paren): ::slotted(foo
|
Pass Fragment.querySelector: Slotted selector (no matching closing paren): ::slotted(foo
|
||||||
Pass In-document Element.querySelectorAll: Type selector, matching html element: html
|
Pass In-document Element.querySelectorAll: Type selector, matching html element: html
|
||||||
Pass In-document Element.querySelector: Type selector, matching html element: html
|
Pass In-document Element.querySelector: Type selector, matching html element: html
|
||||||
Pass In-document Element.querySelectorAll: Type selector, matching body element: body
|
Pass In-document Element.querySelectorAll: Type selector, matching body element: body
|
||||||
|
@ -2111,7 +2110,7 @@ Pass In-document Element.querySelector: Syntax, group of selectors separator, wh
|
||||||
,#group strong
|
,#group strong
|
||||||
Pass In-document Element.querySelectorAll: Syntax, group of selectors separator, no whitespace: #group em,#group strong
|
Pass In-document Element.querySelectorAll: Syntax, group of selectors separator, no whitespace: #group em,#group strong
|
||||||
Pass In-document Element.querySelector: Syntax, group of selectors separator, no whitespace: #group em,#group strong
|
Pass In-document Element.querySelector: Syntax, group of selectors separator, no whitespace: #group em,#group strong
|
||||||
Fail In-document Element.querySelectorAll: Slotted selector: ::slotted(foo)
|
Pass In-document Element.querySelectorAll: Slotted selector: ::slotted(foo)
|
||||||
Fail In-document Element.querySelector: Slotted selector: ::slotted(foo)
|
Pass In-document Element.querySelector: Slotted selector: ::slotted(foo)
|
||||||
Fail In-document Element.querySelectorAll: Slotted selector (no matching closing paren): ::slotted(foo
|
Pass In-document Element.querySelectorAll: Slotted selector (no matching closing paren): ::slotted(foo
|
||||||
Fail In-document Element.querySelector: Slotted selector (no matching closing paren): ::slotted(foo
|
Pass In-document Element.querySelector: Slotted selector (no matching closing paren): ::slotted(foo
|
|
@ -0,0 +1,51 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>CSS Scoping: ::slotted pseudo parsing</title>
|
||||||
|
<link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css-scoping/#slotted-pseudo">
|
||||||
|
<script src="../../resources/testharness.js"></script>
|
||||||
|
<script src="../../resources/testharnessreport.js"></script>
|
||||||
|
<script src="../../css/support/parsing-testcommon.js"></script>
|
||||||
|
<style id="styleElm">
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
test_invalid_selector("::slotted");
|
||||||
|
test_invalid_selector("::slotted()");
|
||||||
|
test_invalid_selector("::slotted(*).class");
|
||||||
|
test_invalid_selector("::slotted(*)#id {}");
|
||||||
|
test_invalid_selector("::slotted(*)[attr]");
|
||||||
|
test_invalid_selector("::slotted(*):host");
|
||||||
|
test_invalid_selector("::slotted(*):host(div)");
|
||||||
|
test_invalid_selector("::slotted(*):hover");
|
||||||
|
test_invalid_selector("::slotted(*):read-only");
|
||||||
|
test_invalid_selector("::slotted(*)::slotted(*)");
|
||||||
|
test_invalid_selector("::slotted(*)::before::slotted(*)");
|
||||||
|
test_invalid_selector("::slotted(*) span");
|
||||||
|
|
||||||
|
test_valid_selector("::slotted(*)");
|
||||||
|
test_valid_selector("::slotted(div)");
|
||||||
|
test_valid_selector("::slotted([attr]:hover)");
|
||||||
|
test_valid_selector("::slotted(:not(.a))");
|
||||||
|
|
||||||
|
test_valid_forgiving_selector("::slotted(*):is()");
|
||||||
|
test_valid_forgiving_selector("::slotted(*):is(:hover)");
|
||||||
|
test_valid_forgiving_selector("::slotted(*):is(#id)");
|
||||||
|
|
||||||
|
test_valid_forgiving_selector("::slotted(*):where()");
|
||||||
|
test_valid_forgiving_selector("::slotted(*):where(:hover)");
|
||||||
|
test_valid_forgiving_selector("::slotted(*):where(#id)");
|
||||||
|
test_valid_forgiving_selector("::slotted(*):where(::before)");
|
||||||
|
|
||||||
|
// Allow tree-abiding pseudo elements after ::slotted
|
||||||
|
test_valid_selector("::slotted(*)::before");
|
||||||
|
test_valid_selector("::slotted(*)::after");
|
||||||
|
test_valid_selector("::slotted(*)::details-content");
|
||||||
|
test_valid_selector("::slotted(*)::file-selector-button");
|
||||||
|
test_valid_selector("::slotted(*)::placeholder");
|
||||||
|
test_valid_selector("::slotted(*)::marker");
|
||||||
|
|
||||||
|
// Other pseudo elements not valid after ::slotted
|
||||||
|
test_invalid_selector("::slotted(*)::first-line");
|
||||||
|
test_invalid_selector("::slotted(*)::first-letter");
|
||||||
|
test_invalid_selector("::slotted(*)::selection");
|
||||||
|
</script>
|
Loading…
Add table
Add a link
Reference in a new issue