/* * Copyright (c) 2018-2020, Andreas Kling * Copyright (c) 2021-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include namespace Web::CSS { using SelectorList = Vector>; enum class PseudoElement : u8 { Before, After, FirstLine, FirstLetter, Marker, Track, Fill, Thumb, Placeholder, Selection, Backdrop, FileSelectorButton, DetailsContent, // Keep this last. KnownPseudoElementCount, // https://www.w3.org/TR/selectors-4/#compat // NOTE: This is not last as the 'unknown -webkit- pseudo-elements' are not stored as part of any Element. UnknownWebKit, }; // This is a in the spec. https://www.w3.org/TR/selectors-4/#complex class Selector : public RefCounted { public: class PseudoElementSelector { public: explicit PseudoElementSelector(PseudoElement type) : m_type(type) { VERIFY(is_known_pseudo_element_type(type)); } PseudoElementSelector(PseudoElement type, String name) : m_type(type) , m_name(move(name)) { } bool operator==(PseudoElementSelector const&) const = default; static Optional from_string(FlyString const&); [[nodiscard]] static bool is_known_pseudo_element_type(PseudoElement type) { return to_underlying(type) < to_underlying(PseudoElement::KnownPseudoElementCount); } static StringView name(PseudoElement pseudo_element); StringView name() const { if (!m_name.is_empty()) return m_name; return name(m_type); } PseudoElement type() const { return m_type; } private: PseudoElement m_type; String m_name; }; struct SimpleSelector { enum class Type : u8 { Universal, TagName, Id, Class, Attribute, PseudoClass, PseudoElement, Nesting, Invalid, }; struct ANPlusBPattern { int step_size { 0 }; // "A" int offset = { 0 }; // "B" // https://www.w3.org/TR/css-syntax-3/#serializing-anb String serialize() const { // 1. If A is zero, return the serialization of B. if (step_size == 0) { return String::number(offset); } // 2. Otherwise, let result initially be an empty string. StringBuilder result; // 3. // - A is 1: Append "n" to result. if (step_size == 1) result.append('n'); // - A is -1: Append "-n" to result. else if (step_size == -1) result.append("-n"sv); // - A is non-zero: Serialize A and append it to result, then append "n" to result. else if (step_size != 0) result.appendff("{}n", step_size); // 4. // - B is greater than zero: Append "+" to result, then append the serialization of B to result. if (offset > 0) result.appendff("+{}", offset); // - B is less than zero: Append the serialization of B to result. if (offset < 0) result.appendff("{}", offset); // 5. Return result. return MUST(result.to_string()); } }; 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". ANPlusBPattern nth_child_pattern {}; // FIXME: This would make more sense as part of SelectorList but that's currently a `using` bool is_forgiving { false }; SelectorList argument_selector_list {}; // Used for :lang(en-gb,dk) Vector languages {}; // Used by :dir() Optional keyword {}; }; struct Name { Name(FlyString n) : name(move(n)) , lowercase_name(name.to_string().to_lowercase().release_value_but_fixme_should_propagate_errors()) { } FlyString name; FlyString lowercase_name; }; // Equivalent to `` // https://www.w3.org/TR/selectors-4/#typedef-wq-name struct QualifiedName { enum class NamespaceType { Default, // `E` None, // `|E` Any, // `*|E` Named, // `ns|E` }; NamespaceType namespace_type { NamespaceType::Default }; FlyString namespace_ {}; Name name; }; struct Attribute { enum class MatchType { HasAttribute, ExactValueMatch, ContainsWord, // [att~=val] ContainsString, // [att*=val] StartsWithSegment, // [att|=val] StartsWithString, // [att^=val] EndsWithString, // [att$=val] }; enum class CaseType { DefaultMatch, CaseSensitiveMatch, CaseInsensitiveMatch, }; MatchType match_type; QualifiedName qualified_name; String value {}; CaseType case_type; }; struct Invalid { Vector component_values; }; Type type; Variant value {}; Attribute const& attribute() const { return value.get(); } Attribute& attribute() { return value.get(); } PseudoClassSelector const& pseudo_class() const { return value.get(); } PseudoClassSelector& pseudo_class() { return value.get(); } PseudoElementSelector const& pseudo_element() const { return value.get(); } PseudoElementSelector& pseudo_element() { return value.get(); } FlyString const& name() const { return value.get().name; } FlyString& name() { return value.get().name; } FlyString const& lowercase_name() const { return value.get().lowercase_name; } FlyString& lowercase_name() { return value.get().lowercase_name; } QualifiedName const& qualified_name() const { return value.get(); } QualifiedName& qualified_name() { return value.get(); } String serialize() const; Optional absolutized(SimpleSelector const& selector_for_nesting) const; }; enum class Combinator { None, ImmediateChild, // > Descendant, // NextSibling, // + SubsequentSibling, // ~ Column, // || }; struct CompoundSelector { // Spec-wise, the is not part of a , // but it is more understandable to put them together. Combinator combinator { Combinator::None }; Vector simple_selectors; Optional absolutized(SimpleSelector const& selector_for_nesting) const; }; static NonnullRefPtr create(Vector&& compound_selectors) { return adopt_ref(*new Selector(move(compound_selectors))); } ~Selector() = default; Vector const& compound_selectors() const { return m_compound_selectors; } Optional const& pseudo_element() const { return m_pseudo_element; } NonnullRefPtr relative_to(SimpleSelector const&) const; bool contains_the_nesting_selector() const { return m_contains_the_nesting_selector; } bool contains_hover_pseudo_class() const { return m_contains_hover_pseudo_class; } bool contains_unknown_webkit_pseudo_element() const; RefPtr absolutized(SimpleSelector const& selector_for_nesting) const; u32 specificity() const; String serialize() const; auto const& ancestor_hashes() const { return m_ancestor_hashes; } bool can_use_fast_matches() const { return m_can_use_fast_matches; } bool can_use_ancestor_filter() const { return m_can_use_ancestor_filter; } size_t sibling_invalidation_distance() const; private: explicit Selector(Vector&&); Vector m_compound_selectors; mutable Optional m_specificity; Optional m_pseudo_element; mutable Optional m_sibling_invalidation_distance; bool m_can_use_fast_matches { false }; bool m_can_use_ancestor_filter { false }; bool m_contains_the_nesting_selector { false }; bool m_contains_hover_pseudo_class { false }; void collect_ancestor_hashes(); Array m_ancestor_hashes; }; String serialize_a_group_of_selectors(SelectorList const& selectors); SelectorList adapt_nested_relative_selector_list(SelectorList const&); bool is_has_allowed_pseudo_element(PseudoElement); } namespace AK { template<> struct Formatter : Formatter { ErrorOr format(FormatBuilder& builder, Web::CSS::Selector const& selector) { return Formatter::format(builder, selector.serialize()); } }; }