LibWeb: Parse the ::part() pseudo-element selector

It doesn't yet do anything, but it helps a few tests that just check
serialization.
This commit is contained in:
Sam Atkins 2025-12-05 12:03:20 +00:00
parent 01b7800068
commit 16c12c1485
9 changed files with 49 additions and 15 deletions

View file

@ -534,6 +534,24 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
value = Selector::create(move(compound_selectors));
break;
}
case PseudoElementMetadata::ParameterType::IdentList: {
// <ident>+
Selector::PseudoElementSelector::IdentList idents;
while (function_tokens.has_next_token()) {
if (!function_tokens.next_token().is(Token::Type::Ident)) {
ErrorReporter::the().report(InvalidPseudoClassOrElementError {
.name = MUST(String::formatted("::{}", pseudo_name)),
.value_string = name_token.to_string(),
.description = "Contains invalid <ident>."_string,
});
return ParseError::SyntaxError;
}
idents.append(function_tokens.consume_a_token().token().ident());
function_tokens.discard_whitespace();
}
value = move(idents);
break;
}
case PseudoElementMetadata::ParameterType::PTNameSelector: {
// <pt-name-selector> = '*' | <custom-ident>
// https://drafts.csswg.org/css-view-transitions-1/#typedef-pt-name-selector

View file

@ -76,6 +76,11 @@
"marker": {
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-marker"
},
"part": {
"spec": "https://drafts.csswg.org/css-shadow-parts/#selectordef-part",
"type": "function",
"function-syntax": "<ident>+"
},
"placeholder": {
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-placeholder",
"property-whitelist": [

View file

@ -308,9 +308,9 @@ String Selector::PseudoElementSelector::serialize() const
}
m_value.visit(
[&builder](NonnullRefPtr<Selector> const& compund_selector) {
[&builder](NonnullRefPtr<Selector> const& compound_selector) {
builder.append('(');
builder.append(compund_selector->serialize());
builder.append(compound_selector->serialize());
builder.append(')');
},
[&builder](PTNameSelector const& pt_name_selector) {
@ -321,6 +321,11 @@ String Selector::PseudoElementSelector::serialize() const
builder.append(pt_name_selector.value);
builder.append(')');
},
[&builder](IdentList const& ident_list) {
builder.append('(');
builder.join(' ', ident_list);
builder.append(')');
},
[](Empty const&) {});
return builder.to_string_without_validation();

View file

@ -30,8 +30,9 @@ public:
bool is_universal { false };
FlyString value {};
};
using IdentList = Vector<FlyString>;
using Value = Variant<Empty, PTNameSelector, NonnullRefPtr<Selector>>;
using Value = Variant<Empty, PTNameSelector, NonnullRefPtr<Selector>, IdentList>;
explicit PseudoElementSelector(PseudoElement type, Value value = {})
: m_type(type)
@ -63,6 +64,8 @@ public:
// NOTE: This can't (currently) be a CompoundSelector due to cyclic dependencies.
Selector const& compound_selector() const { return m_value.get<NonnullRefPtr<Selector>>(); }
IdentList const& ident_list() const { return m_value.get<IdentList>(); }
private:
PseudoElement m_type;
String m_name;

View file

@ -595,6 +595,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:
case CSS::PseudoElementMetadata::ParameterType::IdentList:
break;
case CSS::PseudoElementMetadata::ParameterType::PTNameSelector: {
auto const& [is_universal, value] = pseudo_element.pt_name_selector();

View file

@ -1300,8 +1300,7 @@ GC::Ref<CSS::CSSStyleProperties> Window::get_computed_style(DOM::Element& elemen
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.
// FIXME: Handle ::part() here too when we support it.
if (!type.has_value() || type.value().type() == CSS::PseudoElement::Slotted) {
if (!type.has_value() || first_is_one_of(type.value().type(), CSS::PseudoElement::Slotted, CSS::PseudoElement::Part)) {
object = {};
}
// 3. Otherwise let obj be the given pseudo-element of elt.

View file

@ -94,6 +94,7 @@ struct PseudoElementMetadata {
enum class ParameterType {
None,
CompoundSelector,
IdentList,
PTNameSelector,
} parameter_type;
bool is_valid_as_function;
@ -515,10 +516,12 @@ PseudoElementMetadata pseudo_element_metadata(PseudoElement pseudo_element)
String parameter_type = "None"_string;
if (is_valid_as_function) {
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) {
if (function_syntax == "<compound-selector>"sv) {
parameter_type = "CompoundSelector"_string;
} else if (function_syntax == "<ident>+"sv) {
parameter_type = "IdentList"_string;
} else if (function_syntax == "<pt-name-selector>"sv) {
parameter_type = "PTNameSelector"_string;
} else {
warnln("Unrecognized pseudo-element parameter type: `{}`", function_syntax);
VERIFY_NOT_REACHED();

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 33 tests
24 Pass
9 Fail
26 Pass
7 Fail
Pass ":is(div )" should be a valid selector
Pass ":where(div )" should be a valid selector
Pass ":is(div + bar, div ~ .baz)" should be a valid selector
@ -28,8 +28,8 @@ Fail ":is(::before)" should be a valid selector
Fail ":where(::before)" should be a valid selector
Pass ":is(div) + bar" should be a valid selector
Pass ":where(div) + bar" should be a valid selector
Fail "::part(foo):is(:hover)" should be a valid selector
Fail "::part(foo):where(:hover)" should be a valid selector
Pass "::part(foo):is(:hover)" should be a valid selector
Pass "::part(foo):where(:hover)" should be a valid selector
Fail "::part(foo):is([attr='value'])" should be a valid selector
Fail "::part(foo):where([attr='value'])" should be a valid selector
Pass ":not(:is(div))" should be a valid selector

View file

@ -2,12 +2,12 @@ Harness status: OK
Found 8 tests
6 Pass
2 Fail
7 Pass
1 Fail
Pass :state() parsing passes
Pass :state() parsing failures
Pass deprecated :--state parsing failures
Fail :state(foo) serialization
Pass :state(foo) serialization
Pass :state(foo) in simple cases
Pass :state(foo) and other pseudo classes
Fail :state(foo) and ::part()