/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 | | } |