From 9ffc15ba3f717e2d31cc1a49501720cf86cdbe23 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Thu, 28 Aug 2025 10:25:44 +0100 Subject: [PATCH] LibWeb/CSS: Serialize :heading(...) pseudo-class properly We originally had special handling for `:host()` as that had been the only pseudo-class that could be both an identifier or a function. However, this meant duplicating the serialization logic, and also we had to manually remember to add the same hack for any other identifier-and-function cases. Which I forgot to do with `:heading()`! So instead, for these cases, detect if they actually have arguments specified and use that to determine which form to serialize as. We do still have to write a check for each one of these pseudo-classes, but the VERIFY should make it easier to remember. --- Libraries/LibWeb/CSS/Selector.cpp | 30 ++++++++------- .../css/selectors/parsing/parse-heading.txt | 34 +++++++++++++++++ .../css/selectors/parsing/parse-heading.html | 38 +++++++++++++++++++ 3 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/css/selectors/parsing/parse-heading.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/css/selectors/parsing/parse-heading.html diff --git a/Libraries/LibWeb/CSS/Selector.cpp b/Libraries/LibWeb/CSS/Selector.cpp index 2d85ec69f8d..4f2a99d2a30 100644 --- a/Libraries/LibWeb/CSS/Selector.cpp +++ b/Libraries/LibWeb/CSS/Selector.cpp @@ -427,22 +427,24 @@ String Selector::SimpleSelector::serialize() const auto& pseudo_class = this->pseudo_class(); auto metadata = pseudo_class_metadata(pseudo_class.type); - // HACK: `:host()` has both a function and a non-function form, so handle that first. - // It's also not in the spec. - if (pseudo_class.type == PseudoClass::Host) { - if (pseudo_class.argument_selector_list.is_empty()) { - s.append(':'); - s.append(pseudo_class_name(pseudo_class.type)); - } else { - s.append(':'); - s.append(pseudo_class_name(pseudo_class.type)); - s.append('('); - s.append(serialize_a_group_of_selectors(pseudo_class.argument_selector_list)); - s.append(')'); + bool accepts_arguments = [&]() { + if (!metadata.is_valid_as_function) + return false; + if (!metadata.is_valid_as_identifier) + return true; + // For pseudo-classes with both a function and identifier form, see if they have arguments. + switch (pseudo_class.type) { + case PseudoClass::Heading: + return !pseudo_class.an_plus_b_patterns.is_empty(); + case PseudoClass::Host: + return !pseudo_class.argument_selector_list.is_empty(); + default: + VERIFY_NOT_REACHED(); } - } + }(); + // If the pseudo-class does not accept arguments append ":" (U+003A), followed by the name of the pseudo-class, to s. - else if (metadata.is_valid_as_identifier) { + if (!accepts_arguments) { s.append(':'); s.append(pseudo_class_name(pseudo_class.type)); } diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/selectors/parsing/parse-heading.txt b/Tests/LibWeb/Text/expected/wpt-import/css/selectors/parsing/parse-heading.txt new file mode 100644 index 00000000000..6baa7303344 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/css/selectors/parsing/parse-heading.txt @@ -0,0 +1,34 @@ +Harness status: OK + +Found 28 tests + +17 Pass +11 Fail +Pass ":heading" should be a valid selector +Pass ":heading(2)" should be a valid selector +Pass ":heading(99999)" should be a valid selector +Pass ":heading(0)" should be a valid selector +Pass ":heading(0, 1, 2)" should be a valid selector +Pass ":heading(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)" should be a valid selector +Pass ":heading(-1)" should be a valid selector +Pass "h1:heading" should be a valid selector +Pass "h1:heading(1)" should be a valid selector +Pass "h1:heading(2)" should be a valid selector +Pass ":heading()" should be an invalid selector +Pass ":heading(1.0)" should be an invalid selector +Pass ":heading(1.4)" should be an invalid selector +Fail ":heading(n)" should be an invalid selector +Fail ":heading(odd)" should be an invalid selector +Fail ":heading(even)" should be an invalid selector +Fail ":heading(2n)" should be an invalid selector +Fail ":heading(2n+1)" should be an invalid selector +Fail ":heading(2n+2)" should be an invalid selector +Fail ":heading(-n+3)" should be an invalid selector +Fail ":heading(2n, 3n)" should be an invalid selector +Fail ":heading(2, 3n)" should be an invalid selector +Fail ":heading(2 of .foo)" should be an invalid selector +Fail ":heading(2n of .foo)" should be an invalid selector +Pass ":heading(calc(1))" should be an invalid selector +Pass ":heading(max(1, 2))" should be an invalid selector +Pass ":heading(min(1, 2)" should be an invalid selector +Pass ":heading(var(--level))" should be an invalid selector \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/css/selectors/parsing/parse-heading.html b/Tests/LibWeb/Text/input/wpt-import/css/selectors/parsing/parse-heading.html new file mode 100644 index 00000000000..de055f9d333 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/css/selectors/parsing/parse-heading.html @@ -0,0 +1,38 @@ + + +CSS Selectors: The heading pseudo-classes + + + + +