From 503d41d02d5d8ea1c701f22b2b6d10d305030e29 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 12 Aug 2025 11:58:17 +0100 Subject: [PATCH] LibWeb/CSS: Implement the `:heading`/`:heading()` pseudo-class Corresponds to part of https://github.com/whatwg/html/commit/65dc095e44436d81316856aaf3c448c7c083ae16 --- Libraries/LibWeb/CSS/PseudoClasses.json | 3 + Libraries/LibWeb/CSS/SelectorEngine.cpp | 39 +++ .../wpt-import/css/selectors/heading.txt | 313 ++++++++++++++++++ .../wpt-import/css/selectors/heading.html | 67 ++++ 4 files changed, 422 insertions(+) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/css/selectors/heading.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/css/selectors/heading.html diff --git a/Libraries/LibWeb/CSS/PseudoClasses.json b/Libraries/LibWeb/CSS/PseudoClasses.json index 4c3f43e42d8..4048796dddb 100644 --- a/Libraries/LibWeb/CSS/PseudoClasses.json +++ b/Libraries/LibWeb/CSS/PseudoClasses.json @@ -50,6 +50,9 @@ "has": { "argument": "" }, + "heading": { + "argument": "#?" + }, "high-value": { "argument": "" }, diff --git a/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Libraries/LibWeb/CSS/SelectorEngine.cpp index d6d0fa9acc2..30f67d1f864 100644 --- a/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -1032,6 +1032,45 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla return custom_state_set->has_state(pseudo_class.ident->string_value); return false; } + case CSS::PseudoClass::Heading: { + // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-heading + // The :heading pseudo-class must match all h1, h2, h3, h4, h5, and h6 elements. + + // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-heading-functional + // The :heading(An+B#) pseudo-class must match all h1, h2, h3, h4, h5, and h6 elements that have a heading level among An+B. [CSSSYNTAX] [CSSVALUES] + + // NB: We combine the "is this an h* element?" and "what is it's level?" checks together here. + if (!element.is_html_element()) + return false; + auto heading_level = [](auto& local_name) -> Optional { + if (local_name == HTML::TagNames::h1) + return 1; + if (local_name == HTML::TagNames::h2) + return 2; + if (local_name == HTML::TagNames::h3) + return 3; + if (local_name == HTML::TagNames::h4) + return 4; + if (local_name == HTML::TagNames::h5) + return 5; + if (local_name == HTML::TagNames::h6) + return 6; + return {}; + }(element.lowercased_local_name()); + + if (!heading_level.has_value()) + return false; + + if (pseudo_class.an_plus_b_patterns.is_empty()) + return true; + + for (auto const& an_plus_b_pattern : pseudo_class.an_plus_b_patterns) { + if (an_plus_b_pattern.matches(heading_level.value())) + return true; + } + + return false; + } } return false; diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/selectors/heading.txt b/Tests/LibWeb/Text/expected/wpt-import/css/selectors/heading.txt new file mode 100644 index 00000000000..ac3cfcad8d7 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/css/selectors/heading.txt @@ -0,0 +1,313 @@ +Harness status: OK + +Found 308 tests + +308 Pass +Pass

:heading +Pass

:heading(1) +Pass

:heading(2) +Pass

:heading(3) +Pass

:heading(4) +Pass

:heading(5) +Pass

:heading(6) +Pass

:heading(7) +Pass

:heading(8) +Pass

:heading(9) +Pass

:heading(0) +Pass

:heading(-1) +Pass

:heading(0, 1, 2) +Pass

:heading(6, 7) +Pass

:heading(n) +Pass

:heading(2n) +Pass

:heading(2n+1) +Pass

:heading(2n+2) +Pass

:heading(-n+3) +Pass

:heading(2n, 3n) +Pass

:heading(even) +Pass

:heading(odd) +Pass

:heading +Pass

:heading(1) +Pass

:heading(2) +Pass

:heading(3) +Pass

:heading(4) +Pass

:heading(5) +Pass

:heading(6) +Pass

:heading(7) +Pass

:heading(8) +Pass

:heading(9) +Pass

:heading(0) +Pass

:heading(-1) +Pass

:heading(0, 1, 2) +Pass

:heading(6, 7) +Pass

:heading(n) +Pass

