Coverage Report

Created: 2026-10-28 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2020-2021, the SerenityOS developers.
4
 * Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
5
 * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
6
 * Copyright (c) 2022, MacDue <macdue@dueutil.tech>
7
 * Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
8
 * Copyright (c) 2024, Tommy van der Vorst <tommy@pixelspark.nl>
9
 * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
10
 * Copyright (c) 2024, Glenn Skrzypczak <glenn.skrzypczak@gmail.com>
11
 *
12
 * SPDX-License-Identifier: BSD-2-Clause
13
 */
14
15
#include <AK/CharacterTypes.h>
16
#include <AK/Debug.h>
17
#include <AK/GenericLexer.h>
18
#include <AK/QuickSort.h>
19
#include <AK/SourceLocation.h>
20
#include <AK/TemporaryChange.h>
21
#include <LibWeb/CSS/CSSFontFaceRule.h>
22
#include <LibWeb/CSS/CSSImportRule.h>
23
#include <LibWeb/CSS/CSSKeyframeRule.h>
24
#include <LibWeb/CSS/CSSKeyframesRule.h>
25
#include <LibWeb/CSS/CSSLayerBlockRule.h>
26
#include <LibWeb/CSS/CSSLayerStatementRule.h>
27
#include <LibWeb/CSS/CSSMediaRule.h>
28
#include <LibWeb/CSS/CSSNamespaceRule.h>
29
#include <LibWeb/CSS/CSSNestedDeclarations.h>
30
#include <LibWeb/CSS/CSSPropertyRule.h>
31
#include <LibWeb/CSS/CSSStyleDeclaration.h>
32
#include <LibWeb/CSS/CSSStyleRule.h>
33
#include <LibWeb/CSS/CSSStyleSheet.h>
34
#include <LibWeb/CSS/CSSStyleValue.h>
35
#include <LibWeb/CSS/CSSSupportsRule.h>
36
#include <LibWeb/CSS/CalculatedOr.h>
37
#include <LibWeb/CSS/EdgeRect.h>
38
#include <LibWeb/CSS/MediaList.h>
39
#include <LibWeb/CSS/Parser/Parser.h>
40
#include <LibWeb/CSS/PropertyName.h>
41
#include <LibWeb/CSS/Selector.h>
42
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
43
#include <LibWeb/CSS/StyleValues/BackgroundRepeatStyleValue.h>
44
#include <LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.h>
45
#include <LibWeb/CSS/StyleValues/BasicShapeStyleValue.h>
46
#include <LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h>
47
#include <LibWeb/CSS/StyleValues/CSSColor.h>
48
#include <LibWeb/CSS/StyleValues/CSSColorValue.h>
49
#include <LibWeb/CSS/StyleValues/CSSHSL.h>
50
#include <LibWeb/CSS/StyleValues/CSSHWB.h>
51
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
52
#include <LibWeb/CSS/StyleValues/CSSLCHLike.h>
53
#include <LibWeb/CSS/StyleValues/CSSLabLike.h>
54
#include <LibWeb/CSS/StyleValues/CSSRGB.h>
55
#include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
56
#include <LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h>
57
#include <LibWeb/CSS/StyleValues/CounterStyleValue.h>
58
#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
59
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
60
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
61
#include <LibWeb/CSS/StyleValues/EdgeStyleValue.h>
62
#include <LibWeb/CSS/StyleValues/FilterValueListStyleValue.h>
63
#include <LibWeb/CSS/StyleValues/FlexStyleValue.h>
64
#include <LibWeb/CSS/StyleValues/FrequencyStyleValue.h>
65
#include <LibWeb/CSS/StyleValues/GridAutoFlowStyleValue.h>
66
#include <LibWeb/CSS/StyleValues/GridTemplateAreaStyleValue.h>
67
#include <LibWeb/CSS/StyleValues/GridTrackPlacementStyleValue.h>
68
#include <LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.h>
69
#include <LibWeb/CSS/StyleValues/ImageStyleValue.h>
70
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
71
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
72
#include <LibWeb/CSS/StyleValues/MathDepthStyleValue.h>
73
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
74
#include <LibWeb/CSS/StyleValues/OpenTypeTaggedStyleValue.h>
75
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
76
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
77
#include <LibWeb/CSS/StyleValues/RatioStyleValue.h>
78
#include <LibWeb/CSS/StyleValues/RectStyleValue.h>
79
#include <LibWeb/CSS/StyleValues/ResolutionStyleValue.h>
80
#include <LibWeb/CSS/StyleValues/RotationStyleValue.h>
81
#include <LibWeb/CSS/StyleValues/ScrollbarGutterStyleValue.h>
82
#include <LibWeb/CSS/StyleValues/ShadowStyleValue.h>
83
#include <LibWeb/CSS/StyleValues/ShorthandStyleValue.h>
84
#include <LibWeb/CSS/StyleValues/StringStyleValue.h>
85
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
86
#include <LibWeb/CSS/StyleValues/TimeStyleValue.h>
87
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
88
#include <LibWeb/CSS/StyleValues/TransitionStyleValue.h>
89
#include <LibWeb/CSS/StyleValues/URLStyleValue.h>
90
#include <LibWeb/CSS/StyleValues/UnresolvedStyleValue.h>
91
#include <LibWeb/Dump.h>
92
#include <LibWeb/Infra/CharacterTypes.h>
93
#include <LibWeb/Infra/Strings.h>
94
95
static void log_parse_error(SourceLocation const& location = SourceLocation::current())
96
0
{
97
0
    dbgln_if(CSS_PARSER_DEBUG, "Parse error (CSS) {}", location);
98
0
}
99
100
namespace Web::CSS::Parser {
101
102
Parser Parser::create(ParsingContext const& context, StringView input, StringView encoding)
103
0
{
104
0
    auto tokens = Tokenizer::tokenize(input, encoding);
105
0
    return Parser { context, move(tokens) };
106
0
}
107
108
Parser::Parser(ParsingContext const& context, Vector<Token> tokens)
109
0
    : m_context(context)
110
0
    , m_tokens(move(tokens))
111
0
    , m_token_stream(m_tokens)
112
0
{
113
0
}
114
115
Parser::Parser(Parser&& other)
116
0
    : m_context(other.m_context)
117
0
    , m_tokens(move(other.m_tokens))
118
0
    , m_token_stream(m_tokens)
119
0
{
120
    // Moving the TokenStream directly from `other` would break it, because TokenStream holds
121
    // a reference to the Vector<Token>, so it would be pointing at the old Parser's tokens.
122
    // So instead, we create a new TokenStream from this Parser's tokens, and then tell it to
123
    // copy the other TokenStream's state. This is quite hacky.
124
0
    m_token_stream.copy_state({}, other.m_token_stream);
125
0
}
126
127
// https://drafts.csswg.org/css-syntax/#parse-stylesheet
128
template<typename T>
129
Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream<T>& input, Optional<URL::URL> location)
130
0
{
131
    // To parse a stylesheet from an input given an optional url location:
132
133
    // 1. If input is a byte stream for a stylesheet, decode bytes from input, and set input to the result.
134
    // 2. Normalize input, and set input to the result.
135
    // NOTE: These are done automatically when creating the Parser.
136
137
    // 3. Create a new stylesheet, with its location set to location (or null, if location was not passed).
138
0
    ParsedStyleSheet style_sheet;
139
0
    style_sheet.location = move(location);
140
141
    // 4. Consume a stylesheet’s contents from input, and set the stylesheet’s rules to the result.
142
0
    style_sheet.rules = consume_a_stylesheets_contents(input);
143
144
    // 5. Return the stylesheet.
145
0
    return style_sheet;
146
0
}
147
148
// https://drafts.csswg.org/css-syntax/#parse-a-stylesheets-contents
149
template<typename T>
150
Vector<Rule> Parser::parse_a_stylesheets_contents(TokenStream<T>& input)
151
{
152
    // To parse a stylesheet’s contents from input:
153
154
    // 1. Normalize input, and set input to the result.
155
    // NOTE: This is done automatically when creating the Parser.
156
157
    // 2. Consume a stylesheet’s contents from input, and return the result.
158
    return consume_a_stylesheets_contents(input);
159
}
160
161
// https://drafts.csswg.org/css-syntax/#parse-a-css-stylesheet
162
CSSStyleSheet* Parser::parse_as_css_stylesheet(Optional<URL::URL> location)
163
0
{
164
    // To parse a CSS stylesheet, first parse a stylesheet.
165
0
    auto style_sheet = parse_a_stylesheet(m_token_stream, {});
166
167
    // Interpret all of the resulting top-level qualified rules as style rules, defined below.
168
0
    JS::MarkedVector<CSSRule*> rules(m_context.realm().heap());
169
0
    for (auto const& raw_rule : style_sheet.rules) {
170
0
        auto rule = convert_to_rule(raw_rule, Nested::No);
171
        // If any style rule is invalid, or any at-rule is not recognized or is invalid according to its grammar or context, it’s a parse error.
172
        // Discard that rule.
173
0
        if (!rule) {
174
0
            log_parse_error();
175
0
            continue;
176
0
        }
177
0
        rules.append(rule);
178
0
    }
179
180
0
    auto rule_list = CSSRuleList::create(m_context.realm(), rules);
181
0
    auto media_list = MediaList::create(m_context.realm(), {});
182
0
    return CSSStyleSheet::create(m_context.realm(), rule_list, media_list, move(location));
183
0
}
184
185
RefPtr<Supports> Parser::parse_as_supports()
186
0
{
187
0
    return parse_a_supports(m_token_stream);
188
0
}
189
190
template<typename T>
191
RefPtr<Supports> Parser::parse_a_supports(TokenStream<T>& tokens)
192
0
{
193
0
    auto component_values = parse_a_list_of_component_values(tokens);
194
0
    TokenStream<ComponentValue> token_stream { component_values };
195
0
    auto maybe_condition = parse_supports_condition(token_stream);
196
0
    token_stream.discard_whitespace();
197
0
    if (maybe_condition && !token_stream.has_next_token())
198
0
        return Supports::create(m_context.realm(), maybe_condition.release_nonnull());
199
200
0
    return {};
201
0
}
Unexecuted instantiation: AK::RefPtr<Web::CSS::Supports> Web::CSS::Parser::Parser::parse_a_supports<Web::CSS::Parser::Token>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::Token>&)
Unexecuted instantiation: AK::RefPtr<Web::CSS::Supports> Web::CSS::Parser::Parser::parse_a_supports<Web::CSS::Parser::ComponentValue>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)
202
203
OwnPtr<Supports::Condition> Parser::parse_supports_condition(TokenStream<ComponentValue>& tokens)
204
0
{
205
0
    auto transaction = tokens.begin_transaction();
206
0
    tokens.discard_whitespace();
207
208
0
    auto const& peeked_token = tokens.next_token();
209
    // `not <supports-in-parens>`
210
0
    if (peeked_token.is_ident("not"sv)) {
211
0
        tokens.discard_a_token();
212
0
        tokens.discard_whitespace();
213
0
        auto child = parse_supports_in_parens(tokens);
214
0
        if (!child.has_value())
215
0
            return {};
216
217
0
        transaction.commit();
218
0
        auto condition = make<Supports::Condition>();
219
0
        condition->type = Supports::Condition::Type::Not;
220
0
        condition->children.append(child.release_value());
221
0
        return condition;
222
0
    }
223
224
    // `  <supports-in-parens> [ and <supports-in-parens> ]*
225
    //  | <supports-in-parens> [ or <supports-in-parens> ]*`
226
0
    Vector<Supports::InParens> children;
227
0
    Optional<Supports::Condition::Type> condition_type {};
228
0
    auto as_condition_type = [](auto& token) -> Optional<Supports::Condition::Type> {
229
0
        if (!token.is(Token::Type::Ident))
230
0
            return {};
231
0
        auto ident = token.token().ident();
232
0
        if (ident.equals_ignoring_ascii_case("and"sv))
233
0
            return Supports::Condition::Type::And;
234
0
        if (ident.equals_ignoring_ascii_case("or"sv))
235
0
            return Supports::Condition::Type::Or;
236
0
        return {};
237
0
    };
238
239
0
    while (tokens.has_next_token()) {
240
0
        if (!children.is_empty()) {
241
            // Expect `and` or `or` here
242
0
            auto maybe_combination = as_condition_type(tokens.consume_a_token());
243
0
            if (!maybe_combination.has_value())
244
0
                return {};
245
0
            if (!condition_type.has_value()) {
246
0
                condition_type = maybe_combination.value();
247
0
            } else if (maybe_combination != condition_type) {
248
0
                return {};
249
0
            }
250
0
        }
251
252
0
        tokens.discard_whitespace();
253
254
0
        if (auto in_parens = parse_supports_in_parens(tokens); in_parens.has_value()) {
255
0
            children.append(in_parens.release_value());
256
0
        } else {
257
0
            return {};
258
0
        }
259
260
0
        tokens.discard_whitespace();
261
0
    }
262
263
0
    if (children.is_empty())
264
0
        return {};
265
266
0
    transaction.commit();
267
0
    auto condition = make<Supports::Condition>();
268
0
    condition->type = condition_type.value_or(Supports::Condition::Type::Or);
269
0
    condition->children = move(children);
270
0
    return condition;
271
0
}
272
273
Optional<Supports::InParens> Parser::parse_supports_in_parens(TokenStream<ComponentValue>& tokens)
274
0
{
275
    // `( <supports-condition> )`
276
0
    auto const& first_token = tokens.next_token();
277
0
    if (first_token.is_block() && first_token.block().is_paren()) {
278
0
        auto transaction = tokens.begin_transaction();
279
0
        tokens.discard_a_token();
280
0
        tokens.discard_whitespace();
281
282
0
        TokenStream child_tokens { first_token.block().value };
283
0
        if (auto condition = parse_supports_condition(child_tokens)) {
284
0
            if (child_tokens.has_next_token())
285
0
                return {};
286
0
            transaction.commit();
287
0
            return Supports::InParens {
288
0
                .value = { condition.release_nonnull() }
289
0
            };
290
0
        }
291
0
    }
292
293
    // `<supports-feature>`
294
0
    if (auto feature = parse_supports_feature(tokens); feature.has_value()) {
295
0
        return Supports::InParens {
296
0
            .value = { feature.release_value() }
297
0
        };
298
0
    }
299
300
    // `<general-enclosed>`
301
0
    if (auto general_enclosed = parse_general_enclosed(tokens); general_enclosed.has_value()) {
302
0
        return Supports::InParens {
303
0
            .value = general_enclosed.release_value()
304
0
        };
305
0
    }
306
307
0
    return {};
308
0
}
309
310
Optional<Supports::Feature> Parser::parse_supports_feature(TokenStream<ComponentValue>& tokens)
311
0
{
312
0
    auto transaction = tokens.begin_transaction();
313
0
    tokens.discard_whitespace();
314
0
    auto const& first_token = tokens.consume_a_token();
315
316
    // `<supports-decl>`
317
0
    if (first_token.is_block() && first_token.block().is_paren()) {
318
0
        TokenStream block_tokens { first_token.block().value };
319
        // FIXME: Parsing and then converting back to a string is weird.
320
0
        if (auto declaration = consume_a_declaration(block_tokens); declaration.has_value()) {
321
0
            transaction.commit();
322
0
            return Supports::Feature {
323
0
                Supports::Declaration { declaration->to_string() }
324
0
            };
325
0
        }
326
0
    }
327
328
    // `<supports-selector-fn>`
329
0
    if (first_token.is_function("selector"sv)) {
330
        // FIXME: Parsing and then converting back to a string is weird.
331
0
        StringBuilder builder;
332
0
        for (auto const& item : first_token.function().value)
333
0
            builder.append(item.to_string());
334
0
        transaction.commit();
335
0
        return Supports::Feature {
336
0
            Supports::Selector { builder.to_string().release_value_but_fixme_should_propagate_errors() }
337
0
        };
338
0
    }
339
340
0
    return {};
341
0
}
342
343
// https://www.w3.org/TR/mediaqueries-4/#typedef-general-enclosed
344
Optional<GeneralEnclosed> Parser::parse_general_enclosed(TokenStream<ComponentValue>& tokens)
345
0
{
346
0
    auto transaction = tokens.begin_transaction();
347
0
    tokens.discard_whitespace();
348
0
    auto const& first_token = tokens.consume_a_token();
349
350
    // `[ <function-token> <any-value>? ) ]`
351
0
    if (first_token.is_function()) {
352
0
        transaction.commit();
353
0
        return GeneralEnclosed { first_token.to_string() };
354
0
    }
355
356
    // `( <any-value>? )`
357
0
    if (first_token.is_block() && first_token.block().is_paren()) {
358
0
        transaction.commit();
359
0
        return GeneralEnclosed { first_token.to_string() };
360
0
    }
361
362
0
    return {};
363
0
}
364
365
// https://drafts.csswg.org/css-syntax/#consume-stylesheet-contents
366
template<typename T>
367
Vector<Rule> Parser::consume_a_stylesheets_contents(TokenStream<T>& input)
368
0
{
369
    // To consume a stylesheet’s contents from a token stream input:
370
371
    // Let rules be an initially empty list of rules.
372
0
    Vector<Rule> rules;
373
374
    // Process input:
375
0
    for (;;) {
376
0
        auto& token = input.next_token();
377
378
        // <whitespace-token>
379
0
        if (token.is(Token::Type::Whitespace)) {
380
            // Discard a token from input.
381
0
            input.discard_a_token();
382
0
            continue;
383
0
        }
384
385
        // <EOF-token>
386
0
        if (token.is(Token::Type::EndOfFile)) {
387
            // Return rules.
388
0
            return rules;
389
0
        }
390
391
        // <CDO-token>
392
        // <CDC-token>
393
0
        if (token.is(Token::Type::CDO) || token.is(Token::Type::CDC)) {
394
            // Discard a token from input.
395
0
            input.discard_a_token();
396
0
            continue;
397
0
        }
398
399
        // <at-keyword-token>
400
0
        if (token.is(Token::Type::AtKeyword)) {
401
            // Consume an at-rule from input. If anything is returned, append it to rules.
402
0
            if (auto maybe_at_rule = consume_an_at_rule(input); maybe_at_rule.has_value())
403
0
                rules.append(*maybe_at_rule);
404
0
            continue;
405
0
        }
406
407
        // anything else
408
0
        {
409
            // Consume a qualified rule from input. If a rule is returned, append it to rules.
410
0
            consume_a_qualified_rule(input).visit(
411
0
                [&](QualifiedRule qualified_rule) { rules.append(move(qualified_rule)); },
412
0
                [](auto&) {});
Unexecuted instantiation: auto Web::CSS::Parser::Parser::consume_a_stylesheets_contents<Web::CSS::Parser::Token>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::Token>&)::{lambda(auto:1&)#1}::operator()<AK::Empty>(AK::Empty&) const
Unexecuted instantiation: auto Web::CSS::Parser::Parser::consume_a_stylesheets_contents<Web::CSS::Parser::Token>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::Token>&)::{lambda(auto:1&)#1}::operator()<Web::CSS::Parser::Parser::InvalidRuleError>(Web::CSS::Parser::Parser::InvalidRuleError&) const
413
0
        }
414
0
    }
415
0
}
416
417
// https://drafts.csswg.org/css-syntax/#consume-at-rule
418
template<typename T>
419
Optional<AtRule> Parser::consume_an_at_rule(TokenStream<T>& input, Nested nested)
420
0
{
421
    // To consume an at-rule from a token stream input, given an optional bool nested (default false):
422
423
    // Assert: The next token is an <at-keyword-token>.
424
0
    VERIFY(input.next_token().is(Token::Type::AtKeyword));
425
426
    // Consume a token from input, and let rule be a new at-rule with its name set to the returned token’s value,
427
    // its prelude initially set to an empty list, and no declarations or child rules.
428
0
    AtRule rule {
429
0
        .name = ((Token)input.consume_a_token()).at_keyword(),
430
0
        .prelude = {},
431
0
        .child_rules_and_lists_of_declarations = {},
432
0
    };
433
434
    // Process input:
435
0
    for (;;) {
436
0
        auto& token = input.next_token();
437
438
        // <semicolon-token>
439
        // <EOF-token>
440
0
        if (token.is(Token::Type::Semicolon) || token.is(Token::Type::EndOfFile)) {
441
            // Discard a token from input. If rule is valid in the current context, return it; otherwise return nothing.
442
0
            input.discard_a_token();
443
0
            if (is_valid_in_the_current_context(rule))
444
0
                return rule;
445
0
            return {};
446
0
        }
447
448
        // <}-token>
449
0
        if (token.is(Token::Type::CloseCurly)) {
450
            // If nested is true:
451
0
            if (nested == Nested::Yes) {
452
                // If rule is valid in the current context, return it.
453
0
                if (is_valid_in_the_current_context(rule))
454
0
                    return rule;
455
                // Otherwise, return nothing.
456
0
                return {};
457
0
            }
458
            // Otherwise, consume a token and append the result to rule’s prelude.
459
0
            else {
460
0
                rule.prelude.append(input.consume_a_token());
461
0
            }
462
0
            continue;
463
0
        }
464
465
        // <{-token>
466
0
        if (token.is(Token::Type::OpenCurly)) {
467
            // Consume a block from input, and assign the result to rule’s child rules.
468
0
            rule.child_rules_and_lists_of_declarations = consume_a_block(input);
469
470
            // If rule is valid in the current context, return it. Otherwise, return nothing.
471
0
            if (is_valid_in_the_current_context(rule))
472
0
                return rule;
473
0
            return {};
474
0
        }
475
476
        // anything else
477
0
        {
478
            // Consume a component value from input and append the returned value to rule’s prelude.
479
0
            rule.prelude.append(consume_a_component_value(input));
480
0
        }
481
0
    }
482
0
}
483
484
// https://drafts.csswg.org/css-syntax/#consume-qualified-rule
485
template<typename T>
486
Variant<Empty, QualifiedRule, Parser::InvalidRuleError> Parser::consume_a_qualified_rule(TokenStream<T>& input, Optional<Token::Type> stop_token, Nested nested)
487
0
{
488
    // To consume a qualified rule, from a token stream input, given an optional token stop token and an optional bool nested (default false):
489
490
    // Let rule be a new qualified rule with its prelude, declarations, and child rules all initially set to empty lists.
491
0
    QualifiedRule rule {
492
0
        .prelude = {},
493
0
        .declarations = {},
494
0
        .child_rules = {},
495
0
    };
496
497
    // Process input:
498
0
    for (;;) {
499
0
        auto& token = input.next_token();
500
501
        // <EOF-token>
502
        // stop token (if passed)
503
0
        if (token.is(Token::Type::EndOfFile) || (stop_token.has_value() && token.is(*stop_token))) {
504
            // This is a parse error. Return nothing.
505
0
            log_parse_error();
506
0
            return {};
507
0
        }
508
509
        // <}-token>
510
0
        if (token.is(Token::Type::CloseCurly)) {
511
            // This is a parse error. If nested is true, return nothing. Otherwise, consume a token and append the result to rule’s prelude.
512
0
            log_parse_error();
513
0
            if (nested == Nested::Yes)
514
0
                return {};
515
0
            rule.prelude.append(input.consume_a_token());
516
0
            continue;
517
0
        }
518
519
        // <{-token>
520
0
        if (token.is(Token::Type::OpenCurly)) {
521
            // If the first two non-<whitespace-token> values of rule’s prelude are an <ident-token> whose value starts with "--"
522
            // followed by a <colon-token>, then:
523
0
            TokenStream prelude_tokens { rule.prelude };
524
0
            prelude_tokens.discard_whitespace();
525
0
            auto& first_non_whitespace = prelude_tokens.consume_a_token();
526
0
            prelude_tokens.discard_whitespace();
527
0
            auto& second_non_whitespace = prelude_tokens.consume_a_token();
528
0
            if (first_non_whitespace.is(Token::Type::Ident) && first_non_whitespace.token().ident().starts_with_bytes("--"sv)
529
0
                && second_non_whitespace.is(Token::Type::Colon)) {
530
                // If nested is true, consume the remnants of a bad declaration from input, with nested set to true, and return nothing.
531
0
                if (nested == Nested::Yes) {
532
0
                    consume_the_remnants_of_a_bad_declaration(input, Nested::Yes);
533
0
                    return {};
534
0
                }
535
536
                // If nested is false, consume a block from input, and return nothing.
537
0
                (void)consume_a_block(input);
538
0
                return {};
539
0
            }
540
541
            // Otherwise, consume a block from input, and let child rules be the result.
542
0
            rule.child_rules = consume_a_block(input);
543
544
            // If the first item of child rules is a list of declarations, remove it from child rules and assign it to rule’s declarations.
545
0
            if (!rule.child_rules.is_empty() && rule.child_rules.first().has<Vector<Declaration>>()) {
546
0
                auto first = rule.child_rules.take_first();
547
0
                rule.declarations = move(first.get<Vector<Declaration>>());
548
0
            }
549
550
            // FIXME: If any remaining items of child rules are lists of declarations, replace them with nested declarations rules
551
            //        containing the list as its sole child. Assign child rules to rule’s child rules.
552
553
            // If rule is valid in the current context, return it; otherwise return an invalid rule error.
554
0
            if (is_valid_in_the_current_context(rule))
555
0
                return rule;
556
0
            return InvalidRuleError {};
557
0
        }
558
559
        // anything else
560
0
        {
561
            // Consume a component value from input and append the result to rule’s prelude.
562
0
            rule.prelude.append(consume_a_component_value(input));
563
0
        }
564
0
    }
565
0
}
566
567
// https://drafts.csswg.org/css-syntax/#consume-block
568
template<typename T>
569
Vector<RuleOrListOfDeclarations> Parser::consume_a_block(TokenStream<T>& input)
570
0
{
571
    // To consume a block, from a token stream input:
572
573
    // Assert: The next token is a <{-token>.
574
0
    VERIFY(input.next_token().is(Token::Type::OpenCurly));
575
576
    // Discard a token from input.
577
0
    input.discard_a_token();
578
    // Consume a block’s contents from input and let rules be the result.
579
0
    auto rules = consume_a_blocks_contents(input);
580
    // Discard a token from input.
581
0
    input.discard_a_token();
582
583
    // Return rules.
584
0
    return rules;
585
0
}
586
587
// https://drafts.csswg.org/css-syntax/#consume-block-contents
588
template<typename T>
589
Vector<RuleOrListOfDeclarations> Parser::consume_a_blocks_contents(TokenStream<T>& input)
590
0
{
591
    // To consume a block’s contents from a token stream input:
592
593
    // Let rules be an empty list, containing either rules or lists of declarations.
594
0
    Vector<RuleOrListOfDeclarations> rules;
595
596
    // Let decls be an empty list of declarations.
597
0
    Vector<Declaration> declarations;
598
599
    // Process input:
600
0
    for (;;) {
601
0
        auto& token = input.next_token();
602
603
        // <whitespace-token>
604
        // <semicolon-token>
605
0
        if (token.is(Token::Type::Whitespace) || token.is(Token::Type::Semicolon)) {
606
            // Discard a token from input.
607
0
            input.discard_a_token();
608
0
            continue;
609
0
        }
610
611
        // <EOF-token>
612
        // <}-token>
613
0
        if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::CloseCurly)) {
614
            // AD-HOC: If decls is not empty, append it to rules.
615
            // Spec issue: https://github.com/w3c/csswg-drafts/issues/11017
616
0
            if (!declarations.is_empty())
617
0
                rules.append(move(declarations));
618
            // Return rules.
619
0
            return rules;
620
0
        }
621
622
        // <at-keyword-token>
623
0
        if (token.is(Token::Type::AtKeyword)) {
624
            // If decls is not empty, append it to rules, and set decls to a fresh empty list of declarations.
625
0
            if (!declarations.is_empty()) {
626
0
                rules.append(move(declarations));
627
0
                declarations = {};
628
0
            }
629
630
            // Consume an at-rule from input, with nested set to true.
631
            // If a rule was returned, append it to rules.
632
0
            if (auto at_rule = consume_an_at_rule(input, Nested::Yes); at_rule.has_value())
633
0
                rules.append({ at_rule.release_value() });
634
635
0
            continue;
636
0
        }
637
638
        // anything else
639
0
        {
640
            // Mark input.
641
0
            input.mark();
642
643
            // Consume a declaration from input, with nested set to true.
644
            // If a declaration was returned, append it to decls, and discard a mark from input.
645
0
            if (auto declaration = consume_a_declaration(input, Nested::Yes); declaration.has_value()) {
646
0
                declarations.append(declaration.release_value());
647
0
                input.discard_a_mark();
648
0
            }
649
650
            // Otherwise, restore a mark from input, then consume a qualified rule from input,
651
            // with nested set to true, and <semicolon-token> as the stop token.
652
0
            else {
653
0
                input.restore_a_mark();
654
0
                consume_a_qualified_rule(input, Token::Type::Semicolon, Nested::Yes).visit(
655
                    // -> If nothing was returned
656
0
                    [](Empty&) {
657
                        // Do nothing
658
0
                    },
659
                    // -> If an invalid rule error was returned
660
0
                    [&](InvalidRuleError&) {
661
                        // If decls is not empty, append decls to rules, and set decls to a fresh empty list of declarations. (Otherwise, do nothing.)
662
0
                        if (!declarations.is_empty()) {
663
0
                            rules.append(move(declarations));
664
0
                            declarations = {};
665
0
                        }
666
0
                    },
667
                    // -> If a rule was returned
668
0
                    [&](QualifiedRule rule) {
669
                        // If decls is not empty, append decls to rules, and set decls to a fresh empty list of declarations.
670
0
                        if (!declarations.is_empty()) {
671
0
                            rules.append(move(declarations));
672
0
                            declarations = {};
673
0
                        }
674
                        // Append the rule to rules.
675
0
                        rules.append({ move(rule) });
676
0
                    });
677
0
            }
678
0
        }
679
0
    }
680
0
}
681
682
template<>
683
ComponentValue Parser::consume_a_component_value(TokenStream<ComponentValue>& tokens)
684
0
{
685
    // Note: This overload is called once tokens have already been converted into component values,
686
    //       so we do not need to do the work in the more general overload.
687
0
    return tokens.consume_a_token();
688
0
}
689
690
// 5.4.7. Consume a component value
691
// https://drafts.csswg.org/css-syntax/#consume-component-value
692
template<typename T>
693
ComponentValue Parser::consume_a_component_value(TokenStream<T>& input)
694
0
{
695
    // To consume a component value from a token stream input:
696
697
    // Process input:
698
0
    for (;;) {
699
0
        auto const& token = input.next_token();
700
701
        // <{-token>
702
        // <[-token>
703
        // <(-token>
704
0
        if (token.is(Token::Type::OpenCurly) || token.is(Token::Type::OpenSquare) || token.is(Token::Type::OpenParen)) {
705
            // Consume a simple block from input and return the result.
706
0
            return ComponentValue { consume_a_simple_block(input) };
707
0
        }
708
709
        // <function-token>
710
0
        if (token.is(Token::Type::Function)) {
711
            // Consume a function from input and return the result.
712
0
            return ComponentValue { consume_a_function(input) };
713
0
        }
714
715
        // anything else
716
0
        {
717
            // Consume a token from input and return the result.
718
0
            return ComponentValue { input.consume_a_token() };
719
0
        }
720
0
    }
721
0
}
722
723
template<>
724
void Parser::consume_a_component_value_and_do_nothing<ComponentValue>(TokenStream<ComponentValue>& tokens)
725
0
{
726
    // AD-HOC: To avoid unnecessairy allocations, we explicitly define a "do nothing" variant that discards the result immediately.
727
    // Note: This overload is called once tokens have already been converted into component values,
728
    //       so we do not need to do the work in the more general overload.
729
0
    (void)tokens.consume_a_token();
730
0
}
731
732
// 5.4.7. Consume a component value
733
// https://drafts.csswg.org/css-syntax/#consume-component-value
734
template<typename T>
735
void Parser::consume_a_component_value_and_do_nothing(TokenStream<T>& input)
736
0
{
737
    // AD-HOC: To avoid unnecessairy allocations, we explicitly define a "do nothing" variant that discards the result immediately.
738
    // To consume a component value from a token stream input:
739
740
    // Process input:
741
0
    for (;;) {
742
0
        auto const& token = input.next_token();
743
744
        // <{-token>
745
        // <[-token>
746
        // <(-token>
747
0
        if (token.is(Token::Type::OpenCurly) || token.is(Token::Type::OpenSquare) || token.is(Token::Type::OpenParen)) {
748
            // Consume a simple block from input and return the result.
749
0
            consume_a_simple_block_and_do_nothing(input);
750
0
            return;
751
0
        }
752
753
        // <function-token>
754
0
        if (token.is(Token::Type::Function)) {
755
            // Consume a function from input and return the result.
756
0
            consume_a_function_and_do_nothing(input);
757
0
            return;
758
0
        }
759
760
        // anything else
761
0
        {
762
            // Consume a token from input and return the result.
763
0
            input.discard_a_token();
764
0
            return;
765
0
        }
766
0
    }
767
0
}
768
769
template<typename T>
770
Vector<ComponentValue> Parser::consume_a_list_of_component_values(TokenStream<T>& input, Optional<Token::Type> stop_token, Nested nested)
771
0
{
772
    // To consume a list of component values from a token stream input, given an optional token stop token
773
    // and an optional boolean nested (default false):
774
775
    // Let values be an empty list of component values.
776
0
    Vector<ComponentValue> values;
777
778
    // Process input:
779
0
    for (;;) {
780
0
        auto& token = input.next_token();
781
782
        // <eof-token>
783
        // stop token (if passed)
784
0
        if (token.is(Token::Type::EndOfFile) || (stop_token.has_value() && token.is(*stop_token))) {
785
            // Return values.
786
0
            return values;
787
0
        }
788
789
        // <}-token>
790
0
        if (token.is(Token::Type::CloseCurly)) {
791
            // If nested is true, return values.
792
0
            if (nested == Nested::Yes) {
793
0
                return values;
794
0
            }
795
            // Otherwise, this is a parse error. Consume a token from input and append the result to values.
796
0
            else {
797
0
                log_parse_error();
798
0
                values.append(input.consume_a_token());
799
0
            }
800
0
        }
801
802
        // anything else
803
0
        {
804
            // Consume a component value from input, and append the result to values.
805
0
            values.append(consume_a_component_value(input));
806
0
        }
807
0
    }
808
0
}
Unexecuted instantiation: AK::Vector<Web::CSS::Parser::ComponentValue, 0ul> Web::CSS::Parser::Parser::consume_a_list_of_component_values<Web::CSS::Parser::ComponentValue>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, AK::Optional<Web::CSS::Parser::Token::Type>, Web::CSS::Parser::Parser::Nested)
Unexecuted instantiation: AK::Vector<Web::CSS::Parser::ComponentValue, 0ul> Web::CSS::Parser::Parser::consume_a_list_of_component_values<Web::CSS::Parser::Token>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::Token>&, AK::Optional<Web::CSS::Parser::Token::Type>, Web::CSS::Parser::Parser::Nested)
809
810
// https://drafts.csswg.org/css-syntax/#consume-simple-block
811
template<typename T>
812
SimpleBlock Parser::consume_a_simple_block(TokenStream<T>& input)
813
0
{
814
    // To consume a simple block from a token stream input:
815
816
    // Assert: the next token of input is <{-token>, <[-token>, or <(-token>.
817
0
    auto const& next = input.next_token();
818
0
    VERIFY(next.is(Token::Type::OpenCurly) || next.is(Token::Type::OpenSquare) || next.is(Token::Type::OpenParen));
819
820
    // Let ending token be the mirror variant of the next token. (E.g. if it was called with <[-token>, the ending token is <]-token>.)
821
0
    auto ending_token = input.next_token().mirror_variant();
822
823
    // Let block be a new simple block with its associated token set to the next token and with its value initially set to an empty list.
824
0
    SimpleBlock block {
825
0
        .token = input.next_token(),
826
0
        .value = {},
827
0
    };
828
829
    // Discard a token from input.
830
0
    input.discard_a_token();
831
832
    // Process input:
833
0
    for (;;) {
834
0
        auto const& token = input.next_token();
835
836
        // <eof-token>
837
        // ending token
838
0
        if (token.is(Token::Type::EndOfFile) || token.is(ending_token)) {
839
            // Discard a token from input. Return block.
840
            // AD-HOC: Store the token instead as the "end token"
841
0
            block.end_token = input.consume_a_token();
842
0
            return block;
843
0
        }
844
845
        // anything else
846
0
        {
847
            // Consume a component value from input and append the result to block’s value.
848
0
            block.value.empend(consume_a_component_value(input));
849
0
        }
850
0
    }
851
0
}
852
853
// https://drafts.csswg.org/css-syntax/#consume-simple-block
854
template<typename T>
855
void Parser::consume_a_simple_block_and_do_nothing(TokenStream<T>& input)
856
0
{
857
    // AD-HOC: To avoid unnecessairy allocations, we explicitly define a "do nothing" variant that discards the result immediately.
858
    // To consume a simple block from a token stream input:
859
860
    // Assert: the next token of input is <{-token>, <[-token>, or <(-token>.
861
0
    auto const& next = input.next_token();
862
0
    VERIFY(next.is(Token::Type::OpenCurly) || next.is(Token::Type::OpenSquare) || next.is(Token::Type::OpenParen));
863
864
    // Let ending token be the mirror variant of the next token. (E.g. if it was called with <[-token>, the ending token is <]-token>.)
865
0
    auto ending_token = input.next_token().mirror_variant();
866
867
    // Let block be a new simple block with its associated token set to the next token and with its value initially set to an empty list.
868
869
    // Discard a token from input.
870
0
    input.discard_a_token();
871
872
    // Process input:
873
0
    for (;;) {
874
0
        auto const& token = input.next_token();
875
876
        // <eof-token>
877
        // ending token
878
0
        if (token.is(Token::Type::EndOfFile) || token.is(ending_token)) {
879
            // Discard a token from input. Return block.
880
0
            input.discard_a_token();
881
0
            return;
882
0
        }
883
884
        // anything else
885
0
        {
886
            // Consume a component value from input and append the result to block’s value.
887
0
            consume_a_component_value_and_do_nothing(input);
888
0
        }
889
0
    }
890
0
}
891
892
// https://drafts.csswg.org/css-syntax/#consume-function
893
template<typename T>
894
Function Parser::consume_a_function(TokenStream<T>& input)
895
0
{
896
    // To consume a function from a token stream input:
897
898
    // Assert: The next token is a <function-token>.
899
0
    VERIFY(input.next_token().is(Token::Type::Function));
900
901
    // Consume a token from input, and let function be a new function with its name equal the returned token’s value,
902
    // and a value set to an empty list.
903
0
    auto name_token = ((Token)input.consume_a_token());
904
0
    Function function {
905
0
        .name = name_token.function(),
906
0
        .value = {},
907
0
        .name_token = name_token,
908
0
    };
909
910
    // Process input:
911
0
    for (;;) {
912
0
        auto const& token = input.next_token();
913
914
        // <eof-token>
915
        // <)-token>
916
0
        if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::CloseParen)) {
917
            // Discard a token from input. Return function.
918
            // AD-HOC: Store the token instead as the "end token"
919
0
            function.end_token = input.consume_a_token();
920
0
            return function;
921
0
        }
922
923
        // anything else
924
0
        {
925
            // Consume a component value from input and append the result to function’s value.
926
0
            function.value.append(consume_a_component_value(input));
927
0
        }
928
0
    }
929
0
}
930
931
// https://drafts.csswg.org/css-syntax/#consume-function
932
template<typename T>
933
void Parser::consume_a_function_and_do_nothing(TokenStream<T>& input)
934
0
{
935
    // AD-HOC: To avoid unnecessairy allocations, we explicitly define a "do nothing" variant that discards the result immediately.
936
    // To consume a function from a token stream input:
937
938
    // Assert: The next token is a <function-token>.
939
0
    VERIFY(input.next_token().is(Token::Type::Function));
940
941
    // Consume a token from input, and let function be a new function with its name equal the returned token’s value,
942
    // and a value set to an empty list.
943
0
    input.discard_a_token();
944
945
    // Process input:
946
0
    for (;;) {
947
0
        auto const& token = input.next_token();
948
949
        // <eof-token>
950
        // <)-token>
951
0
        if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::CloseParen)) {
952
            // Discard a token from input. Return function.
953
0
            input.discard_a_token();
954
0
            return;
955
0
        }
956
957
        // anything else
958
0
        {
959
            // Consume a component value from input and append the result to function’s value.
960
0
            consume_a_component_value_and_do_nothing(input);
961
0
        }
962
0
    }
963
0
}
964
965
// https://drafts.csswg.org/css-syntax/#consume-declaration
966
template<typename T>
967
Optional<Declaration> Parser::consume_a_declaration(TokenStream<T>& input, Nested nested)
968
0
{
969
    // To consume a declaration from a token stream input, given an optional bool nested (default false):
970
971
    // TODO: As noted in the "Implementation note" below https://drafts.csswg.org/css-syntax/#consume-block-contents
972
    //       there are ways we can optimise this by early-exiting.
973
974
    // Let decl be a new declaration, with an initially empty name and a value set to an empty list.
975
0
    Declaration declaration {
976
0
        .name {},
977
0
        .value {},
978
0
    };
979
980
    // 1. If the next token is an <ident-token>, consume a token from input and set decl’s name to the token’s value.
981
0
    if (input.next_token().is(Token::Type::Ident)) {
982
0
        declaration.name = ((Token)input.consume_a_token()).ident();
983
0
    }
984
    //    Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
985
0
    else {
986
0
        consume_the_remnants_of_a_bad_declaration(input, nested);
987
0
        return {};
988
0
    }
989
990
    // 2. Discard whitespace from input.
991
0
    input.discard_whitespace();
992
993
    // 3. If the next token is a <colon-token>, discard a token from input.
994
0
    if (input.next_token().is(Token::Type::Colon)) {
995
0
        input.discard_a_token();
996
0
    }
997
    //    Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
998
0
    else {
999
0
        consume_the_remnants_of_a_bad_declaration(input, nested);
1000
0
        return {};
1001
0
    }
1002
1003
    // 4. Discard whitespace from input.
1004
0
    input.discard_whitespace();
1005
1006
    // 5. Consume a list of component values from input, with nested, and with <semicolon-token> as the stop token,
1007
    //    and set decl’s value to the result.
1008
0
    declaration.value = consume_a_list_of_component_values(input, Token::Type::Semicolon, nested);
1009
1010
    // 6. If the last two non-<whitespace-token>s in decl’s value are a <delim-token> with the value "!"
1011
    //    followed by an <ident-token> with a value that is an ASCII case-insensitive match for "important",
1012
    //    remove them from decl’s value and set decl’s important flag.
1013
0
    if (declaration.value.size() >= 2) {
1014
        // NOTE: Walk backwards from the end until we find "important"
1015
0
        Optional<size_t> important_index;
1016
0
        for (size_t i = declaration.value.size() - 1; i > 0; i--) {
1017
0
            auto const& value = declaration.value[i];
1018
0
            if (value.is_ident("important"sv)) {
1019
0
                important_index = i;
1020
0
                break;
1021
0
            }
1022
0
            if (!value.is(Token::Type::Whitespace))
1023
0
                break;
1024
0
        }
1025
1026
        // NOTE: Walk backwards from important until we find "!"
1027
0
        if (important_index.has_value()) {
1028
0
            Optional<size_t> bang_index;
1029
0
            for (size_t i = important_index.value() - 1; i > 0; i--) {
1030
0
                auto const& value = declaration.value[i];
1031
0
                if (value.is_delim('!')) {
1032
0
                    bang_index = i;
1033
0
                    break;
1034
0
                }
1035
0
                if (value.is(Token::Type::Whitespace))
1036
0
                    continue;
1037
0
                break;
1038
0
            }
1039
1040
0
            if (bang_index.has_value()) {
1041
0
                declaration.value.remove(important_index.value());
1042
0
                declaration.value.remove(bang_index.value());
1043
0
                declaration.important = Important::Yes;
1044
0
            }
1045
0
        }
1046
0
    }
1047
1048
    // 7. While the last item in decl’s value is a <whitespace-token>, remove that token.
1049
0
    while (!declaration.value.is_empty() && declaration.value.last().is(Token::Type::Whitespace)) {
1050
0
        declaration.value.take_last();
1051
0
    }
1052
1053
    // See second clause of step 8.
1054
0
    auto contains_a_curly_block_and_non_whitespace = [](Vector<ComponentValue> const& declaration_value) {
1055
0
        bool contains_curly_block = false;
1056
0
        bool contains_non_whitespace = false;
1057
0
        for (auto const& value : declaration_value) {
1058
0
            if (value.is_block() && value.block().is_curly()) {
1059
0
                if (contains_non_whitespace)
1060
0
                    return true;
1061
0
                contains_curly_block = true;
1062
0
                continue;
1063
0
            }
1064
1065
0
            if (!value.is(Token::Type::Whitespace)) {
1066
0
                if (contains_curly_block)
1067
0
                    return true;
1068
0
                contains_non_whitespace = true;
1069
0
                continue;
1070
0
            }
1071
0
        }
1072
0
        return false;
1073
0
    };
Unexecuted instantiation: Web::CSS::Parser::Parser::consume_a_declaration<Web::CSS::Parser::Token>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::Token>&, Web::CSS::Parser::Parser::Nested)::{lambda(AK::Vector<Web::CSS::Parser::ComponentValue, 0ul> const&)#1}::operator()(AK::Vector<Web::CSS::Parser::ComponentValue, 0ul> const&) const
Unexecuted instantiation: Web::CSS::Parser::Parser::consume_a_declaration<Web::CSS::Parser::ComponentValue>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, Web::CSS::Parser::Parser::Nested)::{lambda(AK::Vector<Web::CSS::Parser::ComponentValue, 0ul> const&)#1}::operator()(AK::Vector<Web::CSS::Parser::ComponentValue, 0ul> const&) const
1074
1075
    // 8. If decl’s name is a custom property name string, then set decl’s original text to the segment
1076
    //    of the original source text string corresponding to the tokens of decl’s value.
1077
0
    if (is_a_custom_property_name_string(declaration.name)) {
1078
        // TODO: If we could reach inside the source string that the TokenStream uses, we could grab this as
1079
        //       a single substring instead of having to reconstruct it.
1080
0
        StringBuilder original_text;
1081
0
        for (auto const& value : declaration.value) {
1082
0
            original_text.append(value.original_source_text());
1083
0
        }
1084
0
        declaration.original_text = original_text.to_string_without_validation();
1085
0
    }
1086
    //    Otherwise, if decl’s value contains a top-level simple block with an associated token of <{-token>,
1087
    //    and also contains any other non-<whitespace-token> value, return nothing.
1088
    //    (That is, a top-level {}-block is only allowed as the entire value of a non-custom property.)
1089
0
    else if (contains_a_curly_block_and_non_whitespace(declaration.value)) {
1090
0
        return {};
1091
0
    }
1092
    //    Otherwise, if decl’s name is an ASCII case-insensitive match for "unicode-range", consume the value of
1093
    //    a unicode-range descriptor from the segment of the original source text string corresponding to the
1094
    //    tokens returned by the consume a list of component values call, and replace decl’s value with the result.
1095
0
    else if (declaration.name.equals_ignoring_ascii_case("unicode-range"sv)) {
1096
        // FIXME: Special unicode-range handling
1097
0
    }
1098
1099
    // 9. If decl is valid in the current context, return it; otherwise return nothing.
1100
0
    if (is_valid_in_the_current_context(declaration))
1101
0
        return declaration;
1102
0
    return {};
1103
0
}
Unexecuted instantiation: AK::Optional<Web::CSS::Parser::Declaration> Web::CSS::Parser::Parser::consume_a_declaration<Web::CSS::Parser::Token>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::Token>&, Web::CSS::Parser::Parser::Nested)
Unexecuted instantiation: AK::Optional<Web::CSS::Parser::Declaration> Web::CSS::Parser::Parser::consume_a_declaration<Web::CSS::Parser::ComponentValue>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, Web::CSS::Parser::Parser::Nested)
1104
1105
// https://drafts.csswg.org/css-syntax/#consume-the-remnants-of-a-bad-declaration
1106
template<typename T>
1107
void Parser::consume_the_remnants_of_a_bad_declaration(TokenStream<T>& input, Nested nested)
1108
0
{
1109
    // To consume the remnants of a bad declaration from a token stream input, given a bool nested:
1110
1111
    // Process input:
1112
0
    for (;;) {
1113
0
        auto const& token = input.next_token();
1114
1115
        // <eof-token>
1116
        // <semicolon-token>
1117
0
        if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::Semicolon)) {
1118
            // Discard a token from input, and return nothing.
1119
0
            input.discard_a_token();
1120
0
            return;
1121
0
        }
1122
1123
        // <}-token>
1124
0
        if (token.is(Token::Type::CloseCurly)) {
1125
            // If nested is true, return nothing. Otherwise, discard a token.
1126
0
            if (nested == Nested::Yes)
1127
0
                return;
1128
0
            input.discard_a_token();
1129
0
            continue;
1130
0
        }
1131
1132
        // anything else
1133
0
        {
1134
            // Consume a component value from input, and do nothing.
1135
0
            consume_a_component_value_and_do_nothing(input);
1136
0
            continue;
1137
0
        }
1138
0
    }
1139
0
}
Unexecuted instantiation: void Web::CSS::Parser::Parser::consume_the_remnants_of_a_bad_declaration<Web::CSS::Parser::Token>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::Token>&, Web::CSS::Parser::Parser::Nested)
Unexecuted instantiation: void Web::CSS::Parser::Parser::consume_the_remnants_of_a_bad_declaration<Web::CSS::Parser::ComponentValue>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, Web::CSS::Parser::Parser::Nested)
1140
1141
CSSRule* Parser::parse_as_css_rule()
1142
0
{
1143
0
    if (auto maybe_rule = parse_a_rule(m_token_stream); maybe_rule.has_value())
1144
0
        return convert_to_rule(maybe_rule.value(), Nested::No);
1145
0
    return {};
1146
0
}
1147
1148
// https://drafts.csswg.org/css-syntax/#parse-rule
1149
template<typename T>
1150
Optional<Rule> Parser::parse_a_rule(TokenStream<T>& input)
1151
0
{
1152
    // To parse a rule from input:
1153
0
    Optional<Rule> rule;
1154
1155
    // 1. Normalize input, and set input to the result.
1156
    // NOTE: This is done when initializing the Parser.
1157
1158
    // 2. Discard whitespace from input.
1159
0
    input.discard_whitespace();
1160
1161
    // 3. If the next token from input is an <EOF-token>, return a syntax error.
1162
0
    if (input.next_token().is(Token::Type::EndOfFile)) {
1163
0
        return {};
1164
0
    }
1165
    //    Otherwise, if the next token from input is an <at-keyword-token>,
1166
    //    consume an at-rule from input, and let rule be the return value.
1167
0
    else if (input.next_token().is(Token::Type::AtKeyword)) {
1168
0
        rule = consume_an_at_rule(m_token_stream).map([](auto& it) { return Rule { it }; });
1169
0
    }
1170
    //    Otherwise, consume a qualified rule from input and let rule be the return value.
1171
    //    If nothing or an invalid rule error was returned, return a syntax error.
1172
0
    else {
1173
0
        consume_a_qualified_rule(input).visit(
1174
0
            [&](QualifiedRule qualified_rule) { rule = move(qualified_rule); },
1175
0
            [](auto&) {});
Unexecuted instantiation: auto Web::CSS::Parser::Parser::parse_a_rule<Web::CSS::Parser::Token>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::Token>&)::{lambda(auto:1&)#2}::operator()<AK::Empty>(AK::Empty&) const
Unexecuted instantiation: auto Web::CSS::Parser::Parser::parse_a_rule<Web::CSS::Parser::Token>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::Token>&)::{lambda(auto:1&)#2}::operator()<Web::CSS::Parser::Parser::InvalidRuleError>(Web::CSS::Parser::Parser::InvalidRuleError&) const
1176
1177
0
        if (!rule.has_value())
1178
0
            return {};
1179
0
    }
1180
1181
    // 4. Discard whitespace from input.
1182
0
    input.discard_whitespace();
1183
1184
    // 5. If the next token from input is an <EOF-token>, return rule. Otherwise, return a syntax error.
1185
0
    if (input.next_token().is(Token::Type::EndOfFile))
1186
0
        return rule;
1187
0
    return {};
1188
0
}
1189
1190
// https://drafts.csswg.org/css-syntax/#parse-block-contents
1191
template<typename T>
1192
Vector<RuleOrListOfDeclarations> Parser::parse_a_blocks_contents(TokenStream<T>& input)
1193
0
{
1194
    // To parse a block’s contents from input:
1195
1196
    // 1. Normalize input, and set input to the result.
1197
    // NOTE: Done by constructing the Parser.
1198
1199
    // 2. Consume a block’s contents from input, and return the result.
1200
0
    return consume_a_blocks_contents(input);
1201
0
}
1202
1203
Optional<StyleProperty> Parser::parse_as_supports_condition()
1204
0
{
1205
0
    auto maybe_declaration = parse_a_declaration(m_token_stream);
1206
0
    if (maybe_declaration.has_value())
1207
0
        return convert_to_style_property(maybe_declaration.release_value());
1208
0
    return {};
1209
0
}
1210
1211
// https://drafts.csswg.org/css-syntax/#parse-declaration
1212
template<typename T>
1213
Optional<Declaration> Parser::parse_a_declaration(TokenStream<T>& input)
1214
0
{
1215
    // To parse a declaration from input:
1216
1217
    // 1. Normalize input, and set input to the result.
1218
    // Note: This is done when initializing the Parser.
1219
1220
    // 2. Discard whitespace from input.
1221
0
    input.discard_whitespace();
1222
1223
    // 3. Consume a declaration from input. If anything was returned, return it. Otherwise, return a syntax error.
1224
0
    if (auto declaration = consume_a_declaration(input); declaration.has_value())
1225
0
        return declaration.release_value();
1226
    // FIXME: Syntax error
1227
0
    return {};
1228
0
}
1229
1230
Optional<ComponentValue> Parser::parse_as_component_value()
1231
0
{
1232
0
    return parse_a_component_value(m_token_stream);
1233
0
}
1234
1235
// https://drafts.csswg.org/css-syntax/#parse-component-value
1236
template<typename T>
1237
Optional<ComponentValue> Parser::parse_a_component_value(TokenStream<T>& input)
1238
0
{
1239
    // To parse a component value from input:
1240
1241
    // 1. Normalize input, and set input to the result.
1242
    // Note: This is done when initializing the Parser.
1243
1244
    // 2. Discard whitespace from input.
1245
0
    input.discard_whitespace();
1246
1247
    // 3. If input is empty, return a syntax error.
1248
    // FIXME: Syntax error
1249
0
    if (input.is_empty())
1250
0
        return {};
1251
1252
    // 4. Consume a component value from input and let value be the return value.
1253
0
    auto value = consume_a_component_value(input);
1254
1255
    // 5. Discard whitespace from input.
1256
0
    input.discard_whitespace();
1257
1258
    // 6. If input is empty, return value. Otherwise, return a syntax error.
1259
0
    if (input.is_empty())
1260
0
        return value;
1261
    // FIXME: Syntax error
1262
0
    return {};
1263
0
}
1264
1265
// https://drafts.csswg.org/css-syntax/#parse-list-of-component-values
1266
template<typename T>
1267
Vector<ComponentValue> Parser::parse_a_list_of_component_values(TokenStream<T>& input)
1268
0
{
1269
    // To parse a list of component values from input:
1270
1271
    // 1. Normalize input, and set input to the result.
1272
    // Note: This is done when initializing the Parser.
1273
1274
    // 2. Consume a list of component values from input, and return the result.
1275
0
    return consume_a_list_of_component_values(input);
1276
0
}
Unexecuted instantiation: AK::Vector<Web::CSS::Parser::ComponentValue, 0ul> Web::CSS::Parser::Parser::parse_a_list_of_component_values<Web::CSS::Parser::ComponentValue>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)
Unexecuted instantiation: AK::Vector<Web::CSS::Parser::ComponentValue, 0ul> Web::CSS::Parser::Parser::parse_a_list_of_component_values<Web::CSS::Parser::Token>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::Token>&)
1277
1278
// https://drafts.csswg.org/css-syntax/#parse-comma-separated-list-of-component-values
1279
template<typename T>
1280
Vector<Vector<ComponentValue>> Parser::parse_a_comma_separated_list_of_component_values(TokenStream<T>& input)
1281
0
{
1282
    // To parse a comma-separated list of component values from input:
1283
1284
    // 1. Normalize input, and set input to the result.
1285
    // Note: This is done when initializing the Parser.
1286
1287
    // 2. Let groups be an empty list.
1288
0
    Vector<Vector<ComponentValue>> groups;
1289
1290
    // 3. While input is not empty:
1291
0
    while (!input.is_empty()) {
1292
1293
        // 1. Consume a list of component values from input, with <comma-token> as the stop token, and append the result to groups.
1294
0
        groups.append(consume_a_list_of_component_values(input, Token::Type::Comma));
1295
1296
        // 2. Discard a token from input.
1297
0
        input.discard_a_token();
1298
0
    }
1299
1300
    // 4. Return groups.
1301
0
    return groups;
1302
0
}
Unexecuted instantiation: AK::Vector<AK::Vector<Web::CSS::Parser::ComponentValue, 0ul>, 0ul> Web::CSS::Parser::Parser::parse_a_comma_separated_list_of_component_values<Web::CSS::Parser::ComponentValue>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)
Unexecuted instantiation: AK::Vector<AK::Vector<Web::CSS::Parser::ComponentValue, 0ul>, 0ul> Web::CSS::Parser::Parser::parse_a_comma_separated_list_of_component_values<Web::CSS::Parser::Token>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::Token>&)
1303
template Vector<Vector<ComponentValue>> Parser::parse_a_comma_separated_list_of_component_values(TokenStream<ComponentValue>&);
1304
template Vector<Vector<ComponentValue>> Parser::parse_a_comma_separated_list_of_component_values(TokenStream<Token>&);
1305
1306
ElementInlineCSSStyleDeclaration* Parser::parse_as_style_attribute(DOM::Element& element)
1307
0
{
1308
0
    auto declarations_and_at_rules = parse_a_blocks_contents(m_token_stream);
1309
0
    auto [properties, custom_properties] = extract_properties(declarations_and_at_rules);
1310
0
    return ElementInlineCSSStyleDeclaration::create(element, move(properties), move(custom_properties));
1311
0
}
1312
1313
Optional<URL::URL> Parser::parse_url_function(TokenStream<ComponentValue>& tokens)
1314
0
{
1315
0
    auto transaction = tokens.begin_transaction();
1316
0
    auto& component_value = tokens.consume_a_token();
1317
1318
0
    auto convert_string_to_url = [&](StringView url_string) -> Optional<URL::URL> {
1319
0
        auto url = m_context.complete_url(url_string);
1320
0
        if (url.is_valid()) {
1321
0
            transaction.commit();
1322
0
            return url;
1323
0
        }
1324
0
        return {};
1325
0
    };
1326
1327
0
    if (component_value.is(Token::Type::Url)) {
1328
0
        auto url_string = component_value.token().url();
1329
0
        return convert_string_to_url(url_string);
1330
0
    }
1331
0
    if (component_value.is_function("url"sv)) {
1332
0
        auto const& function_values = component_value.function().value;
1333
        // FIXME: Handle url-modifiers. https://www.w3.org/TR/css-values-4/#url-modifiers
1334
0
        for (size_t i = 0; i < function_values.size(); ++i) {
1335
0
            auto const& value = function_values[i];
1336
0
            if (value.is(Token::Type::Whitespace))
1337
0
                continue;
1338
0
            if (value.is(Token::Type::String)) {
1339
0
                auto url_string = value.token().string();
1340
0
                return convert_string_to_url(url_string);
1341
0
            }
1342
0
            break;
1343
0
        }
1344
0
    }
1345
1346
0
    return {};
1347
0
}
1348
1349
RefPtr<CSSStyleValue> Parser::parse_url_value(TokenStream<ComponentValue>& tokens)
1350
0
{
1351
0
    auto url = parse_url_function(tokens);
1352
0
    if (!url.has_value())
1353
0
        return nullptr;
1354
0
    return URLStyleValue::create(*url);
1355
0
}
1356
1357
RefPtr<CSSStyleValue> Parser::parse_basic_shape_value(TokenStream<ComponentValue>& tokens)
1358
0
{
1359
0
    auto transaction = tokens.begin_transaction();
1360
0
    auto& component_value = tokens.consume_a_token();
1361
0
    if (!component_value.is_function())
1362
0
        return nullptr;
1363
1364
0
    auto function_name = component_value.function().name.bytes_as_string_view();
1365
1366
    // FIXME: Implement other shapes. See: https://www.w3.org/TR/css-shapes-1/#basic-shape-functions
1367
0
    if (!function_name.equals_ignoring_ascii_case("polygon"sv))
1368
0
        return nullptr;
1369
1370
    // polygon() = polygon( <'fill-rule'>? , [<length-percentage> <length-percentage>]# )
1371
    // FIXME: Parse the fill-rule.
1372
0
    auto arguments_tokens = TokenStream { component_value.function().value };
1373
0
    auto arguments = parse_a_comma_separated_list_of_component_values(arguments_tokens);
1374
1375
0
    Vector<Polygon::Point> points;
1376
0
    for (auto& argument : arguments) {
1377
0
        TokenStream argument_tokens { argument };
1378
1379
0
        argument_tokens.discard_whitespace();
1380
0
        auto x_pos = parse_length_percentage(argument_tokens);
1381
0
        if (!x_pos.has_value())
1382
0
            return nullptr;
1383
1384
0
        argument_tokens.discard_whitespace();
1385
0
        auto y_pos = parse_length_percentage(argument_tokens);
1386
0
        if (!y_pos.has_value())
1387
0
            return nullptr;
1388
1389
0
        argument_tokens.discard_whitespace();
1390
0
        if (argument_tokens.has_next_token())
1391
0
            return nullptr;
1392
1393
0
        points.append(Polygon::Point { *x_pos, *y_pos });
1394
0
    }
1395
1396
0
    transaction.commit();
1397
0
    return BasicShapeStyleValue::create(Polygon { FillRule::Nonzero, move(points) });
1398
0
}
1399
1400
Optional<FlyString> Parser::parse_layer_name(TokenStream<ComponentValue>& tokens, AllowBlankLayerName allow_blank_layer_name)
1401
0
{
1402
    // https://drafts.csswg.org/css-cascade-5/#typedef-layer-name
1403
    // <layer-name> = <ident> [ '.' <ident> ]*
1404
1405
    // "The CSS-wide keywords are reserved for future use, and cause the rule to be invalid at parse time if used as an <ident> in the <layer-name>."
1406
0
    auto is_valid_layer_name_part = [](auto& token) {
1407
0
        return token.is(Token::Type::Ident) && !is_css_wide_keyword(token.token().ident());
1408
0
    };
1409
1410
0
    auto transaction = tokens.begin_transaction();
1411
0
    tokens.discard_whitespace();
1412
0
    if (!tokens.has_next_token() && allow_blank_layer_name == AllowBlankLayerName::Yes) {
1413
        // No name present, just return a blank one
1414
0
        return FlyString();
1415
0
    }
1416
1417
0
    auto& first_name_token = tokens.consume_a_token();
1418
0
    if (!is_valid_layer_name_part(first_name_token))
1419
0
        return {};
1420
1421
0
    StringBuilder builder;
1422
0
    builder.append(first_name_token.token().ident());
1423
1424
0
    while (tokens.has_next_token()) {
1425
        // Repeatedly parse `'.' <ident>`
1426
0
        if (!tokens.next_token().is_delim('.'))
1427
0
            break;
1428
0
        tokens.discard_a_token(); // '.'
1429
1430
0
        auto& name_token = tokens.consume_a_token();
1431
0
        if (!is_valid_layer_name_part(name_token))
1432
0
            return {};
1433
0
        builder.appendff(".{}", name_token.token().ident());
1434
0
    }
1435
1436
0
    transaction.commit();
1437
0
    return builder.to_fly_string_without_validation();
1438
0
}
1439
1440
bool Parser::is_valid_in_the_current_context(Declaration&)
1441
0
{
1442
    // FIXME: Implement this check
1443
0
    return true;
1444
0
}
1445
1446
bool Parser::is_valid_in_the_current_context(AtRule&)
1447
0
{
1448
    // FIXME: Implement this check
1449
0
    return true;
1450
0
}
1451
1452
bool Parser::is_valid_in_the_current_context(QualifiedRule&)
1453
0
{
1454
    // FIXME: Implement this check
1455
0
    return true;
1456
0
}
1457
1458
JS::GCPtr<CSSRule> Parser::convert_to_rule(Rule const& rule, Nested nested)
1459
0
{
1460
0
    return rule.visit(
1461
0
        [this, nested](AtRule const& at_rule) -> JS::GCPtr<CSSRule> {
1462
0
            if (has_ignored_vendor_prefix(at_rule.name))
1463
0
                return {};
1464
1465
0
            if (at_rule.name.equals_ignoring_ascii_case("font-face"sv))
1466
0
                return convert_to_font_face_rule(at_rule);
1467
1468
0
            if (at_rule.name.equals_ignoring_ascii_case("import"sv))
1469
0
                return convert_to_import_rule(at_rule);
1470
1471
0
            if (at_rule.name.equals_ignoring_ascii_case("keyframes"sv))
1472
0
                return convert_to_keyframes_rule(at_rule);
1473
1474
0
            if (at_rule.name.equals_ignoring_ascii_case("layer"sv))
1475
0
                return convert_to_layer_rule(at_rule, nested);
1476
1477
0
            if (at_rule.name.equals_ignoring_ascii_case("media"sv))
1478
0
                return convert_to_media_rule(at_rule, nested);
1479
1480
0
            if (at_rule.name.equals_ignoring_ascii_case("namespace"sv))
1481
0
                return convert_to_namespace_rule(at_rule);
1482
1483
0
            if (at_rule.name.equals_ignoring_ascii_case("supports"sv))
1484
0
                return convert_to_supports_rule(at_rule, nested);
1485
1486
0
            if (at_rule.name.equals_ignoring_ascii_case("property"sv))
1487
0
                return convert_to_property_rule(at_rule);
1488
1489
            // FIXME: More at rules!
1490
0
            dbgln_if(CSS_PARSER_DEBUG, "Unrecognized CSS at-rule: @{}", at_rule.name);
1491
0
            return {};
1492
0
        },
1493
0
        [this, nested](QualifiedRule const& qualified_rule) -> JS::GCPtr<CSSRule> {
1494
0
            return convert_to_style_rule(qualified_rule, nested);
1495
0
        });
1496
0
}
1497
1498
JS::GCPtr<CSSStyleRule> Parser::convert_to_style_rule(QualifiedRule const& qualified_rule, Nested nested)
1499
0
{
1500
0
    TokenStream prelude_stream { qualified_rule.prelude };
1501
1502
0
    auto maybe_selectors = parse_a_selector_list(prelude_stream,
1503
0
        nested == Nested::Yes ? SelectorType::Relative : SelectorType::Standalone);
1504
1505
0
    if (maybe_selectors.is_error()) {
1506
0
        if (maybe_selectors.error() == ParseError::SyntaxError) {
1507
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule selectors invalid; discarding.");
1508
            if constexpr (CSS_PARSER_DEBUG) {
1509
                prelude_stream.dump_all_tokens();
1510
            }
1511
0
        }
1512
0
        return {};
1513
0
    }
1514
1515
0
    if (maybe_selectors.value().is_empty()) {
1516
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: empty selector; discarding.");
1517
0
        return {};
1518
0
    }
1519
1520
0
    SelectorList selectors = maybe_selectors.release_value();
1521
0
    if (nested == Nested::Yes) {
1522
        // "Nested style rules differ from non-nested rules in the following ways:
1523
        // - A nested style rule accepts a <relative-selector-list> as its prelude (rather than just a <selector-list>).
1524
        //   Any relative selectors are relative to the elements represented by the nesting selector.
1525
        // - If a selector in the <relative-selector-list> does not start with a combinator but does contain the nesting
1526
        //   selector, it is interpreted as a non-relative selector."
1527
        // https://drafts.csswg.org/css-nesting-1/#syntax
1528
        // NOTE: We already parsed the selectors as a <relative-selector-list>
1529
1530
        // Nested relative selectors get a `&` inserted at the beginning.
1531
        // This is, handily, how the spec wants them serialized:
1532
        // "When serializing a relative selector in a nested style rule, the selector must be absolutized,
1533
        // with the implied nesting selector inserted."
1534
        // - https://drafts.csswg.org/css-nesting-1/#cssom
1535
1536
0
        SelectorList new_list;
1537
0
        new_list.ensure_capacity(selectors.size());
1538
0
        for (auto const& selector : selectors) {
1539
0
            auto first_combinator = selector->compound_selectors().first().combinator;
1540
0
            if (!first_is_one_of(first_combinator, Selector::Combinator::None, Selector::Combinator::Descendant)
1541
0
                || !selector->contains_the_nesting_selector()) {
1542
0
                new_list.append(selector->relative_to(Selector::SimpleSelector { .type = Selector::SimpleSelector::Type::Nesting }));
1543
0
            } else if (first_combinator == Selector::Combinator::Descendant) {
1544
                // Replace leading descendant combinator (whitespace) with none, because we're not actually relative.
1545
0
                auto copied_compound_selectors = selector->compound_selectors();
1546
0
                copied_compound_selectors.first().combinator = Selector::Combinator::None;
1547
0
                new_list.append(Selector::create(move(copied_compound_selectors)));
1548
0
            } else {
1549
0
                new_list.append(selector);
1550
0
            }
1551
0
        }
1552
0
        selectors = move(new_list);
1553
0
    }
1554
1555
0
    auto* declaration = convert_to_style_declaration(qualified_rule.declarations);
1556
0
    if (!declaration) {
1557
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule declaration invalid; discarding.");
1558
0
        return {};
1559
0
    }
1560
1561
0
    JS::MarkedVector<CSSRule*> child_rules { m_context.realm().heap() };
1562
0
    for (auto& child : qualified_rule.child_rules) {
1563
0
        child.visit(
1564
0
            [&](Rule const& rule) {
1565
                // "In addition to nested style rules, this specification allows nested group rules inside of style rules:
1566
                // any at-rule whose body contains style rules can be nested inside of a style rule as well."
1567
                // https://drafts.csswg.org/css-nesting-1/#nested-group-rules
1568
0
                if (auto converted_rule = convert_to_rule(rule, Nested::Yes)) {
1569
0
                    if (is<CSSGroupingRule>(*converted_rule)) {
1570
0
                        child_rules.append(converted_rule);
1571
0
                    } else {
1572
0
                        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: nested {} is not allowed inside style rule; discarding.", converted_rule->class_name());
1573
0
                    }
1574
0
                }
1575
0
            },
1576
0
            [&](Vector<Declaration> const& declarations) {
1577
0
                auto* declaration = convert_to_style_declaration(declarations);
1578
0
                if (!declaration) {
1579
0
                    dbgln_if(CSS_PARSER_DEBUG, "CSSParser: nested declarations invalid; discarding.");
1580
0
                    return;
1581
0
                }
1582
0
                child_rules.append(CSSNestedDeclarations::create(m_context.realm(), *declaration));
1583
0
            });
1584
0
    }
1585
0
    auto nested_rules = CSSRuleList::create(m_context.realm(), move(child_rules));
1586
0
    return CSSStyleRule::create(m_context.realm(), move(selectors), *declaration, *nested_rules);
1587
0
}
1588
1589
JS::GCPtr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
1590
0
{
1591
    // https://drafts.csswg.org/css-cascade-5/#at-import
1592
    // @import [ <url> | <string> ]
1593
    //         [ layer | layer(<layer-name>) ]?
1594
    //         <import-conditions> ;
1595
    //
1596
    // <import-conditions> = [ supports( [ <supports-condition> | <declaration> ] ) ]?
1597
    //                      <media-query-list>?
1598
1599
0
    if (rule.prelude.is_empty()) {
1600
0
        dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Empty prelude.");
1601
0
        return {};
1602
0
    }
1603
1604
0
    if (!rule.child_rules_and_lists_of_declarations.is_empty()) {
1605
0
        dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Block is not allowed.");
1606
0
        return {};
1607
0
    }
1608
1609
0
    TokenStream tokens { rule.prelude };
1610
0
    tokens.discard_whitespace();
1611
1612
0
    Optional<URL::URL> url = parse_url_function(tokens);
1613
0
    if (!url.has_value() && tokens.next_token().is(Token::Type::String))
1614
0
        url = m_context.complete_url(tokens.consume_a_token().token().string());
1615
1616
0
    if (!url.has_value()) {
1617
0
        dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Unable to parse `{}` as URL.", tokens.next_token().to_debug_string());
1618
0
        return {};
1619
0
    }
1620
1621
0
    tokens.discard_whitespace();
1622
    // TODO: Support layers and import-conditions
1623
0
    if (tokens.has_next_token()) {
1624
        if constexpr (CSS_PARSER_DEBUG) {
1625
            dbgln("Failed to parse @import rule: Trailing tokens after URL are not yet supported.");
1626
            tokens.dump_all_tokens();
1627
        }
1628
0
        return {};
1629
0
    }
1630
1631
0
    return CSSImportRule::create(url.value(), const_cast<DOM::Document&>(*m_context.document()));
1632
0
}
1633
1634
JS::GCPtr<CSSRule> Parser::convert_to_layer_rule(AtRule const& rule, Nested nested)
1635
0
{
1636
    // https://drafts.csswg.org/css-cascade-5/#at-layer
1637
0
    if (!rule.child_rules_and_lists_of_declarations.is_empty()) {
1638
        // CSSLayerBlockRule
1639
        // @layer <layer-name>? {
1640
        //   <rule-list>
1641
        // }
1642
1643
        // First, the name
1644
0
        FlyString layer_name = {};
1645
0
        auto prelude_tokens = TokenStream { rule.prelude };
1646
0
        if (auto maybe_name = parse_layer_name(prelude_tokens, AllowBlankLayerName::Yes); maybe_name.has_value()) {
1647
0
            layer_name = maybe_name.release_value();
1648
0
        } else {
1649
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer has invalid prelude, (not a valid layer name) prelude = {}; discarding.", rule.prelude);
1650
0
            return {};
1651
0
        }
1652
1653
0
        prelude_tokens.discard_whitespace();
1654
0
        if (prelude_tokens.has_next_token()) {
1655
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer has invalid prelude, (tokens after layer name) prelude = {}; discarding.", rule.prelude);
1656
0
            return {};
1657
0
        }
1658
1659
        // Then the rules
1660
0
        JS::MarkedVector<CSSRule*> child_rules { m_context.realm().heap() };
1661
0
        rule.for_each_as_rule_list([&](auto& rule) {
1662
0
            if (auto child_rule = convert_to_rule(rule, nested))
1663
0
                child_rules.append(child_rule);
1664
0
        });
1665
0
        auto rule_list = CSSRuleList::create(m_context.realm(), child_rules);
1666
0
        return CSSLayerBlockRule::create(m_context.realm(), layer_name, rule_list);
1667
0
    }
1668
1669
    // CSSLayerStatementRule
1670
    // @layer <layer-name>#;
1671
0
    auto tokens = TokenStream { rule.prelude };
1672
0
    tokens.discard_whitespace();
1673
0
    Vector<FlyString> layer_names;
1674
0
    while (tokens.has_next_token()) {
1675
        // Comma
1676
0
        if (!layer_names.is_empty()) {
1677
0
            if (auto comma = tokens.consume_a_token(); !comma.is(Token::Type::Comma)) {
1678
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer missing separating comma, ({}) prelude = {}; discarding.", comma.to_debug_string(), rule.prelude);
1679
0
                return {};
1680
0
            }
1681
0
            tokens.discard_whitespace();
1682
0
        }
1683
1684
0
        if (auto name = parse_layer_name(tokens, AllowBlankLayerName::No); name.has_value()) {
1685
0
            layer_names.append(name.release_value());
1686
0
        } else {
1687
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer contains invalid name, prelude = {}; discarding.", rule.prelude);
1688
0
            return {};
1689
0
        }
1690
0
        tokens.discard_whitespace();
1691
0
    }
1692
1693
0
    if (layer_names.is_empty()) {
1694
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer statement has no layer names, prelude = {}; discarding.", rule.prelude);
1695
0
        return {};
1696
0
    }
1697
1698
0
    return CSSLayerStatementRule::create(m_context.realm(), move(layer_names));
1699
0
}
1700
1701
JS::GCPtr<CSSKeyframesRule> Parser::convert_to_keyframes_rule(AtRule const& rule)
1702
0
{
1703
    // https://drafts.csswg.org/css-animations/#keyframes
1704
    // @keyframes = @keyframes <keyframes-name> { <qualified-rule-list> }
1705
    // <keyframes-name> = <custom-ident> | <string>
1706
    // <keyframe-block> = <keyframe-selector># { <declaration-list> }
1707
    // <keyframe-selector> = from | to | <percentage [0,100]>
1708
1709
0
    if (rule.prelude.is_empty()) {
1710
0
        dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @keyframes rule: Empty prelude.");
1711
0
        return {};
1712
0
    }
1713
1714
    // FIXME: Is there some way of detecting if there is a block or not?
1715
1716
0
    auto prelude_stream = TokenStream { rule.prelude };
1717
0
    prelude_stream.discard_whitespace();
1718
0
    auto& token = prelude_stream.consume_a_token();
1719
0
    if (!token.is_token()) {
1720
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes has invalid prelude, prelude = {}; discarding.", rule.prelude);
1721
0
        return {};
1722
0
    }
1723
1724
0
    auto name_token = token.token();
1725
0
    prelude_stream.discard_whitespace();
1726
1727
0
    if (prelude_stream.has_next_token()) {
1728
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes has invalid prelude, prelude = {}; discarding.", rule.prelude);
1729
0
        return {};
1730
0
    }
1731
1732
0
    if (name_token.is(Token::Type::Ident) && (is_css_wide_keyword(name_token.ident()) || name_token.ident().equals_ignoring_ascii_case("none"sv))) {
1733
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes rule name is invalid: {}; discarding.", name_token.ident());
1734
0
        return {};
1735
0
    }
1736
1737
0
    if (!name_token.is(Token::Type::String) && !name_token.is(Token::Type::Ident)) {
1738
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes rule name is invalid: {}; discarding.", name_token.to_debug_string());
1739
0
        return {};
1740
0
    }
1741
1742
0
    auto name = name_token.to_string();
1743
1744
0
    JS::MarkedVector<CSSRule*> keyframes(m_context.realm().heap());
1745
0
    rule.for_each_as_qualified_rule_list([&](auto& qualified_rule) {
1746
0
        if (!qualified_rule.child_rules.is_empty()) {
1747
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes keyframe rule contains at-rules; discarding them.");
1748
0
        }
1749
1750
0
        auto selectors = Vector<CSS::Percentage> {};
1751
0
        TokenStream child_tokens { qualified_rule.prelude };
1752
0
        while (child_tokens.has_next_token()) {
1753
0
            child_tokens.discard_whitespace();
1754
0
            if (!child_tokens.has_next_token())
1755
0
                break;
1756
0
            auto tok = child_tokens.consume_a_token();
1757
0
            if (!tok.is_token()) {
1758
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes rule has invalid selector: {}; discarding.", tok.to_debug_string());
1759
0
                child_tokens.reconsume_current_input_token();
1760
0
                break;
1761
0
            }
1762
0
            auto token = tok.token();
1763
0
            auto read_a_selector = false;
1764
0
            if (token.is(Token::Type::Ident)) {
1765
0
                if (token.ident().equals_ignoring_ascii_case("from"sv)) {
1766
0
                    selectors.append(CSS::Percentage(0));
1767
0
                    read_a_selector = true;
1768
0
                }
1769
0
                if (token.ident().equals_ignoring_ascii_case("to"sv)) {
1770
0
                    selectors.append(CSS::Percentage(100));
1771
0
                    read_a_selector = true;
1772
0
                }
1773
0
            } else if (token.is(Token::Type::Percentage)) {
1774
0
                selectors.append(CSS::Percentage(token.percentage()));
1775
0
                read_a_selector = true;
1776
0
            }
1777
1778
0
            if (read_a_selector) {
1779
0
                child_tokens.discard_whitespace();
1780
0
                if (child_tokens.consume_a_token().is(Token::Type::Comma))
1781
0
                    continue;
1782
0
            }
1783
1784
0
            child_tokens.reconsume_current_input_token();
1785
0
            break;
1786
0
        }
1787
1788
0
        PropertiesAndCustomProperties properties;
1789
0
        qualified_rule.for_each_as_declaration_list([&](auto const& declaration) {
1790
0
            extract_property(declaration, properties);
1791
0
        });
1792
0
        auto style = PropertyOwningCSSStyleDeclaration::create(m_context.realm(), move(properties.properties), move(properties.custom_properties));
1793
0
        for (auto& selector : selectors) {
1794
0
            auto keyframe_rule = CSSKeyframeRule::create(m_context.realm(), selector, *style);
1795
0
            keyframes.append(keyframe_rule);
1796
0
        }
1797
0
    });
1798
1799
0
    return CSSKeyframesRule::create(m_context.realm(), name, CSSRuleList::create(m_context.realm(), move(keyframes)));
1800
0
}
1801
1802
JS::GCPtr<CSSNamespaceRule> Parser::convert_to_namespace_rule(AtRule const& rule)
1803
0
{
1804
    // https://drafts.csswg.org/css-namespaces/#syntax
1805
    // @namespace <namespace-prefix>? [ <string> | <url> ] ;
1806
    // <namespace-prefix> = <ident>
1807
1808
0
    if (rule.prelude.is_empty()) {
1809
0
        dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @namespace rule: Empty prelude.");
1810
0
        return {};
1811
0
    }
1812
1813
0
    if (!rule.child_rules_and_lists_of_declarations.is_empty()) {
1814
0
        dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @namespace rule: Block is not allowed.");
1815
0
        return {};
1816
0
    }
1817
1818
0
    auto tokens = TokenStream { rule.prelude };
1819
0
    tokens.discard_whitespace();
1820
1821
0
    Optional<FlyString> prefix = {};
1822
0
    if (tokens.next_token().is(Token::Type::Ident)) {
1823
0
        prefix = tokens.consume_a_token().token().ident();
1824
0
        tokens.discard_whitespace();
1825
0
    }
1826
1827
0
    FlyString namespace_uri;
1828
0
    if (auto url = parse_url_function(tokens); url.has_value()) {
1829
0
        namespace_uri = MUST(url.value().to_string());
1830
0
    } else if (auto& url_token = tokens.consume_a_token(); url_token.is(Token::Type::String)) {
1831
0
        namespace_uri = url_token.token().string();
1832
0
    } else {
1833
0
        dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @namespace rule: Unable to parse `{}` as URL.", tokens.next_token().to_debug_string());
1834
0
        return {};
1835
0
    }
1836
1837
0
    tokens.discard_whitespace();
1838
0
    if (tokens.has_next_token()) {
1839
        if constexpr (CSS_PARSER_DEBUG) {
1840
            dbgln("Failed to parse @namespace rule: Trailing tokens after URL.");
1841
            tokens.dump_all_tokens();
1842
        }
1843
0
        return {};
1844
0
    }
1845
1846
0
    return CSSNamespaceRule::create(m_context.realm(), prefix, namespace_uri);
1847
0
}
1848
1849
JS::GCPtr<CSSSupportsRule> Parser::convert_to_supports_rule(AtRule const& rule, Nested nested)
1850
0
{
1851
    // https://drafts.csswg.org/css-conditional-3/#at-supports
1852
    // @supports <supports-condition> {
1853
    //   <rule-list>
1854
    // }
1855
1856
0
    if (rule.prelude.is_empty()) {
1857
0
        dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @supports rule: Empty prelude.");
1858
0
        return {};
1859
0
    }
1860
1861
0
    auto supports_tokens = TokenStream { rule.prelude };
1862
0
    auto supports = parse_a_supports(supports_tokens);
1863
0
    if (!supports) {
1864
        if constexpr (CSS_PARSER_DEBUG) {
1865
            dbgln("Failed to parse @supports rule: supports clause invalid.");
1866
            supports_tokens.dump_all_tokens();
1867
        }
1868
0
        return {};
1869
0
    }
1870
1871
0
    JS::MarkedVector<CSSRule*> child_rules { m_context.realm().heap() };
1872
0
    rule.for_each_as_rule_list([&](auto& rule) {
1873
0
        if (auto child_rule = convert_to_rule(rule, nested))
1874
0
            child_rules.append(child_rule);
1875
0
    });
1876
1877
0
    auto rule_list = CSSRuleList::create(m_context.realm(), child_rules);
1878
0
    return CSSSupportsRule::create(m_context.realm(), supports.release_nonnull(), rule_list);
1879
0
}
1880
JS::GCPtr<CSSPropertyRule> Parser::convert_to_property_rule(AtRule const& rule)
1881
0
{
1882
    // https://drafts.css-houdini.org/css-properties-values-api-1/#at-ruledef-property
1883
    // @property <custom-property-name> {
1884
    // <declaration-list>
1885
    // }
1886
1887
0
    if (rule.prelude.is_empty()) {
1888
0
        dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @property rule: Empty prelude.");
1889
0
        return {};
1890
0
    }
1891
1892
0
    auto prelude_stream = TokenStream { rule.prelude };
1893
0
    prelude_stream.discard_whitespace();
1894
0
    auto const& token = prelude_stream.consume_a_token();
1895
0
    if (!token.is_token()) {
1896
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @property has invalid prelude, prelude = {}; discarding.", rule.prelude);
1897
0
        return {};
1898
0
    }
1899
1900
0
    auto name_token = token.token();
1901
0
    prelude_stream.discard_whitespace();
1902
1903
0
    if (prelude_stream.has_next_token()) {
1904
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @property has invalid prelude, prelude = {}; discarding.", rule.prelude);
1905
0
        return {};
1906
0
    }
1907
1908
0
    if (!name_token.is(Token::Type::Ident)) {
1909
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @property name is invalid: {}; discarding.", name_token.to_debug_string());
1910
0
        return {};
1911
0
    }
1912
1913
0
    if (!is_a_custom_property_name_string(name_token.ident())) {
1914
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @property name doesn't start with '--': {}; discarding.", name_token.ident());
1915
0
        return {};
1916
0
    }
1917
1918
0
    auto const& name = name_token.ident();
1919
1920
0
    Optional<FlyString> syntax_maybe;
1921
0
    Optional<bool> inherits_maybe;
1922
0
    Optional<String> initial_value_maybe;
1923
1924
0
    rule.for_each_as_declaration_list([&](auto& declaration) {
1925
0
        if (declaration.name.equals_ignoring_ascii_case("syntax"sv)) {
1926
0
            TokenStream token_stream { declaration.value };
1927
0
            token_stream.discard_whitespace();
1928
1929
0
            auto const& syntax_token = token_stream.consume_a_token();
1930
0
            if (syntax_token.is(Token::Type::String)) {
1931
0
                token_stream.discard_whitespace();
1932
0
                if (token_stream.has_next_token()) {
1933
0
                    dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected trailing tokens in syntax");
1934
0
                } else {
1935
0
                    syntax_maybe = syntax_token.token().string();
1936
0
                }
1937
0
            } else {
1938
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected value for @property \"syntax\": {}; discarding.", declaration.to_string());
1939
0
            }
1940
0
            return;
1941
0
        }
1942
0
        if (declaration.name.equals_ignoring_ascii_case("inherits"sv)) {
1943
0
            TokenStream token_stream { declaration.value };
1944
0
            token_stream.discard_whitespace();
1945
1946
0
            auto const& inherits_token = token_stream.consume_a_token();
1947
0
            if (inherits_token.is_ident("true"sv) || inherits_token.is_ident("false"sv)) {
1948
0
                auto const& ident = inherits_token.token().ident();
1949
0
                token_stream.discard_whitespace();
1950
0
                if (token_stream.has_next_token()) {
1951
0
                    dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected trailing tokens in inherits");
1952
0
                } else {
1953
0
                    inherits_maybe = (ident == "true");
1954
0
                }
1955
0
            } else {
1956
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Expected true/false for @property \"inherits\" value, got: {}; discarding.", inherits_token.to_debug_string());
1957
0
            }
1958
0
            return;
1959
0
        }
1960
0
        if (declaration.name.equals_ignoring_ascii_case("initial-value"sv)) {
1961
            // FIXME: Ensure that the initial value matches the syntax, and parse the correct CSSValue out
1962
0
            StringBuilder initial_value_sb;
1963
0
            for (auto const& component : declaration.value) {
1964
0
                initial_value_sb.append(component.to_string());
1965
0
            }
1966
0
            initial_value_maybe = MUST(initial_value_sb.to_string());
1967
0
            return;
1968
0
        }
1969
0
    });
1970
1971
0
    if (syntax_maybe.has_value() && inherits_maybe.has_value()) {
1972
0
        return CSSPropertyRule::create(m_context.realm(), name, syntax_maybe.value(), inherits_maybe.value(), std::move(initial_value_maybe));
1973
0
    }
1974
0
    return {};
1975
0
}
1976
1977
Parser::PropertiesAndCustomProperties Parser::extract_properties(Vector<RuleOrListOfDeclarations> const& rules_and_lists_of_declarations)
1978
0
{
1979
0
    PropertiesAndCustomProperties result;
1980
0
    for (auto const& rule_or_list : rules_and_lists_of_declarations) {
1981
0
        if (rule_or_list.has<Rule>())
1982
0
            continue;
1983
1984
0
        auto& declarations = rule_or_list.get<Vector<Declaration>>();
1985
0
        PropertiesAndCustomProperties& dest = result;
1986
0
        for (auto const& declaration : declarations) {
1987
0
            extract_property(declaration, dest);
1988
0
        }
1989
0
    }
1990
0
    return result;
1991
0
}
1992
1993
void Parser::extract_property(Declaration const& declaration, PropertiesAndCustomProperties& dest)
1994
0
{
1995
0
    if (auto maybe_property = convert_to_style_property(declaration); maybe_property.has_value()) {
1996
0
        auto property = maybe_property.release_value();
1997
0
        if (property.property_id == PropertyID::Custom) {
1998
0
            dest.custom_properties.set(property.custom_name, property);
1999
0
        } else {
2000
0
            dest.properties.append(move(property));
2001
0
        }
2002
0
    }
2003
0
}
2004
2005
PropertyOwningCSSStyleDeclaration* Parser::convert_to_style_declaration(Vector<Declaration> const& declarations)
2006
0
{
2007
0
    PropertiesAndCustomProperties properties;
2008
0
    PropertiesAndCustomProperties& dest = properties;
2009
0
    for (auto const& declaration : declarations) {
2010
0
        extract_property(declaration, dest);
2011
0
    }
2012
0
    return PropertyOwningCSSStyleDeclaration::create(m_context.realm(), move(properties.properties), move(properties.custom_properties));
2013
0
}
2014
2015
Optional<StyleProperty> Parser::convert_to_style_property(Declaration const& declaration)
2016
0
{
2017
0
    auto const& property_name = declaration.name;
2018
0
    auto property_id = property_id_from_string(property_name);
2019
2020
0
    if (!property_id.has_value()) {
2021
0
        if (property_name.bytes_as_string_view().starts_with("--"sv)) {
2022
0
            property_id = PropertyID::Custom;
2023
0
        } else if (has_ignored_vendor_prefix(property_name)) {
2024
0
            return {};
2025
0
        } else if (!property_name.bytes_as_string_view().starts_with('-')) {
2026
0
            dbgln_if(CSS_PARSER_DEBUG, "Unrecognized CSS property '{}'", property_name);
2027
0
            return {};
2028
0
        }
2029
0
    }
2030
2031
0
    auto value_token_stream = TokenStream(declaration.value);
2032
0
    auto value = parse_css_value(property_id.value(), value_token_stream, declaration.original_text);
2033
0
    if (value.is_error()) {
2034
0
        if (value.error() == ParseError::SyntaxError) {
2035
0
            dbgln_if(CSS_PARSER_DEBUG, "Unable to parse value for CSS property '{}'.", property_name);
2036
            if constexpr (CSS_PARSER_DEBUG) {
2037
                value_token_stream.dump_all_tokens();
2038
            }
2039
0
        }
2040
0
        return {};
2041
0
    }
2042
2043
0
    if (property_id.value() == PropertyID::Custom)
2044
0
        return StyleProperty { declaration.important, property_id.value(), value.release_value(), declaration.name };
2045
2046
0
    return StyleProperty { declaration.important, property_id.value(), value.release_value(), {} };
2047
0
}
2048
2049
RefPtr<CSSStyleValue> Parser::parse_builtin_value(TokenStream<ComponentValue>& tokens)
2050
0
{
2051
0
    auto transaction = tokens.begin_transaction();
2052
0
    auto& component_value = tokens.consume_a_token();
2053
0
    if (component_value.is(Token::Type::Ident)) {
2054
0
        auto ident = component_value.token().ident();
2055
0
        if (ident.equals_ignoring_ascii_case("inherit"sv)) {
2056
0
            transaction.commit();
2057
0
            return CSSKeywordValue::create(Keyword::Inherit);
2058
0
        }
2059
0
        if (ident.equals_ignoring_ascii_case("initial"sv)) {
2060
0
            transaction.commit();
2061
0
            return CSSKeywordValue::create(Keyword::Initial);
2062
0
        }
2063
0
        if (ident.equals_ignoring_ascii_case("unset"sv)) {
2064
0
            transaction.commit();
2065
0
            return CSSKeywordValue::create(Keyword::Unset);
2066
0
        }
2067
0
        if (ident.equals_ignoring_ascii_case("revert"sv)) {
2068
0
            transaction.commit();
2069
0
            return CSSKeywordValue::create(Keyword::Revert);
2070
0
        }
2071
0
        if (ident.equals_ignoring_ascii_case("revert-layer"sv)) {
2072
0
            transaction.commit();
2073
0
            return CSSKeywordValue::create(Keyword::RevertLayer);
2074
0
        }
2075
0
    }
2076
2077
0
    return nullptr;
2078
0
}
2079
2080
// https://www.w3.org/TR/css-values-4/#custom-idents
2081
RefPtr<CustomIdentStyleValue> Parser::parse_custom_ident_value(TokenStream<ComponentValue>& tokens, std::initializer_list<StringView> blacklist)
2082
0
{
2083
0
    auto transaction = tokens.begin_transaction();
2084
0
    tokens.discard_whitespace();
2085
2086
0
    auto token = tokens.consume_a_token();
2087
0
    if (!token.is(Token::Type::Ident))
2088
0
        return nullptr;
2089
0
    auto custom_ident = token.token().ident();
2090
2091
    // The CSS-wide keywords are not valid <custom-ident>s.
2092
0
    if (is_css_wide_keyword(custom_ident))
2093
0
        return nullptr;
2094
2095
    // The default keyword is reserved and is also not a valid <custom-ident>.
2096
0
    if (custom_ident.equals_ignoring_ascii_case("default"sv))
2097
0
        return nullptr;
2098
2099
    // Specifications using <custom-ident> must specify clearly what other keywords are excluded from <custom-ident>,
2100
    // if any—for example by saying that any pre-defined keywords in that property’s value definition are excluded.
2101
    // Excluded keywords are excluded in all ASCII case permutations.
2102
0
    for (auto& value : blacklist) {
2103
0
        if (custom_ident.equals_ignoring_ascii_case(value))
2104
0
            return nullptr;
2105
0
    }
2106
2107
0
    transaction.commit();
2108
0
    return CustomIdentStyleValue::create(custom_ident);
2109
0
}
2110
2111
RefPtr<CSSMathValue> Parser::parse_calculated_value(ComponentValue const& component_value)
2112
0
{
2113
0
    if (!component_value.is_function())
2114
0
        return nullptr;
2115
2116
0
    auto const& function = component_value.function();
2117
2118
0
    auto function_node = parse_a_calc_function_node(function);
2119
0
    if (!function_node)
2120
0
        return nullptr;
2121
2122
0
    auto function_type = function_node->determine_type(m_context.current_property_id());
2123
0
    if (!function_type.has_value())
2124
0
        return nullptr;
2125
2126
0
    return CSSMathValue::create(function_node.release_nonnull(), function_type.release_value());
2127
0
}
2128
2129
OwnPtr<CalculationNode> Parser::parse_a_calc_function_node(Function const& function)
2130
0
{
2131
0
    if (function.name.equals_ignoring_ascii_case("calc"sv))
2132
0
        return parse_a_calculation(function.value);
2133
2134
0
    if (auto maybe_function = parse_math_function(m_context.current_property_id(), function))
2135
0
        return maybe_function;
2136
2137
0
    return nullptr;
2138
0
}
2139
2140
Optional<Dimension> Parser::parse_dimension(ComponentValue const& component_value)
2141
0
{
2142
0
    if (component_value.is(Token::Type::Dimension)) {
2143
0
        auto numeric_value = component_value.token().dimension_value();
2144
0
        auto unit_string = component_value.token().dimension_unit();
2145
2146
0
        if (auto length_type = Length::unit_from_name(unit_string); length_type.has_value())
2147
0
            return Length { numeric_value, length_type.release_value() };
2148
2149
0
        if (auto angle_type = Angle::unit_from_name(unit_string); angle_type.has_value())
2150
0
            return Angle { numeric_value, angle_type.release_value() };
2151
2152
0
        if (auto flex_type = Flex::unit_from_name(unit_string); flex_type.has_value())
2153
0
            return Flex { numeric_value, flex_type.release_value() };
2154
2155
0
        if (auto frequency_type = Frequency::unit_from_name(unit_string); frequency_type.has_value())
2156
0
            return Frequency { numeric_value, frequency_type.release_value() };
2157
2158
0
        if (auto resolution_type = Resolution::unit_from_name(unit_string); resolution_type.has_value())
2159
0
            return Resolution { numeric_value, resolution_type.release_value() };
2160
2161
0
        if (auto time_type = Time::unit_from_name(unit_string); time_type.has_value())
2162
0
            return Time { numeric_value, time_type.release_value() };
2163
0
    }
2164
2165
0
    if (component_value.is(Token::Type::Percentage))
2166
0
        return Percentage { component_value.token().percentage() };
2167
2168
0
    if (component_value.is(Token::Type::Number)) {
2169
0
        auto numeric_value = component_value.token().number_value();
2170
0
        if (numeric_value == 0)
2171
0
            return Length::make_px(0);
2172
0
        if (m_context.in_quirks_mode() && property_has_quirk(m_context.current_property_id(), Quirk::UnitlessLength)) {
2173
            // https://quirks.spec.whatwg.org/#quirky-length-value
2174
            // FIXME: Disallow quirk when inside a CSS sub-expression (like `calc()`)
2175
            // "The <quirky-length> value must not be supported in arguments to CSS expressions other than the rect()
2176
            // expression, and must not be supported in the supports() static method of the CSS interface."
2177
0
            return Length::make_px(CSSPixels::nearest_value_for(numeric_value));
2178
0
        }
2179
0
    }
2180
2181
0
    return {};
2182
0
}
2183
2184
Optional<AngleOrCalculated> Parser::parse_angle(TokenStream<ComponentValue>& tokens)
2185
0
{
2186
0
    auto transaction = tokens.begin_transaction();
2187
0
    auto& token = tokens.consume_a_token();
2188
2189
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2190
0
        if (dimension->is_angle()) {
2191
0
            transaction.commit();
2192
0
            return dimension->angle();
2193
0
        }
2194
0
        return {};
2195
0
    }
2196
2197
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_angle()) {
2198
0
        transaction.commit();
2199
0
        return calc.release_nonnull();
2200
0
    }
2201
2202
0
    return {};
2203
0
}
2204
2205
Optional<AnglePercentage> Parser::parse_angle_percentage(TokenStream<ComponentValue>& tokens)
2206
0
{
2207
0
    auto transaction = tokens.begin_transaction();
2208
0
    auto& token = tokens.consume_a_token();
2209
2210
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2211
0
        if (dimension->is_angle_percentage()) {
2212
0
            transaction.commit();
2213
0
            return dimension->angle_percentage();
2214
0
        }
2215
0
        return {};
2216
0
    }
2217
2218
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_angle_percentage()) {
2219
0
        transaction.commit();
2220
0
        return calc.release_nonnull();
2221
0
    }
2222
2223
0
    return {};
2224
0
}
2225
2226
Optional<FlexOrCalculated> Parser::parse_flex(TokenStream<ComponentValue>& tokens)
2227
0
{
2228
0
    auto transaction = tokens.begin_transaction();
2229
0
    auto& token = tokens.consume_a_token();
2230
2231
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2232
0
        if (dimension->is_flex()) {
2233
0
            transaction.commit();
2234
0
            return dimension->flex();
2235
0
        }
2236
0
        return {};
2237
0
    }
2238
2239
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_flex()) {
2240
0
        transaction.commit();
2241
0
        return calc.release_nonnull();
2242
0
    }
2243
2244
0
    return {};
2245
0
}
2246
2247
Optional<FrequencyOrCalculated> Parser::parse_frequency(TokenStream<ComponentValue>& tokens)
2248
0
{
2249
0
    auto transaction = tokens.begin_transaction();
2250
0
    auto& token = tokens.consume_a_token();
2251
2252
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2253
0
        if (dimension->is_frequency()) {
2254
0
            transaction.commit();
2255
0
            return dimension->frequency();
2256
0
        }
2257
0
        return {};
2258
0
    }
2259
2260
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_frequency()) {
2261
0
        transaction.commit();
2262
0
        return calc.release_nonnull();
2263
0
    }
2264
2265
0
    return {};
2266
0
}
2267
2268
Optional<FrequencyPercentage> Parser::parse_frequency_percentage(TokenStream<ComponentValue>& tokens)
2269
0
{
2270
0
    auto transaction = tokens.begin_transaction();
2271
0
    auto& token = tokens.consume_a_token();
2272
2273
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2274
0
        if (dimension->is_frequency_percentage()) {
2275
0
            transaction.commit();
2276
0
            return dimension->frequency_percentage();
2277
0
        }
2278
0
        return {};
2279
0
    }
2280
2281
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_frequency_percentage()) {
2282
0
        transaction.commit();
2283
0
        return calc.release_nonnull();
2284
0
    }
2285
2286
0
    return {};
2287
0
}
2288
2289
Optional<IntegerOrCalculated> Parser::parse_integer(TokenStream<ComponentValue>& tokens)
2290
0
{
2291
0
    auto transaction = tokens.begin_transaction();
2292
0
    auto& token = tokens.consume_a_token();
2293
2294
0
    if (token.is(Token::Type::Number) && token.token().number().is_integer()) {
2295
0
        transaction.commit();
2296
0
        return token.token().to_integer();
2297
0
    }
2298
2299
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_number()) {
2300
0
        transaction.commit();
2301
0
        return calc.release_nonnull();
2302
0
    }
2303
2304
0
    return {};
2305
0
}
2306
2307
Optional<LengthOrCalculated> Parser::parse_length(TokenStream<ComponentValue>& tokens)
2308
0
{
2309
0
    auto transaction = tokens.begin_transaction();
2310
0
    auto& token = tokens.consume_a_token();
2311
2312
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2313
0
        if (dimension->is_length()) {
2314
0
            transaction.commit();
2315
0
            return dimension->length();
2316
0
        }
2317
0
        return {};
2318
0
    }
2319
2320
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_length()) {
2321
0
        transaction.commit();
2322
0
        return calc.release_nonnull();
2323
0
    }
2324
2325
0
    return {};
2326
0
}
2327
2328
Optional<LengthPercentage> Parser::parse_length_percentage(TokenStream<ComponentValue>& tokens)
2329
0
{
2330
0
    auto transaction = tokens.begin_transaction();
2331
0
    auto& token = tokens.consume_a_token();
2332
2333
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2334
0
        if (dimension->is_length_percentage()) {
2335
0
            transaction.commit();
2336
0
            return dimension->length_percentage();
2337
0
        }
2338
0
        return {};
2339
0
    }
2340
2341
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_length_percentage()) {
2342
0
        transaction.commit();
2343
0
        return calc.release_nonnull();
2344
0
    }
2345
2346
0
    return {};
2347
0
}
2348
2349
Optional<NumberOrCalculated> Parser::parse_number(TokenStream<ComponentValue>& tokens)
2350
0
{
2351
0
    auto transaction = tokens.begin_transaction();
2352
0
    auto& token = tokens.consume_a_token();
2353
2354
0
    if (token.is(Token::Type::Number)) {
2355
0
        transaction.commit();
2356
0
        return token.token().number_value();
2357
0
    }
2358
2359
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_number()) {
2360
0
        transaction.commit();
2361
0
        return calc.release_nonnull();
2362
0
    }
2363
2364
0
    return {};
2365
0
}
2366
2367
Optional<ResolutionOrCalculated> Parser::parse_resolution(TokenStream<ComponentValue>& tokens)
2368
0
{
2369
0
    auto transaction = tokens.begin_transaction();
2370
0
    auto& token = tokens.consume_a_token();
2371
2372
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2373
0
        if (dimension->is_resolution()) {
2374
0
            transaction.commit();
2375
0
            return dimension->resolution();
2376
0
        }
2377
0
        return {};
2378
0
    }
2379
2380
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_resolution()) {
2381
0
        transaction.commit();
2382
0
        return calc.release_nonnull();
2383
0
    }
2384
2385
0
    return {};
2386
0
}
2387
2388
Optional<TimeOrCalculated> Parser::parse_time(TokenStream<ComponentValue>& tokens)
2389
0
{
2390
0
    auto transaction = tokens.begin_transaction();
2391
0
    auto& token = tokens.consume_a_token();
2392
2393
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2394
0
        if (dimension->is_time()) {
2395
0
            transaction.commit();
2396
0
            return dimension->time();
2397
0
        }
2398
0
        return {};
2399
0
    }
2400
2401
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_time()) {
2402
0
        transaction.commit();
2403
0
        return calc.release_nonnull();
2404
0
    }
2405
2406
0
    return {};
2407
0
}
2408
2409
Optional<TimePercentage> Parser::parse_time_percentage(TokenStream<ComponentValue>& tokens)
2410
0
{
2411
0
    auto transaction = tokens.begin_transaction();
2412
0
    auto& token = tokens.consume_a_token();
2413
2414
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2415
0
        if (dimension->is_time_percentage()) {
2416
0
            transaction.commit();
2417
0
            return dimension->time_percentage();
2418
0
        }
2419
0
        return {};
2420
0
    }
2421
2422
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_time_percentage()) {
2423
0
        transaction.commit();
2424
0
        return calc.release_nonnull();
2425
0
    }
2426
2427
0
    return {};
2428
0
}
2429
2430
Optional<LengthOrCalculated> Parser::parse_source_size_value(TokenStream<ComponentValue>& tokens)
2431
0
{
2432
0
    if (tokens.next_token().is_ident("auto"sv)) {
2433
0
        tokens.discard_a_token(); // auto
2434
0
        return LengthOrCalculated { Length::make_auto() };
2435
0
    }
2436
2437
0
    return parse_length(tokens);
2438
0
}
2439
2440
Optional<Ratio> Parser::parse_ratio(TokenStream<ComponentValue>& tokens)
2441
0
{
2442
0
    auto transaction = tokens.begin_transaction();
2443
0
    tokens.discard_whitespace();
2444
2445
0
    auto read_number_value = [this](ComponentValue const& component_value) -> Optional<double> {
2446
0
        if (component_value.is(Token::Type::Number)) {
2447
0
            return component_value.token().number_value();
2448
0
        } else if (component_value.is_function()) {
2449
0
            auto maybe_calc = parse_calculated_value(component_value);
2450
0
            if (!maybe_calc || !maybe_calc->resolves_to_number())
2451
0
                return {};
2452
0
            if (auto resolved_number = maybe_calc->resolve_number(); resolved_number.has_value() && resolved_number.value() >= 0) {
2453
0
                return resolved_number.value();
2454
0
            }
2455
0
        }
2456
0
        return {};
2457
0
    };
2458
2459
    // `<ratio> = <number [0,∞]> [ / <number [0,∞]> ]?`
2460
0
    auto maybe_numerator = read_number_value(tokens.consume_a_token());
2461
0
    if (!maybe_numerator.has_value() || maybe_numerator.value() < 0)
2462
0
        return {};
2463
0
    auto numerator = maybe_numerator.value();
2464
2465
0
    {
2466
0
        auto two_value_transaction = tokens.begin_transaction();
2467
0
        tokens.discard_whitespace();
2468
0
        auto solidus = tokens.consume_a_token();
2469
0
        tokens.discard_whitespace();
2470
0
        auto maybe_denominator = read_number_value(tokens.consume_a_token());
2471
2472
0
        if (solidus.is_delim('/') && maybe_denominator.has_value() && maybe_denominator.value() >= 0) {
2473
0
            auto denominator = maybe_denominator.value();
2474
            // Two-value ratio
2475
0
            two_value_transaction.commit();
2476
0
            transaction.commit();
2477
0
            return Ratio { numerator, denominator };
2478
0
        }
2479
0
    }
2480
2481
    // Single-value ratio
2482
0
    transaction.commit();
2483
0
    return Ratio { numerator };
2484
0
}
2485
2486
// https://www.w3.org/TR/css-syntax-3/#urange-syntax
2487
Optional<Gfx::UnicodeRange> Parser::parse_unicode_range(TokenStream<ComponentValue>& tokens)
2488
0
{
2489
0
    auto transaction = tokens.begin_transaction();
2490
0
    tokens.discard_whitespace();
2491
2492
    // <urange> =
2493
    //  u '+' <ident-token> '?'* |
2494
    //  u <dimension-token> '?'* |
2495
    //  u <number-token> '?'* |
2496
    //  u <number-token> <dimension-token> |
2497
    //  u <number-token> <number-token> |
2498
    //  u '+' '?'+
2499
    // (All with no whitespace in between tokens.)
2500
2501
    // NOTE: Parsing this is different from usual. We take these steps:
2502
    // 1. Match the grammar above against the tokens, concatenating them into a string using their original representation.
2503
    // 2. Then, parse that string according to the spec algorithm.
2504
    // Step 2 is performed by calling the other parse_unicode_range() overload.
2505
2506
0
    auto is_ending_token = [](ComponentValue const& component_value) {
2507
0
        return component_value.is(Token::Type::EndOfFile)
2508
0
            || component_value.is(Token::Type::Comma)
2509
0
            || component_value.is(Token::Type::Semicolon)
2510
0
            || component_value.is(Token::Type::Whitespace);
2511
0
    };
2512
2513
0
    auto create_unicode_range = [&](StringView text, auto& local_transaction) -> Optional<Gfx::UnicodeRange> {
2514
0
        auto maybe_unicode_range = parse_unicode_range(text);
2515
0
        if (maybe_unicode_range.has_value()) {
2516
0
            local_transaction.commit();
2517
0
            transaction.commit();
2518
0
        }
2519
0
        return maybe_unicode_range;
2520
0
    };
2521
2522
    // All options start with 'u'/'U'.
2523
0
    auto const& u = tokens.consume_a_token();
2524
0
    if (!u.is_ident("u"sv)) {
2525
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> does not start with 'u'");
2526
0
        return {};
2527
0
    }
2528
2529
0
    auto const& second_token = tokens.consume_a_token();
2530
2531
    //  u '+' <ident-token> '?'* |
2532
    //  u '+' '?'+
2533
0
    if (second_token.is_delim('+')) {
2534
0
        auto local_transaction = tokens.begin_transaction();
2535
0
        StringBuilder string_builder;
2536
0
        string_builder.append(second_token.token().original_source_text());
2537
2538
0
        auto const& third_token = tokens.consume_a_token();
2539
0
        if (third_token.is(Token::Type::Ident) || third_token.is_delim('?')) {
2540
0
            string_builder.append(third_token.token().original_source_text());
2541
0
            while (tokens.next_token().is_delim('?'))
2542
0
                string_builder.append(tokens.consume_a_token().token().original_source_text());
2543
0
            if (is_ending_token(tokens.next_token()))
2544
0
                return create_unicode_range(string_builder.string_view(), local_transaction);
2545
0
        }
2546
0
    }
2547
2548
    //  u <dimension-token> '?'*
2549
0
    if (second_token.is(Token::Type::Dimension)) {
2550
0
        auto local_transaction = tokens.begin_transaction();
2551
0
        StringBuilder string_builder;
2552
0
        string_builder.append(second_token.token().original_source_text());
2553
0
        while (tokens.next_token().is_delim('?'))
2554
0
            string_builder.append(tokens.consume_a_token().token().original_source_text());
2555
0
        if (is_ending_token(tokens.next_token()))
2556
0
            return create_unicode_range(string_builder.string_view(), local_transaction);
2557
0
    }
2558
2559
    //  u <number-token> '?'* |
2560
    //  u <number-token> <dimension-token> |
2561
    //  u <number-token> <number-token>
2562
0
    if (second_token.is(Token::Type::Number)) {
2563
0
        auto local_transaction = tokens.begin_transaction();
2564
0
        StringBuilder string_builder;
2565
0
        string_builder.append(second_token.token().original_source_text());
2566
2567
0
        if (is_ending_token(tokens.next_token()))
2568
0
            return create_unicode_range(string_builder.string_view(), local_transaction);
2569
2570
0
        auto const& third_token = tokens.consume_a_token();
2571
0
        if (third_token.is_delim('?')) {
2572
0
            string_builder.append(third_token.token().original_source_text());
2573
0
            while (tokens.next_token().is_delim('?'))
2574
0
                string_builder.append(tokens.consume_a_token().token().original_source_text());
2575
0
            if (is_ending_token(tokens.next_token()))
2576
0
                return create_unicode_range(string_builder.string_view(), local_transaction);
2577
0
        } else if (third_token.is(Token::Type::Dimension)) {
2578
0
            string_builder.append(third_token.token().original_source_text());
2579
0
            if (is_ending_token(tokens.next_token()))
2580
0
                return create_unicode_range(string_builder.string_view(), local_transaction);
2581
0
        } else if (third_token.is(Token::Type::Number)) {
2582
0
            string_builder.append(third_token.token().original_source_text());
2583
0
            if (is_ending_token(tokens.next_token()))
2584
0
                return create_unicode_range(string_builder.string_view(), local_transaction);
2585
0
        }
2586
0
    }
2587
2588
    if constexpr (CSS_PARSER_DEBUG) {
2589
        dbgln("CSSParser: Tokens did not match <urange> grammar.");
2590
        tokens.dump_all_tokens();
2591
    }
2592
0
    return {};
2593
0
}
2594
2595
Optional<Gfx::UnicodeRange> Parser::parse_unicode_range(StringView text)
2596
0
{
2597
0
    auto make_valid_unicode_range = [&](u32 start_value, u32 end_value) -> Optional<Gfx::UnicodeRange> {
2598
        // https://www.w3.org/TR/css-syntax-3/#maximum-allowed-code-point
2599
0
        constexpr u32 maximum_allowed_code_point = 0x10FFFF;
2600
2601
        // To determine what codepoints the <urange> represents:
2602
        // 1. If end value is greater than the maximum allowed code point,
2603
        //    the <urange> is invalid and a syntax error.
2604
0
        if (end_value > maximum_allowed_code_point) {
2605
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Invalid <urange>: end_value ({}) > maximum ({})", end_value, maximum_allowed_code_point);
2606
0
            return {};
2607
0
        }
2608
2609
        // 2. If start value is greater than end value, the <urange> is invalid and a syntax error.
2610
0
        if (start_value > end_value) {
2611
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Invalid <urange>: start_value ({}) > end_value ({})", start_value, end_value);
2612
0
            return {};
2613
0
        }
2614
2615
        // 3. Otherwise, the <urange> represents a contiguous range of codepoints from start value to end value, inclusive.
2616
0
        return Gfx::UnicodeRange { start_value, end_value };
2617
0
    };
2618
2619
    // 1. Skipping the first u token, concatenate the representations of all the tokens in the production together.
2620
    //    Let this be text.
2621
    // NOTE: The concatenation is already done by the caller.
2622
0
    GenericLexer lexer { text };
2623
2624
    // 2. If the first character of text is U+002B PLUS SIGN, consume it.
2625
    //    Otherwise, this is an invalid <urange>, and this algorithm must exit.
2626
0
    if (lexer.next_is('+')) {
2627
0
        lexer.consume();
2628
0
    } else {
2629
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Second character of <urange> was not '+'; got: '{}'", lexer.consume());
2630
0
        return {};
2631
0
    }
2632
2633
    // 3. Consume as many hex digits from text as possible.
2634
    //    then consume as many U+003F QUESTION MARK (?) code points as possible.
2635
0
    auto start_position = lexer.tell();
2636
0
    auto hex_digits = lexer.consume_while(is_ascii_hex_digit);
2637
0
    auto question_marks = lexer.consume_while([](auto it) { return it == '?'; });
2638
    //    If zero code points were consumed, or more than six code points were consumed,
2639
    //    this is an invalid <urange>, and this algorithm must exit.
2640
0
    size_t consumed_code_points = hex_digits.length() + question_marks.length();
2641
0
    if (consumed_code_points == 0 || consumed_code_points > 6) {
2642
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> start value had {} digits/?s, expected between 1 and 6.", consumed_code_points);
2643
0
        return {};
2644
0
    }
2645
0
    StringView start_value_code_points = text.substring_view(start_position, consumed_code_points);
2646
2647
    //    If any U+003F QUESTION MARK (?) code points were consumed, then:
2648
0
    if (question_marks.length() > 0) {
2649
        // 1. If there are any code points left in text, this is an invalid <urange>,
2650
        //    and this algorithm must exit.
2651
0
        if (lexer.tell_remaining() != 0) {
2652
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> invalid; had {} code points left over.", lexer.tell_remaining());
2653
0
            return {};
2654
0
        }
2655
2656
        // 2. Interpret the consumed code points as a hexadecimal number,
2657
        //    with the U+003F QUESTION MARK (?) code points replaced by U+0030 DIGIT ZERO (0) code points.
2658
        //    This is the start value.
2659
0
        auto start_value_string = start_value_code_points.replace("?"sv, "0"sv, ReplaceMode::All);
2660
0
        auto maybe_start_value = AK::StringUtils::convert_to_uint_from_hex<u32>(start_value_string);
2661
0
        if (!maybe_start_value.has_value()) {
2662
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> ?-converted start value did not parse as hex number.");
2663
0
            return {};
2664
0
        }
2665
0
        u32 start_value = maybe_start_value.release_value();
2666
2667
        // 3. Interpret the consumed code points as a hexadecimal number again,
2668
        //    with the U+003F QUESTION MARK (?) code points replaced by U+0046 LATIN CAPITAL LETTER F (F) code points.
2669
        //    This is the end value.
2670
0
        auto end_value_string = start_value_code_points.replace("?"sv, "F"sv, ReplaceMode::All);
2671
0
        auto maybe_end_value = AK::StringUtils::convert_to_uint_from_hex<u32>(end_value_string);
2672
0
        if (!maybe_end_value.has_value()) {
2673
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> ?-converted end value did not parse as hex number.");
2674
0
            return {};
2675
0
        }
2676
0
        u32 end_value = maybe_end_value.release_value();
2677
2678
        // 4. Exit this algorithm.
2679
0
        return make_valid_unicode_range(start_value, end_value);
2680
0
    }
2681
    //   Otherwise, interpret the consumed code points as a hexadecimal number. This is the start value.
2682
0
    auto maybe_start_value = AK::StringUtils::convert_to_uint_from_hex<u32>(start_value_code_points);
2683
0
    if (!maybe_start_value.has_value()) {
2684
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> start value did not parse as hex number.");
2685
0
        return {};
2686
0
    }
2687
0
    u32 start_value = maybe_start_value.release_value();
2688
2689
    // 4. If there are no code points left in text, The end value is the same as the start value.
2690
    //    Exit this algorithm.
2691
0
    if (lexer.tell_remaining() == 0)
2692
0
        return make_valid_unicode_range(start_value, start_value);
2693
2694
    // 5. If the next code point in text is U+002D HYPHEN-MINUS (-), consume it.
2695
0
    if (lexer.next_is('-')) {
2696
0
        lexer.consume();
2697
0
    }
2698
    //    Otherwise, this is an invalid <urange>, and this algorithm must exit.
2699
0
    else {
2700
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> start and end values not separated by '-'.");
2701
0
        return {};
2702
0
    }
2703
2704
    // 6. Consume as many hex digits as possible from text.
2705
0
    auto end_hex_digits = lexer.consume_while(is_ascii_hex_digit);
2706
2707
    //   If zero hex digits were consumed, or more than 6 hex digits were consumed,
2708
    //   this is an invalid <urange>, and this algorithm must exit.
2709
0
    if (end_hex_digits.length() == 0 || end_hex_digits.length() > 6) {
2710
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> end value had {} digits, expected between 1 and 6.", end_hex_digits.length());
2711
0
        return {};
2712
0
    }
2713
2714
    //   If there are any code points left in text, this is an invalid <urange>, and this algorithm must exit.
2715
0
    if (lexer.tell_remaining() != 0) {
2716
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> invalid; had {} code points left over.", lexer.tell_remaining());
2717
0
        return {};
2718
0
    }
2719
2720
    // 7. Interpret the consumed code points as a hexadecimal number. This is the end value.
2721
0
    auto maybe_end_value = AK::StringUtils::convert_to_uint_from_hex<u32>(end_hex_digits);
2722
0
    if (!maybe_end_value.has_value()) {
2723
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> end value did not parse as hex number.");
2724
0
        return {};
2725
0
    }
2726
0
    u32 end_value = maybe_end_value.release_value();
2727
2728
0
    return make_valid_unicode_range(start_value, end_value);
2729
0
}
2730
2731
Vector<Gfx::UnicodeRange> Parser::parse_unicode_ranges(TokenStream<ComponentValue>& tokens)
2732
0
{
2733
0
    Vector<Gfx::UnicodeRange> unicode_ranges;
2734
0
    auto range_token_lists = parse_a_comma_separated_list_of_component_values(tokens);
2735
0
    for (auto& range_tokens : range_token_lists) {
2736
0
        TokenStream range_token_stream { range_tokens };
2737
0
        auto maybe_unicode_range = parse_unicode_range(range_token_stream);
2738
0
        if (!maybe_unicode_range.has_value()) {
2739
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: unicode-range format invalid; discarding.");
2740
0
            return {};
2741
0
        }
2742
0
        unicode_ranges.append(maybe_unicode_range.release_value());
2743
0
    }
2744
0
    return unicode_ranges;
2745
0
}
2746
2747
RefPtr<CSSStyleValue> Parser::parse_dimension_value(TokenStream<ComponentValue>& tokens)
2748
0
{
2749
0
    if (auto dimension = parse_dimension(tokens.next_token()); dimension.has_value()) {
2750
0
        tokens.discard_a_token(); // dimension
2751
2752
0
        if (dimension->is_angle())
2753
0
            return AngleStyleValue::create(dimension->angle());
2754
0
        if (dimension->is_frequency())
2755
0
            return FrequencyStyleValue::create(dimension->frequency());
2756
0
        if (dimension->is_length())
2757
0
            return LengthStyleValue::create(dimension->length());
2758
0
        if (dimension->is_percentage())
2759
0
            return PercentageStyleValue::create(dimension->percentage());
2760
0
        if (dimension->is_resolution())
2761
0
            return ResolutionStyleValue::create(dimension->resolution());
2762
0
        if (dimension->is_time())
2763
0
            return TimeStyleValue::create(dimension->time());
2764
0
        VERIFY_NOT_REACHED();
2765
0
    }
2766
2767
0
    if (auto calc = parse_calculated_value(tokens.next_token()); calc && calc->resolves_to_dimension()) {
2768
0
        tokens.discard_a_token(); // calc
2769
0
        return calc;
2770
0
    }
2771
2772
0
    return nullptr;
2773
0
}
2774
2775
RefPtr<CSSStyleValue> Parser::parse_integer_value(TokenStream<ComponentValue>& tokens)
2776
0
{
2777
0
    auto peek_token = tokens.next_token();
2778
0
    if (peek_token.is(Token::Type::Number) && peek_token.token().number().is_integer()) {
2779
0
        tokens.discard_a_token(); // integer
2780
0
        return IntegerStyleValue::create(peek_token.token().number().integer_value());
2781
0
    }
2782
0
    if (auto calc = parse_calculated_value(peek_token); calc && calc->resolves_to_number()) {
2783
0
        tokens.discard_a_token(); // calc
2784
0
        return calc;
2785
0
    }
2786
2787
0
    return nullptr;
2788
0
}
2789
2790
RefPtr<CSSStyleValue> Parser::parse_number_value(TokenStream<ComponentValue>& tokens)
2791
0
{
2792
0
    auto peek_token = tokens.next_token();
2793
0
    if (peek_token.is(Token::Type::Number)) {
2794
0
        tokens.discard_a_token(); // number
2795
0
        return NumberStyleValue::create(peek_token.token().number().value());
2796
0
    }
2797
0
    if (auto calc = parse_calculated_value(peek_token); calc && calc->resolves_to_number()) {
2798
0
        tokens.discard_a_token(); // calc
2799
0
        return calc;
2800
0
    }
2801
2802
0
    return nullptr;
2803
0
}
2804
2805
RefPtr<CSSStyleValue> Parser::parse_number_percentage_value(TokenStream<ComponentValue>& tokens)
2806
0
{
2807
0
    auto peek_token = tokens.next_token();
2808
0
    if (peek_token.is(Token::Type::Number)) {
2809
0
        tokens.discard_a_token(); // number
2810
0
        return NumberStyleValue::create(peek_token.token().number().value());
2811
0
    }
2812
0
    if (peek_token.is(Token::Type::Percentage)) {
2813
0
        tokens.discard_a_token(); // percentage
2814
0
        return PercentageStyleValue::create(Percentage(peek_token.token().percentage()));
2815
0
    }
2816
0
    if (auto calc = parse_calculated_value(peek_token); calc && calc->resolves_to_number_percentage()) {
2817
0
        tokens.discard_a_token(); // calc
2818
0
        return calc;
2819
0
    }
2820
2821
0
    return nullptr;
2822
0
}
2823
2824
RefPtr<CSSStyleValue> Parser::parse_percentage_value(TokenStream<ComponentValue>& tokens)
2825
0
{
2826
0
    auto peek_token = tokens.next_token();
2827
0
    if (peek_token.is(Token::Type::Percentage)) {
2828
0
        tokens.discard_a_token(); // percentage
2829
0
        return PercentageStyleValue::create(Percentage(peek_token.token().percentage()));
2830
0
    }
2831
0
    if (auto calc = parse_calculated_value(peek_token); calc && calc->resolves_to_percentage()) {
2832
0
        tokens.discard_a_token(); // calc
2833
0
        return calc;
2834
0
    }
2835
2836
0
    return nullptr;
2837
0
}
2838
2839
RefPtr<CSSStyleValue> Parser::parse_angle_value(TokenStream<ComponentValue>& tokens)
2840
0
{
2841
0
    auto transaction = tokens.begin_transaction();
2842
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2843
0
        if (dimension_value->is_angle()
2844
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_angle())) {
2845
0
            transaction.commit();
2846
0
            return dimension_value;
2847
0
        }
2848
0
    }
2849
0
    return nullptr;
2850
0
}
2851
2852
RefPtr<CSSStyleValue> Parser::parse_angle_percentage_value(TokenStream<ComponentValue>& tokens)
2853
0
{
2854
0
    auto transaction = tokens.begin_transaction();
2855
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2856
0
        if (dimension_value->is_angle() || dimension_value->is_percentage()
2857
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_angle_percentage())) {
2858
0
            transaction.commit();
2859
0
            return dimension_value;
2860
0
        }
2861
0
    }
2862
0
    return nullptr;
2863
0
}
2864
2865
RefPtr<CSSStyleValue> Parser::parse_flex_value(TokenStream<ComponentValue>& tokens)
2866
0
{
2867
0
    auto transaction = tokens.begin_transaction();
2868
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2869
0
        if (dimension_value->is_flex()
2870
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_flex())) {
2871
0
            transaction.commit();
2872
0
            return dimension_value;
2873
0
        }
2874
0
    }
2875
0
    return nullptr;
2876
0
}
2877
2878
RefPtr<CSSStyleValue> Parser::parse_frequency_value(TokenStream<ComponentValue>& tokens)
2879
0
{
2880
0
    auto transaction = tokens.begin_transaction();
2881
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2882
0
        if (dimension_value->is_frequency()
2883
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_frequency())) {
2884
0
            transaction.commit();
2885
0
            return dimension_value;
2886
0
        }
2887
0
    }
2888
0
    return nullptr;
2889
0
}
2890
2891
RefPtr<CSSStyleValue> Parser::parse_frequency_percentage_value(TokenStream<ComponentValue>& tokens)
2892
0
{
2893
0
    auto transaction = tokens.begin_transaction();
2894
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2895
0
        if (dimension_value->is_frequency() || dimension_value->is_percentage()
2896
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_frequency_percentage())) {
2897
0
            transaction.commit();
2898
0
            return dimension_value;
2899
0
        }
2900
0
    }
2901
0
    return nullptr;
2902
0
}
2903
2904
RefPtr<CSSStyleValue> Parser::parse_length_value(TokenStream<ComponentValue>& tokens)
2905
0
{
2906
0
    auto transaction = tokens.begin_transaction();
2907
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2908
0
        if (dimension_value->is_length()
2909
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_length())) {
2910
0
            transaction.commit();
2911
0
            return dimension_value;
2912
0
        }
2913
0
    }
2914
0
    return nullptr;
2915
0
}
2916
2917
RefPtr<CSSStyleValue> Parser::parse_length_percentage_value(TokenStream<ComponentValue>& tokens)
2918
0
{
2919
0
    auto transaction = tokens.begin_transaction();
2920
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2921
0
        if (dimension_value->is_length() || dimension_value->is_percentage()
2922
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_length_percentage())) {
2923
0
            transaction.commit();
2924
0
            return dimension_value;
2925
0
        }
2926
0
    }
2927
0
    return nullptr;
2928
0
}
2929
2930
RefPtr<CSSStyleValue> Parser::parse_resolution_value(TokenStream<ComponentValue>& tokens)
2931
0
{
2932
0
    auto transaction = tokens.begin_transaction();
2933
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2934
0
        if (dimension_value->is_resolution()
2935
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_resolution())) {
2936
0
            transaction.commit();
2937
0
            return dimension_value;
2938
0
        }
2939
0
    }
2940
0
    return nullptr;
2941
0
}
2942
2943
RefPtr<CSSStyleValue> Parser::parse_time_value(TokenStream<ComponentValue>& tokens)
2944
0
{
2945
0
    auto transaction = tokens.begin_transaction();
2946
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2947
0
        if (dimension_value->is_time()
2948
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_time())) {
2949
0
            transaction.commit();
2950
0
            return dimension_value;
2951
0
        }
2952
0
    }
2953
0
    return nullptr;
2954
0
}
2955
2956
RefPtr<CSSStyleValue> Parser::parse_time_percentage_value(TokenStream<ComponentValue>& tokens)
2957
0
{
2958
0
    auto transaction = tokens.begin_transaction();
2959
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2960
0
        if (dimension_value->is_time() || dimension_value->is_percentage()
2961
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_time_percentage())) {
2962
0
            transaction.commit();
2963
0
            return dimension_value;
2964
0
        }
2965
0
    }
2966
0
    return nullptr;
2967
0
}
2968
2969
RefPtr<CSSStyleValue> Parser::parse_keyword_value(TokenStream<ComponentValue>& tokens)
2970
0
{
2971
0
    auto peek_token = tokens.next_token();
2972
0
    if (peek_token.is(Token::Type::Ident)) {
2973
0
        auto keyword = keyword_from_string(peek_token.token().ident());
2974
0
        if (keyword.has_value()) {
2975
0
            tokens.discard_a_token(); // ident
2976
0
            return CSSKeywordValue::create(keyword.value());
2977
0
        }
2978
0
    }
2979
2980
0
    return nullptr;
2981
0
}
2982
2983
// https://www.w3.org/TR/CSS2/visufx.html#value-def-shape
2984
RefPtr<CSSStyleValue> Parser::parse_rect_value(TokenStream<ComponentValue>& tokens)
2985
0
{
2986
0
    auto transaction = tokens.begin_transaction();
2987
0
    auto function_token = tokens.consume_a_token();
2988
0
    if (!function_token.is_function("rect"sv))
2989
0
        return nullptr;
2990
2991
0
    Vector<Length, 4> params;
2992
0
    auto argument_tokens = TokenStream { function_token.function().value };
2993
2994
0
    enum class CommaRequirement {
2995
0
        Unknown,
2996
0
        RequiresCommas,
2997
0
        RequiresNoCommas
2998
0
    };
2999
3000
0
    enum class Side {
3001
0
        Top = 0,
3002
0
        Right = 1,
3003
0
        Bottom = 2,
3004
0
        Left = 3
3005
0
    };
3006
3007
0
    auto comma_requirement = CommaRequirement::Unknown;
3008
3009
    // In CSS 2.1, the only valid <shape> value is: rect(<top>, <right>, <bottom>, <left>) where
3010
    // <top> and <bottom> specify offsets from the top border edge of the box, and <right>, and
3011
    //  <left> specify offsets from the left border edge of the box.
3012
0
    for (size_t side = 0; side < 4; side++) {
3013
0
        argument_tokens.discard_whitespace();
3014
3015
        // <top>, <right>, <bottom>, and <left> may either have a <length> value or 'auto'.
3016
        // Negative lengths are permitted.
3017
0
        if (argument_tokens.next_token().is_ident("auto"sv)) {
3018
0
            (void)argument_tokens.consume_a_token(); // `auto`
3019
0
            params.append(Length::make_auto());
3020
0
        } else {
3021
0
            auto maybe_length = parse_length(argument_tokens);
3022
0
            if (!maybe_length.has_value())
3023
0
                return nullptr;
3024
0
            if (maybe_length.value().is_calculated()) {
3025
0
                dbgln("FIXME: Support calculated lengths in rect(): {}", maybe_length.value().calculated()->to_string());
3026
0
                return nullptr;
3027
0
            }
3028
0
            params.append(maybe_length.value().value());
3029
0
        }
3030
0
        argument_tokens.discard_whitespace();
3031
3032
        // The last side, should be no more tokens following it.
3033
0
        if (static_cast<Side>(side) == Side::Left) {
3034
0
            if (argument_tokens.has_next_token())
3035
0
                return nullptr;
3036
0
            break;
3037
0
        }
3038
3039
0
        bool next_is_comma = argument_tokens.next_token().is(Token::Type::Comma);
3040
3041
        // Authors should separate offset values with commas. User agents must support separation
3042
        // with commas, but may also support separation without commas (but not a combination),
3043
        // because a previous revision of this specification was ambiguous in this respect.
3044
0
        if (comma_requirement == CommaRequirement::Unknown)
3045
0
            comma_requirement = next_is_comma ? CommaRequirement::RequiresCommas : CommaRequirement::RequiresNoCommas;
3046
3047
0
        if (comma_requirement == CommaRequirement::RequiresCommas) {
3048
0
            if (next_is_comma)
3049
0
                argument_tokens.discard_a_token();
3050
0
            else
3051
0
                return nullptr;
3052
0
        } else if (comma_requirement == CommaRequirement::RequiresNoCommas) {
3053
0
            if (next_is_comma)
3054
0
                return nullptr;
3055
0
        } else {
3056
0
            VERIFY_NOT_REACHED();
3057
0
        }
3058
0
    }
3059
3060
0
    transaction.commit();
3061
0
    return RectStyleValue::create(EdgeRect { params[0], params[1], params[2], params[3] });
3062
0
}
3063
3064
// https://www.w3.org/TR/css-color-4/#typedef-hue
3065
RefPtr<CSSStyleValue> Parser::parse_hue_value(TokenStream<ComponentValue>& tokens)
3066
0
{
3067
    // <hue> = <number> | <angle>
3068
0
    if (auto number = parse_number_value(tokens))
3069
0
        return number;
3070
0
    if (auto angle = parse_angle_value(tokens))
3071
0
        return angle;
3072
3073
0
    return nullptr;
3074
0
}
3075
3076
RefPtr<CSSStyleValue> Parser::parse_solidus_and_alpha_value(TokenStream<ComponentValue>& tokens)
3077
0
{
3078
    // [ / [<alpha-value> | none] ]?
3079
    // Common to the modern-syntax color functions.
3080
    // TODO: Parse `none`
3081
3082
0
    auto transaction = tokens.begin_transaction();
3083
0
    tokens.discard_whitespace();
3084
0
    if (!tokens.consume_a_token().is_delim('/'))
3085
0
        return {};
3086
0
    tokens.discard_whitespace();
3087
0
    auto alpha = parse_number_percentage_value(tokens);
3088
0
    if (!alpha)
3089
0
        return {};
3090
0
    tokens.discard_whitespace();
3091
3092
0
    transaction.commit();
3093
0
    return alpha;
3094
0
}
3095
3096
// https://www.w3.org/TR/css-color-4/#funcdef-rgb
3097
RefPtr<CSSStyleValue> Parser::parse_rgb_color_value(TokenStream<ComponentValue>& outer_tokens)
3098
0
{
3099
    // rgb() = [ <legacy-rgb-syntax> | <modern-rgb-syntax> ]
3100
    // rgba() = [ <legacy-rgba-syntax> | <modern-rgba-syntax> ]
3101
    // <legacy-rgb-syntax> = rgb( <percentage>#{3} , <alpha-value>? ) |
3102
    //                       rgb( <number>#{3} , <alpha-value>? )
3103
    // <legacy-rgba-syntax> = rgba( <percentage>#{3} , <alpha-value>? ) |
3104
    //                        rgba( <number>#{3} , <alpha-value>? )
3105
    // <modern-rgb-syntax> = rgb(
3106
    //     [ <number> | <percentage> | none]{3}
3107
    //     [ / [<alpha-value> | none] ]?  )
3108
    // <modern-rgba-syntax> = rgba(
3109
    //     [ <number> | <percentage> | none]{3}
3110
    //     [ / [<alpha-value> | none] ]?  )
3111
    // TODO: Handle none values
3112
3113
0
    auto transaction = outer_tokens.begin_transaction();
3114
0
    outer_tokens.discard_whitespace();
3115
3116
0
    auto& function_token = outer_tokens.consume_a_token();
3117
0
    if (!function_token.is_function("rgb"sv) && !function_token.is_function("rgba"sv))
3118
0
        return {};
3119
3120
0
    RefPtr<CSSStyleValue> red;
3121
0
    RefPtr<CSSStyleValue> green;
3122
0
    RefPtr<CSSStyleValue> blue;
3123
0
    RefPtr<CSSStyleValue> alpha;
3124
3125
0
    auto inner_tokens = TokenStream { function_token.function().value };
3126
0
    inner_tokens.discard_whitespace();
3127
3128
0
    red = parse_number_percentage_value(inner_tokens);
3129
0
    if (!red)
3130
0
        return {};
3131
3132
0
    inner_tokens.discard_whitespace();
3133
0
    bool legacy_syntax = inner_tokens.next_token().is(Token::Type::Comma);
3134
0
    if (legacy_syntax) {
3135
        // Legacy syntax
3136
        //   <percentage>#{3} , <alpha-value>?
3137
        //   | <number>#{3} , <alpha-value>?
3138
        // So, r/g/b can be numbers or percentages, as long as they're all the same type.
3139
3140
0
        inner_tokens.discard_a_token(); // comma
3141
0
        inner_tokens.discard_whitespace();
3142
3143
0
        green = parse_number_percentage_value(inner_tokens);
3144
0
        if (!green)
3145
0
            return {};
3146
0
        inner_tokens.discard_whitespace();
3147
3148
0
        if (!inner_tokens.consume_a_token().is(Token::Type::Comma))
3149
0
            return {};
3150
0
        inner_tokens.discard_whitespace();
3151
3152
0
        blue = parse_number_percentage_value(inner_tokens);
3153
0
        if (!blue)
3154
0
            return {};
3155
0
        inner_tokens.discard_whitespace();
3156
3157
0
        if (inner_tokens.has_next_token()) {
3158
            // Try and read comma and alpha
3159
0
            if (!inner_tokens.consume_a_token().is(Token::Type::Comma))
3160
0
                return {};
3161
0
            inner_tokens.discard_whitespace();
3162
3163
0
            alpha = parse_number_percentage_value(inner_tokens);
3164
3165
0
            if (!alpha)
3166
0
                return {};
3167
3168
0
            inner_tokens.discard_whitespace();
3169
3170
0
            if (inner_tokens.has_next_token())
3171
0
                return {};
3172
0
        }
3173
3174
        // Verify we're all percentages or all numbers
3175
0
        auto is_percentage = [](CSSStyleValue const& style_value) {
3176
0
            return style_value.is_percentage()
3177
0
                || (style_value.is_math() && style_value.as_math().resolves_to_percentage());
3178
0
        };
3179
0
        bool red_is_percentage = is_percentage(*red);
3180
0
        bool green_is_percentage = is_percentage(*green);
3181
0
        bool blue_is_percentage = is_percentage(*blue);
3182
0
        if (red_is_percentage != green_is_percentage || red_is_percentage != blue_is_percentage)
3183
0
            return {};
3184
3185
0
    } else {
3186
        // Modern syntax
3187
        //   [ <number> | <percentage> | none]{3}  [ / [<alpha-value> | none] ]?
3188
3189
0
        green = parse_number_percentage_value(inner_tokens);
3190
0
        if (!green)
3191
0
            return {};
3192
0
        inner_tokens.discard_whitespace();
3193
3194
0
        blue = parse_number_percentage_value(inner_tokens);
3195
0
        if (!blue)
3196
0
            return {};
3197
0
        inner_tokens.discard_whitespace();
3198
3199
0
        if (inner_tokens.has_next_token()) {
3200
0
            alpha = parse_solidus_and_alpha_value(inner_tokens);
3201
0
            if (!alpha || inner_tokens.has_next_token())
3202
0
                return {};
3203
0
        }
3204
0
    }
3205
3206
0
    if (!alpha)
3207
0
        alpha = NumberStyleValue::create(1);
3208
3209
0
    transaction.commit();
3210
0
    return CSSRGB::create(red.release_nonnull(), green.release_nonnull(), blue.release_nonnull(), alpha.release_nonnull());
3211
0
}
3212
3213
// https://www.w3.org/TR/css-color-4/#funcdef-hsl
3214
RefPtr<CSSStyleValue> Parser::parse_hsl_color_value(TokenStream<ComponentValue>& outer_tokens)
3215
0
{
3216
    // hsl() = [ <legacy-hsl-syntax> | <modern-hsl-syntax> ]
3217
    // hsla() = [ <legacy-hsla-syntax> | <modern-hsla-syntax> ]
3218
    // <modern-hsl-syntax> = hsl(
3219
    //     [<hue> | none]
3220
    //     [<percentage> | <number> | none]
3221
    //     [<percentage> | <number> | none]
3222
    //     [ / [<alpha-value> | none] ]? )
3223
    // <modern-hsla-syntax> = hsla(
3224
    //     [<hue> | none]
3225
    //     [<percentage> | <number> | none]
3226
    //     [<percentage> | <number> | none]
3227
    //     [ / [<alpha-value> | none] ]? )
3228
    // <legacy-hsl-syntax> = hsl( <hue>, <percentage>, <percentage>, <alpha-value>? )
3229
    // <legacy-hsla-syntax> = hsla( <hue>, <percentage>, <percentage>, <alpha-value>? )
3230
    // TODO: Handle none values
3231
3232
0
    auto transaction = outer_tokens.begin_transaction();
3233
0
    outer_tokens.discard_whitespace();
3234
3235
0
    auto& function_token = outer_tokens.consume_a_token();
3236
0
    if (!function_token.is_function("hsl"sv) && !function_token.is_function("hsla"sv))
3237
0
        return {};
3238
3239
0
    RefPtr<CSSStyleValue> h;
3240
0
    RefPtr<CSSStyleValue> s;
3241
0
    RefPtr<CSSStyleValue> l;
3242
0
    RefPtr<CSSStyleValue> alpha;
3243
3244
0
    auto inner_tokens = TokenStream { function_token.function().value };
3245
0
    inner_tokens.discard_whitespace();
3246
3247
0
    h = parse_hue_value(inner_tokens);
3248
0
    if (!h)
3249
0
        return {};
3250
3251
0
    inner_tokens.discard_whitespace();
3252
0
    bool legacy_syntax = inner_tokens.next_token().is(Token::Type::Comma);
3253
0
    if (legacy_syntax) {
3254
        // Legacy syntax
3255
        //   <hue>, <percentage>, <percentage>, <alpha-value>?
3256
0
        (void)inner_tokens.consume_a_token(); // comma
3257
0
        inner_tokens.discard_whitespace();
3258
3259
0
        s = parse_percentage_value(inner_tokens);
3260
0
        if (!s)
3261
0
            return {};
3262
0
        inner_tokens.discard_whitespace();
3263
3264
0
        if (!inner_tokens.consume_a_token().is(Token::Type::Comma))
3265
0
            return {};
3266
0
        inner_tokens.discard_whitespace();
3267
3268
0
        l = parse_percentage_value(inner_tokens);
3269
0
        if (!l)
3270
0
            return {};
3271
0
        inner_tokens.discard_whitespace();
3272
3273
0
        if (inner_tokens.has_next_token()) {
3274
            // Try and read comma and alpha
3275
0
            if (!inner_tokens.consume_a_token().is(Token::Type::Comma))
3276
0
                return {};
3277
0
            inner_tokens.discard_whitespace();
3278
3279
0
            alpha = parse_number_percentage_value(inner_tokens);
3280
0
            inner_tokens.discard_whitespace();
3281
3282
0
            if (inner_tokens.has_next_token())
3283
0
                return {};
3284
0
        }
3285
0
    } else {
3286
        // Modern syntax
3287
        //   [<hue> | none]
3288
        //   [<percentage> | <number> | none]
3289
        //   [<percentage> | <number> | none]
3290
        //   [ / [<alpha-value> | none] ]?
3291
3292
0
        s = parse_number_percentage_value(inner_tokens);
3293
0
        if (!s)
3294
0
            return {};
3295
0
        inner_tokens.discard_whitespace();
3296
3297
0
        l = parse_number_percentage_value(inner_tokens);
3298
0
        if (!l)
3299
0
            return {};
3300
0
        inner_tokens.discard_whitespace();
3301
3302
0
        if (inner_tokens.has_next_token()) {
3303
0
            alpha = parse_solidus_and_alpha_value(inner_tokens);
3304
0
            if (!alpha || inner_tokens.has_next_token())
3305
0
                return {};
3306
0
        }
3307
0
    }
3308
3309
0
    if (!alpha)
3310
0
        alpha = NumberStyleValue::create(1);
3311
3312
0
    transaction.commit();
3313
0
    return CSSHSL::create(h.release_nonnull(), s.release_nonnull(), l.release_nonnull(), alpha.release_nonnull());
3314
0
}
3315
3316
// https://www.w3.org/TR/css-color-4/#funcdef-hwb
3317
RefPtr<CSSStyleValue> Parser::parse_hwb_color_value(TokenStream<ComponentValue>& outer_tokens)
3318
0
{
3319
    // hwb() = hwb(
3320
    //     [<hue> | none]
3321
    //     [<percentage> | <number> | none]
3322
    //     [<percentage> | <number> | none]
3323
    //     [ / [<alpha-value> | none] ]? )
3324
3325
0
    auto transaction = outer_tokens.begin_transaction();
3326
0
    outer_tokens.discard_whitespace();
3327
3328
0
    auto& function_token = outer_tokens.consume_a_token();
3329
0
    if (!function_token.is_function("hwb"sv))
3330
0
        return {};
3331
3332
0
    RefPtr<CSSStyleValue> h;
3333
0
    RefPtr<CSSStyleValue> w;
3334
0
    RefPtr<CSSStyleValue> b;
3335
0
    RefPtr<CSSStyleValue> alpha;
3336
3337
0
    auto inner_tokens = TokenStream { function_token.function().value };
3338
0
    inner_tokens.discard_whitespace();
3339
3340
0
    h = parse_hue_value(inner_tokens);
3341
0
    if (!h)
3342
0
        return {};
3343
0
    inner_tokens.discard_whitespace();
3344
3345
0
    w = parse_number_percentage_value(inner_tokens);
3346
0
    if (!w)
3347
0
        return {};
3348
0
    inner_tokens.discard_whitespace();
3349
3350
0
    b = parse_number_percentage_value(inner_tokens);
3351
0
    if (!b)
3352
0
        return {};
3353
0
    inner_tokens.discard_whitespace();
3354
3355
0
    if (inner_tokens.has_next_token()) {
3356
0
        alpha = parse_solidus_and_alpha_value(inner_tokens);
3357
0
        if (!alpha || inner_tokens.has_next_token())
3358
0
            return {};
3359
0
    }
3360
3361
0
    if (!alpha)
3362
0
        alpha = NumberStyleValue::create(1);
3363
3364
0
    transaction.commit();
3365
0
    return CSSHWB::create(h.release_nonnull(), w.release_nonnull(), b.release_nonnull(), alpha.release_nonnull());
3366
0
}
3367
3368
Optional<Array<RefPtr<CSSStyleValue>, 4>> Parser::parse_lab_like_color_value(TokenStream<ComponentValue>& outer_tokens, StringView function_name)
3369
0
{
3370
    // This helper is designed to be compatible with lab and oklab and parses a function with a form like:
3371
    // f() = f( [ <percentage> | <number> | none]
3372
    //     [ <percentage> | <number> | none]
3373
    //     [ <percentage> | <number> | none]
3374
    //     [ / [<alpha-value> | none] ]? )
3375
3376
0
    auto transaction = outer_tokens.begin_transaction();
3377
0
    outer_tokens.discard_whitespace();
3378
3379
0
    auto& function_token = outer_tokens.consume_a_token();
3380
0
    if (!function_token.is_function(function_name))
3381
0
        return OptionalNone {};
3382
3383
0
    RefPtr<CSSStyleValue> l;
3384
0
    RefPtr<CSSStyleValue> a;
3385
0
    RefPtr<CSSStyleValue> b;
3386
0
    RefPtr<CSSStyleValue> alpha;
3387
3388
0
    auto inner_tokens = TokenStream { function_token.function().value };
3389
0
    inner_tokens.discard_whitespace();
3390
3391
0
    l = parse_number_percentage_value(inner_tokens);
3392
0
    if (!l)
3393
0
        return OptionalNone {};
3394
0
    inner_tokens.discard_whitespace();
3395
3396
0
    a = parse_number_percentage_value(inner_tokens);
3397
0
    if (!a)
3398
0
        return OptionalNone {};
3399
0
    inner_tokens.discard_whitespace();
3400
3401
0
    b = parse_number_percentage_value(inner_tokens);
3402
0
    if (!b)
3403
0
        return OptionalNone {};
3404
0
    inner_tokens.discard_whitespace();
3405
3406
0
    if (inner_tokens.has_next_token()) {
3407
0
        alpha = parse_solidus_and_alpha_value(inner_tokens);
3408
0
        if (!alpha || inner_tokens.has_next_token())
3409
0
            return OptionalNone {};
3410
0
    }
3411
3412
0
    if (!alpha)
3413
0
        alpha = NumberStyleValue::create(1);
3414
3415
0
    transaction.commit();
3416
3417
0
    return Array { move(l), move(a), move(b), move(alpha) };
3418
0
}
3419
3420
// https://www.w3.org/TR/css-color-4/#funcdef-lab
3421
RefPtr<CSSStyleValue> Parser::parse_lab_color_value(TokenStream<ComponentValue>& outer_tokens)
3422
0
{
3423
    // lab() = lab( [<percentage> | <number> | none]
3424
    //      [ <percentage> | <number> | none]
3425
    //      [ <percentage> | <number> | none]
3426
    //      [ / [<alpha-value> | none] ]? )
3427
3428
0
    auto maybe_color_values = parse_lab_like_color_value(outer_tokens, "lab"sv);
3429
0
    if (!maybe_color_values.has_value())
3430
0
        return {};
3431
3432
0
    auto& color_values = *maybe_color_values;
3433
3434
0
    return CSSLabLike::create<CSSLab>(color_values[0].release_nonnull(),
3435
0
        color_values[1].release_nonnull(),
3436
0
        color_values[2].release_nonnull(),
3437
0
        color_values[3].release_nonnull());
3438
0
}
3439
3440
// https://www.w3.org/TR/css-color-4/#funcdef-oklab
3441
RefPtr<CSSStyleValue> Parser::parse_oklab_color_value(TokenStream<ComponentValue>& outer_tokens)
3442
0
{
3443
    // oklab() = oklab( [ <percentage> | <number> | none]
3444
    //     [ <percentage> | <number> | none]
3445
    //     [ <percentage> | <number> | none]
3446
    //     [ / [<alpha-value> | none] ]? )
3447
3448
0
    auto maybe_color_values = parse_lab_like_color_value(outer_tokens, "oklab"sv);
3449
0
    if (!maybe_color_values.has_value())
3450
0
        return {};
3451
3452
0
    auto& color_values = *maybe_color_values;
3453
3454
0
    return CSSLabLike::create<CSSOKLab>(color_values[0].release_nonnull(),
3455
0
        color_values[1].release_nonnull(),
3456
0
        color_values[2].release_nonnull(),
3457
0
        color_values[3].release_nonnull());
3458
0
}
3459
3460
Optional<Array<RefPtr<CSSStyleValue>, 4>> Parser::parse_lch_like_color_value(TokenStream<ComponentValue>& outer_tokens, StringView function_name)
3461
0
{
3462
    // This helper is designed to be compatible with lch and oklch and parses a function with a form like:
3463
    // f() = f( [<percentage> | <number> | none]
3464
    //     [ <percentage> | <number> | none]
3465
    //     [ <hue> | none]
3466
    //     [ / [<alpha-value> | none] ]? )
3467
3468
0
    auto transaction = outer_tokens.begin_transaction();
3469
0
    outer_tokens.discard_whitespace();
3470
3471
0
    auto const& function_token = outer_tokens.consume_a_token();
3472
0
    if (!function_token.is_function(function_name))
3473
0
        return OptionalNone {};
3474
3475
0
    auto inner_tokens = TokenStream { function_token.function().value };
3476
0
    inner_tokens.discard_whitespace();
3477
3478
0
    auto l = parse_number_percentage_value(inner_tokens);
3479
0
    if (!l)
3480
0
        return OptionalNone {};
3481
0
    inner_tokens.discard_whitespace();
3482
3483
0
    auto c = parse_number_percentage_value(inner_tokens);
3484
0
    if (!c)
3485
0
        return OptionalNone {};
3486
0
    inner_tokens.discard_whitespace();
3487
3488
0
    auto h = parse_hue_value(inner_tokens);
3489
0
    if (!h)
3490
0
        return OptionalNone {};
3491
0
    inner_tokens.discard_whitespace();
3492
3493
0
    RefPtr<CSSStyleValue> alpha;
3494
0
    if (inner_tokens.has_next_token()) {
3495
0
        alpha = parse_solidus_and_alpha_value(inner_tokens);
3496
0
        if (!alpha || inner_tokens.has_next_token())
3497
0
            return OptionalNone {};
3498
0
    }
3499
3500
0
    if (!alpha)
3501
0
        alpha = NumberStyleValue::create(1);
3502
3503
0
    transaction.commit();
3504
3505
0
    return Array { move(l), move(c), move(h), move(alpha) };
3506
0
}
3507
3508
// https://www.w3.org/TR/css-color-4/#funcdef-lch
3509
RefPtr<CSSStyleValue> Parser::parse_lch_color_value(TokenStream<ComponentValue>& outer_tokens)
3510
0
{
3511
    // lch() = lch( [<percentage> | <number> | none]
3512
    //      [ <percentage> | <number> | none]
3513
    //      [ <hue> | none]
3514
    //      [ / [<alpha-value> | none] ]? )
3515
3516
0
    auto maybe_color_values = parse_lch_like_color_value(outer_tokens, "lch"sv);
3517
0
    if (!maybe_color_values.has_value())
3518
0
        return {};
3519
3520
0
    auto& color_values = *maybe_color_values;
3521
3522
0
    return CSSLCHLike::create<CSSLCH>(color_values[0].release_nonnull(),
3523
0
        color_values[1].release_nonnull(),
3524
0
        color_values[2].release_nonnull(),
3525
0
        color_values[3].release_nonnull());
3526
0
}
3527
3528
// https://www.w3.org/TR/css-color-4/#funcdef-oklch
3529
RefPtr<CSSStyleValue> Parser::parse_oklch_color_value(TokenStream<ComponentValue>& outer_tokens)
3530
0
{
3531
    // oklch() = oklch( [ <percentage> | <number> | none]
3532
    //     [ <percentage> | <number> | none]
3533
    //     [ <hue> | none]
3534
    //     [ / [<alpha-value> | none] ]? )
3535
3536
0
    auto maybe_color_values = parse_lch_like_color_value(outer_tokens, "oklch"sv);
3537
0
    if (!maybe_color_values.has_value())
3538
0
        return {};
3539
3540
0
    auto& color_values = *maybe_color_values;
3541
3542
0
    return CSSLCHLike::create<CSSOKLCH>(color_values[0].release_nonnull(),
3543
0
        color_values[1].release_nonnull(),
3544
0
        color_values[2].release_nonnull(),
3545
0
        color_values[3].release_nonnull());
3546
0
}
3547
3548
// https://www.w3.org/TR/css-color-4/#funcdef-color
3549
RefPtr<CSSStyleValue> Parser::parse_color_function(TokenStream<ComponentValue>& outer_tokens)
3550
0
{
3551
    // color() = color( <colorspace-params> [ / [ <alpha-value> | none ] ]? )
3552
    //     <colorspace-params> = [ <predefined-rgb-params> | <xyz-params>]
3553
    //     <predefined-rgb-params> = <predefined-rgb> [ <number> | <percentage> | none ]{3}
3554
    //     <predefined-rgb> = srgb | srgb-linear | display-p3 | a98-rgb | prophoto-rgb | rec2020
3555
    //     <xyz-params> = <xyz-space> [ <number> | <percentage> | none ]{3}
3556
    //     <xyz-space> = xyz | xyz-d50 | xyz-d65
3557
3558
0
    auto transaction = outer_tokens.begin_transaction();
3559
0
    outer_tokens.discard_whitespace();
3560
3561
0
    auto const& function_token = outer_tokens.consume_a_token();
3562
0
    if (!function_token.is_function("color"sv))
3563
0
        return {};
3564
3565
0
    auto inner_tokens = TokenStream { function_token.function().value };
3566
0
    inner_tokens.discard_whitespace();
3567
3568
0
    auto maybe_color_space = inner_tokens.consume_a_token();
3569
0
    inner_tokens.discard_whitespace();
3570
0
    if (!any_of(CSSColor::s_supported_color_space, [&](auto supported) { return maybe_color_space.is_ident(supported); }))
3571
0
        return {};
3572
3573
0
    auto const& color_space = maybe_color_space.token().ident();
3574
3575
0
    auto c1 = parse_number_percentage_value(inner_tokens);
3576
0
    if (!c1)
3577
0
        return {};
3578
0
    inner_tokens.discard_whitespace();
3579
3580
0
    auto c2 = parse_number_percentage_value(inner_tokens);
3581
0
    if (!c2)
3582
0
        return {};
3583
0
    inner_tokens.discard_whitespace();
3584
3585
0
    auto c3 = parse_number_percentage_value(inner_tokens);
3586
0
    if (!c3)
3587
0
        return {};
3588
0
    inner_tokens.discard_whitespace();
3589
3590
0
    RefPtr<CSSStyleValue> alpha;
3591
0
    if (inner_tokens.has_next_token()) {
3592
0
        alpha = parse_solidus_and_alpha_value(inner_tokens);
3593
0
        if (!alpha || inner_tokens.has_next_token())
3594
0
            return {};
3595
0
    }
3596
3597
0
    if (!alpha)
3598
0
        alpha = NumberStyleValue::create(1);
3599
3600
0
    transaction.commit();
3601
0
    return CSSColor::create(color_space.to_ascii_lowercase(),
3602
0
        c1.release_nonnull(),
3603
0
        c2.release_nonnull(),
3604
0
        c3.release_nonnull(),
3605
0
        alpha.release_nonnull());
3606
0
}
3607
3608
// https://www.w3.org/TR/css-color-4/#color-syntax
3609
RefPtr<CSSStyleValue> Parser::parse_color_value(TokenStream<ComponentValue>& tokens)
3610
0
{
3611
3612
    // Keywords: <system-color> | <deprecated-color> | currentColor
3613
0
    {
3614
0
        auto transaction = tokens.begin_transaction();
3615
0
        if (auto keyword = parse_keyword_value(tokens); keyword && keyword->has_color()) {
3616
0
            transaction.commit();
3617
0
            return keyword;
3618
0
        }
3619
0
    }
3620
3621
    // Functions
3622
0
    if (auto color = parse_color_function(tokens))
3623
0
        return color;
3624
3625
0
    if (auto rgb = parse_rgb_color_value(tokens))
3626
0
        return rgb;
3627
0
    if (auto hsl = parse_hsl_color_value(tokens))
3628
0
        return hsl;
3629
0
    if (auto hwb = parse_hwb_color_value(tokens))
3630
0
        return hwb;
3631
0
    if (auto lab = parse_lab_color_value(tokens))
3632
0
        return lab;
3633
0
    if (auto lch = parse_lch_color_value(tokens))
3634
0
        return lch;
3635
0
    if (auto oklab = parse_oklab_color_value(tokens))
3636
0
        return oklab;
3637
0
    if (auto oklch = parse_oklch_color_value(tokens))
3638
0
        return oklch;
3639
3640
0
    auto transaction = tokens.begin_transaction();
3641
0
    tokens.discard_whitespace();
3642
0
    auto component_value = tokens.consume_a_token();
3643
3644
0
    if (component_value.is(Token::Type::Ident)) {
3645
0
        auto ident = component_value.token().ident();
3646
3647
0
        auto color = Color::from_string(ident);
3648
0
        if (color.has_value()) {
3649
0
            transaction.commit();
3650
0
            return CSSColorValue::create_from_color(color.release_value());
3651
0
        }
3652
        // Otherwise, fall through to the hashless-hex-color case
3653
0
    }
3654
3655
0
    if (component_value.is(Token::Type::Hash)) {
3656
0
        auto color = Color::from_string(MUST(String::formatted("#{}", component_value.token().hash_value())));
3657
0
        if (color.has_value()) {
3658
0
            transaction.commit();
3659
0
            return CSSColorValue::create_from_color(color.release_value());
3660
0
        }
3661
0
        return {};
3662
0
    }
3663
3664
    // https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk
3665
0
    if (m_context.in_quirks_mode() && property_has_quirk(m_context.current_property_id(), Quirk::HashlessHexColor)) {
3666
        // The value of a quirky color is obtained from the possible component values using the following algorithm,
3667
        // aborting on the first step that returns a value:
3668
3669
        // 1. Let cv be the component value.
3670
0
        auto const& cv = component_value;
3671
0
        String serialization;
3672
        // 2. If cv is a <number-token> or a <dimension-token>, follow these substeps:
3673
0
        if (cv.is(Token::Type::Number) || cv.is(Token::Type::Dimension)) {
3674
            // 1. If cv’s type flag is not "integer", return an error.
3675
            //    This means that values that happen to use scientific notation, e.g., 5e5e5e, will fail to parse.
3676
0
            if (!cv.token().number().is_integer())
3677
0
                return {};
3678
3679
            // 2. If cv’s value is less than zero, return an error.
3680
0
            auto value = cv.is(Token::Type::Number) ? cv.token().to_integer() : cv.token().dimension_value_int();
3681
0
            if (value < 0)
3682
0
                return {};
3683
3684
            // 3. Let serialization be the serialization of cv’s value, as a base-ten integer using digits 0-9 (U+0030 to U+0039) in the shortest form possible.
3685
0
            StringBuilder serialization_builder;
3686
0
            serialization_builder.appendff("{}", value);
3687
3688
            // 4. If cv is a <dimension-token>, append the unit to serialization.
3689
0
            if (cv.is(Token::Type::Dimension))
3690
0
                serialization_builder.append(cv.token().dimension_unit());
3691
3692
            // 5. If serialization consists of fewer than six characters, prepend zeros (U+0030) so that it becomes six characters.
3693
0
            serialization = MUST(serialization_builder.to_string());
3694
0
            if (serialization_builder.length() < 6) {
3695
0
                StringBuilder builder;
3696
0
                for (size_t i = 0; i < (6 - serialization_builder.length()); i++)
3697
0
                    builder.append('0');
3698
0
                builder.append(serialization_builder.string_view());
3699
0
                serialization = MUST(builder.to_string());
3700
0
            }
3701
0
        }
3702
        // 3. Otherwise, cv is an <ident-token>; let serialization be cv’s value.
3703
0
        else {
3704
0
            if (!cv.is(Token::Type::Ident))
3705
0
                return {};
3706
0
            serialization = cv.token().ident().to_string();
3707
0
        }
3708
3709
        // 4. If serialization does not consist of three or six characters, return an error.
3710
0
        if (serialization.bytes().size() != 3 && serialization.bytes().size() != 6)
3711
0
            return {};
3712
3713
        // 5. If serialization contains any characters not in the range [0-9A-Fa-f] (U+0030 to U+0039, U+0041 to U+0046, U+0061 to U+0066), return an error.
3714
0
        for (auto c : serialization.bytes_as_string_view()) {
3715
0
            if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')))
3716
0
                return {};
3717
0
        }
3718
3719
        // 6. Return the concatenation of "#" (U+0023) and serialization.
3720
0
        auto color = Color::from_string(MUST(String::formatted("#{}", serialization)));
3721
0
        if (color.has_value()) {
3722
0
            transaction.commit();
3723
0
            return CSSColorValue::create_from_color(color.release_value());
3724
0
        }
3725
0
    }
3726
3727
0
    return {};
3728
0
}
3729
3730
// https://drafts.csswg.org/css-lists-3/#counter-functions
3731
RefPtr<CSSStyleValue> Parser::parse_counter_value(TokenStream<ComponentValue>& tokens)
3732
0
{
3733
0
    auto parse_counter_name = [this](TokenStream<ComponentValue>& tokens) -> Optional<FlyString> {
3734
        // https://drafts.csswg.org/css-lists-3/#typedef-counter-name
3735
        // Counters are referred to in CSS syntax using the <counter-name> type, which represents
3736
        // their name as a <custom-ident>. A <counter-name> name cannot match the keyword none;
3737
        // such an identifier is invalid as a <counter-name>.
3738
0
        auto transaction = tokens.begin_transaction();
3739
0
        tokens.discard_whitespace();
3740
3741
0
        auto counter_name = parse_custom_ident_value(tokens, { "none"sv });
3742
0
        if (!counter_name)
3743
0
            return {};
3744
3745
0
        tokens.discard_whitespace();
3746
0
        if (tokens.has_next_token())
3747
0
            return {};
3748
3749
0
        transaction.commit();
3750
0
        return counter_name->custom_ident();
3751
0
    };
3752
3753
0
    auto parse_counter_style = [this](TokenStream<ComponentValue>& tokens) -> RefPtr<CSSStyleValue> {
3754
        // https://drafts.csswg.org/css-counter-styles-3/#typedef-counter-style
3755
        // <counter-style> = <counter-style-name> | <symbols()>
3756
        // For now we just support <counter-style-name>, found here:
3757
        // https://drafts.csswg.org/css-counter-styles-3/#typedef-counter-style-name
3758
        // <counter-style-name> is a <custom-ident> that is not an ASCII case-insensitive match for none.
3759
0
        auto transaction = tokens.begin_transaction();
3760
0
        tokens.discard_whitespace();
3761
3762
0
        auto counter_style_name = parse_custom_ident_value(tokens, { "none"sv });
3763
0
        if (!counter_style_name)
3764
0
            return {};
3765
3766
0
        tokens.discard_whitespace();
3767
0
        if (tokens.has_next_token())
3768
0
            return {};
3769
3770
0
        transaction.commit();
3771
0
        return counter_style_name.release_nonnull();
3772
0
    };
3773
3774
0
    auto transaction = tokens.begin_transaction();
3775
0
    auto token = tokens.consume_a_token();
3776
0
    if (token.is_function("counter"sv)) {
3777
        // counter() = counter( <counter-name>, <counter-style>? )
3778
0
        auto& function = token.function();
3779
0
        TokenStream function_tokens { function.value };
3780
0
        auto function_values = parse_a_comma_separated_list_of_component_values(function_tokens);
3781
0
        if (function_values.is_empty() || function_values.size() > 2)
3782
0
            return nullptr;
3783
3784
0
        TokenStream name_tokens { function_values[0] };
3785
0
        auto counter_name = parse_counter_name(name_tokens);
3786
0
        if (!counter_name.has_value())
3787
0
            return nullptr;
3788
3789
0
        RefPtr<CSSStyleValue> counter_style;
3790
0
        if (function_values.size() > 1) {
3791
0
            TokenStream counter_style_tokens { function_values[1] };
3792
0
            counter_style = parse_counter_style(counter_style_tokens);
3793
0
            if (!counter_style)
3794
0
                return nullptr;
3795
0
        } else {
3796
            // In both cases, if the <counter-style> argument is omitted it defaults to `decimal`.
3797
0
            counter_style = CustomIdentStyleValue::create("decimal"_fly_string);
3798
0
        }
3799
3800
0
        transaction.commit();
3801
0
        return CounterStyleValue::create_counter(counter_name.release_value(), counter_style.release_nonnull());
3802
0
    }
3803
3804
0
    if (token.is_function("counters"sv)) {
3805
        // counters() = counters( <counter-name>, <string>, <counter-style>? )
3806
0
        auto& function = token.function();
3807
0
        TokenStream function_tokens { function.value };
3808
0
        auto function_values = parse_a_comma_separated_list_of_component_values(function_tokens);
3809
0
        if (function_values.size() < 2 || function_values.size() > 3)
3810
0
            return nullptr;
3811
3812
0
        TokenStream name_tokens { function_values[0] };
3813
0
        auto counter_name = parse_counter_name(name_tokens);
3814
0
        if (!counter_name.has_value())
3815
0
            return nullptr;
3816
3817
0
        TokenStream string_tokens { function_values[1] };
3818
0
        string_tokens.discard_whitespace();
3819
0
        auto join_string = parse_string_value(string_tokens);
3820
0
        string_tokens.discard_whitespace();
3821
0
        if (!join_string || string_tokens.has_next_token())
3822
0
            return nullptr;
3823
3824
0
        RefPtr<CSSStyleValue> counter_style;
3825
0
        if (function_values.size() > 2) {
3826
0
            TokenStream counter_style_tokens { function_values[2] };
3827
0
            counter_style = parse_counter_style(counter_style_tokens);
3828
0
            if (!counter_style)
3829
0
                return nullptr;
3830
0
        } else {
3831
            // In both cases, if the <counter-style> argument is omitted it defaults to `decimal`.
3832
0
            counter_style = CustomIdentStyleValue::create("decimal"_fly_string);
3833
0
        }
3834
3835
0
        transaction.commit();
3836
0
        return CounterStyleValue::create_counters(counter_name.release_value(), join_string->string_value(), counter_style.release_nonnull());
3837
0
    }
3838
3839
0
    return nullptr;
3840
0
}
3841
3842
RefPtr<CSSStyleValue> Parser::parse_counter_definitions_value(TokenStream<ComponentValue>& tokens, AllowReversed allow_reversed, i32 default_value_if_not_reversed)
3843
0
{
3844
    // If AllowReversed is Yes, parses:
3845
    //   [ <counter-name> <integer>? | <reversed-counter-name> <integer>? ]+
3846
    // Otherwise parses:
3847
    //   [ <counter-name> <integer>? ]+
3848
3849
    // FIXME: This disabled parsing of `reversed()` counters. Remove this line once they're supported.
3850
0
    allow_reversed = AllowReversed::No;
3851
3852
0
    auto transaction = tokens.begin_transaction();
3853
0
    tokens.discard_whitespace();
3854
3855
0
    Vector<CounterDefinition> counter_definitions;
3856
0
    while (tokens.has_next_token()) {
3857
0
        auto per_item_transaction = tokens.begin_transaction();
3858
0
        CounterDefinition definition {};
3859
3860
        // <counter-name> | <reversed-counter-name>
3861
0
        auto& token = tokens.consume_a_token();
3862
0
        if (token.is(Token::Type::Ident)) {
3863
0
            definition.name = token.token().ident();
3864
0
            definition.is_reversed = false;
3865
0
        } else if (allow_reversed == AllowReversed::Yes && token.is_function("reversed"sv)) {
3866
0
            TokenStream function_tokens { token.function().value };
3867
0
            function_tokens.discard_whitespace();
3868
0
            auto& name_token = function_tokens.consume_a_token();
3869
0
            if (!name_token.is(Token::Type::Ident))
3870
0
                break;
3871
0
            function_tokens.discard_whitespace();
3872
0
            if (function_tokens.has_next_token())
3873
0
                break;
3874
3875
0
            definition.name = name_token.token().ident();
3876
0
            definition.is_reversed = true;
3877
0
        } else {
3878
0
            break;
3879
0
        }
3880
0
        tokens.discard_whitespace();
3881
3882
        // <integer>?
3883
0
        definition.value = parse_integer_value(tokens);
3884
0
        if (!definition.value && !definition.is_reversed)
3885
0
            definition.value = IntegerStyleValue::create(default_value_if_not_reversed);
3886
3887
0
        counter_definitions.append(move(definition));
3888
0
        tokens.discard_whitespace();
3889
0
        per_item_transaction.commit();
3890
0
    }
3891
3892
0
    if (counter_definitions.is_empty())
3893
0
        return {};
3894
3895
0
    transaction.commit();
3896
0
    return CounterDefinitionsStyleValue::create(move(counter_definitions));
3897
0
}
3898
3899
RefPtr<CSSStyleValue> Parser::parse_ratio_value(TokenStream<ComponentValue>& tokens)
3900
0
{
3901
0
    if (auto ratio = parse_ratio(tokens); ratio.has_value())
3902
0
        return RatioStyleValue::create(ratio.release_value());
3903
0
    return nullptr;
3904
0
}
3905
3906
RefPtr<StringStyleValue> Parser::parse_string_value(TokenStream<ComponentValue>& tokens)
3907
0
{
3908
0
    auto peek = tokens.next_token();
3909
0
    if (peek.is(Token::Type::String)) {
3910
0
        tokens.discard_a_token();
3911
0
        return StringStyleValue::create(peek.token().string());
3912
0
    }
3913
3914
0
    return nullptr;
3915
0
}
3916
3917
RefPtr<CSSStyleValue> Parser::parse_image_value(TokenStream<ComponentValue>& tokens)
3918
0
{
3919
0
    if (auto url = parse_url_function(tokens); url.has_value())
3920
0
        return ImageStyleValue::create(url.value());
3921
3922
0
    if (auto linear_gradient = parse_linear_gradient_function(tokens))
3923
0
        return linear_gradient;
3924
3925
0
    if (auto conic_gradient = parse_conic_gradient_function(tokens))
3926
0
        return conic_gradient;
3927
3928
0
    if (auto radial_gradient = parse_radial_gradient_function(tokens))
3929
0
        return radial_gradient;
3930
3931
0
    return nullptr;
3932
0
}
3933
3934
// https://svgwg.org/svg2-draft/painting.html#SpecifyingPaint
3935
RefPtr<CSSStyleValue> Parser::parse_paint_value(TokenStream<ComponentValue>& tokens)
3936
0
{
3937
    // `<paint> = none | <color> | <url> [none | <color>]? | context-fill | context-stroke`
3938
3939
0
    auto parse_color_or_none = [&]() -> Optional<RefPtr<CSSStyleValue>> {
3940
0
        if (auto color = parse_color_value(tokens))
3941
0
            return color;
3942
3943
        // NOTE: <color> also accepts identifiers, so we do this identifier check last.
3944
0
        if (tokens.next_token().is(Token::Type::Ident)) {
3945
0
            auto maybe_keyword = keyword_from_string(tokens.next_token().token().ident());
3946
0
            if (maybe_keyword.has_value()) {
3947
                // FIXME: Accept `context-fill` and `context-stroke`
3948
0
                switch (*maybe_keyword) {
3949
0
                case Keyword::None:
3950
0
                    tokens.discard_a_token();
3951
0
                    return CSSKeywordValue::create(*maybe_keyword);
3952
0
                default:
3953
0
                    return nullptr;
3954
0
                }
3955
0
            }
3956
0
        }
3957
3958
0
        return OptionalNone {};
3959
0
    };
3960
3961
    // FIMXE: Allow context-fill/context-stroke here
3962
0
    if (auto color_or_none = parse_color_or_none(); color_or_none.has_value())
3963
0
        return *color_or_none;
3964
3965
0
    if (auto url = parse_url_value(tokens)) {
3966
0
        tokens.discard_whitespace();
3967
0
        if (auto color_or_none = parse_color_or_none(); color_or_none == nullptr) {
3968
            // Fail to parse if the fallback is invalid, but otherwise ignore it.
3969
            // FIXME: Use fallback color
3970
0
            return nullptr;
3971
0
        }
3972
0
        return url;
3973
0
    }
3974
3975
0
    return nullptr;
3976
0
}
3977
3978
// https://www.w3.org/TR/css-values-4/#position
3979
RefPtr<PositionStyleValue> Parser::parse_position_value(TokenStream<ComponentValue>& tokens, PositionParsingMode position_parsing_mode)
3980
0
{
3981
0
    auto parse_position_edge = [](ComponentValue const& token) -> Optional<PositionEdge> {
3982
0
        if (!token.is(Token::Type::Ident))
3983
0
            return {};
3984
0
        auto keyword = keyword_from_string(token.token().ident());
3985
0
        if (!keyword.has_value())
3986
0
            return {};
3987
0
        return keyword_to_position_edge(*keyword);
3988
0
    };
3989
3990
0
    auto parse_length_percentage = [&](ComponentValue const& token) -> Optional<LengthPercentage> {
3991
0
        if (token.is(Token::Type::EndOfFile))
3992
0
            return {};
3993
3994
0
        if (auto dimension = parse_dimension(token); dimension.has_value()) {
3995
0
            if (dimension->is_length_percentage())
3996
0
                return dimension->length_percentage();
3997
0
            return {};
3998
0
        }
3999
4000
0
        if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_length_percentage())
4001
0
            return LengthPercentage { calc.release_nonnull() };
4002
4003
0
        return {};
4004
0
    };
4005
4006
0
    auto is_horizontal = [](PositionEdge edge, bool accept_center) -> bool {
4007
0
        switch (edge) {
4008
0
        case PositionEdge::Left:
4009
0
        case PositionEdge::Right:
4010
0
            return true;
4011
0
        case PositionEdge::Center:
4012
0
            return accept_center;
4013
0
        default:
4014
0
            return false;
4015
0
        }
4016
0
    };
4017
4018
0
    auto is_vertical = [](PositionEdge edge, bool accept_center) -> bool {
4019
0
        switch (edge) {
4020
0
        case PositionEdge::Top:
4021
0
        case PositionEdge::Bottom:
4022
0
            return true;
4023
0
        case PositionEdge::Center:
4024
0
            return accept_center;
4025
0
        default:
4026
0
            return false;
4027
0
        }
4028
0
    };
4029
4030
0
    auto make_edge_style_value = [](PositionEdge position_edge, bool is_horizontal) -> NonnullRefPtr<EdgeStyleValue> {
4031
0
        if (position_edge == PositionEdge::Center)
4032
0
            return EdgeStyleValue::create(is_horizontal ? PositionEdge::Left : PositionEdge::Top, Percentage { 50 });
4033
0
        return EdgeStyleValue::create(position_edge, Length::make_px(0));
4034
0
    };
4035
4036
    // <position> = [
4037
    //   [ left | center | right | top | bottom | <length-percentage> ]
4038
    // |
4039
    //   [ left | center | right ] && [ top | center | bottom ]
4040
    // |
4041
    //   [ left | center | right | <length-percentage> ]
4042
    //   [ top | center | bottom | <length-percentage> ]
4043
    // |
4044
    //   [ [ left | right ] <length-percentage> ] &&
4045
    //   [ [ top | bottom ] <length-percentage> ]
4046
    // ]
4047
4048
    // [ left | center | right | top | bottom | <length-percentage> ]
4049
0
    auto alternative_1 = [&]() -> RefPtr<PositionStyleValue> {
4050
0
        auto transaction = tokens.begin_transaction();
4051
4052
0
        tokens.discard_whitespace();
4053
0
        auto const& token = tokens.consume_a_token();
4054
4055
        // [ left | center | right | top | bottom ]
4056
0
        if (auto maybe_edge = parse_position_edge(token); maybe_edge.has_value()) {
4057
0
            auto edge = maybe_edge.release_value();
4058
0
            transaction.commit();
4059
4060
            // [ left | right ]
4061
0
            if (is_horizontal(edge, false))
4062
0
                return PositionStyleValue::create(make_edge_style_value(edge, true), make_edge_style_value(PositionEdge::Center, false));
4063
4064
            // [ top | bottom ]
4065
0
            if (is_vertical(edge, false))
4066
0
                return PositionStyleValue::create(make_edge_style_value(PositionEdge::Center, true), make_edge_style_value(edge, false));
4067
4068
            // [ center ]
4069
0
            VERIFY(edge == PositionEdge::Center);
4070
0
            return PositionStyleValue::create(make_edge_style_value(PositionEdge::Center, true), make_edge_style_value(PositionEdge::Center, false));
4071
0
        }
4072
4073
        // [ <length-percentage> ]
4074
0
        if (auto maybe_percentage = parse_length_percentage(token); maybe_percentage.has_value()) {
4075
0
            transaction.commit();
4076
0
            return PositionStyleValue::create(EdgeStyleValue::create(PositionEdge::Left, *maybe_percentage), make_edge_style_value(PositionEdge::Center, false));
4077
0
        }
4078
4079
0
        return nullptr;
4080
0
    };
4081
4082
    // [ left | center | right ] && [ top | center | bottom ]
4083
0
    auto alternative_2 = [&]() -> RefPtr<PositionStyleValue> {
4084
0
        auto transaction = tokens.begin_transaction();
4085
4086
0
        tokens.discard_whitespace();
4087
4088
        // Parse out two position edges
4089
0
        auto maybe_first_edge = parse_position_edge(tokens.consume_a_token());
4090
0
        if (!maybe_first_edge.has_value())
4091
0
            return nullptr;
4092
4093
0
        auto first_edge = maybe_first_edge.release_value();
4094
0
        tokens.discard_whitespace();
4095
4096
0
        auto maybe_second_edge = parse_position_edge(tokens.consume_a_token());
4097
0
        if (!maybe_second_edge.has_value())
4098
0
            return nullptr;
4099
4100
0
        auto second_edge = maybe_second_edge.release_value();
4101
4102
        // If 'left' or 'right' is given, that position is X and the other is Y.
4103
        // Conversely -
4104
        // If 'top' or 'bottom' is given, that position is Y and the other is X.
4105
0
        if (is_vertical(first_edge, false) || is_horizontal(second_edge, false))
4106
0
            swap(first_edge, second_edge);
4107
4108
        // [ left | center | right ] [ top | bottom | center ]
4109
0
        if (is_horizontal(first_edge, true) && is_vertical(second_edge, true)) {
4110
0
            transaction.commit();
4111
0
            return PositionStyleValue::create(make_edge_style_value(first_edge, true), make_edge_style_value(second_edge, false));
4112
0
        }
4113
4114
0
        return nullptr;
4115
0
    };
4116
4117
    // [ left | center | right | <length-percentage> ]
4118
    // [ top | center | bottom | <length-percentage> ]
4119
0
    auto alternative_3 = [&]() -> RefPtr<PositionStyleValue> {
4120
0
        auto transaction = tokens.begin_transaction();
4121
4122
0
        auto parse_position_or_length = [&](bool as_horizontal) -> RefPtr<EdgeStyleValue> {
4123
0
            tokens.discard_whitespace();
4124
0
            auto const& token = tokens.consume_a_token();
4125
4126
0
            if (auto maybe_position = parse_position_edge(token); maybe_position.has_value()) {
4127
0
                auto position = maybe_position.release_value();
4128
0
                bool valid = as_horizontal ? is_horizontal(position, true) : is_vertical(position, true);
4129
0
                if (!valid)
4130
0
                    return nullptr;
4131
0
                return make_edge_style_value(position, as_horizontal);
4132
0
            }
4133
4134
0
            auto maybe_length = parse_length_percentage(token);
4135
0
            if (!maybe_length.has_value())
4136
0
                return nullptr;
4137
4138
0
            return EdgeStyleValue::create(as_horizontal ? PositionEdge::Left : PositionEdge::Top, maybe_length.release_value());
4139
0
        };
4140
4141
        // [ left | center | right | <length-percentage> ]
4142
0
        auto horizontal_edge = parse_position_or_length(true);
4143
0
        if (!horizontal_edge)
4144
0
            return nullptr;
4145
4146
        // [ top | center | bottom | <length-percentage> ]
4147
0
        auto vertical_edge = parse_position_or_length(false);
4148
0
        if (!vertical_edge)
4149
0
            return nullptr;
4150
4151
0
        transaction.commit();
4152
0
        return PositionStyleValue::create(horizontal_edge.release_nonnull(), vertical_edge.release_nonnull());
4153
0
    };
4154
4155
    // [ [ left | right ] <length-percentage> ] &&
4156
    // [ [ top | bottom ] <length-percentage> ]
4157
0
    auto alternative_4 = [&]() -> RefPtr<PositionStyleValue> {
4158
0
        struct PositionAndLength {
4159
0
            PositionEdge position;
4160
0
            LengthPercentage length;
4161
0
        };
4162
4163
0
        auto parse_position_and_length = [&]() -> Optional<PositionAndLength> {
4164
0
            tokens.discard_whitespace();
4165
4166
0
            auto maybe_position = parse_position_edge(tokens.consume_a_token());
4167
0
            if (!maybe_position.has_value())
4168
0
                return {};
4169
4170
0
            tokens.discard_whitespace();
4171
4172
0
            auto maybe_length = parse_length_percentage(tokens.consume_a_token());
4173
0
            if (!maybe_length.has_value())
4174
0
                return {};
4175
4176
0
            return PositionAndLength {
4177
0
                .position = maybe_position.release_value(),
4178
0
                .length = maybe_length.release_value(),
4179
0
            };
4180
0
        };
4181
4182
0
        auto transaction = tokens.begin_transaction();
4183
4184
0
        auto maybe_group1 = parse_position_and_length();
4185
0
        if (!maybe_group1.has_value())
4186
0
            return nullptr;
4187
4188
0
        auto maybe_group2 = parse_position_and_length();
4189
0
        if (!maybe_group2.has_value())
4190
0
            return nullptr;
4191
4192
0
        auto group1 = maybe_group1.release_value();
4193
0
        auto group2 = maybe_group2.release_value();
4194
4195
        // [ [ left | right ] <length-percentage> ] [ [ top | bottom ] <length-percentage> ]
4196
0
        if (is_horizontal(group1.position, false) && is_vertical(group2.position, false)) {
4197
0
            transaction.commit();
4198
0
            return PositionStyleValue::create(EdgeStyleValue::create(group1.position, group1.length), EdgeStyleValue::create(group2.position, group2.length));
4199
0
        }
4200
4201
        // [ [ top | bottom ] <length-percentage> ] [ [ left | right ] <length-percentage> ]
4202
0
        if (is_vertical(group1.position, false) && is_horizontal(group2.position, false)) {
4203
0
            transaction.commit();
4204
0
            return PositionStyleValue::create(EdgeStyleValue::create(group2.position, group2.length), EdgeStyleValue::create(group1.position, group1.length));
4205
0
        }
4206
4207
0
        return nullptr;
4208
0
    };
4209
4210
    // The extra 3-value syntax that's allowed for background-position:
4211
    // [ center | [ left | right ] <length-percentage>? ] &&
4212
    // [ center | [ top | bottom ] <length-percentage>? ]
4213
0
    auto alternative_5_for_background_position = [&]() -> RefPtr<PositionStyleValue> {
4214
0
        auto transaction = tokens.begin_transaction();
4215
4216
0
        struct PositionAndMaybeLength {
4217
0
            PositionEdge position;
4218
0
            Optional<LengthPercentage> length;
4219
0
        };
4220
4221
        // [ <position> <length-percentage>? ]
4222
0
        auto parse_position_and_maybe_length = [&]() -> Optional<PositionAndMaybeLength> {
4223
0
            tokens.discard_whitespace();
4224
4225
0
            auto maybe_position = parse_position_edge(tokens.consume_a_token());
4226
0
            if (!maybe_position.has_value())
4227
0
                return {};
4228
4229
0
            tokens.discard_whitespace();
4230
4231
0
            auto maybe_length = parse_length_percentage(tokens.next_token());
4232
0
            if (maybe_length.has_value()) {
4233
                // 'center' cannot be followed by a <length-percentage>
4234
0
                if (maybe_position.value() == PositionEdge::Center && maybe_length.has_value())
4235
0
                    return {};
4236
0
                tokens.discard_a_token();
4237
0
            }
4238
4239
0
            return PositionAndMaybeLength {
4240
0
                .position = maybe_position.release_value(),
4241
0
                .length = move(maybe_length),
4242
0
            };
4243
0
        };
4244
4245
0
        auto maybe_group1 = parse_position_and_maybe_length();
4246
0
        if (!maybe_group1.has_value())
4247
0
            return nullptr;
4248
4249
0
        auto maybe_group2 = parse_position_and_maybe_length();
4250
0
        if (!maybe_group2.has_value())
4251
0
            return nullptr;
4252
4253
0
        auto group1 = maybe_group1.release_value();
4254
0
        auto group2 = maybe_group2.release_value();
4255
4256
        // 2-value or 4-value if both <length-percentage>s are present or missing.
4257
0
        if (group1.length.has_value() == group2.length.has_value())
4258
0
            return nullptr;
4259
4260
        // If 'left' or 'right' is given, that position is X and the other is Y.
4261
        // Conversely -
4262
        // If 'top' or 'bottom' is given, that position is Y and the other is X.
4263
0
        if (is_vertical(group1.position, false) || is_horizontal(group2.position, false))
4264
0
            swap(group1, group2);
4265
4266
        // [ center | [ left | right ] ]
4267
0
        if (!is_horizontal(group1.position, true))
4268
0
            return nullptr;
4269
4270
        // [ center | [ top | bottom ] ]
4271
0
        if (!is_vertical(group2.position, true))
4272
0
            return nullptr;
4273
4274
0
        auto to_style_value = [&](PositionAndMaybeLength const& group, bool is_horizontal) -> NonnullRefPtr<EdgeStyleValue> {
4275
0
            if (group.position == PositionEdge::Center)
4276
0
                return EdgeStyleValue::create(is_horizontal ? PositionEdge::Left : PositionEdge::Top, Percentage { 50 });
4277
4278
0
            return EdgeStyleValue::create(group.position, group.length.value_or(Length::make_px(0)));
4279
0
        };
4280
4281
0
        transaction.commit();
4282
0
        return PositionStyleValue::create(to_style_value(group1, true), to_style_value(group2, false));
4283
0
    };
4284
4285
    // Note: The alternatives must be attempted in this order since shorter alternatives can match a prefix of longer ones.
4286
0
    if (auto position = alternative_4())
4287
0
        return position;
4288
0
    if (position_parsing_mode == PositionParsingMode::BackgroundPosition) {
4289
0
        if (auto position = alternative_5_for_background_position())
4290
0
            return position;
4291
0
    }
4292
0
    if (auto position = alternative_3())
4293
0
        return position;
4294
0
    if (auto position = alternative_2())
4295
0
        return position;
4296
0
    if (auto position = alternative_1())
4297
0
        return position;
4298
0
    return nullptr;
4299
0
}
4300
4301
template<typename ParseFunction>
4302
RefPtr<CSSStyleValue> Parser::parse_comma_separated_value_list(TokenStream<ComponentValue>& tokens, ParseFunction parse_one_value)
4303
0
{
4304
0
    auto first = parse_one_value(tokens);
4305
0
    if (!first || !tokens.has_next_token())
4306
0
        return first;
4307
4308
0
    StyleValueVector values;
4309
0
    values.append(first.release_nonnull());
4310
4311
0
    while (tokens.has_next_token()) {
4312
0
        if (!tokens.consume_a_token().is(Token::Type::Comma))
4313
0
            return nullptr;
4314
4315
0
        if (auto maybe_value = parse_one_value(tokens)) {
4316
0
            values.append(maybe_value.release_nonnull());
4317
0
            continue;
4318
0
        }
4319
0
        return nullptr;
4320
0
    }
4321
4322
0
    return StyleValueList::create(move(values), StyleValueList::Separator::Comma);
4323
0
}
Unexecuted instantiation: Parser.cpp:AK::RefPtr<Web::CSS::CSSStyleValue> Web::CSS::Parser::Parser::parse_comma_separated_value_list<Web::CSS::Parser::Parser::parse_simple_comma_separated_value_list(Web::CSS::PropertyID, Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_0>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, Web::CSS::Parser::Parser::parse_simple_comma_separated_value_list(Web::CSS::PropertyID, Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_0)
Unexecuted instantiation: Parser.cpp:AK::RefPtr<Web::CSS::CSSStyleValue> Web::CSS::Parser::Parser::parse_comma_separated_value_list<Web::CSS::Parser::Parser::parse_shadow_value(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, Web::CSS::Parser::Parser::AllowInsetKeyword)::$_0>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, Web::CSS::Parser::Parser::parse_shadow_value(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, Web::CSS::Parser::Parser::AllowInsetKeyword)::$_0)
Unexecuted instantiation: Parser.cpp:AK::RefPtr<Web::CSS::CSSStyleValue> Web::CSS::Parser::Parser::parse_comma_separated_value_list<Web::CSS::Parser::Parser::parse_css_value(Web::CSS::PropertyID, Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, AK::Optional<AK::String>)::$_0>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, Web::CSS::Parser::Parser::parse_css_value(Web::CSS::PropertyID, Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, AK::Optional<AK::String>)::$_0)
Unexecuted instantiation: Parser.cpp:AK::RefPtr<Web::CSS::CSSStyleValue> Web::CSS::Parser::Parser::parse_comma_separated_value_list<Web::CSS::Parser::Parser::parse_css_value(Web::CSS::PropertyID, Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, AK::Optional<AK::String>)::$_1>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, Web::CSS::Parser::Parser::parse_css_value(Web::CSS::PropertyID, Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, AK::Optional<AK::String>)::$_1)
Unexecuted instantiation: Parser.cpp:AK::RefPtr<Web::CSS::CSSStyleValue> Web::CSS::Parser::Parser::parse_comma_separated_value_list<Web::CSS::Parser::Parser::parse_css_value(Web::CSS::PropertyID, Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, AK::Optional<AK::String>)::$_2>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, Web::CSS::Parser::Parser::parse_css_value(Web::CSS::PropertyID, Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, AK::Optional<AK::String>)::$_2)
Unexecuted instantiation: Parser.cpp:AK::RefPtr<Web::CSS::CSSStyleValue> Web::CSS::Parser::Parser::parse_comma_separated_value_list<Web::CSS::Parser::Parser::parse_css_value(Web::CSS::PropertyID, Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, AK::Optional<AK::String>)::$_3>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, Web::CSS::Parser::Parser::parse_css_value(Web::CSS::PropertyID, Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&, AK::Optional<AK::String>)::$_3)
4324
4325
RefPtr<CSSStyleValue> Parser::parse_simple_comma_separated_value_list(PropertyID property_id, TokenStream<ComponentValue>& tokens)
4326
0
{
4327
0
    return parse_comma_separated_value_list(tokens, [this, property_id](auto& tokens) -> RefPtr<CSSStyleValue> {
4328
0
        if (auto value = parse_css_value_for_property(property_id, tokens))
4329
0
            return value;
4330
0
        tokens.reconsume_current_input_token();
4331
0
        return nullptr;
4332
0
    });
4333
0
}
4334
4335
RefPtr<CSSStyleValue> Parser::parse_all_as_single_keyword_value(TokenStream<ComponentValue>& tokens, Keyword keyword)
4336
0
{
4337
0
    auto transaction = tokens.begin_transaction();
4338
0
    tokens.discard_whitespace();
4339
0
    auto keyword_value = parse_keyword_value(tokens);
4340
0
    tokens.discard_whitespace();
4341
4342
0
    if (tokens.has_next_token() || !keyword_value || keyword_value->to_keyword() != keyword)
4343
0
        return {};
4344
4345
0
    transaction.commit();
4346
0
    return keyword_value;
4347
0
}
4348
4349
static void remove_property(Vector<PropertyID>& properties, PropertyID property_to_remove)
4350
0
{
4351
0
    properties.remove_first_matching([&](auto it) { return it == property_to_remove; });
4352
0
}
4353
4354
// https://www.w3.org/TR/css-sizing-4/#aspect-ratio
4355
RefPtr<CSSStyleValue> Parser::parse_aspect_ratio_value(TokenStream<ComponentValue>& tokens)
4356
0
{
4357
    // `auto || <ratio>`
4358
0
    RefPtr<CSSStyleValue> auto_value;
4359
0
    RefPtr<CSSStyleValue> ratio_value;
4360
4361
0
    auto transaction = tokens.begin_transaction();
4362
0
    while (tokens.has_next_token()) {
4363
0
        auto maybe_value = parse_css_value_for_property(PropertyID::AspectRatio, tokens);
4364
0
        if (!maybe_value)
4365
0
            return nullptr;
4366
4367
0
        if (maybe_value->is_ratio()) {
4368
0
            if (ratio_value)
4369
0
                return nullptr;
4370
0
            ratio_value = maybe_value.release_nonnull();
4371
0
            continue;
4372
0
        }
4373
4374
0
        if (maybe_value->is_keyword() && maybe_value->as_keyword().keyword() == Keyword::Auto) {
4375
0
            if (auto_value)
4376
0
                return nullptr;
4377
0
            auto_value = maybe_value.release_nonnull();
4378
0
            continue;
4379
0
        }
4380
4381
0
        return nullptr;
4382
0
    }
4383
4384
0
    if (auto_value && ratio_value) {
4385
0
        transaction.commit();
4386
0
        return StyleValueList::create(
4387
0
            StyleValueVector { auto_value.release_nonnull(), ratio_value.release_nonnull() },
4388
0
            StyleValueList::Separator::Space);
4389
0
    }
4390
4391
0
    if (ratio_value) {
4392
0
        transaction.commit();
4393
0
        return ratio_value.release_nonnull();
4394
0
    }
4395
4396
0
    if (auto_value) {
4397
0
        transaction.commit();
4398
0
        return auto_value.release_nonnull();
4399
0
    }
4400
4401
0
    return nullptr;
4402
0
}
4403
4404
RefPtr<CSSStyleValue> Parser::parse_background_value(TokenStream<ComponentValue>& tokens)
4405
0
{
4406
0
    auto transaction = tokens.begin_transaction();
4407
4408
0
    auto make_background_shorthand = [&](auto background_color, auto background_image, auto background_position, auto background_size, auto background_repeat, auto background_attachment, auto background_origin, auto background_clip) {
4409
0
        return ShorthandStyleValue::create(PropertyID::Background,
4410
0
            { PropertyID::BackgroundColor, PropertyID::BackgroundImage, PropertyID::BackgroundPosition, PropertyID::BackgroundSize, PropertyID::BackgroundRepeat, PropertyID::BackgroundAttachment, PropertyID::BackgroundOrigin, PropertyID::BackgroundClip },
4411
0
            { move(background_color), move(background_image), move(background_position), move(background_size), move(background_repeat), move(background_attachment), move(background_origin), move(background_clip) });
4412
0
    };
Unexecuted instantiation: Parser.cpp:auto Web::CSS::Parser::Parser::parse_background_value(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_0::operator()<AK::NonnullRefPtr<Web::CSS::CSSStyleValue>, Web::CSS::ValueComparingNonnullRefPtr<Web::CSS::StyleValueList>, Web::CSS::ValueComparingNonnullRefPtr<Web::CSS::StyleValueList>, Web::CSS::ValueComparingNonnullRefPtr<Web::CSS::StyleValueList>, Web::CSS::ValueComparingNonnullRefPtr<Web::CSS::StyleValueList>, Web::CSS::ValueComparingNonnullRefPtr<Web::CSS::StyleValueList>, Web::CSS::ValueComparingNonnullRefPtr<Web::CSS::StyleValueList>, Web::CSS::ValueComparingNonnullRefPtr<Web::CSS::StyleValueList> >(AK::NonnullRefPtr<Web::CSS::CSSStyleValue>, Web::CSS::ValueComparingNonnullRefPtr<Web::CSS::StyleValueList>, Web::CSS::ValueComparingNonnullRefPtr<Web::CSS::StyleValueList>, Web::CSS::ValueComparingNonnullRefPtr<Web::CSS::StyleValueList>, Web::CSS::ValueComparingNonnullRefPtr<Web::CSS::StyleValueList>, Web::CSS::ValueComparingNonnullRefPtr<Web::CSS::StyleValueList>, Web::CSS::ValueComparingNonnullRefPtr<Web::CSS::StyleValueList>, Web::CSS::ValueComparingNonnullRefPtr<Web::CSS::StyleValueList>) const
Unexecuted instantiation: Parser.cpp:auto Web::CSS::Parser::Parser::parse_background_value(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_0::operator()<AK::NonnullRefPtr<Web::CSS::CSSStyleValue>, AK::NonnullRefPtr<Web::CSS::CSSStyleValue>, AK::NonnullRefPtr<Web::CSS::CSSStyleValue>, AK::NonnullRefPtr<Web::CSS::CSSStyleValue>, AK::NonnullRefPtr<Web::CSS::CSSStyleValue>, AK::NonnullRefPtr<Web::CSS::CSSStyleValue>, AK::NonnullRefPtr<Web::CSS::CSSStyleValue>, AK::NonnullRefPtr<Web::CSS::CSSStyleValue> >(AK::NonnullRefPtr<Web::CSS::CSSStyleValue>, AK::NonnullRefPtr<Web::CSS::CSSStyleValue>, AK::NonnullRefPtr<Web::CSS::CSSStyleValue>, AK::NonnullRefPtr<Web::CSS::CSSStyleValue>, AK::NonnullRefPtr<Web::CSS::CSSStyleValue>, AK::NonnullRefPtr<Web::CSS::CSSStyleValue>, AK::NonnullRefPtr<Web::CSS::CSSStyleValue>, AK::NonnullRefPtr<Web::CSS::CSSStyleValue>) const
4413
4414
0
    StyleValueVector background_images;
4415
0
    StyleValueVector background_positions;
4416
0
    StyleValueVector background_sizes;
4417
0
    StyleValueVector background_repeats;
4418
0
    StyleValueVector background_attachments;
4419
0
    StyleValueVector background_clips;
4420
0
    StyleValueVector background_origins;
4421
0
    RefPtr<CSSStyleValue> background_color;
4422
4423
0
    auto initial_background_image = property_initial_value(m_context.realm(), PropertyID::BackgroundImage);
4424
0
    auto initial_background_position = property_initial_value(m_context.realm(), PropertyID::BackgroundPosition);
4425
0
    auto initial_background_size = property_initial_value(m_context.realm(), PropertyID::BackgroundSize);
4426
0
    auto initial_background_repeat = property_initial_value(m_context.realm(), PropertyID::BackgroundRepeat);
4427
0
    auto initial_background_attachment = property_initial_value(m_context.realm(), PropertyID::BackgroundAttachment);
4428
0
    auto initial_background_clip = property_initial_value(m_context.realm(), PropertyID::BackgroundClip);
4429
0
    auto initial_background_origin = property_initial_value(m_context.realm(), PropertyID::BackgroundOrigin);
4430
0
    auto initial_background_color = property_initial_value(m_context.realm(), PropertyID::BackgroundColor);
4431
4432
    // Per-layer values
4433
0
    RefPtr<CSSStyleValue> background_image;
4434
0
    RefPtr<CSSStyleValue> background_position;
4435
0
    RefPtr<CSSStyleValue> background_size;
4436
0
    RefPtr<CSSStyleValue> background_repeat;
4437
0
    RefPtr<CSSStyleValue> background_attachment;
4438
0
    RefPtr<CSSStyleValue> background_clip;
4439
0
    RefPtr<CSSStyleValue> background_origin;
4440
4441
0
    bool has_multiple_layers = false;
4442
    // BackgroundSize is always parsed as part of BackgroundPosition, so we don't include it here.
4443
0
    Vector<PropertyID> remaining_layer_properties {
4444
0
        PropertyID::BackgroundAttachment,
4445
0
        PropertyID::BackgroundClip,
4446
0
        PropertyID::BackgroundColor,
4447
0
        PropertyID::BackgroundImage,
4448
0
        PropertyID::BackgroundOrigin,
4449
0
        PropertyID::BackgroundPosition,
4450
0
        PropertyID::BackgroundRepeat,
4451
0
    };
4452
4453
0
    auto background_layer_is_valid = [&](bool allow_background_color) -> bool {
4454
0
        if (allow_background_color) {
4455
0
            if (background_color)
4456
0
                return true;
4457
0
        } else {
4458
0
            if (background_color)
4459
0
                return false;
4460
0
        }
4461
0
        return background_image || background_position || background_size || background_repeat || background_attachment || background_clip || background_origin;
4462
0
    };
4463
4464
0
    auto complete_background_layer = [&]() {
4465
0
        background_images.append(background_image ? background_image.release_nonnull() : initial_background_image);
4466
0
        background_positions.append(background_position ? background_position.release_nonnull() : initial_background_position);
4467
0
        background_sizes.append(background_size ? background_size.release_nonnull() : initial_background_size);
4468
0
        background_repeats.append(background_repeat ? background_repeat.release_nonnull() : initial_background_repeat);
4469
0
        background_attachments.append(background_attachment ? background_attachment.release_nonnull() : initial_background_attachment);
4470
4471
0
        if (!background_origin && !background_clip) {
4472
0
            background_origin = initial_background_origin;
4473
0
            background_clip = initial_background_clip;
4474
0
        } else if (!background_clip) {
4475
0
            background_clip = background_origin;
4476
0
        }
4477
0
        background_origins.append(background_origin.release_nonnull());
4478
0
        background_clips.append(background_clip.release_nonnull());
4479
4480
0
        background_image = nullptr;
4481
0
        background_position = nullptr;
4482
0
        background_size = nullptr;
4483
0
        background_repeat = nullptr;
4484
0
        background_attachment = nullptr;
4485
0
        background_clip = nullptr;
4486
0
        background_origin = nullptr;
4487
4488
0
        remaining_layer_properties.clear_with_capacity();
4489
0
        remaining_layer_properties.unchecked_append(PropertyID::BackgroundAttachment);
4490
0
        remaining_layer_properties.unchecked_append(PropertyID::BackgroundClip);
4491
0
        remaining_layer_properties.unchecked_append(PropertyID::BackgroundColor);
4492
0
        remaining_layer_properties.unchecked_append(PropertyID::BackgroundImage);
4493
0
        remaining_layer_properties.unchecked_append(PropertyID::BackgroundOrigin);
4494
0
        remaining_layer_properties.unchecked_append(PropertyID::BackgroundPosition);
4495
0
        remaining_layer_properties.unchecked_append(PropertyID::BackgroundRepeat);
4496
0
    };
4497
4498
0
    while (tokens.has_next_token()) {
4499
0
        if (tokens.next_token().is(Token::Type::Comma)) {
4500
0
            has_multiple_layers = true;
4501
0
            if (!background_layer_is_valid(false))
4502
0
                return nullptr;
4503
0
            complete_background_layer();
4504
0
            tokens.discard_a_token();
4505
0
            continue;
4506
0
        }
4507
4508
0
        auto value_and_property = parse_css_value_for_properties(remaining_layer_properties, tokens);
4509
0
        if (!value_and_property.has_value())
4510
0
            return nullptr;
4511
0
        auto& value = value_and_property->style_value;
4512
0
        remove_property(remaining_layer_properties, value_and_property->property);
4513
4514
0
        switch (value_and_property->property) {
4515
0
        case PropertyID::BackgroundAttachment:
4516
0
            VERIFY(!background_attachment);
4517
0
            background_attachment = value.release_nonnull();
4518
0
            continue;
4519
0
        case PropertyID::BackgroundColor:
4520
0
            VERIFY(!background_color);
4521
0
            background_color = value.release_nonnull();
4522
0
            continue;
4523
0
        case PropertyID::BackgroundImage:
4524
0
            VERIFY(!background_image);
4525
0
            background_image = value.release_nonnull();
4526
0
            continue;
4527
0
        case PropertyID::BackgroundClip:
4528
0
        case PropertyID::BackgroundOrigin: {
4529
            // background-origin and background-clip accept the same values. From the spec:
4530
            //   "If one <box> value is present then it sets both background-origin and background-clip to that value.
4531
            //    If two values are present, then the first sets background-origin and the second background-clip."
4532
            //        - https://www.w3.org/TR/css-backgrounds-3/#background
4533
            // So, we put the first one in background-origin, then if we get a second, we put it in background-clip.
4534
            // If we only get one, we copy the value before creating the ShorthandStyleValue.
4535
0
            if (!background_origin) {
4536
0
                background_origin = value.release_nonnull();
4537
0
            } else if (!background_clip) {
4538
0
                background_clip = value.release_nonnull();
4539
0
            } else {
4540
0
                VERIFY_NOT_REACHED();
4541
0
            }
4542
0
            continue;
4543
0
        }
4544
0
        case PropertyID::BackgroundPosition: {
4545
0
            VERIFY(!background_position);
4546
0
            background_position = value.release_nonnull();
4547
4548
            // Attempt to parse `/ <background-size>`
4549
0
            auto background_size_transaction = tokens.begin_transaction();
4550
0
            auto& maybe_slash = tokens.consume_a_token();
4551
0
            if (maybe_slash.is_delim('/')) {
4552
0
                if (auto maybe_background_size = parse_single_background_size_value(tokens)) {
4553
0
                    background_size_transaction.commit();
4554
0
                    background_size = maybe_background_size.release_nonnull();
4555
0
                    continue;
4556
0
                }
4557
0
                return nullptr;
4558
0
            }
4559
0
            continue;
4560
0
        }
4561
0
        case PropertyID::BackgroundRepeat: {
4562
0
            VERIFY(!background_repeat);
4563
0
            tokens.reconsume_current_input_token();
4564
0
            if (auto maybe_repeat = parse_single_background_repeat_value(tokens)) {
4565
0
                background_repeat = maybe_repeat.release_nonnull();
4566
0
                continue;
4567
0
            }
4568
0
            return nullptr;
4569
0
        }
4570
0
        default:
4571
0
            VERIFY_NOT_REACHED();
4572
0
        }
4573
4574
0
        return nullptr;
4575
0
    }
4576
4577
0
    if (!background_layer_is_valid(true))
4578
0
        return nullptr;
4579
4580
    // We only need to create StyleValueLists if there are multiple layers.
4581
    // Otherwise, we can pass the single StyleValues directly.
4582
0
    if (has_multiple_layers) {
4583
0
        complete_background_layer();
4584
4585
0
        if (!background_color)
4586
0
            background_color = initial_background_color;
4587
0
        transaction.commit();
4588
0
        return make_background_shorthand(
4589
0
            background_color.release_nonnull(),
4590
0
            StyleValueList::create(move(background_images), StyleValueList::Separator::Comma),
4591
0
            StyleValueList::create(move(background_positions), StyleValueList::Separator::Comma),
4592
0
            StyleValueList::create(move(background_sizes), StyleValueList::Separator::Comma),
4593
0
            StyleValueList::create(move(background_repeats), StyleValueList::Separator::Comma),
4594
0
            StyleValueList::create(move(background_attachments), StyleValueList::Separator::Comma),
4595
0
            StyleValueList::create(move(background_origins), StyleValueList::Separator::Comma),
4596
0
            StyleValueList::create(move(background_clips), StyleValueList::Separator::Comma));
4597
0
    }
4598
4599
0
    if (!background_color)
4600
0
        background_color = initial_background_color;
4601
0
    if (!background_image)
4602
0
        background_image = initial_background_image;
4603
0
    if (!background_position)
4604
0
        background_position = initial_background_position;
4605
0
    if (!background_size)
4606
0
        background_size = initial_background_size;
4607
0
    if (!background_repeat)
4608
0
        background_repeat = initial_background_repeat;
4609
0
    if (!background_attachment)
4610
0
        background_attachment = initial_background_attachment;
4611
4612
0
    if (!background_origin && !background_clip) {
4613
0
        background_origin = initial_background_origin;
4614
0
        background_clip = initial_background_clip;
4615
0
    } else if (!background_clip) {
4616
0
        background_clip = background_origin;
4617
0
    }
4618
4619
0
    transaction.commit();
4620
0
    return make_background_shorthand(
4621
0
        background_color.release_nonnull(),
4622
0
        background_image.release_nonnull(),
4623
0
        background_position.release_nonnull(),
4624
0
        background_size.release_nonnull(),
4625
0
        background_repeat.release_nonnull(),
4626
0
        background_attachment.release_nonnull(),
4627
0
        background_origin.release_nonnull(),
4628
0
        background_clip.release_nonnull());
4629
0
}
4630
4631
static Optional<LengthPercentage> style_value_to_length_percentage(auto value)
4632
0
{
4633
0
    if (value->is_percentage())
4634
0
        return LengthPercentage { value->as_percentage().percentage() };
4635
0
    if (value->is_length())
4636
0
        return LengthPercentage { value->as_length().length() };
4637
0
    if (value->is_math())
4638
0
        return LengthPercentage { value->as_math() };
4639
0
    return {};
4640
0
}
4641
4642
RefPtr<CSSStyleValue> Parser::parse_single_background_position_x_or_y_value(TokenStream<ComponentValue>& tokens, PropertyID property)
4643
0
{
4644
0
    PositionEdge relative_edge {};
4645
0
    if (property == PropertyID::BackgroundPositionX) {
4646
        // [ center | [ [ left | right | x-start | x-end ]? <length-percentage>? ]! ]#
4647
0
        relative_edge = PositionEdge::Left;
4648
0
    } else if (property == PropertyID::BackgroundPositionY) {
4649
        // [ center | [ [ top | bottom | y-start | y-end ]? <length-percentage>? ]! ]#
4650
0
        relative_edge = PositionEdge::Top;
4651
0
    } else {
4652
0
        VERIFY_NOT_REACHED();
4653
0
    }
4654
4655
0
    auto transaction = tokens.begin_transaction();
4656
0
    if (!tokens.has_next_token())
4657
0
        return nullptr;
4658
4659
0
    auto value = parse_css_value_for_property(property, tokens);
4660
0
    if (!value)
4661
0
        return nullptr;
4662
4663
0
    if (value->is_keyword()) {
4664
0
        auto keyword = value->to_keyword();
4665
0
        if (keyword == Keyword::Center) {
4666
0
            transaction.commit();
4667
0
            return EdgeStyleValue::create(relative_edge, Percentage { 50 });
4668
0
        }
4669
0
        if (auto edge = keyword_to_position_edge(keyword); edge.has_value()) {
4670
0
            relative_edge = *edge;
4671
0
        } else {
4672
0
            return nullptr;
4673
0
        }
4674
0
        if (tokens.has_next_token()) {
4675
0
            value = parse_css_value_for_property(property, tokens);
4676
0
            if (!value) {
4677
0
                transaction.commit();
4678
0
                return EdgeStyleValue::create(relative_edge, Length::make_px(0));
4679
0
            }
4680
0
        }
4681
0
    }
4682
4683
0
    auto offset = style_value_to_length_percentage(value);
4684
0
    if (offset.has_value()) {
4685
0
        transaction.commit();
4686
0
        return EdgeStyleValue::create(relative_edge, *offset);
4687
0
    }
4688
4689
    // If no offset is provided create this element but with an offset of default value of zero
4690
0
    transaction.commit();
4691
0
    return EdgeStyleValue::create(relative_edge, Length::make_px(0));
4692
0
}
4693
4694
RefPtr<CSSStyleValue> Parser::parse_single_background_repeat_value(TokenStream<ComponentValue>& tokens)
4695
0
{
4696
0
    auto transaction = tokens.begin_transaction();
4697
4698
0
    auto is_directional_repeat = [](CSSStyleValue const& value) -> bool {
4699
0
        auto keyword = value.to_keyword();
4700
0
        return keyword == Keyword::RepeatX || keyword == Keyword::RepeatY;
4701
0
    };
4702
4703
0
    auto as_repeat = [](Keyword keyword) -> Optional<Repeat> {
4704
0
        switch (keyword) {
4705
0
        case Keyword::NoRepeat:
4706
0
            return Repeat::NoRepeat;
4707
0
        case Keyword::Repeat:
4708
0
            return Repeat::Repeat;
4709
0
        case Keyword::Round:
4710
0
            return Repeat::Round;
4711
0
        case Keyword::Space:
4712
0
            return Repeat::Space;
4713
0
        default:
4714
0
            return {};
4715
0
        }
4716
0
    };
4717
4718
0
    auto maybe_x_value = parse_css_value_for_property(PropertyID::BackgroundRepeat, tokens);
4719
0
    if (!maybe_x_value)
4720
0
        return nullptr;
4721
0
    auto x_value = maybe_x_value.release_nonnull();
4722
4723
0
    if (is_directional_repeat(*x_value)) {
4724
0
        auto keyword = x_value->to_keyword();
4725
0
        transaction.commit();
4726
0
        return BackgroundRepeatStyleValue::create(
4727
0
            keyword == Keyword::RepeatX ? Repeat::Repeat : Repeat::NoRepeat,
4728
0
            keyword == Keyword::RepeatX ? Repeat::NoRepeat : Repeat::Repeat);
4729
0
    }
4730
4731
0
    auto x_repeat = as_repeat(x_value->to_keyword());
4732
0
    if (!x_repeat.has_value())
4733
0
        return nullptr;
4734
4735
    // See if we have a second value for Y
4736
0
    auto maybe_y_value = parse_css_value_for_property(PropertyID::BackgroundRepeat, tokens);
4737
0
    if (!maybe_y_value) {
4738
        // We don't have a second value, so use x for both
4739
0
        transaction.commit();
4740
0
        return BackgroundRepeatStyleValue::create(x_repeat.value(), x_repeat.value());
4741
0
    }
4742
0
    auto y_value = maybe_y_value.release_nonnull();
4743
0
    if (is_directional_repeat(*y_value))
4744
0
        return nullptr;
4745
4746
0
    auto y_repeat = as_repeat(y_value->to_keyword());
4747
0
    if (!y_repeat.has_value())
4748
0
        return nullptr;
4749
4750
0
    transaction.commit();
4751
0
    return BackgroundRepeatStyleValue::create(x_repeat.value(), y_repeat.value());
4752
0
}
4753
4754
RefPtr<CSSStyleValue> Parser::parse_single_background_size_value(TokenStream<ComponentValue>& tokens)
4755
0
{
4756
0
    auto transaction = tokens.begin_transaction();
4757
4758
0
    auto get_length_percentage = [](CSSStyleValue& style_value) -> Optional<LengthPercentage> {
4759
0
        if (style_value.has_auto())
4760
0
            return LengthPercentage { Length::make_auto() };
4761
0
        if (style_value.is_percentage())
4762
0
            return LengthPercentage { style_value.as_percentage().percentage() };
4763
0
        if (style_value.is_length())
4764
0
            return LengthPercentage { style_value.as_length().length() };
4765
0
        if (style_value.is_math())
4766
0
            return LengthPercentage { style_value.as_math() };
4767
0
        return {};
4768
0
    };
4769
4770
0
    auto maybe_x_value = parse_css_value_for_property(PropertyID::BackgroundSize, tokens);
4771
0
    if (!maybe_x_value)
4772
0
        return nullptr;
4773
0
    auto x_value = maybe_x_value.release_nonnull();
4774
4775
0
    if (x_value->to_keyword() == Keyword::Cover || x_value->to_keyword() == Keyword::Contain) {
4776
0
        transaction.commit();
4777
0
        return x_value;
4778
0
    }
4779
4780
0
    auto maybe_y_value = parse_css_value_for_property(PropertyID::BackgroundSize, tokens);
4781
0
    if (!maybe_y_value) {
4782
0
        auto y_value = LengthPercentage { Length::make_auto() };
4783
0
        auto x_size = get_length_percentage(*x_value);
4784
0
        if (!x_size.has_value())
4785
0
            return nullptr;
4786
4787
0
        transaction.commit();
4788
0
        return BackgroundSizeStyleValue::create(x_size.value(), y_value);
4789
0
    }
4790
4791
0
    auto y_value = maybe_y_value.release_nonnull();
4792
0
    auto x_size = get_length_percentage(*x_value);
4793
0
    auto y_size = get_length_percentage(*y_value);
4794
4795
0
    if (!x_size.has_value() || !y_size.has_value())
4796
0
        return nullptr;
4797
4798
0
    transaction.commit();
4799
0
    return BackgroundSizeStyleValue::create(x_size.release_value(), y_size.release_value());
4800
0
}
4801
4802
RefPtr<CSSStyleValue> Parser::parse_border_value(PropertyID property_id, TokenStream<ComponentValue>& tokens)
4803
0
{
4804
0
    RefPtr<CSSStyleValue> border_width;
4805
0
    RefPtr<CSSStyleValue> border_color;
4806
0
    RefPtr<CSSStyleValue> border_style;
4807
4808
0
    auto color_property = PropertyID::Invalid;
4809
0
    auto style_property = PropertyID::Invalid;
4810
0
    auto width_property = PropertyID::Invalid;
4811
4812
0
    switch (property_id) {
4813
0
    case PropertyID::Border:
4814
0
        color_property = PropertyID::BorderColor;
4815
0
        style_property = PropertyID::BorderStyle;
4816
0
        width_property = PropertyID::BorderWidth;
4817
0
        break;
4818
0
    case PropertyID::BorderBottom:
4819
0
        color_property = PropertyID::BorderBottomColor;
4820
0
        style_property = PropertyID::BorderBottomStyle;
4821
0
        width_property = PropertyID::BorderBottomWidth;
4822
0
        break;
4823
0
    case PropertyID::BorderLeft:
4824
0
        color_property = PropertyID::BorderLeftColor;
4825
0
        style_property = PropertyID::BorderLeftStyle;
4826
0
        width_property = PropertyID::BorderLeftWidth;
4827
0
        break;
4828
0
    case PropertyID::BorderRight:
4829
0
        color_property = PropertyID::BorderRightColor;
4830
0
        style_property = PropertyID::BorderRightStyle;
4831
0
        width_property = PropertyID::BorderRightWidth;
4832
0
        break;
4833
0
    case PropertyID::BorderTop:
4834
0
        color_property = PropertyID::BorderTopColor;
4835
0
        style_property = PropertyID::BorderTopStyle;
4836
0
        width_property = PropertyID::BorderTopWidth;
4837
0
        break;
4838
0
    default:
4839
0
        VERIFY_NOT_REACHED();
4840
0
    }
4841
4842
0
    auto remaining_longhands = Vector { width_property, color_property, style_property };
4843
0
    auto transaction = tokens.begin_transaction();
4844
4845
0
    while (tokens.has_next_token()) {
4846
0
        auto property_and_value = parse_css_value_for_properties(remaining_longhands, tokens);
4847
0
        if (!property_and_value.has_value())
4848
0
            return nullptr;
4849
0
        auto& value = property_and_value->style_value;
4850
0
        remove_property(remaining_longhands, property_and_value->property);
4851
4852
0
        if (property_and_value->property == width_property) {
4853
0
            VERIFY(!border_width);
4854
0
            border_width = value.release_nonnull();
4855
0
        } else if (property_and_value->property == color_property) {
4856
0
            VERIFY(!border_color);
4857
0
            border_color = value.release_nonnull();
4858
0
        } else if (property_and_value->property == style_property) {
4859
0
            VERIFY(!border_style);
4860
0
            border_style = value.release_nonnull();
4861
0
        } else {
4862
0
            VERIFY_NOT_REACHED();
4863
0
        }
4864
0
    }
4865
4866
0
    if (!border_width)
4867
0
        border_width = property_initial_value(m_context.realm(), width_property);
4868
0
    if (!border_style)
4869
0
        border_style = property_initial_value(m_context.realm(), style_property);
4870
0
    if (!border_color)
4871
0
        border_color = property_initial_value(m_context.realm(), color_property);
4872
4873
0
    transaction.commit();
4874
0
    return ShorthandStyleValue::create(property_id,
4875
0
        { width_property, style_property, color_property },
4876
0
        { border_width.release_nonnull(), border_style.release_nonnull(), border_color.release_nonnull() });
4877
0
}
4878
4879
RefPtr<CSSStyleValue> Parser::parse_border_radius_value(TokenStream<ComponentValue>& tokens)
4880
0
{
4881
0
    if (tokens.remaining_token_count() == 2) {
4882
0
        auto transaction = tokens.begin_transaction();
4883
0
        auto horizontal = parse_length_percentage(tokens);
4884
0
        auto vertical = parse_length_percentage(tokens);
4885
0
        if (horizontal.has_value() && vertical.has_value()) {
4886
0
            transaction.commit();
4887
0
            return BorderRadiusStyleValue::create(horizontal.release_value(), vertical.release_value());
4888
0
        }
4889
0
    }
4890
4891
0
    if (tokens.remaining_token_count() == 1) {
4892
0
        auto transaction = tokens.begin_transaction();
4893
0
        auto radius = parse_length_percentage(tokens);
4894
0
        if (radius.has_value()) {
4895
0
            transaction.commit();
4896
0
            return BorderRadiusStyleValue::create(radius.value(), radius.value());
4897
0
        }
4898
0
    }
4899
4900
0
    return nullptr;
4901
0
}
4902
4903
RefPtr<CSSStyleValue> Parser::parse_border_radius_shorthand_value(TokenStream<ComponentValue>& tokens)
4904
0
{
4905
0
    auto top_left = [&](Vector<LengthPercentage>& radii) { return radii[0]; };
4906
0
    auto top_right = [&](Vector<LengthPercentage>& radii) {
4907
0
        switch (radii.size()) {
4908
0
        case 4:
4909
0
        case 3:
4910
0
        case 2:
4911
0
            return radii[1];
4912
0
        case 1:
4913
0
            return radii[0];
4914
0
        default:
4915
0
            VERIFY_NOT_REACHED();
4916
0
        }
4917
0
    };
4918
0
    auto bottom_right = [&](Vector<LengthPercentage>& radii) {
4919
0
        switch (radii.size()) {
4920
0
        case 4:
4921
0
        case 3:
4922
0
            return radii[2];
4923
0
        case 2:
4924
0
        case 1:
4925
0
            return radii[0];
4926
0
        default:
4927
0
            VERIFY_NOT_REACHED();
4928
0
        }
4929
0
    };
4930
0
    auto bottom_left = [&](Vector<LengthPercentage>& radii) {
4931
0
        switch (radii.size()) {
4932
0
        case 4:
4933
0
            return radii[3];
4934
0
        case 3:
4935
0
        case 2:
4936
0
            return radii[1];
4937
0
        case 1:
4938
0
            return radii[0];
4939
0
        default:
4940
0
            VERIFY_NOT_REACHED();
4941
0
        }
4942
0
    };
4943
4944
0
    Vector<LengthPercentage> horizontal_radii;
4945
0
    Vector<LengthPercentage> vertical_radii;
4946
0
    bool reading_vertical = false;
4947
0
    auto transaction = tokens.begin_transaction();
4948
4949
0
    while (tokens.has_next_token()) {
4950
0
        if (tokens.next_token().is_delim('/')) {
4951
0
            if (reading_vertical || horizontal_radii.is_empty())
4952
0
                return nullptr;
4953
4954
0
            reading_vertical = true;
4955
0
            tokens.discard_a_token(); // `/`
4956
0
            continue;
4957
0
        }
4958
4959
0
        auto maybe_dimension = parse_length_percentage(tokens);
4960
0
        if (!maybe_dimension.has_value())
4961
0
            return nullptr;
4962
0
        if (reading_vertical) {
4963
0
            vertical_radii.append(maybe_dimension.release_value());
4964
0
        } else {
4965
0
            horizontal_radii.append(maybe_dimension.release_value());
4966
0
        }
4967
0
    }
4968
4969
0
    if (horizontal_radii.size() > 4 || vertical_radii.size() > 4
4970
0
        || horizontal_radii.is_empty()
4971
0
        || (reading_vertical && vertical_radii.is_empty()))
4972
0
        return nullptr;
4973
4974
0
    auto top_left_radius = BorderRadiusStyleValue::create(top_left(horizontal_radii),
4975
0
        vertical_radii.is_empty() ? top_left(horizontal_radii) : top_left(vertical_radii));
4976
0
    auto top_right_radius = BorderRadiusStyleValue::create(top_right(horizontal_radii),
4977
0
        vertical_radii.is_empty() ? top_right(horizontal_radii) : top_right(vertical_radii));
4978
0
    auto bottom_right_radius = BorderRadiusStyleValue::create(bottom_right(horizontal_radii),
4979
0
        vertical_radii.is_empty() ? bottom_right(horizontal_radii) : bottom_right(vertical_radii));
4980
0
    auto bottom_left_radius = BorderRadiusStyleValue::create(bottom_left(horizontal_radii),
4981
0
        vertical_radii.is_empty() ? bottom_left(horizontal_radii) : bottom_left(vertical_radii));
4982
4983
0
    transaction.commit();
4984
0
    return ShorthandStyleValue::create(PropertyID::BorderRadius,
4985
0
        { PropertyID::BorderTopLeftRadius, PropertyID::BorderTopRightRadius, PropertyID::BorderBottomRightRadius, PropertyID::BorderBottomLeftRadius },
4986
0
        { move(top_left_radius), move(top_right_radius), move(bottom_right_radius), move(bottom_left_radius) });
4987
0
}
4988
4989
RefPtr<CSSStyleValue> Parser::parse_columns_value(TokenStream<ComponentValue>& tokens)
4990
0
{
4991
0
    if (tokens.remaining_token_count() > 2)
4992
0
        return nullptr;
4993
4994
0
    RefPtr<CSSStyleValue> column_count;
4995
0
    RefPtr<CSSStyleValue> column_width;
4996
4997
0
    Vector<PropertyID> remaining_longhands { PropertyID::ColumnCount, PropertyID::ColumnWidth };
4998
0
    int found_autos = 0;
4999
5000
0
    auto transaction = tokens.begin_transaction();
5001
0
    while (tokens.has_next_token()) {
5002
0
        auto property_and_value = parse_css_value_for_properties(remaining_longhands, tokens);
5003
0
        if (!property_and_value.has_value())
5004
0
            return nullptr;
5005
0
        auto& value = property_and_value->style_value;
5006
5007
        // since the values can be in either order, we want to skip over autos
5008
0
        if (value->has_auto()) {
5009
0
            found_autos++;
5010
0
            continue;
5011
0
        }
5012
5013
0
        remove_property(remaining_longhands, property_and_value->property);
5014
5015
0
        switch (property_and_value->property) {
5016
0
        case PropertyID::ColumnCount: {
5017
0
            VERIFY(!column_count);
5018
0
            column_count = value.release_nonnull();
5019
0
            continue;
5020
0
        }
5021
0
        case PropertyID::ColumnWidth: {
5022
0
            VERIFY(!column_width);
5023
0
            column_width = value.release_nonnull();
5024
0
            continue;
5025
0
        }
5026
0
        default:
5027
0
            VERIFY_NOT_REACHED();
5028
0
        }
5029
0
    }
5030
5031
0
    if (found_autos > 2)
5032
0
        return nullptr;
5033
5034
0
    if (found_autos == 2) {
5035
0
        column_count = CSSKeywordValue::create(Keyword::Auto);
5036
0
        column_width = CSSKeywordValue::create(Keyword::Auto);
5037
0
    }
5038
5039
0
    if (found_autos == 1) {
5040
0
        if (!column_count)
5041
0
            column_count = CSSKeywordValue::create(Keyword::Auto);
5042
0
        if (!column_width)
5043
0
            column_width = CSSKeywordValue::create(Keyword::Auto);
5044
0
    }
5045
5046
0
    if (!column_count)
5047
0
        column_count = property_initial_value(m_context.realm(), PropertyID::ColumnCount);
5048
0
    if (!column_width)
5049
0
        column_width = property_initial_value(m_context.realm(), PropertyID::ColumnWidth);
5050
5051
0
    transaction.commit();
5052
0
    return ShorthandStyleValue::create(PropertyID::Columns,
5053
0
        { PropertyID::ColumnCount, PropertyID::ColumnWidth },
5054
0
        { column_count.release_nonnull(), column_width.release_nonnull() });
5055
0
}
5056
5057
RefPtr<CSSStyleValue> Parser::parse_shadow_value(TokenStream<ComponentValue>& tokens, AllowInsetKeyword allow_inset_keyword)
5058
0
{
5059
    // "none"
5060
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
5061
0
        return none;
5062
5063
0
    return parse_comma_separated_value_list(tokens, [this, allow_inset_keyword](auto& tokens) {
5064
0
        return parse_single_shadow_value(tokens, allow_inset_keyword);
5065
0
    });
5066
0
}
5067
5068
RefPtr<CSSStyleValue> Parser::parse_single_shadow_value(TokenStream<ComponentValue>& tokens, AllowInsetKeyword allow_inset_keyword)
5069
0
{
5070
0
    auto transaction = tokens.begin_transaction();
5071
5072
0
    RefPtr<CSSStyleValue> color;
5073
0
    RefPtr<CSSStyleValue> offset_x;
5074
0
    RefPtr<CSSStyleValue> offset_y;
5075
0
    RefPtr<CSSStyleValue> blur_radius;
5076
0
    RefPtr<CSSStyleValue> spread_distance;
5077
0
    Optional<ShadowPlacement> placement;
5078
5079
0
    auto possibly_dynamic_length = [&](ComponentValue const& token) -> RefPtr<CSSStyleValue> {
5080
0
        auto tokens = TokenStream<ComponentValue>::of_single_token(token);
5081
0
        auto maybe_length = parse_length(tokens);
5082
0
        if (!maybe_length.has_value())
5083
0
            return nullptr;
5084
0
        return maybe_length->as_style_value();
5085
0
    };
5086
5087
0
    while (tokens.has_next_token()) {
5088
0
        if (auto maybe_color = parse_color_value(tokens); maybe_color) {
5089
0
            if (color)
5090
0
                return nullptr;
5091
0
            color = maybe_color.release_nonnull();
5092
0
            continue;
5093
0
        }
5094
5095
0
        auto const& token = tokens.next_token();
5096
0
        if (auto maybe_offset_x = possibly_dynamic_length(token); maybe_offset_x) {
5097
            // horizontal offset
5098
0
            if (offset_x)
5099
0
                return nullptr;
5100
0
            offset_x = maybe_offset_x;
5101
0
            tokens.discard_a_token();
5102
5103
            // vertical offset
5104
0
            if (!tokens.has_next_token())
5105
0
                return nullptr;
5106
0
            auto maybe_offset_y = possibly_dynamic_length(tokens.next_token());
5107
0
            if (!maybe_offset_y)
5108
0
                return nullptr;
5109
0
            offset_y = maybe_offset_y;
5110
0
            tokens.discard_a_token();
5111
5112
            // blur radius (optional)
5113
0
            if (!tokens.has_next_token())
5114
0
                break;
5115
0
            auto maybe_blur_radius = possibly_dynamic_length(tokens.next_token());
5116
0
            if (!maybe_blur_radius)
5117
0
                continue;
5118
0
            blur_radius = maybe_blur_radius;
5119
0
            tokens.discard_a_token();
5120
5121
            // spread distance (optional)
5122
0
            if (!tokens.has_next_token())
5123
0
                break;
5124
0
            auto maybe_spread_distance = possibly_dynamic_length(tokens.next_token());
5125
0
            if (!maybe_spread_distance)
5126
0
                continue;
5127
0
            spread_distance = maybe_spread_distance;
5128
0
            tokens.discard_a_token();
5129
5130
0
            continue;
5131
0
        }
5132
5133
0
        if (allow_inset_keyword == AllowInsetKeyword::Yes && token.is_ident("inset"sv)) {
5134
0
            if (placement.has_value())
5135
0
                return nullptr;
5136
0
            placement = ShadowPlacement::Inner;
5137
0
            tokens.discard_a_token();
5138
0
            continue;
5139
0
        }
5140
5141
0
        if (token.is(Token::Type::Comma))
5142
0
            break;
5143
5144
0
        return nullptr;
5145
0
    }
5146
5147
    // If color is absent, default to `currentColor`
5148
0
    if (!color)
5149
0
        color = CSSKeywordValue::create(Keyword::Currentcolor);
5150
5151
    // x/y offsets are required
5152
0
    if (!offset_x || !offset_y)
5153
0
        return nullptr;
5154
5155
    // Other lengths default to 0
5156
0
    if (!blur_radius)
5157
0
        blur_radius = LengthStyleValue::create(Length::make_px(0));
5158
0
    if (!spread_distance)
5159
0
        spread_distance = LengthStyleValue::create(Length::make_px(0));
5160
5161
    // Placement is outer by default
5162
0
    if (!placement.has_value())
5163
0
        placement = ShadowPlacement::Outer;
5164
5165
0
    transaction.commit();
5166
0
    return ShadowStyleValue::create(color.release_nonnull(), offset_x.release_nonnull(), offset_y.release_nonnull(), blur_radius.release_nonnull(), spread_distance.release_nonnull(), placement.release_value());
5167
0
}
5168
5169
RefPtr<CSSStyleValue> Parser::parse_rotate_value(TokenStream<ComponentValue>& tokens)
5170
0
{
5171
    // Value: none | <angle> | [ x | y | z | <number>{3} ] && <angle>
5172
5173
0
    if (tokens.remaining_token_count() == 1) {
5174
        // "none"
5175
0
        if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
5176
0
            return none;
5177
5178
        // <angle>
5179
0
        if (auto angle = parse_angle_value(tokens))
5180
0
            return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(0), NumberStyleValue::create(0), NumberStyleValue::create(1));
5181
0
    }
5182
5183
0
    auto parse_one_of_xyz = [&]() -> Optional<ComponentValue> {
5184
0
        auto transaction = tokens.begin_transaction();
5185
0
        auto axis = tokens.consume_a_token();
5186
5187
0
        if (axis.is_ident("x"sv) || axis.is_ident("y"sv) || axis.is_ident("z"sv)) {
5188
0
            transaction.commit();
5189
0
            return axis;
5190
0
        }
5191
5192
0
        return {};
5193
0
    };
5194
5195
    // [ x | y | z ] && <angle>
5196
0
    if (tokens.remaining_token_count() == 2) {
5197
        // Try parsing `x <angle>`
5198
0
        if (auto axis = parse_one_of_xyz(); axis.has_value()) {
5199
0
            if (auto angle = parse_angle_value(tokens); angle) {
5200
0
                if (axis->is_ident("x"sv))
5201
0
                    return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(1), NumberStyleValue::create(0), NumberStyleValue::create(0));
5202
0
                if (axis->is_ident("y"sv))
5203
0
                    return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(0), NumberStyleValue::create(1), NumberStyleValue::create(0));
5204
0
                if (axis->is_ident("z"sv))
5205
0
                    return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(0), NumberStyleValue::create(0), NumberStyleValue::create(1));
5206
0
            }
5207
0
        }
5208
5209
        // Try parsing `<angle> x`
5210
0
        if (auto angle = parse_angle_value(tokens); angle) {
5211
0
            if (auto axis = parse_one_of_xyz(); axis.has_value()) {
5212
0
                if (axis->is_ident("x"sv))
5213
0
                    return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(1), NumberStyleValue::create(0), NumberStyleValue::create(0));
5214
0
                if (axis->is_ident("y"sv))
5215
0
                    return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(0), NumberStyleValue::create(1), NumberStyleValue::create(0));
5216
0
                if (axis->is_ident("z"sv))
5217
0
                    return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(0), NumberStyleValue::create(0), NumberStyleValue::create(1));
5218
0
            }
5219
0
        }
5220
0
    }
5221
5222
0
    auto parse_three_numbers = [&]() -> Optional<StyleValueVector> {
5223
0
        auto transaction = tokens.begin_transaction();
5224
0
        StyleValueVector numbers;
5225
0
        for (size_t i = 0; i < 3; ++i) {
5226
0
            if (auto number = parse_number_value(tokens); number) {
5227
0
                numbers.append(number.release_nonnull());
5228
0
            } else {
5229
0
                return {};
5230
0
            }
5231
0
        }
5232
0
        transaction.commit();
5233
0
        return numbers;
5234
0
    };
5235
5236
    // <number>{3} && <angle>
5237
0
    if (tokens.remaining_token_count() == 4) {
5238
        // Try parsing <number>{3} <angle>
5239
0
        if (auto maybe_numbers = parse_three_numbers(); maybe_numbers.has_value()) {
5240
0
            if (auto angle = parse_angle_value(tokens); angle) {
5241
0
                auto numbers = maybe_numbers.release_value();
5242
0
                return RotationStyleValue::create(angle.release_nonnull(), numbers[0], numbers[1], numbers[2]);
5243
0
            }
5244
0
        }
5245
5246
        // Try parsing <angle> <number>{3}
5247
0
        if (auto angle = parse_angle_value(tokens); angle) {
5248
0
            if (auto maybe_numbers = parse_three_numbers(); maybe_numbers.has_value()) {
5249
0
                auto numbers = maybe_numbers.release_value();
5250
0
                return RotationStyleValue::create(angle.release_nonnull(), numbers[0], numbers[1], numbers[2]);
5251
0
            }
5252
0
        }
5253
0
    }
5254
5255
0
    return nullptr;
5256
0
}
5257
5258
RefPtr<CSSStyleValue> Parser::parse_content_value(TokenStream<ComponentValue>& tokens)
5259
0
{
5260
    // FIXME: `content` accepts several kinds of function() type, which we don't handle in property_accepts_value() yet.
5261
5262
0
    auto is_single_value_keyword = [](Keyword keyword) -> bool {
5263
0
        switch (keyword) {
5264
0
        case Keyword::None:
5265
0
        case Keyword::Normal:
5266
0
            return true;
5267
0
        default:
5268
0
            return false;
5269
0
        }
5270
0
    };
5271
5272
0
    if (tokens.remaining_token_count() == 1) {
5273
0
        auto transaction = tokens.begin_transaction();
5274
0
        if (auto keyword = parse_keyword_value(tokens)) {
5275
0
            if (is_single_value_keyword(keyword->to_keyword())) {
5276
0
                transaction.commit();
5277
0
                return keyword;
5278
0
            }
5279
0
        }
5280
0
    }
5281
5282
0
    auto transaction = tokens.begin_transaction();
5283
5284
0
    StyleValueVector content_values;
5285
0
    StyleValueVector alt_text_values;
5286
0
    bool in_alt_text = false;
5287
5288
0
    while (tokens.has_next_token()) {
5289
0
        auto& next = tokens.next_token();
5290
0
        if (next.is_delim('/')) {
5291
0
            if (in_alt_text || content_values.is_empty())
5292
0
                return nullptr;
5293
0
            in_alt_text = true;
5294
0
            tokens.discard_a_token();
5295
0
            continue;
5296
0
        }
5297
5298
0
        if (auto style_value = parse_css_value_for_property(PropertyID::Content, tokens)) {
5299
0
            if (is_single_value_keyword(style_value->to_keyword()))
5300
0
                return nullptr;
5301
5302
0
            if (in_alt_text) {
5303
0
                alt_text_values.append(style_value.release_nonnull());
5304
0
            } else {
5305
0
                content_values.append(style_value.release_nonnull());
5306
0
            }
5307
0
            continue;
5308
0
        }
5309
5310
0
        return nullptr;
5311
0
    }
5312
5313
0
    if (content_values.is_empty())
5314
0
        return nullptr;
5315
0
    if (in_alt_text && alt_text_values.is_empty())
5316
0
        return nullptr;
5317
5318
0
    RefPtr<StyleValueList> alt_text;
5319
0
    if (!alt_text_values.is_empty())
5320
0
        alt_text = StyleValueList::create(move(alt_text_values), StyleValueList::Separator::Space);
5321
5322
0
    transaction.commit();
5323
0
    return ContentStyleValue::create(StyleValueList::create(move(content_values), StyleValueList::Separator::Space), move(alt_text));
5324
0
}
5325
5326
// https://drafts.csswg.org/css-lists-3/#propdef-counter-increment
5327
RefPtr<CSSStyleValue> Parser::parse_counter_increment_value(TokenStream<ComponentValue>& tokens)
5328
0
{
5329
    // [ <counter-name> <integer>? ]+ | none
5330
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
5331
0
        return none;
5332
5333
0
    return parse_counter_definitions_value(tokens, AllowReversed::No, 1);
5334
0
}
5335
5336
// https://drafts.csswg.org/css-lists-3/#propdef-counter-reset
5337
RefPtr<CSSStyleValue> Parser::parse_counter_reset_value(TokenStream<ComponentValue>& tokens)
5338
0
{
5339
    // [ <counter-name> <integer>? | <reversed-counter-name> <integer>? ]+ | none
5340
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
5341
0
        return none;
5342
5343
0
    return parse_counter_definitions_value(tokens, AllowReversed::Yes, 0);
5344
0
}
5345
5346
// https://drafts.csswg.org/css-lists-3/#propdef-counter-set
5347
RefPtr<CSSStyleValue> Parser::parse_counter_set_value(TokenStream<ComponentValue>& tokens)
5348
0
{
5349
    // [ <counter-name> <integer>? ]+ | none
5350
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
5351
0
        return none;
5352
5353
0
    return parse_counter_definitions_value(tokens, AllowReversed::No, 0);
5354
0
}
5355
5356
// https://www.w3.org/TR/css-display-3/#the-display-properties
5357
RefPtr<CSSStyleValue> Parser::parse_display_value(TokenStream<ComponentValue>& tokens)
5358
0
{
5359
0
    auto parse_single_component_display = [this](TokenStream<ComponentValue>& tokens) -> Optional<Display> {
5360
0
        auto transaction = tokens.begin_transaction();
5361
0
        if (auto keyword_value = parse_keyword_value(tokens)) {
5362
0
            auto keyword = keyword_value->to_keyword();
5363
0
            if (keyword == Keyword::ListItem) {
5364
0
                transaction.commit();
5365
0
                return Display::from_short(Display::Short::ListItem);
5366
0
            }
5367
5368
0
            if (auto display_outside = keyword_to_display_outside(keyword); display_outside.has_value()) {
5369
0
                transaction.commit();
5370
0
                switch (display_outside.value()) {
5371
0
                case DisplayOutside::Block:
5372
0
                    return Display::from_short(Display::Short::Block);
5373
0
                case DisplayOutside::Inline:
5374
0
                    return Display::from_short(Display::Short::Inline);
5375
0
                case DisplayOutside::RunIn:
5376
0
                    return Display::from_short(Display::Short::RunIn);
5377
0
                }
5378
0
            }
5379
5380
0
            if (auto display_inside = keyword_to_display_inside(keyword); display_inside.has_value()) {
5381
0
                transaction.commit();
5382
0
                switch (display_inside.value()) {
5383
0
                case DisplayInside::Flow:
5384
0
                    return Display::from_short(Display::Short::Flow);
5385
0
                case DisplayInside::FlowRoot:
5386
0
                    return Display::from_short(Display::Short::FlowRoot);
5387
0
                case DisplayInside::Table:
5388
0
                    return Display::from_short(Display::Short::Table);
5389
0
                case DisplayInside::Flex:
5390
0
                    return Display::from_short(Display::Short::Flex);
5391
0
                case DisplayInside::Grid:
5392
0
                    return Display::from_short(Display::Short::Grid);
5393
0
                case DisplayInside::Ruby:
5394
0
                    return Display::from_short(Display::Short::Ruby);
5395
0
                case DisplayInside::Math:
5396
0
                    return Display::from_short(Display::Short::Math);
5397
0
                }
5398
0
            }
5399
5400
0
            if (auto display_internal = keyword_to_display_internal(keyword); display_internal.has_value()) {
5401
0
                transaction.commit();
5402
0
                return Display { display_internal.value() };
5403
0
            }
5404
5405
0
            if (auto display_box = keyword_to_display_box(keyword); display_box.has_value()) {
5406
0
                transaction.commit();
5407
0
                switch (display_box.value()) {
5408
0
                case DisplayBox::Contents:
5409
0
                    return Display::from_short(Display::Short::Contents);
5410
0
                case DisplayBox::None:
5411
0
                    return Display::from_short(Display::Short::None);
5412
0
                }
5413
0
            }
5414
5415
0
            if (auto display_legacy = keyword_to_display_legacy(keyword); display_legacy.has_value()) {
5416
0
                transaction.commit();
5417
0
                switch (display_legacy.value()) {
5418
0
                case DisplayLegacy::InlineBlock:
5419
0
                    return Display::from_short(Display::Short::InlineBlock);
5420
0
                case DisplayLegacy::InlineTable:
5421
0
                    return Display::from_short(Display::Short::InlineTable);
5422
0
                case DisplayLegacy::InlineFlex:
5423
0
                    return Display::from_short(Display::Short::InlineFlex);
5424
0
                case DisplayLegacy::InlineGrid:
5425
0
                    return Display::from_short(Display::Short::InlineGrid);
5426
0
                }
5427
0
            }
5428
0
        }
5429
0
        return OptionalNone {};
5430
0
    };
5431
5432
0
    auto parse_multi_component_display = [this](TokenStream<ComponentValue>& tokens) -> Optional<Display> {
5433
0
        auto list_item = Display::ListItem::No;
5434
0
        Optional<DisplayInside> inside;
5435
0
        Optional<DisplayOutside> outside;
5436
5437
0
        auto transaction = tokens.begin_transaction();
5438
0
        while (tokens.has_next_token()) {
5439
0
            if (auto value = parse_keyword_value(tokens)) {
5440
0
                auto keyword = value->to_keyword();
5441
0
                if (keyword == Keyword::ListItem) {
5442
0
                    if (list_item == Display::ListItem::Yes)
5443
0
                        return {};
5444
0
                    list_item = Display::ListItem::Yes;
5445
0
                    continue;
5446
0
                }
5447
0
                if (auto inside_value = keyword_to_display_inside(keyword); inside_value.has_value()) {
5448
0
                    if (inside.has_value())
5449
0
                        return {};
5450
0
                    inside = inside_value.value();
5451
0
                    continue;
5452
0
                }
5453
0
                if (auto outside_value = keyword_to_display_outside(keyword); outside_value.has_value()) {
5454
0
                    if (outside.has_value())
5455
0
                        return {};
5456
0
                    outside = outside_value.value();
5457
0
                    continue;
5458
0
                }
5459
0
            }
5460
5461
            // Not a display value, abort.
5462
0
            dbgln_if(CSS_PARSER_DEBUG, "Unrecognized display value: `{}`", tokens.next_token().to_string());
5463
0
            return {};
5464
0
        }
5465
5466
        // The spec does not allow any other inside values to be combined with list-item
5467
        // <display-outside>? && [ flow | flow-root ]? && list-item
5468
0
        if (list_item == Display::ListItem::Yes && inside.has_value() && inside != DisplayInside::Flow && inside != DisplayInside::FlowRoot)
5469
0
            return {};
5470
5471
0
        transaction.commit();
5472
0
        return Display { outside.value_or(DisplayOutside::Block), inside.value_or(DisplayInside::Flow), list_item };
5473
0
    };
5474
5475
0
    Optional<Display> display;
5476
0
    if (tokens.remaining_token_count() == 1)
5477
0
        display = parse_single_component_display(tokens);
5478
0
    else
5479
0
        display = parse_multi_component_display(tokens);
5480
5481
0
    if (display.has_value())
5482
0
        return DisplayStyleValue::create(display.value());
5483
5484
0
    return nullptr;
5485
0
}
5486
5487
RefPtr<CSSStyleValue> Parser::parse_filter_value_list_value(TokenStream<ComponentValue>& tokens)
5488
0
{
5489
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
5490
0
        return none;
5491
5492
0
    auto transaction = tokens.begin_transaction();
5493
5494
    // FIXME: <url>s are ignored for now
5495
    // <filter-value-list> = [ <filter-function> | <url> ]+
5496
5497
0
    enum class FilterToken {
5498
        // Color filters:
5499
0
        Brightness,
5500
0
        Contrast,
5501
0
        Grayscale,
5502
0
        Invert,
5503
0
        Opacity,
5504
0
        Saturate,
5505
0
        Sepia,
5506
        // Special filters:
5507
0
        Blur,
5508
0
        DropShadow,
5509
0
        HueRotate
5510
0
    };
5511
5512
0
    auto filter_token_to_operation = [&](auto filter) {
5513
0
        VERIFY(to_underlying(filter) < to_underlying(FilterToken::Blur));
5514
0
        return static_cast<FilterOperation::Color::Type>(filter);
5515
0
    };
5516
5517
0
    auto parse_number_percentage = [&](auto& token) -> Optional<NumberPercentage> {
5518
0
        if (token.is(Token::Type::Percentage))
5519
0
            return NumberPercentage(Percentage(token.token().percentage()));
5520
0
        if (token.is(Token::Type::Number))
5521
0
            return NumberPercentage(Number(Number::Type::Number, token.token().number_value()));
5522
0
        return {};
5523
0
    };
5524
5525
0
    auto parse_filter_function_name = [&](auto name) -> Optional<FilterToken> {
5526
0
        if (name.equals_ignoring_ascii_case("blur"sv))
5527
0
            return FilterToken::Blur;
5528
0
        if (name.equals_ignoring_ascii_case("brightness"sv))
5529
0
            return FilterToken::Brightness;
5530
0
        if (name.equals_ignoring_ascii_case("contrast"sv))
5531
0
            return FilterToken::Contrast;
5532
0
        if (name.equals_ignoring_ascii_case("drop-shadow"sv))
5533
0
            return FilterToken::DropShadow;
5534
0
        if (name.equals_ignoring_ascii_case("grayscale"sv))
5535
0
            return FilterToken::Grayscale;
5536
0
        if (name.equals_ignoring_ascii_case("hue-rotate"sv))
5537
0
            return FilterToken::HueRotate;
5538
0
        if (name.equals_ignoring_ascii_case("invert"sv))
5539
0
            return FilterToken::Invert;
5540
0
        if (name.equals_ignoring_ascii_case("opacity"sv))
5541
0
            return FilterToken::Opacity;
5542
0
        if (name.equals_ignoring_ascii_case("saturate"sv))
5543
0
            return FilterToken::Saturate;
5544
0
        if (name.equals_ignoring_ascii_case("sepia"sv))
5545
0
            return FilterToken::Sepia;
5546
0
        return {};
5547
0
    };
5548
5549
0
    auto parse_filter_function = [&](auto filter_token, auto function_values) -> Optional<FilterFunction> {
5550
0
        TokenStream tokens { function_values };
5551
0
        tokens.discard_whitespace();
5552
5553
0
        auto if_no_more_tokens_return = [&](auto filter) -> Optional<FilterFunction> {
5554
0
            tokens.discard_whitespace();
5555
0
            if (tokens.has_next_token())
5556
0
                return {};
5557
0
            return filter;
5558
0
        };
Unexecuted instantiation: Parser.cpp:AK::Optional<AK::Variant<Web::CSS::FilterOperation::Blur, Web::CSS::FilterOperation::DropShadow, Web::CSS::FilterOperation::HueRotate, Web::CSS::FilterOperation::Color> > Web::CSS::Parser::Parser::parse_filter_value_list_value(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1::operator()<Web::CSS::Parser::Parser::parse_filter_value_list_value(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::FilterToken, AK::Vector<Web::CSS::Parser::ComponentValue, 0ul> >(Web::CSS::Parser::Parser::parse_filter_value_list_value(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::FilterToken, AK::Vector<Web::CSS::Parser::ComponentValue, 0ul>) const::{lambda(auto:1)#1}::operator()<Web::CSS::FilterOperation::Blur>(Web::CSS::FilterOperation::Blur) const
Unexecuted instantiation: Parser.cpp:AK::Optional<AK::Variant<Web::CSS::FilterOperation::Blur, Web::CSS::FilterOperation::DropShadow, Web::CSS::FilterOperation::HueRotate, Web::CSS::FilterOperation::Color> > Web::CSS::Parser::Parser::parse_filter_value_list_value(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1::operator()<Web::CSS::Parser::Parser::parse_filter_value_list_value(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::FilterToken, AK::Vector<Web::CSS::Parser::ComponentValue, 0ul> >(Web::CSS::Parser::Parser::parse_filter_value_list_value(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::FilterToken, AK::Vector<Web::CSS::Parser::ComponentValue, 0ul>) const::{lambda(auto:1)#1}::operator()<Web::CSS::FilterOperation::DropShadow>(Web::CSS::FilterOperation::DropShadow) const
Unexecuted instantiation: Parser.cpp:AK::Optional<AK::Variant<Web::CSS::FilterOperation::Blur, Web::CSS::FilterOperation::DropShadow, Web::CSS::FilterOperation::HueRotate, Web::CSS::FilterOperation::Color> > Web::CSS::Parser::Parser::parse_filter_value_list_value(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1::operator()<Web::CSS::Parser::Parser::parse_filter_value_list_value(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::FilterToken, AK::Vector<Web::CSS::Parser::ComponentValue, 0ul> >(Web::CSS::Parser::Parser::parse_filter_value_list_value(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::FilterToken, AK::Vector<Web::CSS::Parser::ComponentValue, 0ul>) const::{lambda(auto:1)#1}::operator()<Web::CSS::FilterOperation::HueRotate>(Web::CSS::FilterOperation::HueRotate) const
Unexecuted instantiation: Parser.cpp:AK::Optional<AK::Variant<Web::CSS::FilterOperation::Blur, Web::CSS::FilterOperation::DropShadow, Web::CSS::FilterOperation::HueRotate, Web::CSS::FilterOperation::Color> > Web::CSS::Parser::Parser::parse_filter_value_list_value(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::$_1::operator()<Web::CSS::Parser::Parser::parse_filter_value_list_value(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::FilterToken, AK::Vector<Web::CSS::Parser::ComponentValue, 0ul> >(Web::CSS::Parser::Parser::parse_filter_value_list_value(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::FilterToken, AK::Vector<Web::CSS::Parser::ComponentValue, 0ul>) const::{lambda(auto:1)#1}::operator()<Web::CSS::FilterOperation::Color>(Web::CSS::FilterOperation::Color) const
5559
5560
0
        if (filter_token == FilterToken::Blur) {
5561
            // blur( <length>? )
5562
0
            if (!tokens.has_next_token())
5563
0
                return FilterOperation::Blur {};
5564
0
            auto blur_radius = parse_length(tokens);
5565
0
            tokens.discard_whitespace();
5566
0
            if (!blur_radius.has_value())
5567
0
                return {};
5568
            // FIXME: Support calculated radius
5569
0
            return if_no_more_tokens_return(FilterOperation::Blur { blur_radius->value() });
5570
0
        } else if (filter_token == FilterToken::DropShadow) {
5571
0
            if (!tokens.has_next_token())
5572
0
                return {};
5573
            // drop-shadow( [ <color>? && <length>{2,3} ] )
5574
            // Note: The following code is a little awkward to allow the color to be before or after the lengths.
5575
0
            Optional<LengthOrCalculated> maybe_radius = {};
5576
0
            auto maybe_color = parse_color_value(tokens);
5577
0
            auto x_offset = parse_length(tokens);
5578
0
            tokens.discard_whitespace();
5579
0
            if (!x_offset.has_value() || !tokens.has_next_token())
5580
0
                return {};
5581
5582
0
            auto y_offset = parse_length(tokens);
5583
0
            tokens.discard_whitespace();
5584
0
            if (!y_offset.has_value())
5585
0
                return {};
5586
5587
0
            if (tokens.has_next_token()) {
5588
0
                maybe_radius = parse_length(tokens);
5589
0
                tokens.discard_whitespace();
5590
0
                if (!maybe_color && (!maybe_radius.has_value() || tokens.has_next_token())) {
5591
0
                    maybe_color = parse_color_value(tokens);
5592
0
                    if (!maybe_color)
5593
0
                        return {};
5594
0
                } else if (!maybe_radius.has_value()) {
5595
0
                    return {};
5596
0
                }
5597
0
            }
5598
            // FIXME: Support calculated offsets and radius
5599
0
            return if_no_more_tokens_return(FilterOperation::DropShadow { x_offset->value(), y_offset->value(), maybe_radius.map([](auto& it) { return it.value(); }), maybe_color->to_color({}) });
5600
0
        } else if (filter_token == FilterToken::HueRotate) {
5601
            // hue-rotate( [ <angle> | <zero> ]? )
5602
0
            if (!tokens.has_next_token())
5603
0
                return FilterOperation::HueRotate {};
5604
0
            auto& token = tokens.consume_a_token();
5605
0
            if (token.is(Token::Type::Number)) {
5606
                // hue-rotate(0)
5607
0
                auto number = token.token().number();
5608
0
                if (number.is_integer() && number.integer_value() == 0)
5609
0
                    return if_no_more_tokens_return(FilterOperation::HueRotate { FilterOperation::HueRotate::Zero {} });
5610
0
                return {};
5611
0
            }
5612
0
            if (!token.is(Token::Type::Dimension))
5613
0
                return {};
5614
0
            auto angle_value = token.token().dimension_value();
5615
0
            auto angle_unit_name = token.token().dimension_unit();
5616
0
            auto angle_unit = Angle::unit_from_name(angle_unit_name);
5617
0
            if (!angle_unit.has_value())
5618
0
                return {};
5619
0
            Angle angle { angle_value, angle_unit.release_value() };
5620
0
            return if_no_more_tokens_return(FilterOperation::HueRotate { angle });
5621
0
        } else {
5622
            // Simple filters:
5623
            // brightness( <number-percentage>? )
5624
            // contrast( <number-percentage>? )
5625
            // grayscale( <number-percentage>? )
5626
            // invert( <number-percentage>? )
5627
            // opacity( <number-percentage>? )
5628
            // sepia( <number-percentage>? )
5629
            // saturate( <number-percentage>? )
5630
0
            if (!tokens.has_next_token())
5631
0
                return FilterOperation::Color { filter_token_to_operation(filter_token) };
5632
0
            auto amount = parse_number_percentage(tokens.consume_a_token());
5633
0
            if (!amount.has_value())
5634
0
                return {};
5635
0
            return if_no_more_tokens_return(FilterOperation::Color { filter_token_to_operation(filter_token), *amount });
5636
0
        }
5637
0
    };
5638
5639
0
    Vector<FilterFunction> filter_value_list {};
5640
5641
0
    while (tokens.has_next_token()) {
5642
0
        tokens.discard_whitespace();
5643
0
        if (!tokens.has_next_token())
5644
0
            break;
5645
0
        auto& token = tokens.consume_a_token();
5646
0
        if (!token.is_function())
5647
0
            return nullptr;
5648
0
        auto filter_token = parse_filter_function_name(token.function().name);
5649
0
        if (!filter_token.has_value())
5650
0
            return nullptr;
5651
0
        auto filter_function = parse_filter_function(*filter_token, token.function().value);
5652
0
        if (!filter_function.has_value())
5653
0
            return nullptr;
5654
0
        filter_value_list.append(*filter_function);
5655
0
    }
5656
5657
0
    if (filter_value_list.is_empty())
5658
0
        return nullptr;
5659
5660
0
    transaction.commit();
5661
0
    return FilterValueListStyleValue::create(move(filter_value_list));
5662
0
}
5663
5664
RefPtr<CSSStyleValue> Parser::parse_flex_shorthand_value(TokenStream<ComponentValue>& tokens)
5665
0
{
5666
0
    auto transaction = tokens.begin_transaction();
5667
5668
0
    auto make_flex_shorthand = [&](NonnullRefPtr<CSSStyleValue> flex_grow, NonnullRefPtr<CSSStyleValue> flex_shrink, NonnullRefPtr<CSSStyleValue> flex_basis) {
5669
0
        transaction.commit();
5670
0
        return ShorthandStyleValue::create(PropertyID::Flex,
5671
0
            { PropertyID::FlexGrow, PropertyID::FlexShrink, PropertyID::FlexBasis },
5672
0
            { move(flex_grow), move(flex_shrink), move(flex_basis) });
5673
0
    };
5674
5675
0
    if (tokens.remaining_token_count() == 1) {
5676
        // One-value syntax: <flex-grow> | <flex-basis> | none
5677
0
        auto properties = Array { PropertyID::FlexGrow, PropertyID::FlexBasis, PropertyID::Flex };
5678
0
        auto property_and_value = parse_css_value_for_properties(properties, tokens);
5679
0
        if (!property_and_value.has_value())
5680
0
            return nullptr;
5681
5682
0
        auto& value = property_and_value->style_value;
5683
0
        switch (property_and_value->property) {
5684
0
        case PropertyID::FlexGrow: {
5685
            // NOTE: The spec says that flex-basis should be 0 here, but other engines currently use 0%.
5686
            // https://github.com/w3c/csswg-drafts/issues/5742
5687
0
            auto flex_basis = PercentageStyleValue::create(Percentage(0));
5688
0
            auto one = NumberStyleValue::create(1);
5689
0
            return make_flex_shorthand(*value, one, flex_basis);
5690
0
        }
5691
0
        case PropertyID::FlexBasis: {
5692
0
            auto one = NumberStyleValue::create(1);
5693
0
            return make_flex_shorthand(one, one, *value);
5694
0
        }
5695
0
        case PropertyID::Flex: {
5696
0
            if (value->is_keyword() && value->to_keyword() == Keyword::None) {
5697
0
                auto zero = NumberStyleValue::create(0);
5698
0
                return make_flex_shorthand(zero, zero, CSSKeywordValue::create(Keyword::Auto));
5699
0
            }
5700
0
            break;
5701
0
        }
5702
0
        default:
5703
0
            VERIFY_NOT_REACHED();
5704
0
        }
5705
5706
0
        return nullptr;
5707
0
    }
5708
5709
0
    RefPtr<CSSStyleValue> flex_grow;
5710
0
    RefPtr<CSSStyleValue> flex_shrink;
5711
0
    RefPtr<CSSStyleValue> flex_basis;
5712
5713
    // NOTE: FlexGrow has to be before FlexBasis. `0` is a valid FlexBasis, but only
5714
    //       if FlexGrow (along with optional FlexShrink) have already been specified.
5715
0
    auto remaining_longhands = Vector { PropertyID::FlexGrow, PropertyID::FlexBasis };
5716
5717
0
    while (tokens.has_next_token()) {
5718
0
        auto property_and_value = parse_css_value_for_properties(remaining_longhands, tokens);
5719
0
        if (!property_and_value.has_value())
5720
0
            return nullptr;
5721
0
        auto& value = property_and_value->style_value;
5722
0
        remove_property(remaining_longhands, property_and_value->property);
5723
5724
0
        switch (property_and_value->property) {
5725
0
        case PropertyID::FlexGrow: {
5726
0
            VERIFY(!flex_grow);
5727
0
            flex_grow = value.release_nonnull();
5728
5729
            // Flex-shrink may optionally follow directly after.
5730
0
            auto maybe_flex_shrink = parse_css_value_for_property(PropertyID::FlexShrink, tokens);
5731
0
            if (maybe_flex_shrink)
5732
0
                flex_shrink = maybe_flex_shrink.release_nonnull();
5733
0
            continue;
5734
0
        }
5735
0
        case PropertyID::FlexBasis: {
5736
0
            VERIFY(!flex_basis);
5737
0
            flex_basis = value.release_nonnull();
5738
0
            continue;
5739
0
        }
5740
0
        default:
5741
0
            VERIFY_NOT_REACHED();
5742
0
        }
5743
0
    }
5744
5745
0
    if (!flex_grow)
5746
0
        flex_grow = property_initial_value(m_context.realm(), PropertyID::FlexGrow);
5747
0
    if (!flex_shrink)
5748
0
        flex_shrink = property_initial_value(m_context.realm(), PropertyID::FlexShrink);
5749
0
    if (!flex_basis) {
5750
        // NOTE: The spec says that flex-basis should be 0 here, but other engines currently use 0%.
5751
        // https://github.com/w3c/csswg-drafts/issues/5742
5752
0
        flex_basis = PercentageStyleValue::create(Percentage(0));
5753
0
    }
5754
5755
0
    return make_flex_shorthand(flex_grow.release_nonnull(), flex_shrink.release_nonnull(), flex_basis.release_nonnull());
5756
0
}
5757
5758
RefPtr<CSSStyleValue> Parser::parse_flex_flow_value(TokenStream<ComponentValue>& tokens)
5759
0
{
5760
0
    RefPtr<CSSStyleValue> flex_direction;
5761
0
    RefPtr<CSSStyleValue> flex_wrap;
5762
5763
0
    auto remaining_longhands = Vector { PropertyID::FlexDirection, PropertyID::FlexWrap };
5764
0
    auto transaction = tokens.begin_transaction();
5765
5766
0
    while (tokens.has_next_token()) {
5767
0
        auto property_and_value = parse_css_value_for_properties(remaining_longhands, tokens);
5768
0
        if (!property_and_value.has_value())
5769
0
            return nullptr;
5770
0
        auto& value = property_and_value->style_value;
5771
0
        remove_property(remaining_longhands, property_and_value->property);
5772
5773
0
        switch (property_and_value->property) {
5774
0
        case PropertyID::FlexDirection:
5775
0
            VERIFY(!flex_direction);
5776
0
            flex_direction = value.release_nonnull();
5777
0
            continue;
5778
0
        case PropertyID::FlexWrap:
5779
0
            VERIFY(!flex_wrap);
5780
0
            flex_wrap = value.release_nonnull();
5781
0
            continue;
5782
0
        default:
5783
0
            VERIFY_NOT_REACHED();
5784
0
        }
5785
0
    }
5786
5787
0
    if (!flex_direction)
5788
0
        flex_direction = property_initial_value(m_context.realm(), PropertyID::FlexDirection);
5789
0
    if (!flex_wrap)
5790
0
        flex_wrap = property_initial_value(m_context.realm(), PropertyID::FlexWrap);
5791
5792
0
    transaction.commit();
5793
0
    return ShorthandStyleValue::create(PropertyID::FlexFlow,
5794
0
        { PropertyID::FlexDirection, PropertyID::FlexWrap },
5795
0
        { flex_direction.release_nonnull(), flex_wrap.release_nonnull() });
5796
0
}
5797
5798
static bool is_generic_font_family(Keyword keyword)
5799
0
{
5800
0
    switch (keyword) {
5801
0
    case Keyword::Cursive:
5802
0
    case Keyword::Fantasy:
5803
0
    case Keyword::Monospace:
5804
0
    case Keyword::Serif:
5805
0
    case Keyword::SansSerif:
5806
0
    case Keyword::UiMonospace:
5807
0
    case Keyword::UiRounded:
5808
0
    case Keyword::UiSerif:
5809
0
    case Keyword::UiSansSerif:
5810
0
        return true;
5811
0
    default:
5812
0
        return false;
5813
0
    }
5814
0
}
5815
5816
RefPtr<CSSStyleValue> Parser::parse_font_value(TokenStream<ComponentValue>& tokens)
5817
0
{
5818
0
    RefPtr<CSSStyleValue> font_width;
5819
0
    RefPtr<CSSStyleValue> font_style;
5820
0
    RefPtr<CSSStyleValue> font_weight;
5821
0
    RefPtr<CSSStyleValue> font_size;
5822
0
    RefPtr<CSSStyleValue> line_height;
5823
0
    RefPtr<CSSStyleValue> font_families;
5824
0
    RefPtr<CSSStyleValue> font_variant;
5825
5826
    // FIXME: Handle system fonts. (caption, icon, menu, message-box, small-caption, status-bar)
5827
5828
    // Several sub-properties can be "normal", and appear in any order: style, variant, weight, stretch
5829
    // So, we have to handle that separately.
5830
0
    int normal_count = 0;
5831
5832
    // FIXME: `font-variant` allows a lot of different values which aren't allowed in the `font` shorthand.
5833
    // FIXME: `font-width` allows <percentage> values, which aren't allowed in the `font` shorthand.
5834
0
    auto remaining_longhands = Vector { PropertyID::FontSize, PropertyID::FontStyle, PropertyID::FontVariant, PropertyID::FontWeight, PropertyID::FontWidth };
5835
0
    auto transaction = tokens.begin_transaction();
5836
5837
0
    while (tokens.has_next_token()) {
5838
0
        auto& peek_token = tokens.next_token();
5839
0
        if (peek_token.is_ident("normal"sv)) {
5840
0
            normal_count++;
5841
0
            tokens.discard_a_token();
5842
0
            continue;
5843
0
        }
5844
5845
0
        auto property_and_value = parse_css_value_for_properties(remaining_longhands, tokens);
5846
0
        if (!property_and_value.has_value())
5847
0
            return nullptr;
5848
0
        auto& value = property_and_value->style_value;
5849
0
        remove_property(remaining_longhands, property_and_value->property);
5850
5851
0
        switch (property_and_value->property) {
5852
0
        case PropertyID::FontSize: {
5853
0
            VERIFY(!font_size);
5854
0
            font_size = value.release_nonnull();
5855
5856
            // Consume `/ line-height` if present
5857
0
            if (tokens.next_token().is_delim('/')) {
5858
0
                tokens.discard_a_token();
5859
0
                auto maybe_line_height = parse_css_value_for_property(PropertyID::LineHeight, tokens);
5860
0
                if (!maybe_line_height)
5861
0
                    return nullptr;
5862
0
                line_height = maybe_line_height.release_nonnull();
5863
0
            }
5864
5865
            // Consume font-families
5866
0
            auto maybe_font_families = parse_font_family_value(tokens);
5867
            // font-family comes last, so we must not have any tokens left over.
5868
0
            if (!maybe_font_families || tokens.has_next_token())
5869
0
                return nullptr;
5870
0
            font_families = maybe_font_families.release_nonnull();
5871
0
            continue;
5872
0
        }
5873
0
        case PropertyID::FontWidth: {
5874
0
            VERIFY(!font_width);
5875
0
            font_width = value.release_nonnull();
5876
0
            continue;
5877
0
        }
5878
0
        case PropertyID::FontStyle: {
5879
0
            VERIFY(!font_style);
5880
0
            font_style = value.release_nonnull();
5881
0
            continue;
5882
0
        }
5883
0
        case PropertyID::FontVariant: {
5884
0
            VERIFY(!font_variant);
5885
0
            font_variant = value.release_nonnull();
5886
0
            continue;
5887
0
        }
5888
0
        case PropertyID::FontWeight: {
5889
0
            VERIFY(!font_weight);
5890
0
            font_weight = value.release_nonnull();
5891
0
            continue;
5892
0
        }
5893
0
        default:
5894
0
            VERIFY_NOT_REACHED();
5895
0
        }
5896
5897
0
        return nullptr;
5898
0
    }
5899
5900
    // Since normal is the default value for all the properties that can have it, we don't have to actually
5901
    // set anything to normal here. It'll be set when we create the ShorthandStyleValue below.
5902
    // We just need to make sure we were not given more normals than will fit.
5903
0
    int unset_value_count = (font_style ? 0 : 1) + (font_weight ? 0 : 1) + (font_variant ? 0 : 1) + (font_width ? 0 : 1);
5904
0
    if (unset_value_count < normal_count)
5905
0
        return nullptr;
5906
5907
0
    if (!font_size || !font_families)
5908
0
        return nullptr;
5909
5910
0
    if (!font_style)
5911
0
        font_style = property_initial_value(m_context.realm(), PropertyID::FontStyle);
5912
0
    if (!font_variant)
5913
0
        font_variant = property_initial_value(m_context.realm(), PropertyID::FontVariant);
5914
0
    if (!font_weight)
5915
0
        font_weight = property_initial_value(m_context.realm(), PropertyID::FontWeight);
5916
0
    if (!font_width)
5917
0
        font_width = property_initial_value(m_context.realm(), PropertyID::FontWidth);
5918
0
    if (!line_height)
5919
0
        line_height = property_initial_value(m_context.realm(), PropertyID::LineHeight);
5920
5921
0
    transaction.commit();
5922
0
    return ShorthandStyleValue::create(PropertyID::Font,
5923
0
        { PropertyID::FontStyle, PropertyID::FontVariant, PropertyID::FontWeight, PropertyID::FontWidth, PropertyID::FontSize, PropertyID::LineHeight, PropertyID::FontFamily },
5924
0
        { font_style.release_nonnull(), font_variant.release_nonnull(), font_weight.release_nonnull(), font_width.release_nonnull(), font_size.release_nonnull(), line_height.release_nonnull(), font_families.release_nonnull() });
5925
0
}
5926
5927
RefPtr<CSSStyleValue> Parser::parse_font_family_value(TokenStream<ComponentValue>& tokens)
5928
0
{
5929
0
    auto next_is_comma_or_eof = [&]() -> bool {
5930
0
        return !tokens.has_next_token() || tokens.next_token().is(Token::Type::Comma);
5931
0
    };
5932
5933
    // Note: Font-family names can either be a quoted string, or a keyword, or a series of custom-idents.
5934
    // eg, these are equivalent:
5935
    //     font-family: my cool     font\!, serif;
5936
    //     font-family: "my cool font!", serif;
5937
0
    StyleValueVector font_families;
5938
0
    Vector<String> current_name_parts;
5939
0
    while (tokens.has_next_token()) {
5940
0
        auto const& peek = tokens.next_token();
5941
5942
0
        if (peek.is(Token::Type::String)) {
5943
            // `font-family: my cool "font";` is invalid.
5944
0
            if (!current_name_parts.is_empty())
5945
0
                return nullptr;
5946
0
            tokens.discard_a_token(); // String
5947
0
            if (!next_is_comma_or_eof())
5948
0
                return nullptr;
5949
0
            font_families.append(StringStyleValue::create(peek.token().string()));
5950
0
            tokens.discard_a_token(); // Comma
5951
0
            continue;
5952
0
        }
5953
5954
0
        if (peek.is(Token::Type::Ident)) {
5955
            // If this is a valid identifier, it's NOT a custom-ident and can't be part of a larger name.
5956
5957
            // CSS-wide keywords are not allowed
5958
0
            if (auto builtin = parse_builtin_value(tokens))
5959
0
                return nullptr;
5960
5961
0
            auto maybe_keyword = keyword_from_string(peek.token().ident());
5962
            // Can't have a generic-font-name as a token in an unquoted font name.
5963
0
            if (maybe_keyword.has_value() && is_generic_font_family(maybe_keyword.value())) {
5964
0
                if (!current_name_parts.is_empty())
5965
0
                    return nullptr;
5966
0
                tokens.discard_a_token(); // Ident
5967
0
                if (!next_is_comma_or_eof())
5968
0
                    return nullptr;
5969
0
                font_families.append(CSSKeywordValue::create(maybe_keyword.value()));
5970
0
                tokens.discard_a_token(); // Comma
5971
0
                continue;
5972
0
            }
5973
0
            current_name_parts.append(tokens.consume_a_token().token().ident().to_string());
5974
0
            continue;
5975
0
        }
5976
5977
0
        if (peek.is(Token::Type::Comma)) {
5978
0
            if (current_name_parts.is_empty())
5979
0
                return nullptr;
5980
0
            tokens.discard_a_token(); // Comma
5981
            // This is really a series of custom-idents, not just one. But for the sake of simplicity we'll make it one.
5982
0
            font_families.append(CustomIdentStyleValue::create(MUST(String::join(' ', current_name_parts))));
5983
0
            current_name_parts.clear();
5984
            // Can't have a trailing comma
5985
0
            if (!tokens.has_next_token())
5986
0
                return nullptr;
5987
0
            continue;
5988
0
        }
5989
5990
0
        return nullptr;
5991
0
    }
5992
5993
0
    if (!current_name_parts.is_empty()) {
5994
        // This is really a series of custom-idents, not just one. But for the sake of simplicity we'll make it one.
5995
0
        font_families.append(CustomIdentStyleValue::create(MUST(String::join(' ', current_name_parts))));
5996
0
        current_name_parts.clear();
5997
0
    }
5998
5999
0
    if (font_families.is_empty())
6000
0
        return nullptr;
6001
0
    return StyleValueList::create(move(font_families), StyleValueList::Separator::Comma);
6002
0
}
6003
6004
RefPtr<CSSStyleValue> Parser::parse_font_language_override_value(TokenStream<ComponentValue>& tokens)
6005
0
{
6006
    // https://drafts.csswg.org/css-fonts/#propdef-font-language-override
6007
    // This is `normal | <string>` but with the constraint that the string has to be 4 characters long:
6008
    // Shorter strings are right-padded with spaces, and longer strings are invalid.
6009
6010
0
    if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal))
6011
0
        return normal;
6012
6013
0
    auto transaction = tokens.begin_transaction();
6014
0
    tokens.discard_whitespace();
6015
0
    if (auto string = parse_string_value(tokens)) {
6016
0
        auto string_value = string->string_value();
6017
0
        tokens.discard_whitespace();
6018
0
        if (tokens.has_next_token()) {
6019
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-language-override: unexpected trailing tokens");
6020
0
            return nullptr;
6021
0
        }
6022
0
        auto length = string_value.code_points().length();
6023
0
        if (length > 4) {
6024
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-language-override: <string> value \"{}\" is too long", string_value);
6025
0
            return nullptr;
6026
0
        }
6027
0
        transaction.commit();
6028
0
        if (length < 4)
6029
0
            return StringStyleValue::create(MUST(String::formatted("{:<4}", string_value)));
6030
0
        return string;
6031
0
    }
6032
6033
0
    return nullptr;
6034
0
}
6035
6036
RefPtr<CSSStyleValue> Parser::parse_font_feature_settings_value(TokenStream<ComponentValue>& tokens)
6037
0
{
6038
    // https://drafts.csswg.org/css-fonts/#propdef-font-feature-settings
6039
    // normal | <feature-tag-value>#
6040
6041
    // normal
6042
0
    if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal))
6043
0
        return normal;
6044
6045
    // <feature-tag-value>#
6046
0
    auto transaction = tokens.begin_transaction();
6047
0
    auto tag_values = parse_a_comma_separated_list_of_component_values(tokens);
6048
6049
    // "The computed value of font-feature-settings is a map, so any duplicates in the specified value must not be preserved.
6050
    // If the same feature tag appears more than once, the value associated with the last appearance supersedes any previous
6051
    // value for that axis."
6052
    // So, we deduplicate them here using a HashSet.
6053
6054
0
    OrderedHashMap<FlyString, NonnullRefPtr<OpenTypeTaggedStyleValue>> feature_tags_map;
6055
0
    for (auto const& values : tag_values) {
6056
        // <feature-tag-value> = <opentype-tag> [ <integer [0,∞]> | on | off ]?
6057
0
        TokenStream tag_tokens { values };
6058
0
        tag_tokens.discard_whitespace();
6059
0
        auto opentype_tag = parse_opentype_tag_value(tag_tokens);
6060
0
        tag_tokens.discard_whitespace();
6061
0
        RefPtr<CSSStyleValue> value;
6062
0
        if (tag_tokens.has_next_token()) {
6063
0
            if (auto integer = parse_integer_value(tag_tokens)) {
6064
0
                if (integer->is_integer() && integer->as_integer().value() < 0)
6065
0
                    return nullptr;
6066
0
                value = integer;
6067
0
            } else {
6068
                // A value of on is synonymous with 1 and off is synonymous with 0.
6069
0
                auto keyword = parse_keyword_value(tag_tokens);
6070
0
                if (!keyword)
6071
0
                    return nullptr;
6072
0
                switch (keyword->to_keyword()) {
6073
0
                case Keyword::On:
6074
0
                    value = IntegerStyleValue::create(1);
6075
0
                    break;
6076
0
                case Keyword::Off:
6077
0
                    value = IntegerStyleValue::create(0);
6078
0
                    break;
6079
0
                default:
6080
0
                    return nullptr;
6081
0
                }
6082
0
            }
6083
0
            tag_tokens.discard_whitespace();
6084
0
        } else {
6085
            // "If the value is omitted, a value of 1 is assumed."
6086
0
            value = IntegerStyleValue::create(1);
6087
0
        }
6088
6089
0
        if (!opentype_tag || !value || tag_tokens.has_next_token())
6090
0
            return nullptr;
6091
6092
0
        feature_tags_map.set(opentype_tag->string_value(), OpenTypeTaggedStyleValue::create(opentype_tag->string_value(), value.release_nonnull()));
6093
0
    }
6094
6095
    // "The computed value contains the de-duplicated feature tags, sorted in ascending order by code unit."
6096
0
    StyleValueVector feature_tags;
6097
0
    feature_tags.ensure_capacity(feature_tags_map.size());
6098
0
    for (auto const& [key, feature_tag] : feature_tags_map)
6099
0
        feature_tags.append(feature_tag);
6100
6101
0
    quick_sort(feature_tags, [](auto& a, auto& b) {
6102
0
        return a->as_open_type_tagged().tag() < b->as_open_type_tagged().tag();
6103
0
    });
6104
6105
0
    transaction.commit();
6106
0
    return StyleValueList::create(move(feature_tags), StyleValueList::Separator::Comma);
6107
0
}
6108
6109
RefPtr<CSSStyleValue> Parser::parse_font_variation_settings_value(TokenStream<ComponentValue>& tokens)
6110
0
{
6111
    // https://drafts.csswg.org/css-fonts/#propdef-font-variation-settings
6112
    // normal | [ <opentype-tag> <number>]#
6113
6114
    // normal
6115
0
    if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal))
6116
0
        return normal;
6117
6118
    // [ <opentype-tag> <number>]#
6119
0
    auto transaction = tokens.begin_transaction();
6120
0
    auto tag_values = parse_a_comma_separated_list_of_component_values(tokens);
6121
6122
    // "If the same axis name appears more than once, the value associated with the last appearance supersedes any
6123
    // previous value for that axis. This deduplication is observable by accessing the computed value of this property."
6124
    // So, we deduplicate them here using a HashSet.
6125
6126
0
    OrderedHashMap<FlyString, NonnullRefPtr<OpenTypeTaggedStyleValue>> axis_tags_map;
6127
0
    for (auto const& values : tag_values) {
6128
0
        TokenStream tag_tokens { values };
6129
0
        tag_tokens.discard_whitespace();
6130
0
        auto opentype_tag = parse_opentype_tag_value(tag_tokens);
6131
0
        tag_tokens.discard_whitespace();
6132
0
        auto number = parse_number_value(tag_tokens);
6133
0
        tag_tokens.discard_whitespace();
6134
6135
0
        if (!opentype_tag || !number || tag_tokens.has_next_token())
6136
0
            return nullptr;
6137
6138
0
        axis_tags_map.set(opentype_tag->string_value(), OpenTypeTaggedStyleValue::create(opentype_tag->string_value(), number.release_nonnull()));
6139
0
    }
6140
6141
    // "The computed value contains the de-duplicated axis names, sorted in ascending order by code unit."
6142
0
    StyleValueVector axis_tags;
6143
0
    axis_tags.ensure_capacity(axis_tags_map.size());
6144
0
    for (auto const& [key, axis_tag] : axis_tags_map)
6145
0
        axis_tags.append(axis_tag);
6146
6147
0
    quick_sort(axis_tags, [](auto& a, auto& b) {
6148
0
        return a->as_open_type_tagged().tag() < b->as_open_type_tagged().tag();
6149
0
    });
6150
6151
0
    transaction.commit();
6152
0
    return StyleValueList::create(move(axis_tags), StyleValueList::Separator::Comma);
6153
0
}
6154
6155
JS::GCPtr<CSSFontFaceRule> Parser::convert_to_font_face_rule(AtRule const& rule)
6156
0
{
6157
    // https://drafts.csswg.org/css-fonts/#font-face-rule
6158
6159
0
    Optional<FlyString> font_family;
6160
0
    Optional<FlyString> font_named_instance;
6161
0
    Vector<ParsedFontFace::Source> src;
6162
0
    Vector<Gfx::UnicodeRange> unicode_range;
6163
0
    Optional<int> weight;
6164
0
    Optional<int> slope;
6165
0
    Optional<int> width;
6166
0
    Optional<Percentage> ascent_override;
6167
0
    Optional<Percentage> descent_override;
6168
0
    Optional<Percentage> line_gap_override;
6169
0
    FontDisplay font_display = FontDisplay::Auto;
6170
0
    Optional<FlyString> language_override;
6171
0
    Optional<OrderedHashMap<FlyString, i64>> font_feature_settings;
6172
0
    Optional<OrderedHashMap<FlyString, double>> font_variation_settings;
6173
6174
    // "normal" is returned as nullptr
6175
0
    auto parse_as_percentage_or_normal = [&](Vector<ComponentValue> const& values) -> ErrorOr<Optional<Percentage>> {
6176
        // normal | <percentage [0,∞]>
6177
0
        TokenStream tokens { values };
6178
0
        if (auto percentage_value = parse_percentage_value(tokens)) {
6179
0
            tokens.discard_whitespace();
6180
0
            if (tokens.has_next_token())
6181
0
                return Error::from_string_literal("Unexpected trailing tokens");
6182
6183
0
            if (percentage_value->is_percentage() && percentage_value->as_percentage().percentage().value() >= 0)
6184
0
                return percentage_value->as_percentage().percentage();
6185
6186
            // TODO: Once we implement calc-simplification in the parser, we should no longer see math values here,
6187
            //       unless they're impossible to resolve and thus invalid.
6188
0
            if (percentage_value->is_math()) {
6189
0
                if (auto result = percentage_value->as_math().resolve_percentage(); result.has_value())
6190
0
                    return result.value();
6191
0
            }
6192
6193
0
            return Error::from_string_literal("Invalid percentage");
6194
0
        }
6195
6196
0
        tokens.discard_whitespace();
6197
0
        if (!tokens.consume_a_token().is_ident("normal"sv))
6198
0
            return Error::from_string_literal("Expected `normal | <percentage [0,∞]>`");
6199
0
        tokens.discard_whitespace();
6200
0
        if (tokens.has_next_token())
6201
0
            return Error::from_string_literal("Unexpected trailing tokens");
6202
6203
0
        return OptionalNone {};
6204
0
    };
6205
6206
0
    rule.for_each_as_declaration_list([&](auto& declaration) {
6207
0
        if (declaration.name.equals_ignoring_ascii_case("ascent-override"sv)) {
6208
0
            auto value = parse_as_percentage_or_normal(declaration.value);
6209
0
            if (value.is_error()) {
6210
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face ascent-override: {}", value.error());
6211
0
            } else {
6212
0
                ascent_override = value.release_value();
6213
0
            }
6214
0
            return;
6215
0
        }
6216
0
        if (declaration.name.equals_ignoring_ascii_case("descent-override"sv)) {
6217
0
            auto value = parse_as_percentage_or_normal(declaration.value);
6218
0
            if (value.is_error()) {
6219
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face descent-override: {}", value.error());
6220
0
            } else {
6221
0
                descent_override = value.release_value();
6222
0
            }
6223
0
            return;
6224
0
        }
6225
0
        if (declaration.name.equals_ignoring_ascii_case("font-display"sv)) {
6226
0
            TokenStream token_stream { declaration.value };
6227
0
            if (auto keyword_value = parse_keyword_value(token_stream)) {
6228
0
                token_stream.discard_whitespace();
6229
0
                if (token_stream.has_next_token()) {
6230
0
                    dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected trailing tokens in font-display");
6231
0
                } else {
6232
0
                    auto value = keyword_to_font_display(keyword_value->to_keyword());
6233
0
                    if (value.has_value()) {
6234
0
                        font_display = *value;
6235
0
                    } else {
6236
0
                        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: `{}` is not a valid value for font-display", keyword_value->to_string());
6237
0
                    }
6238
0
                }
6239
0
            }
6240
0
            return;
6241
0
        }
6242
0
        if (declaration.name.equals_ignoring_ascii_case("font-family"sv)) {
6243
            // FIXME: This is very similar to, but different from, the logic in parse_font_family_value().
6244
            //        Ideally they could share code.
6245
0
            Vector<FlyString> font_family_parts;
6246
0
            bool had_syntax_error = false;
6247
0
            for (size_t i = 0; i < declaration.value.size(); ++i) {
6248
0
                auto const& part = declaration.value[i];
6249
0
                if (part.is(Token::Type::Whitespace))
6250
0
                    continue;
6251
0
                if (part.is(Token::Type::String)) {
6252
0
                    if (!font_family_parts.is_empty()) {
6253
0
                        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
6254
0
                        had_syntax_error = true;
6255
0
                        break;
6256
0
                    }
6257
0
                    font_family_parts.append(part.token().string());
6258
0
                    continue;
6259
0
                }
6260
0
                if (part.is(Token::Type::Ident)) {
6261
0
                    if (is_css_wide_keyword(part.token().ident())) {
6262
0
                        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
6263
0
                        had_syntax_error = true;
6264
0
                        break;
6265
0
                    }
6266
0
                    auto keyword = keyword_from_string(part.token().ident());
6267
0
                    if (keyword.has_value() && is_generic_font_family(keyword.value())) {
6268
0
                        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
6269
0
                        had_syntax_error = true;
6270
0
                        break;
6271
0
                    }
6272
0
                    font_family_parts.append(part.token().ident());
6273
0
                    continue;
6274
0
                }
6275
6276
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
6277
0
                had_syntax_error = true;
6278
0
                break;
6279
0
            }
6280
0
            if (had_syntax_error || font_family_parts.is_empty())
6281
0
                return;
6282
6283
0
            font_family = String::join(' ', font_family_parts).release_value_but_fixme_should_propagate_errors();
6284
0
            return;
6285
0
        }
6286
0
        if (declaration.name.equals_ignoring_ascii_case("font-feature-settings"sv)) {
6287
0
            TokenStream token_stream { declaration.value };
6288
0
            if (auto value = parse_css_value(CSS::PropertyID::FontFeatureSettings, token_stream); !value.is_error()) {
6289
0
                if (value.value()->to_keyword() == Keyword::Normal) {
6290
0
                    font_feature_settings.clear();
6291
0
                } else if (value.value()->is_value_list()) {
6292
0
                    auto const& feature_tags = value.value()->as_value_list().values();
6293
0
                    OrderedHashMap<FlyString, i64> settings;
6294
0
                    settings.ensure_capacity(feature_tags.size());
6295
0
                    for (auto const& feature_tag : feature_tags) {
6296
0
                        if (!feature_tag->is_open_type_tagged()) {
6297
0
                            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-feature-settings descriptor is not an OpenTypeTaggedStyleValue; skipping");
6298
0
                            continue;
6299
0
                        }
6300
0
                        auto const& setting_value = feature_tag->as_open_type_tagged().value();
6301
0
                        if (setting_value->is_integer()) {
6302
0
                            settings.set(feature_tag->as_open_type_tagged().tag(), setting_value->as_integer().integer());
6303
0
                        } else if (setting_value->is_math() && setting_value->as_math().resolves_to_number()) {
6304
0
                            if (auto integer = setting_value->as_math().resolve_integer(); integer.has_value()) {
6305
0
                                settings.set(feature_tag->as_open_type_tagged().tag(), *integer);
6306
0
                            } else {
6307
0
                                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Calculated value in font-feature-settings descriptor cannot be resolved at parse time; skipping");
6308
0
                            }
6309
0
                        } else {
6310
0
                            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-feature-settings descriptor is not an OpenTypeTaggedStyleValue holding a <integer>; skipping");
6311
0
                        }
6312
0
                    }
6313
0
                    font_feature_settings = move(settings);
6314
0
                } else {
6315
0
                    dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-feature-settings descriptor, not compatible with value returned from parsing font-feature-settings property: {}", value.value()->to_string());
6316
0
                }
6317
0
            }
6318
0
            return;
6319
0
        }
6320
0
        if (declaration.name.equals_ignoring_ascii_case("font-language-override"sv)) {
6321
0
            TokenStream token_stream { declaration.value };
6322
0
            if (auto maybe_value = parse_css_value(CSS::PropertyID::FontLanguageOverride, token_stream); !maybe_value.is_error()) {
6323
0
                auto& value = maybe_value.value();
6324
0
                if (value->is_string()) {
6325
0
                    language_override = value->as_string().string_value();
6326
0
                } else {
6327
0
                    language_override.clear();
6328
0
                }
6329
0
            }
6330
0
            return;
6331
0
        }
6332
0
        if (declaration.name.equals_ignoring_ascii_case("font-named-instance"sv)) {
6333
            // auto | <string>
6334
0
            TokenStream token_stream { declaration.value };
6335
0
            token_stream.discard_whitespace();
6336
0
            auto& token = token_stream.consume_a_token();
6337
0
            token_stream.discard_whitespace();
6338
0
            if (token_stream.has_next_token()) {
6339
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected trailing tokens in font-named-instance");
6340
0
                return;
6341
0
            }
6342
6343
0
            if (token.is_ident("auto"sv)) {
6344
0
                font_named_instance.clear();
6345
0
            } else if (token.is(Token::Type::String)) {
6346
0
                font_named_instance = token.token().string();
6347
0
            } else {
6348
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-named-instance from {}", token.to_debug_string());
6349
0
            }
6350
6351
0
            return;
6352
0
        }
6353
0
        if (declaration.name.equals_ignoring_ascii_case("font-style"sv)) {
6354
0
            TokenStream token_stream { declaration.value };
6355
0
            if (auto value = parse_css_value(CSS::PropertyID::FontStyle, token_stream); !value.is_error()) {
6356
0
                slope = value.value()->to_font_slope();
6357
0
            }
6358
0
            return;
6359
0
        }
6360
0
        if (declaration.name.equals_ignoring_ascii_case("font-variation-settings"sv)) {
6361
0
            TokenStream token_stream { declaration.value };
6362
0
            if (auto value = parse_css_value(CSS::PropertyID::FontVariationSettings, token_stream); !value.is_error()) {
6363
0
                if (value.value()->to_keyword() == Keyword::Normal) {
6364
0
                    font_variation_settings.clear();
6365
0
                } else if (value.value()->is_value_list()) {
6366
0
                    auto const& variation_tags = value.value()->as_value_list().values();
6367
0
                    OrderedHashMap<FlyString, double> settings;
6368
0
                    settings.ensure_capacity(variation_tags.size());
6369
0
                    for (auto const& variation_tag : variation_tags) {
6370
0
                        if (!variation_tag->is_open_type_tagged()) {
6371
0
                            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-variation-settings descriptor is not an OpenTypeTaggedStyleValue; skipping");
6372
0
                            continue;
6373
0
                        }
6374
0
                        auto const& setting_value = variation_tag->as_open_type_tagged().value();
6375
0
                        if (setting_value->is_number()) {
6376
0
                            settings.set(variation_tag->as_open_type_tagged().tag(), setting_value->as_number().number());
6377
0
                        } else if (setting_value->is_math() && setting_value->as_math().resolves_to_number()) {
6378
0
                            if (auto number = setting_value->as_math().resolve_number(); number.has_value()) {
6379
0
                                settings.set(variation_tag->as_open_type_tagged().tag(), *number);
6380
0
                            } else {
6381
0
                                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Calculated value in font-variation-settings descriptor cannot be resolved at parse time; skipping");
6382
0
                            }
6383
0
                        } else {
6384
0
                            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-variation-settings descriptor is not an OpenTypeTaggedStyleValue holding a <number>; skipping");
6385
0
                        }
6386
0
                    }
6387
0
                    font_variation_settings = move(settings);
6388
0
                } else {
6389
0
                    dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-variation-settings descriptor, not compatible with value returned from parsing font-variation-settings property: {}", value.value()->to_string());
6390
0
                }
6391
0
            }
6392
0
            return;
6393
0
        }
6394
0
        if (declaration.name.equals_ignoring_ascii_case("font-weight"sv)) {
6395
0
            TokenStream token_stream { declaration.value };
6396
0
            if (auto value = parse_css_value(CSS::PropertyID::FontWeight, token_stream); !value.is_error()) {
6397
0
                weight = value.value()->to_font_weight();
6398
0
            }
6399
0
            return;
6400
0
        }
6401
0
        if (declaration.name.equals_ignoring_ascii_case("font-width"sv)
6402
0
            || declaration.name.equals_ignoring_ascii_case("font-stretch"sv)) {
6403
0
            TokenStream token_stream { declaration.value };
6404
0
            if (auto value = parse_css_value(CSS::PropertyID::FontWidth, token_stream); !value.is_error()) {
6405
0
                width = value.value()->to_font_width();
6406
0
            }
6407
0
            return;
6408
0
        }
6409
0
        if (declaration.name.equals_ignoring_ascii_case("line-gap-override"sv)) {
6410
0
            auto value = parse_as_percentage_or_normal(declaration.value);
6411
0
            if (value.is_error()) {
6412
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face line-gap-override: {}", value.error());
6413
0
            } else {
6414
0
                line_gap_override = value.release_value();
6415
0
            }
6416
0
            return;
6417
0
        }
6418
0
        if (declaration.name.equals_ignoring_ascii_case("src"sv)) {
6419
0
            TokenStream token_stream { declaration.value };
6420
0
            Vector<ParsedFontFace::Source> supported_sources = parse_font_face_src(token_stream);
6421
0
            if (!supported_sources.is_empty())
6422
0
                src = move(supported_sources);
6423
0
            return;
6424
0
        }
6425
0
        if (declaration.name.equals_ignoring_ascii_case("unicode-range"sv)) {
6426
0
            TokenStream token_stream { declaration.value };
6427
0
            auto unicode_ranges = parse_unicode_ranges(token_stream);
6428
0
            if (unicode_ranges.is_empty())
6429
0
                return;
6430
6431
0
            unicode_range = move(unicode_ranges);
6432
0
            return;
6433
0
        }
6434
6435
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unrecognized descriptor '{}' in @font-face; discarding.", declaration.name);
6436
0
    });
6437
6438
0
    if (!font_family.has_value()) {
6439
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face: no font-family!");
6440
0
        return {};
6441
0
    }
6442
6443
0
    if (unicode_range.is_empty()) {
6444
0
        unicode_range.empend(0x0u, 0x10FFFFu);
6445
0
    }
6446
6447
0
    return CSSFontFaceRule::create(m_context.realm(), ParsedFontFace { font_family.release_value(), move(weight), move(slope), move(width), move(src), move(unicode_range), move(ascent_override), move(descent_override), move(line_gap_override), font_display, move(font_named_instance), move(language_override), move(font_feature_settings), move(font_variation_settings) });
6448
0
}
6449
6450
Vector<ParsedFontFace::Source> Parser::parse_as_font_face_src()
6451
0
{
6452
0
    return parse_font_face_src(m_token_stream);
6453
0
}
6454
6455
template<typename T>
6456
Vector<ParsedFontFace::Source> Parser::parse_font_face_src(TokenStream<T>& component_values)
6457
0
{
6458
    // FIXME: Get this information from the system somehow?
6459
    // Format-name table: https://www.w3.org/TR/css-fonts-4/#font-format-definitions
6460
0
    auto font_format_is_supported = [](StringView name) {
6461
        // The spec requires us to treat opentype and truetype as synonymous.
6462
0
        if (name.is_one_of_ignoring_ascii_case("opentype"sv, "truetype"sv, "woff"sv, "woff2"sv))
6463
0
            return true;
6464
0
        return false;
6465
0
    };
Unexecuted instantiation: Web::CSS::Parser::Parser::parse_font_face_src<Web::CSS::Parser::ComponentValue>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)::{lambda(AK::StringView)#1}::operator()(AK::StringView) const
Unexecuted instantiation: Web::CSS::Parser::Parser::parse_font_face_src<Web::CSS::Parser::Token>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::Token>&)::{lambda(AK::StringView)#1}::operator()(AK::StringView) const
6466
6467
0
    Vector<ParsedFontFace::Source> supported_sources;
6468
6469
0
    auto list_of_source_token_lists = parse_a_comma_separated_list_of_component_values(component_values);
6470
0
    for (auto const& source_token_list : list_of_source_token_lists) {
6471
0
        TokenStream source_tokens { source_token_list };
6472
0
        source_tokens.discard_whitespace();
6473
6474
        // <url> [ format(<font-format>)]?
6475
        // FIXME: Implement optional tech() function from CSS-Fonts-4.
6476
0
        if (auto maybe_url = parse_url_function(source_tokens); maybe_url.has_value()) {
6477
0
            auto url = maybe_url.release_value();
6478
0
            if (!url.is_valid()) {
6479
0
                continue;
6480
0
            }
6481
6482
0
            Optional<FlyString> format;
6483
6484
0
            source_tokens.discard_whitespace();
6485
0
            if (!source_tokens.has_next_token()) {
6486
0
                supported_sources.empend(move(url), format);
6487
0
                continue;
6488
0
            }
6489
6490
0
            auto maybe_function = source_tokens.consume_a_token();
6491
0
            if (!maybe_function.is_function()) {
6492
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (token after `url()` that isn't a function: {}); discarding.", maybe_function.to_debug_string());
6493
0
                return {};
6494
0
            }
6495
6496
0
            auto const& function = maybe_function.function();
6497
0
            if (function.name.equals_ignoring_ascii_case("format"sv)) {
6498
0
                TokenStream format_tokens { function.value };
6499
0
                format_tokens.discard_whitespace();
6500
0
                auto const& format_name_token = format_tokens.consume_a_token();
6501
0
                StringView format_name;
6502
0
                if (format_name_token.is(Token::Type::Ident)) {
6503
0
                    format_name = format_name_token.token().ident();
6504
0
                } else if (format_name_token.is(Token::Type::String)) {
6505
0
                    format_name = format_name_token.token().string();
6506
0
                } else {
6507
0
                    dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (`format()` parameter not an ident or string; is: {}); discarding.", format_name_token.to_debug_string());
6508
0
                    return {};
6509
0
                }
6510
6511
0
                if (!font_format_is_supported(format_name)) {
6512
0
                    dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src format({}) not supported; skipping.", format_name);
6513
0
                    continue;
6514
0
                }
6515
6516
0
                format = FlyString::from_utf8(format_name).release_value_but_fixme_should_propagate_errors();
6517
0
            } else {
6518
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (unrecognized function token `{}`); discarding.", function.name);
6519
0
                return {};
6520
0
            }
6521
6522
0
            source_tokens.discard_whitespace();
6523
0
            if (source_tokens.has_next_token()) {
6524
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (extra token `{}`); discarding.", source_tokens.next_token().to_debug_string());
6525
0
                return {};
6526
0
            }
6527
6528
0
            supported_sources.empend(move(url), format);
6529
0
            continue;
6530
0
        }
6531
6532
0
        auto const& first = source_tokens.consume_a_token();
6533
0
        if (first.is_function("local"sv)) {
6534
0
            if (first.function().value.is_empty()) {
6535
0
                continue;
6536
0
            }
6537
0
            supported_sources.empend(first.function().value.first().to_string(), Optional<FlyString> {});
6538
0
            continue;
6539
0
        }
6540
6541
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (failed to parse url from: {}); discarding.", first.to_debug_string());
6542
0
        return {};
6543
0
    }
6544
6545
0
    return supported_sources;
6546
0
}
Unexecuted instantiation: AK::Vector<Web::CSS::ParsedFontFace::Source, 0ul> Web::CSS::Parser::Parser::parse_font_face_src<Web::CSS::Parser::ComponentValue>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::ComponentValue>&)
Unexecuted instantiation: AK::Vector<Web::CSS::ParsedFontFace::Source, 0ul> Web::CSS::Parser::Parser::parse_font_face_src<Web::CSS::Parser::Token>(Web::CSS::Parser::TokenStream<Web::CSS::Parser::Token>&)
6547
6548
RefPtr<CSSStyleValue> Parser::parse_list_style_value(TokenStream<ComponentValue>& tokens)
6549
0
{
6550
0
    RefPtr<CSSStyleValue> list_position;
6551
0
    RefPtr<CSSStyleValue> list_image;
6552
0
    RefPtr<CSSStyleValue> list_type;
6553
0
    int found_nones = 0;
6554
6555
0
    Vector<PropertyID> remaining_longhands { PropertyID::ListStyleImage, PropertyID::ListStylePosition, PropertyID::ListStyleType };
6556
6557
0
    auto transaction = tokens.begin_transaction();
6558
0
    while (tokens.has_next_token()) {
6559
0
        if (auto peek = tokens.next_token(); peek.is_ident("none"sv)) {
6560
0
            tokens.discard_a_token();
6561
0
            found_nones++;
6562
0
            continue;
6563
0
        }
6564
6565
0
        auto property_and_value = parse_css_value_for_properties(remaining_longhands, tokens);
6566
0
        if (!property_and_value.has_value())
6567
0
            return nullptr;
6568
0
        auto& value = property_and_value->style_value;
6569
0
        remove_property(remaining_longhands, property_and_value->property);
6570
6571
0
        switch (property_and_value->property) {
6572
0
        case PropertyID::ListStylePosition: {
6573
0
            VERIFY(!list_position);
6574
0
            list_position = value.release_nonnull();
6575
0
            continue;
6576
0
        }
6577
0
        case PropertyID::ListStyleImage: {
6578
0
            VERIFY(!list_image);
6579
0
            list_image = value.release_nonnull();
6580
0
            continue;
6581
0
        }
6582
0
        case PropertyID::ListStyleType: {
6583
0
            VERIFY(!list_type);
6584
0
            list_type = value.release_nonnull();
6585
0
            continue;
6586
0
        }
6587
0
        default:
6588
0
            VERIFY_NOT_REACHED();
6589
0
        }
6590
0
    }
6591
6592
0
    if (found_nones > 2)
6593
0
        return nullptr;
6594
6595
0
    if (found_nones == 2) {
6596
0
        if (list_image || list_type)
6597
0
            return nullptr;
6598
0
        auto none = CSSKeywordValue::create(Keyword::None);
6599
0
        list_image = none;
6600
0
        list_type = none;
6601
6602
0
    } else if (found_nones == 1) {
6603
0
        if (list_image && list_type)
6604
0
            return nullptr;
6605
0
        auto none = CSSKeywordValue::create(Keyword::None);
6606
0
        if (!list_image)
6607
0
            list_image = none;
6608
0
        if (!list_type)
6609
0
            list_type = none;
6610
0
    }
6611
6612
0
    if (!list_position)
6613
0
        list_position = property_initial_value(m_context.realm(), PropertyID::ListStylePosition);
6614
0
    if (!list_image)
6615
0
        list_image = property_initial_value(m_context.realm(), PropertyID::ListStyleImage);
6616
0
    if (!list_type)
6617
0
        list_type = property_initial_value(m_context.realm(), PropertyID::ListStyleType);
6618
6619
0
    transaction.commit();
6620
0
    return ShorthandStyleValue::create(PropertyID::ListStyle,
6621
0
        { PropertyID::ListStylePosition, PropertyID::ListStyleImage, PropertyID::ListStyleType },
6622
0
        { list_position.release_nonnull(), list_image.release_nonnull(), list_type.release_nonnull() });
6623
0
}
6624
6625
RefPtr<CSSStyleValue> Parser::parse_math_depth_value(TokenStream<ComponentValue>& tokens)
6626
0
{
6627
    // https://w3c.github.io/mathml-core/#propdef-math-depth
6628
    // auto-add | add(<integer>) | <integer>
6629
0
    auto transaction = tokens.begin_transaction();
6630
6631
0
    auto token = tokens.consume_a_token();
6632
0
    if (tokens.has_next_token())
6633
0
        return nullptr;
6634
6635
    // auto-add
6636
0
    if (token.is_ident("auto-add"sv)) {
6637
0
        transaction.commit();
6638
0
        return MathDepthStyleValue::create_auto_add();
6639
0
    }
6640
6641
    // FIXME: Make it easier to parse "thing that might be <bar> or literally anything that resolves to it" and get rid of this
6642
0
    auto parse_something_that_resolves_to_integer = [this](ComponentValue& token) -> RefPtr<CSSStyleValue> {
6643
0
        if (token.is(Token::Type::Number) && token.token().number().is_integer())
6644
0
            return IntegerStyleValue::create(token.token().to_integer());
6645
0
        if (auto value = parse_calculated_value(token); value && value->resolves_to_number())
6646
0
            return value;
6647
0
        return nullptr;
6648
0
    };
6649
6650
    // add(<integer>)
6651
0
    if (token.is_function("add"sv)) {
6652
0
        auto add_tokens = TokenStream { token.function().value };
6653
0
        add_tokens.discard_whitespace();
6654
0
        auto integer_token = add_tokens.consume_a_token();
6655
0
        add_tokens.discard_whitespace();
6656
0
        if (add_tokens.has_next_token())
6657
0
            return nullptr;
6658
0
        if (auto integer_value = parse_something_that_resolves_to_integer(integer_token)) {
6659
0
            transaction.commit();
6660
0
            return MathDepthStyleValue::create_add(integer_value.release_nonnull());
6661
0
        }
6662
0
        return nullptr;
6663
0
    }
6664
6665
    // <integer>
6666
0
    if (auto integer_value = parse_something_that_resolves_to_integer(token)) {
6667
0
        transaction.commit();
6668
0
        return MathDepthStyleValue::create_integer(integer_value.release_nonnull());
6669
0
    }
6670
6671
0
    return nullptr;
6672
0
}
6673
6674
RefPtr<CSSStyleValue> Parser::parse_overflow_value(TokenStream<ComponentValue>& tokens)
6675
0
{
6676
0
    auto transaction = tokens.begin_transaction();
6677
0
    auto maybe_x_value = parse_css_value_for_property(PropertyID::OverflowX, tokens);
6678
0
    if (!maybe_x_value)
6679
0
        return nullptr;
6680
0
    auto maybe_y_value = parse_css_value_for_property(PropertyID::OverflowY, tokens);
6681
0
    transaction.commit();
6682
0
    if (maybe_y_value) {
6683
0
        return ShorthandStyleValue::create(PropertyID::Overflow,
6684
0
            { PropertyID::OverflowX, PropertyID::OverflowY },
6685
0
            { maybe_x_value.release_nonnull(), maybe_y_value.release_nonnull() });
6686
0
    }
6687
0
    return ShorthandStyleValue::create(PropertyID::Overflow,
6688
0
        { PropertyID::OverflowX, PropertyID::OverflowY },
6689
0
        { *maybe_x_value, *maybe_x_value });
6690
0
}
6691
6692
RefPtr<CSSStyleValue> Parser::parse_place_content_value(TokenStream<ComponentValue>& tokens)
6693
0
{
6694
0
    auto transaction = tokens.begin_transaction();
6695
0
    auto maybe_align_content_value = parse_css_value_for_property(PropertyID::AlignContent, tokens);
6696
0
    if (!maybe_align_content_value)
6697
0
        return nullptr;
6698
6699
0
    if (!tokens.has_next_token()) {
6700
0
        if (!property_accepts_keyword(PropertyID::JustifyContent, maybe_align_content_value->to_keyword()))
6701
0
            return nullptr;
6702
0
        transaction.commit();
6703
0
        return ShorthandStyleValue::create(PropertyID::PlaceContent,
6704
0
            { PropertyID::AlignContent, PropertyID::JustifyContent },
6705
0
            { *maybe_align_content_value, *maybe_align_content_value });
6706
0
    }
6707
6708
0
    auto maybe_justify_content_value = parse_css_value_for_property(PropertyID::JustifyContent, tokens);
6709
0
    if (!maybe_justify_content_value)
6710
0
        return nullptr;
6711
0
    transaction.commit();
6712
0
    return ShorthandStyleValue::create(PropertyID::PlaceContent,
6713
0
        { PropertyID::AlignContent, PropertyID::JustifyContent },
6714
0
        { maybe_align_content_value.release_nonnull(), maybe_justify_content_value.release_nonnull() });
6715
0
}
6716
6717
RefPtr<CSSStyleValue> Parser::parse_place_items_value(TokenStream<ComponentValue>& tokens)
6718
0
{
6719
0
    auto transaction = tokens.begin_transaction();
6720
0
    auto maybe_align_items_value = parse_css_value_for_property(PropertyID::AlignItems, tokens);
6721
0
    if (!maybe_align_items_value)
6722
0
        return nullptr;
6723
6724
0
    if (!tokens.has_next_token()) {
6725
0
        if (!property_accepts_keyword(PropertyID::JustifyItems, maybe_align_items_value->to_keyword()))
6726
0
            return nullptr;
6727
0
        transaction.commit();
6728
0
        return ShorthandStyleValue::create(PropertyID::PlaceItems,
6729
0
            { PropertyID::AlignItems, PropertyID::JustifyItems },
6730
0
            { *maybe_align_items_value, *maybe_align_items_value });
6731
0
    }
6732
6733
0
    auto maybe_justify_items_value = parse_css_value_for_property(PropertyID::JustifyItems, tokens);
6734
0
    if (!maybe_justify_items_value)
6735
0
        return nullptr;
6736
0
    transaction.commit();
6737
0
    return ShorthandStyleValue::create(PropertyID::PlaceItems,
6738
0
        { PropertyID::AlignItems, PropertyID::JustifyItems },
6739
0
        { *maybe_align_items_value, *maybe_justify_items_value });
6740
0
}
6741
6742
RefPtr<CSSStyleValue> Parser::parse_place_self_value(TokenStream<ComponentValue>& tokens)
6743
0
{
6744
0
    auto transaction = tokens.begin_transaction();
6745
0
    auto maybe_align_self_value = parse_css_value_for_property(PropertyID::AlignSelf, tokens);
6746
0
    if (!maybe_align_self_value)
6747
0
        return nullptr;
6748
6749
0
    if (!tokens.has_next_token()) {
6750
0
        if (!property_accepts_keyword(PropertyID::JustifySelf, maybe_align_self_value->to_keyword()))
6751
0
            return nullptr;
6752
0
        transaction.commit();
6753
0
        return ShorthandStyleValue::create(PropertyID::PlaceSelf,
6754
0
            { PropertyID::AlignSelf, PropertyID::JustifySelf },
6755
0
            { *maybe_align_self_value, *maybe_align_self_value });
6756
0
    }
6757
6758
0
    auto maybe_justify_self_value = parse_css_value_for_property(PropertyID::JustifySelf, tokens);
6759
0
    if (!maybe_justify_self_value)
6760
0
        return nullptr;
6761
0
    transaction.commit();
6762
0
    return ShorthandStyleValue::create(PropertyID::PlaceSelf,
6763
0
        { PropertyID::AlignSelf, PropertyID::JustifySelf },
6764
0
        { *maybe_align_self_value, *maybe_justify_self_value });
6765
0
}
6766
6767
RefPtr<CSSStyleValue> Parser::parse_quotes_value(TokenStream<ComponentValue>& tokens)
6768
0
{
6769
    // https://www.w3.org/TR/css-content-3/#quotes-property
6770
    // auto | none | [ <string> <string> ]+
6771
0
    auto transaction = tokens.begin_transaction();
6772
6773
0
    if (tokens.remaining_token_count() == 1) {
6774
0
        auto keyword = parse_keyword_value(tokens);
6775
0
        if (keyword && property_accepts_keyword(PropertyID::Quotes, keyword->to_keyword())) {
6776
0
            transaction.commit();
6777
0
            return keyword;
6778
0
        }
6779
0
        return nullptr;
6780
0
    }
6781
6782
    // Parse an even number of <string> values.
6783
0
    if (tokens.remaining_token_count() % 2 != 0)
6784
0
        return nullptr;
6785
6786
0
    StyleValueVector string_values;
6787
0
    while (tokens.has_next_token()) {
6788
0
        auto maybe_string = parse_string_value(tokens);
6789
0
        if (!maybe_string)
6790
0
            return nullptr;
6791
6792
0
        string_values.append(maybe_string.release_nonnull());
6793
0
    }
6794
6795
0
    transaction.commit();
6796
0
    return StyleValueList::create(move(string_values), StyleValueList::Separator::Space);
6797
0
}
6798
6799
RefPtr<CSSStyleValue> Parser::parse_text_decoration_value(TokenStream<ComponentValue>& tokens)
6800
0
{
6801
0
    RefPtr<CSSStyleValue> decoration_line;
6802
0
    RefPtr<CSSStyleValue> decoration_thickness;
6803
0
    RefPtr<CSSStyleValue> decoration_style;
6804
0
    RefPtr<CSSStyleValue> decoration_color;
6805
6806
0
    auto remaining_longhands = Vector { PropertyID::TextDecorationColor, PropertyID::TextDecorationLine, PropertyID::TextDecorationStyle, PropertyID::TextDecorationThickness };
6807
6808
0
    auto transaction = tokens.begin_transaction();
6809
0
    while (tokens.has_next_token()) {
6810
0
        auto property_and_value = parse_css_value_for_properties(remaining_longhands, tokens);
6811
0
        if (!property_and_value.has_value())
6812
0
            return nullptr;
6813
0
        auto& value = property_and_value->style_value;
6814
0
        remove_property(remaining_longhands, property_and_value->property);
6815
6816
0
        switch (property_and_value->property) {
6817
0
        case PropertyID::TextDecorationColor: {
6818
0
            VERIFY(!decoration_color);
6819
0
            decoration_color = value.release_nonnull();
6820
0
            continue;
6821
0
        }
6822
0
        case PropertyID::TextDecorationLine: {
6823
0
            VERIFY(!decoration_line);
6824
0
            tokens.reconsume_current_input_token();
6825
0
            auto parsed_decoration_line = parse_text_decoration_line_value(tokens);
6826
0
            if (!parsed_decoration_line)
6827
0
                return nullptr;
6828
0
            decoration_line = parsed_decoration_line.release_nonnull();
6829
0
            continue;
6830
0
        }
6831
0
        case PropertyID::TextDecorationThickness: {
6832
0
            VERIFY(!decoration_thickness);
6833
0
            decoration_thickness = value.release_nonnull();
6834
0
            continue;
6835
0
        }
6836
0
        case PropertyID::TextDecorationStyle: {
6837
0
            VERIFY(!decoration_style);
6838
0
            decoration_style = value.release_nonnull();
6839
0
            continue;
6840
0
        }
6841
0
        default:
6842
0
            VERIFY_NOT_REACHED();
6843
0
        }
6844
0
    }
6845
6846
0
    if (!decoration_line)
6847
0
        decoration_line = property_initial_value(m_context.realm(), PropertyID::TextDecorationLine);
6848
0
    if (!decoration_thickness)
6849
0
        decoration_thickness = property_initial_value(m_context.realm(), PropertyID::TextDecorationThickness);
6850
0
    if (!decoration_style)
6851
0
        decoration_style = property_initial_value(m_context.realm(), PropertyID::TextDecorationStyle);
6852
0
    if (!decoration_color)
6853
0
        decoration_color = property_initial_value(m_context.realm(), PropertyID::TextDecorationColor);
6854
6855
0
    transaction.commit();
6856
0
    return ShorthandStyleValue::create(PropertyID::TextDecoration,
6857
0
        { PropertyID::TextDecorationLine, PropertyID::TextDecorationThickness, PropertyID::TextDecorationStyle, PropertyID::TextDecorationColor },
6858
0
        { decoration_line.release_nonnull(), decoration_thickness.release_nonnull(), decoration_style.release_nonnull(), decoration_color.release_nonnull() });
6859
0
}
6860
6861
RefPtr<CSSStyleValue> Parser::parse_text_decoration_line_value(TokenStream<ComponentValue>& tokens)
6862
0
{
6863
0
    StyleValueVector style_values;
6864
6865
0
    while (tokens.has_next_token()) {
6866
0
        auto maybe_value = parse_css_value_for_property(PropertyID::TextDecorationLine, tokens);
6867
0
        if (!maybe_value)
6868
0
            break;
6869
0
        auto value = maybe_value.release_nonnull();
6870
6871
0
        if (auto maybe_line = keyword_to_text_decoration_line(value->to_keyword()); maybe_line.has_value()) {
6872
0
            if (maybe_line == TextDecorationLine::None) {
6873
0
                if (!style_values.is_empty())
6874
0
                    break;
6875
0
                return value;
6876
0
            }
6877
0
            if (style_values.contains_slow(value))
6878
0
                break;
6879
0
            style_values.append(move(value));
6880
0
            continue;
6881
0
        }
6882
6883
0
        break;
6884
0
    }
6885
6886
0
    if (style_values.is_empty())
6887
0
        return nullptr;
6888
0
    return StyleValueList::create(move(style_values), StyleValueList::Separator::Space);
6889
0
}
6890
6891
RefPtr<CSSStyleValue> Parser::parse_easing_value(TokenStream<ComponentValue>& tokens)
6892
0
{
6893
0
    auto transaction = tokens.begin_transaction();
6894
6895
0
    tokens.discard_whitespace();
6896
6897
0
    auto const& part = tokens.consume_a_token();
6898
6899
0
    if (part.is(Token::Type::Ident)) {
6900
0
        auto name = part.token().ident();
6901
0
        auto maybe_simple_easing = [&] -> RefPtr<EasingStyleValue> {
6902
0
            if (name == "linear"sv)
6903
0
                return EasingStyleValue::create(EasingStyleValue::Linear {});
6904
0
            if (name == "ease"sv)
6905
0
                return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease());
6906
0
            if (name == "ease-in"sv)
6907
0
                return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_in());
6908
0
            if (name == "ease-out"sv)
6909
0
                return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_out());
6910
0
            if (name == "ease-in-out"sv)
6911
0
                return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_in_out());
6912
0
            if (name == "step-start"sv)
6913
0
                return EasingStyleValue::create(EasingStyleValue::Steps::step_start());
6914
0
            if (name == "step-end"sv)
6915
0
                return EasingStyleValue::create(EasingStyleValue::Steps::step_end());
6916
0
            return {};
6917
0
        }();
6918
6919
0
        if (!maybe_simple_easing)
6920
0
            return nullptr;
6921
6922
0
        transaction.commit();
6923
0
        return maybe_simple_easing;
6924
0
    }
6925
6926
0
    if (!part.is_function())
6927
0
        return nullptr;
6928
6929
0
    TokenStream argument_tokens { part.function().value };
6930
0
    auto comma_separated_arguments = parse_a_comma_separated_list_of_component_values(argument_tokens);
6931
6932
    // Remove whitespace
6933
0
    for (auto& argument : comma_separated_arguments)
6934
0
        argument.remove_all_matching([](auto& value) { return value.is(Token::Type::Whitespace); });
6935
6936
0
    auto name = part.function().name;
6937
0
    if (name == "linear"sv) {
6938
0
        Vector<EasingStyleValue::Linear::Stop> stops;
6939
0
        for (auto const& argument : comma_separated_arguments) {
6940
0
            if (argument.is_empty() || argument.size() > 2)
6941
0
                return nullptr;
6942
6943
0
            Optional<double> offset;
6944
0
            Optional<double> position;
6945
6946
0
            for (auto const& part : argument) {
6947
0
                if (part.is(Token::Type::Number)) {
6948
0
                    if (offset.has_value())
6949
0
                        return nullptr;
6950
0
                    offset = part.token().number_value();
6951
0
                } else if (part.is(Token::Type::Percentage)) {
6952
0
                    if (position.has_value())
6953
0
                        return nullptr;
6954
0
                    position = part.token().percentage();
6955
0
                } else {
6956
0
                    return nullptr;
6957
0
                };
6958
0
            }
6959
6960
0
            if (!offset.has_value())
6961
0
                return nullptr;
6962
6963
0
            stops.append({ offset.value(), move(position) });
6964
0
        }
6965
6966
0
        if (stops.is_empty())
6967
0
            return nullptr;
6968
6969
0
        transaction.commit();
6970
0
        return EasingStyleValue::create(EasingStyleValue::Linear { move(stops) });
6971
0
    }
6972
6973
0
    if (name == "cubic-bezier") {
6974
0
        if (comma_separated_arguments.size() != 4)
6975
0
            return nullptr;
6976
6977
0
        for (auto const& argument : comma_separated_arguments) {
6978
0
            if (argument.size() != 1)
6979
0
                return nullptr;
6980
0
            if (!argument[0].is(Token::Type::Number))
6981
0
                return nullptr;
6982
0
        }
6983
6984
0
        EasingStyleValue::CubicBezier bezier {
6985
0
            comma_separated_arguments[0][0].token().number_value(),
6986
0
            comma_separated_arguments[1][0].token().number_value(),
6987
0
            comma_separated_arguments[2][0].token().number_value(),
6988
0
            comma_separated_arguments[3][0].token().number_value(),
6989
0
        };
6990
6991
0
        if (bezier.x1 < 0.0 || bezier.x1 > 1.0 || bezier.x2 < 0.0 || bezier.x2 > 1.0)
6992
0
            return nullptr;
6993
6994
0
        transaction.commit();
6995
0
        return EasingStyleValue::create(bezier);
6996
0
    }
6997
6998
0
    if (name == "steps") {
6999
0
        if (comma_separated_arguments.is_empty() || comma_separated_arguments.size() > 2)
7000
0
            return nullptr;
7001
7002
0
        for (auto const& argument : comma_separated_arguments) {
7003
0
            if (argument.size() != 1)
7004
0
                return nullptr;
7005
0
        }
7006
7007
0
        EasingStyleValue::Steps steps;
7008
7009
0
        auto intervals_argument = comma_separated_arguments[0][0];
7010
0
        if (!intervals_argument.is(Token::Type::Number))
7011
0
            return nullptr;
7012
0
        if (!intervals_argument.token().number().is_integer())
7013
0
            return nullptr;
7014
0
        auto intervals = intervals_argument.token().to_integer();
7015
7016
0
        if (comma_separated_arguments.size() == 2) {
7017
0
            TokenStream identifier_stream { comma_separated_arguments[1] };
7018
0
            auto keyword_value = parse_keyword_value(identifier_stream);
7019
0
            if (!keyword_value)
7020
0
                return nullptr;
7021
0
            switch (keyword_value->to_keyword()) {
7022
0
            case Keyword::JumpStart:
7023
0
                steps.position = EasingStyleValue::Steps::Position::JumpStart;
7024
0
                break;
7025
0
            case Keyword::JumpEnd:
7026
0
                steps.position = EasingStyleValue::Steps::Position::JumpEnd;
7027
0
                break;
7028
0
            case Keyword::JumpBoth:
7029
0
                steps.position = EasingStyleValue::Steps::Position::JumpBoth;
7030
0
                break;
7031
0
            case Keyword::JumpNone:
7032
0
                steps.position = EasingStyleValue::Steps::Position::JumpNone;
7033
0
                break;
7034
0
            case Keyword::Start:
7035
0
                steps.position = EasingStyleValue::Steps::Position::Start;
7036
0
                break;
7037
0
            case Keyword::End:
7038
0
                steps.position = EasingStyleValue::Steps::Position::End;
7039
0
                break;
7040
0
            default:
7041
0
                return nullptr;
7042
0
            }
7043
0
        }
7044
7045
        // Perform extra validation
7046
        // https://drafts.csswg.org/css-easing/#step-easing-functions
7047
        // If the <step-position> is jump-none, the <integer> must be at least 2, or the function is invalid.
7048
        // Otherwise, the <integer> must be at least 1, or the function is invalid.
7049
0
        if (steps.position == EasingStyleValue::Steps::Position::JumpNone) {
7050
0
            if (intervals <= 1)
7051
0
                return nullptr;
7052
0
        } else if (intervals <= 0) {
7053
0
            return nullptr;
7054
0
        }
7055
7056
0
        steps.number_of_intervals = intervals;
7057
0
        transaction.commit();
7058
0
        return EasingStyleValue::create(steps);
7059
0
    }
7060
7061
0
    return nullptr;
7062
0
}
7063
7064
// https://www.w3.org/TR/css-transforms-1/#transform-property
7065
RefPtr<CSSStyleValue> Parser::parse_transform_value(TokenStream<ComponentValue>& tokens)
7066
0
{
7067
    // <transform> = none | <transform-list>
7068
    // <transform-list> = <transform-function>+
7069
7070
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
7071
0
        return none;
7072
7073
0
    StyleValueVector transformations;
7074
0
    auto transaction = tokens.begin_transaction();
7075
0
    while (tokens.has_next_token()) {
7076
0
        auto const& part = tokens.consume_a_token();
7077
0
        if (!part.is_function())
7078
0
            return nullptr;
7079
0
        auto maybe_function = transform_function_from_string(part.function().name);
7080
0
        if (!maybe_function.has_value())
7081
0
            return nullptr;
7082
0
        auto function = maybe_function.release_value();
7083
0
        auto function_metadata = transform_function_metadata(function);
7084
7085
0
        auto function_tokens = TokenStream { part.function().value };
7086
0
        auto arguments = parse_a_comma_separated_list_of_component_values(function_tokens);
7087
7088
0
        if (arguments.size() > function_metadata.parameters.size()) {
7089
0
            dbgln_if(CSS_PARSER_DEBUG, "Too many arguments to {}. max: {}", part.function().name, function_metadata.parameters.size());
7090
0
            return nullptr;
7091
0
        }
7092
7093
0
        if (arguments.size() < function_metadata.parameters.size() && function_metadata.parameters[arguments.size()].required) {
7094
0
            dbgln_if(CSS_PARSER_DEBUG, "Required parameter at position {} is missing", arguments.size());
7095
0
            return nullptr;
7096
0
        }
7097
7098
0
        StyleValueVector values;
7099
0
        for (auto argument_index = 0u; argument_index < arguments.size(); ++argument_index) {
7100
0
            TokenStream argument_tokens { arguments[argument_index] };
7101
0
            argument_tokens.discard_whitespace();
7102
7103
0
            auto const& value = argument_tokens.consume_a_token();
7104
0
            RefPtr<CSSMathValue> maybe_calc_value = parse_calculated_value(value);
7105
7106
0
            switch (function_metadata.parameters[argument_index].type) {
7107
0
            case TransformFunctionParameterType::Angle: {
7108
                // These are `<angle> | <zero>` in the spec, so we have to check for both kinds.
7109
0
                if (maybe_calc_value && maybe_calc_value->resolves_to_angle()) {
7110
0
                    values.append(maybe_calc_value.release_nonnull());
7111
0
                } else if (value.is(Token::Type::Number) && value.token().number_value() == 0) {
7112
0
                    values.append(AngleStyleValue::create(Angle::make_degrees(0)));
7113
0
                } else {
7114
                    // FIXME: Remove this reconsume once all parsing functions are TokenStream-based.
7115
0
                    argument_tokens.reconsume_current_input_token();
7116
0
                    auto dimension_value = parse_dimension_value(argument_tokens);
7117
0
                    if (!dimension_value || !dimension_value->is_angle())
7118
0
                        return nullptr;
7119
0
                    values.append(dimension_value.release_nonnull());
7120
0
                }
7121
0
                break;
7122
0
            }
7123
0
            case TransformFunctionParameterType::Length:
7124
0
            case TransformFunctionParameterType::LengthNone: {
7125
0
                if (maybe_calc_value && maybe_calc_value->resolves_to_length()) {
7126
0
                    argument_tokens.discard_a_token(); // calc()
7127
0
                    values.append(maybe_calc_value.release_nonnull());
7128
0
                } else {
7129
                    // FIXME: Remove this reconsume once all parsing functions are TokenStream-based.
7130
0
                    argument_tokens.reconsume_current_input_token();
7131
7132
0
                    if (function_metadata.parameters[argument_index].type == TransformFunctionParameterType::LengthNone) {
7133
0
                        auto keyword_transaction = argument_tokens.begin_transaction();
7134
0
                        auto keyword_value = parse_keyword_value(argument_tokens);
7135
0
                        if (keyword_value && keyword_value->to_keyword() == Keyword::None) {
7136
0
                            values.append(keyword_value.release_nonnull());
7137
0
                            keyword_transaction.commit();
7138
0
                            break;
7139
0
                        }
7140
0
                    }
7141
7142
0
                    auto dimension_value = parse_dimension_value(argument_tokens);
7143
0
                    if (!dimension_value || !dimension_value->is_length())
7144
0
                        return nullptr;
7145
7146
0
                    values.append(dimension_value.release_nonnull());
7147
0
                }
7148
0
                break;
7149
0
            }
7150
0
            case TransformFunctionParameterType::LengthPercentage: {
7151
0
                if (maybe_calc_value && maybe_calc_value->resolves_to_length_percentage()) {
7152
0
                    values.append(maybe_calc_value.release_nonnull());
7153
0
                } else {
7154
                    // FIXME: Remove this reconsume once all parsing functions are TokenStream-based.
7155
0
                    argument_tokens.reconsume_current_input_token();
7156
0
                    auto dimension_value = parse_dimension_value(argument_tokens);
7157
0
                    if (!dimension_value)
7158
0
                        return nullptr;
7159
7160
0
                    if (dimension_value->is_percentage() || dimension_value->is_length())
7161
0
                        values.append(dimension_value.release_nonnull());
7162
0
                    else
7163
0
                        return nullptr;
7164
0
                }
7165
0
                break;
7166
0
            }
7167
0
            case TransformFunctionParameterType::Number: {
7168
0
                if (maybe_calc_value && maybe_calc_value->resolves_to_number()) {
7169
0
                    values.append(maybe_calc_value.release_nonnull());
7170
0
                } else {
7171
                    // FIXME: Remove this reconsume once all parsing functions are TokenStream-based.
7172
0
                    argument_tokens.reconsume_current_input_token();
7173
0
                    auto number = parse_number_value(argument_tokens);
7174
0
                    if (!number)
7175
0
                        return nullptr;
7176
0
                    values.append(number.release_nonnull());
7177
0
                }
7178
0
                break;
7179
0
            }
7180
0
            case TransformFunctionParameterType::NumberPercentage: {
7181
0
                if (maybe_calc_value && maybe_calc_value->resolves_to_number()) {
7182
0
                    values.append(maybe_calc_value.release_nonnull());
7183
0
                } else {
7184
                    // FIXME: Remove this reconsume once all parsing functions are TokenStream-based.
7185
0
                    argument_tokens.reconsume_current_input_token();
7186
0
                    auto number_or_percentage = parse_number_percentage_value(argument_tokens);
7187
0
                    if (!number_or_percentage)
7188
0
                        return nullptr;
7189
0
                    values.append(number_or_percentage.release_nonnull());
7190
0
                }
7191
0
                break;
7192
0
            }
7193
0
            }
7194
7195
0
            argument_tokens.discard_whitespace();
7196
0
            if (argument_tokens.has_next_token())
7197
0
                return nullptr;
7198
0
        }
7199
7200
0
        transformations.append(TransformationStyleValue::create(function, move(values)));
7201
0
    }
7202
0
    transaction.commit();
7203
0
    return StyleValueList::create(move(transformations), StyleValueList::Separator::Space);
7204
0
}
7205
7206
// https://www.w3.org/TR/css-transforms-1/#propdef-transform-origin
7207
// FIXME: This only supports a 2D position
7208
RefPtr<CSSStyleValue> Parser::parse_transform_origin_value(TokenStream<ComponentValue>& tokens)
7209
0
{
7210
0
    enum class Axis {
7211
0
        None,
7212
0
        X,
7213
0
        Y,
7214
0
    };
7215
7216
0
    struct AxisOffset {
7217
0
        Axis axis;
7218
0
        NonnullRefPtr<CSSStyleValue> offset;
7219
0
    };
7220
7221
0
    auto to_axis_offset = [](RefPtr<CSSStyleValue> value) -> Optional<AxisOffset> {
7222
0
        if (!value)
7223
0
            return OptionalNone {};
7224
0
        if (value->is_percentage())
7225
0
            return AxisOffset { Axis::None, value->as_percentage() };
7226
0
        if (value->is_length())
7227
0
            return AxisOffset { Axis::None, value->as_length() };
7228
0
        if (value->is_keyword()) {
7229
0
            switch (value->to_keyword()) {
7230
0
            case Keyword::Top:
7231
0
                return AxisOffset { Axis::Y, PercentageStyleValue::create(Percentage(0)) };
7232
0
            case Keyword::Left:
7233
0
                return AxisOffset { Axis::X, PercentageStyleValue::create(Percentage(0)) };
7234
0
            case Keyword::Center:
7235
0
                return AxisOffset { Axis::None, PercentageStyleValue::create(Percentage(50)) };
7236
0
            case Keyword::Bottom:
7237
0
                return AxisOffset { Axis::Y, PercentageStyleValue::create(Percentage(100)) };
7238
0
            case Keyword::Right:
7239
0
                return AxisOffset { Axis::X, PercentageStyleValue::create(Percentage(100)) };
7240
0
            default:
7241
0
                return OptionalNone {};
7242
0
            }
7243
0
        }
7244
0
        if (value->is_math()) {
7245
0
            return AxisOffset { Axis::None, value->as_math() };
7246
0
        }
7247
0
        return OptionalNone {};
7248
0
    };
7249
7250
0
    auto transaction = tokens.begin_transaction();
7251
7252
0
    auto make_list = [&transaction](NonnullRefPtr<CSSStyleValue> const& x_value, NonnullRefPtr<CSSStyleValue> const& y_value) -> NonnullRefPtr<StyleValueList> {
7253
0
        transaction.commit();
7254
0
        return StyleValueList::create(StyleValueVector { x_value, y_value }, StyleValueList::Separator::Space);
7255
0
    };
7256
7257
0
    switch (tokens.remaining_token_count()) {
7258
0
    case 1: {
7259
0
        auto single_value = to_axis_offset(parse_css_value_for_property(PropertyID::TransformOrigin, tokens));
7260
0
        if (!single_value.has_value())
7261
0
            return nullptr;
7262
        // If only one value is specified, the second value is assumed to be center.
7263
        // FIXME: If one or two values are specified, the third value is assumed to be 0px.
7264
0
        switch (single_value->axis) {
7265
0
        case Axis::None:
7266
0
        case Axis::X:
7267
0
            return make_list(single_value->offset, PercentageStyleValue::create(Percentage(50)));
7268
0
        case Axis::Y:
7269
0
            return make_list(PercentageStyleValue::create(Percentage(50)), single_value->offset);
7270
0
        }
7271
0
        VERIFY_NOT_REACHED();
7272
0
    }
7273
0
    case 2: {
7274
0
        auto first_value = to_axis_offset(parse_css_value_for_property(PropertyID::TransformOrigin, tokens));
7275
0
        auto second_value = to_axis_offset(parse_css_value_for_property(PropertyID::TransformOrigin, tokens));
7276
0
        if (!first_value.has_value() || !second_value.has_value())
7277
0
            return nullptr;
7278
7279
0
        RefPtr<CSSStyleValue> x_value;
7280
0
        RefPtr<CSSStyleValue> y_value;
7281
7282
0
        if (first_value->axis == Axis::X) {
7283
0
            x_value = first_value->offset;
7284
0
        } else if (first_value->axis == Axis::Y) {
7285
0
            y_value = first_value->offset;
7286
0
        }
7287
7288
0
        if (second_value->axis == Axis::X) {
7289
0
            if (x_value)
7290
0
                return nullptr;
7291
0
            x_value = second_value->offset;
7292
            // Put the other in Y since its axis can't have been X
7293
0
            y_value = first_value->offset;
7294
0
        } else if (second_value->axis == Axis::Y) {
7295
0
            if (y_value)
7296
0
                return nullptr;
7297
0
            y_value = second_value->offset;
7298
            // Put the other in X since its axis can't have been Y
7299
0
            x_value = first_value->offset;
7300
0
        } else {
7301
0
            if (x_value) {
7302
0
                VERIFY(!y_value);
7303
0
                y_value = second_value->offset;
7304
0
            } else {
7305
0
                VERIFY(!x_value);
7306
0
                x_value = second_value->offset;
7307
0
            }
7308
0
        }
7309
        // If two or more values are defined and either no value is a keyword, or the only used keyword is center,
7310
        // then the first value represents the horizontal position (or offset) and the second represents the vertical position (or offset).
7311
        // FIXME: A third value always represents the Z position (or offset) and must be of type <length>.
7312
0
        if (first_value->axis == Axis::None && second_value->axis == Axis::None) {
7313
0
            x_value = first_value->offset;
7314
0
            y_value = second_value->offset;
7315
0
        }
7316
0
        return make_list(x_value.release_nonnull(), y_value.release_nonnull());
7317
0
    }
7318
0
    }
7319
7320
0
    return nullptr;
7321
0
}
7322
7323
RefPtr<CSSStyleValue> Parser::parse_transition_value(TokenStream<ComponentValue>& tokens)
7324
0
{
7325
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
7326
0
        return none;
7327
7328
0
    Vector<TransitionStyleValue::Transition> transitions;
7329
0
    auto transaction = tokens.begin_transaction();
7330
7331
0
    while (tokens.has_next_token()) {
7332
0
        TransitionStyleValue::Transition transition;
7333
0
        auto time_value_count = 0;
7334
7335
0
        while (tokens.has_next_token() && !tokens.next_token().is(Token::Type::Comma)) {
7336
0
            if (auto time = parse_time(tokens); time.has_value()) {
7337
0
                switch (time_value_count) {
7338
0
                case 0:
7339
0
                    transition.duration = time.release_value();
7340
0
                    break;
7341
0
                case 1:
7342
0
                    transition.delay = time.release_value();
7343
0
                    break;
7344
0
                default:
7345
0
                    dbgln_if(CSS_PARSER_DEBUG, "Transition property has more than two time values");
7346
0
                    return {};
7347
0
                }
7348
0
                time_value_count++;
7349
0
                continue;
7350
0
            }
7351
7352
0
            if (auto easing = parse_easing_value(tokens)) {
7353
0
                if (transition.easing) {
7354
0
                    dbgln_if(CSS_PARSER_DEBUG, "Transition property has multiple easing values");
7355
0
                    return {};
7356
0
                }
7357
7358
0
                transition.easing = easing->as_easing();
7359
0
                continue;
7360
0
            }
7361
7362
0
            if (tokens.next_token().is(Token::Type::Ident)) {
7363
0
                if (transition.property_name) {
7364
0
                    dbgln_if(CSS_PARSER_DEBUG, "Transition property has multiple property identifiers");
7365
0
                    return {};
7366
0
                }
7367
7368
0
                auto ident = tokens.consume_a_token().token().ident();
7369
0
                if (auto property = property_id_from_string(ident); property.has_value())
7370
0
                    transition.property_name = CustomIdentStyleValue::create(ident);
7371
7372
0
                continue;
7373
0
            }
7374
7375
0
            dbgln_if(CSS_PARSER_DEBUG, "Transition property has unexpected token \"{}\"", tokens.next_token().to_string());
7376
0
            return {};
7377
0
        }
7378
7379
0
        if (!transition.property_name)
7380
0
            transition.property_name = CustomIdentStyleValue::create("all"_fly_string);
7381
7382
0
        if (!transition.easing)
7383
0
            transition.easing = EasingStyleValue::create(EasingStyleValue::CubicBezier::ease());
7384
7385
0
        transitions.append(move(transition));
7386
7387
0
        if (!tokens.next_token().is(Token::Type::Comma))
7388
0
            break;
7389
7390
0
        tokens.discard_a_token();
7391
0
    }
7392
7393
0
    transaction.commit();
7394
0
    return TransitionStyleValue::create(move(transitions));
7395
0
}
7396
7397
RefPtr<CSSStyleValue> Parser::parse_as_css_value(PropertyID property_id)
7398
0
{
7399
0
    auto component_values = parse_a_list_of_component_values(m_token_stream);
7400
0
    auto tokens = TokenStream(component_values);
7401
0
    auto parsed_value = parse_css_value(property_id, tokens);
7402
0
    if (parsed_value.is_error())
7403
0
        return nullptr;
7404
0
    return parsed_value.release_value();
7405
0
}
7406
7407
Optional<CSS::GridSize> Parser::parse_grid_size(ComponentValue const& component_value)
7408
0
{
7409
0
    if (component_value.is_function()) {
7410
0
        if (auto maybe_calculated = parse_calculated_value(component_value)) {
7411
0
            if (maybe_calculated->resolves_to_length_percentage())
7412
0
                return GridSize(LengthPercentage(maybe_calculated.release_nonnull()));
7413
            // FIXME: Support calculated <flex>
7414
0
        }
7415
7416
0
        return {};
7417
0
    }
7418
0
    if (component_value.is_ident("auto"sv))
7419
0
        return GridSize::make_auto();
7420
0
    if (component_value.is_ident("max-content"sv))
7421
0
        return GridSize(GridSize::Type::MaxContent);
7422
0
    if (component_value.is_ident("min-content"sv))
7423
0
        return GridSize(GridSize::Type::MinContent);
7424
0
    auto dimension = parse_dimension(component_value);
7425
0
    if (!dimension.has_value())
7426
0
        return {};
7427
0
    if (dimension->is_length())
7428
0
        return GridSize(dimension->length());
7429
0
    else if (dimension->is_percentage())
7430
0
        return GridSize(dimension->percentage());
7431
0
    else if (dimension->is_flex())
7432
0
        return GridSize(dimension->flex());
7433
0
    return {};
7434
0
}
7435
7436
Optional<CSS::GridFitContent> Parser::parse_fit_content(Vector<ComponentValue> const& component_values)
7437
0
{
7438
    // https://www.w3.org/TR/css-grid-2/#valdef-grid-template-columns-fit-content
7439
    // 'fit-content( <length-percentage> )'
7440
    // Represents the formula max(minimum, min(limit, max-content)), where minimum represents an auto minimum (which is often, but not always,
7441
    // equal to a min-content minimum), and limit is the track sizing function passed as an argument to fit-content().
7442
    // This is essentially calculated as the smaller of minmax(auto, max-content) and minmax(auto, limit).
7443
0
    auto function_tokens = TokenStream(component_values);
7444
0
    function_tokens.discard_whitespace();
7445
0
    auto maybe_length_percentage = parse_length_percentage(function_tokens);
7446
0
    if (maybe_length_percentage.has_value())
7447
0
        return CSS::GridFitContent(CSS::GridSize(CSS::GridSize::Type::FitContent, maybe_length_percentage.value()));
7448
0
    return {};
7449
0
}
7450
7451
Optional<CSS::GridMinMax> Parser::parse_min_max(Vector<ComponentValue> const& component_values)
7452
0
{
7453
    // https://www.w3.org/TR/css-grid-2/#valdef-grid-template-columns-minmax
7454
    // 'minmax(min, max)'
7455
    // Defines a size range greater than or equal to min and less than or equal to max. If the max is
7456
    // less than the min, then the max will be floored by the min (essentially yielding minmax(min,
7457
    // min)). As a maximum, a <flex> value sets the track’s flex factor; it is invalid as a minimum.
7458
0
    auto function_tokens = TokenStream(component_values);
7459
0
    auto comma_separated_list = parse_a_comma_separated_list_of_component_values(function_tokens);
7460
0
    if (comma_separated_list.size() != 2)
7461
0
        return {};
7462
7463
0
    TokenStream part_one_tokens { comma_separated_list[0] };
7464
0
    part_one_tokens.discard_whitespace();
7465
0
    if (!part_one_tokens.has_next_token())
7466
0
        return {};
7467
0
    auto current_token = part_one_tokens.consume_a_token();
7468
0
    auto min_grid_size = parse_grid_size(current_token);
7469
7470
0
    TokenStream part_two_tokens { comma_separated_list[1] };
7471
0
    part_two_tokens.discard_whitespace();
7472
0
    if (!part_two_tokens.has_next_token())
7473
0
        return {};
7474
0
    current_token = part_two_tokens.consume_a_token();
7475
0
    auto max_grid_size = parse_grid_size(current_token);
7476
7477
0
    if (min_grid_size.has_value() && max_grid_size.has_value()) {
7478
        // https://www.w3.org/TR/css-grid-2/#valdef-grid-template-columns-minmax
7479
        // As a maximum, a <flex> value sets the track’s flex factor; it is invalid as a minimum.
7480
0
        if (min_grid_size.value().is_flexible_length())
7481
0
            return {};
7482
0
        return CSS::GridMinMax(min_grid_size.value(), max_grid_size.value());
7483
0
    }
7484
0
    return {};
7485
0
}
7486
7487
Optional<CSS::GridRepeat> Parser::parse_repeat(Vector<ComponentValue> const& component_values)
7488
0
{
7489
    // https://www.w3.org/TR/css-grid-2/#repeat-syntax
7490
    // 7.2.3.1. Syntax of repeat()
7491
    // The generic form of the repeat() syntax is, approximately,
7492
    // repeat( [ <integer [1,∞]> | auto-fill | auto-fit ] , <track-list> )
7493
0
    auto is_auto_fill = false;
7494
0
    auto is_auto_fit = false;
7495
0
    auto function_tokens = TokenStream(component_values);
7496
0
    auto comma_separated_list = parse_a_comma_separated_list_of_component_values(function_tokens);
7497
0
    if (comma_separated_list.size() != 2)
7498
0
        return {};
7499
    // The first argument specifies the number of repetitions.
7500
0
    TokenStream part_one_tokens { comma_separated_list[0] };
7501
0
    part_one_tokens.discard_whitespace();
7502
0
    if (!part_one_tokens.has_next_token())
7503
0
        return {};
7504
0
    auto& current_token = part_one_tokens.consume_a_token();
7505
7506
0
    auto repeat_count = 0;
7507
0
    if (current_token.is(Token::Type::Number) && current_token.token().number().is_integer() && current_token.token().number_value() > 0)
7508
0
        repeat_count = current_token.token().number_value();
7509
0
    else if (current_token.is_ident("auto-fill"sv))
7510
0
        is_auto_fill = true;
7511
0
    else if (current_token.is_ident("auto-fit"sv))
7512
0
        is_auto_fit = true;
7513
7514
    // The second argument is a track list, which is repeated that number of times.
7515
0
    TokenStream part_two_tokens { comma_separated_list[1] };
7516
0
    part_two_tokens.discard_whitespace();
7517
0
    if (!part_two_tokens.has_next_token())
7518
0
        return {};
7519
7520
0
    Vector<Variant<ExplicitGridTrack, GridLineNames>> repeat_params;
7521
0
    auto last_object_was_line_names = false;
7522
0
    while (part_two_tokens.has_next_token()) {
7523
0
        auto token = part_two_tokens.consume_a_token();
7524
0
        Vector<String> line_names;
7525
0
        if (token.is_block()) {
7526
0
            if (last_object_was_line_names)
7527
0
                return {};
7528
0
            last_object_was_line_names = true;
7529
0
            if (!token.block().is_square())
7530
0
                return {};
7531
0
            TokenStream block_tokens { token.block().value };
7532
0
            while (block_tokens.has_next_token()) {
7533
0
                auto current_block_token = block_tokens.consume_a_token();
7534
0
                line_names.append(current_block_token.token().ident().to_string());
7535
0
                block_tokens.discard_whitespace();
7536
0
            }
7537
0
            repeat_params.append(GridLineNames { move(line_names) });
7538
0
            part_two_tokens.discard_whitespace();
7539
0
        } else {
7540
0
            last_object_was_line_names = false;
7541
0
            auto track_sizing_function = parse_track_sizing_function(token);
7542
0
            if (!track_sizing_function.has_value())
7543
0
                return {};
7544
            // However, there are some restrictions:
7545
            // The repeat() notation can’t be nested.
7546
0
            if (track_sizing_function.value().is_repeat())
7547
0
                return {};
7548
7549
            // Automatic repetitions (auto-fill or auto-fit) cannot be combined with intrinsic or flexible sizes.
7550
            // Note that 'auto' is also an intrinsic size (and thus not permitted) but we can't use
7551
            // track_sizing_function.is_auto(..) to check for it, as it requires AvailableSize, which is why there is
7552
            // a separate check for it below.
7553
            // https://www.w3.org/TR/css-grid-2/#repeat-syntax
7554
            // https://www.w3.org/TR/css-grid-2/#intrinsic-sizing-function
7555
0
            if (track_sizing_function.value().is_default()
7556
0
                && (track_sizing_function.value().grid_size().is_flexible_length() || token.is_ident("auto"sv))
7557
0
                && (is_auto_fill || is_auto_fit))
7558
0
                return {};
7559
7560
0
            repeat_params.append(track_sizing_function.value());
7561
0
            part_two_tokens.discard_whitespace();
7562
0
        }
7563
0
    }
7564
7565
    // Thus the precise syntax of the repeat() notation has several forms:
7566
    // <track-repeat> = repeat( [ <integer [1,∞]> ] , [ <line-names>? <track-size> ]+ <line-names>? )
7567
    // <auto-repeat>  = repeat( [ auto-fill | auto-fit ] , [ <line-names>? <fixed-size> ]+ <line-names>? )
7568
    // <fixed-repeat> = repeat( [ <integer [1,∞]> ] , [ <line-names>? <fixed-size> ]+ <line-names>? )
7569
    // <name-repeat>  = repeat( [ <integer [1,∞]> | auto-fill ], <line-names>+)
7570
7571
    // The <track-repeat> variant can represent the repetition of any <track-size>, but is limited to a
7572
    // fixed number of repetitions.
7573
7574
    // The <auto-repeat> variant can repeat automatically to fill a space, but requires definite track
7575
    // sizes so that the number of repetitions can be calculated. It can only appear once in the track
7576
    // list, but the same track list can also contain <fixed-repeat>s.
7577
7578
    // The <name-repeat> variant is for adding line names to subgrids. It can only be used with the
7579
    // subgrid keyword and cannot specify track sizes, only line names.
7580
7581
    // If a repeat() function that is not a <name-repeat> ends up placing two <line-names> adjacent to
7582
    // each other, the name lists are merged. For example, repeat(2, [a] 1fr [b]) is equivalent to [a]
7583
    // 1fr [b a] 1fr [b].
7584
0
    if (is_auto_fill)
7585
0
        return GridRepeat(GridTrackSizeList(move(repeat_params)), GridRepeat::Type::AutoFill);
7586
0
    else if (is_auto_fit)
7587
0
        return GridRepeat(GridTrackSizeList(move(repeat_params)), GridRepeat::Type::AutoFit);
7588
0
    else
7589
0
        return GridRepeat(GridTrackSizeList(move(repeat_params)), repeat_count);
7590
0
}
7591
7592
Optional<CSS::ExplicitGridTrack> Parser::parse_track_sizing_function(ComponentValue const& token)
7593
0
{
7594
0
    if (token.is_function()) {
7595
0
        auto const& function_token = token.function();
7596
0
        if (function_token.name.equals_ignoring_ascii_case("repeat"sv)) {
7597
0
            auto maybe_repeat = parse_repeat(function_token.value);
7598
0
            if (maybe_repeat.has_value())
7599
0
                return CSS::ExplicitGridTrack(maybe_repeat.value());
7600
0
            else
7601
0
                return {};
7602
0
        } else if (function_token.name.equals_ignoring_ascii_case("minmax"sv)) {
7603
0
            auto maybe_min_max_value = parse_min_max(function_token.value);
7604
0
            if (maybe_min_max_value.has_value())
7605
0
                return CSS::ExplicitGridTrack(maybe_min_max_value.value());
7606
0
            else
7607
0
                return {};
7608
0
        } else if (function_token.name.equals_ignoring_ascii_case("fit-content"sv)) {
7609
0
            auto maybe_fit_content_value = parse_fit_content(function_token.value);
7610
0
            if (maybe_fit_content_value.has_value())
7611
0
                return CSS::ExplicitGridTrack(maybe_fit_content_value.value());
7612
0
            return {};
7613
0
        } else if (auto maybe_calculated = parse_calculated_value(token)) {
7614
0
            return CSS::ExplicitGridTrack(GridSize(LengthPercentage(maybe_calculated.release_nonnull())));
7615
0
        }
7616
0
        return {};
7617
0
    } else if (token.is_ident("auto"sv)) {
7618
0
        return CSS::ExplicitGridTrack(GridSize(Length::make_auto()));
7619
0
    } else if (token.is_block()) {
7620
0
        return {};
7621
0
    } else {
7622
0
        auto grid_size = parse_grid_size(token);
7623
0
        if (!grid_size.has_value())
7624
0
            return {};
7625
0
        return CSS::ExplicitGridTrack(grid_size.value());
7626
0
    }
7627
0
}
7628
7629
RefPtr<CSSStyleValue> Parser::parse_grid_track_size_list(TokenStream<ComponentValue>& tokens, bool allow_separate_line_name_blocks)
7630
0
{
7631
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
7632
0
        return GridTrackSizeListStyleValue::make_none();
7633
7634
0
    auto transaction = tokens.begin_transaction();
7635
7636
0
    Vector<Variant<ExplicitGridTrack, GridLineNames>> track_list;
7637
0
    auto last_object_was_line_names = false;
7638
0
    while (tokens.has_next_token()) {
7639
0
        auto token = tokens.consume_a_token();
7640
0
        if (token.is_block()) {
7641
0
            if (last_object_was_line_names && !allow_separate_line_name_blocks) {
7642
0
                transaction.commit();
7643
0
                return GridTrackSizeListStyleValue::make_auto();
7644
0
            }
7645
0
            last_object_was_line_names = true;
7646
0
            Vector<String> line_names;
7647
0
            if (!token.block().is_square()) {
7648
0
                transaction.commit();
7649
0
                return GridTrackSizeListStyleValue::make_auto();
7650
0
            }
7651
0
            TokenStream block_tokens { token.block().value };
7652
0
            block_tokens.discard_whitespace();
7653
0
            while (block_tokens.has_next_token()) {
7654
0
                auto current_block_token = block_tokens.consume_a_token();
7655
0
                line_names.append(current_block_token.token().ident().to_string());
7656
0
                block_tokens.discard_whitespace();
7657
0
            }
7658
0
            track_list.append(GridLineNames { move(line_names) });
7659
0
        } else {
7660
0
            last_object_was_line_names = false;
7661
0
            auto track_sizing_function = parse_track_sizing_function(token);
7662
0
            if (!track_sizing_function.has_value()) {
7663
0
                transaction.commit();
7664
0
                return GridTrackSizeListStyleValue::make_auto();
7665
0
            }
7666
            // FIXME: Handle multiple repeat values (should combine them here, or remove
7667
            // any other ones if the first one is auto-fill, etc.)
7668
0
            track_list.append(track_sizing_function.value());
7669
0
        }
7670
0
    }
7671
7672
0
    transaction.commit();
7673
0
    return GridTrackSizeListStyleValue::create(GridTrackSizeList(move(track_list)));
7674
0
}
7675
7676
// https://www.w3.org/TR/css-grid-1/#grid-auto-flow-property
7677
RefPtr<GridAutoFlowStyleValue> Parser::parse_grid_auto_flow_value(TokenStream<ComponentValue>& tokens)
7678
0
{
7679
    // [ row | column ] || dense
7680
0
    if (!tokens.has_next_token())
7681
0
        return nullptr;
7682
7683
0
    auto transaction = tokens.begin_transaction();
7684
7685
0
    auto parse_axis = [&]() -> Optional<GridAutoFlowStyleValue::Axis> {
7686
0
        auto transaction = tokens.begin_transaction();
7687
0
        auto token = tokens.consume_a_token();
7688
0
        if (!token.is(Token::Type::Ident))
7689
0
            return {};
7690
0
        auto const& ident = token.token().ident();
7691
0
        if (ident.equals_ignoring_ascii_case("row"sv)) {
7692
0
            transaction.commit();
7693
0
            return GridAutoFlowStyleValue::Axis::Row;
7694
0
        } else if (ident.equals_ignoring_ascii_case("column"sv)) {
7695
0
            transaction.commit();
7696
0
            return GridAutoFlowStyleValue::Axis::Column;
7697
0
        }
7698
0
        return {};
7699
0
    };
7700
7701
0
    auto parse_dense = [&]() -> Optional<GridAutoFlowStyleValue::Dense> {
7702
0
        auto transaction = tokens.begin_transaction();
7703
0
        auto token = tokens.consume_a_token();
7704
0
        if (!token.is(Token::Type::Ident))
7705
0
            return {};
7706
0
        auto const& ident = token.token().ident();
7707
0
        if (ident.equals_ignoring_ascii_case("dense"sv)) {
7708
0
            transaction.commit();
7709
0
            return GridAutoFlowStyleValue::Dense::Yes;
7710
0
        }
7711
0
        return {};
7712
0
    };
7713
7714
0
    Optional<GridAutoFlowStyleValue::Axis> axis;
7715
0
    Optional<GridAutoFlowStyleValue::Dense> dense;
7716
0
    if (axis = parse_axis(); axis.has_value()) {
7717
0
        dense = parse_dense();
7718
0
    } else if (dense = parse_dense(); dense.has_value()) {
7719
0
        axis = parse_axis();
7720
0
    }
7721
7722
0
    if (tokens.has_next_token())
7723
0
        return nullptr;
7724
7725
0
    transaction.commit();
7726
0
    return GridAutoFlowStyleValue::create(axis.value_or(GridAutoFlowStyleValue::Axis::Row), dense.value_or(GridAutoFlowStyleValue::Dense::No));
7727
0
}
7728
7729
// https://drafts.csswg.org/css-overflow/#propdef-scrollbar-gutter
7730
RefPtr<CSSStyleValue> Parser::parse_scrollbar_gutter_value(TokenStream<ComponentValue>& tokens)
7731
0
{
7732
    // auto | stable && both-edges?
7733
0
    if (!tokens.has_next_token())
7734
0
        return nullptr;
7735
7736
0
    auto transaction = tokens.begin_transaction();
7737
7738
0
    auto parse_stable = [&]() -> Optional<bool> {
7739
0
        auto transaction = tokens.begin_transaction();
7740
0
        auto token = tokens.consume_a_token();
7741
0
        if (!token.is(Token::Type::Ident))
7742
0
            return {};
7743
0
        auto const& ident = token.token().ident();
7744
0
        if (ident.equals_ignoring_ascii_case("auto"sv)) {
7745
0
            transaction.commit();
7746
0
            return false;
7747
0
        } else if (ident.equals_ignoring_ascii_case("stable"sv)) {
7748
0
            transaction.commit();
7749
0
            return true;
7750
0
        }
7751
0
        return {};
7752
0
    };
7753
7754
0
    auto parse_both_edges = [&]() -> Optional<bool> {
7755
0
        auto transaction = tokens.begin_transaction();
7756
0
        auto token = tokens.consume_a_token();
7757
0
        if (!token.is(Token::Type::Ident))
7758
0
            return {};
7759
0
        auto const& ident = token.token().ident();
7760
0
        if (ident.equals_ignoring_ascii_case("both-edges"sv)) {
7761
0
            transaction.commit();
7762
0
            return true;
7763
0
        }
7764
0
        return {};
7765
0
    };
7766
7767
0
    Optional<bool> stable;
7768
0
    Optional<bool> both_edges;
7769
0
    if (stable = parse_stable(); stable.has_value()) {
7770
0
        if (stable.value())
7771
0
            both_edges = parse_both_edges();
7772
0
    } else if (both_edges = parse_both_edges(); both_edges.has_value()) {
7773
0
        stable = parse_stable();
7774
0
        if (!stable.has_value() || !stable.value())
7775
0
            return nullptr;
7776
0
    }
7777
7778
0
    if (tokens.has_next_token())
7779
0
        return nullptr;
7780
7781
0
    transaction.commit();
7782
7783
0
    ScrollbarGutter gutter_value;
7784
0
    if (both_edges.has_value())
7785
0
        gutter_value = ScrollbarGutter::BothEdges;
7786
0
    else if (stable.has_value() && stable.value())
7787
0
        gutter_value = ScrollbarGutter::Stable;
7788
0
    else
7789
0
        gutter_value = ScrollbarGutter::Auto;
7790
0
    return ScrollbarGutterStyleValue::create(gutter_value);
7791
0
}
7792
7793
RefPtr<CSSStyleValue> Parser::parse_grid_auto_track_sizes(TokenStream<ComponentValue>& tokens)
7794
0
{
7795
    // https://www.w3.org/TR/css-grid-2/#auto-tracks
7796
    // <track-size>+
7797
0
    Vector<Variant<ExplicitGridTrack, GridLineNames>> track_list;
7798
0
    auto transaction = tokens.begin_transaction();
7799
0
    while (tokens.has_next_token()) {
7800
0
        auto token = tokens.consume_a_token();
7801
0
        auto track_sizing_function = parse_track_sizing_function(token);
7802
0
        if (!track_sizing_function.has_value()) {
7803
0
            transaction.commit();
7804
0
            return GridTrackSizeListStyleValue::make_auto();
7805
0
        }
7806
        // FIXME: Handle multiple repeat values (should combine them here, or remove
7807
        //        any other ones if the first one is auto-fill, etc.)
7808
0
        track_list.append(track_sizing_function.value());
7809
0
    }
7810
0
    transaction.commit();
7811
0
    return GridTrackSizeListStyleValue::create(GridTrackSizeList(move(track_list)));
7812
0
}
7813
7814
RefPtr<GridTrackPlacementStyleValue> Parser::parse_grid_track_placement(TokenStream<ComponentValue>& tokens)
7815
0
{
7816
    // FIXME: This shouldn't be needed. Right now, the below code returns a CSSStyleValue even if no tokens are consumed!
7817
0
    if (!tokens.has_next_token())
7818
0
        return nullptr;
7819
7820
    // https://www.w3.org/TR/css-grid-2/#line-placement
7821
    // Line-based Placement: the grid-row-start, grid-column-start, grid-row-end, and grid-column-end properties
7822
    // <grid-line> =
7823
    //     auto |
7824
    //     <custom-ident> |
7825
    //     [ <integer> && <custom-ident>? ] |
7826
    //     [ span && [ <integer> || <custom-ident> ] ]
7827
0
    auto is_valid_integer = [](auto& token) -> bool {
7828
        // An <integer> value of zero makes the declaration invalid.
7829
0
        if (token.is(Token::Type::Number) && token.token().number().is_integer() && token.token().number_value() != 0)
7830
0
            return true;
7831
0
        return false;
7832
0
    };
7833
0
    auto parse_custom_ident = [this](auto& tokens) {
7834
        // The <custom-ident> additionally excludes the keywords span and auto.
7835
0
        return parse_custom_ident_value(tokens, { "span"sv, "auto"sv });
7836
0
    };
7837
7838
0
    auto transaction = tokens.begin_transaction();
7839
7840
    // FIXME: Handle the single-token case inside the loop instead, so that we can more easily call this from
7841
    //        `parse_grid_area_shorthand_value()` using a single TokenStream.
7842
0
    if (tokens.remaining_token_count() == 1) {
7843
0
        if (auto custom_ident = parse_custom_ident(tokens)) {
7844
0
            transaction.commit();
7845
0
            return GridTrackPlacementStyleValue::create(GridTrackPlacement::make_line({}, custom_ident->custom_ident().to_string()));
7846
0
        }
7847
0
        auto& token = tokens.consume_a_token();
7848
0
        if (auto maybe_calculated = parse_calculated_value(token); maybe_calculated && maybe_calculated->resolves_to_number()) {
7849
0
            transaction.commit();
7850
0
            return GridTrackPlacementStyleValue::create(GridTrackPlacement::make_line(static_cast<int>(maybe_calculated->resolve_integer().value()), {}));
7851
0
        }
7852
0
        if (token.is_ident("auto"sv)) {
7853
0
            transaction.commit();
7854
0
            return GridTrackPlacementStyleValue::create(GridTrackPlacement::make_auto());
7855
0
        }
7856
0
        if (token.is_ident("span"sv)) {
7857
0
            transaction.commit();
7858
0
            return GridTrackPlacementStyleValue::create(GridTrackPlacement::make_span(1));
7859
0
        }
7860
0
        if (is_valid_integer(token)) {
7861
0
            transaction.commit();
7862
0
            return GridTrackPlacementStyleValue::create(GridTrackPlacement::make_line(static_cast<int>(token.token().number_value()), {}));
7863
0
        }
7864
0
        return nullptr;
7865
0
    }
7866
7867
0
    auto span_value = false;
7868
0
    auto span_or_position_value = 0;
7869
0
    String identifier_value;
7870
0
    while (tokens.has_next_token()) {
7871
0
        auto& token = tokens.next_token();
7872
0
        if (token.is_ident("auto"sv))
7873
0
            return nullptr;
7874
0
        if (token.is_ident("span"sv)) {
7875
0
            if (span_value)
7876
0
                return nullptr;
7877
0
            tokens.discard_a_token(); // span
7878
0
            span_value = true;
7879
0
            continue;
7880
0
        }
7881
0
        if (is_valid_integer(token)) {
7882
0
            if (span_or_position_value != 0)
7883
0
                return nullptr;
7884
0
            span_or_position_value = static_cast<int>(tokens.consume_a_token().token().to_integer());
7885
0
            continue;
7886
0
        }
7887
0
        if (auto custom_ident = parse_custom_ident(tokens)) {
7888
0
            if (!identifier_value.is_empty())
7889
0
                return nullptr;
7890
0
            identifier_value = custom_ident->custom_ident().to_string();
7891
0
            continue;
7892
0
        }
7893
0
        break;
7894
0
    }
7895
7896
    // Negative integers or zero are invalid.
7897
0
    if (span_value && span_or_position_value < 1)
7898
0
        return nullptr;
7899
7900
    // If the <integer> is omitted, it defaults to 1.
7901
0
    if (span_or_position_value == 0)
7902
0
        span_or_position_value = 1;
7903
7904
0
    transaction.commit();
7905
0
    if (!identifier_value.is_empty())
7906
0
        return GridTrackPlacementStyleValue::create(GridTrackPlacement::make_line(span_or_position_value, identifier_value));
7907
0
    return GridTrackPlacementStyleValue::create(GridTrackPlacement::make_span(span_or_position_value));
7908
0
}
7909
7910
RefPtr<CSSStyleValue> Parser::parse_grid_track_placement_shorthand_value(PropertyID property_id, TokenStream<ComponentValue>& tokens)
7911
0
{
7912
0
    auto start_property = (property_id == PropertyID::GridColumn) ? PropertyID::GridColumnStart : PropertyID::GridRowStart;
7913
0
    auto end_property = (property_id == PropertyID::GridColumn) ? PropertyID::GridColumnEnd : PropertyID::GridRowEnd;
7914
7915
0
    auto transaction = tokens.begin_transaction();
7916
0
    auto current_token = tokens.consume_a_token();
7917
7918
0
    Vector<ComponentValue> track_start_placement_tokens;
7919
0
    while (true) {
7920
0
        if (current_token.is_delim('/'))
7921
0
            break;
7922
0
        track_start_placement_tokens.append(current_token);
7923
0
        if (!tokens.has_next_token())
7924
0
            break;
7925
0
        current_token = tokens.consume_a_token();
7926
0
    }
7927
7928
0
    Vector<ComponentValue> track_end_placement_tokens;
7929
0
    if (tokens.has_next_token()) {
7930
0
        current_token = tokens.consume_a_token();
7931
0
        while (true) {
7932
0
            track_end_placement_tokens.append(current_token);
7933
0
            if (!tokens.has_next_token())
7934
0
                break;
7935
0
            current_token = tokens.consume_a_token();
7936
0
        }
7937
0
    }
7938
7939
0
    TokenStream track_start_placement_token_stream { track_start_placement_tokens };
7940
0
    auto parsed_start_value = parse_grid_track_placement(track_start_placement_token_stream);
7941
0
    if (parsed_start_value && track_end_placement_tokens.is_empty()) {
7942
0
        transaction.commit();
7943
0
        if (parsed_start_value->grid_track_placement().has_identifier()) {
7944
0
            auto custom_ident = parsed_start_value.release_nonnull();
7945
0
            return ShorthandStyleValue::create(property_id, { start_property, end_property }, { custom_ident, custom_ident });
7946
0
        }
7947
0
        return ShorthandStyleValue::create(property_id,
7948
0
            { start_property, end_property },
7949
0
            { parsed_start_value.release_nonnull(), GridTrackPlacementStyleValue::create(GridTrackPlacement::make_auto()) });
7950
0
    }
7951
7952
0
    TokenStream track_end_placement_token_stream { track_end_placement_tokens };
7953
0
    auto parsed_end_value = parse_grid_track_placement(track_end_placement_token_stream);
7954
0
    if (parsed_start_value && parsed_end_value) {
7955
0
        transaction.commit();
7956
0
        return ShorthandStyleValue::create(property_id,
7957
0
            { start_property, end_property },
7958
0
            { parsed_start_value.release_nonnull(), parsed_end_value.release_nonnull() });
7959
0
    }
7960
7961
0
    return nullptr;
7962
0
}
7963
7964
// https://www.w3.org/TR/css-grid-2/#explicit-grid-shorthand
7965
// 7.4. Explicit Grid Shorthand: the grid-template property
7966
RefPtr<CSSStyleValue> Parser::parse_grid_track_size_list_shorthand_value(PropertyID property_id, TokenStream<ComponentValue>& tokens)
7967
0
{
7968
    // The grid-template property is a shorthand for setting grid-template-columns, grid-template-rows,
7969
    // and grid-template-areas in a single declaration. It has several distinct syntax forms:
7970
    // none
7971
    //    - Sets all three properties to their initial values (none).
7972
    // <'grid-template-rows'> / <'grid-template-columns'>
7973
    //    - Sets grid-template-rows and grid-template-columns to the specified values, respectively, and sets grid-template-areas to none.
7974
    // [ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list> ]?
7975
    //    - Sets grid-template-areas to the strings listed.
7976
    //    - Sets grid-template-rows to the <track-size>s following each string (filling in auto for any missing sizes),
7977
    //      and splicing in the named lines defined before/after each size.
7978
    //    - Sets grid-template-columns to the track listing specified after the slash (or none, if not specified).
7979
0
    auto transaction = tokens.begin_transaction();
7980
7981
    // FIXME: Read the parts in place if possible, instead of constructing separate vectors and streams.
7982
0
    Vector<ComponentValue> template_rows_tokens;
7983
0
    Vector<ComponentValue> template_columns_tokens;
7984
0
    Vector<ComponentValue> template_area_tokens;
7985
7986
0
    bool found_forward_slash = false;
7987
7988
0
    while (tokens.has_next_token()) {
7989
0
        auto& token = tokens.consume_a_token();
7990
0
        if (token.is_delim('/')) {
7991
0
            if (found_forward_slash)
7992
0
                return nullptr;
7993
0
            found_forward_slash = true;
7994
0
            continue;
7995
0
        }
7996
0
        if (found_forward_slash) {
7997
0
            template_columns_tokens.append(token);
7998
0
            continue;
7999
0
        }
8000
0
        if (token.is(Token::Type::String))
8001
0
            template_area_tokens.append(token);
8002
0
        else
8003
0
            template_rows_tokens.append(token);
8004
0
    }
8005
8006
0
    TokenStream template_area_token_stream { template_area_tokens };
8007
0
    TokenStream template_rows_token_stream { template_rows_tokens };
8008
0
    TokenStream template_columns_token_stream { template_columns_tokens };
8009
0
    auto parsed_template_areas_values = parse_grid_template_areas_value(template_area_token_stream);
8010
0
    auto parsed_template_rows_values = parse_grid_track_size_list(template_rows_token_stream, true);
8011
0
    auto parsed_template_columns_values = parse_grid_track_size_list(template_columns_token_stream);
8012
8013
0
    if (template_area_token_stream.has_next_token()
8014
0
        || template_rows_token_stream.has_next_token()
8015
0
        || template_columns_token_stream.has_next_token())
8016
0
        return nullptr;
8017
8018
0
    transaction.commit();
8019
0
    return ShorthandStyleValue::create(property_id,
8020
0
        { PropertyID::GridTemplateAreas, PropertyID::GridTemplateRows, PropertyID::GridTemplateColumns },
8021
0
        { parsed_template_areas_values.release_nonnull(), parsed_template_rows_values.release_nonnull(), parsed_template_columns_values.release_nonnull() });
8022
0
}
8023
8024
RefPtr<CSSStyleValue> Parser::parse_grid_area_shorthand_value(TokenStream<ComponentValue>& tokens)
8025
0
{
8026
0
    auto transaction = tokens.begin_transaction();
8027
8028
0
    auto parse_placement_tokens = [&](Vector<ComponentValue>& placement_tokens, bool check_for_delimiter = true) -> void {
8029
0
        while (tokens.has_next_token()) {
8030
0
            auto& current_token = tokens.consume_a_token();
8031
0
            if (check_for_delimiter && current_token.is_delim('/'))
8032
0
                break;
8033
0
            placement_tokens.append(current_token);
8034
0
        }
8035
0
    };
8036
8037
0
    Vector<ComponentValue> row_start_placement_tokens;
8038
0
    parse_placement_tokens(row_start_placement_tokens);
8039
8040
0
    Vector<ComponentValue> column_start_placement_tokens;
8041
0
    if (tokens.has_next_token())
8042
0
        parse_placement_tokens(column_start_placement_tokens);
8043
8044
0
    Vector<ComponentValue> row_end_placement_tokens;
8045
0
    if (tokens.has_next_token())
8046
0
        parse_placement_tokens(row_end_placement_tokens);
8047
8048
0
    Vector<ComponentValue> column_end_placement_tokens;
8049
0
    if (tokens.has_next_token())
8050
0
        parse_placement_tokens(column_end_placement_tokens, false);
8051
8052
    // https://www.w3.org/TR/css-grid-2/#placement-shorthands
8053
    // The grid-area property is a shorthand for grid-row-start, grid-column-start, grid-row-end and
8054
    // grid-column-end.
8055
0
    TokenStream row_start_placement_token_stream { row_start_placement_tokens };
8056
0
    auto row_start_style_value = parse_grid_track_placement(row_start_placement_token_stream);
8057
0
    if (row_start_placement_token_stream.has_next_token())
8058
0
        return nullptr;
8059
8060
0
    TokenStream column_start_placement_token_stream { column_start_placement_tokens };
8061
0
    auto column_start_style_value = parse_grid_track_placement(column_start_placement_token_stream);
8062
0
    if (column_start_placement_token_stream.has_next_token())
8063
0
        return nullptr;
8064
8065
0
    TokenStream row_end_placement_token_stream { row_end_placement_tokens };
8066
0
    auto row_end_style_value = parse_grid_track_placement(row_end_placement_token_stream);
8067
0
    if (row_end_placement_token_stream.has_next_token())
8068
0
        return nullptr;
8069
8070
0
    TokenStream column_end_placement_token_stream { column_end_placement_tokens };
8071
0
    auto column_end_style_value = parse_grid_track_placement(column_end_placement_token_stream);
8072
0
    if (column_end_placement_token_stream.has_next_token())
8073
0
        return nullptr;
8074
8075
    // If four <grid-line> values are specified, grid-row-start is set to the first value, grid-column-start
8076
    // is set to the second value, grid-row-end is set to the third value, and grid-column-end is set to the
8077
    // fourth value.
8078
0
    auto row_start = GridTrackPlacement::make_auto();
8079
0
    auto column_start = GridTrackPlacement::make_auto();
8080
0
    auto row_end = GridTrackPlacement::make_auto();
8081
0
    auto column_end = GridTrackPlacement::make_auto();
8082
8083
0
    if (row_start_style_value)
8084
0
        row_start = row_start_style_value.release_nonnull()->as_grid_track_placement().grid_track_placement();
8085
8086
    // When grid-column-start is omitted, if grid-row-start is a <custom-ident>, all four longhands are set to
8087
    // that value. Otherwise, it is set to auto.
8088
0
    if (column_start_style_value)
8089
0
        column_start = column_start_style_value.release_nonnull()->as_grid_track_placement().grid_track_placement();
8090
0
    else
8091
0
        column_start = row_start;
8092
8093
    // When grid-row-end is omitted, if grid-row-start is a <custom-ident>, grid-row-end is set to that
8094
    // <custom-ident>; otherwise, it is set to auto.
8095
0
    if (row_end_style_value)
8096
0
        row_end = row_end_style_value.release_nonnull()->as_grid_track_placement().grid_track_placement();
8097
0
    else
8098
0
        row_end = column_start;
8099
8100
    // When grid-column-end is omitted, if grid-column-start is a <custom-ident>, grid-column-end is set to
8101
    // that <custom-ident>; otherwise, it is set to auto.
8102
0
    if (column_end_style_value)
8103
0
        column_end = column_end_style_value.release_nonnull()->as_grid_track_placement().grid_track_placement();
8104
0
    else
8105
0
        column_end = row_end;
8106
8107
0
    transaction.commit();
8108
0
    return ShorthandStyleValue::create(PropertyID::GridArea,
8109
0
        { PropertyID::GridRowStart, PropertyID::GridColumnStart, PropertyID::GridRowEnd, PropertyID::GridColumnEnd },
8110
0
        { GridTrackPlacementStyleValue::create(row_start), GridTrackPlacementStyleValue::create(column_start), GridTrackPlacementStyleValue::create(row_end), GridTrackPlacementStyleValue::create(column_end) });
8111
0
}
8112
8113
RefPtr<CSSStyleValue> Parser::parse_grid_shorthand_value(TokenStream<ComponentValue>& tokens)
8114
0
{
8115
    // <'grid-template'> |
8116
    // FIXME: <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? |
8117
    // FIXME: [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
8118
0
    return parse_grid_track_size_list_shorthand_value(PropertyID::Grid, tokens);
8119
0
}
8120
8121
// https://www.w3.org/TR/css-grid-1/#grid-template-areas-property
8122
RefPtr<CSSStyleValue> Parser::parse_grid_template_areas_value(TokenStream<ComponentValue>& tokens)
8123
0
{
8124
    // none | <string>+
8125
0
    Vector<Vector<String>> grid_area_rows;
8126
8127
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
8128
0
        return GridTemplateAreaStyleValue::create(move(grid_area_rows));
8129
8130
0
    auto transaction = tokens.begin_transaction();
8131
0
    while (tokens.has_next_token() && tokens.next_token().is(Token::Type::String)) {
8132
0
        Vector<String> grid_area_columns;
8133
0
        auto const parts = MUST(tokens.consume_a_token().token().string().to_string().split(' '));
8134
0
        for (auto& part : parts) {
8135
0
            grid_area_columns.append(part);
8136
0
        }
8137
0
        grid_area_rows.append(move(grid_area_columns));
8138
0
    }
8139
0
    transaction.commit();
8140
0
    return GridTemplateAreaStyleValue::create(grid_area_rows);
8141
0
}
8142
8143
static bool block_contains_var_or_attr(SimpleBlock const& block);
8144
8145
static bool function_contains_var_or_attr(Function const& function)
8146
0
{
8147
0
    if (function.name.equals_ignoring_ascii_case("var"sv) || function.name.equals_ignoring_ascii_case("attr"sv))
8148
0
        return true;
8149
0
    for (auto const& token : function.value) {
8150
0
        if (token.is_function() && function_contains_var_or_attr(token.function()))
8151
0
            return true;
8152
0
        if (token.is_block() && block_contains_var_or_attr(token.block()))
8153
0
            return true;
8154
0
    }
8155
0
    return false;
8156
0
}
8157
8158
bool block_contains_var_or_attr(SimpleBlock const& block)
8159
0
{
8160
0
    for (auto const& token : block.value) {
8161
0
        if (token.is_function() && function_contains_var_or_attr(token.function()))
8162
0
            return true;
8163
0
        if (token.is_block() && block_contains_var_or_attr(token.block()))
8164
0
            return true;
8165
0
    }
8166
0
    return false;
8167
0
}
8168
8169
Parser::ParseErrorOr<NonnullRefPtr<CSSStyleValue>> Parser::parse_css_value(PropertyID property_id, TokenStream<ComponentValue>& unprocessed_tokens, Optional<String> original_source_text)
8170
0
{
8171
0
    m_context.set_current_property_id(property_id);
8172
0
    Vector<ComponentValue> component_values;
8173
0
    bool contains_var_or_attr = false;
8174
0
    bool const property_accepts_custom_ident = property_accepts_type(property_id, ValueType::CustomIdent);
8175
8176
0
    while (unprocessed_tokens.has_next_token()) {
8177
0
        auto const& token = unprocessed_tokens.consume_a_token();
8178
8179
0
        if (token.is(Token::Type::Semicolon)) {
8180
0
            unprocessed_tokens.reconsume_current_input_token();
8181
0
            break;
8182
0
        }
8183
8184
0
        if (property_id != PropertyID::Custom) {
8185
0
            if (token.is(Token::Type::Whitespace))
8186
0
                continue;
8187
8188
0
            if (!property_accepts_custom_ident && token.is(Token::Type::Ident) && has_ignored_vendor_prefix(token.token().ident()))
8189
0
                return ParseError::IncludesIgnoredVendorPrefix;
8190
0
        }
8191
8192
0
        if (!contains_var_or_attr) {
8193
0
            if (token.is_function() && function_contains_var_or_attr(token.function()))
8194
0
                contains_var_or_attr = true;
8195
0
            else if (token.is_block() && block_contains_var_or_attr(token.block()))
8196
0
                contains_var_or_attr = true;
8197
0
        }
8198
8199
0
        component_values.append(token);
8200
0
    }
8201
8202
0
    if (property_id == PropertyID::Custom || contains_var_or_attr)
8203
0
        return UnresolvedStyleValue::create(move(component_values), contains_var_or_attr, original_source_text);
8204
8205
0
    if (component_values.is_empty())
8206
0
        return ParseError::SyntaxError;
8207
8208
0
    auto tokens = TokenStream { component_values };
8209
8210
0
    if (component_values.size() == 1) {
8211
0
        if (auto parsed_value = parse_builtin_value(tokens))
8212
0
            return parsed_value.release_nonnull();
8213
0
    }
8214
8215
    // Special-case property handling
8216
0
    switch (property_id) {
8217
0
    case PropertyID::AspectRatio:
8218
0
        if (auto parsed_value = parse_aspect_ratio_value(tokens); parsed_value && !tokens.has_next_token())
8219
0
            return parsed_value.release_nonnull();
8220
0
        return ParseError::SyntaxError;
8221
0
    case PropertyID::BackdropFilter:
8222
0
    case PropertyID::Filter:
8223
0
        if (auto parsed_value = parse_filter_value_list_value(tokens); parsed_value && !tokens.has_next_token())
8224
0
            return parsed_value.release_nonnull();
8225
0
        return ParseError::SyntaxError;
8226
0
    case PropertyID::Background:
8227
0
        if (auto parsed_value = parse_background_value(tokens); parsed_value && !tokens.has_next_token())
8228
0
            return parsed_value.release_nonnull();
8229
0
        return ParseError::SyntaxError;
8230
0
    case PropertyID::BackgroundAttachment:
8231
0
    case PropertyID::BackgroundClip:
8232
0
    case PropertyID::BackgroundImage:
8233
0
    case PropertyID::BackgroundOrigin:
8234
0
        if (auto parsed_value = parse_simple_comma_separated_value_list(property_id, tokens))
8235
0
            return parsed_value.release_nonnull();
8236
0
        return ParseError::SyntaxError;
8237
0
    case PropertyID::BackgroundPosition:
8238
0
        if (auto parsed_value = parse_comma_separated_value_list(tokens, [this](auto& tokens) { return parse_position_value(tokens, PositionParsingMode::BackgroundPosition); }))
8239
0
            return parsed_value.release_nonnull();
8240
0
        return ParseError::SyntaxError;
8241
0
    case PropertyID::BackgroundPositionX:
8242
0
    case PropertyID::BackgroundPositionY:
8243
0
        if (auto parsed_value = parse_comma_separated_value_list(tokens, [this, property_id](auto& tokens) { return parse_single_background_position_x_or_y_value(tokens, property_id); }))
8244
0
            return parsed_value.release_nonnull();
8245
0
        return ParseError::SyntaxError;
8246
0
    case PropertyID::BackgroundRepeat:
8247
0
        if (auto parsed_value = parse_comma_separated_value_list(tokens, [this](auto& tokens) { return parse_single_background_repeat_value(tokens); }))
8248
0
            return parsed_value.release_nonnull();
8249
0
        return ParseError::SyntaxError;
8250
0
    case PropertyID::BackgroundSize:
8251
0
        if (auto parsed_value = parse_comma_separated_value_list(tokens, [this](auto& tokens) { return parse_single_background_size_value(tokens); }))
8252
0
            return parsed_value.release_nonnull();
8253
0
        return ParseError::SyntaxError;
8254
0
    case PropertyID::Border:
8255
0
    case PropertyID::BorderBottom:
8256
0
    case PropertyID::BorderLeft:
8257
0
    case PropertyID::BorderRight:
8258
0
    case PropertyID::BorderTop:
8259
0
        if (auto parsed_value = parse_border_value(property_id, tokens); parsed_value && !tokens.has_next_token())
8260
0
            return parsed_value.release_nonnull();
8261
0
        return ParseError::SyntaxError;
8262
0
    case PropertyID::BorderTopLeftRadius:
8263
0
    case PropertyID::BorderTopRightRadius:
8264
0
    case PropertyID::BorderBottomRightRadius:
8265
0
    case PropertyID::BorderBottomLeftRadius:
8266
0
        if (auto parsed_value = parse_border_radius_value(tokens); parsed_value && !tokens.has_next_token())
8267
0
            return parsed_value.release_nonnull();
8268
0
        return ParseError::SyntaxError;
8269
0
    case PropertyID::BorderRadius:
8270
0
        if (auto parsed_value = parse_border_radius_shorthand_value(tokens); parsed_value && !tokens.has_next_token())
8271
0
            return parsed_value.release_nonnull();
8272
0
        return ParseError::SyntaxError;
8273
0
    case PropertyID::BoxShadow:
8274
0
        if (auto parsed_value = parse_shadow_value(tokens, AllowInsetKeyword::Yes); parsed_value && !tokens.has_next_token())
8275
0
            return parsed_value.release_nonnull();
8276
0
        return ParseError::SyntaxError;
8277
0
    case PropertyID::Columns:
8278
0
        if (auto parsed_value = parse_columns_value(tokens); parsed_value && !tokens.has_next_token())
8279
0
            return parsed_value.release_nonnull();
8280
0
        return ParseError::SyntaxError;
8281
0
    case PropertyID::Content:
8282
0
        if (auto parsed_value = parse_content_value(tokens); parsed_value && !tokens.has_next_token())
8283
0
            return parsed_value.release_nonnull();
8284
0
        return ParseError::SyntaxError;
8285
0
    case PropertyID::CounterIncrement:
8286
0
        if (auto parsed_value = parse_counter_increment_value(tokens); parsed_value && !tokens.has_next_token())
8287
0
            return parsed_value.release_nonnull();
8288
0
        return ParseError::SyntaxError;
8289
0
    case PropertyID::CounterReset:
8290
0
        if (auto parsed_value = parse_counter_reset_value(tokens); parsed_value && !tokens.has_next_token())
8291
0
            return parsed_value.release_nonnull();
8292
0
        return ParseError::SyntaxError;
8293
0
    case PropertyID::CounterSet:
8294
0
        if (auto parsed_value = parse_counter_set_value(tokens); parsed_value && !tokens.has_next_token())
8295
0
            return parsed_value.release_nonnull();
8296
0
        return ParseError::SyntaxError;
8297
0
    case PropertyID::Display:
8298
0
        if (auto parsed_value = parse_display_value(tokens); parsed_value && !tokens.has_next_token())
8299
0
            return parsed_value.release_nonnull();
8300
0
        return ParseError::SyntaxError;
8301
0
    case PropertyID::Flex:
8302
0
        if (auto parsed_value = parse_flex_shorthand_value(tokens); parsed_value && !tokens.has_next_token())
8303
0
            return parsed_value.release_nonnull();
8304
0
        return ParseError::SyntaxError;
8305
0
    case PropertyID::FlexFlow:
8306
0
        if (auto parsed_value = parse_flex_flow_value(tokens); parsed_value && !tokens.has_next_token())
8307
0
            return parsed_value.release_nonnull();
8308
0
        return ParseError::SyntaxError;
8309
0
    case PropertyID::Font:
8310
0
        if (auto parsed_value = parse_font_value(tokens); parsed_value && !tokens.has_next_token())
8311
0
            return parsed_value.release_nonnull();
8312
0
        return ParseError::SyntaxError;
8313
0
    case PropertyID::FontFamily:
8314
0
        if (auto parsed_value = parse_font_family_value(tokens); parsed_value && !tokens.has_next_token())
8315
0
            return parsed_value.release_nonnull();
8316
0
        return ParseError::SyntaxError;
8317
0
    case PropertyID::FontFeatureSettings:
8318
0
        if (auto parsed_value = parse_font_feature_settings_value(tokens); parsed_value && !tokens.has_next_token())
8319
0
            return parsed_value.release_nonnull();
8320
0
        return ParseError::SyntaxError;
8321
0
    case PropertyID::FontLanguageOverride:
8322
0
        if (auto parsed_value = parse_font_language_override_value(tokens); parsed_value && !tokens.has_next_token())
8323
0
            return parsed_value.release_nonnull();
8324
0
        return ParseError::SyntaxError;
8325
0
    case PropertyID::FontVariationSettings:
8326
0
        if (auto parsed_value = parse_font_variation_settings_value(tokens); parsed_value && !tokens.has_next_token())
8327
0
            return parsed_value.release_nonnull();
8328
0
        return ParseError::SyntaxError;
8329
0
    case PropertyID::GridArea:
8330
0
        if (auto parsed_value = parse_grid_area_shorthand_value(tokens); parsed_value && !tokens.has_next_token())
8331
0
            return parsed_value.release_nonnull();
8332
0
        return ParseError::SyntaxError;
8333
0
    case PropertyID::GridAutoFlow:
8334
0
        if (auto parsed_value = parse_grid_auto_flow_value(tokens); parsed_value && !tokens.has_next_token())
8335
0
            return parsed_value.release_nonnull();
8336
0
        return ParseError::SyntaxError;
8337
0
    case PropertyID::GridColumn:
8338
0
        if (auto parsed_value = parse_grid_track_placement_shorthand_value(property_id, tokens); parsed_value && !tokens.has_next_token())
8339
0
            return parsed_value.release_nonnull();
8340
0
        return ParseError::SyntaxError;
8341
0
    case PropertyID::GridColumnEnd:
8342
0
        if (auto parsed_value = parse_grid_track_placement(tokens); parsed_value && !tokens.has_next_token())
8343
0
            return parsed_value.release_nonnull();
8344
0
        return ParseError::SyntaxError;
8345
0
    case PropertyID::GridColumnStart:
8346
0
        if (auto parsed_value = parse_grid_track_placement(tokens); parsed_value && !tokens.has_next_token())
8347
0
            return parsed_value.release_nonnull();
8348
0
        return ParseError::SyntaxError;
8349
0
    case PropertyID::GridRow:
8350
0
        if (auto parsed_value = parse_grid_track_placement_shorthand_value(property_id, tokens); parsed_value && !tokens.has_next_token())
8351
0
            return parsed_value.release_nonnull();
8352
0
        return ParseError::SyntaxError;
8353
0
    case PropertyID::GridRowEnd:
8354
0
        if (auto parsed_value = parse_grid_track_placement(tokens); parsed_value && !tokens.has_next_token())
8355
0
            return parsed_value.release_nonnull();
8356
0
        return ParseError::SyntaxError;
8357
0
    case PropertyID::GridRowStart:
8358
0
        if (auto parsed_value = parse_grid_track_placement(tokens); parsed_value && !tokens.has_next_token())
8359
0
            return parsed_value.release_nonnull();
8360
0
        return ParseError::SyntaxError;
8361
0
    case PropertyID::Grid:
8362
0
        if (auto parsed_value = parse_grid_shorthand_value(tokens); parsed_value && !tokens.has_next_token())
8363
0
            return parsed_value.release_nonnull();
8364
0
        return ParseError::SyntaxError;
8365
0
    case PropertyID::GridTemplate:
8366
0
        if (auto parsed_value = parse_grid_track_size_list_shorthand_value(property_id, tokens); parsed_value && !tokens.has_next_token())
8367
0
            return parsed_value.release_nonnull();
8368
0
        return ParseError::SyntaxError;
8369
0
    case PropertyID::GridTemplateAreas:
8370
0
        if (auto parsed_value = parse_grid_template_areas_value(tokens); parsed_value && !tokens.has_next_token())
8371
0
            return parsed_value.release_nonnull();
8372
0
        return ParseError::SyntaxError;
8373
0
    case PropertyID::GridTemplateColumns:
8374
0
        if (auto parsed_value = parse_grid_track_size_list(tokens); parsed_value && !tokens.has_next_token())
8375
0
            return parsed_value.release_nonnull();
8376
0
        return ParseError::SyntaxError;
8377
0
    case PropertyID::GridTemplateRows:
8378
0
        if (auto parsed_value = parse_grid_track_size_list(tokens); parsed_value && !tokens.has_next_token())
8379
0
            return parsed_value.release_nonnull();
8380
0
        return ParseError::SyntaxError;
8381
0
    case PropertyID::GridAutoColumns:
8382
0
        if (auto parsed_value = parse_grid_auto_track_sizes(tokens); parsed_value && !tokens.has_next_token())
8383
0
            return parsed_value.release_nonnull();
8384
0
        return ParseError::SyntaxError;
8385
0
    case PropertyID::GridAutoRows:
8386
0
        if (auto parsed_value = parse_grid_auto_track_sizes(tokens); parsed_value && !tokens.has_next_token())
8387
0
            return parsed_value.release_nonnull();
8388
0
        return ParseError::SyntaxError;
8389
0
    case PropertyID::ListStyle:
8390
0
        if (auto parsed_value = parse_list_style_value(tokens); parsed_value && !tokens.has_next_token())
8391
0
            return parsed_value.release_nonnull();
8392
0
        return ParseError::SyntaxError;
8393
0
    case PropertyID::MathDepth:
8394
0
        if (auto parsed_value = parse_math_depth_value(tokens); parsed_value && !tokens.has_next_token())
8395
0
            return parsed_value.release_nonnull();
8396
0
        return ParseError::SyntaxError;
8397
0
    case PropertyID::Overflow:
8398
0
        if (auto parsed_value = parse_overflow_value(tokens); parsed_value && !tokens.has_next_token())
8399
0
            return parsed_value.release_nonnull();
8400
0
        return ParseError::SyntaxError;
8401
0
    case PropertyID::PlaceContent:
8402
0
        if (auto parsed_value = parse_place_content_value(tokens); parsed_value && !tokens.has_next_token())
8403
0
            return parsed_value.release_nonnull();
8404
0
        return ParseError::SyntaxError;
8405
0
    case PropertyID::PlaceItems:
8406
0
        if (auto parsed_value = parse_place_items_value(tokens); parsed_value && !tokens.has_next_token())
8407
0
            return parsed_value.release_nonnull();
8408
0
        return ParseError::SyntaxError;
8409
0
    case PropertyID::PlaceSelf:
8410
0
        if (auto parsed_value = parse_place_self_value(tokens); parsed_value && !tokens.has_next_token())
8411
0
            return parsed_value.release_nonnull();
8412
0
        return ParseError::SyntaxError;
8413
0
    case PropertyID::Quotes:
8414
0
        if (auto parsed_value = parse_quotes_value(tokens); parsed_value && !tokens.has_next_token())
8415
0
            return parsed_value.release_nonnull();
8416
0
        return ParseError::SyntaxError;
8417
0
    case PropertyID::Rotate:
8418
0
        if (auto parsed_value = parse_rotate_value(tokens); parsed_value && !tokens.has_next_token())
8419
0
            return parsed_value.release_nonnull();
8420
0
        return ParseError::SyntaxError;
8421
0
    case PropertyID::ScrollbarGutter:
8422
0
        if (auto parsed_value = parse_scrollbar_gutter_value(tokens); parsed_value && !tokens.has_next_token())
8423
0
            return parsed_value.release_nonnull();
8424
0
        return ParseError::SyntaxError;
8425
0
    case PropertyID::TextDecoration:
8426
0
        if (auto parsed_value = parse_text_decoration_value(tokens); parsed_value && !tokens.has_next_token())
8427
0
            return parsed_value.release_nonnull();
8428
0
        return ParseError::SyntaxError;
8429
0
    case PropertyID::TextDecorationLine:
8430
0
        if (auto parsed_value = parse_text_decoration_line_value(tokens); parsed_value && !tokens.has_next_token())
8431
0
            return parsed_value.release_nonnull();
8432
0
        return ParseError::SyntaxError;
8433
0
    case PropertyID::TextShadow:
8434
0
        if (auto parsed_value = parse_shadow_value(tokens, AllowInsetKeyword::No); parsed_value && !tokens.has_next_token())
8435
0
            return parsed_value.release_nonnull();
8436
0
        return ParseError::SyntaxError;
8437
0
    case PropertyID::Transform:
8438
0
        if (auto parsed_value = parse_transform_value(tokens); parsed_value && !tokens.has_next_token())
8439
0
            return parsed_value.release_nonnull();
8440
0
        return ParseError::SyntaxError;
8441
0
    case PropertyID::TransformOrigin:
8442
0
        if (auto parsed_value = parse_transform_origin_value(tokens); parsed_value && !tokens.has_next_token())
8443
0
            return parsed_value.release_nonnull();
8444
0
        return ParseError::SyntaxError;
8445
0
    case PropertyID::Transition:
8446
0
        if (auto parsed_value = parse_transition_value(tokens); parsed_value && !tokens.has_next_token())
8447
0
            return parsed_value.release_nonnull();
8448
0
        return ParseError::SyntaxError;
8449
0
    default:
8450
0
        break;
8451
0
    }
8452
8453
    // If there's only 1 ComponentValue, we can only produce a single CSSStyleValue.
8454
0
    if (component_values.size() == 1) {
8455
0
        auto stream = TokenStream { component_values };
8456
0
        if (auto parsed_value = parse_css_value_for_property(property_id, stream))
8457
0
            return parsed_value.release_nonnull();
8458
0
    } else {
8459
0
        StyleValueVector parsed_values;
8460
0
        auto stream = TokenStream { component_values };
8461
0
        while (auto parsed_value = parse_css_value_for_property(property_id, stream)) {
8462
0
            parsed_values.append(parsed_value.release_nonnull());
8463
0
            if (!stream.has_next_token())
8464
0
                break;
8465
0
        }
8466
8467
        // Some types (such as <ratio>) can be made from multiple ComponentValues, so if we only made 1 CSSStyleValue, return it directly.
8468
0
        if (parsed_values.size() == 1)
8469
0
            return *parsed_values.take_first();
8470
8471
0
        if (!parsed_values.is_empty() && parsed_values.size() <= property_maximum_value_count(property_id))
8472
0
            return StyleValueList::create(move(parsed_values), StyleValueList::Separator::Space);
8473
0
    }
8474
8475
    // We have multiple values, but the property claims to accept only a single one, check if it's a shorthand property.
8476
0
    auto unassigned_properties = longhands_for_shorthand(property_id);
8477
0
    if (unassigned_properties.is_empty())
8478
0
        return ParseError::SyntaxError;
8479
8480
0
    auto stream = TokenStream { component_values };
8481
8482
0
    HashMap<UnderlyingType<PropertyID>, Vector<ValueComparingNonnullRefPtr<CSSStyleValue const>>> assigned_values;
8483
8484
0
    while (stream.has_next_token() && !unassigned_properties.is_empty()) {
8485
0
        auto property_and_value = parse_css_value_for_properties(unassigned_properties, stream);
8486
0
        if (property_and_value.has_value()) {
8487
0
            auto property = property_and_value->property;
8488
0
            auto value = property_and_value->style_value;
8489
0
            auto& values = assigned_values.ensure(to_underlying(property));
8490
0
            if (values.size() + 1 == property_maximum_value_count(property)) {
8491
                // We're done with this property, move on to the next one.
8492
0
                unassigned_properties.remove_first_matching([&](auto& unassigned_property) { return unassigned_property == property; });
8493
0
            }
8494
8495
0
            values.append(value.release_nonnull());
8496
0
            continue;
8497
0
        }
8498
8499
        // No property matched, so we're done.
8500
0
        dbgln("No property (from {} properties) matched {}", unassigned_properties.size(), stream.next_token().to_debug_string());
8501
0
        for (auto id : unassigned_properties)
8502
0
            dbgln("    {}", string_from_property_id(id));
8503
0
        break;
8504
0
    }
8505
8506
0
    for (auto& property : unassigned_properties)
8507
0
        assigned_values.ensure(to_underlying(property)).append(property_initial_value(m_context.realm(), property));
8508
8509
0
    stream.discard_whitespace();
8510
0
    if (stream.has_next_token())
8511
0
        return ParseError::SyntaxError;
8512
8513
0
    Vector<PropertyID> longhand_properties;
8514
0
    longhand_properties.ensure_capacity(assigned_values.size());
8515
0
    for (auto& it : assigned_values)
8516
0
        longhand_properties.unchecked_append(static_cast<PropertyID>(it.key));
8517
8518
0
    StyleValueVector longhand_values;
8519
0
    longhand_values.ensure_capacity(assigned_values.size());
8520
0
    for (auto& it : assigned_values) {
8521
0
        if (it.value.size() == 1)
8522
0
            longhand_values.unchecked_append(it.value.take_first());
8523
0
        else
8524
0
            longhand_values.unchecked_append(StyleValueList::create(move(it.value), StyleValueList::Separator::Space));
8525
0
    }
8526
8527
0
    return { ShorthandStyleValue::create(property_id, move(longhand_properties), move(longhand_values)) };
8528
0
}
8529
8530
RefPtr<CSSStyleValue> Parser::parse_css_value_for_property(PropertyID property_id, TokenStream<ComponentValue>& tokens)
8531
0
{
8532
0
    return parse_css_value_for_properties({ &property_id, 1 }, tokens)
8533
0
        .map([](auto& it) { return it.style_value; })
8534
0
        .value_or(nullptr);
8535
0
}
8536
8537
Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(ReadonlySpan<PropertyID> property_ids, TokenStream<ComponentValue>& tokens)
8538
0
{
8539
0
    auto any_property_accepts_type = [](ReadonlySpan<PropertyID> property_ids, ValueType value_type) -> Optional<PropertyID> {
8540
0
        for (auto const& property : property_ids) {
8541
0
            if (property_accepts_type(property, value_type))
8542
0
                return property;
8543
0
        }
8544
0
        return {};
8545
0
    };
8546
0
    auto any_property_accepts_type_percentage = [](ReadonlySpan<PropertyID> property_ids, ValueType value_type) -> Optional<PropertyID> {
8547
0
        for (auto const& property : property_ids) {
8548
0
            if (property_accepts_type(property, value_type) && property_accepts_type(property, ValueType::Percentage))
8549
0
                return property;
8550
0
        }
8551
0
        return {};
8552
0
    };
8553
0
    auto any_property_accepts_keyword = [](ReadonlySpan<PropertyID> property_ids, Keyword keyword) -> Optional<PropertyID> {
8554
0
        for (auto const& property : property_ids) {
8555
0
            if (property_accepts_keyword(property, keyword))
8556
0
                return property;
8557
0
        }
8558
0
        return {};
8559
0
    };
8560
8561
0
    auto& peek_token = tokens.next_token();
8562
8563
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::EasingFunction); property.has_value()) {
8564
0
        if (auto maybe_easing_function = parse_easing_value(tokens))
8565
0
            return PropertyAndValue { *property, maybe_easing_function };
8566
0
    }
8567
8568
0
    if (peek_token.is(Token::Type::Ident)) {
8569
        // NOTE: We do not try to parse "CSS-wide keywords" here. https://www.w3.org/TR/css-values-4/#common-keywords
8570
        //       These are only valid on their own, and so should be parsed directly in `parse_css_value()`.
8571
0
        auto keyword = keyword_from_string(peek_token.token().ident());
8572
0
        if (keyword.has_value()) {
8573
0
            if (auto property = any_property_accepts_keyword(property_ids, keyword.value()); property.has_value()) {
8574
0
                tokens.discard_a_token();
8575
0
                return PropertyAndValue { *property, CSSKeywordValue::create(keyword.value()) };
8576
0
            }
8577
0
        }
8578
8579
        // Custom idents
8580
0
        if (auto property = any_property_accepts_type(property_ids, ValueType::CustomIdent); property.has_value()) {
8581
0
            if (auto custom_ident = parse_custom_ident_value(tokens, {}))
8582
0
                return PropertyAndValue { *property, custom_ident };
8583
0
        }
8584
0
    }
8585
8586
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::Color); property.has_value()) {
8587
0
        if (auto maybe_color = parse_color_value(tokens))
8588
0
            return PropertyAndValue { *property, maybe_color };
8589
0
    }
8590
8591
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::Counter); property.has_value()) {
8592
0
        if (auto maybe_counter = parse_counter_value(tokens))
8593
0
            return PropertyAndValue { *property, maybe_counter };
8594
0
    }
8595
8596
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::Image); property.has_value()) {
8597
0
        if (auto maybe_image = parse_image_value(tokens))
8598
0
            return PropertyAndValue { *property, maybe_image };
8599
0
    }
8600
8601
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::Position); property.has_value()) {
8602
0
        if (auto maybe_position = parse_position_value(tokens))
8603
0
            return PropertyAndValue { *property, maybe_position };
8604
0
    }
8605
8606
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::BackgroundPosition); property.has_value()) {
8607
0
        if (auto maybe_position = parse_position_value(tokens, PositionParsingMode::BackgroundPosition))
8608
0
            return PropertyAndValue { *property, maybe_position };
8609
0
    }
8610
8611
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::BasicShape); property.has_value()) {
8612
0
        if (auto maybe_basic_shape = parse_basic_shape_value(tokens))
8613
0
            return PropertyAndValue { *property, maybe_basic_shape };
8614
0
    }
8615
8616
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::Ratio); property.has_value()) {
8617
0
        if (auto maybe_ratio = parse_ratio_value(tokens))
8618
0
            return PropertyAndValue { *property, maybe_ratio };
8619
0
    }
8620
8621
0
    auto property_accepting_integer = any_property_accepts_type(property_ids, ValueType::Integer);
8622
0
    auto property_accepting_number = any_property_accepts_type(property_ids, ValueType::Number);
8623
0
    bool property_accepts_numeric = property_accepting_integer.has_value() || property_accepting_number.has_value();
8624
8625
0
    if (peek_token.is(Token::Type::Number) && property_accepts_numeric) {
8626
0
        if (peek_token.token().number().is_integer() && property_accepting_integer.has_value()) {
8627
0
            auto integer = IntegerStyleValue::create(peek_token.token().number().integer_value());
8628
0
            if (property_accepts_integer(*property_accepting_integer, integer->as_integer().integer())) {
8629
0
                tokens.discard_a_token(); // integer
8630
0
                return PropertyAndValue { *property_accepting_integer, integer };
8631
0
            }
8632
0
        }
8633
0
        if (property_accepting_number.has_value()) {
8634
0
            auto number = NumberStyleValue::create(peek_token.token().number().value());
8635
0
            if (property_accepts_number(*property_accepting_number, number->as_number().number())) {
8636
0
                tokens.discard_a_token(); // number
8637
0
                return PropertyAndValue { *property_accepting_number, number };
8638
0
            }
8639
0
        }
8640
0
    }
8641
8642
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::OpenTypeTag); property.has_value()) {
8643
0
        if (auto maybe_rect = parse_opentype_tag_value(tokens))
8644
0
            return PropertyAndValue { *property, maybe_rect };
8645
0
    }
8646
8647
0
    if (peek_token.is(Token::Type::Percentage)) {
8648
0
        auto percentage = Percentage(peek_token.token().percentage());
8649
0
        if (auto property = any_property_accepts_type(property_ids, ValueType::Percentage); property.has_value() && property_accepts_percentage(*property, percentage)) {
8650
0
            tokens.discard_a_token();
8651
0
            return PropertyAndValue { *property, PercentageStyleValue::create(percentage) };
8652
0
        }
8653
0
    }
8654
8655
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::Rect); property.has_value()) {
8656
0
        if (auto maybe_rect = parse_rect_value(tokens))
8657
0
            return PropertyAndValue { *property, maybe_rect };
8658
0
    }
8659
8660
0
    if (peek_token.is(Token::Type::String)) {
8661
0
        if (auto property = any_property_accepts_type(property_ids, ValueType::String); property.has_value())
8662
0
            return PropertyAndValue { *property, StringStyleValue::create(tokens.consume_a_token().token().string()) };
8663
0
    }
8664
8665
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::Url); property.has_value()) {
8666
0
        if (auto url = parse_url_value(tokens))
8667
0
            return PropertyAndValue { *property, url };
8668
0
    }
8669
8670
0
    bool property_accepts_dimension = any_property_accepts_type(property_ids, ValueType::Angle).has_value()
8671
0
        || any_property_accepts_type(property_ids, ValueType::Flex).has_value()
8672
0
        || any_property_accepts_type(property_ids, ValueType::Frequency).has_value()
8673
0
        || any_property_accepts_type(property_ids, ValueType::Length).has_value()
8674
0
        || any_property_accepts_type(property_ids, ValueType::Percentage).has_value()
8675
0
        || any_property_accepts_type(property_ids, ValueType::Resolution).has_value()
8676
0
        || any_property_accepts_type(property_ids, ValueType::Time).has_value();
8677
8678
0
    if (property_accepts_dimension) {
8679
0
        if (peek_token.is(Token::Type::Number) && m_context.is_parsing_svg_presentation_attribute()) {
8680
0
            auto transaction = tokens.begin_transaction();
8681
0
            auto token = tokens.consume_a_token();
8682
            // https://svgwg.org/svg2-draft/types.html#presentation-attribute-css-value
8683
            // We need to allow <number> in any place that expects a <length> or <angle>.
8684
            // FIXME: How should these numbers be interpreted? https://github.com/w3c/svgwg/issues/792
8685
            //        For now: Convert them to px lengths, or deg angles.
8686
0
            auto angle = Angle::make_degrees(token.token().number_value());
8687
0
            if (auto property = any_property_accepts_type(property_ids, ValueType::Angle); property.has_value() && property_accepts_angle(*property, angle)) {
8688
0
                transaction.commit();
8689
0
                return PropertyAndValue { *property, AngleStyleValue::create(angle) };
8690
0
            }
8691
0
            auto length = Length::make_px(CSSPixels::nearest_value_for(token.token().number_value()));
8692
0
            if (auto property = any_property_accepts_type(property_ids, ValueType::Length); property.has_value() && property_accepts_length(*property, length)) {
8693
0
                transaction.commit();
8694
0
                return PropertyAndValue { *property, LengthStyleValue::create(length) };
8695
0
            }
8696
0
        }
8697
8698
0
        auto transaction = tokens.begin_transaction();
8699
0
        if (auto maybe_dimension = parse_dimension(peek_token); maybe_dimension.has_value()) {
8700
0
            tokens.discard_a_token();
8701
0
            auto dimension = maybe_dimension.release_value();
8702
0
            if (dimension.is_angle()) {
8703
0
                auto angle = dimension.angle();
8704
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Angle); property.has_value() && property_accepts_angle(*property, angle)) {
8705
0
                    transaction.commit();
8706
0
                    return PropertyAndValue { *property, AngleStyleValue::create(angle) };
8707
0
                }
8708
0
            }
8709
0
            if (dimension.is_flex()) {
8710
0
                auto flex = dimension.flex();
8711
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Flex); property.has_value() && property_accepts_flex(*property, flex)) {
8712
0
                    transaction.commit();
8713
0
                    return PropertyAndValue { *property, FlexStyleValue::create(flex) };
8714
0
                }
8715
0
            }
8716
0
            if (dimension.is_frequency()) {
8717
0
                auto frequency = dimension.frequency();
8718
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Frequency); property.has_value() && property_accepts_frequency(*property, frequency)) {
8719
0
                    transaction.commit();
8720
0
                    return PropertyAndValue { *property, FrequencyStyleValue::create(frequency) };
8721
0
                }
8722
0
            }
8723
0
            if (dimension.is_length()) {
8724
0
                auto length = dimension.length();
8725
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Length); property.has_value() && property_accepts_length(*property, length)) {
8726
0
                    transaction.commit();
8727
0
                    return PropertyAndValue { *property, LengthStyleValue::create(length) };
8728
0
                }
8729
0
            }
8730
0
            if (dimension.is_resolution()) {
8731
0
                auto resolution = dimension.resolution();
8732
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Resolution); property.has_value() && property_accepts_resolution(*property, resolution)) {
8733
0
                    transaction.commit();
8734
0
                    return PropertyAndValue { *property, ResolutionStyleValue::create(resolution) };
8735
0
                }
8736
0
            }
8737
0
            if (dimension.is_time()) {
8738
0
                auto time = dimension.time();
8739
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Time); property.has_value() && property_accepts_time(*property, time)) {
8740
0
                    transaction.commit();
8741
0
                    return PropertyAndValue { *property, TimeStyleValue::create(time) };
8742
0
                }
8743
0
            }
8744
0
        }
8745
0
    }
8746
8747
    // In order to not end up parsing `calc()` and other math expressions multiple times,
8748
    // we parse it once, and then see if its resolved type matches what the property accepts.
8749
0
    if (peek_token.is_function() && (property_accepts_dimension || property_accepts_numeric)) {
8750
0
        if (auto maybe_calculated = parse_calculated_value(peek_token); maybe_calculated) {
8751
0
            tokens.discard_a_token();
8752
0
            auto& calculated = *maybe_calculated;
8753
            // This is a bit sensitive to ordering: `<foo>` and `<percentage>` have to be checked before `<foo-percentage>`.
8754
            // FIXME: When parsing SVG presentation attributes, <number> is permitted wherever <length>, <length-percentage>, or <angle> are.
8755
            //        The specifics are unclear, so I'm ignoring this for calculated values for now.
8756
            //        See https://github.com/w3c/svgwg/issues/792
8757
0
            if (calculated.resolves_to_percentage()) {
8758
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Percentage); property.has_value())
8759
0
                    return PropertyAndValue { *property, calculated };
8760
0
            } else if (calculated.resolves_to_angle()) {
8761
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Angle); property.has_value())
8762
0
                    return PropertyAndValue { *property, calculated };
8763
0
            } else if (calculated.resolves_to_angle_percentage()) {
8764
0
                if (auto property = any_property_accepts_type_percentage(property_ids, ValueType::Angle); property.has_value())
8765
0
                    return PropertyAndValue { *property, calculated };
8766
0
            } else if (calculated.resolves_to_flex()) {
8767
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Flex); property.has_value())
8768
0
                    return PropertyAndValue { *property, calculated };
8769
0
            } else if (calculated.resolves_to_frequency()) {
8770
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Frequency); property.has_value())
8771
0
                    return PropertyAndValue { *property, calculated };
8772
0
            } else if (calculated.resolves_to_frequency_percentage()) {
8773
0
                if (auto property = any_property_accepts_type_percentage(property_ids, ValueType::Frequency); property.has_value())
8774
0
                    return PropertyAndValue { *property, calculated };
8775
0
            } else if (calculated.resolves_to_number()) {
8776
0
                if (property_accepts_numeric) {
8777
0
                    auto property_or_resolved = property_accepting_integer.value_or_lazy_evaluated([property_accepting_number]() { return property_accepting_number.value(); });
8778
0
                    return PropertyAndValue { property_or_resolved, calculated };
8779
0
                }
8780
0
            } else if (calculated.resolves_to_number_percentage()) {
8781
0
                if (auto property = any_property_accepts_type_percentage(property_ids, ValueType::Number); property.has_value())
8782
0
                    return PropertyAndValue { *property, calculated };
8783
0
            } else if (calculated.resolves_to_length()) {
8784
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Length); property.has_value())
8785
0
                    return PropertyAndValue { *property, calculated };
8786
0
            } else if (calculated.resolves_to_length_percentage()) {
8787
0
                if (auto property = any_property_accepts_type_percentage(property_ids, ValueType::Length); property.has_value())
8788
0
                    return PropertyAndValue { *property, calculated };
8789
0
            } else if (calculated.resolves_to_time()) {
8790
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Time); property.has_value())
8791
0
                    return PropertyAndValue { *property, calculated };
8792
0
            } else if (calculated.resolves_to_time_percentage()) {
8793
0
                if (auto property = any_property_accepts_type_percentage(property_ids, ValueType::Time); property.has_value())
8794
0
                    return PropertyAndValue { *property, calculated };
8795
0
            }
8796
0
        }
8797
0
    }
8798
8799
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::Paint); property.has_value()) {
8800
0
        if (auto value = parse_paint_value(tokens))
8801
0
            return PropertyAndValue { *property, value.release_nonnull() };
8802
0
    }
8803
8804
0
    return OptionalNone {};
8805
0
}
8806
8807
class UnparsedCalculationNode final : public CalculationNode {
8808
public:
8809
    static NonnullOwnPtr<UnparsedCalculationNode> create(ComponentValue component_value)
8810
0
    {
8811
0
        return adopt_own(*new (nothrow) UnparsedCalculationNode(move(component_value)));
8812
0
    }
8813
0
    virtual ~UnparsedCalculationNode() = default;
8814
8815
0
    ComponentValue& component_value() { return m_component_value; }
8816
8817
0
    virtual String to_string() const override { VERIFY_NOT_REACHED(); }
8818
0
    virtual Optional<CSSMathValue::ResolvedType> resolved_type() const override { VERIFY_NOT_REACHED(); }
8819
0
    virtual Optional<CSSNumericType> determine_type(Web::CSS::PropertyID) const override { VERIFY_NOT_REACHED(); }
8820
0
    virtual bool contains_percentage() const override { VERIFY_NOT_REACHED(); }
8821
0
    virtual CSSMathValue::CalculationResult resolve(Optional<Length::ResolutionContext const&>, CSSMathValue::PercentageBasis const&) const override { VERIFY_NOT_REACHED(); }
8822
0
    virtual void for_each_child_node(AK::Function<void(NonnullOwnPtr<CalculationNode>&)> const&) override { }
8823
8824
    virtual void dump(StringBuilder& builder, int indent) const override
8825
0
    {
8826
0
        builder.appendff("{: >{}}UNPARSED({})\n", "", indent, m_component_value.to_debug_string());
8827
0
    }
8828
0
    virtual bool equals(CalculationNode const&) const override { return false; }
8829
8830
private:
8831
    UnparsedCalculationNode(ComponentValue component_value)
8832
0
        : CalculationNode(Type::Unparsed)
8833
0
        , m_component_value(move(component_value))
8834
0
    {
8835
0
    }
8836
8837
    ComponentValue m_component_value;
8838
};
8839
8840
// https://html.spec.whatwg.org/multipage/images.html#parsing-a-sizes-attribute
8841
LengthOrCalculated Parser::Parser::parse_as_sizes_attribute()
8842
0
{
8843
    // 1. Let unparsed sizes list be the result of parsing a comma-separated list of component values
8844
    //    from the value of element's sizes attribute (or the empty string, if the attribute is absent).
8845
0
    auto unparsed_sizes_list = parse_a_comma_separated_list_of_component_values(m_token_stream);
8846
8847
    // 2. Let size be null.
8848
0
    Optional<LengthOrCalculated> size;
8849
8850
    // 3. For each unparsed size in unparsed sizes list:
8851
0
    for (auto& unparsed_size : unparsed_sizes_list) {
8852
        // 1. Remove all consecutive <whitespace-token>s from the end of unparsed size.
8853
        //    If unparsed size is now empty, that is a parse error; continue.
8854
0
        while (!unparsed_size.is_empty() && unparsed_size.last().is_token() && unparsed_size.last().token().is(Token::Type::Whitespace))
8855
0
            unparsed_size.take_last();
8856
0
        if (unparsed_size.is_empty()) {
8857
0
            log_parse_error();
8858
0
            continue;
8859
0
        }
8860
8861
        // 2. If the last component value in unparsed size is a valid non-negative <source-size-value>,
8862
        //    let size be its value and remove the component value from unparsed size.
8863
        //    FIXME: Any CSS function other than the math functions is invalid.
8864
        //    Otherwise, there is a parse error; continue.
8865
0
        auto last_value_stream = TokenStream<ComponentValue>::of_single_token(unparsed_size.last());
8866
0
        if (auto source_size_value = parse_source_size_value(last_value_stream); source_size_value.has_value()) {
8867
0
            size = source_size_value.value();
8868
0
            unparsed_size.take_last();
8869
0
        } else {
8870
0
            log_parse_error();
8871
0
            continue;
8872
0
        }
8873
8874
        // 3. Remove all consecutive <whitespace-token>s from the end of unparsed size.
8875
0
        while (!unparsed_size.is_empty() && unparsed_size.last().is_token() && unparsed_size.last().token().is(Token::Type::Whitespace))
8876
0
            unparsed_size.take_last();
8877
8878
        // If unparsed size is now empty, then return size.
8879
0
        if (unparsed_size.is_empty())
8880
0
            return size.value();
8881
8882
        // FIXME: If this was not the keyword auto and it was not the last item in unparsed sizes list, that is a parse error.
8883
8884
        // 4. Parse the remaining component values in unparsed size as a <media-condition>.
8885
        //    If it does not parse correctly, or it does parse correctly but the <media-condition> evaluates to false, continue.
8886
0
        TokenStream<ComponentValue> token_stream { unparsed_size };
8887
0
        auto media_condition = parse_media_condition(token_stream, MediaCondition::AllowOr::Yes);
8888
0
        auto context_window = m_context.window();
8889
0
        if (context_window && media_condition && media_condition->evaluate(*context_window) == MatchResult::True) {
8890
0
            return size.value();
8891
0
        }
8892
8893
        // 5. If size is not auto, then return size.
8894
0
        if (size.value().is_calculated() || !size.value().value().is_auto())
8895
0
            return size.value();
8896
0
    }
8897
8898
0
    return Length(100, Length::Type::Vw);
8899
0
}
8900
8901
// https://www.w3.org/TR/css-values-4/#parse-a-calculation
8902
OwnPtr<CalculationNode> Parser::parse_a_calculation(Vector<ComponentValue> const& original_values)
8903
0
{
8904
    // 1. Discard any <whitespace-token>s from values.
8905
    // 2. An item in values is an “operator” if it’s a <delim-token> with the value "+", "-", "*", or "/". Otherwise, it’s a “value”.
8906
0
    struct Operator {
8907
0
        char delim;
8908
0
    };
8909
0
    using Value = Variant<NonnullOwnPtr<CalculationNode>, Operator>;
8910
0
    Vector<Value> values;
8911
0
    for (auto& value : original_values) {
8912
0
        if (value.is(Token::Type::Whitespace))
8913
0
            continue;
8914
0
        if (value.is(Token::Type::Delim)) {
8915
0
            if (first_is_one_of(value.token().delim(), static_cast<u32>('+'), static_cast<u32>('-'), static_cast<u32>('*'), static_cast<u32>('/'))) {
8916
                // NOTE: Sequential operators are invalid syntax.
8917
0
                if (!values.is_empty() && values.last().has<Operator>())
8918
0
                    return nullptr;
8919
8920
0
                values.append(Operator { static_cast<char>(value.token().delim()) });
8921
0
                continue;
8922
0
            }
8923
0
        }
8924
8925
0
        if (value.is(Token::Type::Ident)) {
8926
0
            auto maybe_constant = CalculationNode::constant_type_from_string(value.token().ident());
8927
0
            if (maybe_constant.has_value()) {
8928
0
                values.append({ ConstantCalculationNode::create(maybe_constant.value()) });
8929
0
                continue;
8930
0
            }
8931
0
        }
8932
8933
0
        if (value.is(Token::Type::Number)) {
8934
0
            values.append({ NumericCalculationNode::create(value.token().number()) });
8935
0
            continue;
8936
0
        }
8937
8938
0
        if (auto dimension = parse_dimension(value); dimension.has_value()) {
8939
0
            if (dimension->is_angle())
8940
0
                values.append({ NumericCalculationNode::create(dimension->angle()) });
8941
0
            else if (dimension->is_frequency())
8942
0
                values.append({ NumericCalculationNode::create(dimension->frequency()) });
8943
0
            else if (dimension->is_length())
8944
0
                values.append({ NumericCalculationNode::create(dimension->length()) });
8945
0
            else if (dimension->is_percentage())
8946
0
                values.append({ NumericCalculationNode::create(dimension->percentage()) });
8947
0
            else if (dimension->is_resolution())
8948
0
                values.append({ NumericCalculationNode::create(dimension->resolution()) });
8949
0
            else if (dimension->is_time())
8950
0
                values.append({ NumericCalculationNode::create(dimension->time()) });
8951
0
            else if (dimension->is_flex()) {
8952
                // https://www.w3.org/TR/css3-grid-layout/#fr-unit
8953
                // NOTE: <flex> values are not <length>s (nor are they compatible with <length>s, like some <percentage> values),
8954
                //       so they cannot be represented in or combined with other unit types in calc() expressions.
8955
0
                return nullptr;
8956
0
            } else {
8957
0
                VERIFY_NOT_REACHED();
8958
0
            }
8959
0
            continue;
8960
0
        }
8961
8962
0
        values.append({ UnparsedCalculationNode::create(value) });
8963
0
    }
8964
8965
    // If we have no values, the syntax is invalid.
8966
0
    if (values.is_empty())
8967
0
        return nullptr;
8968
8969
    // NOTE: If the first or last value is an operator, the syntax is invalid.
8970
0
    if (values.first().has<Operator>() || values.last().has<Operator>())
8971
0
        return nullptr;
8972
8973
    // 3. Collect children into Product and Invert nodes.
8974
    //    For every consecutive run of value items in values separated by "*" or "/" operators:
8975
0
    while (true) {
8976
0
        Optional<size_t> first_product_operator = values.find_first_index_if([](auto const& item) {
8977
0
            return item.template has<Operator>()
8978
0
                && first_is_one_of(item.template get<Operator>().delim, '*', '/');
8979
0
        });
8980
8981
0
        if (!first_product_operator.has_value())
8982
0
            break;
8983
8984
0
        auto start_of_run = first_product_operator.value() - 1;
8985
0
        auto end_of_run = first_product_operator.value() + 1;
8986
0
        for (auto i = start_of_run + 1; i < values.size(); i += 2) {
8987
0
            auto& item = values[i];
8988
0
            if (!item.has<Operator>()) {
8989
0
                end_of_run = i - 1;
8990
0
                break;
8991
0
            }
8992
8993
0
            auto delim = item.get<Operator>().delim;
8994
0
            if (!first_is_one_of(delim, '*', '/')) {
8995
0
                end_of_run = i - 1;
8996
0
                break;
8997
0
            }
8998
0
        }
8999
9000
        // 1. For each "/" operator in the run, replace its right-hand value item rhs with an Invert node containing rhs as its child.
9001
0
        Vector<NonnullOwnPtr<CalculationNode>> run_values;
9002
0
        run_values.append(move(values[start_of_run].get<NonnullOwnPtr<CalculationNode>>()));
9003
0
        for (auto i = start_of_run + 1; i <= end_of_run; i += 2) {
9004
0
            auto& operator_ = values[i].get<Operator>().delim;
9005
0
            auto& rhs = values[i + 1];
9006
0
            if (operator_ == '/') {
9007
0
                run_values.append(InvertCalculationNode::create(move(rhs.get<NonnullOwnPtr<CalculationNode>>())));
9008
0
                continue;
9009
0
            }
9010
0
            VERIFY(operator_ == '*');
9011
0
            run_values.append(move(rhs.get<NonnullOwnPtr<CalculationNode>>()));
9012
0
        }
9013
        // 2. Replace the entire run with a Product node containing the value items of the run as its children.
9014
0
        auto product_node = ProductCalculationNode::create(move(run_values));
9015
0
        values.remove(start_of_run, end_of_run - start_of_run + 1);
9016
0
        values.insert(start_of_run, { move(product_node) });
9017
0
    }
9018
9019
    // 4. Collect children into Sum and Negate nodes.
9020
0
    Optional<NonnullOwnPtr<CalculationNode>> single_value;
9021
0
    {
9022
        // 1. For each "-" operator item in values, replace its right-hand value item rhs with a Negate node containing rhs as its child.
9023
0
        for (auto i = 0u; i < values.size(); ++i) {
9024
0
            auto& maybe_minus_operator = values[i];
9025
0
            if (!maybe_minus_operator.has<Operator>() || maybe_minus_operator.get<Operator>().delim != '-')
9026
0
                continue;
9027
9028
0
            auto rhs_index = ++i;
9029
0
            auto& rhs = values[rhs_index];
9030
9031
0
            NonnullOwnPtr<CalculationNode> negate_node = NegateCalculationNode::create(move(rhs.get<NonnullOwnPtr<CalculationNode>>()));
9032
0
            values.remove(rhs_index);
9033
0
            values.insert(rhs_index, move(negate_node));
9034
0
        }
9035
9036
        // 2. If values has only one item, and it is a Product node or a parenthesized simple block, replace values with that item.
9037
0
        if (values.size() == 1) {
9038
0
            values.first().visit(
9039
0
                [&](ComponentValue& component_value) {
9040
0
                    if (component_value.is_block() && component_value.block().is_paren())
9041
0
                        single_value = UnparsedCalculationNode::create(component_value);
9042
0
                },
9043
0
                [&](NonnullOwnPtr<CalculationNode>& node) {
9044
0
                    if (node->type() == CalculationNode::Type::Product)
9045
0
                        single_value = move(node);
9046
0
                },
9047
0
                [](auto&) {});
9048
0
        }
9049
        //    Otherwise, replace values with a Sum node containing the value items of values as its children.
9050
0
        if (!single_value.has_value()) {
9051
0
            values.remove_all_matching([](Value& value) { return value.has<Operator>(); });
9052
0
            Vector<NonnullOwnPtr<CalculationNode>> value_items;
9053
0
            value_items.ensure_capacity(values.size());
9054
0
            for (auto& value : values) {
9055
0
                if (value.has<Operator>())
9056
0
                    continue;
9057
0
                value_items.unchecked_append(move(value.get<NonnullOwnPtr<CalculationNode>>()));
9058
0
            }
9059
0
            single_value = SumCalculationNode::create(move(value_items));
9060
0
        }
9061
0
    }
9062
9063
    // 5. At this point values is a tree of Sum, Product, Negate, and Invert nodes, with other types of values at the leaf nodes. Process the leaf nodes.
9064
    //     For every leaf node leaf in values:
9065
0
    bool parsing_failed_for_child_node = false;
9066
0
    single_value.value()->for_each_child_node([&](NonnullOwnPtr<CalculationNode>& node) {
9067
0
        if (node->type() != CalculationNode::Type::Unparsed)
9068
0
            return;
9069
9070
0
        auto& unparsed_node = static_cast<UnparsedCalculationNode&>(*node);
9071
0
        auto& component_value = unparsed_node.component_value();
9072
9073
        // 1. If leaf is a parenthesized simple block, replace leaf with the result of parsing a calculation from leaf’s contents.
9074
0
        if (component_value.is_block() && component_value.block().is_paren()) {
9075
0
            auto leaf_calculation = parse_a_calculation(component_value.block().value);
9076
0
            if (!leaf_calculation) {
9077
0
                parsing_failed_for_child_node = true;
9078
0
                return;
9079
0
            }
9080
0
            node = leaf_calculation.release_nonnull();
9081
0
            return;
9082
0
        }
9083
9084
        // 2. If leaf is a math function, replace leaf with the internal representation of that math function.
9085
        // NOTE: All function tokens at this point should be math functions.
9086
0
        else if (component_value.is_function()) {
9087
0
            auto& function = component_value.function();
9088
0
            auto leaf_calculation = parse_a_calc_function_node(function);
9089
0
            if (!leaf_calculation) {
9090
0
                parsing_failed_for_child_node = true;
9091
0
                return;
9092
0
            }
9093
9094
0
            node = leaf_calculation.release_nonnull();
9095
0
            return;
9096
0
        }
9097
9098
        // NOTE: If we get here, then we have an UnparsedCalculationNode that didn't get replaced with something else.
9099
        //       So, the calc() is invalid.
9100
0
        dbgln_if(CSS_PARSER_DEBUG, "Leftover UnparsedCalculationNode in calc tree! That probably means the syntax is invalid, but maybe we just didn't implement `{}` yet.", component_value.to_debug_string());
9101
0
        parsing_failed_for_child_node = true;
9102
0
        return;
9103
0
    });
9104
9105
0
    if (parsing_failed_for_child_node)
9106
0
        return nullptr;
9107
9108
    // FIXME: 6. Return the result of simplifying a calculation tree from values.
9109
0
    return single_value.release_value();
9110
0
}
9111
9112
bool Parser::has_ignored_vendor_prefix(StringView string)
9113
0
{
9114
0
    if (!string.starts_with('-'))
9115
0
        return false;
9116
0
    if (string.starts_with("--"sv))
9117
0
        return false;
9118
0
    if (string.starts_with("-libweb-"sv))
9119
0
        return false;
9120
0
    return true;
9121
0
}
9122
9123
NonnullRefPtr<CSSStyleValue> Parser::resolve_unresolved_style_value(ParsingContext const& context, DOM::Element& element, Optional<Selector::PseudoElement::Type> pseudo_element, PropertyID property_id, UnresolvedStyleValue const& unresolved)
9124
0
{
9125
    // Unresolved always contains a var() or attr(), unless it is a custom property's value, in which case we shouldn't be trying
9126
    // to produce a different CSSStyleValue from it.
9127
0
    VERIFY(unresolved.contains_var_or_attr());
9128
9129
    // If the value is invalid, we fall back to `unset`: https://www.w3.org/TR/css-variables-1/#invalid-at-computed-value-time
9130
9131
0
    auto parser = Parser::create(context, ""sv);
9132
0
    return parser.resolve_unresolved_style_value(element, pseudo_element, property_id, unresolved);
9133
0
}
9134
9135
class PropertyDependencyNode : public RefCounted<PropertyDependencyNode> {
9136
public:
9137
    static NonnullRefPtr<PropertyDependencyNode> create(FlyString name)
9138
0
    {
9139
0
        return adopt_ref(*new PropertyDependencyNode(move(name)));
9140
0
    }
9141
9142
    void add_child(NonnullRefPtr<PropertyDependencyNode> new_child)
9143
0
    {
9144
0
        for (auto const& child : m_children) {
9145
0
            if (child->m_name == new_child->m_name)
9146
0
                return;
9147
0
        }
9148
9149
        // We detect self-reference already.
9150
0
        VERIFY(new_child->m_name != m_name);
9151
0
        m_children.append(move(new_child));
9152
0
    }
9153
9154
    bool has_cycles()
9155
0
    {
9156
0
        if (m_marked)
9157
0
            return true;
9158
9159
0
        TemporaryChange change { m_marked, true };
9160
0
        for (auto& child : m_children) {
9161
0
            if (child->has_cycles())
9162
0
                return true;
9163
0
        }
9164
0
        return false;
9165
0
    }
9166
9167
private:
9168
    explicit PropertyDependencyNode(FlyString name)
9169
0
        : m_name(move(name))
9170
0
    {
9171
0
    }
9172
9173
    FlyString m_name;
9174
    Vector<NonnullRefPtr<PropertyDependencyNode>> m_children;
9175
    bool m_marked { false };
9176
};
9177
9178
NonnullRefPtr<CSSStyleValue> Parser::resolve_unresolved_style_value(DOM::Element& element, Optional<Selector::PseudoElement::Type> pseudo_element, PropertyID property_id, UnresolvedStyleValue const& unresolved)
9179
0
{
9180
0
    TokenStream unresolved_values_without_variables_expanded { unresolved.values() };
9181
0
    Vector<ComponentValue> values_with_variables_expanded;
9182
9183
0
    HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>> dependencies;
9184
0
    if (!expand_variables(element, pseudo_element, string_from_property_id(property_id), dependencies, unresolved_values_without_variables_expanded, values_with_variables_expanded))
9185
0
        return CSSKeywordValue::create(Keyword::Unset);
9186
9187
0
    TokenStream unresolved_values_with_variables_expanded { values_with_variables_expanded };
9188
0
    Vector<ComponentValue> expanded_values;
9189
0
    if (!expand_unresolved_values(element, string_from_property_id(property_id), unresolved_values_with_variables_expanded, expanded_values))
9190
0
        return CSSKeywordValue::create(Keyword::Unset);
9191
9192
0
    auto expanded_value_tokens = TokenStream { expanded_values };
9193
0
    if (auto parsed_value = parse_css_value(property_id, expanded_value_tokens); !parsed_value.is_error())
9194
0
        return parsed_value.release_value();
9195
9196
0
    return CSSKeywordValue::create(Keyword::Unset);
9197
0
}
9198
9199
static RefPtr<CSSStyleValue const> get_custom_property(DOM::Element const& element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, FlyString const& custom_property_name)
9200
0
{
9201
0
    if (pseudo_element.has_value()) {
9202
0
        if (auto it = element.custom_properties(pseudo_element).find(custom_property_name); it != element.custom_properties(pseudo_element).end())
9203
0
            return it->value.value;
9204
0
    }
9205
9206
0
    for (auto const* current_element = &element; current_element; current_element = current_element->parent_or_shadow_host_element()) {
9207
0
        if (auto it = current_element->custom_properties({}).find(custom_property_name); it != current_element->custom_properties({}).end())
9208
0
            return it->value.value;
9209
0
    }
9210
0
    return nullptr;
9211
0
}
9212
9213
bool Parser::expand_variables(DOM::Element& element, Optional<Selector::PseudoElement::Type> pseudo_element, FlyString const& property_name, HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>>& dependencies, TokenStream<ComponentValue>& source, Vector<ComponentValue>& dest)
9214
0
{
9215
    // Arbitrary large value chosen to avoid the billion-laughs attack.
9216
    // https://www.w3.org/TR/css-variables-1/#long-variables
9217
0
    size_t const MAX_VALUE_COUNT = 16384;
9218
0
    if (source.remaining_token_count() + dest.size() > MAX_VALUE_COUNT) {
9219
0
        dbgln("Stopped expanding CSS variables: maximum length reached.");
9220
0
        return false;
9221
0
    }
9222
9223
0
    auto get_dependency_node = [&](FlyString const& name) -> NonnullRefPtr<PropertyDependencyNode> {
9224
0
        if (auto existing = dependencies.get(name); existing.has_value())
9225
0
            return *existing.value();
9226
0
        auto new_node = PropertyDependencyNode::create(name);
9227
0
        dependencies.set(name, new_node);
9228
0
        return new_node;
9229
0
    };
9230
9231
0
    while (source.has_next_token()) {
9232
0
        auto const& value = source.consume_a_token();
9233
0
        if (value.is_block()) {
9234
0
            auto const& source_block = value.block();
9235
0
            Vector<ComponentValue> block_values;
9236
0
            TokenStream source_block_contents { source_block.value };
9237
0
            if (!expand_variables(element, pseudo_element, property_name, dependencies, source_block_contents, block_values))
9238
0
                return false;
9239
0
            dest.empend(SimpleBlock { source_block.token, move(block_values) });
9240
0
            continue;
9241
0
        }
9242
0
        if (!value.is_function()) {
9243
0
            dest.empend(value);
9244
0
            continue;
9245
0
        }
9246
0
        if (!value.function().name.equals_ignoring_ascii_case("var"sv)) {
9247
0
            auto const& source_function = value.function();
9248
0
            Vector<ComponentValue> function_values;
9249
0
            TokenStream source_function_contents { source_function.value };
9250
0
            if (!expand_variables(element, pseudo_element, property_name, dependencies, source_function_contents, function_values))
9251
0
                return false;
9252
0
            dest.empend(Function { source_function.name, move(function_values) });
9253
0
            continue;
9254
0
        }
9255
9256
0
        TokenStream var_contents { value.function().value };
9257
0
        var_contents.discard_whitespace();
9258
0
        if (!var_contents.has_next_token())
9259
0
            return false;
9260
9261
0
        auto const& custom_property_name_token = var_contents.consume_a_token();
9262
0
        if (!custom_property_name_token.is(Token::Type::Ident))
9263
0
            return false;
9264
0
        auto custom_property_name = custom_property_name_token.token().ident();
9265
0
        if (!custom_property_name.bytes_as_string_view().starts_with("--"sv))
9266
0
            return false;
9267
9268
        // Detect dependency cycles. https://www.w3.org/TR/css-variables-1/#cycles
9269
        // We do not do this by the spec, since we are not keeping a graph of var dependencies around,
9270
        // but rebuilding it every time.
9271
0
        if (custom_property_name == property_name)
9272
0
            return false;
9273
0
        auto parent = get_dependency_node(property_name);
9274
0
        auto child = get_dependency_node(custom_property_name);
9275
0
        parent->add_child(child);
9276
0
        if (parent->has_cycles())
9277
0
            return false;
9278
9279
0
        if (auto custom_property_value = get_custom_property(element, pseudo_element, custom_property_name)) {
9280
0
            VERIFY(custom_property_value->is_unresolved());
9281
0
            TokenStream custom_property_tokens { custom_property_value->as_unresolved().values() };
9282
0
            if (!expand_variables(element, pseudo_element, custom_property_name, dependencies, custom_property_tokens, dest))
9283
0
                return false;
9284
0
            continue;
9285
0
        }
9286
9287
        // Use the provided fallback value, if any.
9288
0
        var_contents.discard_whitespace();
9289
0
        if (var_contents.has_next_token()) {
9290
0
            auto const& comma_token = var_contents.consume_a_token();
9291
0
            if (!comma_token.is(Token::Type::Comma))
9292
0
                return false;
9293
0
            var_contents.discard_whitespace();
9294
0
            if (!expand_variables(element, pseudo_element, property_name, dependencies, var_contents, dest))
9295
0
                return false;
9296
0
        }
9297
0
    }
9298
0
    return true;
9299
0
}
9300
9301
bool Parser::expand_unresolved_values(DOM::Element& element, FlyString const& property_name, TokenStream<ComponentValue>& source, Vector<ComponentValue>& dest)
9302
0
{
9303
0
    auto property = property_id_from_string(property_name);
9304
9305
0
    while (source.has_next_token()) {
9306
0
        auto const& value = source.consume_a_token();
9307
0
        if (value.is_function()) {
9308
0
            if (value.function().name.equals_ignoring_ascii_case("attr"sv)) {
9309
0
                if (!substitute_attr_function(element, property_name, value.function(), dest))
9310
0
                    return false;
9311
0
                continue;
9312
0
            }
9313
9314
0
            if (property.has_value()) {
9315
0
                if (auto maybe_calc_value = parse_calculated_value(value); maybe_calc_value && maybe_calc_value->is_math()) {
9316
                    // FIXME: Run the actual simplification algorithm
9317
0
                    auto& calc_value = maybe_calc_value->as_math();
9318
0
                    if (property_accepts_type(*property, ValueType::Angle) && calc_value.resolves_to_angle()) {
9319
0
                        auto resolved_value = calc_value.resolve_angle();
9320
0
                        dest.empend(Token::create_dimension(resolved_value->to_degrees(), "deg"_fly_string));
9321
0
                        continue;
9322
0
                    }
9323
0
                    if (property_accepts_type(*property, ValueType::Frequency) && calc_value.resolves_to_frequency()) {
9324
0
                        auto resolved_value = calc_value.resolve_frequency();
9325
0
                        dest.empend(Token::create_dimension(resolved_value->to_hertz(), "hz"_fly_string));
9326
0
                        continue;
9327
0
                    }
9328
0
                    if (property_accepts_type(*property, ValueType::Length) && calc_value.resolves_to_length()) {
9329
                        // FIXME: In order to resolve lengths, we need to know the font metrics in case a font-relative unit
9330
                        //  is used. So... we can't do that until style is computed?
9331
                        //  This might be easier once we have calc-simplification implemented.
9332
0
                    }
9333
0
                    if (property_accepts_type(*property, ValueType::Percentage) && calc_value.resolves_to_percentage()) {
9334
0
                        auto resolved_value = calc_value.resolve_percentage();
9335
0
                        dest.empend(Token::create_percentage(resolved_value.value().value()));
9336
0
                        continue;
9337
0
                    }
9338
0
                    if (property_accepts_type(*property, ValueType::Time) && calc_value.resolves_to_time()) {
9339
0
                        auto resolved_value = calc_value.resolve_time();
9340
0
                        dest.empend(Token::create_dimension(resolved_value->to_seconds(), "s"_fly_string));
9341
0
                        continue;
9342
0
                    }
9343
0
                    if (property_accepts_type(*property, ValueType::Number) && calc_value.resolves_to_number()) {
9344
0
                        auto resolved_value = calc_value.resolve_number();
9345
0
                        dest.empend(Token::create_number(resolved_value.value(), Number::Type::Number));
9346
0
                        continue;
9347
0
                    }
9348
0
                    if (property_accepts_type(*property, ValueType::Integer) && calc_value.resolves_to_number()) {
9349
0
                        auto resolved_value = calc_value.resolve_integer();
9350
0
                        dest.empend(Token::create_number(resolved_value.value(), Number::Type::Integer));
9351
0
                        continue;
9352
0
                    }
9353
0
                }
9354
0
            }
9355
9356
0
            auto const& source_function = value.function();
9357
0
            Vector<ComponentValue> function_values;
9358
0
            TokenStream source_function_contents { source_function.value };
9359
0
            if (!expand_unresolved_values(element, property_name, source_function_contents, function_values))
9360
0
                return false;
9361
0
            dest.empend(Function { source_function.name, move(function_values) });
9362
0
            continue;
9363
0
        }
9364
0
        if (value.is_block()) {
9365
0
            auto const& source_block = value.block();
9366
0
            TokenStream source_block_values { source_block.value };
9367
0
            Vector<ComponentValue> block_values;
9368
0
            if (!expand_unresolved_values(element, property_name, source_block_values, block_values))
9369
0
                return false;
9370
0
            dest.empend(SimpleBlock { source_block.token, move(block_values) });
9371
0
            continue;
9372
0
        }
9373
0
        dest.empend(value.token());
9374
0
    }
9375
9376
0
    return true;
9377
0
}
9378
9379
// https://drafts.csswg.org/css-values-5/#attr-substitution
9380
bool Parser::substitute_attr_function(DOM::Element& element, FlyString const& property_name, Function const& attr_function, Vector<ComponentValue>& dest)
9381
0
{
9382
    // First, parse the arguments to attr():
9383
    // attr() = attr( <q-name> <attr-type>? , <declaration-value>?)
9384
    // <attr-type> = string | url | ident | color | number | percentage | length | angle | time | frequency | flex | <dimension-unit>
9385
0
    TokenStream attr_contents { attr_function.value };
9386
0
    attr_contents.discard_whitespace();
9387
0
    if (!attr_contents.has_next_token())
9388
0
        return false;
9389
9390
    // - Attribute name
9391
    // FIXME: Support optional attribute namespace
9392
0
    if (!attr_contents.next_token().is(Token::Type::Ident))
9393
0
        return false;
9394
0
    auto attribute_name = attr_contents.consume_a_token().token().ident();
9395
0
    attr_contents.discard_whitespace();
9396
9397
    // - Attribute type (optional)
9398
0
    auto attribute_type = "string"_fly_string;
9399
0
    if (attr_contents.next_token().is(Token::Type::Ident)) {
9400
0
        attribute_type = attr_contents.consume_a_token().token().ident();
9401
0
        attr_contents.discard_whitespace();
9402
0
    }
9403
9404
    // - Comma, then fallback values (optional)
9405
0
    bool has_fallback_values = false;
9406
0
    if (attr_contents.has_next_token()) {
9407
0
        if (!attr_contents.next_token().is(Token::Type::Comma))
9408
0
            return false;
9409
0
        (void)attr_contents.consume_a_token(); // Comma
9410
0
        has_fallback_values = true;
9411
0
    }
9412
9413
    // Then, run the substitution algorithm:
9414
9415
    // 1. If the attr() function has a substitution value, replace the attr() function by the substitution value.
9416
    // https://drafts.csswg.org/css-values-5/#attr-types
9417
0
    if (element.has_attribute(attribute_name)) {
9418
0
        auto attribute_value = element.get_attribute_value(attribute_name);
9419
0
        if (attribute_type.equals_ignoring_ascii_case("angle"_fly_string)) {
9420
            // Parse a component value from the attribute’s value.
9421
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9422
            // If the result is a <dimension-token> whose unit matches the given type, the result is the substitution value.
9423
            // Otherwise, there is no substitution value.
9424
0
            if (component_value.has_value() && component_value->is(Token::Type::Dimension)) {
9425
0
                if (Angle::unit_from_name(component_value->token().dimension_unit()).has_value()) {
9426
0
                    dest.append(component_value.release_value());
9427
0
                    return true;
9428
0
                }
9429
0
            }
9430
0
        } else if (attribute_type.equals_ignoring_ascii_case("color"_fly_string)) {
9431
            // Parse a component value from the attribute’s value.
9432
            // If the result is a <hex-color> or a named color ident, the substitution value is that result as a <color>.
9433
            // Otherwise there is no substitution value.
9434
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9435
0
            if (component_value.has_value()) {
9436
0
                if ((component_value->is(Token::Type::Hash)
9437
0
                        && Color::from_string(MUST(String::formatted("#{}", component_value->token().hash_value()))).has_value())
9438
0
                    || (component_value->is(Token::Type::Ident)
9439
0
                        && Color::from_string(component_value->token().ident()).has_value())) {
9440
0
                    dest.append(component_value.release_value());
9441
0
                    return true;
9442
0
                }
9443
0
            }
9444
0
        } else if (attribute_type.equals_ignoring_ascii_case("flex"_fly_string)) {
9445
            // Parse a component value from the attribute’s value.
9446
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9447
            // If the result is a <dimension-token> whose unit matches the given type, the result is the substitution value.
9448
            // Otherwise, there is no substitution value.
9449
0
            if (component_value.has_value() && component_value->is(Token::Type::Dimension)) {
9450
0
                if (Flex::unit_from_name(component_value->token().dimension_unit()).has_value()) {
9451
0
                    dest.append(component_value.release_value());
9452
0
                    return true;
9453
0
                }
9454
0
            }
9455
0
        } else if (attribute_type.equals_ignoring_ascii_case("frequency"_fly_string)) {
9456
            // Parse a component value from the attribute’s value.
9457
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9458
            // If the result is a <dimension-token> whose unit matches the given type, the result is the substitution value.
9459
            // Otherwise, there is no substitution value.
9460
0
            if (component_value.has_value() && component_value->is(Token::Type::Dimension)) {
9461
0
                if (Frequency::unit_from_name(component_value->token().dimension_unit()).has_value()) {
9462
0
                    dest.append(component_value.release_value());
9463
0
                    return true;
9464
0
                }
9465
0
            }
9466
0
        } else if (attribute_type.equals_ignoring_ascii_case("ident"_fly_string)) {
9467
            // The substitution value is a CSS <custom-ident>, whose value is the literal value of the attribute,
9468
            // with leading and trailing ASCII whitespace stripped. (No CSS parsing of the value is performed.)
9469
            // If the attribute value, after trimming, is the empty string, there is instead no substitution value.
9470
            // If the <custom-ident>’s value is a CSS-wide keyword or `default`, there is instead no substitution value.
9471
0
            auto substitution_value = MUST(attribute_value.trim(Infra::ASCII_WHITESPACE));
9472
0
            if (!substitution_value.is_empty()
9473
0
                && !substitution_value.equals_ignoring_ascii_case("default"sv)
9474
0
                && !is_css_wide_keyword(substitution_value)) {
9475
0
                dest.empend(Token::create_ident(substitution_value));
9476
0
                return true;
9477
0
            }
9478
0
        } else if (attribute_type.equals_ignoring_ascii_case("length"_fly_string)) {
9479
            // Parse a component value from the attribute’s value.
9480
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9481
            // If the result is a <dimension-token> whose unit matches the given type, the result is the substitution value.
9482
            // Otherwise, there is no substitution value.
9483
0
            if (component_value.has_value() && component_value->is(Token::Type::Dimension)) {
9484
0
                if (Length::unit_from_name(component_value->token().dimension_unit()).has_value()) {
9485
0
                    dest.append(component_value.release_value());
9486
0
                    return true;
9487
0
                }
9488
0
            }
9489
0
        } else if (attribute_type.equals_ignoring_ascii_case("number"_fly_string)) {
9490
            // Parse a component value from the attribute’s value.
9491
            // If the result is a <number-token>, the result is the substitution value.
9492
            // Otherwise, there is no substitution value.
9493
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9494
0
            if (component_value.has_value() && component_value->is(Token::Type::Number)) {
9495
0
                dest.append(component_value.release_value());
9496
0
                return true;
9497
0
            }
9498
0
        } else if (attribute_type.equals_ignoring_ascii_case("percentage"_fly_string)) {
9499
            // Parse a component value from the attribute’s value.
9500
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9501
            // If the result is a <percentage-token>, the result is the substitution value.
9502
            // Otherwise, there is no substitution value.
9503
0
            if (component_value.has_value() && component_value->is(Token::Type::Percentage)) {
9504
0
                dest.append(component_value.release_value());
9505
0
                return true;
9506
0
            }
9507
0
        } else if (attribute_type.equals_ignoring_ascii_case("string"_fly_string)) {
9508
            // The substitution value is a CSS string, whose value is the literal value of the attribute.
9509
            // (No CSS parsing or "cleanup" of the value is performed.)
9510
            // No value triggers fallback.
9511
0
            dest.empend(Token::create_string(attribute_value));
9512
0
            return true;
9513
0
        } else if (attribute_type.equals_ignoring_ascii_case("time"_fly_string)) {
9514
            // Parse a component value from the attribute’s value.
9515
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9516
            // If the result is a <dimension-token> whose unit matches the given type, the result is the substitution value.
9517
            // Otherwise, there is no substitution value.
9518
0
            if (component_value.has_value() && component_value->is(Token::Type::Dimension)) {
9519
0
                if (Time::unit_from_name(component_value->token().dimension_unit()).has_value()) {
9520
0
                    dest.append(component_value.release_value());
9521
0
                    return true;
9522
0
                }
9523
0
            }
9524
0
        } else if (attribute_type.equals_ignoring_ascii_case("url"_fly_string)) {
9525
            // The substitution value is a CSS <url> value, whose url is the literal value of the attribute.
9526
            // (No CSS parsing or "cleanup" of the value is performed.)
9527
            // No value triggers fallback.
9528
0
            dest.empend(Token::create_url(attribute_value));
9529
0
            return true;
9530
0
        } else {
9531
            // Dimension units
9532
            // Parse a component value from the attribute’s value.
9533
            // If the result is a <number-token>, the substitution value is a dimension with the result’s value, and the given unit.
9534
            // Otherwise, there is no substitution value.
9535
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9536
0
            if (component_value.has_value() && component_value->is(Token::Type::Number)) {
9537
0
                if (attribute_value == "%"sv) {
9538
0
                    dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
9539
0
                    return true;
9540
0
                } else if (auto angle_unit = Angle::unit_from_name(attribute_type); angle_unit.has_value()) {
9541
0
                    dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
9542
0
                    return true;
9543
0
                } else if (auto flex_unit = Flex::unit_from_name(attribute_type); flex_unit.has_value()) {
9544
0
                    dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
9545
0
                    return true;
9546
0
                } else if (auto frequency_unit = Frequency::unit_from_name(attribute_type); frequency_unit.has_value()) {
9547
0
                    dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
9548
0
                    return true;
9549
0
                } else if (auto length_unit = Length::unit_from_name(attribute_type); length_unit.has_value()) {
9550
0
                    dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
9551
0
                    return true;
9552
0
                } else if (auto time_unit = Time::unit_from_name(attribute_type); time_unit.has_value()) {
9553
0
                    dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
9554
0
                    return true;
9555
0
                } else {
9556
                    // Not a dimension unit.
9557
0
                    return false;
9558
0
                }
9559
0
            }
9560
0
        }
9561
0
    }
9562
9563
    // 2. Otherwise, if the attr() function has a fallback value as its last argument, replace the attr() function by the fallback value.
9564
    //    If there are any var() or attr() references in the fallback, substitute them as well.
9565
0
    if (has_fallback_values)
9566
0
        return expand_unresolved_values(element, property_name, attr_contents, dest);
9567
9568
0
    if (attribute_type.equals_ignoring_ascii_case("string"_fly_string)) {
9569
        // If the <attr-type> argument is string, defaults to the empty string if omitted
9570
0
        dest.empend(Token::create_string({}));
9571
0
        return true;
9572
0
    }
9573
9574
    // 3. Otherwise, the property containing the attr() function is invalid at computed-value time.
9575
0
    return false;
9576
0
}
9577
9578
// https://drafts.csswg.org/css-fonts/#typedef-opentype-tag
9579
RefPtr<StringStyleValue> Parser::parse_opentype_tag_value(TokenStream<ComponentValue>& tokens)
9580
0
{
9581
    // <opentype-tag> = <string>
9582
    // The <opentype-tag> is a case-sensitive OpenType feature tag.
9583
    // As specified in the OpenType specification [OPENTYPE], feature tags contain four ASCII characters.
9584
    // Tag strings longer or shorter than four characters, or containing characters outside the U+20–7E codepoint range are invalid.
9585
9586
0
    auto transaction = tokens.begin_transaction();
9587
0
    auto string_value = parse_string_value(tokens);
9588
0
    if (string_value == nullptr)
9589
0
        return nullptr;
9590
9591
0
    auto string = string_value->string_value().bytes_as_string_view();
9592
0
    if (string.length() != 4)
9593
0
        return nullptr;
9594
0
    for (char c : string) {
9595
0
        if (c < 0x20 || c > 0x7E)
9596
0
            return nullptr;
9597
0
    }
9598
9599
0
    transaction.commit();
9600
0
    return string_value;
9601
0
}
9602
9603
}