summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
authorSam Atkins <atkinssj@serenityos.org>2022-03-17 19:13:51 +0000
committerAndreas Kling <kling@serenityos.org>2022-03-18 11:34:02 +0100
commit5b0187477ba82449051225bbc23628320bbe2520 (patch)
treec3a193b0dfe0d2c97ed7f5f8e51c180b879c8dd2 /Userland
parent0e4c35b4b22fce5b045a903d77ff5d4a18b320ab (diff)
downloadserenity-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.cpp34
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/Parser.h6
-rw-r--r--Userland/Libraries/LibWeb/CSS/Selector.cpp37
-rw-r--r--Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp27
-rw-r--r--Userland/Libraries/LibWeb/Dump.cpp13
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(")");
}
}