:heading(2n) +Pass

:heading(2n+1) +Pass

:heading(2n+2) +Pass

:heading(-n+3) +Pass

:heading(2n, 3n) +Pass

:heading(even) +Pass

:heading(odd) +Pass

:heading +Pass

:heading(1) +Pass

:heading(2) +Pass

:heading(3) +Pass

:heading(4) +Pass

:heading(5) +Pass

:heading(6) +Pass

:heading(7) +Pass

:heading(8) +Pass

:heading(9) +Pass

:heading(0) +Pass

:heading(-1) +Pass

:heading(0, 1, 2) +Pass

:heading(6, 7) +Pass

:heading(n) +Pass

:heading(2n) +Pass

:heading(2n+1) +Pass

:heading(2n+2) +Pass

:heading(-n+3) +Pass

:heading(2n, 3n) +Pass

:heading(even) +Pass

:heading(odd) +Pass

:heading +Pass

:heading(1) +Pass

:heading(2) +Pass

:heading(3) +Pass

:heading(4) +Pass

:heading(5) +Pass

:heading(6) +Pass

:heading(7) +Pass

:heading(8) +Pass

:heading(9) +Pass

:heading(0) +Pass

:heading(-1) +Pass

:heading(0, 1, 2) +Pass

:heading(6, 7) +Pass

:heading(n) +Pass

:heading(2n) +Pass

:heading(2n+1) +Pass

:heading(2n+2) +Pass

:heading(-n+3) +Pass

:heading(2n, 3n) +Pass

:heading(even) +Pass

:heading(odd) +Pass
:heading +Pass
:heading(1) +Pass
:heading(2) +Pass
:heading(3) +Pass
:heading(4) +Pass
:heading(5) +Pass
:heading(6) +Pass
:heading(7) +Pass
:heading(8) +Pass
:heading(9) +Pass
:heading(0) +Pass
:heading(-1) +Pass
:heading(0, 1, 2) +Pass
:heading(6, 7) +Pass
:heading(n) +Pass
:heading(2n) +Pass
:heading(2n+1) +Pass
:heading(2n+2) +Pass
:heading(-n+3) +Pass
:heading(2n, 3n) +Pass
:heading(even) +Pass
:heading(odd) +Pass
:heading +Pass
:heading(1) +Pass
:heading(2) +Pass
:heading(3) +Pass
:heading(4) +Pass
:heading(5) +Pass
:heading(6) +Pass
:heading(7) +Pass
:heading(8) +Pass
:heading(9) +Pass
:heading(0) +Pass
:heading(-1) +Pass
:heading(0, 1, 2) +Pass
:heading(6, 7) +Pass
:heading(n) +Pass
:heading(2n) +Pass
:heading(2n+1) +Pass
:heading(2n+2) +Pass
:heading(-n+3) +Pass
:heading(2n, 3n) +Pass
:heading(even) +Pass
:heading(odd) +Pass :heading +Pass :heading(1) +Pass :heading(2) +Pass :heading(3) +Pass :heading(4) +Pass :heading(5) +Pass :heading(6) +Pass :heading(7) +Pass :heading(8) +Pass :heading(9) +Pass :heading(0) +Pass :heading(-1) +Pass :heading(0, 1, 2) +Pass :heading(6, 7) +Pass :heading(n) +Pass :heading(2n) +Pass :heading(2n+1) +Pass :heading(2n+2) +Pass :heading(-n+3) +Pass :heading(2n, 3n) +Pass :heading(even) +Pass :heading(odd) +Pass :heading +Pass :heading(1) +Pass :heading(2) +Pass :heading(3) +Pass :heading(4) +Pass :heading(5) +Pass :heading(6) +Pass :heading(7) +Pass :heading(8) +Pass :heading(9) +Pass :heading(0) +Pass :heading(-1) +Pass :heading(0, 1, 2) +Pass :heading(6, 7) +Pass :heading(n) +Pass :heading(2n) +Pass :heading(2n+1) +Pass :heading(2n+2) +Pass :heading(-n+3) +Pass :heading(2n, 3n) +Pass :heading(even) +Pass :heading(odd) +Pass :heading +Pass :heading(1) +Pass :heading(2) +Pass :heading(3) +Pass :heading(4) +Pass :heading(5) +Pass :heading(6) +Pass :heading(7) +Pass :heading(8) +Pass :heading(9) +Pass :heading(0) +Pass :heading(-1) +Pass :heading(0, 1, 2) +Pass :heading(6, 7) +Pass :heading(n) +Pass :heading(2n) +Pass :heading(2n+1) +Pass :heading(2n+2) +Pass :heading(-n+3) +Pass :heading(2n, 3n) +Pass :heading(even) +Pass :heading(odd) +Pass :heading +Pass :heading(1) +Pass :heading(2) +Pass :heading(3) +Pass :heading(4) +Pass :heading(5) +Pass :heading(6) +Pass :heading(7) +Pass :heading(8) +Pass :heading(9) +Pass :heading(0) +Pass :heading(-1) +Pass :heading(0, 1, 2) +Pass :heading(6, 7) +Pass :heading(n) +Pass :heading(2n) +Pass :heading(2n+1) +Pass :heading(2n+2) +Pass :heading(-n+3) +Pass :heading(2n, 3n) +Pass :heading(even) +Pass :heading(odd) +Pass

