From b314a115cab9dfab336bc2dd0386b47742948263 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 11 Aug 2023 21:26:04 +0100 Subject: [PATCH] LibWeb: Use generated PseudoClass data Everything should behave the same as before. --- .../Libraries/LibWeb/CSS/Parser/Parser.cpp | 163 ++++++------------ Userland/Libraries/LibWeb/CSS/Selector.cpp | 102 +++++------ Userland/Libraries/LibWeb/CSS/Selector.h | 134 +------------- .../Libraries/LibWeb/CSS/SelectorEngine.cpp | 86 ++++----- Userland/Libraries/LibWeb/Dump.cpp | 160 +++-------------- 5 files changed, 179 insertions(+), 466 deletions(-) diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 7eaac1428bf..9d8d53ec482 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -532,71 +533,17 @@ Parser::ParseErrorOr Parser::parse_pseudo_simple_selec auto make_pseudo_class_selector = [](auto pseudo_class) { return Selector::SimpleSelector { .type = Selector::SimpleSelector::Type::PseudoClass, - .value = Selector::SimpleSelector::PseudoClass { - .type = pseudo_class } + .value = Selector::SimpleSelector::PseudoClassSelector { .type = pseudo_class } }; }; - if (pseudo_name.equals_ignoring_ascii_case("active"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Active); - if (pseudo_name.equals_ignoring_ascii_case("buffering"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Buffering); - if (pseudo_name.equals_ignoring_ascii_case("checked"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Checked); - if (pseudo_name.equals_ignoring_ascii_case("indeterminate"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Indeterminate); - if (pseudo_name.equals_ignoring_ascii_case("defined"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Defined); - if (pseudo_name.equals_ignoring_ascii_case("disabled"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Disabled); - if (pseudo_name.equals_ignoring_ascii_case("empty"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Empty); - if (pseudo_name.equals_ignoring_ascii_case("enabled"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Enabled); - if (pseudo_name.equals_ignoring_ascii_case("first-child"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::FirstChild); - if (pseudo_name.equals_ignoring_ascii_case("first-of-type"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::FirstOfType); - if (pseudo_name.equals_ignoring_ascii_case("focus"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Focus); - if (pseudo_name.equals_ignoring_ascii_case("focus-visible"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::FocusVisible); - if (pseudo_name.equals_ignoring_ascii_case("focus-within"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::FocusWithin); - if (pseudo_name.equals_ignoring_ascii_case("hover"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Hover); - if (pseudo_name.equals_ignoring_ascii_case("last-child"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::LastChild); - if (pseudo_name.equals_ignoring_ascii_case("last-of-type"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::LastOfType); - if (pseudo_name.equals_ignoring_ascii_case("link"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Link); - if (pseudo_name.equals_ignoring_ascii_case("muted"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Muted); - if (pseudo_name.equals_ignoring_ascii_case("only-child"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::OnlyChild); - if (pseudo_name.equals_ignoring_ascii_case("only-of-type"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::OnlyOfType); - if (pseudo_name.equals_ignoring_ascii_case("playing"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Playing); - if (pseudo_name.equals_ignoring_ascii_case("paused"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Paused); - if (pseudo_name.equals_ignoring_ascii_case("root"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Root); - if (pseudo_name.equals_ignoring_ascii_case("seeking"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Seeking); - if (pseudo_name.equals_ignoring_ascii_case("stalled"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Stalled); - if (pseudo_name.equals_ignoring_ascii_case("target"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Target); - if (pseudo_name.equals_ignoring_ascii_case("host"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Host); - if (pseudo_name.equals_ignoring_ascii_case("visited"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Visited); - if (pseudo_name.equals_ignoring_ascii_case("volume-locked"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::VolumeLocked); - if (pseudo_name.equals_ignoring_ascii_case("scope"sv)) - return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Scope); + if (auto pseudo_class = pseudo_class_from_string(pseudo_name); pseudo_class.has_value()) { + if (!pseudo_class_metadata(pseudo_class.value()).is_valid_as_identifier) { + dbgln_if(CSS_PARSER_DEBUG, "Pseudo-class ':{}' is only valid as a function", pseudo_name); + return ParseError::SyntaxError; + } + return make_pseudo_class_selector(pseudo_class.value()); + } // Single-colon syntax allowed for ::after, ::before, ::first-letter and ::first-line for compatibility. // https://www.w3.org/TR/selectors/#pseudo-element-syntax @@ -632,7 +579,7 @@ Parser::ParseErrorOr Parser::parse_pseudo_simple_selec if (!tokens.has_next_token()) { return Selector::SimpleSelector { .type = Selector::SimpleSelector::Type::PseudoClass, - .value = Selector::SimpleSelector::PseudoClass { + .value = Selector::SimpleSelector::PseudoClassSelector { .type = pseudo_class, .nth_child_pattern = nth_child_pattern.release_value() } }; @@ -655,7 +602,7 @@ Parser::ParseErrorOr Parser::parse_pseudo_simple_selec return Selector::SimpleSelector { .type = Selector::SimpleSelector::Type::PseudoClass, - .value = Selector::SimpleSelector::PseudoClass { + .value = Selector::SimpleSelector::PseudoClassSelector { .type = pseudo_class, .nth_child_pattern = nth_child_pattern.release_value(), .argument_selector_list = move(selector_list) } @@ -663,69 +610,67 @@ Parser::ParseErrorOr Parser::parse_pseudo_simple_selec }; auto const& pseudo_function = pseudo_class_token.function(); - if (pseudo_function.name().equals_ignoring_ascii_case("is"sv) - || pseudo_function.name().equals_ignoring_ascii_case("where"sv)) { + auto maybe_pseudo_class = pseudo_class_from_string(pseudo_function.name()); + if (!maybe_pseudo_class.has_value()) { + dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class function: ':{}'()", pseudo_function.name()); + return ParseError::SyntaxError; + } + auto pseudo_class = maybe_pseudo_class.value(); + auto metadata = pseudo_class_metadata(pseudo_class); + + if (!metadata.is_valid_as_function) { + dbgln_if(CSS_PARSER_DEBUG, "Pseudo-class ':{}' is not valid as a function", pseudo_function.name()); + return ParseError::SyntaxError; + } + + if (pseudo_function.values().is_empty()) { + dbgln_if(CSS_PARSER_DEBUG, "Empty :{}() selector", pseudo_function.name()); + return ParseError::SyntaxError; + } + + switch (metadata.parameter_type) { + case PseudoClassMetadata::ParameterType::ANPlusB: + return parse_nth_child_selector(pseudo_class, pseudo_function.values(), false); + case PseudoClassMetadata::ParameterType::ANPlusBOf: + return parse_nth_child_selector(pseudo_class, pseudo_function.values(), true); + case PseudoClassMetadata::ParameterType::ForgivingSelectorList: { auto function_token_stream = TokenStream(pseudo_function.values()); // NOTE: Because it's forgiving, even complete garbage will parse OK as an empty selector-list. auto argument_selector_list = MUST(parse_a_selector_list(function_token_stream, SelectorType::Standalone, SelectorParsingMode::Forgiving)); return Selector::SimpleSelector { .type = Selector::SimpleSelector::Type::PseudoClass, - .value = Selector::SimpleSelector::PseudoClass { - .type = pseudo_function.name().equals_ignoring_ascii_case("is"sv) - ? Selector::SimpleSelector::PseudoClass::Type::Is - : Selector::SimpleSelector::PseudoClass::Type::Where, + .value = Selector::SimpleSelector::PseudoClassSelector { + .type = pseudo_class, .argument_selector_list = move(argument_selector_list) } }; } - if (pseudo_function.name().equals_ignoring_ascii_case("not"sv)) { - auto function_token_stream = TokenStream(pseudo_function.values()); - auto not_selector = TRY(parse_a_selector_list(function_token_stream, SelectorType::Standalone)); - - return Selector::SimpleSelector { - .type = Selector::SimpleSelector::Type::PseudoClass, - .value = Selector::SimpleSelector::PseudoClass { - .type = Selector::SimpleSelector::PseudoClass::Type::Not, - .argument_selector_list = move(not_selector) } - }; - } - if (pseudo_function.name().equals_ignoring_ascii_case("host"sv)) { - auto function_token_stream = TokenStream(pseudo_function.values()); - auto host_selector = TRY(parse_a_selector_list(function_token_stream, SelectorType::Standalone)); - - return Selector::SimpleSelector { - .type = Selector::SimpleSelector::Type::PseudoClass, - .value = Selector::SimpleSelector::PseudoClass { - .type = Selector::SimpleSelector::PseudoClass::Type::Host, - .argument_selector_list = move(host_selector) } - }; - } - if (pseudo_function.name().equals_ignoring_ascii_case("lang"sv)) { - if (pseudo_function.values().is_empty()) { - dbgln_if(CSS_PARSER_DEBUG, "Empty :lang() selector"); - return ParseError::SyntaxError; - } + case PseudoClassMetadata::ParameterType::LanguageRanges: { // FIXME: Support multiple, comma-separated, language ranges. Vector languages; languages.append(pseudo_function.values().first().token().to_string().release_value_but_fixme_should_propagate_errors()); return Selector::SimpleSelector { .type = Selector::SimpleSelector::Type::PseudoClass, - .value = Selector::SimpleSelector::PseudoClass { - .type = Selector::SimpleSelector::PseudoClass::Type::Lang, + .value = Selector::SimpleSelector::PseudoClassSelector { + .type = pseudo_class, .languages = move(languages) } }; } - if (pseudo_function.name().equals_ignoring_ascii_case("nth-child"sv)) - return parse_nth_child_selector(Selector::SimpleSelector::PseudoClass::Type::NthChild, pseudo_function.values(), true); - if (pseudo_function.name().equals_ignoring_ascii_case("nth-last-child"sv)) - return parse_nth_child_selector(Selector::SimpleSelector::PseudoClass::Type::NthLastChild, pseudo_function.values(), true); - if (pseudo_function.name().equals_ignoring_ascii_case("nth-of-type"sv)) - return parse_nth_child_selector(Selector::SimpleSelector::PseudoClass::Type::NthOfType, pseudo_function.values(), false); - if (pseudo_function.name().equals_ignoring_ascii_case("nth-last-of-type"sv)) - return parse_nth_child_selector(Selector::SimpleSelector::PseudoClass::Type::NthLastOfType, pseudo_function.values(), false); + case PseudoClassMetadata::ParameterType::SelectorList: { + auto function_token_stream = TokenStream(pseudo_function.values()); + auto not_selector = TRY(parse_a_selector_list(function_token_stream, SelectorType::Standalone)); - dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class function: ':{}'()", pseudo_function.name()); - return ParseError::SyntaxError; + return Selector::SimpleSelector { + .type = Selector::SimpleSelector::Type::PseudoClass, + .value = Selector::SimpleSelector::PseudoClassSelector { + .type = pseudo_class, + .argument_selector_list = move(not_selector) } + }; + } + case PseudoClassMetadata::ParameterType::None: + // `None` means this is not a function-type pseudo-class, so this state should be impossible. + VERIFY_NOT_REACHED(); + } } dbgln_if(CSS_PARSER_DEBUG, "Unexpected Block in pseudo-class name, expected a function or identifier. '{}'", pseudo_class_token.to_debug_string()); return ParseError::SyntaxError; diff --git a/Userland/Libraries/LibWeb/CSS/Selector.cpp b/Userland/Libraries/LibWeb/CSS/Selector.cpp index 55038445e24..7da8725b6f1 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.cpp +++ b/Userland/Libraries/LibWeb/CSS/Selector.cpp @@ -72,15 +72,15 @@ u32 Selector::specificity() const case SimpleSelector::Type::PseudoClass: { auto& pseudo_class = simple_selector.pseudo_class(); switch (pseudo_class.type) { - case SimpleSelector::PseudoClass::Type::Is: - case SimpleSelector::PseudoClass::Type::Not: { + case PseudoClass::Is: + case PseudoClass::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. count_specificity_of_most_complex_selector(pseudo_class.argument_selector_list); break; } - case SimpleSelector::PseudoClass::Type::NthChild: - case SimpleSelector::PseudoClass::Type::NthLastChild: { + case PseudoClass::NthChild: + case PseudoClass::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). @@ -88,7 +88,7 @@ u32 Selector::specificity() const count_specificity_of_most_complex_selector(pseudo_class.argument_selector_list); break; } - case SimpleSelector::PseudoClass::Type::Where: + case PseudoClass::Where: // The specificity of a :where() pseudo-class is replaced by zero. break; default: @@ -229,66 +229,66 @@ ErrorOr Selector::SimpleSelector::serialize() const auto& pseudo_class = this->pseudo_class(); switch (pseudo_class.type) { - case Selector::SimpleSelector::PseudoClass::Type::Link: - case Selector::SimpleSelector::PseudoClass::Type::Visited: - case Selector::SimpleSelector::PseudoClass::Type::Hover: - case Selector::SimpleSelector::PseudoClass::Type::Focus: - case Selector::SimpleSelector::PseudoClass::Type::FocusVisible: - case Selector::SimpleSelector::PseudoClass::Type::FocusWithin: - case Selector::SimpleSelector::PseudoClass::Type::FirstChild: - case Selector::SimpleSelector::PseudoClass::Type::LastChild: - case Selector::SimpleSelector::PseudoClass::Type::OnlyChild: - case Selector::SimpleSelector::PseudoClass::Type::Empty: - case Selector::SimpleSelector::PseudoClass::Type::Root: - case Selector::SimpleSelector::PseudoClass::Type::Host: - case Selector::SimpleSelector::PseudoClass::Type::FirstOfType: - case Selector::SimpleSelector::PseudoClass::Type::LastOfType: - case Selector::SimpleSelector::PseudoClass::Type::OnlyOfType: - case Selector::SimpleSelector::PseudoClass::Type::Disabled: - case Selector::SimpleSelector::PseudoClass::Type::Enabled: - case Selector::SimpleSelector::PseudoClass::Type::Checked: - case Selector::SimpleSelector::PseudoClass::Type::Indeterminate: - case Selector::SimpleSelector::PseudoClass::Type::Active: - case Selector::SimpleSelector::PseudoClass::Type::Scope: - case Selector::SimpleSelector::PseudoClass::Type::Defined: - case Selector::SimpleSelector::PseudoClass::Type::Playing: - case Selector::SimpleSelector::PseudoClass::Type::Paused: - case Selector::SimpleSelector::PseudoClass::Type::Seeking: - case Selector::SimpleSelector::PseudoClass::Type::Muted: - case Selector::SimpleSelector::PseudoClass::Type::VolumeLocked: - case Selector::SimpleSelector::PseudoClass::Type::Buffering: - case Selector::SimpleSelector::PseudoClass::Type::Stalled: - case Selector::SimpleSelector::PseudoClass::Type::Target: + case PseudoClass::Link: + case PseudoClass::Visited: + case PseudoClass::Hover: + case PseudoClass::Focus: + case PseudoClass::FocusVisible: + case PseudoClass::FocusWithin: + case PseudoClass::FirstChild: + case PseudoClass::LastChild: + case PseudoClass::OnlyChild: + case PseudoClass::Empty: + case PseudoClass::Root: + case PseudoClass::Host: + case PseudoClass::FirstOfType: + case PseudoClass::LastOfType: + case PseudoClass::OnlyOfType: + case PseudoClass::Disabled: + case PseudoClass::Enabled: + case PseudoClass::Checked: + case PseudoClass::Indeterminate: + case PseudoClass::Active: + case PseudoClass::Scope: + case PseudoClass::Defined: + case PseudoClass::Playing: + case PseudoClass::Paused: + case PseudoClass::Seeking: + case PseudoClass::Muted: + case PseudoClass::VolumeLocked: + case PseudoClass::Buffering: + case PseudoClass::Stalled: + case PseudoClass::Target: // If the pseudo-class does not accept arguments append ":" (U+003A), followed by the name of the pseudo-class, to s. TRY(s.try_append(':')); TRY(s.try_append(pseudo_class_name(pseudo_class.type))); break; - case Selector::SimpleSelector::PseudoClass::Type::NthChild: - case Selector::SimpleSelector::PseudoClass::Type::NthLastChild: - case Selector::SimpleSelector::PseudoClass::Type::NthOfType: - case Selector::SimpleSelector::PseudoClass::Type::NthLastOfType: - case Selector::SimpleSelector::PseudoClass::Type::Not: - case Selector::SimpleSelector::PseudoClass::Type::Is: - case Selector::SimpleSelector::PseudoClass::Type::Where: - case Selector::SimpleSelector::PseudoClass::Type::Lang: + case PseudoClass::NthChild: + case PseudoClass::NthLastChild: + case PseudoClass::NthOfType: + case PseudoClass::NthLastOfType: + case PseudoClass::Not: + case PseudoClass::Is: + case PseudoClass::Where: + case PseudoClass::Lang: // Otherwise, append ":" (U+003A), followed by the name of the pseudo-class, followed by "(" (U+0028), // followed by the value of the pseudo-class argument(s) determined as per below, followed by ")" (U+0029), to s. TRY(s.try_append(':')); TRY(s.try_append(pseudo_class_name(pseudo_class.type))); TRY(s.try_append('(')); - if (pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::NthChild - || pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::NthLastChild - || pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::NthOfType - || pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::NthLastOfType) { + if (pseudo_class.type == PseudoClass::NthChild + || pseudo_class.type == PseudoClass::NthLastChild + || pseudo_class.type == PseudoClass::NthOfType + || pseudo_class.type == PseudoClass::NthLastOfType) { // The result of serializing the value using the rules to serialize an value. TRY(s.try_append(TRY(pseudo_class.nth_child_pattern.serialize()))); - } else if (pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::Not - || pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::Is - || pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::Where) { + } else if (pseudo_class.type == PseudoClass::Not + || pseudo_class.type == PseudoClass::Is + || pseudo_class.type == PseudoClass::Where) { // The result of serializing the value using the rules for serializing a group of selectors. // NOTE: `:is()` and `:where()` aren't in the spec for this yet, but it should be! TRY(s.try_append(TRY(serialize_a_group_of_selectors(pseudo_class.argument_selector_list)))); - } else if (pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::Lang) { + } else if (pseudo_class.type == PseudoClass::Lang) { // The serialization of a comma-separated list of each argument’s serialization as a string, preserving relative order. s.join(", "sv, pseudo_class.languages); } diff --git a/Userland/Libraries/LibWeb/CSS/Selector.h b/Userland/Libraries/LibWeb/CSS/Selector.h index df30219a043..532c2396ae0 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.h +++ b/Userland/Libraries/LibWeb/CSS/Selector.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace Web::CSS { @@ -84,48 +85,8 @@ public: } }; - struct PseudoClass { - enum class Type { - Link, - Visited, - Hover, - Focus, - FocusVisible, - FocusWithin, - FirstChild, - LastChild, - OnlyChild, - NthChild, - NthLastChild, - Empty, - Root, - Host, - FirstOfType, - LastOfType, - OnlyOfType, - NthOfType, - NthLastOfType, - Disabled, - Enabled, - Checked, - Indeterminate, - Is, - Not, - Where, - Active, - Lang, - Scope, - Defined, - Playing, - Paused, - Seeking, - Muted, - VolumeLocked, - Buffering, - Stalled, - Target, - }; - Type type; + struct PseudoClassSelector { + PseudoClass type; // 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". @@ -184,12 +145,12 @@ public: }; Type type; - Variant value {}; + Variant value {}; Attribute const& attribute() const { return value.get(); } Attribute& attribute() { return value.get(); } - PseudoClass const& pseudo_class() const { return value.get(); } - PseudoClass& pseudo_class() { return value.get(); } + PseudoClassSelector const& pseudo_class() const { return value.get(); } + PseudoClassSelector& pseudo_class() { return value.get(); } PseudoElement const& pseudo_element() const { return value.get(); } PseudoElement& pseudo_element() { return value.get(); } @@ -268,89 +229,6 @@ constexpr StringView pseudo_element_name(Selector::PseudoElement pseudo_element) Optional pseudo_element_from_string(StringView); -constexpr StringView pseudo_class_name(Selector::SimpleSelector::PseudoClass::Type pseudo_class) -{ - switch (pseudo_class) { - case Selector::SimpleSelector::PseudoClass::Type::Link: - return "link"sv; - case Selector::SimpleSelector::PseudoClass::Type::Visited: - return "visited"sv; - case Selector::SimpleSelector::PseudoClass::Type::Hover: - return "hover"sv; - case Selector::SimpleSelector::PseudoClass::Type::Focus: - return "focus"sv; - case Selector::SimpleSelector::PseudoClass::Type::FocusVisible: - return "focus-visible"sv; - case Selector::SimpleSelector::PseudoClass::Type::FocusWithin: - return "focus-within"sv; - case Selector::SimpleSelector::PseudoClass::Type::FirstChild: - return "first-child"sv; - case Selector::SimpleSelector::PseudoClass::Type::LastChild: - return "last-child"sv; - case Selector::SimpleSelector::PseudoClass::Type::OnlyChild: - return "only-child"sv; - case Selector::SimpleSelector::PseudoClass::Type::Empty: - return "empty"sv; - case Selector::SimpleSelector::PseudoClass::Type::Root: - return "root"sv; - case Selector::SimpleSelector::PseudoClass::Type::Host: - return "host"sv; - case Selector::SimpleSelector::PseudoClass::Type::FirstOfType: - return "first-of-type"sv; - case Selector::SimpleSelector::PseudoClass::Type::LastOfType: - 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: - return "enabled"sv; - case Selector::SimpleSelector::PseudoClass::Type::Checked: - return "checked"sv; - case Selector::SimpleSelector::PseudoClass::Type::Indeterminate: - return "indeterminate"sv; - case Selector::SimpleSelector::PseudoClass::Type::Active: - return "active"sv; - case Selector::SimpleSelector::PseudoClass::Type::NthChild: - return "nth-child"sv; - case Selector::SimpleSelector::PseudoClass::Type::NthLastChild: - return "nth-last-child"sv; - case Selector::SimpleSelector::PseudoClass::Type::Is: - return "is"sv; - case Selector::SimpleSelector::PseudoClass::Type::Not: - return "not"sv; - case Selector::SimpleSelector::PseudoClass::Type::Where: - return "where"sv; - case Selector::SimpleSelector::PseudoClass::Type::Lang: - return "lang"sv; - case Selector::SimpleSelector::PseudoClass::Type::Scope: - return "scope"sv; - case Selector::SimpleSelector::PseudoClass::Type::Defined: - return "defined"sv; - case Selector::SimpleSelector::PseudoClass::Type::Playing: - return "playing"sv; - case Selector::SimpleSelector::PseudoClass::Type::Paused: - return "paused"sv; - case Selector::SimpleSelector::PseudoClass::Type::Seeking: - return "seeking"sv; - case Selector::SimpleSelector::PseudoClass::Type::Muted: - return "muted"sv; - case Selector::SimpleSelector::PseudoClass::Type::VolumeLocked: - return "volume-locked"sv; - case Selector::SimpleSelector::PseudoClass::Type::Buffering: - return "buffering"sv; - case Selector::SimpleSelector::PseudoClass::Type::Stalled: - return "stalled"sv; - case Selector::SimpleSelector::PseudoClass::Type::Target: - return "target"sv; - } - VERIFY_NOT_REACHED(); -} - ErrorOr serialize_a_group_of_selectors(Vector> const& selectors); } diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp index 7a61461a9a0..f89f0dd64cb 100644 --- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -207,34 +207,34 @@ static inline DOM::Element const* next_sibling_with_same_tag_name(DOM::Element c return nullptr; } -static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClass const& pseudo_class, Optional style_sheet_for_rule, DOM::Element const& element, JS::GCPtr scope) +static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClassSelector const& pseudo_class, Optional style_sheet_for_rule, DOM::Element const& element, JS::GCPtr scope) { switch (pseudo_class.type) { - case CSS::Selector::SimpleSelector::PseudoClass::Type::Link: + case CSS::PseudoClass::Link: return matches_link_pseudo_class(element); - case CSS::Selector::SimpleSelector::PseudoClass::Type::Visited: + case CSS::PseudoClass::Visited: // FIXME: Maybe match this selector sometimes? return false; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Active: + case CSS::PseudoClass::Active: return element.is_active(); - case CSS::Selector::SimpleSelector::PseudoClass::Type::Hover: + case CSS::PseudoClass::Hover: return matches_hover_pseudo_class(element); - case CSS::Selector::SimpleSelector::PseudoClass::Type::Focus: + case CSS::PseudoClass::Focus: return element.is_focused(); - case CSS::Selector::SimpleSelector::PseudoClass::Type::FocusVisible: + case CSS::PseudoClass::FocusVisible: // FIXME: We should only apply this when a visible focus is useful. Decide when that is! return element.is_focused(); - case CSS::Selector::SimpleSelector::PseudoClass::Type::FocusWithin: { + case CSS::PseudoClass::FocusWithin: { auto* focused_element = element.document().focused_element(); return focused_element && element.is_inclusive_ancestor_of(*focused_element); } - case CSS::Selector::SimpleSelector::PseudoClass::Type::FirstChild: + case CSS::PseudoClass::FirstChild: return !element.previous_element_sibling(); - case CSS::Selector::SimpleSelector::PseudoClass::Type::LastChild: + case CSS::PseudoClass::LastChild: return !element.next_element_sibling(); - case CSS::Selector::SimpleSelector::PseudoClass::Type::OnlyChild: + case CSS::PseudoClass::OnlyChild: return !(element.previous_element_sibling() || element.next_element_sibling()); - case CSS::Selector::SimpleSelector::PseudoClass::Type::Empty: { + case CSS::PseudoClass::Empty: { if (!element.has_children()) return true; if (element.first_child_of_type()) @@ -251,53 +251,53 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla }); return !has_nonempty_text_child; } - case CSS::Selector::SimpleSelector::PseudoClass::Type::Root: + case CSS::PseudoClass::Root: return is(element); - case CSS::Selector::SimpleSelector::PseudoClass::Type::Host: + case CSS::PseudoClass::Host: // FIXME: Implement :host selector. return false; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Scope: + case CSS::PseudoClass::Scope: return scope ? &element == scope : is(element); - case CSS::Selector::SimpleSelector::PseudoClass::Type::FirstOfType: + case CSS::PseudoClass::FirstOfType: return !previous_sibling_with_same_tag_name(element); - case CSS::Selector::SimpleSelector::PseudoClass::Type::LastOfType: + case CSS::PseudoClass::LastOfType: return !next_sibling_with_same_tag_name(element); - case CSS::Selector::SimpleSelector::PseudoClass::Type::OnlyOfType: + case CSS::PseudoClass::OnlyOfType: return !previous_sibling_with_same_tag_name(element) && !next_sibling_with_same_tag_name(element); - case CSS::Selector::SimpleSelector::PseudoClass::Type::Lang: + case CSS::PseudoClass::Lang: return matches_lang_pseudo_class(element, pseudo_class.languages); - case CSS::Selector::SimpleSelector::PseudoClass::Type::Disabled: + case CSS::PseudoClass::Disabled: // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-disabled // The :disabled pseudo-class must match any element that is actually disabled. return element.is_actually_disabled(); - case CSS::Selector::SimpleSelector::PseudoClass::Type::Enabled: + case CSS::PseudoClass::Enabled: // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-enabled // The :enabled pseudo-class must match any button, input, select, textarea, optgroup, option, fieldset element, or form-associated custom element that is not actually disabled. return (is(element) || is(element) || is(element) || is(element) || is(element) || is(element) || is(element)) && !element.is_actually_disabled(); - case CSS::Selector::SimpleSelector::PseudoClass::Type::Checked: + case CSS::PseudoClass::Checked: return matches_checked_pseudo_class(element); - case CSS::Selector::SimpleSelector::PseudoClass::Type::Indeterminate: + case CSS::PseudoClass::Indeterminate: return matches_indeterminate_pseudo_class(element); - case CSS::Selector::SimpleSelector::PseudoClass::Type::Defined: + case CSS::PseudoClass::Defined: return element.is_defined(); - case CSS::Selector::SimpleSelector::PseudoClass::Type::Is: - case CSS::Selector::SimpleSelector::PseudoClass::Type::Where: + case CSS::PseudoClass::Is: + case CSS::PseudoClass::Where: for (auto& selector : pseudo_class.argument_selector_list) { if (matches(selector, style_sheet_for_rule, element)) return true; } return false; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Not: + case CSS::PseudoClass::Not: for (auto& selector : pseudo_class.argument_selector_list) { if (matches(selector, style_sheet_for_rule, element)) return false; } 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: { + case CSS::PseudoClass::NthChild: + case CSS::PseudoClass::NthLastChild: + case CSS::PseudoClass::NthOfType: + case CSS::PseudoClass::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) @@ -320,7 +320,7 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla int index = 1; switch (pseudo_class.type) { - case CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild: { + case CSS::PseudoClass::NthChild: { if (!matches_selector_list(pseudo_class.argument_selector_list, element)) return false; for (auto* child = parent->first_child_of_type(); child && child != &element; child = child->next_element_sibling()) { @@ -329,7 +329,7 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla } break; } - case CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild: { + case CSS::PseudoClass::NthLastChild: { if (!matches_selector_list(pseudo_class.argument_selector_list, element)) return false; for (auto* child = parent->last_child_of_type(); child && child != &element; child = child->previous_element_sibling()) { @@ -338,12 +338,12 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla } break; } - case CSS::Selector::SimpleSelector::PseudoClass::Type::NthOfType: { + case CSS::PseudoClass::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: { + case CSS::PseudoClass::NthLastOfType: { for (auto* child = next_sibling_with_same_tag_name(element); child; child = next_sibling_with_same_tag_name(*child)) ++index; break; @@ -384,48 +384,48 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla // Otherwise, we start at "offset" and count forwards. return index >= offset && canonical_modulo(index - offset, step_size) == 0; } - case CSS::Selector::SimpleSelector::PseudoClass::Type::Playing: { + case CSS::PseudoClass::Playing: { if (!is(element)) return false; auto const& media_element = static_cast(element); return !media_element.paused(); } - case CSS::Selector::SimpleSelector::PseudoClass::Type::Paused: { + case CSS::PseudoClass::Paused: { if (!is(element)) return false; auto const& media_element = static_cast(element); return media_element.paused(); } - case CSS::Selector::SimpleSelector::PseudoClass::Type::Seeking: { + case CSS::PseudoClass::Seeking: { if (!is(element)) return false; auto const& media_element = static_cast(element); return media_element.seeking(); } - case CSS::Selector::SimpleSelector::PseudoClass::Type::Muted: { + case CSS::PseudoClass::Muted: { if (!is(element)) return false; auto const& media_element = static_cast(element); return media_element.muted(); } - case CSS::Selector::SimpleSelector::PseudoClass::Type::VolumeLocked: { + case CSS::PseudoClass::VolumeLocked: { // FIXME: Currently we don't allow the user to specify an override volume, so this is always false. // Once we do, implement this! return false; } - case CSS::Selector::SimpleSelector::PseudoClass::Type::Buffering: { + case CSS::PseudoClass::Buffering: { if (!is(element)) return false; auto const& media_element = static_cast(element); return media_element.blocked(); } - case CSS::Selector::SimpleSelector::PseudoClass::Type::Stalled: { + case CSS::PseudoClass::Stalled: { if (!is(element)) return false; auto const& media_element = static_cast(element); return media_element.stalled(); } - case CSS::Selector::SimpleSelector::PseudoClass::Type::Target: + case CSS::PseudoClass::Target: return element.is_target(); } diff --git a/Userland/Libraries/LibWeb/Dump.cpp b/Userland/Libraries/LibWeb/Dump.cpp index 67c3d7d6053..4997ae1be2d 100644 --- a/Userland/Libraries/LibWeb/Dump.cpp +++ b/Userland/Libraries/LibWeb/Dump.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -458,7 +459,7 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector) type_description = "Attribute"; break; case CSS::Selector::SimpleSelector::Type::PseudoClass: - type_description = "PseudoClass"; + type_description = "PseudoClassSelector"; break; case CSS::Selector::SimpleSelector::Type::PseudoElement: type_description = "PseudoElement"; @@ -477,141 +478,14 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector) if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoClass) { auto const& pseudo_class = simple_selector.pseudo_class(); - char const* pseudo_class_description = ""; - switch (pseudo_class.type) { - case CSS::Selector::SimpleSelector::PseudoClass::Type::Link: - pseudo_class_description = "Link"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Visited: - pseudo_class_description = "Visited"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Active: - pseudo_class_description = "Active"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Root: - pseudo_class_description = "Root"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Host: - pseudo_class_description = "Host"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::FirstOfType: - pseudo_class_description = "FirstOfType"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::LastOfType: - pseudo_class_description = "LastOfType"; - break; - 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; - case CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild: - pseudo_class_description = "NthLastChild"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Focus: - pseudo_class_description = "Focus"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::FocusVisible: - pseudo_class_description = "FocusVisible"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::FocusWithin: - pseudo_class_description = "FocusWithin"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Empty: - pseudo_class_description = "Empty"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Hover: - pseudo_class_description = "Hover"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::LastChild: - pseudo_class_description = "LastChild"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::FirstChild: - pseudo_class_description = "FirstChild"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::OnlyChild: - pseudo_class_description = "OnlyChild"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Disabled: - pseudo_class_description = "Disabled"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Enabled: - pseudo_class_description = "Enabled"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Checked: - pseudo_class_description = "Checked"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Indeterminate: - pseudo_class_description = "Indeterminate"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Not: - pseudo_class_description = "Not"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Is: - pseudo_class_description = "Is"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Where: - pseudo_class_description = "Where"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Lang: - pseudo_class_description = "Lang"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Scope: - pseudo_class_description = "Scope"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Defined: - pseudo_class_description = "Defined"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Playing: - pseudo_class_description = "Playing"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Paused: - pseudo_class_description = "Paused"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Seeking: - pseudo_class_description = "Seeking"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Muted: - pseudo_class_description = "Muted"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::VolumeLocked: - pseudo_class_description = "VolumeLocked"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Buffering: - pseudo_class_description = "Buffering"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Stalled: - pseudo_class_description = "Stalled"; - break; - case CSS::Selector::SimpleSelector::PseudoClass::Type::Target: - pseudo_class_description = "Target"; - break; - } + builder.appendff(" pseudo_class={}", CSS::pseudo_class_name(pseudo_class.type)); + auto pseudo_class_metadata = CSS::pseudo_class_metadata(pseudo_class.type); - builder.appendff(" pseudo_class={}", pseudo_class_description); - if (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Lang) { - builder.append('('); - builder.join(',', pseudo_class.languages); - builder.append(')'); - } else if (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Not - || pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Host - || pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Is - || pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Where) { - builder.append("(["sv); - for (auto& selector : pseudo_class.argument_selector_list) - dump_selector(builder, selector); - builder.append("])"sv); - } else if ((pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild) - || (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)) { + switch (pseudo_class_metadata.parameter_type) { + case CSS::PseudoClassMetadata::ParameterType::None: + break; + case CSS::PseudoClassMetadata::ParameterType::ANPlusB: + case CSS::PseudoClassMetadata::ParameterType::ANPlusBOf: { 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=["sv); @@ -620,6 +494,22 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector) builder.append("]"sv); } builder.append(")"sv); + break; + } + case CSS::PseudoClassMetadata::ParameterType::ForgivingSelectorList: + case CSS::PseudoClassMetadata::ParameterType::SelectorList: { + builder.append("(["sv); + for (auto& selector : pseudo_class.argument_selector_list) + dump_selector(builder, selector); + builder.append("])"sv); + break; + } + case CSS::PseudoClassMetadata::ParameterType::LanguageRanges: { + builder.append('('); + builder.join(',', pseudo_class.languages); + builder.append(')'); + break; + } } }