Coverage Report

Created: 2025-12-18 07:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}