From 7aed541ed01cce12f31eecb6595cdb55d4a9147f Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 16 May 2025 14:24:40 +0100 Subject: [PATCH] LibWeb/CSS: Automatically serialize functional pseudo-class arguments The spec gives us a hard-coded list of functional pseudo-classes and how to serialize them - but this list is incomplete and likely to always be outdated compared to the list of pseudo-classes that exist. So instead, use the generated metadata we already have to serialize their arguments based on their type. This fixes :dir() and :has(), which previously did not serialize their arguments. Gets us 26 passes (including 6 from that as-yet-unmerged :dir() test). --- Libraries/LibWeb/CSS/Selector.cpp | 28 ++++++++---- .../Text/expected/css/parse-dir-selector.txt | 16 +++---- .../css/selectors/parsing/parse-has.txt | 44 +++++++++---------- 3 files changed, 49 insertions(+), 39 deletions(-) diff --git a/Libraries/LibWeb/CSS/Selector.cpp b/Libraries/LibWeb/CSS/Selector.cpp index 4e054a31160..0cf2b34e3b3 100644 --- a/Libraries/LibWeb/CSS/Selector.cpp +++ b/Libraries/LibWeb/CSS/Selector.cpp @@ -444,21 +444,31 @@ String Selector::SimpleSelector::serialize() const s.append(':'); s.append(pseudo_class_name(pseudo_class.type)); s.append('('); - if (pseudo_class.type == PseudoClass::NthChild - || pseudo_class.type == PseudoClass::NthLastChild - || pseudo_class.type == PseudoClass::NthOfType - || pseudo_class.type == PseudoClass::NthLastOfType) { + // NB: The spec list is incomplete. For ease of maintenance, we use the data from PseudoClasses.json for + // this instead of a hard-coded list. + switch (metadata.parameter_type) { + case PseudoClassMetadata::ParameterType::None: + break; + case PseudoClassMetadata::ParameterType::ANPlusB: + case PseudoClassMetadata::ParameterType::ANPlusBOf: // The result of serializing the value using the rules to serialize an value. s.append(pseudo_class.nth_child_pattern.serialize()); - } else if (pseudo_class.type == PseudoClass::Not - || pseudo_class.type == PseudoClass::Is - || pseudo_class.type == PseudoClass::Where) { + break; + case PseudoClassMetadata::ParameterType::CompoundSelector: + case PseudoClassMetadata::ParameterType::ForgivingSelectorList: + case PseudoClassMetadata::ParameterType::ForgivingRelativeSelectorList: + case PseudoClassMetadata::ParameterType::RelativeSelectorList: + case PseudoClassMetadata::ParameterType::SelectorList: // The result of serializing the value using the rules for serializing a group of selectors. - // NOTE: `:is()` and `:where()` aren't in the spec for this yet, but it should be! s.append(serialize_a_group_of_selectors(pseudo_class.argument_selector_list)); - } else if (pseudo_class.type == PseudoClass::Lang) { + break; + case PseudoClassMetadata::ParameterType::Ident: + s.append(string_from_keyword(pseudo_class.keyword.value())); + break; + case PseudoClassMetadata::ParameterType::LanguageRanges: // The serialization of a comma-separated list of each argument’s serialization as a string, preserving relative order. s.join(", "sv, pseudo_class.languages); + break; } s.append(')'); } diff --git a/Tests/LibWeb/Text/expected/css/parse-dir-selector.txt b/Tests/LibWeb/Text/expected/css/parse-dir-selector.txt index 3654f6277d2..3b331a788fb 100644 --- a/Tests/LibWeb/Text/expected/css/parse-dir-selector.txt +++ b/Tests/LibWeb/Text/expected/css/parse-dir-selector.txt @@ -2,14 +2,14 @@ Harness status: OK Found 10 tests -3 Pass -7 Fail -Fail ":dir(rtl)" should be a valid selector -Fail ":dir( rtl )" should be a valid selector -Fail ":dir(ltr):dir(rtl)" should be a valid selector -Fail "foo:dir(RTL)" should be a valid selector -Fail ":dir(auto)" should be a valid selector -Fail ":dir(none)" should be a valid selector +9 Pass +1 Fail +Pass ":dir(rtl)" should be a valid selector +Pass ":dir( rtl )" should be a valid selector +Pass ":dir(ltr):dir(rtl)" should be a valid selector +Pass "foo:dir(RTL)" should be a valid selector +Pass ":dir(auto)" should be a valid selector +Pass ":dir(none)" should be a valid selector Fail ":dir(something-made-up)" should be a valid selector Pass ":dir()" should be an invalid selector Pass ":dir(\"ltr\")" should be an invalid selector diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/selectors/parsing/parse-has.txt b/Tests/LibWeb/Text/expected/wpt-import/css/selectors/parsing/parse-has.txt index dbc9fa4fa3f..abf8e4775a5 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/selectors/parsing/parse-has.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/selectors/parsing/parse-has.txt @@ -2,31 +2,31 @@ Harness status: OK Found 29 tests -6 Pass -23 Fail -Fail ":has(a)" should be a valid selector -Fail ":has(#a)" should be a valid selector -Fail ":has(.a)" should be a valid selector -Fail ":has([a])" should be a valid selector -Fail ":has([a=\"b\"])" should be a valid selector -Fail ":has([a|=\"b\"])" should be a valid selector -Fail ":has(:hover)" should be a valid selector -Fail "*:has(.a)" should be a valid selector -Fail ".a:has(.b)" should be a valid selector +26 Pass +3 Fail +Pass ":has(a)" should be a valid selector +Pass ":has(#a)" should be a valid selector +Pass ":has(.a)" should be a valid selector +Pass ":has([a])" should be a valid selector +Pass ":has([a=\"b\"])" should be a valid selector +Pass ":has([a|=\"b\"])" should be a valid selector +Pass ":has(:hover)" should be a valid selector +Pass "*:has(.a)" should be a valid selector +Pass ".a:has(.b)" should be a valid selector Fail ".a:has(> .b)" should be a valid selector Fail ".a:has(~ .b)" should be a valid selector Fail ".a:has(+ .b)" should be a valid selector -Fail ".a:has(.b) .c" should be a valid selector -Fail ".a .b:has(.c)" should be a valid selector -Fail ".a .b:has(.c .d)" should be a valid selector -Fail ".a .b:has(.c .d) .e" should be a valid selector -Fail ".a:has(.b:is(.c .d))" should be a valid selector -Fail ".a:is(.b:has(.c) .d)" should be a valid selector -Fail ".a:not(:has(.b))" should be a valid selector -Fail ".a:has(:not(.b))" should be a valid selector -Fail ".a:has(.b):has(.c)" should be a valid selector -Fail "*|*:has(*)" should be a valid selector -Fail ":has(*|*)" should be a valid selector +Pass ".a:has(.b) .c" should be a valid selector +Pass ".a .b:has(.c)" should be a valid selector +Pass ".a .b:has(.c .d)" should be a valid selector +Pass ".a .b:has(.c .d) .e" should be a valid selector +Pass ".a:has(.b:is(.c .d))" should be a valid selector +Pass ".a:is(.b:has(.c) .d)" should be a valid selector +Pass ".a:not(:has(.b))" should be a valid selector +Pass ".a:has(:not(.b))" should be a valid selector +Pass ".a:has(.b):has(.c)" should be a valid selector +Pass "*|*:has(*)" should be a valid selector +Pass ":has(*|*)" should be a valid selector Pass ":has" should be an invalid selector Pass ".a:has" should be an invalid selector Pass ".a:has b" should be an invalid selector