From a57128467a2f11fb61cb4efe9b0fa3e3e6d51540 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Sat, 26 Feb 2022 13:53:13 +0000 Subject: [PATCH] LibWeb: Implement :nth-of-type and :nth-last-of-type selectors :^) --- .../Libraries/LibWeb/CSS/Parser/Parser.cpp | 36 +++++++++++-------- Userland/Libraries/LibWeb/CSS/Selector.cpp | 4 +++ Userland/Libraries/LibWeb/CSS/Selector.h | 6 ++-- .../Libraries/LibWeb/CSS/SelectorEngine.cpp | 23 ++++++++++-- Userland/Libraries/LibWeb/Dump.cpp | 6 ++++ 5 files changed, 57 insertions(+), 18 deletions(-) diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 70168a0938..c1530bc627 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -585,6 +585,18 @@ Result Parser::parse_simple_sel } else if (pseudo_class_token.is_function()) { + auto parse_nth_child_pattern = [this](auto& simple_selector, auto& pseudo_function) -> bool { + auto function_values = TokenStream(pseudo_function.values()); + auto nth_child_pattern = parse_a_n_plus_b_pattern(function_values); + if (nth_child_pattern.has_value()) { + simple_selector.pseudo_class.nth_child_pattern = nth_child_pattern.value(); + } else { + dbgln_if(CSS_PARSER_DEBUG, "!!! Invalid format for {}", pseudo_function.name()); + return false; + } + return true; + }; + auto& pseudo_function = pseudo_class_token.function(); if (pseudo_function.name().equals_ignoring_case("not")) { simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Not; @@ -597,24 +609,20 @@ Result Parser::parse_simple_sel simple_selector.pseudo_class.not_selector = not_selector.release_value(); } else if (pseudo_function.name().equals_ignoring_case("nth-child")) { simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthChild; - auto function_values = TokenStream(pseudo_function.values()); - auto nth_child_pattern = parse_a_n_plus_b_pattern(function_values); - if (nth_child_pattern.has_value()) { - simple_selector.pseudo_class.nth_child_pattern = nth_child_pattern.value(); - } else { - dbgln_if(CSS_PARSER_DEBUG, "!!! Invalid nth-child format"); + if (!parse_nth_child_pattern(simple_selector, pseudo_function)) return ParsingResult::SyntaxError; - } } else if (pseudo_function.name().equals_ignoring_case("nth-last-child")) { simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthLastChild; - auto function_values = TokenStream(pseudo_function.values()); - auto nth_child_pattern = parse_a_n_plus_b_pattern(function_values); - if (nth_child_pattern.has_value()) { - simple_selector.pseudo_class.nth_child_pattern = nth_child_pattern.value(); - } else { - dbgln_if(CSS_PARSER_DEBUG, "!!! Invalid nth-child format"); + if (!parse_nth_child_pattern(simple_selector, pseudo_function)) + return ParsingResult::SyntaxError; + } else if (pseudo_function.name().equals_ignoring_case("nth-of-type")) { + simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthOfType; + if (!parse_nth_child_pattern(simple_selector, pseudo_function)) + return ParsingResult::SyntaxError; + } else if (pseudo_function.name().equals_ignoring_case("nth-last-of-type")) { + simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthLastOfType; + if (!parse_nth_child_pattern(simple_selector, pseudo_function)) return ParsingResult::SyntaxError; - } } else { dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class function: ':{}'()", pseudo_function.name()); return ParsingResult::SyntaxError; diff --git a/Userland/Libraries/LibWeb/CSS/Selector.cpp b/Userland/Libraries/LibWeb/CSS/Selector.cpp index 7bd98df4b0..c8b2971eab 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.cpp +++ b/Userland/Libraries/LibWeb/CSS/Selector.cpp @@ -307,6 +307,10 @@ constexpr StringView pseudo_class_name(Selector::SimpleSelector::PseudoClass::Ty return "last-of-type"sv; case Selector::SimpleSelector::PseudoClass::Type::OnlyOfType: return "only-of-type"sv; + case Selector::SimpleSelector::PseudoClass::Type::NthOfType: + return "nth-of-type"sv; + case Selector::SimpleSelector::PseudoClass::Type::NthLastOfType: + return "nth-last-of-type"sv; case Selector::SimpleSelector::PseudoClass::Type::Disabled: return "disabled"sv; case Selector::SimpleSelector::PseudoClass::Type::Enabled: diff --git a/Userland/Libraries/LibWeb/CSS/Selector.h b/Userland/Libraries/LibWeb/CSS/Selector.h index 9e03072199..5938bda1d1 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.h +++ b/Userland/Libraries/LibWeb/CSS/Selector.h @@ -62,13 +62,15 @@ public: FirstChild, LastChild, OnlyChild, + NthChild, + NthLastChild, Empty, Root, FirstOfType, LastOfType, OnlyOfType, - NthChild, - NthLastChild, + NthOfType, + NthLastOfType, Disabled, Enabled, Checked, diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp index ddbd02cd25..52b4965ab9 100644 --- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -142,6 +142,8 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla return true; case CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild: case CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild: + case CSS::Selector::SimpleSelector::PseudoClass::Type::NthOfType: + case CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastOfType: auto const step_size = pseudo_class.nth_child_pattern.step_size; auto const offset = pseudo_class.nth_child_pattern.offset; if (step_size == 0 && offset == 0) @@ -152,12 +154,29 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla return false; int index = 1; - if (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild) { + switch (pseudo_class.type) { + case CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild: { for (auto* child = parent->first_child_of_type(); child && child != &element; child = child->next_element_sibling()) ++index; - } else { + break; + } + case CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild: { for (auto* child = parent->last_child_of_type(); child && child != &element; child = child->previous_element_sibling()) ++index; + break; + } + case CSS::Selector::SimpleSelector::PseudoClass::Type::NthOfType: { + for (auto* child = previous_sibling_with_same_tag_name(element); child; child = previous_sibling_with_same_tag_name(*child)) + ++index; + break; + } + case CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastOfType: { + for (auto* child = next_sibling_with_same_tag_name(element); child; child = next_sibling_with_same_tag_name(*child)) + ++index; + break; + } + default: + VERIFY_NOT_REACHED(); } if (step_size < 0) { diff --git a/Userland/Libraries/LibWeb/Dump.cpp b/Userland/Libraries/LibWeb/Dump.cpp index 320a02c157..8fdf2e6d76 100644 --- a/Userland/Libraries/LibWeb/Dump.cpp +++ b/Userland/Libraries/LibWeb/Dump.cpp @@ -385,6 +385,12 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector) case CSS::Selector::SimpleSelector::PseudoClass::Type::OnlyOfType: pseudo_class_description = "OnlyOfType"; break; + case CSS::Selector::SimpleSelector::PseudoClass::Type::NthOfType: + pseudo_class_description = "NthOfType"; + break; + case CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastOfType: + pseudo_class_description = "NthLastOfType"; + break; case CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild: pseudo_class_description = "NthChild"; break;