From 75ec960495637d7f866e58be4208509729369431 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Mon, 21 Mar 2022 16:21:01 +0000 Subject: [PATCH] LibWeb: Initialize PseudoClass/PseudoElement selectors in one go There was no real benefit to creating the SimpleSelector early and then modifying it, and doing so made this code harder to follow than it needs to be. --- .../Libraries/LibWeb/CSS/Parser/Parser.cpp | 227 +++++++++--------- Userland/Libraries/LibWeb/CSS/Selector.h | 4 +- 2 files changed, 120 insertions(+), 111 deletions(-) diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 34a54e5af2..880ee41a22 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -420,10 +420,6 @@ Result Parser::parse_pseudo_sim } if (is_pseudo) { - Selector::SimpleSelector simple_selector { - .type = Selector::SimpleSelector::Type::PseudoElement - }; - auto const& name_token = tokens.next_token(); if (!name_token.is(Token::Type::Ident)) { dbgln_if(CSS_PARSER_DEBUG, "Expected an ident for pseudo-element, got: '{}'", name_token.to_debug_string()); @@ -439,158 +435,171 @@ Result Parser::parse_pseudo_sim dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-element: '::{}'", pseudo_name); return ParsingResult::SyntaxError; } - simple_selector.value = pseudo_element.value(); - return simple_selector; + return Selector::SimpleSelector { + .type = Selector::SimpleSelector::Type::PseudoElement, + .value = pseudo_element.value() + }; } if (peek_token_ends_selector()) return ParsingResult::SyntaxError; auto const& pseudo_class_token = tokens.next_token(); - Selector::SimpleSelector simple_selector { - .type = Selector::SimpleSelector::Type::PseudoClass, - .value = Selector::SimpleSelector::PseudoClass {} - }; if (pseudo_class_token.is(Token::Type::Ident)) { auto pseudo_name = pseudo_class_token.token().ident(); if (has_ignored_vendor_prefix(pseudo_name)) return ParsingResult::IncludesIgnoredVendorPrefix; - if (pseudo_name.equals_ignoring_case("active")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Active; - } else if (pseudo_name.equals_ignoring_case("checked")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Checked; - } else if (pseudo_name.equals_ignoring_case("disabled")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Disabled; - } else if (pseudo_name.equals_ignoring_case("empty")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Empty; - } else if (pseudo_name.equals_ignoring_case("enabled")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Enabled; - } else if (pseudo_name.equals_ignoring_case("first-child")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::FirstChild; - } else if (pseudo_name.equals_ignoring_case("first-of-type")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::FirstOfType; - } else if (pseudo_name.equals_ignoring_case("focus")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Focus; - } else if (pseudo_name.equals_ignoring_case("focus-within")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::FocusWithin; - } else if (pseudo_name.equals_ignoring_case("hover")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Hover; - } else if (pseudo_name.equals_ignoring_case("last-child")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::LastChild; - } else if (pseudo_name.equals_ignoring_case("last-of-type")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::LastOfType; - } else if (pseudo_name.equals_ignoring_case("link")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Link; - } else if (pseudo_name.equals_ignoring_case("only-child")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::OnlyChild; - } else if (pseudo_name.equals_ignoring_case("only-of-type")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::OnlyOfType; - } else if (pseudo_name.equals_ignoring_case("root")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Root; - } else if (pseudo_name.equals_ignoring_case("visited")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Visited; + auto make_pseudo_class_selector = [](auto pseudo_class) { + return Selector::SimpleSelector { + .type = Selector::SimpleSelector::Type::PseudoClass, + .value = Selector::SimpleSelector::PseudoClass { + .type = pseudo_class } + }; + }; - } else if (pseudo_name.equals_ignoring_case("after")) { - // Single-colon syntax allowed for compatibility. https://www.w3.org/TR/selectors/#pseudo-element-syntax - simple_selector.type = Selector::SimpleSelector::Type::PseudoElement; - simple_selector.value = Selector::PseudoElement::After; - } else if (pseudo_name.equals_ignoring_case("before")) { - // See :after - simple_selector.type = Selector::SimpleSelector::Type::PseudoElement; - simple_selector.value = Selector::PseudoElement::Before; - } else if (pseudo_name.equals_ignoring_case("first-letter")) { - // See :after - simple_selector.type = Selector::SimpleSelector::Type::PseudoElement; - simple_selector.value = Selector::PseudoElement::FirstLetter; - } else if (pseudo_name.equals_ignoring_case("first-line")) { - // See :after - simple_selector.type = Selector::SimpleSelector::Type::PseudoElement; - simple_selector.value = Selector::PseudoElement::FirstLine; - } else { - dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class: ':{}'", pseudo_name); - return ParsingResult::SyntaxError; + if (pseudo_name.equals_ignoring_case("active")) { + return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Active); + } else if (pseudo_name.equals_ignoring_case("checked")) { + return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Checked); + } else if (pseudo_name.equals_ignoring_case("disabled")) { + return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Disabled); + } else if (pseudo_name.equals_ignoring_case("empty")) { + return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Empty); + } else if (pseudo_name.equals_ignoring_case("enabled")) { + return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Enabled); + } else if (pseudo_name.equals_ignoring_case("first-child")) { + return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::FirstChild); + } else if (pseudo_name.equals_ignoring_case("first-of-type")) { + return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::FirstOfType); + } else if (pseudo_name.equals_ignoring_case("focus")) { + return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Focus); + } else if (pseudo_name.equals_ignoring_case("focus-within")) { + return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::FocusWithin); + } else if (pseudo_name.equals_ignoring_case("hover")) { + return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Hover); + } else if (pseudo_name.equals_ignoring_case("last-child")) { + return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::LastChild); + } else if (pseudo_name.equals_ignoring_case("last-of-type")) { + return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::LastOfType); + } else if (pseudo_name.equals_ignoring_case("link")) { + return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Link); + } else if (pseudo_name.equals_ignoring_case("only-child")) { + return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::OnlyChild); + } else if (pseudo_name.equals_ignoring_case("only-of-type")) { + return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::OnlyOfType); + } else if (pseudo_name.equals_ignoring_case("root")) { + return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Root); + } else if (pseudo_name.equals_ignoring_case("visited")) { + return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Visited); } - return simple_selector; - } - if (pseudo_class_token.is_function()) { - auto parse_nth_child_pattern = [this](Selector::SimpleSelector& simple_selector, StyleFunctionRule const& pseudo_function, bool allow_of = false) -> bool { - auto function_values = TokenStream(pseudo_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; + // Single-colon syntax allowed for ::after, ::before, ::first-letter and ::first-line for compatibility. + // https://www.w3.org/TR/selectors/#pseudo-element-syntax + if (auto pseudo_element = pseudo_element_from_string(pseudo_name); pseudo_element.has_value()) { + switch (pseudo_element.value()) { + case Selector::PseudoElement::After: + case Selector::PseudoElement::Before: + case Selector::PseudoElement::FirstLetter: + case Selector::PseudoElement::FirstLine: + return Selector::SimpleSelector { + .type = Selector::SimpleSelector::Type::PseudoElement, + .value = pseudo_element.value() + }; + default: + break; } - if (!allow_of) - return true; + } - function_values.skip_whitespace(); - if (!function_values.has_next_token()) - return true; + dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class: ':{}'", pseudo_name); + return ParsingResult::SyntaxError; + } + + if (pseudo_class_token.is_function()) { + auto parse_nth_child_selector = [this](auto pseudo_class, Vector const& function_values, bool allow_of = false) -> Result { + auto tokens = TokenStream(function_values); + auto nth_child_pattern = parse_a_n_plus_b_pattern(tokens, allow_of ? AllowTrailingTokens::Yes : AllowTrailingTokens::No); + if (!nth_child_pattern.has_value()) { + dbgln_if(CSS_PARSER_DEBUG, "!!! Invalid An+B format for {}", pseudo_class_name(pseudo_class)); + return ParsingResult::SyntaxError; + } + + tokens.skip_whitespace(); + if (!allow_of || !tokens.has_next_token()) { + return Selector::SimpleSelector { + .type = Selector::SimpleSelector::Type::PseudoClass, + .value = Selector::SimpleSelector::PseudoClass { + .type = pseudo_class, + .nth_child_pattern = nth_child_pattern.release_value() } + }; + } // Parse the `of ` syntax - auto const& maybe_of = function_values.next_token(); + auto const& maybe_of = tokens.next_token(); if (!(maybe_of.is(Token::Type::Ident) && maybe_of.token().ident().equals_ignoring_case("of"sv))) - return false; + return ParsingResult::SyntaxError; - function_values.skip_whitespace(); - auto selector_list = parse_a_selector_list(function_values, SelectorType::Standalone); + tokens.skip_whitespace(); + auto selector_list = parse_a_selector_list(tokens, SelectorType::Standalone); if (selector_list.is_error()) - return false; + return ParsingResult::SyntaxError; - function_values.skip_whitespace(); - if (function_values.has_next_token()) - return false; + tokens.skip_whitespace(); + if (tokens.has_next_token()) + return ParsingResult::SyntaxError; - simple_selector.pseudo_class().argument_selector_list = selector_list.value(); - return true; + return Selector::SimpleSelector { + .type = Selector::SimpleSelector::Type::PseudoClass, + .value = Selector::SimpleSelector::PseudoClass { + .type = pseudo_class, + .nth_child_pattern = nth_child_pattern.release_value(), + .argument_selector_list = selector_list.release_value() } + }; }; auto const& pseudo_function = pseudo_class_token.function(); if (pseudo_function.name().equals_ignoring_case("not")) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Not; auto function_token_stream = TokenStream(pseudo_function.values()); auto not_selector = parse_a_selector_list(function_token_stream, SelectorType::Standalone); if (not_selector.is_error()) { dbgln_if(CSS_PARSER_DEBUG, "Invalid selector in :not() clause"); return ParsingResult::SyntaxError; } - simple_selector.pseudo_class().argument_selector_list = not_selector.release_value(); + + return Selector::SimpleSelector { + .type = Selector::SimpleSelector::Type::PseudoClass, + .value = Selector::SimpleSelector::PseudoClass { + .type = Selector::SimpleSelector::PseudoClass::Type::Not, + .argument_selector_list = not_selector.release_value() } + }; } else if (pseudo_function.name().equals_ignoring_case("lang"sv)) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Lang; if (pseudo_function.values().is_empty()) { dbgln_if(CSS_PARSER_DEBUG, "Empty :lang() selector"); return ParsingResult::SyntaxError; } // FIXME: Support multiple, comma-separated, language ranges. - simple_selector.pseudo_class().languages.append(pseudo_function.values().first().token().to_string()); + Vector languages; + languages.append(pseudo_function.values().first().token().to_string()); + return Selector::SimpleSelector { + .type = Selector::SimpleSelector::Type::PseudoClass, + .value = Selector::SimpleSelector::PseudoClass { + .type = Selector::SimpleSelector::PseudoClass::Type::Lang, + .languages = move(languages) } + }; } 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, true)) - return ParsingResult::SyntaxError; + return parse_nth_child_selector(Selector::SimpleSelector::PseudoClass::Type::NthChild, pseudo_function.values(), true); } 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, true)) - return ParsingResult::SyntaxError; + return parse_nth_child_selector(Selector::SimpleSelector::PseudoClass::Type::NthLastChild, pseudo_function.values(), true); } else if (pseudo_function.name().equals_ignoring_case("nth-of-type"sv)) { - simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::NthOfType; - if (!parse_nth_child_pattern(simple_selector, pseudo_function)) - return ParsingResult::SyntaxError; + return parse_nth_child_selector(Selector::SimpleSelector::PseudoClass::Type::NthOfType, pseudo_function.values(), false); } else if (pseudo_function.name().equals_ignoring_case("nth-last-of-type"sv)) { - 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; + return parse_nth_child_selector(Selector::SimpleSelector::PseudoClass::Type::NthLastOfType, pseudo_function.values(), false); } - return simple_selector; + dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class function: ':{}'()", pseudo_function.name()); + return ParsingResult::SyntaxError; } dbgln_if(CSS_PARSER_DEBUG, "Unexpected Block in pseudo-class name, expected a function or identifier. '{}'", pseudo_class_token.to_debug_string()); return ParsingResult::SyntaxError; diff --git a/Userland/Libraries/LibWeb/CSS/Selector.h b/Userland/Libraries/LibWeb/CSS/Selector.h index bf63a82a25..6893bf083a 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.h +++ b/Userland/Libraries/LibWeb/CSS/Selector.h @@ -85,12 +85,12 @@ public: // FIXME: We don't need this field on every single SimpleSelector, but it's also annoying to malloc it somewhere. // Only used when "pseudo_class" is "NthChild" or "NthLastChild". - ANPlusBPattern nth_child_pattern; + ANPlusBPattern nth_child_pattern {}; SelectorList argument_selector_list {}; // Used for :lang(en-gb,dk) - Vector languages; + Vector languages {}; }; struct Attribute {