/src/serenity/Userland/Libraries/LibWeb/CSS/MediaQuery.h
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #pragma once |
8 | | |
9 | | #include <AK/NonnullRefPtr.h> |
10 | | #include <AK/Optional.h> |
11 | | #include <AK/OwnPtr.h> |
12 | | #include <AK/RefCounted.h> |
13 | | #include <LibWeb/CSS/GeneralEnclosed.h> |
14 | | #include <LibWeb/CSS/Length.h> |
15 | | #include <LibWeb/CSS/MediaFeatureID.h> |
16 | | #include <LibWeb/CSS/Ratio.h> |
17 | | #include <LibWeb/CSS/Resolution.h> |
18 | | |
19 | | namespace Web::CSS { |
20 | | |
21 | | // https://www.w3.org/TR/mediaqueries-4/#typedef-mf-value |
22 | | class MediaFeatureValue { |
23 | | public: |
24 | | explicit MediaFeatureValue(Keyword ident) |
25 | 0 | : m_value(move(ident)) |
26 | 0 | { |
27 | 0 | } |
28 | | |
29 | | explicit MediaFeatureValue(Length length) |
30 | 0 | : m_value(move(length)) |
31 | 0 | { |
32 | 0 | } |
33 | | |
34 | | explicit MediaFeatureValue(Ratio ratio) |
35 | 0 | : m_value(move(ratio)) |
36 | 0 | { |
37 | 0 | } |
38 | | |
39 | | explicit MediaFeatureValue(Resolution resolution) |
40 | 0 | : m_value(move(resolution)) |
41 | 0 | { |
42 | 0 | } |
43 | | |
44 | | explicit MediaFeatureValue(float number) |
45 | 0 | : m_value(number) |
46 | 0 | { |
47 | 0 | } |
48 | | |
49 | | String to_string() const; |
50 | | |
51 | 0 | bool is_ident() const { return m_value.has<Keyword>(); } |
52 | 0 | bool is_length() const { return m_value.has<Length>(); } |
53 | 0 | bool is_number() const { return m_value.has<float>(); } |
54 | 0 | bool is_ratio() const { return m_value.has<Ratio>(); } |
55 | 0 | bool is_resolution() const { return m_value.has<Resolution>(); } |
56 | | bool is_same_type(MediaFeatureValue const& other) const; |
57 | | |
58 | | Keyword const& ident() const |
59 | 0 | { |
60 | 0 | VERIFY(is_ident()); |
61 | 0 | return m_value.get<Keyword>(); |
62 | 0 | } |
63 | | |
64 | | Length const& length() const |
65 | 0 | { |
66 | 0 | VERIFY(is_length()); |
67 | 0 | return m_value.get<Length>(); |
68 | 0 | } |
69 | | |
70 | | Ratio const& ratio() const |
71 | 0 | { |
72 | 0 | VERIFY(is_ratio()); |
73 | 0 | return m_value.get<Ratio>(); |
74 | 0 | } |
75 | | |
76 | | Resolution const& resolution() const |
77 | 0 | { |
78 | 0 | VERIFY(is_resolution()); |
79 | 0 | return m_value.get<Resolution>(); |
80 | 0 | } |
81 | | |
82 | | float number() const |
83 | 0 | { |
84 | 0 | VERIFY(is_number()); |
85 | 0 | return m_value.get<float>(); |
86 | 0 | } |
87 | | |
88 | | private: |
89 | | Variant<Keyword, Length, Ratio, Resolution, float> m_value; |
90 | | }; |
91 | | |
92 | | // https://www.w3.org/TR/mediaqueries-4/#mq-features |
93 | | class MediaFeature { |
94 | | public: |
95 | | enum class Comparison { |
96 | | Equal, |
97 | | LessThan, |
98 | | LessThanOrEqual, |
99 | | GreaterThan, |
100 | | GreaterThanOrEqual, |
101 | | }; |
102 | | |
103 | | // Corresponds to `<mf-boolean>` grammar |
104 | | static MediaFeature boolean(MediaFeatureID id) |
105 | 0 | { |
106 | 0 | return MediaFeature(Type::IsTrue, id); |
107 | 0 | } |
108 | | |
109 | | // Corresponds to `<mf-plain>` grammar |
110 | | static MediaFeature plain(MediaFeatureID id, MediaFeatureValue value) |
111 | 0 | { |
112 | 0 | return MediaFeature(Type::ExactValue, move(id), move(value)); |
113 | 0 | } |
114 | | static MediaFeature min(MediaFeatureID id, MediaFeatureValue value) |
115 | 0 | { |
116 | 0 | return MediaFeature(Type::MinValue, id, move(value)); |
117 | 0 | } |
118 | | static MediaFeature max(MediaFeatureID id, MediaFeatureValue value) |
119 | 0 | { |
120 | 0 | return MediaFeature(Type::MaxValue, id, move(value)); |
121 | 0 | } |
122 | | |
123 | | // Corresponds to `<mf-range>` grammar, with a single comparison |
124 | | static MediaFeature half_range(MediaFeatureValue value, Comparison comparison, MediaFeatureID id) |
125 | 0 | { |
126 | 0 | MediaFeature feature { Type::Range, id }; |
127 | 0 | feature.m_range = Range { |
128 | 0 | .left_value = value, |
129 | 0 | .left_comparison = comparison, |
130 | 0 | }; |
131 | 0 | return feature; |
132 | 0 | } |
133 | | |
134 | | // Corresponds to `<mf-range>` grammar, with two comparisons |
135 | | static MediaFeature range(MediaFeatureValue left_value, Comparison left_comparison, MediaFeatureID id, Comparison right_comparison, MediaFeatureValue right_value) |
136 | 0 | { |
137 | 0 | MediaFeature feature { Type::Range, id }; |
138 | 0 | feature.m_range = Range { |
139 | 0 | .left_value = left_value, |
140 | 0 | .left_comparison = left_comparison, |
141 | 0 | .right_comparison = right_comparison, |
142 | 0 | .right_value = right_value, |
143 | 0 | }; |
144 | 0 | return feature; |
145 | 0 | } |
146 | | |
147 | | bool evaluate(HTML::Window const&) const; |
148 | | String to_string() const; |
149 | | |
150 | | private: |
151 | | enum class Type { |
152 | | IsTrue, |
153 | | ExactValue, |
154 | | MinValue, |
155 | | MaxValue, |
156 | | Range, |
157 | | }; |
158 | | |
159 | | MediaFeature(Type type, MediaFeatureID id, Optional<MediaFeatureValue> value = {}) |
160 | 0 | : m_type(type) |
161 | 0 | , m_id(move(id)) |
162 | 0 | , m_value(move(value)) |
163 | 0 | { |
164 | 0 | } |
165 | | |
166 | | static bool compare(HTML::Window const& window, MediaFeatureValue left, Comparison comparison, MediaFeatureValue right); |
167 | | |
168 | | struct Range { |
169 | | MediaFeatureValue left_value; |
170 | | Comparison left_comparison; |
171 | | Optional<Comparison> right_comparison {}; |
172 | | Optional<MediaFeatureValue> right_value {}; |
173 | | }; |
174 | | |
175 | | Type m_type; |
176 | | MediaFeatureID m_id; |
177 | | Optional<MediaFeatureValue> m_value {}; |
178 | | Optional<Range> m_range {}; |
179 | | }; |
180 | | |
181 | | // https://www.w3.org/TR/mediaqueries-4/#media-conditions |
182 | | struct MediaCondition { |
183 | | enum class Type { |
184 | | Single, |
185 | | And, |
186 | | Or, |
187 | | Not, |
188 | | GeneralEnclosed, |
189 | | }; |
190 | | |
191 | | // Only used in parsing |
192 | | enum class AllowOr { |
193 | | No = 0, |
194 | | Yes = 1, |
195 | | }; |
196 | | |
197 | | static NonnullOwnPtr<MediaCondition> from_general_enclosed(GeneralEnclosed&&); |
198 | | static NonnullOwnPtr<MediaCondition> from_feature(MediaFeature&&); |
199 | | static NonnullOwnPtr<MediaCondition> from_not(NonnullOwnPtr<MediaCondition>&&); |
200 | | static NonnullOwnPtr<MediaCondition> from_and_list(Vector<NonnullOwnPtr<MediaCondition>>&&); |
201 | | static NonnullOwnPtr<MediaCondition> from_or_list(Vector<NonnullOwnPtr<MediaCondition>>&&); |
202 | | |
203 | | MatchResult evaluate(HTML::Window const&) const; |
204 | | String to_string() const; |
205 | | |
206 | | private: |
207 | 0 | MediaCondition() = default; |
208 | | Type type; |
209 | | Optional<MediaFeature> feature; |
210 | | Vector<NonnullOwnPtr<MediaCondition>> conditions; |
211 | | Optional<GeneralEnclosed> general_enclosed; |
212 | | }; |
213 | | |
214 | | class MediaQuery : public RefCounted<MediaQuery> { |
215 | | friend class Parser::Parser; |
216 | | |
217 | | public: |
218 | 0 | ~MediaQuery() = default; |
219 | | |
220 | | // https://www.w3.org/TR/mediaqueries-4/#media-types |
221 | | enum class MediaType { |
222 | | All, |
223 | | Print, |
224 | | Screen, |
225 | | Unknown, |
226 | | |
227 | | // Deprecated, must never match: |
228 | | TTY, |
229 | | TV, |
230 | | Projection, |
231 | | Handheld, |
232 | | Braille, |
233 | | Embossed, |
234 | | Aural, |
235 | | Speech, |
236 | | }; |
237 | | |
238 | | static NonnullRefPtr<MediaQuery> create_not_all(); |
239 | 0 | static NonnullRefPtr<MediaQuery> create() { return adopt_ref(*new MediaQuery); } |
240 | | |
241 | 0 | bool matches() const { return m_matches; } |
242 | | bool evaluate(HTML::Window const&); |
243 | | String to_string() const; |
244 | | |
245 | | private: |
246 | 0 | MediaQuery() = default; |
247 | | |
248 | | // https://www.w3.org/TR/mediaqueries-4/#mq-not |
249 | | bool m_negated { false }; |
250 | | MediaType m_media_type { MediaType::All }; |
251 | | OwnPtr<MediaCondition> m_media_condition { nullptr }; |
252 | | |
253 | | // Cached value, updated by evaluate() |
254 | | bool m_matches { false }; |
255 | | }; |
256 | | |
257 | | String serialize_a_media_query_list(Vector<NonnullRefPtr<MediaQuery>> const&); |
258 | | |
259 | | MediaQuery::MediaType media_type_from_string(StringView); |
260 | | StringView to_string(MediaQuery::MediaType); |
261 | | |
262 | | } |
263 | | |
264 | | namespace AK { |
265 | | |
266 | | template<> |
267 | | struct Formatter<Web::CSS::MediaFeature> : Formatter<StringView> { |
268 | | ErrorOr<void> format(FormatBuilder& builder, Web::CSS::MediaFeature const& media_feature) |
269 | 0 | { |
270 | 0 | return Formatter<StringView>::format(builder, media_feature.to_string()); |
271 | 0 | } |
272 | | }; |
273 | | |
274 | | template<> |
275 | | struct Formatter<Web::CSS::MediaCondition> : Formatter<StringView> { |
276 | | ErrorOr<void> format(FormatBuilder& builder, Web::CSS::MediaCondition const& media_condition) |
277 | 0 | { |
278 | 0 | return Formatter<StringView>::format(builder, media_condition.to_string()); |
279 | 0 | } |
280 | | }; |
281 | | |
282 | | template<> |
283 | | struct Formatter<Web::CSS::MediaQuery> : Formatter<StringView> { |
284 | | ErrorOr<void> format(FormatBuilder& builder, Web::CSS::MediaQuery const& media_query) |
285 | 0 | { |
286 | 0 | return Formatter<StringView>::format(builder, media_query.to_string()); |
287 | 0 | } |
288 | | }; |
289 | | |
290 | | } |