/* * Copyright (c) 2018-2020, Andreas Kling * Copyright (c) 2021-2023, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include namespace Web::CSS { using SelectorList = NonnullRefPtrVector; // This is a in the spec. https://www.w3.org/TR/selectors-4/#complex class Selector : public RefCounted { public: enum class PseudoElement { Before, After, FirstLine, FirstLetter, Marker, ProgressValue, ProgressBar, Placeholder, // Keep this last. PseudoElementCount, }; struct SimpleSelector { enum class Type { Universal, TagName, Id, Class, Attribute, PseudoClass, PseudoElement, }; struct ANPlusBPattern { int step_size { 0 }; // "A" int offset = { 0 }; // "B" // https://www.w3.org/TR/css-syntax-3/#serializing-anb ErrorOr serialize() const { // 1. If A is zero, return the serialization of B. if (step_size == 0) { return String::formatted("{}", offset); } // 2. Otherwise, let result initially be an empty string. StringBuilder result; // 3. // - A is 1: Append "n" to result. if (step_size == 1) TRY(result.try_append('n')); // - A is -1: Append "-n" to result. else if (step_size == -1) TRY(result.try_append("-n"sv)); // - A is non-zero: Serialize A and append it to result, then append "n" to result. else if (step_size != 0) TRY(result.try_appendff("{}n", step_size)); // 4. // - B is greater than zero: Append "+" to result, then append the serialization of B to result. if (offset > 0) TRY(result.try_appendff("+{}", offset)); // - B is less than zero: Append the serialization of B to result. if (offset < 0) TRY(result.try_appendff("{}", offset)); // 5. Return result. return result.to_string(); } }; struct PseudoClass { enum class Type { Link, Visited, Hover, Focus, FocusWithin, FirstChild, LastChild, OnlyChild, NthChild, NthLastChild, Empty, Root, FirstOfType, LastOfType, OnlyOfType, NthOfType, NthLastOfType, Disabled, Enabled, Checked, Is, Not, Where, Active, Lang, }; Type 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 {}; SelectorList argument_selector_list {}; // Used for :lang(en-gb,dk) Vector languages {}; }; 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; FlyString name {}; String value {}; CaseType case_type; }; 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; }; Type type; 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(); } PseudoElement const& pseudo_element() const { return value.get(); } PseudoElement& 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; } ErrorOr serialize() 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; }; 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 pseudo_element() const { return m_pseudo_element; } u32 specificity() const; ErrorOr serialize() const; private: explicit Selector(Vector&&); Vector m_compound_selectors; mutable Optional m_specificity; Optional m_pseudo_element; }; constexpr StringView pseudo_element_name(Selector::PseudoElement pseudo_element) { switch (pseudo_element) { case Selector::PseudoElement::Before: return "before"sv; case Selector::PseudoElement::After: return "after"sv; case Selector::PseudoElement::FirstLine: return "first-line"sv; case Selector::PseudoElement::FirstLetter: return "first-letter"sv; case Selector::PseudoElement::Marker: return "marker"sv; case Selector::PseudoElement::ProgressBar: return "-webkit-progress-bar"sv; case Selector::PseudoElement::ProgressValue: return "-webkit-progress-value"sv; case Selector::PseudoElement::Placeholder: return "placeholder"sv; case Selector::PseudoElement::PseudoElementCount: break; } VERIFY_NOT_REACHED(); } 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::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::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::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; } VERIFY_NOT_REACHED(); } ErrorOr serialize_a_group_of_selectors(NonnullRefPtrVector const& selectors); } namespace AK { template<> struct Formatter : Formatter { ErrorOr format(FormatBuilder& builder, Web::CSS::Selector const& selector) { return Formatter::format(builder, TRY(selector.serialize())); } }; }