Coverage Report

Created: 2026-02-16 07:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/CSS/Selector.h
Line
Count
Source
1
/*
2
 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#pragma once
9
10
#include <AK/FlyString.h>
11
#include <AK/RefCounted.h>
12
#include <AK/String.h>
13
#include <AK/Variant.h>
14
#include <AK/Vector.h>
15
#include <LibWeb/CSS/Keyword.h>
16
#include <LibWeb/CSS/PseudoClass.h>
17
18
namespace Web::CSS {
19
20
using SelectorList = Vector<NonnullRefPtr<class Selector>>;
21
22
// This is a <complex-selector> in the spec. https://www.w3.org/TR/selectors-4/#complex
23
class Selector : public RefCounted<Selector> {
24
public:
25
    class PseudoElement {
26
    public:
27
        enum class Type : u8 {
28
            Before,
29
            After,
30
            FirstLine,
31
            FirstLetter,
32
            Marker,
33
            MeterBar,
34
            MeterEvenLessGoodValue,
35
            MeterOptimumValue,
36
            MeterSuboptimumValue,
37
            ProgressValue,
38
            ProgressBar,
39
            Placeholder,
40
            Selection,
41
            SliderRunnableTrack,
42
            SliderThumb,
43
            Backdrop,
44
45
            // Keep this last.
46
            KnownPseudoElementCount,
47
48
            // https://www.w3.org/TR/selectors-4/#compat
49
            // NOTE: This is not last as the 'unknown -webkit- pseudo-elements' are not stored as part of any Element.
50
            UnknownWebKit,
51
        };
52
53
        explicit PseudoElement(Type type)
54
0
            : m_type(type)
55
0
        {
56
0
            VERIFY(is_known_pseudo_element_type(type));
57
0
        }
58
59
        PseudoElement(Type type, String name)
60
0
            : m_type(type)
61
0
            , m_name(move(name))
62
0
        {
63
0
        }
64
65
        bool operator==(PseudoElement const&) const = default;
66
67
        static Optional<PseudoElement> from_string(FlyString const&);
68
69
        [[nodiscard]] static bool is_known_pseudo_element_type(Type type)
70
0
        {
71
0
            return to_underlying(type) < to_underlying(CSS::Selector::PseudoElement::Type::KnownPseudoElementCount);
72
0
        }
73
74
        static StringView name(Selector::PseudoElement::Type pseudo_element);
75
76
        StringView name() const
77
0
        {
78
0
            if (!m_name.is_empty())
79
0
                return m_name;
80
81
0
            return name(m_type);
82
0
        }
83
84
0
        Type type() const { return m_type; }
85
86
    private:
87
        Type m_type;
88
        String m_name;
89
    };
90
91
    struct SimpleSelector {
92
        enum class Type : u8 {
93
            Universal,
94
            TagName,
95
            Id,
96
            Class,
97
            Attribute,
98
            PseudoClass,
99
            PseudoElement,
100
            Nesting,
101
        };
102
103
        struct ANPlusBPattern {
104
            int step_size { 0 }; // "A"
105
            int offset = { 0 };  // "B"
106
107
            // https://www.w3.org/TR/css-syntax-3/#serializing-anb
108
            String serialize() const
109
0
            {
110
                // 1. If A is zero, return the serialization of B.
111
0
                if (step_size == 0) {
112
0
                    return String::number(offset);
113
0
                }
114
115
                // 2. Otherwise, let result initially be an empty string.
116
0
                StringBuilder result;
117
118
                // 3.
119
                // - A is 1: Append "n" to result.
120
0
                if (step_size == 1)
121
0
                    result.append('n');
122
                // - A is -1: Append "-n" to result.
123
0
                else if (step_size == -1)
124
0
                    result.append("-n"sv);
125
                // - A is non-zero: Serialize A and append it to result, then append "n" to result.
126
0
                else if (step_size != 0)
127
0
                    result.appendff("{}n", step_size);
128
129
                // 4.
130
                // - B is greater than zero: Append "+" to result, then append the serialization of B to result.
131
0
                if (offset > 0)
132
0
                    result.appendff("+{}", offset);
133
                // - B is less than zero: Append the serialization of B to result.
134
0
                if (offset < 0)
135
0
                    result.appendff("{}", offset);
136
137
                // 5. Return result.
138
0
                return MUST(result.to_string());
139
0
            }
140
        };
141
142
        struct PseudoClassSelector {
143
            PseudoClass type;
144
145
            // FIXME: We don't need this field on every single SimpleSelector, but it's also annoying to malloc it somewhere.
146
            // Only used when "pseudo_class" is "NthChild" or "NthLastChild".
147
            ANPlusBPattern nth_child_pattern {};
148
149
            SelectorList argument_selector_list {};
150
151
            // Used for :lang(en-gb,dk)
152
            Vector<FlyString> languages {};
153
154
            // Used by :dir()
155
            Optional<Keyword> keyword {};
156
        };
157
158
        struct Name {
159
            Name(FlyString n)
160
0
                : name(move(n))
161
0
                , lowercase_name(name.to_string().to_lowercase().release_value_but_fixme_should_propagate_errors())
162
0
            {
163
0
            }
164
165
            FlyString name;
166
            FlyString lowercase_name;
167
        };
168
169
        // Equivalent to `<wq-name>`
170
        // https://www.w3.org/TR/selectors-4/#typedef-wq-name
171
        struct QualifiedName {
172
            enum class NamespaceType {
173
                Default, // `E`
174
                None,    // `|E`
175
                Any,     // `*|E`
176
                Named,   // `ns|E`
177
            };
178
            NamespaceType namespace_type { NamespaceType::Default };
179
            FlyString namespace_ {};
180
            Name name;
181
        };
182
183
        struct Attribute {
184
            enum class MatchType {
185
                HasAttribute,
186
                ExactValueMatch,
187
                ContainsWord,      // [att~=val]
188
                ContainsString,    // [att*=val]
189
                StartsWithSegment, // [att|=val]
190
                StartsWithString,  // [att^=val]
191
                EndsWithString,    // [att$=val]
192
            };
193
            enum class CaseType {
194
                DefaultMatch,
195
                CaseSensitiveMatch,
196
                CaseInsensitiveMatch,
197
            };
198
            MatchType match_type;
199
            QualifiedName qualified_name;
200
            String value {};
201
            CaseType case_type;
202
        };
203
204
        Type type;
205
        Variant<Empty, Attribute, PseudoClassSelector, PseudoElement, Name, QualifiedName> value {};
206
207
0
        Attribute const& attribute() const { return value.get<Attribute>(); }
208
0
        Attribute& attribute() { return value.get<Attribute>(); }
209
0
        PseudoClassSelector const& pseudo_class() const { return value.get<PseudoClassSelector>(); }
210
0
        PseudoClassSelector& pseudo_class() { return value.get<PseudoClassSelector>(); }
211
0
        PseudoElement const& pseudo_element() const { return value.get<PseudoElement>(); }
212
0
        PseudoElement& pseudo_element() { return value.get<PseudoElement>(); }
213
214
0
        FlyString const& name() const { return value.get<Name>().name; }
215
0
        FlyString& name() { return value.get<Name>().name; }
216
0
        FlyString const& lowercase_name() const { return value.get<Name>().lowercase_name; }
217
0
        FlyString& lowercase_name() { return value.get<Name>().lowercase_name; }
218
0
        QualifiedName const& qualified_name() const { return value.get<QualifiedName>(); }
219
0
        QualifiedName& qualified_name() { return value.get<QualifiedName>(); }
220
221
        String serialize() const;
222
223
        SimpleSelector absolutized(SimpleSelector const& selector_for_nesting) const;
224
    };
225
226
    enum class Combinator {
227
        None,
228
        ImmediateChild,    // >
229
        Descendant,        // <whitespace>
230
        NextSibling,       // +
231
        SubsequentSibling, // ~
232
        Column,            // ||
233
    };
234
235
    struct CompoundSelector {
236
        // Spec-wise, the <combinator> is not part of a <compound-selector>,
237
        // but it is more understandable to put them together.
238
        Combinator combinator { Combinator::None };
239
        Vector<SimpleSelector> simple_selectors;
240
241
        CompoundSelector absolutized(SimpleSelector const& selector_for_nesting) const;
242
    };
243
244
    static NonnullRefPtr<Selector> create(Vector<CompoundSelector>&& compound_selectors)
245
0
    {
246
0
        return adopt_ref(*new Selector(move(compound_selectors)));
247
0
    }
248
249
0
    ~Selector() = default;
250
251
0
    Vector<CompoundSelector> const& compound_selectors() const { return m_compound_selectors; }
252
0
    Optional<PseudoElement> const& pseudo_element() const { return m_pseudo_element; }
253
    NonnullRefPtr<Selector> relative_to(SimpleSelector const&) const;
254
0
    bool contains_the_nesting_selector() const { return m_contains_the_nesting_selector; }
255
    NonnullRefPtr<Selector> absolutized(SimpleSelector const& selector_for_nesting) const;
256
    u32 specificity() const;
257
    String serialize() const;
258
259
0
    auto const& ancestor_hashes() const { return m_ancestor_hashes; }
260
261
private:
262
    explicit Selector(Vector<CompoundSelector>&&);
263
264
    Vector<CompoundSelector> m_compound_selectors;
265
    mutable Optional<u32> m_specificity;
266
    Optional<Selector::PseudoElement> m_pseudo_element;
267
    bool m_contains_the_nesting_selector { false };
268
269
    void collect_ancestor_hashes();
270
271
    Array<u32, 8> m_ancestor_hashes;
272
};
273
274
String serialize_a_group_of_selectors(SelectorList const& selectors);
275
276
}
277
278
namespace AK {
279
280
template<>
281
struct Formatter<Web::CSS::Selector> : Formatter<StringView> {
282
    ErrorOr<void> format(FormatBuilder& builder, Web::CSS::Selector const& selector)
283
0
    {
284
0
        return Formatter<StringView>::format(builder, selector.serialize());
285
0
    }
286
};
287
288
}