/* * Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org> * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include <AK/FlyString.h> #include <AK/RefCounted.h> #include <AK/String.h> #include <AK/Vector.h> #include <LibWeb/CSS/Keyword.h> #include <LibWeb/CSS/Parser/ComponentValue.h> #include <LibWeb/CSS/PseudoClass.h> namespace Web::CSS { using SelectorList = Vector<NonnullRefPtr<class Selector>>; // This is a <complex-selector> in the spec. https://www.w3.org/TR/selectors-4/#complex class Selector : public RefCounted<Selector> { public: class PseudoElement { public: enum class Type : u8 { Before, After, FirstLine, FirstLetter, Marker, MeterBar, MeterEvenLessGoodValue, MeterOptimumValue, MeterSuboptimumValue, ProgressValue, ProgressBar, Placeholder, Selection, SliderRunnableTrack, SliderThumb, 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, }; explicit PseudoElement(Type type) : m_type(type) { VERIFY(is_known_pseudo_element_type(type)); } PseudoElement(Type type, String name) : m_type(type) , m_name(move(name)) { } bool operator==(PseudoElement const&) const = default; static Optional<PseudoElement> from_string(FlyString const&); [[nodiscard]] static bool is_known_pseudo_element_type(Type type) { return to_underlying(type) < to_underlying(CSS::Selector::PseudoElement::Type::KnownPseudoElementCount); } static StringView name(Selector::PseudoElement::Type pseudo_element); StringView name() const { if (!m_name.is_empty()) return m_name; return name(m_type); } Type type() const { return m_type; } private: Type 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<FlyString> languages {}; // Used by :dir() Optional<Keyword> 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 `<wq-name>` // 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<Parser::ComponentValue> component_values; }; Type type; Variant<Empty, Attribute, PseudoClassSelector, PseudoElement, Name, QualifiedName, Invalid> value {}; Attribute const& attribute() const { return value.get<Attribute>(); } Attribute& attribute() { return value.get<Attribute>(); } PseudoClassSelector const& pseudo_class() const { return value.get<PseudoClassSelector>(); } PseudoClassSelector& pseudo_class() { return value.get<PseudoClassSelector>(); } PseudoElement const& pseudo_element() const { return value.get<PseudoElement>(); } PseudoElement& pseudo_element() { return value.get<PseudoElement>(); } FlyString const& name() const { return value.get<Name>().name; } FlyString& name() { return value.get<Name>().name; } FlyString const& lowercase_name() const { return value.get<Name>().lowercase_name; } FlyString& lowercase_name() { return value.get<Name>().lowercase_name; } QualifiedName const& qualified_name() const { return value.get<QualifiedName>(); } QualifiedName& qualified_name() { return value.get<QualifiedName>(); } String serialize() const; Optional<SimpleSelector> absolutized(SimpleSelector const& selector_for_nesting) const; }; enum class Combinator { None, ImmediateChild, // > Descendant, // <whitespace> NextSibling, // + SubsequentSibling, // ~ Column, // || }; struct CompoundSelector { // Spec-wise, the <combinator> is not part of a <compound-selector>, // but it is more understandable to put them together. Combinator combinator { Combinator::None }; Vector<SimpleSelector> simple_selectors; Optional<CompoundSelector> absolutized(SimpleSelector const& selector_for_nesting) const; }; static NonnullRefPtr<Selector> create(Vector<CompoundSelector>&& compound_selectors) { return adopt_ref(*new Selector(move(compound_selectors))); } ~Selector() = default; Vector<CompoundSelector> const& compound_selectors() const { return m_compound_selectors; } Optional<PseudoElement> const& pseudo_element() const { return m_pseudo_element; } NonnullRefPtr<Selector> 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; } RefPtr<Selector> absolutized(SimpleSelector const& selector_for_nesting) const; u32 specificity() const; String serialize() const; auto const& ancestor_hashes() const { return m_ancestor_hashes; } private: explicit Selector(Vector<CompoundSelector>&&); Vector<CompoundSelector> m_compound_selectors; mutable Optional<u32> m_specificity; Optional<Selector::PseudoElement> m_pseudo_element; bool m_contains_the_nesting_selector { false }; bool m_contains_hover_pseudo_class { false }; void collect_ancestor_hashes(); Array<u32, 8> 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(Selector::PseudoElement::Type); } namespace AK { template<> struct Formatter<Web::CSS::Selector> : Formatter<StringView> { ErrorOr<void> format(FormatBuilder& builder, Web::CSS::Selector const& selector) { return Formatter<StringView>::format(builder, selector.serialize()); } }; }