Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.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/Vector.h>
14
15namespace Web::CSS {
16
17using SelectorList = Vector<NonnullRefPtr<class Selector>>;
18
19// This is a <complex-selector> in the spec. https://www.w3.org/TR/selectors-4/#complex
20class Selector : public RefCounted<Selector> {
21public:
22 enum class PseudoElement {
23 Before,
24 After,
25 FirstLine,
26 FirstLetter,
27 Marker,
28 ProgressValue,
29 ProgressBar,
30 Placeholder,
31
32 // Keep this last.
33 PseudoElementCount,
34 };
35
36 struct SimpleSelector {
37 enum class Type {
38 Universal,
39 TagName,
40 Id,
41 Class,
42 Attribute,
43 PseudoClass,
44 PseudoElement,
45 };
46
47 struct ANPlusBPattern {
48 int step_size { 0 }; // "A"
49 int offset = { 0 }; // "B"
50
51 // https://www.w3.org/TR/css-syntax-3/#serializing-anb
52 ErrorOr<String> serialize() const
53 {
54 // 1. If A is zero, return the serialization of B.
55 if (step_size == 0) {
56 return String::formatted("{}", offset);
57 }
58
59 // 2. Otherwise, let result initially be an empty string.
60 StringBuilder result;
61
62 // 3.
63 // - A is 1: Append "n" to result.
64 if (step_size == 1)
65 TRY(result.try_append('n'));
66 // - A is -1: Append "-n" to result.
67 else if (step_size == -1)
68 TRY(result.try_append("-n"sv));
69 // - A is non-zero: Serialize A and append it to result, then append "n" to result.
70 else if (step_size != 0)
71 TRY(result.try_appendff("{}n", step_size));
72
73 // 4.
74 // - B is greater than zero: Append "+" to result, then append the serialization of B to result.
75 if (offset > 0)
76 TRY(result.try_appendff("+{}", offset));
77 // - B is less than zero: Append the serialization of B to result.
78 if (offset < 0)
79 TRY(result.try_appendff("{}", offset));
80
81 // 5. Return result.
82 return result.to_string();
83 }
84 };
85
86 struct PseudoClass {
87 enum class Type {
88 Link,
89 Visited,
90 Hover,
91 Focus,
92 FocusWithin,
93 FirstChild,
94 LastChild,
95 OnlyChild,
96 NthChild,
97 NthLastChild,
98 Empty,
99 Root,
100 FirstOfType,
101 LastOfType,
102 OnlyOfType,
103 NthOfType,
104 NthLastOfType,
105 Disabled,
106 Enabled,
107 Checked,
108 Is,
109 Not,
110 Where,
111 Active,
112 Lang,
113 };
114 Type type;
115
116 // FIXME: We don't need this field on every single SimpleSelector, but it's also annoying to malloc it somewhere.
117 // Only used when "pseudo_class" is "NthChild" or "NthLastChild".
118 ANPlusBPattern nth_child_pattern {};
119
120 SelectorList argument_selector_list {};
121
122 // Used for :lang(en-gb,dk)
123 Vector<FlyString> languages {};
124 };
125
126 struct Attribute {
127 enum class MatchType {
128 HasAttribute,
129 ExactValueMatch,
130 ContainsWord, // [att~=val]
131 ContainsString, // [att*=val]
132 StartsWithSegment, // [att|=val]
133 StartsWithString, // [att^=val]
134 EndsWithString, // [att$=val]
135 };
136 enum class CaseType {
137 DefaultMatch,
138 CaseSensitiveMatch,
139 CaseInsensitiveMatch,
140 };
141 MatchType match_type;
142 FlyString name {};
143 String value {};
144 CaseType case_type;
145 };
146
147 struct Name {
148 Name(FlyString n)
149 : name(move(n))
150 , lowercase_name(name.to_string().to_lowercase().release_value_but_fixme_should_propagate_errors())
151 {
152 }
153
154 FlyString name;
155 FlyString lowercase_name;
156 };
157
158 Type type;
159 Variant<Empty, Attribute, PseudoClass, PseudoElement, Name> value {};
160
161 Attribute const& attribute() const { return value.get<Attribute>(); }
162 Attribute& attribute() { return value.get<Attribute>(); }
163 PseudoClass const& pseudo_class() const { return value.get<PseudoClass>(); }
164 PseudoClass& pseudo_class() { return value.get<PseudoClass>(); }
165 PseudoElement const& pseudo_element() const { return value.get<PseudoElement>(); }
166 PseudoElement& pseudo_element() { return value.get<PseudoElement>(); }
167
168 FlyString const& name() const { return value.get<Name>().name; }
169 FlyString& name() { return value.get<Name>().name; }
170 FlyString const& lowercase_name() const { return value.get<Name>().lowercase_name; }
171 FlyString& lowercase_name() { return value.get<Name>().lowercase_name; }
172
173 ErrorOr<String> serialize() const;
174 };
175
176 enum class Combinator {
177 None,
178 ImmediateChild, // >
179 Descendant, // <whitespace>
180 NextSibling, // +
181 SubsequentSibling, // ~
182 Column, // ||
183 };
184
185 struct CompoundSelector {
186 // Spec-wise, the <combinator> is not part of a <compound-selector>,
187 // but it is more understandable to put them together.
188 Combinator combinator { Combinator::None };
189 Vector<SimpleSelector> simple_selectors;
190 };
191
192 static NonnullRefPtr<Selector> create(Vector<CompoundSelector>&& compound_selectors)
193 {
194 return adopt_ref(*new Selector(move(compound_selectors)));
195 }
196
197 ~Selector() = default;
198
199 Vector<CompoundSelector> const& compound_selectors() const { return m_compound_selectors; }
200 Optional<PseudoElement> pseudo_element() const { return m_pseudo_element; }
201 u32 specificity() const;
202 ErrorOr<String> serialize() const;
203
204private:
205 explicit Selector(Vector<CompoundSelector>&&);
206
207 Vector<CompoundSelector> m_compound_selectors;
208 mutable Optional<u32> m_specificity;
209 Optional<Selector::PseudoElement> m_pseudo_element;
210};
211
212constexpr StringView pseudo_element_name(Selector::PseudoElement pseudo_element)
213{
214 switch (pseudo_element) {
215 case Selector::PseudoElement::Before:
216 return "before"sv;
217 case Selector::PseudoElement::After:
218 return "after"sv;
219 case Selector::PseudoElement::FirstLine:
220 return "first-line"sv;
221 case Selector::PseudoElement::FirstLetter:
222 return "first-letter"sv;
223 case Selector::PseudoElement::Marker:
224 return "marker"sv;
225 case Selector::PseudoElement::ProgressBar:
226 return "-webkit-progress-bar"sv;
227 case Selector::PseudoElement::ProgressValue:
228 return "-webkit-progress-value"sv;
229 case Selector::PseudoElement::Placeholder:
230 return "placeholder"sv;
231 case Selector::PseudoElement::PseudoElementCount:
232 break;
233 }
234 VERIFY_NOT_REACHED();
235}
236
237Optional<Selector::PseudoElement> pseudo_element_from_string(StringView);
238
239constexpr StringView pseudo_class_name(Selector::SimpleSelector::PseudoClass::Type pseudo_class)
240{
241 switch (pseudo_class) {
242 case Selector::SimpleSelector::PseudoClass::Type::Link:
243 return "link"sv;
244 case Selector::SimpleSelector::PseudoClass::Type::Visited:
245 return "visited"sv;
246 case Selector::SimpleSelector::PseudoClass::Type::Hover:
247 return "hover"sv;
248 case Selector::SimpleSelector::PseudoClass::Type::Focus:
249 return "focus"sv;
250 case Selector::SimpleSelector::PseudoClass::Type::FocusWithin:
251 return "focus-within"sv;
252 case Selector::SimpleSelector::PseudoClass::Type::FirstChild:
253 return "first-child"sv;
254 case Selector::SimpleSelector::PseudoClass::Type::LastChild:
255 return "last-child"sv;
256 case Selector::SimpleSelector::PseudoClass::Type::OnlyChild:
257 return "only-child"sv;
258 case Selector::SimpleSelector::PseudoClass::Type::Empty:
259 return "empty"sv;
260 case Selector::SimpleSelector::PseudoClass::Type::Root:
261 return "root"sv;
262 case Selector::SimpleSelector::PseudoClass::Type::FirstOfType:
263 return "first-of-type"sv;
264 case Selector::SimpleSelector::PseudoClass::Type::LastOfType:
265 return "last-of-type"sv;
266 case Selector::SimpleSelector::PseudoClass::Type::OnlyOfType:
267 return "only-of-type"sv;
268 case Selector::SimpleSelector::PseudoClass::Type::NthOfType:
269 return "nth-of-type"sv;
270 case Selector::SimpleSelector::PseudoClass::Type::NthLastOfType:
271 return "nth-last-of-type"sv;
272 case Selector::SimpleSelector::PseudoClass::Type::Disabled:
273 return "disabled"sv;
274 case Selector::SimpleSelector::PseudoClass::Type::Enabled:
275 return "enabled"sv;
276 case Selector::SimpleSelector::PseudoClass::Type::Checked:
277 return "checked"sv;
278 case Selector::SimpleSelector::PseudoClass::Type::Active:
279 return "active"sv;
280 case Selector::SimpleSelector::PseudoClass::Type::NthChild:
281 return "nth-child"sv;
282 case Selector::SimpleSelector::PseudoClass::Type::NthLastChild:
283 return "nth-last-child"sv;
284 case Selector::SimpleSelector::PseudoClass::Type::Is:
285 return "is"sv;
286 case Selector::SimpleSelector::PseudoClass::Type::Not:
287 return "not"sv;
288 case Selector::SimpleSelector::PseudoClass::Type::Where:
289 return "where"sv;
290 case Selector::SimpleSelector::PseudoClass::Type::Lang:
291 return "lang"sv;
292 }
293 VERIFY_NOT_REACHED();
294}
295
296ErrorOr<String> serialize_a_group_of_selectors(Vector<NonnullRefPtr<Selector>> const& selectors);
297
298}
299
300namespace AK {
301
302template<>
303struct Formatter<Web::CSS::Selector> : Formatter<StringView> {
304 ErrorOr<void> format(FormatBuilder& builder, Web::CSS::Selector const& selector)
305 {
306 return Formatter<StringView>::format(builder, TRY(selector.serialize()));
307 }
308};
309
310}