:heading +Pass

:heading(1) +Pass

:heading(2) +Pass

:heading(3) +Pass

:heading(4) +Pass

:heading(5) +Pass

:heading(6) +Pass

:heading(7) +Pass

:heading(8) +Pass

:heading(9) +Pass

:heading(0) +Pass

:heading(-1) +Pass

:heading(0, 1, 2) +Pass

:heading(6, 7) +Pass

:heading(n) +Pass

:heading(2n) +Pass

:heading(2n+1) +Pass

:heading(2n+2) +Pass

:heading(-n+3) +Pass

:heading(2n, 3n) +Pass

:heading(even) +Pass

:heading(odd) +Pass

in section :heading +Pass

in section :heading(1) +Pass

in section :heading(2) +Pass

in section :heading(3) +Pass

in section :heading(4) +Pass

in section :heading(5) +Pass

in section :heading(6) +Pass

in section :heading(7) +Pass

in section :heading(8) +Pass

in section :heading(9) +Pass

in section :heading(0) +Pass

in section :heading(-1) +Pass

in section :heading(0, 1, 2) +Pass

in section :heading(6, 7) +Pass

in section :heading(n) +Pass

in section :heading(2n) +Pass

in section :heading(2n+1) +Pass

in section :heading(2n+2) +Pass

in section :heading(-n+3) +Pass

in section :heading(2n, 3n) +Pass

in section :heading(even) +Pass

in section :heading(odd) +Pass
:heading +Pass
:heading(1) +Pass
:heading(2) +Pass
:heading(3) +Pass
:heading(4) +Pass
:heading(5) +Pass
:heading(6) +Pass
:heading(7) +Pass
:heading(8) +Pass
:heading(9) +Pass
:heading(0) +Pass
:heading(-1) +Pass
:heading(0, 1, 2) +Pass
:heading(6, 7) +Pass
:heading(n) +Pass
:heading(2n) +Pass
:heading(2n+1) +Pass
:heading(2n+2) +Pass
:heading(-n+3) +Pass
:heading(2n, 3n) +Pass
:heading(even) +Pass
:heading(odd) +Pass

:heading +Pass

:heading(1) +Pass

:heading(2) +Pass

:heading(3) +Pass

:heading(4) +Pass

:heading(5) +Pass

:heading(6) +Pass

:heading(7) +Pass

:heading(8) +Pass

:heading(9) +Pass

:heading(0) +Pass

:heading(-1) +Pass

:heading(0, 1, 2) +Pass

:heading(6, 7) +Pass

:heading(n) +Pass

:heading(2n) +Pass

:heading(2n+1) +Pass

:heading(2n+2) +Pass

:heading(-n+3) +Pass

:heading(2n, 3n) +Pass

:heading(even) +Pass

:heading(odd) \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/css/selectors/heading.html b/Tests/LibWeb/Text/input/wpt-import/css/selectors/heading.html new file mode 100644 index 00000000000..5165474b2ff --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/css/selectors/heading.html @@ -0,0 +1,67 @@ + + +:heading and :heading() pseudo-classes + + + + +

+

+

+

+
+
+ + + + +

+

+
+

+ +