mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-28 11:49:44 +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;
|
||||
}
|
||||
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: {
|
||||
// <pt-name-selector> = '*' | <custom-ident>
|
||||
// https://drafts.csswg.org/css-view-transitions-1/#typedef-pt-name-selector
|
||||
|
|
|
@ -110,6 +110,11 @@
|
|||
"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": {
|
||||
"spec": "https://drafts.csswg.org/css-view-transitions-1/#selectordef-view-transition",
|
||||
"is-pseudo-root": true
|
||||
|
|
|
@ -294,6 +294,11 @@ String Selector::PseudoElementSelector::serialize() const
|
|||
}
|
||||
|
||||
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.append('(');
|
||||
if (pt_name_selector.is_universal)
|
||||
|
|
|
@ -31,7 +31,7 @@ public:
|
|||
FlyString value {};
|
||||
};
|
||||
|
||||
using Value = Variant<Empty, PTNameSelector>;
|
||||
using Value = Variant<Empty, PTNameSelector, NonnullRefPtr<Selector>>;
|
||||
|
||||
explicit PseudoElementSelector(PseudoElement type, Value value = {})
|
||||
: m_type(type)
|
||||
|
@ -60,10 +60,13 @@ public:
|
|||
|
||||
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:
|
||||
PseudoElement m_type;
|
||||
String m_name;
|
||||
Variant<Empty, PTNameSelector> m_value;
|
||||
Value m_value;
|
||||
};
|
||||
|
||||
struct SimpleSelector {
|
||||
|
|
|
@ -613,6 +613,7 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector, int in
|
|||
|
||||
switch (pseudo_element_metadata.parameter_type) {
|
||||
case CSS::PseudoElementMetadata::ParameterType::None:
|
||||
case CSS::PseudoElementMetadata::ParameterType::CompoundSelector:
|
||||
break;
|
||||
case CSS::PseudoElementMetadata::ParameterType::PTNameSelector: {
|
||||
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());
|
||||
|
||||
// 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 = {};
|
||||
}
|
||||
// 3. Otherwise let obj be the given pseudo-element of elt.
|
||||
|
|
|
@ -92,6 +92,7 @@ bool pseudo_element_supports_property(PseudoElement, PropertyID);
|
|||
struct PseudoElementMetadata {
|
||||
enum class ParameterType {
|
||||
None,
|
||||
CompoundSelector,
|
||||
PTNameSelector,
|
||||
} parameter_type;
|
||||
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();
|
||||
if (function_syntax == "<pt-name-selector>"sv) {
|
||||
parameter_type = "PTNameSelector"_string;
|
||||
} else if (function_syntax == "<compound-selector>"sv) {
|
||||
parameter_type = "CompoundSelector"_string;
|
||||
} else {
|
||||
warnln("Unrecognized pseudo-element parameter type: `{}`", function_syntax);
|
||||
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
|
||||
|
||||
1959 Pass
|
||||
16 Fail
|
||||
1975 Pass
|
||||
Pass Selectors-API Test Suite: HTML
|
||||
Pass Document supports querySelector
|
||||
Pass Document supports querySelectorAll
|
||||
|
@ -819,10 +818,10 @@ Pass Document.querySelector: Syntax, group of selectors separator, whitespace be
|
|||
,#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
|
||||
Fail Document.querySelectorAll: Slotted selector: ::slotted(foo)
|
||||
Fail Document.querySelector: Slotted selector: ::slotted(foo)
|
||||
Fail Document.querySelectorAll: Slotted selector (no matching closing paren): ::slotted(foo
|
||||
Fail Document.querySelector: Slotted selector (no matching closing paren): ::slotted(foo
|
||||
Pass Document.querySelectorAll: Slotted selector: ::slotted(foo)
|
||||
Pass Document.querySelector: Slotted selector: ::slotted(foo)
|
||||
Pass Document.querySelectorAll: 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.querySelector: Type selector, matching html element: html
|
||||
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
|
||||
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
|
||||
Fail Detached Element.querySelectorAll: Slotted selector: ::slotted(foo)
|
||||
Fail Detached Element.querySelector: Slotted selector: ::slotted(foo)
|
||||
Fail 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.querySelectorAll: Slotted selector: ::slotted(foo)
|
||||
Pass Detached Element.querySelector: Slotted selector: ::slotted(foo)
|
||||
Pass Detached Element.querySelectorAll: 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.querySelector: Type selector, matching html element: html
|
||||
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
|
||||
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
|
||||
Fail Fragment.querySelectorAll: Slotted selector: ::slotted(foo)
|
||||
Fail Fragment.querySelector: Slotted selector: ::slotted(foo)
|
||||
Fail Fragment.querySelectorAll: Slotted selector (no matching closing paren): ::slotted(foo
|
||||
Fail Fragment.querySelector: Slotted selector (no matching closing paren): ::slotted(foo
|
||||
Pass Fragment.querySelectorAll: Slotted selector: ::slotted(foo)
|
||||
Pass Fragment.querySelector: Slotted selector: ::slotted(foo)
|
||||
Pass Fragment.querySelectorAll: 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.querySelector: Type selector, matching html element: html
|
||||
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
|
||||
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
|
||||
Fail In-document Element.querySelectorAll: Slotted selector: ::slotted(foo)
|
||||
Fail In-document Element.querySelector: Slotted selector: ::slotted(foo)
|
||||
Fail 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.querySelectorAll: Slotted selector: ::slotted(foo)
|
||||
Pass In-document Element.querySelector: Slotted selector: ::slotted(foo)
|
||||
Pass In-document Element.querySelectorAll: 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