Coverage Report

Created: 2025-08-28 06:26

/src/serenity/Userland/Libraries/LibWeb/CSS/Parser/GradientParsing.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2020-2021, the SerenityOS developers.
4
 * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
5
 * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
6
 * Copyright (c) 2022, MacDue <macdue@dueutil.tech>
7
 *
8
 * SPDX-License-Identifier: BSD-2-Clause
9
 */
10
11
#include <AK/Debug.h>
12
#include <LibWeb/CSS/Parser/Parser.h>
13
#include <LibWeb/CSS/StyleValues/ConicGradientStyleValue.h>
14
#include <LibWeb/CSS/StyleValues/LinearGradientStyleValue.h>
15
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
16
#include <LibWeb/CSS/StyleValues/RadialGradientStyleValue.h>
17
18
namespace Web::CSS::Parser {
19
20
template<typename TElement>
21
Optional<Vector<TElement>> Parser::parse_color_stop_list(TokenStream<ComponentValue>& tokens, auto is_position, auto get_position)
22
0
{
23
0
    enum class ElementType {
24
0
        Garbage,
25
0
        ColorStop,
26
0
        ColorHint
27
0
    };
28
29
0
    auto parse_color_stop_list_element = [&](TElement& element) -> ElementType {
30
0
        tokens.discard_whitespace();
31
0
        if (!tokens.has_next_token())
32
0
            return ElementType::Garbage;
33
34
0
        RefPtr<CSSStyleValue> color;
35
0
        Optional<typename TElement::PositionType> position;
36
0
        Optional<typename TElement::PositionType> second_position;
37
0
        if (auto dimension = parse_dimension(tokens.next_token()); dimension.has_value() && is_position(*dimension)) {
38
            // [<T-percentage> <color>] or [<T-percentage>]
39
0
            position = get_position(*dimension);
40
0
            tokens.discard_a_token(); // dimension
41
0
            tokens.discard_whitespace();
42
            // <T-percentage>
43
0
            if (!tokens.has_next_token() || tokens.next_token().is(Token::Type::Comma)) {
44
0
                element.transition_hint = typename TElement::ColorHint { *position };
45
0
                return ElementType::ColorHint;
46
0
            }
47
            // <T-percentage> <color>
48
0
            auto maybe_color = parse_color_value(tokens);
49
0
            if (!maybe_color)
50
0
                return ElementType::Garbage;
51
0
            color = maybe_color.release_nonnull();
52
0
        } else {
53
            // [<color> <T-percentage>?]
54
0
            auto maybe_color = parse_color_value(tokens);
55
0
            if (!maybe_color)
56
0
                return ElementType::Garbage;
57
0
            color = maybe_color.release_nonnull();
58
0
            tokens.discard_whitespace();
59
            // Allow up to [<color> <T-percentage> <T-percentage>] (double-position color stops)
60
            // Note: Double-position color stops only appear to be valid in this order.
61
0
            for (auto stop_position : Array { &position, &second_position }) {
62
0
                if (tokens.has_next_token() && !tokens.next_token().is(Token::Type::Comma)) {
63
0
                    auto dimension = parse_dimension(tokens.consume_a_token());
64
0
                    if (!dimension.has_value() || !is_position(*dimension))
65
0
                        return ElementType::Garbage;
66
0
                    *stop_position = get_position(*dimension);
67
0
                    tokens.discard_whitespace();
68
0
                }
69
0
            }
70
0
        }
71
72
0
        element.color_stop = typename TElement::ColorStop { color, position, second_position };
73
0
        return ElementType::ColorStop;
74
0
    };
Unexecuted instantiation: GradientParsing.cpp:Web::CSS::Parser::Parser::parse_color_stop_list<Web::CSS::ColorStopListElement<Web::CSS::LengthPercentage>, Web::CSS::Parser::Parser::parse_linear_color_stop_list(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_0, Web::CSS::Parser::Parser::parse_linear_color_stop_list(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, Web::CSS::Parser::Parser::parse_linear_color_stop_list(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_0, Web::CSS::Parser::Parser::parse_linear_color_stop_list(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1)::{lambda(Web::CSS::ColorStopListElement<Web::CSS::LengthPercentage>&)#1}::operator()(Web::CSS::ColorStopListElement<Web::CSS::LengthPercentage>&) const
Unexecuted instantiation: GradientParsing.cpp:Web::CSS::Parser::Parser::parse_color_stop_list<Web::CSS::ColorStopListElement<Web::CSS::AnglePercentage>, Web::CSS::Parser::Parser::parse_angular_color_stop_list(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_0, Web::CSS::Parser::Parser::parse_angular_color_stop_list(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, Web::CSS::Parser::Parser::parse_angular_color_stop_list(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_0, Web::CSS::Parser::Parser::parse_angular_color_stop_list(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1)::{lambda(Web::CSS::ColorStopListElement<Web::CSS::AnglePercentage>&)#1}::operator()(Web::CSS::ColorStopListElement<Web::CSS::AnglePercentage>&) const
75
76
0
    TElement first_element {};
77
0
    if (parse_color_stop_list_element(first_element) != ElementType::ColorStop)
78
0
        return {};
79
80
0
    if (!tokens.has_next_token())
81
0
        return {};
82
83
0
    Vector<TElement> color_stops { first_element };
84
0
    while (tokens.has_next_token()) {
85
0
        TElement list_element {};
86
0
        tokens.discard_whitespace();
87
0
        if (!tokens.consume_a_token().is(Token::Type::Comma))
88
0
            return {};
89
0
        auto element_type = parse_color_stop_list_element(list_element);
90
0
        if (element_type == ElementType::ColorHint) {
91
            // <color-hint>, <color-stop>
92
0
            tokens.discard_whitespace();
93
0
            if (!tokens.consume_a_token().is(Token::Type::Comma))
94
0
                return {};
95
            // Note: This fills in the color stop on the same list_element as the color hint (it does not overwrite it).
96
0
            if (parse_color_stop_list_element(list_element) != ElementType::ColorStop)
97
0
                return {};
98
0
        } else if (element_type == ElementType::ColorStop) {
99
            // <color-stop>
100
0
        } else {
101
0
            return {};
102
0
        }
103
0
        color_stops.append(list_element);
104
0
    }
105
106
0
    return color_stops;
107
0
}
Unexecuted instantiation: GradientParsing.cpp:AK::Optional<AK::Vector<Web::CSS::ColorStopListElement<Web::CSS::LengthPercentage>, 0ul> > Web::CSS::Parser::Parser::parse_color_stop_list<Web::CSS::ColorStopListElement<Web::CSS::LengthPercentage>, Web::CSS::Parser::Parser::parse_linear_color_stop_list(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_0, Web::CSS::Parser::Parser::parse_linear_color_stop_list(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, Web::CSS::Parser::Parser::parse_linear_color_stop_list(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_0, Web::CSS::Parser::Parser::parse_linear_color_stop_list(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1)
Unexecuted instantiation: GradientParsing.cpp:AK::Optional<AK::Vector<Web::CSS::ColorStopListElement<Web::CSS::AnglePercentage>, 0ul> > Web::CSS::Parser::Parser::parse_color_stop_list<Web::CSS::ColorStopListElement<Web::CSS::AnglePercentage>, Web::CSS::Parser::Parser::parse_angular_color_stop_list(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_0, Web::CSS::Parser::Parser::parse_angular_color_stop_list(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, Web::CSS::Parser::Parser::parse_angular_color_stop_list(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_0, Web::CSS::Parser::Parser::parse_angular_color_stop_list(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1)
108
109
static StringView consume_if_starts_with(StringView str, StringView start, auto found_callback)
110
0
{
111
0
    if (str.starts_with(start, CaseSensitivity::CaseInsensitive)) {
112
0
        found_callback();
113
0
        return str.substring_view(start.length());
114
0
    }
115
0
    return str;
116
0
}
Unexecuted instantiation: GradientParsing.cpp:AK::StringView Web::CSS::Parser::consume_if_starts_with<Web::CSS::Parser::Parser::parse_linear_gradient_function(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1>(AK::StringView, AK::StringView, Web::CSS::Parser::Parser::parse_linear_gradient_function(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1)
Unexecuted instantiation: GradientParsing.cpp:AK::StringView Web::CSS::Parser::consume_if_starts_with<Web::CSS::Parser::Parser::parse_linear_gradient_function(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_2>(AK::StringView, AK::StringView, Web::CSS::Parser::Parser::parse_linear_gradient_function(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_2)
Unexecuted instantiation: GradientParsing.cpp:AK::StringView Web::CSS::Parser::consume_if_starts_with<Web::CSS::Parser::Parser::parse_conic_gradient_function(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1>(AK::StringView, AK::StringView, Web::CSS::Parser::Parser::parse_conic_gradient_function(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1)
Unexecuted instantiation: GradientParsing.cpp:AK::StringView Web::CSS::Parser::consume_if_starts_with<Web::CSS::Parser::Parser::parse_radial_gradient_function(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1>(AK::StringView, AK::StringView, Web::CSS::Parser::Parser::parse_radial_gradient_function(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1)
117
118
Optional<Vector<LinearColorStopListElement>> Parser::parse_linear_color_stop_list(TokenStream<ComponentValue>& tokens)
119
0
{
120
    // <color-stop-list> =
121
    //   <linear-color-stop> , [ <linear-color-hint>? , <linear-color-stop> ]#
122
0
    return parse_color_stop_list<LinearColorStopListElement>(
123
0
        tokens,
124
0
        [](Dimension& dimension) { return dimension.is_length_percentage(); },
125
0
        [](Dimension& dimension) { return dimension.length_percentage(); });
126
0
}
127
128
Optional<Vector<AngularColorStopListElement>> Parser::parse_angular_color_stop_list(TokenStream<ComponentValue>& tokens)
129
0
{
130
    // <angular-color-stop-list> =
131
    //   <angular-color-stop> , [ <angular-color-hint>? , <angular-color-stop> ]#
132
0
    return parse_color_stop_list<AngularColorStopListElement>(
133
0
        tokens,
134
0
        [](Dimension& dimension) { return dimension.is_angle_percentage(); },
135
0
        [](Dimension& dimension) { return dimension.angle_percentage(); });
136
0
}
137
138
RefPtr<CSSStyleValue> Parser::parse_linear_gradient_function(TokenStream<ComponentValue>& outer_tokens)
139
0
{
140
0
    using GradientType = LinearGradientStyleValue::GradientType;
141
142
0
    auto transaction = outer_tokens.begin_transaction();
143
0
    auto& component_value = outer_tokens.consume_a_token();
144
145
0
    if (!component_value.is_function())
146
0
        return nullptr;
147
148
0
    GradientRepeating repeating_gradient = GradientRepeating::No;
149
0
    GradientType gradient_type { GradientType::Standard };
150
151
0
    auto function_name = component_value.function().name.bytes_as_string_view();
152
153
0
    function_name = consume_if_starts_with(function_name, "-webkit-"sv, [&] {
154
0
        gradient_type = GradientType::WebKit;
155
0
    });
156
157
0
    function_name = consume_if_starts_with(function_name, "repeating-"sv, [&] {
158
0
        repeating_gradient = GradientRepeating::Yes;
159
0
    });
160
161
0
    if (!function_name.equals_ignoring_ascii_case("linear-gradient"sv))
162
0
        return nullptr;
163
164
    // linear-gradient() = linear-gradient([ <angle> | to <side-or-corner> ]?, <color-stop-list>)
165
166
0
    TokenStream tokens { component_value.function().value };
167
0
    tokens.discard_whitespace();
168
169
0
    if (!tokens.has_next_token())
170
0
        return nullptr;
171
172
0
    bool has_direction_param = true;
173
0
    LinearGradientStyleValue::GradientDirection gradient_direction = gradient_type == GradientType::Standard
174
0
        ? SideOrCorner::Bottom
175
0
        : SideOrCorner::Top;
176
177
0
    auto to_side = [](StringView value) -> Optional<SideOrCorner> {
178
0
        if (value.equals_ignoring_ascii_case("top"sv))
179
0
            return SideOrCorner::Top;
180
0
        if (value.equals_ignoring_ascii_case("bottom"sv))
181
0
            return SideOrCorner::Bottom;
182
0
        if (value.equals_ignoring_ascii_case("left"sv))
183
0
            return SideOrCorner::Left;
184
0
        if (value.equals_ignoring_ascii_case("right"sv))
185
0
            return SideOrCorner::Right;
186
0
        return {};
187
0
    };
188
189
0
    auto is_to_side_or_corner = [&](auto const& token) {
190
0
        if (!token.is(Token::Type::Ident))
191
0
            return false;
192
0
        if (gradient_type == GradientType::WebKit)
193
0
            return to_side(token.token().ident()).has_value();
194
0
        return token.token().ident().equals_ignoring_ascii_case("to"sv);
195
0
    };
196
197
0
    auto const& first_param = tokens.next_token();
198
0
    if (first_param.is(Token::Type::Dimension)) {
199
        // <angle>
200
0
        tokens.discard_a_token();
201
0
        auto angle_value = first_param.token().dimension_value();
202
0
        auto unit_string = first_param.token().dimension_unit();
203
0
        auto angle_type = Angle::unit_from_name(unit_string);
204
205
0
        if (!angle_type.has_value())
206
0
            return nullptr;
207
208
0
        gradient_direction = Angle { angle_value, angle_type.release_value() };
209
0
    } else if (is_to_side_or_corner(first_param)) {
210
        // <side-or-corner> = [left | right] || [top | bottom]
211
212
        // Note: -webkit-linear-gradient does not include to the "to" prefix on the side or corner
213
0
        if (gradient_type == GradientType::Standard) {
214
0
            tokens.discard_a_token();
215
0
            tokens.discard_whitespace();
216
217
0
            if (!tokens.has_next_token())
218
0
                return nullptr;
219
0
        }
220
221
        // [left | right] || [top | bottom]
222
0
        auto const& first_side = tokens.consume_a_token();
223
0
        if (!first_side.is(Token::Type::Ident))
224
0
            return nullptr;
225
226
0
        auto side_a = to_side(first_side.token().ident());
227
0
        tokens.discard_whitespace();
228
0
        Optional<SideOrCorner> side_b;
229
0
        if (tokens.has_next_token() && tokens.next_token().is(Token::Type::Ident))
230
0
            side_b = to_side(tokens.consume_a_token().token().ident());
231
232
0
        if (side_a.has_value() && !side_b.has_value()) {
233
0
            gradient_direction = *side_a;
234
0
        } else if (side_a.has_value() && side_b.has_value()) {
235
            // Convert two sides to a corner
236
0
            if (to_underlying(*side_b) < to_underlying(*side_a))
237
0
                swap(side_a, side_b);
238
0
            if (side_a == SideOrCorner::Top && side_b == SideOrCorner::Left)
239
0
                gradient_direction = SideOrCorner::TopLeft;
240
0
            else if (side_a == SideOrCorner::Top && side_b == SideOrCorner::Right)
241
0
                gradient_direction = SideOrCorner::TopRight;
242
0
            else if (side_a == SideOrCorner::Bottom && side_b == SideOrCorner::Left)
243
0
                gradient_direction = SideOrCorner::BottomLeft;
244
0
            else if (side_a == SideOrCorner::Bottom && side_b == SideOrCorner::Right)
245
0
                gradient_direction = SideOrCorner::BottomRight;
246
0
            else
247
0
                return nullptr;
248
0
        } else {
249
0
            return nullptr;
250
0
        }
251
0
    } else {
252
0
        has_direction_param = false;
253
0
    }
254
255
0
    tokens.discard_whitespace();
256
0
    if (!tokens.has_next_token())
257
0
        return nullptr;
258
259
0
    if (has_direction_param && !tokens.consume_a_token().is(Token::Type::Comma))
260
0
        return nullptr;
261
262
0
    auto color_stops = parse_linear_color_stop_list(tokens);
263
0
    if (!color_stops.has_value())
264
0
        return nullptr;
265
266
0
    transaction.commit();
267
0
    return LinearGradientStyleValue::create(gradient_direction, move(*color_stops), gradient_type, repeating_gradient);
268
0
}
269
270
RefPtr<CSSStyleValue> Parser::parse_conic_gradient_function(TokenStream<ComponentValue>& outer_tokens)
271
0
{
272
0
    auto transaction = outer_tokens.begin_transaction();
273
0
    auto& component_value = outer_tokens.consume_a_token();
274
275
0
    if (!component_value.is_function())
276
0
        return nullptr;
277
278
0
    GradientRepeating repeating_gradient = GradientRepeating::No;
279
280
0
    auto function_name = component_value.function().name.bytes_as_string_view();
281
282
0
    function_name = consume_if_starts_with(function_name, "repeating-"sv, [&] {
283
0
        repeating_gradient = GradientRepeating::Yes;
284
0
    });
285
286
0
    if (!function_name.equals_ignoring_ascii_case("conic-gradient"sv))
287
0
        return nullptr;
288
289
0
    TokenStream tokens { component_value.function().value };
290
0
    tokens.discard_whitespace();
291
292
0
    if (!tokens.has_next_token())
293
0
        return nullptr;
294
295
0
    Angle from_angle(0, Angle::Type::Deg);
296
0
    RefPtr<PositionStyleValue> at_position;
297
298
    // conic-gradient( [ [ from <angle> ]? [ at <position> ]? ]  ||
299
    // <color-interpolation-method> , <angular-color-stop-list> )
300
0
    auto token = tokens.next_token();
301
0
    bool got_from_angle = false;
302
0
    bool got_color_interpolation_method = false;
303
0
    bool got_at_position = false;
304
0
    while (token.is(Token::Type::Ident)) {
305
0
        auto consume_identifier = [&](auto identifier) {
306
0
            auto token_string = token.token().ident();
307
0
            if (token_string.equals_ignoring_ascii_case(identifier)) {
308
0
                tokens.discard_a_token();
309
0
                tokens.discard_whitespace();
310
0
                return true;
311
0
            }
312
0
            return false;
313
0
        };
314
315
0
        if (consume_identifier("from"sv)) {
316
            // from <angle>
317
0
            if (got_from_angle || got_at_position)
318
0
                return nullptr;
319
0
            if (!tokens.has_next_token())
320
0
                return nullptr;
321
322
0
            auto angle_token = tokens.consume_a_token();
323
0
            if (!angle_token.is(Token::Type::Dimension))
324
0
                return nullptr;
325
0
            auto angle = angle_token.token().dimension_value();
326
0
            auto angle_unit = angle_token.token().dimension_unit();
327
0
            auto angle_type = Angle::unit_from_name(angle_unit);
328
0
            if (!angle_type.has_value())
329
0
                return nullptr;
330
331
0
            from_angle = Angle(angle, *angle_type);
332
0
            got_from_angle = true;
333
0
        } else if (consume_identifier("at"sv)) {
334
            // at <position>
335
0
            if (got_at_position)
336
0
                return nullptr;
337
0
            auto position = parse_position_value(tokens);
338
0
            if (!position)
339
0
                return nullptr;
340
0
            at_position = position;
341
0
            got_at_position = true;
342
0
        } else if (consume_identifier("in"sv)) {
343
            // <color-interpolation-method>
344
0
            if (got_color_interpolation_method)
345
0
                return nullptr;
346
0
            dbgln("FIXME: Parse color interpolation method for conic-gradient()");
347
0
            got_color_interpolation_method = true;
348
0
        } else {
349
0
            break;
350
0
        }
351
0
        tokens.discard_whitespace();
352
0
        if (!tokens.has_next_token())
353
0
            return nullptr;
354
0
        token = tokens.next_token();
355
0
    }
356
357
0
    tokens.discard_whitespace();
358
0
    if (!tokens.has_next_token())
359
0
        return nullptr;
360
0
    if ((got_from_angle || got_at_position || got_color_interpolation_method) && !tokens.consume_a_token().is(Token::Type::Comma))
361
0
        return nullptr;
362
363
0
    auto color_stops = parse_angular_color_stop_list(tokens);
364
0
    if (!color_stops.has_value())
365
0
        return nullptr;
366
367
0
    if (!at_position)
368
0
        at_position = PositionStyleValue::create_center();
369
370
0
    transaction.commit();
371
0
    return ConicGradientStyleValue::create(from_angle, at_position.release_nonnull(), move(*color_stops), repeating_gradient);
372
0
}
373
374
RefPtr<CSSStyleValue> Parser::parse_radial_gradient_function(TokenStream<ComponentValue>& outer_tokens)
375
0
{
376
0
    using EndingShape = RadialGradientStyleValue::EndingShape;
377
0
    using Extent = RadialGradientStyleValue::Extent;
378
0
    using CircleSize = RadialGradientStyleValue::CircleSize;
379
0
    using EllipseSize = RadialGradientStyleValue::EllipseSize;
380
0
    using Size = RadialGradientStyleValue::Size;
381
382
0
    auto transaction = outer_tokens.begin_transaction();
383
0
    auto& component_value = outer_tokens.consume_a_token();
384
385
0
    if (!component_value.is_function())
386
0
        return nullptr;
387
388
0
    auto repeating_gradient = GradientRepeating::No;
389
390
0
    auto function_name = component_value.function().name.bytes_as_string_view();
391
392
0
    function_name = consume_if_starts_with(function_name, "repeating-"sv, [&] {
393
0
        repeating_gradient = GradientRepeating::Yes;
394
0
    });
395
396
0
    if (!function_name.equals_ignoring_ascii_case("radial-gradient"sv))
397
0
        return nullptr;
398
399
0
    TokenStream tokens { component_value.function().value };
400
0
    tokens.discard_whitespace();
401
0
    if (!tokens.has_next_token())
402
0
        return nullptr;
403
404
0
    bool expect_comma = false;
405
406
0
    auto commit_value = [&]<typename... T>(auto value, T&... transactions) {
407
0
        (transactions.commit(), ...);
408
0
        return value;
409
0
    };
Unexecuted instantiation: GradientParsing.cpp:auto Web::CSS::Parser::Parser::parse_radial_gradient_function(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_0::operator()<Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>::StateTransaction, Web::CSS::RadialGradientStyleValue::EndingShape>(Web::CSS::RadialGradientStyleValue::EndingShape, Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>::StateTransaction&) const
Unexecuted instantiation: GradientParsing.cpp:auto Web::CSS::Parser::Parser::parse_radial_gradient_function(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_0::operator()<Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>::StateTransaction, Web::CSS::RadialGradientStyleValue::Extent>(Web::CSS::RadialGradientStyleValue::Extent, Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>::StateTransaction&) const
Unexecuted instantiation: GradientParsing.cpp:auto Web::CSS::Parser::Parser::parse_radial_gradient_function(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_0::operator()<Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>::StateTransaction, Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>::StateTransaction, Web::CSS::RadialGradientStyleValue::EllipseSize>(Web::CSS::RadialGradientStyleValue::EllipseSize, Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>::StateTransaction&, Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>::StateTransaction&) const
Unexecuted instantiation: GradientParsing.cpp:auto Web::CSS::Parser::Parser::parse_radial_gradient_function(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_0::operator()<Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>::StateTransaction, Web::CSS::RadialGradientStyleValue::CircleSize>(Web::CSS::RadialGradientStyleValue::CircleSize, Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>::StateTransaction&) const
410
411
    // radial-gradient( [ <ending-shape> || <size> ]? [ at <position> ]? , <color-stop-list> )
412
413
0
    Size size = Extent::FarthestCorner;
414
0
    EndingShape ending_shape = EndingShape::Circle;
415
0
    RefPtr<PositionStyleValue> at_position;
416
417
0
    auto parse_ending_shape = [&]() -> Optional<EndingShape> {
418
0
        auto transaction = tokens.begin_transaction();
419
0
        tokens.discard_whitespace();
420
0
        auto& token = tokens.consume_a_token();
421
0
        if (!token.is(Token::Type::Ident))
422
0
            return {};
423
0
        auto ident = token.token().ident();
424
0
        if (ident.equals_ignoring_ascii_case("circle"sv))
425
0
            return commit_value(EndingShape::Circle, transaction);
426
0
        if (ident.equals_ignoring_ascii_case("ellipse"sv))
427
0
            return commit_value(EndingShape::Ellipse, transaction);
428
0
        return {};
429
0
    };
430
431
0
    auto parse_extent_keyword = [](StringView keyword) -> Optional<Extent> {
432
0
        if (keyword.equals_ignoring_ascii_case("closest-corner"sv))
433
0
            return Extent::ClosestCorner;
434
0
        if (keyword.equals_ignoring_ascii_case("closest-side"sv))
435
0
            return Extent::ClosestSide;
436
0
        if (keyword.equals_ignoring_ascii_case("farthest-corner"sv))
437
0
            return Extent::FarthestCorner;
438
0
        if (keyword.equals_ignoring_ascii_case("farthest-side"sv))
439
0
            return Extent::FarthestSide;
440
0
        return {};
441
0
    };
442
443
0
    auto parse_size = [&]() -> Optional<Size> {
444
        // <size> =
445
        //      <extent-keyword>              |
446
        //      <length [0,∞]>                |
447
        //      <length-percentage [0,∞]>{2}
448
0
        auto transaction_size = tokens.begin_transaction();
449
0
        tokens.discard_whitespace();
450
0
        if (!tokens.has_next_token())
451
0
            return {};
452
0
        if (tokens.next_token().is(Token::Type::Ident)) {
453
0
            auto extent = parse_extent_keyword(tokens.consume_a_token().token().ident());
454
0
            if (!extent.has_value())
455
0
                return {};
456
0
            return commit_value(*extent, transaction_size);
457
0
        }
458
0
        auto first_radius = parse_length_percentage(tokens);
459
0
        if (!first_radius.has_value())
460
0
            return {};
461
0
        auto transaction_second_dimension = tokens.begin_transaction();
462
0
        tokens.discard_whitespace();
463
0
        if (tokens.has_next_token()) {
464
0
            auto second_radius = parse_length_percentage(tokens);
465
0
            if (second_radius.has_value())
466
0
                return commit_value(EllipseSize { first_radius.release_value(), second_radius.release_value() },
467
0
                    transaction_size, transaction_second_dimension);
468
0
        }
469
        // FIXME: Support calculated lengths
470
0
        if (first_radius->is_length())
471
0
            return commit_value(CircleSize { first_radius->length() }, transaction_size);
472
0
        return {};
473
0
    };
474
475
0
    {
476
        // [ <ending-shape> || <size> ]?
477
0
        auto maybe_ending_shape = parse_ending_shape();
478
0
        auto maybe_size = parse_size();
479
0
        if (!maybe_ending_shape.has_value() && maybe_size.has_value())
480
0
            maybe_ending_shape = parse_ending_shape();
481
0
        if (maybe_size.has_value()) {
482
0
            size = *maybe_size;
483
0
            expect_comma = true;
484
0
        }
485
0
        if (maybe_ending_shape.has_value()) {
486
0
            expect_comma = true;
487
0
            ending_shape = *maybe_ending_shape;
488
0
            if (ending_shape == EndingShape::Circle && size.has<EllipseSize>())
489
0
                return nullptr;
490
0
            if (ending_shape == EndingShape::Ellipse && size.has<CircleSize>())
491
0
                return nullptr;
492
0
        } else {
493
0
            ending_shape = size.has<CircleSize>() ? EndingShape::Circle : EndingShape::Ellipse;
494
0
        }
495
0
    }
496
497
0
    tokens.discard_whitespace();
498
0
    if (!tokens.has_next_token())
499
0
        return nullptr;
500
501
0
    auto& token = tokens.next_token();
502
0
    if (token.is_ident("at"sv)) {
503
0
        tokens.discard_a_token();
504
0
        auto position = parse_position_value(tokens);
505
0
        if (!position)
506
0
            return nullptr;
507
0
        at_position = position;
508
0
        expect_comma = true;
509
0
    }
510
511
0
    tokens.discard_whitespace();
512
0
    if (!tokens.has_next_token())
513
0
        return nullptr;
514
0
    if (expect_comma && !tokens.consume_a_token().is(Token::Type::Comma))
515
0
        return nullptr;
516
517
    // <color-stop-list>
518
0
    auto color_stops = parse_linear_color_stop_list(tokens);
519
0
    if (!color_stops.has_value())
520
0
        return nullptr;
521
522
0
    if (!at_position)
523
0
        at_position = PositionStyleValue::create_center();
524
525
0
    transaction.commit();
526
0
    return RadialGradientStyleValue::create(ending_shape, size, at_position.release_nonnull(), move(*color_stops), repeating_gradient);
527
0
}
528
529
}