diff options
author | Sam Atkins <atkinssj@serenityos.org> | 2022-03-17 19:13:51 +0000 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-03-18 11:34:02 +0100 |
commit | 5b0187477ba82449051225bbc23628320bbe2520 (patch) | |
tree | c3a193b0dfe0d2c97ed7f5f8e51c180b879c8dd2 /Userland | |
parent | 0e4c35b4b22fce5b045a903d77ff5d4a18b320ab (diff) | |
download | serenity-5b0187477ba82449051225bbc23628320bbe2520.zip |
LibWeb: Implement `:nth-[last-]child(n of foo)` syntax
In Selectors level 4, `:nth-child()` and `:nth-last-child()` can both
optionally take a selector-list argument. This selector-list acts as a
filter, so that only elements matching the list are counted. For
example, this means that the following are equivalent:
```css
:nth-child(2n+1 of p) {}
p:nth-of-type(2n+1) {}
```
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp | 34 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.h | 6 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Selector.cpp | 37 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp | 27 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Dump.cpp | 13 |
5 files changed, 92 insertions, 25 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 73a1a46fe3..fec89e8252 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -571,15 +571,37 @@ Result<Selector::SimpleSelector, Parser::ParsingResult> 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 parse_nth_child_pattern = [this](Selector::SimpleSelector& simple_selector, StyleFunctionRule const& pseudo_function, bool allow_of = false) -> bool { auto function_values = TokenStream<StyleComponentValueRule>(pseudo_function.values()); - auto nth_child_pattern = parse_a_n_plus_b_pattern(function_values); + auto nth_child_pattern = parse_a_n_plus_b_pattern(function_values, allow_of ? AllowTrailingTokens::Yes : AllowTrailingTokens::No); 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; } + if (!allow_of) + return true; + + function_values.skip_whitespace(); + if (!function_values.has_next_token()) + return true; + + // Parse the `of <selector-list>` syntax + auto& maybe_of = function_values.next_token(); + if (!(maybe_of.is(Token::Type::Ident) && maybe_of.token().ident().equals_ignoring_case("of"sv))) + return false; + + function_values.skip_whitespace(); + auto selector_list = parse_a_selector_list(function_values); + if (selector_list.is_error()) + return false; + + function_values.skip_whitespace(); + if (function_values.has_next_token()) + return false; + + simple_selector.pseudo_class.argument_selector_list = selector_list.value(); return true; }; @@ -606,11 +628,11 @@ Result<Selector::SimpleSelector, Parser::ParsingResult> Parser::parse_simple_sel simple_selector.pseudo_class.argument_selector_list = not_selector.release_value(); } else if (pseudo_function.name().equals_ignoring_case("nth-child"sv)) { simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthChild; - if (!parse_nth_child_pattern(simple_selector, pseudo_function)) + if (!parse_nth_child_pattern(simple_selector, pseudo_function, true)) return ParsingResult::SyntaxError; } else if (pseudo_function.name().equals_ignoring_case("nth-last-child"sv)) { simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthLastChild; - if (!parse_nth_child_pattern(simple_selector, pseudo_function)) + if (!parse_nth_child_pattern(simple_selector, pseudo_function, true)) return ParsingResult::SyntaxError; } else if (pseudo_function.name().equals_ignoring_case("nth-of-type"sv)) { simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthOfType; @@ -4129,7 +4151,7 @@ RefPtr<StyleValue> Parser::parse_css_value(StyleComponentValueRule const& compon return {}; } -Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_pattern(TokenStream<StyleComponentValueRule>& values) +Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_pattern(TokenStream<StyleComponentValueRule>& values, AllowTrailingTokens allow_trailing_tokens) { int a = 0; int b = 0; @@ -4145,7 +4167,7 @@ Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_patt auto make_return_value = [&]() -> Optional<Selector::SimpleSelector::ANPlusBPattern> { // When we think we are done, but there are more non-whitespace tokens, then it's a parse error. values.skip_whitespace(); - if (values.has_next_token()) { + if (values.has_next_token() && allow_trailing_tokens == AllowTrailingTokens::No) { if constexpr (CSS_PARSER_DEBUG) { dbgln_if(CSS_PARSER_DEBUG, "Extra tokens at end of An+B value:"); values.dump_all_tokens(); diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index 5039614029..12e5fd0f8c 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -157,7 +157,11 @@ private: template<typename T> RefPtr<Supports> parse_a_supports(TokenStream<T>&); - Optional<Selector::SimpleSelector::ANPlusBPattern> parse_a_n_plus_b_pattern(TokenStream<StyleComponentValueRule>&); + enum class AllowTrailingTokens { + No, + Yes + }; + Optional<Selector::SimpleSelector::ANPlusBPattern> parse_a_n_plus_b_pattern(TokenStream<StyleComponentValueRule>&, AllowTrailingTokens = AllowTrailingTokens::No); template<typename T> [[nodiscard]] NonnullRefPtrVector<StyleRule> consume_a_list_of_rules(TokenStream<T>&, bool top_level); diff --git a/Userland/Libraries/LibWeb/CSS/Selector.cpp b/Userland/Libraries/LibWeb/CSS/Selector.cpp index 0957a0643f..1d8ab119f8 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.cpp +++ b/Userland/Libraries/LibWeb/CSS/Selector.cpp @@ -42,6 +42,21 @@ u32 Selector::specificity() const u32 classes = 0; u32 tag_names = 0; + auto count_specificity_of_most_complex_selector = [&](auto& selector_list) { + u32 max_selector_list_argument_specificity = 0; + for (auto const& complex_selector : selector_list) { + max_selector_list_argument_specificity = max(max_selector_list_argument_specificity, complex_selector.specificity()); + } + + u32 child_ids = (max_selector_list_argument_specificity & ids_mask) >> ids_shift; + u32 child_classes = (max_selector_list_argument_specificity & classes_mask) >> classes_shift; + u32 child_tag_names = (max_selector_list_argument_specificity & tag_names_mask) >> tag_names_shift; + + ids += child_ids; + classes += child_classes; + tag_names += child_tag_names; + }; + for (auto& list : m_compound_selectors) { for (auto& simple_selector : list.simple_selectors) { switch (simple_selector.type) { @@ -60,18 +75,16 @@ u32 Selector::specificity() const case SimpleSelector::PseudoClass::Type::Not: { // The specificity of an :is(), :not(), or :has() pseudo-class is replaced by the // specificity of the most specific complex selector in its selector list argument. - u32 max_selector_list_argument_specificity = 0; - for (auto const& complex_selector : simple_selector.pseudo_class.argument_selector_list) { - max_selector_list_argument_specificity = max(max_selector_list_argument_specificity, complex_selector.specificity()); - } - - u32 child_ids = (max_selector_list_argument_specificity & ids_mask) >> ids_shift; - u32 child_classes = (max_selector_list_argument_specificity & classes_mask) >> classes_shift; - u32 child_tag_names = (max_selector_list_argument_specificity & tag_names_mask) >> tag_names_shift; - - ids += child_ids; - classes += child_classes; - tag_names += child_tag_names; + count_specificity_of_most_complex_selector(simple_selector.pseudo_class.argument_selector_list); + break; + } + case SimpleSelector::PseudoClass::Type::NthChild: + case SimpleSelector::PseudoClass::Type::NthLastChild: { + // Analogously, the specificity of an :nth-child() or :nth-last-child() selector + // is the specificity of the pseudo class itself (counting as one pseudo-class selector) + // plus the specificity of the most specific complex selector in its selector list argument (if any). + ++classes; + count_specificity_of_most_complex_selector(simple_selector.pseudo_class.argument_selector_list); break; } case SimpleSelector::PseudoClass::Type::Where: diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp index bcd0707e2f..8d73df25b5 100644 --- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -180,16 +180,35 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla if (!parent) return false; + auto matches_selector_list = [](CSS::SelectorList const& list, DOM::Element const& element) { + if (list.is_empty()) + return true; + for (auto const& child_selector : list) { + if (matches(child_selector, element)) { + return true; + } + } + return false; + }; + int index = 1; switch (pseudo_class.type) { case CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild: { - for (auto* child = parent->first_child_of_type<DOM::Element>(); child && child != &element; child = child->next_element_sibling()) - ++index; + if (!matches_selector_list(pseudo_class.argument_selector_list, element)) + return false; + for (auto* child = parent->first_child_of_type<DOM::Element>(); child && child != &element; child = child->next_element_sibling()) { + if (matches_selector_list(pseudo_class.argument_selector_list, *child)) + ++index; + } break; } case CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild: { - for (auto* child = parent->last_child_of_type<DOM::Element>(); child && child != &element; child = child->previous_element_sibling()) - ++index; + if (!matches_selector_list(pseudo_class.argument_selector_list, element)) + return false; + for (auto* child = parent->last_child_of_type<DOM::Element>(); child && child != &element; child = child->previous_element_sibling()) { + if (matches_selector_list(pseudo_class.argument_selector_list, *child)) + ++index; + } break; } case CSS::Selector::SimpleSelector::PseudoClass::Type::NthOfType: { diff --git a/Userland/Libraries/LibWeb/Dump.cpp b/Userland/Libraries/LibWeb/Dump.cpp index 7916a996d3..b1029cc36a 100644 --- a/Userland/Libraries/LibWeb/Dump.cpp +++ b/Userland/Libraries/LibWeb/Dump.cpp @@ -447,8 +447,17 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector) dump_selector(builder, selector); builder.append("])"); } else if ((pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild) - || (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild)) { - builder.appendff("(step={}, offset={})", pseudo_class.nth_child_pattern.step_size, pseudo_class.nth_child_pattern.offset); + || (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild) + || (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthOfType) + || (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastOfType)) { + builder.appendff("(step={}, offset={}", pseudo_class.nth_child_pattern.step_size, pseudo_class.nth_child_pattern.offset); + if (!pseudo_class.argument_selector_list.is_empty()) { + builder.append(", selectors=["); + for (auto const& child_selector : pseudo_class.argument_selector_list) + dump_selector(builder, child_selector); + builder.append("]"); + } + builder.append(")"); } } |