Coverage Report

Created: 2025-08-28 06:26

/src/serenity/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2020-2021, the SerenityOS developers.
4
 * Copyright (c) 2021-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/CSSStyleDeclaration.h>
31
#include <LibWeb/CSS/CSSStyleRule.h>
32
#include <LibWeb/CSS/CSSStyleSheet.h>
33
#include <LibWeb/CSS/CSSStyleValue.h>
34
#include <LibWeb/CSS/CSSSupportsRule.h>
35
#include <LibWeb/CSS/CalculatedOr.h>
36
#include <LibWeb/CSS/EdgeRect.h>
37
#include <LibWeb/CSS/MediaList.h>
38
#include <LibWeb/CSS/Parser/Parser.h>
39
#include <LibWeb/CSS/PropertyName.h>
40
#include <LibWeb/CSS/Selector.h>
41
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
42
#include <LibWeb/CSS/StyleValues/BackgroundRepeatStyleValue.h>
43
#include <LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.h>
44
#include <LibWeb/CSS/StyleValues/BasicShapeStyleValue.h>
45
#include <LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h>
46
#include <LibWeb/CSS/StyleValues/CSSColor.h>
47
#include <LibWeb/CSS/StyleValues/CSSColorValue.h>
48
#include <LibWeb/CSS/StyleValues/CSSHSL.h>
49
#include <LibWeb/CSS/StyleValues/CSSHWB.h>
50
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
51
#include <LibWeb/CSS/StyleValues/CSSLCHLike.h>
52
#include <LibWeb/CSS/StyleValues/CSSLabLike.h>
53
#include <LibWeb/CSS/StyleValues/CSSRGB.h>
54
#include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
55
#include <LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h>
56
#include <LibWeb/CSS/StyleValues/CounterStyleValue.h>
57
#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
58
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
59
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
60
#include <LibWeb/CSS/StyleValues/EdgeStyleValue.h>
61
#include <LibWeb/CSS/StyleValues/FilterValueListStyleValue.h>
62
#include <LibWeb/CSS/StyleValues/FlexStyleValue.h>
63
#include <LibWeb/CSS/StyleValues/FrequencyStyleValue.h>
64
#include <LibWeb/CSS/StyleValues/GridAutoFlowStyleValue.h>
65
#include <LibWeb/CSS/StyleValues/GridTemplateAreaStyleValue.h>
66
#include <LibWeb/CSS/StyleValues/GridTrackPlacementStyleValue.h>
67
#include <LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.h>
68
#include <LibWeb/CSS/StyleValues/ImageStyleValue.h>
69
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
70
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
71
#include <LibWeb/CSS/StyleValues/MathDepthStyleValue.h>
72
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
73
#include <LibWeb/CSS/StyleValues/OpenTypeTaggedStyleValue.h>
74
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
75
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
76
#include <LibWeb/CSS/StyleValues/RatioStyleValue.h>
77
#include <LibWeb/CSS/StyleValues/RectStyleValue.h>
78
#include <LibWeb/CSS/StyleValues/ResolutionStyleValue.h>
79
#include <LibWeb/CSS/StyleValues/RotationStyleValue.h>
80
#include <LibWeb/CSS/StyleValues/ScrollbarGutterStyleValue.h>
81
#include <LibWeb/CSS/StyleValues/ShadowStyleValue.h>
82
#include <LibWeb/CSS/StyleValues/ShorthandStyleValue.h>
83
#include <LibWeb/CSS/StyleValues/StringStyleValue.h>
84
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
85
#include <LibWeb/CSS/StyleValues/TimeStyleValue.h>
86
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
87
#include <LibWeb/CSS/StyleValues/TransitionStyleValue.h>
88
#include <LibWeb/CSS/StyleValues/URLStyleValue.h>
89
#include <LibWeb/CSS/StyleValues/UnresolvedStyleValue.h>
90
#include <LibWeb/Dump.h>
91
#include <LibWeb/Infra/CharacterTypes.h>
92
#include <LibWeb/Infra/Strings.h>
93
94
static void log_parse_error(SourceLocation const& location = SourceLocation::current())
95
0
{
96
0
    dbgln_if(CSS_PARSER_DEBUG, "Parse error (CSS) {}", location);
97
0
}
98
99
namespace Web::CSS::Parser {
100
101
Parser Parser::create(ParsingContext const& context, StringView input, StringView encoding)
102
0
{
103
0
    auto tokens = Tokenizer::tokenize(input, encoding);
104
0
    return Parser { context, move(tokens) };
105
0
}
106
107
Parser::Parser(ParsingContext const& context, Vector<Token> tokens)
108
0
    : m_context(context)
109
0
    , m_tokens(move(tokens))
110
0
    , m_token_stream(m_tokens)
111
0
{
112
0
}
113
114
Parser::Parser(Parser&& other)
115
0
    : m_context(other.m_context)
116
0
    , m_tokens(move(other.m_tokens))
117
0
    , m_token_stream(m_tokens)
118
0
{
119
    // Moving the TokenStream directly from `other` would break it, because TokenStream holds
120
    // a reference to the Vector<Token>, so it would be pointing at the old Parser's tokens.
121
    // So instead, we create a new TokenStream from this Parser's tokens, and then tell it to
122
    // copy the other TokenStream's state. This is quite hacky.
123
0
    m_token_stream.copy_state({}, other.m_token_stream);
124
0
}
125
126
// https://drafts.csswg.org/css-syntax/#parse-stylesheet
127
template<typename T>
128
Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream<T>& input, Optional<URL::URL> location)
129
0
{
130
    // To parse a stylesheet from an input given an optional url location:
131
132
    // 1. If input is a byte stream for a stylesheet, decode bytes from input, and set input to the result.
133
    // 2. Normalize input, and set input to the result.
134
    // NOTE: These are done automatically when creating the Parser.
135
136
    // 3. Create a new stylesheet, with its location set to location (or null, if location was not passed).
137
0
    ParsedStyleSheet style_sheet;
138
0
    style_sheet.location = move(location);
139
140
    // 4. Consume a stylesheet’s contents from input, and set the stylesheet’s rules to the result.
141
0
    style_sheet.rules = consume_a_stylesheets_contents(input);
142
143
    // 5. Return the stylesheet.
144
0
    return style_sheet;
145
0
}
146
147
// https://drafts.csswg.org/css-syntax/#parse-a-stylesheets-contents
148
template<typename T>
149
Vector<Rule> Parser::parse_a_stylesheets_contents(TokenStream<T>& input)
150
{
151
    // To parse a stylesheet’s contents from input:
152
153
    // 1. Normalize input, and set input to the result.
154
    // NOTE: This is done automatically when creating the Parser.
155
156
    // 2. Consume a stylesheet’s contents from input, and return the result.
157
    return consume_a_stylesheets_contents(input);
158
}
159
160
// https://drafts.csswg.org/css-syntax/#parse-a-css-stylesheet
161
CSSStyleSheet* Parser::parse_as_css_stylesheet(Optional<URL::URL> location)
162
0
{
163
    // To parse a CSS stylesheet, first parse a stylesheet.
164
0
    auto style_sheet = parse_a_stylesheet(m_token_stream, {});
165
166
    // Interpret all of the resulting top-level qualified rules as style rules, defined below.
167
0
    JS::MarkedVector<CSSRule*> rules(m_context.realm().heap());
168
0
    for (auto const& raw_rule : style_sheet.rules) {
169
0
        auto rule = convert_to_rule(raw_rule, Nested::No);
170
        // 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.
171
        // Discard that rule.
172
0
        if (!rule) {
173
0
            log_parse_error();
174
0
            continue;
175
0
        }
176
0
        rules.append(rule);
177
0
    }
178
179
0
    auto rule_list = CSSRuleList::create(m_context.realm(), rules);
180
0
    auto media_list = MediaList::create(m_context.realm(), {});
181
0
    return CSSStyleSheet::create(m_context.realm(), rule_list, media_list, move(location));
182
0
}
183
184
RefPtr<Supports> Parser::parse_as_supports()
185
0
{
186
0
    return parse_a_supports(m_token_stream);
187
0
}
188
189
template<typename T>
190
RefPtr<Supports> Parser::parse_a_supports(TokenStream<T>& tokens)
191
0
{
192
0
    auto component_values = parse_a_list_of_component_values(tokens);
193
0
    TokenStream<ComponentValue> token_stream { component_values };
194
0
    auto maybe_condition = parse_supports_condition(token_stream);
195
0
    token_stream.discard_whitespace();
196
0
    if (maybe_condition && !token_stream.has_next_token())
197
0
        return Supports::create(m_context.realm(), maybe_condition.release_nonnull());
198
199
0
    return {};
200
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>&)
201
202
OwnPtr<Supports::Condition> Parser::parse_supports_condition(TokenStream<ComponentValue>& tokens)
203
0
{
204
0
    auto transaction = tokens.begin_transaction();
205
0
    tokens.discard_whitespace();
206
207
0
    auto const& peeked_token = tokens.next_token();
208
    // `not <supports-in-parens>`
209
0
    if (peeked_token.is_ident("not"sv)) {
210
0
        tokens.discard_a_token();
211
0
        tokens.discard_whitespace();
212
0
        auto child = parse_supports_in_parens(tokens);
213
0
        if (!child.has_value())
214
0
            return {};
215
216
0
        transaction.commit();
217
0
        auto condition = make<Supports::Condition>();
218
0
        condition->type = Supports::Condition::Type::Not;
219
0
        condition->children.append(child.release_value());
220
0
        return condition;
221
0
    }
222
223
    // `  <supports-in-parens> [ and <supports-in-parens> ]*
224
    //  | <supports-in-parens> [ or <supports-in-parens> ]*`
225
0
    Vector<Supports::InParens> children;
226
0
    Optional<Supports::Condition::Type> condition_type {};
227
0
    auto as_condition_type = [](auto& token) -> Optional<Supports::Condition::Type> {
228
0
        if (!token.is(Token::Type::Ident))
229
0
            return {};
230
0
        auto ident = token.token().ident();
231
0
        if (ident.equals_ignoring_ascii_case("and"sv))
232
0
            return Supports::Condition::Type::And;
233
0
        if (ident.equals_ignoring_ascii_case("or"sv))
234
0
            return Supports::Condition::Type::Or;
235
0
        return {};
236
0
    };
237
238
0
    while (tokens.has_next_token()) {
239
0
        if (!children.is_empty()) {
240
            // Expect `and` or `or` here
241
0
            auto maybe_combination = as_condition_type(tokens.consume_a_token());
242
0
            if (!maybe_combination.has_value())
243
0
                return {};
244
0
            if (!condition_type.has_value()) {
245
0
                condition_type = maybe_combination.value();
246
0
            } else if (maybe_combination != condition_type) {
247
0
                return {};
248
0
            }
249
0
        }
250
251
0
        tokens.discard_whitespace();
252
253
0
        if (auto in_parens = parse_supports_in_parens(tokens); in_parens.has_value()) {
254
0
            children.append(in_parens.release_value());
255
0
        } else {
256
0
            return {};
257
0
        }
258
259
0
        tokens.discard_whitespace();
260
0
    }
261
262
0
    if (children.is_empty())
263
0
        return {};
264
265
0
    transaction.commit();
266
0
    auto condition = make<Supports::Condition>();
267
0
    condition->type = condition_type.value_or(Supports::Condition::Type::Or);
268
0
    condition->children = move(children);
269
0
    return condition;
270
0
}
271
272
Optional<Supports::InParens> Parser::parse_supports_in_parens(TokenStream<ComponentValue>& tokens)
273
0
{
274
    // `( <supports-condition> )`
275
0
    auto const& first_token = tokens.next_token();
276
0
    if (first_token.is_block() && first_token.block().is_paren()) {
277
0
        auto transaction = tokens.begin_transaction();
278
0
        tokens.discard_a_token();
279
0
        tokens.discard_whitespace();
280
281
0
        TokenStream child_tokens { first_token.block().value };
282
0
        if (auto condition = parse_supports_condition(child_tokens)) {
283
0
            if (child_tokens.has_next_token())
284
0
                return {};
285
0
            transaction.commit();
286
0
            return Supports::InParens {
287
0
                .value = { condition.release_nonnull() }
288
0
            };
289
0
        }
290
0
    }
291
292
    // `<supports-feature>`
293
0
    if (auto feature = parse_supports_feature(tokens); feature.has_value()) {
294
0
        return Supports::InParens {
295
0
            .value = { feature.release_value() }
296
0
        };
297
0
    }
298
299
    // `<general-enclosed>`
300
0
    if (auto general_enclosed = parse_general_enclosed(tokens); general_enclosed.has_value()) {
301
0
        return Supports::InParens {
302
0
            .value = general_enclosed.release_value()
303
0
        };
304
0
    }
305
306
0
    return {};
307
0
}
308
309
Optional<Supports::Feature> Parser::parse_supports_feature(TokenStream<ComponentValue>& tokens)
310
0
{
311
0
    auto transaction = tokens.begin_transaction();
312
0
    tokens.discard_whitespace();
313
0
    auto const& first_token = tokens.consume_a_token();
314
315
    // `<supports-decl>`
316
0
    if (first_token.is_block() && first_token.block().is_paren()) {
317
0
        TokenStream block_tokens { first_token.block().value };
318
        // FIXME: Parsing and then converting back to a string is weird.
319
0
        if (auto declaration = consume_a_declaration(block_tokens); declaration.has_value()) {
320
0
            transaction.commit();
321
0
            return Supports::Feature {
322
0
                Supports::Declaration { declaration->to_string() }
323
0
            };
324
0
        }
325
0
    }
326
327
    // `<supports-selector-fn>`
328
0
    if (first_token.is_function("selector"sv)) {
329
        // FIXME: Parsing and then converting back to a string is weird.
330
0
        StringBuilder builder;
331
0
        for (auto const& item : first_token.function().value)
332
0
            builder.append(item.to_string());
333
0
        transaction.commit();
334
0
        return Supports::Feature {
335
0
            Supports::Selector { builder.to_string().release_value_but_fixme_should_propagate_errors() }
336
0
        };
337
0
    }
338
339
0
    return {};
340
0
}
341
342
// https://www.w3.org/TR/mediaqueries-4/#typedef-general-enclosed
343
Optional<GeneralEnclosed> Parser::parse_general_enclosed(TokenStream<ComponentValue>& tokens)
344
0
{
345
0
    auto transaction = tokens.begin_transaction();
346
0
    tokens.discard_whitespace();
347
0
    auto const& first_token = tokens.consume_a_token();
348
349
    // `[ <function-token> <any-value>? ) ]`
350
0
    if (first_token.is_function()) {
351
0
        transaction.commit();
352
0
        return GeneralEnclosed { first_token.to_string() };
353
0
    }
354
355
    // `( <any-value>? )`
356
0
    if (first_token.is_block() && first_token.block().is_paren()) {
357
0
        transaction.commit();
358
0
        return GeneralEnclosed { first_token.to_string() };
359
0
    }
360
361
0
    return {};
362
0
}
363
364
// https://drafts.csswg.org/css-syntax/#consume-stylesheet-contents
365
template<typename T>
366
Vector<Rule> Parser::consume_a_stylesheets_contents(TokenStream<T>& input)
367
0
{
368
    // To consume a stylesheet’s contents from a token stream input:
369
370
    // Let rules be an initially empty list of rules.
371
0
    Vector<Rule> rules;
372
373
    // Process input:
374
0
    for (;;) {
375
0
        auto& token = input.next_token();
376
377
        // <whitespace-token>
378
0
        if (token.is(Token::Type::Whitespace)) {
379
            // Discard a token from input.
380
0
            input.discard_a_token();
381
0
            continue;
382
0
        }
383
384
        // <EOF-token>
385
0
        if (token.is(Token::Type::EndOfFile)) {
386
            // Return rules.
387
0
            return rules;
388
0
        }
389
390
        // <CDO-token>
391
        // <CDC-token>
392
0
        if (token.is(Token::Type::CDO) || token.is(Token::Type::CDC)) {
393
            // Discard a token from input.
394
0
            input.discard_a_token();
395
0
            continue;
396
0
        }
397
398
        // <at-keyword-token>
399
0
        if (token.is(Token::Type::AtKeyword)) {
400
            // Consume an at-rule from input. If anything is returned, append it to rules.
401
0
            if (auto maybe_at_rule = consume_an_at_rule(input); maybe_at_rule.has_value())
402
0
                rules.append(*maybe_at_rule);
403
0
            continue;
404
0
        }
405
406
        // anything else
407
0
        {
408
            // Consume a qualified rule from input. If a rule is returned, append it to rules.
409
0
            consume_a_qualified_rule(input).visit(
410
0
                [&](QualifiedRule qualified_rule) { rules.append(move(qualified_rule)); },
411
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
412
0
        }
413
0
    }
414
0
}
415
416
// https://drafts.csswg.org/css-syntax/#consume-at-rule
417
template<typename T>
418
Optional<AtRule> Parser::consume_an_at_rule(TokenStream<T>& input, Nested nested)
419
0
{
420
    // To consume an at-rule from a token stream input, given an optional bool nested (default false):
421
422
    // Assert: The next token is an <at-keyword-token>.
423
0
    VERIFY(input.next_token().is(Token::Type::AtKeyword));
424
425
    // Consume a token from input, and let rule be a new at-rule with its name set to the returned token’s value,
426
    // its prelude initially set to an empty list, and no declarations or child rules.
427
0
    AtRule rule {
428
0
        .name = ((Token)input.consume_a_token()).at_keyword(),
429
0
        .prelude = {},
430
0
        .child_rules_and_lists_of_declarations = {},
431
0
    };
432
433
    // Process input:
434
0
    for (;;) {
435
0
        auto& token = input.next_token();
436
437
        // <semicolon-token>
438
        // <EOF-token>
439
0
        if (token.is(Token::Type::Semicolon) || token.is(Token::Type::EndOfFile)) {
440
            // Discard a token from input. If rule is valid in the current context, return it; otherwise return nothing.
441
0
            input.discard_a_token();
442
0
            if (is_valid_in_the_current_context(rule))
443
0
                return rule;
444
0
            return {};
445
0
        }
446
447
        // <}-token>
448
0
        if (token.is(Token::Type::CloseCurly)) {
449
            // If nested is true:
450
0
            if (nested == Nested::Yes) {
451
                // If rule is valid in the current context, return it.
452
0
                if (is_valid_in_the_current_context(rule))
453
0
                    return rule;
454
                // Otherwise, return nothing.
455
0
                return {};
456
0
            }
457
            // Otherwise, consume a token and append the result to rule’s prelude.
458
0
            else {
459
0
                rule.prelude.append(input.consume_a_token());
460
0
            }
461
0
            continue;
462
0
        }
463
464
        // <{-token>
465
0
        if (token.is(Token::Type::OpenCurly)) {
466
            // Consume a block from input, and assign the result to rule’s child rules.
467
0
            rule.child_rules_and_lists_of_declarations = consume_a_block(input);
468
469
            // If rule is valid in the current context, return it. Otherwise, return nothing.
470
0
            if (is_valid_in_the_current_context(rule))
471
0
                return rule;
472
0
            return {};
473
0
        }
474
475
        // anything else
476
0
        {
477
            // Consume a component value from input and append the returned value to rule’s prelude.
478
0
            rule.prelude.append(consume_a_component_value(input));
479
0
        }
480
0
    }
481
0
}
482
483
// https://drafts.csswg.org/css-syntax/#consume-qualified-rule
484
template<typename T>
485
Variant<Empty, QualifiedRule, Parser::InvalidRuleError> Parser::consume_a_qualified_rule(TokenStream<T>& input, Optional<Token::Type> stop_token, Nested nested)
486
0
{
487
    // To consume a qualified rule, from a token stream input, given an optional token stop token and an optional bool nested (default false):
488
489
    // Let rule be a new qualified rule with its prelude, declarations, and child rules all initially set to empty lists.
490
0
    QualifiedRule rule {
491
0
        .prelude = {},
492
0
        .declarations = {},
493
0
        .child_rules = {},
494
0
    };
495
496
    // Process input:
497
0
    for (;;) {
498
0
        auto& token = input.next_token();
499
500
        // <EOF-token>
501
        // stop token (if passed)
502
0
        if (token.is(Token::Type::EndOfFile) || (stop_token.has_value() && token.is(*stop_token))) {
503
            // This is a parse error. Return nothing.
504
0
            log_parse_error();
505
0
            return {};
506
0
        }
507
508
        // <}-token>
509
0
        if (token.is(Token::Type::CloseCurly)) {
510
            // This is a parse error. If nested is true, return nothing. Otherwise, consume a token and append the result to rule’s prelude.
511
0
            log_parse_error();
512
0
            if (nested == Nested::Yes)
513
0
                return {};
514
0
            rule.prelude.append(input.consume_a_token());
515
0
            continue;
516
0
        }
517
518
        // <{-token>
519
0
        if (token.is(Token::Type::OpenCurly)) {
520
            // If the first two non-<whitespace-token> values of rule’s prelude are an <ident-token> whose value starts with "--"
521
            // followed by a <colon-token>, then:
522
0
            TokenStream prelude_tokens { rule.prelude };
523
0
            prelude_tokens.discard_whitespace();
524
0
            auto& first_non_whitespace = prelude_tokens.consume_a_token();
525
0
            prelude_tokens.discard_whitespace();
526
0
            auto& second_non_whitespace = prelude_tokens.consume_a_token();
527
0
            if (first_non_whitespace.is(Token::Type::Ident) && first_non_whitespace.token().ident().starts_with_bytes("--"sv)
528
0
                && second_non_whitespace.is(Token::Type::Colon)) {
529
                // If nested is true, consume the remnants of a bad declaration from input, with nested set to true, and return nothing.
530
0
                if (nested == Nested::Yes) {
531
0
                    consume_the_remnants_of_a_bad_declaration(input, Nested::Yes);
532
0
                    return {};
533
0
                }
534
535
                // If nested is false, consume a block from input, and return nothing.
536
0
                (void)consume_a_block(input);
537
0
                return {};
538
0
            }
539
540
            // Otherwise, consume a block from input, and let child rules be the result.
541
0
            rule.child_rules = consume_a_block(input);
542
543
            // If the first item of child rules is a list of declarations, remove it from child rules and assign it to rule’s declarations.
544
0
            if (!rule.child_rules.is_empty() && rule.child_rules.first().has<Vector<Declaration>>()) {
545
0
                auto first = rule.child_rules.take_first();
546
0
                rule.declarations = move(first.get<Vector<Declaration>>());
547
0
            }
548
549
            // FIXME: If any remaining items of child rules are lists of declarations, replace them with nested declarations rules
550
            //        containing the list as its sole child. Assign child rules to rule’s child rules.
551
552
            // If rule is valid in the current context, return it; otherwise return an invalid rule error.
553
0
            if (is_valid_in_the_current_context(rule))
554
0
                return rule;
555
0
            return InvalidRuleError {};
556
0
        }
557
558
        // anything else
559
0
        {
560
            // Consume a component value from input and append the result to rule’s prelude.
561
0
            rule.prelude.append(consume_a_component_value(input));
562
0
        }
563
0
    }
564
0
}
565
566
// https://drafts.csswg.org/css-syntax/#consume-block
567
template<typename T>
568
Vector<RuleOrListOfDeclarations> Parser::consume_a_block(TokenStream<T>& input)
569
0
{
570
    // To consume a block, from a token stream input:
571
572
    // Assert: The next token is a <{-token>.
573
0
    VERIFY(input.next_token().is(Token::Type::OpenCurly));
574
575
    // Discard a token from input.
576
0
    input.discard_a_token();
577
    // Consume a block’s contents from input and let rules be the result.
578
0
    auto rules = consume_a_blocks_contents(input);
579
    // Discard a token from input.
580
0
    input.discard_a_token();
581
582
    // Return rules.
583
0
    return rules;
584
0
}
585
586
// https://drafts.csswg.org/css-syntax/#consume-block-contents
587
template<typename T>
588
Vector<RuleOrListOfDeclarations> Parser::consume_a_blocks_contents(TokenStream<T>& input)
589
0
{
590
    // To consume a block’s contents from a token stream input:
591
592
    // Let rules be an empty list, containing either rules or lists of declarations.
593
0
    Vector<RuleOrListOfDeclarations> rules;
594
595
    // Let decls be an empty list of declarations.
596
0
    Vector<Declaration> declarations;
597
598
    // Process input:
599
0
    for (;;) {
600
0
        auto& token = input.next_token();
601
602
        // <whitespace-token>
603
        // <semicolon-token>
604
0
        if (token.is(Token::Type::Whitespace) || token.is(Token::Type::Semicolon)) {
605
            // Discard a token from input.
606
0
            input.discard_a_token();
607
0
            continue;
608
0
        }
609
610
        // <EOF-token>
611
        // <}-token>
612
0
        if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::CloseCurly)) {
613
            // AD-HOC: If decls is not empty, append it to rules.
614
            // Spec issue: https://github.com/w3c/csswg-drafts/issues/11017
615
0
            if (!declarations.is_empty())
616
0
                rules.append(move(declarations));
617
            // Return rules.
618
0
            return rules;
619
0
        }
620
621
        // <at-keyword-token>
622
0
        if (token.is(Token::Type::AtKeyword)) {
623
            // If decls is not empty, append it to rules, and set decls to a fresh empty list of declarations.
624
0
            if (!declarations.is_empty()) {
625
0
                rules.append(move(declarations));
626
0
                declarations = {};
627
0
            }
628
629
            // Consume an at-rule from input, with nested set to true.
630
            // If a rule was returned, append it to rules.
631
0
            if (auto at_rule = consume_an_at_rule(input, Nested::Yes); at_rule.has_value())
632
0
                rules.append({ at_rule.release_value() });
633
634
0
            continue;
635
0
        }
636
637
        // anything else
638
0
        {
639
            // Mark input.
640
0
            input.mark();
641
642
            // Consume a declaration from input, with nested set to true.
643
            // If a declaration was returned, append it to decls, and discard a mark from input.
644
0
            if (auto declaration = consume_a_declaration(input, Nested::Yes); declaration.has_value()) {
645
0
                declarations.append(declaration.release_value());
646
0
                input.discard_a_mark();
647
0
            }
648
649
            // Otherwise, restore a mark from input, then consume a qualified rule from input,
650
            // with nested set to true, and <semicolon-token> as the stop token.
651
0
            else {
652
0
                input.restore_a_mark();
653
0
                consume_a_qualified_rule(input, Token::Type::Semicolon, Nested::Yes).visit(
654
                    // -> If nothing was returned
655
0
                    [](Empty&) {
656
                        // Do nothing
657
0
                    },
658
                    // -> If an invalid rule error was returned
659
0
                    [&](InvalidRuleError&) {
660
                        // If decls is not empty, append decls to rules, and set decls to a fresh empty list of declarations. (Otherwise, do nothing.)
661
0
                        if (!declarations.is_empty()) {
662
0
                            rules.append(move(declarations));
663
0
                            declarations = {};
664
0
                        }
665
0
                    },
666
                    // -> If a rule was returned
667
0
                    [&](QualifiedRule rule) {
668
                        // If decls is not empty, append decls to rules, and set decls to a fresh empty list of declarations.
669
0
                        if (!declarations.is_empty()) {
670
0
                            rules.append(move(declarations));
671
0
                            declarations = {};
672
0
                        }
673
                        // Append the rule to rules.
674
0
                        rules.append({ move(rule) });
675
0
                    });
676
0
            }
677
0
        }
678
0
    }
679
0
}
680
681
template<>
682
ComponentValue Parser::consume_a_component_value(TokenStream<ComponentValue>& tokens)
683
0
{
684
    // Note: This overload is called once tokens have already been converted into component values,
685
    //       so we do not need to do the work in the more general overload.
686
0
    return tokens.consume_a_token();
687
0
}
688
689
// 5.4.7. Consume a component value
690
// https://drafts.csswg.org/css-syntax/#consume-component-value
691
template<typename T>
692
ComponentValue Parser::consume_a_component_value(TokenStream<T>& input)
693
0
{
694
    // To consume a component value from a token stream input:
695
696
    // Process input:
697
0
    for (;;) {
698
0
        auto const& token = input.next_token();
699
700
        // <{-token>
701
        // <[-token>
702
        // <(-token>
703
0
        if (token.is(Token::Type::OpenCurly) || token.is(Token::Type::OpenSquare) || token.is(Token::Type::OpenParen)) {
704
            // Consume a simple block from input and return the result.
705
0
            return ComponentValue { consume_a_simple_block(input) };
706
0
        }
707
708
        // <function-token>
709
0
        if (token.is(Token::Type::Function)) {
710
            // Consume a function from input and return the result.
711
0
            return ComponentValue { consume_a_function(input) };
712
0
        }
713
714
        // anything else
715
0
        {
716
            // Consume a token from input and return the result.
717
0
            return ComponentValue { input.consume_a_token() };
718
0
        }
719
0
    }
720
0
}
721
722
template<>
723
void Parser::consume_a_component_value_and_do_nothing<ComponentValue>(TokenStream<ComponentValue>& tokens)
724
0
{
725
    // AD-HOC: To avoid unnecessairy allocations, we explicitly define a "do nothing" variant that discards the result immediately.
726
    // Note: This overload is called once tokens have already been converted into component values,
727
    //       so we do not need to do the work in the more general overload.
728
0
    (void)tokens.consume_a_token();
729
0
}
730
731
// 5.4.7. Consume a component value
732
// https://drafts.csswg.org/css-syntax/#consume-component-value
733
template<typename T>
734
void Parser::consume_a_component_value_and_do_nothing(TokenStream<T>& input)
735
0
{
736
    // AD-HOC: To avoid unnecessairy allocations, we explicitly define a "do nothing" variant that discards the result immediately.
737
    // To consume a component value from a token stream input:
738
739
    // Process input:
740
0
    for (;;) {
741
0
        auto const& token = input.next_token();
742
743
        // <{-token>
744
        // <[-token>
745
        // <(-token>
746
0
        if (token.is(Token::Type::OpenCurly) || token.is(Token::Type::OpenSquare) || token.is(Token::Type::OpenParen)) {
747
            // Consume a simple block from input and return the result.
748
0
            consume_a_simple_block_and_do_nothing(input);
749
0
            return;
750
0
        }
751
752
        // <function-token>
753
0
        if (token.is(Token::Type::Function)) {
754
            // Consume a function from input and return the result.
755
0
            consume_a_function_and_do_nothing(input);
756
0
            return;
757
0
        }
758
759
        // anything else
760
0
        {
761
            // Consume a token from input and return the result.
762
0
            input.discard_a_token();
763
0
            return;
764
0
        }
765
0
    }
766
0
}
767
768
template<typename T>
769
Vector<ComponentValue> Parser::consume_a_list_of_component_values(TokenStream<T>& input, Optional<Token::Type> stop_token, Nested nested)
770
0
{
771
    // To consume a list of component values from a token stream input, given an optional token stop token
772
    // and an optional boolean nested (default false):
773
774
    // Let values be an empty list of component values.
775
0
    Vector<ComponentValue> values;
776
777
    // Process input:
778
0
    for (;;) {
779
0
        auto& token = input.next_token();
780
781
        // <eof-token>
782
        // stop token (if passed)
783
0
        if (token.is(Token::Type::EndOfFile) || (stop_token.has_value() && token.is(*stop_token))) {
784
            // Return values.
785
0
            return values;
786
0
        }
787
788
        // <}-token>
789
0
        if (token.is(Token::Type::CloseCurly)) {
790
            // If nested is true, return values.
791
0
            if (nested == Nested::Yes) {
792
0
                return values;
793
0
            }
794
            // Otherwise, this is a parse error. Consume a token from input and append the result to values.
795
0
            else {
796
0
                log_parse_error();
797
0
                values.append(input.consume_a_token());
798
0
            }
799
0
        }
800
801
        // anything else
802
0
        {
803
            // Consume a component value from input, and append the result to values.
804
0
            values.append(consume_a_component_value(input));
805
0
        }
806
0
    }
807
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)
808
809
// https://drafts.csswg.org/css-syntax/#consume-simple-block
810
template<typename T>
811
SimpleBlock Parser::consume_a_simple_block(TokenStream<T>& input)
812
0
{
813
    // To consume a simple block from a token stream input:
814
815
    // Assert: the next token of input is <{-token>, <[-token>, or <(-token>.
816
0
    auto const& next = input.next_token();
817
0
    VERIFY(next.is(Token::Type::OpenCurly) || next.is(Token::Type::OpenSquare) || next.is(Token::Type::OpenParen));
818
819
    // Let ending token be the mirror variant of the next token. (E.g. if it was called with <[-token>, the ending token is <]-token>.)
820
0
    auto ending_token = input.next_token().mirror_variant();
821
822
    // 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.
823
0
    SimpleBlock block {
824
0
        .token = input.next_token(),
825
0
        .value = {},
826
0
    };
827
828
    // Discard a token from input.
829
0
    input.discard_a_token();
830
831
    // Process input:
832
0
    for (;;) {
833
0
        auto const& token = input.next_token();
834
835
        // <eof-token>
836
        // ending token
837
0
        if (token.is(Token::Type::EndOfFile) || token.is(ending_token)) {
838
            // Discard a token from input. Return block.
839
            // AD-HOC: Store the token instead as the "end token"
840
0
            block.end_token = input.consume_a_token();
841
0
            return block;
842
0
        }
843
844
        // anything else
845
0
        {
846
            // Consume a component value from input and append the result to block’s value.
847
0
            block.value.empend(consume_a_component_value(input));
848
0
        }
849
0
    }
850
0
}
851
852
// https://drafts.csswg.org/css-syntax/#consume-simple-block
853
template<typename T>
854
void Parser::consume_a_simple_block_and_do_nothing(TokenStream<T>& input)
855
0
{
856
    // AD-HOC: To avoid unnecessairy allocations, we explicitly define a "do nothing" variant that discards the result immediately.
857
    // To consume a simple block from a token stream input:
858
859
    // Assert: the next token of input is <{-token>, <[-token>, or <(-token>.
860
0
    auto const& next = input.next_token();
861
0
    VERIFY(next.is(Token::Type::OpenCurly) || next.is(Token::Type::OpenSquare) || next.is(Token::Type::OpenParen));
862
863
    // Let ending token be the mirror variant of the next token. (E.g. if it was called with <[-token>, the ending token is <]-token>.)
864
0
    auto ending_token = input.next_token().mirror_variant();
865
866
    // 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.
867
868
    // Discard a token from input.
869
0
    input.discard_a_token();
870
871
    // Process input:
872
0
    for (;;) {
873
0
        auto const& token = input.next_token();
874
875
        // <eof-token>
876
        // ending token
877
0
        if (token.is(Token::Type::EndOfFile) || token.is(ending_token)) {
878
            // Discard a token from input. Return block.
879
0
            input.discard_a_token();
880
0
            return;
881
0
        }
882
883
        // anything else
884
0
        {
885
            // Consume a component value from input and append the result to block’s value.
886
0
            consume_a_component_value_and_do_nothing(input);
887
0
        }
888
0
    }
889
0
}
890
891
// https://drafts.csswg.org/css-syntax/#consume-function
892
template<typename T>
893
Function Parser::consume_a_function(TokenStream<T>& input)
894
0
{
895
    // To consume a function from a token stream input:
896
897
    // Assert: The next token is a <function-token>.
898
0
    VERIFY(input.next_token().is(Token::Type::Function));
899
900
    // Consume a token from input, and let function be a new function with its name equal the returned token’s value,
901
    // and a value set to an empty list.
902
0
    auto name_token = ((Token)input.consume_a_token());
903
0
    Function function {
904
0
        .name = name_token.function(),
905
0
        .value = {},
906
0
        .name_token = name_token,
907
0
    };
908
909
    // Process input:
910
0
    for (;;) {
911
0
        auto const& token = input.next_token();
912
913
        // <eof-token>
914
        // <)-token>
915
0
        if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::CloseParen)) {
916
            // Discard a token from input. Return function.
917
            // AD-HOC: Store the token instead as the "end token"
918
0
            function.end_token = input.consume_a_token();
919
0
            return function;
920
0
        }
921
922
        // anything else
923
0
        {
924
            // Consume a component value from input and append the result to function’s value.
925
0
            function.value.append(consume_a_component_value(input));
926
0
        }
927
0
    }
928
0
}
929
930
// https://drafts.csswg.org/css-syntax/#consume-function
931
template<typename T>
932
void Parser::consume_a_function_and_do_nothing(TokenStream<T>& input)
933
0
{
934
    // AD-HOC: To avoid unnecessairy allocations, we explicitly define a "do nothing" variant that discards the result immediately.
935
    // To consume a function from a token stream input:
936
937
    // Assert: The next token is a <function-token>.
938
0
    VERIFY(input.next_token().is(Token::Type::Function));
939
940
    // Consume a token from input, and let function be a new function with its name equal the returned token’s value,
941
    // and a value set to an empty list.
942
0
    input.discard_a_token();
943
944
    // Process input:
945
0
    for (;;) {
946
0
        auto const& token = input.next_token();
947
948
        // <eof-token>
949
        // <)-token>
950
0
        if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::CloseParen)) {
951
            // Discard a token from input. Return function.
952
0
            input.discard_a_token();
953
0
            return;
954
0
        }
955
956
        // anything else
957
0
        {
958
            // Consume a component value from input and append the result to function’s value.
959
0
            consume_a_component_value_and_do_nothing(input);
960
0
        }
961
0
    }
962
0
}
963
964
// https://drafts.csswg.org/css-syntax/#consume-declaration
965
template<typename T>
966
Optional<Declaration> Parser::consume_a_declaration(TokenStream<T>& input, Nested nested)
967
0
{
968
    // To consume a declaration from a token stream input, given an optional bool nested (default false):
969
970
    // TODO: As noted in the "Implementation note" below https://drafts.csswg.org/css-syntax/#consume-block-contents
971
    //       there are ways we can optimise this by early-exiting.
972
973
    // Let decl be a new declaration, with an initially empty name and a value set to an empty list.
974
0
    Declaration declaration {
975
0
        .name {},
976
0
        .value {},
977
0
    };
978
979
    // 1. If the next token is an <ident-token>, consume a token from input and set decl’s name to the token’s value.
980
0
    if (input.next_token().is(Token::Type::Ident)) {
981
0
        declaration.name = ((Token)input.consume_a_token()).ident();
982
0
    }
983
    //    Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
984
0
    else {
985
0
        consume_the_remnants_of_a_bad_declaration(input, nested);
986
0
        return {};
987
0
    }
988
989
    // 2. Discard whitespace from input.
990
0
    input.discard_whitespace();
991
992
    // 3. If the next token is a <colon-token>, discard a token from input.
993
0
    if (input.next_token().is(Token::Type::Colon)) {
994
0
        input.discard_a_token();
995
0
    }
996
    //    Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
997
0
    else {
998
0
        consume_the_remnants_of_a_bad_declaration(input, nested);
999
0
        return {};
1000
0
    }
1001
1002
    // 4. Discard whitespace from input.
1003
0
    input.discard_whitespace();
1004
1005
    // 5. Consume a list of component values from input, with nested, and with <semicolon-token> as the stop token,
1006
    //    and set decl’s value to the result.
1007
0
    declaration.value = consume_a_list_of_component_values(input, Token::Type::Semicolon, nested);
1008
1009
    // 6. If the last two non-<whitespace-token>s in decl’s value are a <delim-token> with the value "!"
1010
    //    followed by an <ident-token> with a value that is an ASCII case-insensitive match for "important",
1011
    //    remove them from decl’s value and set decl’s important flag.
1012
0
    if (declaration.value.size() >= 2) {
1013
        // NOTE: Walk backwards from the end until we find "important"
1014
0
        Optional<size_t> important_index;
1015
0
        for (size_t i = declaration.value.size() - 1; i > 0; i--) {
1016
0
            auto const& value = declaration.value[i];
1017
0
            if (value.is_ident("important"sv)) {
1018
0
                important_index = i;
1019
0
                break;
1020
0
            }
1021
0
            if (!value.is(Token::Type::Whitespace))
1022
0
                break;
1023
0
        }
1024
1025
        // NOTE: Walk backwards from important until we find "!"
1026
0
        if (important_index.has_value()) {
1027
0
            Optional<size_t> bang_index;
1028
0
            for (size_t i = important_index.value() - 1; i > 0; i--) {
1029
0
                auto const& value = declaration.value[i];
1030
0
                if (value.is_delim('!')) {
1031
0
                    bang_index = i;
1032
0
                    break;
1033
0
                }
1034
0
                if (value.is(Token::Type::Whitespace))
1035
0
                    continue;
1036
0
                break;
1037
0
            }
1038
1039
0
            if (bang_index.has_value()) {
1040
0
                declaration.value.remove(important_index.value());
1041
0
                declaration.value.remove(bang_index.value());
1042
0
                declaration.important = Important::Yes;
1043
0
            }
1044
0
        }
1045
0
    }
1046
1047
    // 7. While the last item in decl’s value is a <whitespace-token>, remove that token.
1048
0
    while (!declaration.value.is_empty() && declaration.value.last().is(Token::Type::Whitespace)) {
1049
0
        declaration.value.take_last();
1050
0
    }
1051
1052
    // See second clause of step 8.
1053
0
    auto contains_a_curly_block_and_non_whitespace = [](Vector<ComponentValue> const& declaration_value) {
1054
0
        bool contains_curly_block = false;
1055
0
        bool contains_non_whitespace = false;
1056
0
        for (auto const& value : declaration_value) {
1057
0
            if (value.is_block() && value.block().is_curly()) {
1058
0
                if (contains_non_whitespace)
1059
0
                    return true;
1060
0
                contains_curly_block = true;
1061
0
                continue;
1062
0
            }
1063
1064
0
            if (!value.is(Token::Type::Whitespace)) {
1065
0
                if (contains_curly_block)
1066
0
                    return true;
1067
0
                contains_non_whitespace = true;
1068
0
                continue;
1069
0
            }
1070
0
        }
1071
0
        return false;
1072
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
1073
1074
    // 8. If decl’s name is a custom property name string, then set decl’s original text to the segment
1075
    //    of the original source text string corresponding to the tokens of decl’s value.
1076
0
    if (is_a_custom_property_name_string(declaration.name)) {
1077
        // TODO: If we could reach inside the source string that the TokenStream uses, we could grab this as
1078
        //       a single substring instead of having to reconstruct it.
1079
0
        StringBuilder original_text;
1080
0
        for (auto const& value : declaration.value) {
1081
0
            original_text.append(value.original_source_text());
1082
0
        }
1083
0
        declaration.original_text = original_text.to_string_without_validation();
1084
0
    }
1085
    //    Otherwise, if decl’s value contains a top-level simple block with an associated token of <{-token>,
1086
    //    and also contains any other non-<whitespace-token> value, return nothing.
1087
    //    (That is, a top-level {}-block is only allowed as the entire value of a non-custom property.)
1088
0
    else if (contains_a_curly_block_and_non_whitespace(declaration.value)) {
1089
0
        return {};
1090
0
    }
1091
    //    Otherwise, if decl’s name is an ASCII case-insensitive match for "unicode-range", consume the value of
1092
    //    a unicode-range descriptor from the segment of the original source text string corresponding to the
1093
    //    tokens returned by the consume a list of component values call, and replace decl’s value with the result.
1094
0
    else if (declaration.name.equals_ignoring_ascii_case("unicode-range"sv)) {
1095
        // FIXME: Special unicode-range handling
1096
0
    }
1097
1098
    // 9. If decl is valid in the current context, return it; otherwise return nothing.
1099
0
    if (is_valid_in_the_current_context(declaration))
1100
0
        return declaration;
1101
0
    return {};
1102
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)
1103
1104
// https://drafts.csswg.org/css-syntax/#consume-the-remnants-of-a-bad-declaration
1105
template<typename T>
1106
void Parser::consume_the_remnants_of_a_bad_declaration(TokenStream<T>& input, Nested nested)
1107
0
{
1108
    // To consume the remnants of a bad declaration from a token stream input, given a bool nested:
1109
1110
    // Process input:
1111
0
    for (;;) {
1112
0
        auto const& token = input.next_token();
1113
1114
        // <eof-token>
1115
        // <semicolon-token>
1116
0
        if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::Semicolon)) {
1117
            // Discard a token from input, and return nothing.
1118
0
            input.discard_a_token();
1119
0
            return;
1120
0
        }
1121
1122
        // <}-token>
1123
0
        if (token.is(Token::Type::CloseCurly)) {
1124
            // If nested is true, return nothing. Otherwise, discard a token.
1125
0
            if (nested == Nested::Yes)
1126
0
                return;
1127
0
            input.discard_a_token();
1128
0
            continue;
1129
0
        }
1130
1131
        // anything else
1132
0
        {
1133
            // Consume a component value from input, and do nothing.
1134
0
            consume_a_component_value_and_do_nothing(input);
1135
0
            continue;
1136
0
        }
1137
0
    }
1138
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)
1139
1140
CSSRule* Parser::parse_as_css_rule()
1141
0
{
1142
0
    if (auto maybe_rule = parse_a_rule(m_token_stream); maybe_rule.has_value())
1143
0
        return convert_to_rule(maybe_rule.value(), Nested::No);
1144
0
    return {};
1145
0
}
1146
1147
// https://drafts.csswg.org/css-syntax/#parse-rule
1148
template<typename T>
1149
Optional<Rule> Parser::parse_a_rule(TokenStream<T>& input)
1150
0
{
1151
    // To parse a rule from input:
1152
0
    Optional<Rule> rule;
1153
1154
    // 1. Normalize input, and set input to the result.
1155
    // NOTE: This is done when initializing the Parser.
1156
1157
    // 2. Discard whitespace from input.
1158
0
    input.discard_whitespace();
1159
1160
    // 3. If the next token from input is an <EOF-token>, return a syntax error.
1161
0
    if (input.next_token().is(Token::Type::EndOfFile)) {
1162
0
        return {};
1163
0
    }
1164
    //    Otherwise, if the next token from input is an <at-keyword-token>,
1165
    //    consume an at-rule from input, and let rule be the return value.
1166
0
    else if (input.next_token().is(Token::Type::AtKeyword)) {
1167
0
        rule = consume_an_at_rule(m_token_stream).map([](auto& it) { return Rule { it }; });
1168
0
    }
1169
    //    Otherwise, consume a qualified rule from input and let rule be the return value.
1170
    //    If nothing or an invalid rule error was returned, return a syntax error.
1171
0
    else {
1172
0
        consume_a_qualified_rule(input).visit(
1173
0
            [&](QualifiedRule qualified_rule) { rule = move(qualified_rule); },
1174
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
1175
1176
0
        if (!rule.has_value())
1177
0
            return {};
1178
0
    }
1179
1180
    // 4. Discard whitespace from input.
1181
0
    input.discard_whitespace();
1182
1183
    // 5. If the next token from input is an <EOF-token>, return rule. Otherwise, return a syntax error.
1184
0
    if (input.next_token().is(Token::Type::EndOfFile))
1185
0
        return rule;
1186
0
    return {};
1187
0
}
1188
1189
// https://drafts.csswg.org/css-syntax/#parse-block-contents
1190
template<typename T>
1191
Vector<RuleOrListOfDeclarations> Parser::parse_a_blocks_contents(TokenStream<T>& input)
1192
0
{
1193
    // To parse a block’s contents from input:
1194
1195
    // 1. Normalize input, and set input to the result.
1196
    // NOTE: Done by constructing the Parser.
1197
1198
    // 2. Consume a block’s contents from input, and return the result.
1199
0
    return consume_a_blocks_contents(input);
1200
0
}
1201
1202
Optional<StyleProperty> Parser::parse_as_supports_condition()
1203
0
{
1204
0
    auto maybe_declaration = parse_a_declaration(m_token_stream);
1205
0
    if (maybe_declaration.has_value())
1206
0
        return convert_to_style_property(maybe_declaration.release_value());
1207
0
    return {};
1208
0
}
1209
1210
// https://drafts.csswg.org/css-syntax/#parse-declaration
1211
template<typename T>
1212
Optional<Declaration> Parser::parse_a_declaration(TokenStream<T>& input)
1213
0
{
1214
    // To parse a declaration from input:
1215
1216
    // 1. Normalize input, and set input to the result.
1217
    // Note: This is done when initializing the Parser.
1218
1219
    // 2. Discard whitespace from input.
1220
0
    input.discard_whitespace();
1221
1222
    // 3. Consume a declaration from input. If anything was returned, return it. Otherwise, return a syntax error.
1223
0
    if (auto declaration = consume_a_declaration(input); declaration.has_value())
1224
0
        return declaration.release_value();
1225
    // FIXME: Syntax error
1226
0
    return {};
1227
0
}
1228
1229
Optional<ComponentValue> Parser::parse_as_component_value()
1230
0
{
1231
0
    return parse_a_component_value(m_token_stream);
1232
0
}
1233
1234
// https://drafts.csswg.org/css-syntax/#parse-component-value
1235
template<typename T>
1236
Optional<ComponentValue> Parser::parse_a_component_value(TokenStream<T>& input)
1237
0
{
1238
    // To parse a component value from input:
1239
1240
    // 1. Normalize input, and set input to the result.
1241
    // Note: This is done when initializing the Parser.
1242
1243
    // 2. Discard whitespace from input.
1244
0
    input.discard_whitespace();
1245
1246
    // 3. If input is empty, return a syntax error.
1247
    // FIXME: Syntax error
1248
0
    if (input.is_empty())
1249
0
        return {};
1250
1251
    // 4. Consume a component value from input and let value be the return value.
1252
0
    auto value = consume_a_component_value(input);
1253
1254
    // 5. Discard whitespace from input.
1255
0
    input.discard_whitespace();
1256
1257
    // 6. If input is empty, return value. Otherwise, return a syntax error.
1258
0
    if (input.is_empty())
1259
0
        return value;
1260
    // FIXME: Syntax error
1261
0
    return {};
1262
0
}
1263
1264
// https://drafts.csswg.org/css-syntax/#parse-list-of-component-values
1265
template<typename T>
1266
Vector<ComponentValue> Parser::parse_a_list_of_component_values(TokenStream<T>& input)
1267
0
{
1268
    // To parse a list of component values from input:
1269
1270
    // 1. Normalize input, and set input to the result.
1271
    // Note: This is done when initializing the Parser.
1272
1273
    // 2. Consume a list of component values from input, and return the result.
1274
0
    return consume_a_list_of_component_values(input);
1275
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>&)
1276
1277
// https://drafts.csswg.org/css-syntax/#parse-comma-separated-list-of-component-values
1278
template<typename T>
1279
Vector<Vector<ComponentValue>> Parser::parse_a_comma_separated_list_of_component_values(TokenStream<T>& input)
1280
0
{
1281
    // To parse a comma-separated list of component values from input:
1282
1283
    // 1. Normalize input, and set input to the result.
1284
    // Note: This is done when initializing the Parser.
1285
1286
    // 2. Let groups be an empty list.
1287
0
    Vector<Vector<ComponentValue>> groups;
1288
1289
    // 3. While input is not empty:
1290
0
    while (!input.is_empty()) {
1291
1292
        // 1. Consume a list of component values from input, with <comma-token> as the stop token, and append the result to groups.
1293
0
        groups.append(consume_a_list_of_component_values(input, Token::Type::Comma));
1294
1295
        // 2. Discard a token from input.
1296
0
        input.discard_a_token();
1297
0
    }
1298
1299
    // 4. Return groups.
1300
0
    return groups;
1301
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>&)
1302
template Vector<Vector<ComponentValue>> Parser::parse_a_comma_separated_list_of_component_values(TokenStream<ComponentValue>&);
1303
template Vector<Vector<ComponentValue>> Parser::parse_a_comma_separated_list_of_component_values(TokenStream<Token>&);
1304
1305
ElementInlineCSSStyleDeclaration* Parser::parse_as_style_attribute(DOM::Element& element)
1306
0
{
1307
0
    auto declarations_and_at_rules = parse_a_blocks_contents(m_token_stream);
1308
0
    auto [properties, custom_properties] = extract_properties(declarations_and_at_rules);
1309
0
    return ElementInlineCSSStyleDeclaration::create(element, move(properties), move(custom_properties));
1310
0
}
1311
1312
Optional<URL::URL> Parser::parse_url_function(TokenStream<ComponentValue>& tokens)
1313
0
{
1314
0
    auto transaction = tokens.begin_transaction();
1315
0
    auto& component_value = tokens.consume_a_token();
1316
1317
0
    auto convert_string_to_url = [&](StringView url_string) -> Optional<URL::URL> {
1318
0
        auto url = m_context.complete_url(url_string);
1319
0
        if (url.is_valid()) {
1320
0
            transaction.commit();
1321
0
            return url;
1322
0
        }
1323
0
        return {};
1324
0
    };
1325
1326
0
    if (component_value.is(Token::Type::Url)) {
1327
0
        auto url_string = component_value.token().url();
1328
0
        return convert_string_to_url(url_string);
1329
0
    }
1330
0
    if (component_value.is_function("url"sv)) {
1331
0
        auto const& function_values = component_value.function().value;
1332
        // FIXME: Handle url-modifiers. https://www.w3.org/TR/css-values-4/#url-modifiers
1333
0
        for (size_t i = 0; i < function_values.size(); ++i) {
1334
0
            auto const& value = function_values[i];
1335
0
            if (value.is(Token::Type::Whitespace))
1336
0
                continue;
1337
0
            if (value.is(Token::Type::String)) {
1338
0
                auto url_string = value.token().string();
1339
0
                return convert_string_to_url(url_string);
1340
0
            }
1341
0
            break;
1342
0
        }
1343
0
    }
1344
1345
0
    return {};
1346
0
}
1347
1348
RefPtr<CSSStyleValue> Parser::parse_url_value(TokenStream<ComponentValue>& tokens)
1349
0
{
1350
0
    auto url = parse_url_function(tokens);
1351
0
    if (!url.has_value())
1352
0
        return nullptr;
1353
0
    return URLStyleValue::create(*url);
1354
0
}
1355
1356
RefPtr<CSSStyleValue> Parser::parse_basic_shape_value(TokenStream<ComponentValue>& tokens)
1357
0
{
1358
0
    auto transaction = tokens.begin_transaction();
1359
0
    auto& component_value = tokens.consume_a_token();
1360
0
    if (!component_value.is_function())
1361
0
        return nullptr;
1362
1363
0
    auto function_name = component_value.function().name.bytes_as_string_view();
1364
1365
    // FIXME: Implement other shapes. See: https://www.w3.org/TR/css-shapes-1/#basic-shape-functions
1366
0
    if (!function_name.equals_ignoring_ascii_case("polygon"sv))
1367
0
        return nullptr;
1368
1369
    // polygon() = polygon( <'fill-rule'>? , [<length-percentage> <length-percentage>]# )
1370
    // FIXME: Parse the fill-rule.
1371
0
    auto arguments_tokens = TokenStream { component_value.function().value };
1372
0
    auto arguments = parse_a_comma_separated_list_of_component_values(arguments_tokens);
1373
1374
0
    Vector<Polygon::Point> points;
1375
0
    for (auto& argument : arguments) {
1376
0
        TokenStream argument_tokens { argument };
1377
1378
0
        argument_tokens.discard_whitespace();
1379
0
        auto x_pos = parse_length_percentage(argument_tokens);
1380
0
        if (!x_pos.has_value())
1381
0
            return nullptr;
1382
1383
0
        argument_tokens.discard_whitespace();
1384
0
        auto y_pos = parse_length_percentage(argument_tokens);
1385
0
        if (!y_pos.has_value())
1386
0
            return nullptr;
1387
1388
0
        argument_tokens.discard_whitespace();
1389
0
        if (argument_tokens.has_next_token())
1390
0
            return nullptr;
1391
1392
0
        points.append(Polygon::Point { *x_pos, *y_pos });
1393
0
    }
1394
1395
0
    transaction.commit();
1396
0
    return BasicShapeStyleValue::create(Polygon { FillRule::Nonzero, move(points) });
1397
0
}
1398
1399
Optional<FlyString> Parser::parse_layer_name(TokenStream<ComponentValue>& tokens, AllowBlankLayerName allow_blank_layer_name)
1400
0
{
1401
    // https://drafts.csswg.org/css-cascade-5/#typedef-layer-name
1402
    // <layer-name> = <ident> [ '.' <ident> ]*
1403
1404
    // "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>."
1405
0
    auto is_valid_layer_name_part = [](auto& token) {
1406
0
        return token.is(Token::Type::Ident) && !is_css_wide_keyword(token.token().ident());
1407
0
    };
1408
1409
0
    auto transaction = tokens.begin_transaction();
1410
0
    tokens.discard_whitespace();
1411
0
    if (!tokens.has_next_token() && allow_blank_layer_name == AllowBlankLayerName::Yes) {
1412
        // No name present, just return a blank one
1413
0
        return FlyString();
1414
0
    }
1415
1416
0
    auto& first_name_token = tokens.consume_a_token();
1417
0
    if (!is_valid_layer_name_part(first_name_token))
1418
0
        return {};
1419
1420
0
    StringBuilder builder;
1421
0
    builder.append(first_name_token.token().ident());
1422
1423
0
    while (tokens.has_next_token()) {
1424
        // Repeatedly parse `'.' <ident>`
1425
0
        if (!tokens.next_token().is_delim('.'))
1426
0
            break;
1427
0
        tokens.discard_a_token(); // '.'
1428
1429
0
        auto& name_token = tokens.consume_a_token();
1430
0
        if (!is_valid_layer_name_part(name_token))
1431
0
            return {};
1432
0
        builder.appendff(".{}", name_token.token().ident());
1433
0
    }
1434
1435
0
    transaction.commit();
1436
0
    return builder.to_fly_string_without_validation();
1437
0
}
1438
1439
bool Parser::is_valid_in_the_current_context(Declaration&)
1440
0
{
1441
    // FIXME: Implement this check
1442
0
    return true;
1443
0
}
1444
1445
bool Parser::is_valid_in_the_current_context(AtRule&)
1446
0
{
1447
    // FIXME: Implement this check
1448
0
    return true;
1449
0
}
1450
1451
bool Parser::is_valid_in_the_current_context(QualifiedRule&)
1452
0
{
1453
    // FIXME: Implement this check
1454
0
    return true;
1455
0
}
1456
1457
JS::GCPtr<CSSRule> Parser::convert_to_rule(Rule const& rule, Nested nested)
1458
0
{
1459
0
    return rule.visit(
1460
0
        [this, nested](AtRule const& at_rule) -> JS::GCPtr<CSSRule> {
1461
0
            if (has_ignored_vendor_prefix(at_rule.name))
1462
0
                return {};
1463
1464
0
            if (at_rule.name.equals_ignoring_ascii_case("font-face"sv))
1465
0
                return convert_to_font_face_rule(at_rule);
1466
1467
0
            if (at_rule.name.equals_ignoring_ascii_case("import"sv))
1468
0
                return convert_to_import_rule(at_rule);
1469
1470
0
            if (at_rule.name.equals_ignoring_ascii_case("keyframes"sv))
1471
0
                return convert_to_keyframes_rule(at_rule);
1472
1473
0
            if (at_rule.name.equals_ignoring_ascii_case("layer"sv))
1474
0
                return convert_to_layer_rule(at_rule, nested);
1475
1476
0
            if (at_rule.name.equals_ignoring_ascii_case("media"sv))
1477
0
                return convert_to_media_rule(at_rule, nested);
1478
1479
0
            if (at_rule.name.equals_ignoring_ascii_case("namespace"sv))
1480
0
                return convert_to_namespace_rule(at_rule);
1481
1482
0
            if (at_rule.name.equals_ignoring_ascii_case("supports"sv))
1483
0
                return convert_to_supports_rule(at_rule, nested);
1484
1485
            // FIXME: More at rules!
1486
0
            dbgln_if(CSS_PARSER_DEBUG, "Unrecognized CSS at-rule: @{}", at_rule.name);
1487
0
            return {};
1488
0
        },
1489
0
        [this, nested](QualifiedRule const& qualified_rule) -> JS::GCPtr<CSSRule> {
1490
0
            return convert_to_style_rule(qualified_rule, nested);
1491
0
        });
1492
0
}
1493
1494
JS::GCPtr<CSSStyleRule> Parser::convert_to_style_rule(QualifiedRule const& qualified_rule, Nested nested)
1495
0
{
1496
0
    TokenStream prelude_stream { qualified_rule.prelude };
1497
1498
0
    auto maybe_selectors = parse_a_selector_list(prelude_stream,
1499
0
        nested == Nested::Yes ? SelectorType::Relative : SelectorType::Standalone);
1500
1501
0
    if (maybe_selectors.is_error()) {
1502
0
        if (maybe_selectors.error() == ParseError::SyntaxError) {
1503
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule selectors invalid; discarding.");
1504
            if constexpr (CSS_PARSER_DEBUG) {
1505
                prelude_stream.dump_all_tokens();
1506
            }
1507
0
        }
1508
0
        return {};
1509
0
    }
1510
1511
0
    if (maybe_selectors.value().is_empty()) {
1512
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: empty selector; discarding.");
1513
0
        return {};
1514
0
    }
1515
1516
0
    SelectorList selectors = maybe_selectors.release_value();
1517
0
    if (nested == Nested::Yes) {
1518
        // "Nested style rules differ from non-nested rules in the following ways:
1519
        // - A nested style rule accepts a <relative-selector-list> as its prelude (rather than just a <selector-list>).
1520
        //   Any relative selectors are relative to the elements represented by the nesting selector.
1521
        // - If a selector in the <relative-selector-list> does not start with a combinator but does contain the nesting
1522
        //   selector, it is interpreted as a non-relative selector."
1523
        // https://drafts.csswg.org/css-nesting-1/#syntax
1524
        // NOTE: We already parsed the selectors as a <relative-selector-list>
1525
1526
        // Nested relative selectors get a `&` inserted at the beginning.
1527
        // This is, handily, how the spec wants them serialized:
1528
        // "When serializing a relative selector in a nested style rule, the selector must be absolutized,
1529
        // with the implied nesting selector inserted."
1530
        // - https://drafts.csswg.org/css-nesting-1/#cssom
1531
1532
0
        SelectorList new_list;
1533
0
        new_list.ensure_capacity(selectors.size());
1534
0
        for (auto const& selector : selectors) {
1535
0
            auto first_combinator = selector->compound_selectors().first().combinator;
1536
0
            if (!first_is_one_of(first_combinator, Selector::Combinator::None, Selector::Combinator::Descendant)
1537
0
                || !selector->contains_the_nesting_selector()) {
1538
0
                new_list.append(selector->relative_to(Selector::SimpleSelector { .type = Selector::SimpleSelector::Type::Nesting }));
1539
0
            } else if (first_combinator == Selector::Combinator::Descendant) {
1540
                // Replace leading descendant combinator (whitespace) with none, because we're not actually relative.
1541
0
                auto copied_compound_selectors = selector->compound_selectors();
1542
0
                copied_compound_selectors.first().combinator = Selector::Combinator::None;
1543
0
                new_list.append(Selector::create(move(copied_compound_selectors)));
1544
0
            } else {
1545
0
                new_list.append(selector);
1546
0
            }
1547
0
        }
1548
0
        selectors = move(new_list);
1549
0
    }
1550
1551
0
    auto* declaration = convert_to_style_declaration(qualified_rule.declarations);
1552
0
    if (!declaration) {
1553
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule declaration invalid; discarding.");
1554
0
        return {};
1555
0
    }
1556
1557
0
    JS::MarkedVector<CSSRule*> child_rules { m_context.realm().heap() };
1558
0
    for (auto& child : qualified_rule.child_rules) {
1559
0
        child.visit(
1560
0
            [&](Rule const& rule) {
1561
                // "In addition to nested style rules, this specification allows nested group rules inside of style rules:
1562
                // any at-rule whose body contains style rules can be nested inside of a style rule as well."
1563
                // https://drafts.csswg.org/css-nesting-1/#nested-group-rules
1564
0
                if (auto converted_rule = convert_to_rule(rule, Nested::Yes)) {
1565
0
                    if (is<CSSGroupingRule>(*converted_rule)) {
1566
0
                        child_rules.append(converted_rule);
1567
0
                    } else {
1568
0
                        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: nested {} is not allowed inside style rule; discarding.", converted_rule->class_name());
1569
0
                    }
1570
0
                }
1571
0
            },
1572
0
            [&](Vector<Declaration> const& declarations) {
1573
0
                auto* declaration = convert_to_style_declaration(declarations);
1574
0
                if (!declaration) {
1575
0
                    dbgln_if(CSS_PARSER_DEBUG, "CSSParser: nested declarations invalid; discarding.");
1576
0
                    return;
1577
0
                }
1578
0
                child_rules.append(CSSNestedDeclarations::create(m_context.realm(), *declaration));
1579
0
            });
1580
0
    }
1581
0
    auto nested_rules = CSSRuleList::create(m_context.realm(), move(child_rules));
1582
0
    return CSSStyleRule::create(m_context.realm(), move(selectors), *declaration, *nested_rules);
1583
0
}
1584
1585
JS::GCPtr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
1586
0
{
1587
    // https://drafts.csswg.org/css-cascade-5/#at-import
1588
    // @import [ <url> | <string> ]
1589
    //         [ layer | layer(<layer-name>) ]?
1590
    //         <import-conditions> ;
1591
    //
1592
    // <import-conditions> = [ supports( [ <supports-condition> | <declaration> ] ) ]?
1593
    //                      <media-query-list>?
1594
1595
0
    if (rule.prelude.is_empty()) {
1596
0
        dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Empty prelude.");
1597
0
        return {};
1598
0
    }
1599
1600
0
    if (!rule.child_rules_and_lists_of_declarations.is_empty()) {
1601
0
        dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Block is not allowed.");
1602
0
        return {};
1603
0
    }
1604
1605
0
    TokenStream tokens { rule.prelude };
1606
0
    tokens.discard_whitespace();
1607
1608
0
    Optional<URL::URL> url = parse_url_function(tokens);
1609
0
    if (!url.has_value() && tokens.next_token().is(Token::Type::String))
1610
0
        url = m_context.complete_url(tokens.consume_a_token().token().string());
1611
1612
0
    if (!url.has_value()) {
1613
0
        dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Unable to parse `{}` as URL.", tokens.next_token().to_debug_string());
1614
0
        return {};
1615
0
    }
1616
1617
0
    tokens.discard_whitespace();
1618
    // TODO: Support layers and import-conditions
1619
0
    if (tokens.has_next_token()) {
1620
        if constexpr (CSS_PARSER_DEBUG) {
1621
            dbgln("Failed to parse @import rule: Trailing tokens after URL are not yet supported.");
1622
            tokens.dump_all_tokens();
1623
        }
1624
0
        return {};
1625
0
    }
1626
1627
0
    return CSSImportRule::create(url.value(), const_cast<DOM::Document&>(*m_context.document()));
1628
0
}
1629
1630
JS::GCPtr<CSSRule> Parser::convert_to_layer_rule(AtRule const& rule, Nested nested)
1631
0
{
1632
    // https://drafts.csswg.org/css-cascade-5/#at-layer
1633
0
    if (!rule.child_rules_and_lists_of_declarations.is_empty()) {
1634
        // CSSLayerBlockRule
1635
        // @layer <layer-name>? {
1636
        //   <rule-list>
1637
        // }
1638
1639
        // First, the name
1640
0
        FlyString layer_name = {};
1641
0
        auto prelude_tokens = TokenStream { rule.prelude };
1642
0
        if (auto maybe_name = parse_layer_name(prelude_tokens, AllowBlankLayerName::Yes); maybe_name.has_value()) {
1643
0
            layer_name = maybe_name.release_value();
1644
0
        } else {
1645
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer has invalid prelude, (not a valid layer name) prelude = {}; discarding.", rule.prelude);
1646
0
            return {};
1647
0
        }
1648
1649
0
        prelude_tokens.discard_whitespace();
1650
0
        if (prelude_tokens.has_next_token()) {
1651
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer has invalid prelude, (tokens after layer name) prelude = {}; discarding.", rule.prelude);
1652
0
            return {};
1653
0
        }
1654
1655
        // Then the rules
1656
0
        JS::MarkedVector<CSSRule*> child_rules { m_context.realm().heap() };
1657
0
        rule.for_each_as_rule_list([&](auto& rule) {
1658
0
            if (auto child_rule = convert_to_rule(rule, nested))
1659
0
                child_rules.append(child_rule);
1660
0
        });
1661
0
        auto rule_list = CSSRuleList::create(m_context.realm(), child_rules);
1662
0
        return CSSLayerBlockRule::create(m_context.realm(), layer_name, rule_list);
1663
0
    }
1664
1665
    // CSSLayerStatementRule
1666
    // @layer <layer-name>#;
1667
0
    auto tokens = TokenStream { rule.prelude };
1668
0
    tokens.discard_whitespace();
1669
0
    Vector<FlyString> layer_names;
1670
0
    while (tokens.has_next_token()) {
1671
        // Comma
1672
0
        if (!layer_names.is_empty()) {
1673
0
            if (auto comma = tokens.consume_a_token(); !comma.is(Token::Type::Comma)) {
1674
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer missing separating comma, ({}) prelude = {}; discarding.", comma.to_debug_string(), rule.prelude);
1675
0
                return {};
1676
0
            }
1677
0
            tokens.discard_whitespace();
1678
0
        }
1679
1680
0
        if (auto name = parse_layer_name(tokens, AllowBlankLayerName::No); name.has_value()) {
1681
0
            layer_names.append(name.release_value());
1682
0
        } else {
1683
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer contains invalid name, prelude = {}; discarding.", rule.prelude);
1684
0
            return {};
1685
0
        }
1686
0
        tokens.discard_whitespace();
1687
0
    }
1688
1689
0
    if (layer_names.is_empty()) {
1690
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer statement has no layer names, prelude = {}; discarding.", rule.prelude);
1691
0
        return {};
1692
0
    }
1693
1694
0
    return CSSLayerStatementRule::create(m_context.realm(), move(layer_names));
1695
0
}
1696
1697
JS::GCPtr<CSSKeyframesRule> Parser::convert_to_keyframes_rule(AtRule const& rule)
1698
0
{
1699
    // https://drafts.csswg.org/css-animations/#keyframes
1700
    // @keyframes = @keyframes <keyframes-name> { <qualified-rule-list> }
1701
    // <keyframes-name> = <custom-ident> | <string>
1702
    // <keyframe-block> = <keyframe-selector># { <declaration-list> }
1703
    // <keyframe-selector> = from | to | <percentage [0,100]>
1704
1705
0
    if (rule.prelude.is_empty()) {
1706
0
        dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @keyframes rule: Empty prelude.");
1707
0
        return {};
1708
0
    }
1709
1710
    // FIXME: Is there some way of detecting if there is a block or not?
1711
1712
0
    auto prelude_stream = TokenStream { rule.prelude };
1713
0
    prelude_stream.discard_whitespace();
1714
0
    auto& token = prelude_stream.consume_a_token();
1715
0
    if (!token.is_token()) {
1716
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes has invalid prelude, prelude = {}; discarding.", rule.prelude);
1717
0
        return {};
1718
0
    }
1719
1720
0
    auto name_token = token.token();
1721
0
    prelude_stream.discard_whitespace();
1722
1723
0
    if (prelude_stream.has_next_token()) {
1724
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes has invalid prelude, prelude = {}; discarding.", rule.prelude);
1725
0
        return {};
1726
0
    }
1727
1728
0
    if (name_token.is(Token::Type::Ident) && (is_css_wide_keyword(name_token.ident()) || name_token.ident().equals_ignoring_ascii_case("none"sv))) {
1729
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes rule name is invalid: {}; discarding.", name_token.ident());
1730
0
        return {};
1731
0
    }
1732
1733
0
    if (!name_token.is(Token::Type::String) && !name_token.is(Token::Type::Ident)) {
1734
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes rule name is invalid: {}; discarding.", name_token.to_debug_string());
1735
0
        return {};
1736
0
    }
1737
1738
0
    auto name = name_token.to_string();
1739
1740
0
    JS::MarkedVector<CSSRule*> keyframes(m_context.realm().heap());
1741
0
    rule.for_each_as_qualified_rule_list([&](auto& qualified_rule) {
1742
0
        if (!qualified_rule.child_rules.is_empty()) {
1743
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes keyframe rule contains at-rules; discarding them.");
1744
0
        }
1745
1746
0
        auto selectors = Vector<CSS::Percentage> {};
1747
0
        TokenStream child_tokens { qualified_rule.prelude };
1748
0
        while (child_tokens.has_next_token()) {
1749
0
            child_tokens.discard_whitespace();
1750
0
            if (!child_tokens.has_next_token())
1751
0
                break;
1752
0
            auto tok = child_tokens.consume_a_token();
1753
0
            if (!tok.is_token()) {
1754
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes rule has invalid selector: {}; discarding.", tok.to_debug_string());
1755
0
                child_tokens.reconsume_current_input_token();
1756
0
                break;
1757
0
            }
1758
0
            auto token = tok.token();
1759
0
            auto read_a_selector = false;
1760
0
            if (token.is(Token::Type::Ident)) {
1761
0
                if (token.ident().equals_ignoring_ascii_case("from"sv)) {
1762
0
                    selectors.append(CSS::Percentage(0));
1763
0
                    read_a_selector = true;
1764
0
                }
1765
0
                if (token.ident().equals_ignoring_ascii_case("to"sv)) {
1766
0
                    selectors.append(CSS::Percentage(100));
1767
0
                    read_a_selector = true;
1768
0
                }
1769
0
            } else if (token.is(Token::Type::Percentage)) {
1770
0
                selectors.append(CSS::Percentage(token.percentage()));
1771
0
                read_a_selector = true;
1772
0
            }
1773
1774
0
            if (read_a_selector) {
1775
0
                child_tokens.discard_whitespace();
1776
0
                if (child_tokens.consume_a_token().is(Token::Type::Comma))
1777
0
                    continue;
1778
0
            }
1779
1780
0
            child_tokens.reconsume_current_input_token();
1781
0
            break;
1782
0
        }
1783
1784
0
        PropertiesAndCustomProperties properties;
1785
0
        qualified_rule.for_each_as_declaration_list([&](auto const& declaration) {
1786
0
            extract_property(declaration, properties);
1787
0
        });
1788
0
        auto style = PropertyOwningCSSStyleDeclaration::create(m_context.realm(), move(properties.properties), move(properties.custom_properties));
1789
0
        for (auto& selector : selectors) {
1790
0
            auto keyframe_rule = CSSKeyframeRule::create(m_context.realm(), selector, *style);
1791
0
            keyframes.append(keyframe_rule);
1792
0
        }
1793
0
    });
1794
1795
0
    return CSSKeyframesRule::create(m_context.realm(), name, CSSRuleList::create(m_context.realm(), move(keyframes)));
1796
0
}
1797
1798
JS::GCPtr<CSSNamespaceRule> Parser::convert_to_namespace_rule(AtRule const& rule)
1799
0
{
1800
    // https://drafts.csswg.org/css-namespaces/#syntax
1801
    // @namespace <namespace-prefix>? [ <string> | <url> ] ;
1802
    // <namespace-prefix> = <ident>
1803
1804
0
    if (rule.prelude.is_empty()) {
1805
0
        dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @namespace rule: Empty prelude.");
1806
0
        return {};
1807
0
    }
1808
1809
0
    if (!rule.child_rules_and_lists_of_declarations.is_empty()) {
1810
0
        dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @namespace rule: Block is not allowed.");
1811
0
        return {};
1812
0
    }
1813
1814
0
    auto tokens = TokenStream { rule.prelude };
1815
0
    tokens.discard_whitespace();
1816
1817
0
    Optional<FlyString> prefix = {};
1818
0
    if (tokens.next_token().is(Token::Type::Ident)) {
1819
0
        prefix = tokens.consume_a_token().token().ident();
1820
0
        tokens.discard_whitespace();
1821
0
    }
1822
1823
0
    FlyString namespace_uri;
1824
0
    if (auto url = parse_url_function(tokens); url.has_value()) {
1825
0
        namespace_uri = MUST(url.value().to_string());
1826
0
    } else if (auto& url_token = tokens.consume_a_token(); url_token.is(Token::Type::String)) {
1827
0
        namespace_uri = url_token.token().string();
1828
0
    } else {
1829
0
        dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @namespace rule: Unable to parse `{}` as URL.", tokens.next_token().to_debug_string());
1830
0
        return {};
1831
0
    }
1832
1833
0
    tokens.discard_whitespace();
1834
0
    if (tokens.has_next_token()) {
1835
        if constexpr (CSS_PARSER_DEBUG) {
1836
            dbgln("Failed to parse @namespace rule: Trailing tokens after URL.");
1837
            tokens.dump_all_tokens();
1838
        }
1839
0
        return {};
1840
0
    }
1841
1842
0
    return CSSNamespaceRule::create(m_context.realm(), prefix, namespace_uri);
1843
0
}
1844
1845
JS::GCPtr<CSSSupportsRule> Parser::convert_to_supports_rule(AtRule const& rule, Nested nested)
1846
0
{
1847
    // https://drafts.csswg.org/css-conditional-3/#at-supports
1848
    // @supports <supports-condition> {
1849
    //   <rule-list>
1850
    // }
1851
1852
0
    if (rule.prelude.is_empty()) {
1853
0
        dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @supports rule: Empty prelude.");
1854
0
        return {};
1855
0
    }
1856
1857
0
    auto supports_tokens = TokenStream { rule.prelude };
1858
0
    auto supports = parse_a_supports(supports_tokens);
1859
0
    if (!supports) {
1860
        if constexpr (CSS_PARSER_DEBUG) {
1861
            dbgln("Failed to parse @supports rule: supports clause invalid.");
1862
            supports_tokens.dump_all_tokens();
1863
        }
1864
0
        return {};
1865
0
    }
1866
1867
0
    JS::MarkedVector<CSSRule*> child_rules { m_context.realm().heap() };
1868
0
    rule.for_each_as_rule_list([&](auto& rule) {
1869
0
        if (auto child_rule = convert_to_rule(rule, nested))
1870
0
            child_rules.append(child_rule);
1871
0
    });
1872
1873
0
    auto rule_list = CSSRuleList::create(m_context.realm(), child_rules);
1874
0
    return CSSSupportsRule::create(m_context.realm(), supports.release_nonnull(), rule_list);
1875
0
}
1876
1877
Parser::PropertiesAndCustomProperties Parser::extract_properties(Vector<RuleOrListOfDeclarations> const& rules_and_lists_of_declarations)
1878
0
{
1879
0
    PropertiesAndCustomProperties result;
1880
0
    for (auto const& rule_or_list : rules_and_lists_of_declarations) {
1881
0
        if (rule_or_list.has<Rule>())
1882
0
            continue;
1883
1884
0
        auto& declarations = rule_or_list.get<Vector<Declaration>>();
1885
0
        PropertiesAndCustomProperties& dest = result;
1886
0
        for (auto const& declaration : declarations) {
1887
0
            extract_property(declaration, dest);
1888
0
        }
1889
0
    }
1890
0
    return result;
1891
0
}
1892
1893
void Parser::extract_property(Declaration const& declaration, PropertiesAndCustomProperties& dest)
1894
0
{
1895
0
    if (auto maybe_property = convert_to_style_property(declaration); maybe_property.has_value()) {
1896
0
        auto property = maybe_property.release_value();
1897
0
        if (property.property_id == PropertyID::Custom) {
1898
0
            dest.custom_properties.set(property.custom_name, property);
1899
0
        } else {
1900
0
            dest.properties.append(move(property));
1901
0
        }
1902
0
    }
1903
0
}
1904
1905
PropertyOwningCSSStyleDeclaration* Parser::convert_to_style_declaration(Vector<Declaration> const& declarations)
1906
0
{
1907
0
    PropertiesAndCustomProperties properties;
1908
0
    PropertiesAndCustomProperties& dest = properties;
1909
0
    for (auto const& declaration : declarations) {
1910
0
        extract_property(declaration, dest);
1911
0
    }
1912
0
    return PropertyOwningCSSStyleDeclaration::create(m_context.realm(), move(properties.properties), move(properties.custom_properties));
1913
0
}
1914
1915
Optional<StyleProperty> Parser::convert_to_style_property(Declaration const& declaration)
1916
0
{
1917
0
    auto const& property_name = declaration.name;
1918
0
    auto property_id = property_id_from_string(property_name);
1919
1920
0
    if (!property_id.has_value()) {
1921
0
        if (property_name.bytes_as_string_view().starts_with("--"sv)) {
1922
0
            property_id = PropertyID::Custom;
1923
0
        } else if (has_ignored_vendor_prefix(property_name)) {
1924
0
            return {};
1925
0
        } else if (!property_name.bytes_as_string_view().starts_with('-')) {
1926
0
            dbgln_if(CSS_PARSER_DEBUG, "Unrecognized CSS property '{}'", property_name);
1927
0
            return {};
1928
0
        }
1929
0
    }
1930
1931
0
    auto value_token_stream = TokenStream(declaration.value);
1932
0
    auto value = parse_css_value(property_id.value(), value_token_stream, declaration.original_text);
1933
0
    if (value.is_error()) {
1934
0
        if (value.error() == ParseError::SyntaxError) {
1935
0
            dbgln_if(CSS_PARSER_DEBUG, "Unable to parse value for CSS property '{}'.", property_name);
1936
            if constexpr (CSS_PARSER_DEBUG) {
1937
                value_token_stream.dump_all_tokens();
1938
            }
1939
0
        }
1940
0
        return {};
1941
0
    }
1942
1943
0
    if (property_id.value() == PropertyID::Custom)
1944
0
        return StyleProperty { declaration.important, property_id.value(), value.release_value(), declaration.name };
1945
1946
0
    return StyleProperty { declaration.important, property_id.value(), value.release_value(), {} };
1947
0
}
1948
1949
RefPtr<CSSStyleValue> Parser::parse_builtin_value(TokenStream<ComponentValue>& tokens)
1950
0
{
1951
0
    auto transaction = tokens.begin_transaction();
1952
0
    auto& component_value = tokens.consume_a_token();
1953
0
    if (component_value.is(Token::Type::Ident)) {
1954
0
        auto ident = component_value.token().ident();
1955
0
        if (ident.equals_ignoring_ascii_case("inherit"sv)) {
1956
0
            transaction.commit();
1957
0
            return CSSKeywordValue::create(Keyword::Inherit);
1958
0
        }
1959
0
        if (ident.equals_ignoring_ascii_case("initial"sv)) {
1960
0
            transaction.commit();
1961
0
            return CSSKeywordValue::create(Keyword::Initial);
1962
0
        }
1963
0
        if (ident.equals_ignoring_ascii_case("unset"sv)) {
1964
0
            transaction.commit();
1965
0
            return CSSKeywordValue::create(Keyword::Unset);
1966
0
        }
1967
0
        if (ident.equals_ignoring_ascii_case("revert"sv)) {
1968
0
            transaction.commit();
1969
0
            return CSSKeywordValue::create(Keyword::Revert);
1970
0
        }
1971
0
        if (ident.equals_ignoring_ascii_case("revert-layer"sv)) {
1972
0
            transaction.commit();
1973
0
            return CSSKeywordValue::create(Keyword::RevertLayer);
1974
0
        }
1975
0
    }
1976
1977
0
    return nullptr;
1978
0
}
1979
1980
// https://www.w3.org/TR/css-values-4/#custom-idents
1981
RefPtr<CustomIdentStyleValue> Parser::parse_custom_ident_value(TokenStream<ComponentValue>& tokens, std::initializer_list<StringView> blacklist)
1982
0
{
1983
0
    auto transaction = tokens.begin_transaction();
1984
0
    tokens.discard_whitespace();
1985
1986
0
    auto token = tokens.consume_a_token();
1987
0
    if (!token.is(Token::Type::Ident))
1988
0
        return nullptr;
1989
0
    auto custom_ident = token.token().ident();
1990
1991
    // The CSS-wide keywords are not valid <custom-ident>s.
1992
0
    if (is_css_wide_keyword(custom_ident))
1993
0
        return nullptr;
1994
1995
    // The default keyword is reserved and is also not a valid <custom-ident>.
1996
0
    if (custom_ident.equals_ignoring_ascii_case("default"sv))
1997
0
        return nullptr;
1998
1999
    // Specifications using <custom-ident> must specify clearly what other keywords are excluded from <custom-ident>,
2000
    // if any—for example by saying that any pre-defined keywords in that property’s value definition are excluded.
2001
    // Excluded keywords are excluded in all ASCII case permutations.
2002
0
    for (auto& value : blacklist) {
2003
0
        if (custom_ident.equals_ignoring_ascii_case(value))
2004
0
            return nullptr;
2005
0
    }
2006
2007
0
    transaction.commit();
2008
0
    return CustomIdentStyleValue::create(custom_ident);
2009
0
}
2010
2011
RefPtr<CSSMathValue> Parser::parse_calculated_value(ComponentValue const& component_value)
2012
0
{
2013
0
    if (!component_value.is_function())
2014
0
        return nullptr;
2015
2016
0
    auto const& function = component_value.function();
2017
2018
0
    auto function_node = parse_a_calc_function_node(function);
2019
0
    if (!function_node)
2020
0
        return nullptr;
2021
2022
0
    auto function_type = function_node->determine_type(m_context.current_property_id());
2023
0
    if (!function_type.has_value())
2024
0
        return nullptr;
2025
2026
0
    return CSSMathValue::create(function_node.release_nonnull(), function_type.release_value());
2027
0
}
2028
2029
OwnPtr<CalculationNode> Parser::parse_a_calc_function_node(Function const& function)
2030
0
{
2031
0
    if (function.name.equals_ignoring_ascii_case("calc"sv))
2032
0
        return parse_a_calculation(function.value);
2033
2034
0
    if (auto maybe_function = parse_math_function(m_context.current_property_id(), function))
2035
0
        return maybe_function;
2036
2037
0
    return nullptr;
2038
0
}
2039
2040
Optional<Dimension> Parser::parse_dimension(ComponentValue const& component_value)
2041
0
{
2042
0
    if (component_value.is(Token::Type::Dimension)) {
2043
0
        auto numeric_value = component_value.token().dimension_value();
2044
0
        auto unit_string = component_value.token().dimension_unit();
2045
2046
0
        if (auto length_type = Length::unit_from_name(unit_string); length_type.has_value())
2047
0
            return Length { numeric_value, length_type.release_value() };
2048
2049
0
        if (auto angle_type = Angle::unit_from_name(unit_string); angle_type.has_value())
2050
0
            return Angle { numeric_value, angle_type.release_value() };
2051
2052
0
        if (auto flex_type = Flex::unit_from_name(unit_string); flex_type.has_value())
2053
0
            return Flex { numeric_value, flex_type.release_value() };
2054
2055
0
        if (auto frequency_type = Frequency::unit_from_name(unit_string); frequency_type.has_value())
2056
0
            return Frequency { numeric_value, frequency_type.release_value() };
2057
2058
0
        if (auto resolution_type = Resolution::unit_from_name(unit_string); resolution_type.has_value())
2059
0
            return Resolution { numeric_value, resolution_type.release_value() };
2060
2061
0
        if (auto time_type = Time::unit_from_name(unit_string); time_type.has_value())
2062
0
            return Time { numeric_value, time_type.release_value() };
2063
0
    }
2064
2065
0
    if (component_value.is(Token::Type::Percentage))
2066
0
        return Percentage { component_value.token().percentage() };
2067
2068
0
    if (component_value.is(Token::Type::Number)) {
2069
0
        auto numeric_value = component_value.token().number_value();
2070
0
        if (numeric_value == 0)
2071
0
            return Length::make_px(0);
2072
0
        if (m_context.in_quirks_mode() && property_has_quirk(m_context.current_property_id(), Quirk::UnitlessLength)) {
2073
            // https://quirks.spec.whatwg.org/#quirky-length-value
2074
            // FIXME: Disallow quirk when inside a CSS sub-expression (like `calc()`)
2075
            // "The <quirky-length> value must not be supported in arguments to CSS expressions other than the rect()
2076
            // expression, and must not be supported in the supports() static method of the CSS interface."
2077
0
            return Length::make_px(CSSPixels::nearest_value_for(numeric_value));
2078
0
        }
2079
0
    }
2080
2081
0
    return {};
2082
0
}
2083
2084
Optional<AngleOrCalculated> Parser::parse_angle(TokenStream<ComponentValue>& tokens)
2085
0
{
2086
0
    auto transaction = tokens.begin_transaction();
2087
0
    auto& token = tokens.consume_a_token();
2088
2089
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2090
0
        if (dimension->is_angle()) {
2091
0
            transaction.commit();
2092
0
            return dimension->angle();
2093
0
        }
2094
0
        return {};
2095
0
    }
2096
2097
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_angle()) {
2098
0
        transaction.commit();
2099
0
        return calc.release_nonnull();
2100
0
    }
2101
2102
0
    return {};
2103
0
}
2104
2105
Optional<AnglePercentage> Parser::parse_angle_percentage(TokenStream<ComponentValue>& tokens)
2106
0
{
2107
0
    auto transaction = tokens.begin_transaction();
2108
0
    auto& token = tokens.consume_a_token();
2109
2110
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2111
0
        if (dimension->is_angle_percentage()) {
2112
0
            transaction.commit();
2113
0
            return dimension->angle_percentage();
2114
0
        }
2115
0
        return {};
2116
0
    }
2117
2118
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_angle_percentage()) {
2119
0
        transaction.commit();
2120
0
        return calc.release_nonnull();
2121
0
    }
2122
2123
0
    return {};
2124
0
}
2125
2126
Optional<FlexOrCalculated> Parser::parse_flex(TokenStream<ComponentValue>& tokens)
2127
0
{
2128
0
    auto transaction = tokens.begin_transaction();
2129
0
    auto& token = tokens.consume_a_token();
2130
2131
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2132
0
        if (dimension->is_flex()) {
2133
0
            transaction.commit();
2134
0
            return dimension->flex();
2135
0
        }
2136
0
        return {};
2137
0
    }
2138
2139
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_flex()) {
2140
0
        transaction.commit();
2141
0
        return calc.release_nonnull();
2142
0
    }
2143
2144
0
    return {};
2145
0
}
2146
2147
Optional<FrequencyOrCalculated> Parser::parse_frequency(TokenStream<ComponentValue>& tokens)
2148
0
{
2149
0
    auto transaction = tokens.begin_transaction();
2150
0
    auto& token = tokens.consume_a_token();
2151
2152
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2153
0
        if (dimension->is_frequency()) {
2154
0
            transaction.commit();
2155
0
            return dimension->frequency();
2156
0
        }
2157
0
        return {};
2158
0
    }
2159
2160
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_frequency()) {
2161
0
        transaction.commit();
2162
0
        return calc.release_nonnull();
2163
0
    }
2164
2165
0
    return {};
2166
0
}
2167
2168
Optional<FrequencyPercentage> Parser::parse_frequency_percentage(TokenStream<ComponentValue>& tokens)
2169
0
{
2170
0
    auto transaction = tokens.begin_transaction();
2171
0
    auto& token = tokens.consume_a_token();
2172
2173
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2174
0
        if (dimension->is_frequency_percentage()) {
2175
0
            transaction.commit();
2176
0
            return dimension->frequency_percentage();
2177
0
        }
2178
0
        return {};
2179
0
    }
2180
2181
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_frequency_percentage()) {
2182
0
        transaction.commit();
2183
0
        return calc.release_nonnull();
2184
0
    }
2185
2186
0
    return {};
2187
0
}
2188
2189
Optional<IntegerOrCalculated> Parser::parse_integer(TokenStream<ComponentValue>& tokens)
2190
0
{
2191
0
    auto transaction = tokens.begin_transaction();
2192
0
    auto& token = tokens.consume_a_token();
2193
2194
0
    if (token.is(Token::Type::Number) && token.token().number().is_integer()) {
2195
0
        transaction.commit();
2196
0
        return token.token().to_integer();
2197
0
    }
2198
2199
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_number()) {
2200
0
        transaction.commit();
2201
0
        return calc.release_nonnull();
2202
0
    }
2203
2204
0
    return {};
2205
0
}
2206
2207
Optional<LengthOrCalculated> Parser::parse_length(TokenStream<ComponentValue>& tokens)
2208
0
{
2209
0
    auto transaction = tokens.begin_transaction();
2210
0
    auto& token = tokens.consume_a_token();
2211
2212
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2213
0
        if (dimension->is_length()) {
2214
0
            transaction.commit();
2215
0
            return dimension->length();
2216
0
        }
2217
0
        return {};
2218
0
    }
2219
2220
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_length()) {
2221
0
        transaction.commit();
2222
0
        return calc.release_nonnull();
2223
0
    }
2224
2225
0
    return {};
2226
0
}
2227
2228
Optional<LengthPercentage> Parser::parse_length_percentage(TokenStream<ComponentValue>& tokens)
2229
0
{
2230
0
    auto transaction = tokens.begin_transaction();
2231
0
    auto& token = tokens.consume_a_token();
2232
2233
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2234
0
        if (dimension->is_length_percentage()) {
2235
0
            transaction.commit();
2236
0
            return dimension->length_percentage();
2237
0
        }
2238
0
        return {};
2239
0
    }
2240
2241
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_length_percentage()) {
2242
0
        transaction.commit();
2243
0
        return calc.release_nonnull();
2244
0
    }
2245
2246
0
    return {};
2247
0
}
2248
2249
Optional<NumberOrCalculated> Parser::parse_number(TokenStream<ComponentValue>& tokens)
2250
0
{
2251
0
    auto transaction = tokens.begin_transaction();
2252
0
    auto& token = tokens.consume_a_token();
2253
2254
0
    if (token.is(Token::Type::Number)) {
2255
0
        transaction.commit();
2256
0
        return token.token().number_value();
2257
0
    }
2258
2259
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_number()) {
2260
0
        transaction.commit();
2261
0
        return calc.release_nonnull();
2262
0
    }
2263
2264
0
    return {};
2265
0
}
2266
2267
Optional<ResolutionOrCalculated> Parser::parse_resolution(TokenStream<ComponentValue>& tokens)
2268
0
{
2269
0
    auto transaction = tokens.begin_transaction();
2270
0
    auto& token = tokens.consume_a_token();
2271
2272
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2273
0
        if (dimension->is_resolution()) {
2274
0
            transaction.commit();
2275
0
            return dimension->resolution();
2276
0
        }
2277
0
        return {};
2278
0
    }
2279
2280
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_resolution()) {
2281
0
        transaction.commit();
2282
0
        return calc.release_nonnull();
2283
0
    }
2284
2285
0
    return {};
2286
0
}
2287
2288
Optional<TimeOrCalculated> Parser::parse_time(TokenStream<ComponentValue>& tokens)
2289
0
{
2290
0
    auto transaction = tokens.begin_transaction();
2291
0
    auto& token = tokens.consume_a_token();
2292
2293
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2294
0
        if (dimension->is_time()) {
2295
0
            transaction.commit();
2296
0
            return dimension->time();
2297
0
        }
2298
0
        return {};
2299
0
    }
2300
2301
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_time()) {
2302
0
        transaction.commit();
2303
0
        return calc.release_nonnull();
2304
0
    }
2305
2306
0
    return {};
2307
0
}
2308
2309
Optional<TimePercentage> Parser::parse_time_percentage(TokenStream<ComponentValue>& tokens)
2310
0
{
2311
0
    auto transaction = tokens.begin_transaction();
2312
0
    auto& token = tokens.consume_a_token();
2313
2314
0
    if (auto dimension = parse_dimension(token); dimension.has_value()) {
2315
0
        if (dimension->is_time_percentage()) {
2316
0
            transaction.commit();
2317
0
            return dimension->time_percentage();
2318
0
        }
2319
0
        return {};
2320
0
    }
2321
2322
0
    if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_time_percentage()) {
2323
0
        transaction.commit();
2324
0
        return calc.release_nonnull();
2325
0
    }
2326
2327
0
    return {};
2328
0
}
2329
2330
Optional<LengthOrCalculated> Parser::parse_source_size_value(TokenStream<ComponentValue>& tokens)
2331
0
{
2332
0
    if (tokens.next_token().is_ident("auto"sv)) {
2333
0
        tokens.discard_a_token(); // auto
2334
0
        return LengthOrCalculated { Length::make_auto() };
2335
0
    }
2336
2337
0
    return parse_length(tokens);
2338
0
}
2339
2340
Optional<Ratio> Parser::parse_ratio(TokenStream<ComponentValue>& tokens)
2341
0
{
2342
0
    auto transaction = tokens.begin_transaction();
2343
0
    tokens.discard_whitespace();
2344
2345
0
    auto read_number_value = [this](ComponentValue const& component_value) -> Optional<double> {
2346
0
        if (component_value.is(Token::Type::Number)) {
2347
0
            return component_value.token().number_value();
2348
0
        } else if (component_value.is_function()) {
2349
0
            auto maybe_calc = parse_calculated_value(component_value);
2350
0
            if (!maybe_calc || !maybe_calc->resolves_to_number())
2351
0
                return {};
2352
0
            if (auto resolved_number = maybe_calc->resolve_number(); resolved_number.has_value() && resolved_number.value() >= 0) {
2353
0
                return resolved_number.value();
2354
0
            }
2355
0
        }
2356
0
        return {};
2357
0
    };
2358
2359
    // `<ratio> = <number [0,∞]> [ / <number [0,∞]> ]?`
2360
0
    auto maybe_numerator = read_number_value(tokens.consume_a_token());
2361
0
    if (!maybe_numerator.has_value() || maybe_numerator.value() < 0)
2362
0
        return {};
2363
0
    auto numerator = maybe_numerator.value();
2364
2365
0
    {
2366
0
        auto two_value_transaction = tokens.begin_transaction();
2367
0
        tokens.discard_whitespace();
2368
0
        auto solidus = tokens.consume_a_token();
2369
0
        tokens.discard_whitespace();
2370
0
        auto maybe_denominator = read_number_value(tokens.consume_a_token());
2371
2372
0
        if (solidus.is_delim('/') && maybe_denominator.has_value() && maybe_denominator.value() >= 0) {
2373
0
            auto denominator = maybe_denominator.value();
2374
            // Two-value ratio
2375
0
            two_value_transaction.commit();
2376
0
            transaction.commit();
2377
0
            return Ratio { numerator, denominator };
2378
0
        }
2379
0
    }
2380
2381
    // Single-value ratio
2382
0
    transaction.commit();
2383
0
    return Ratio { numerator };
2384
0
}
2385
2386
// https://www.w3.org/TR/css-syntax-3/#urange-syntax
2387
Optional<Gfx::UnicodeRange> Parser::parse_unicode_range(TokenStream<ComponentValue>& tokens)
2388
0
{
2389
0
    auto transaction = tokens.begin_transaction();
2390
0
    tokens.discard_whitespace();
2391
2392
    // <urange> =
2393
    //  u '+' <ident-token> '?'* |
2394
    //  u <dimension-token> '?'* |
2395
    //  u <number-token> '?'* |
2396
    //  u <number-token> <dimension-token> |
2397
    //  u <number-token> <number-token> |
2398
    //  u '+' '?'+
2399
    // (All with no whitespace in between tokens.)
2400
2401
    // NOTE: Parsing this is different from usual. We take these steps:
2402
    // 1. Match the grammar above against the tokens, concatenating them into a string using their original representation.
2403
    // 2. Then, parse that string according to the spec algorithm.
2404
    // Step 2 is performed by calling the other parse_unicode_range() overload.
2405
2406
0
    auto is_ending_token = [](ComponentValue const& component_value) {
2407
0
        return component_value.is(Token::Type::EndOfFile)
2408
0
            || component_value.is(Token::Type::Comma)
2409
0
            || component_value.is(Token::Type::Semicolon)
2410
0
            || component_value.is(Token::Type::Whitespace);
2411
0
    };
2412
2413
0
    auto create_unicode_range = [&](StringView text, auto& local_transaction) -> Optional<Gfx::UnicodeRange> {
2414
0
        auto maybe_unicode_range = parse_unicode_range(text);
2415
0
        if (maybe_unicode_range.has_value()) {
2416
0
            local_transaction.commit();
2417
0
            transaction.commit();
2418
0
        }
2419
0
        return maybe_unicode_range;
2420
0
    };
2421
2422
    // All options start with 'u'/'U'.
2423
0
    auto const& u = tokens.consume_a_token();
2424
0
    if (!u.is_ident("u"sv)) {
2425
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> does not start with 'u'");
2426
0
        return {};
2427
0
    }
2428
2429
0
    auto const& second_token = tokens.consume_a_token();
2430
2431
    //  u '+' <ident-token> '?'* |
2432
    //  u '+' '?'+
2433
0
    if (second_token.is_delim('+')) {
2434
0
        auto local_transaction = tokens.begin_transaction();
2435
0
        StringBuilder string_builder;
2436
0
        string_builder.append(second_token.token().original_source_text());
2437
2438
0
        auto const& third_token = tokens.consume_a_token();
2439
0
        if (third_token.is(Token::Type::Ident) || third_token.is_delim('?')) {
2440
0
            string_builder.append(third_token.token().original_source_text());
2441
0
            while (tokens.next_token().is_delim('?'))
2442
0
                string_builder.append(tokens.consume_a_token().token().original_source_text());
2443
0
            if (is_ending_token(tokens.next_token()))
2444
0
                return create_unicode_range(string_builder.string_view(), local_transaction);
2445
0
        }
2446
0
    }
2447
2448
    //  u <dimension-token> '?'*
2449
0
    if (second_token.is(Token::Type::Dimension)) {
2450
0
        auto local_transaction = tokens.begin_transaction();
2451
0
        StringBuilder string_builder;
2452
0
        string_builder.append(second_token.token().original_source_text());
2453
0
        while (tokens.next_token().is_delim('?'))
2454
0
            string_builder.append(tokens.consume_a_token().token().original_source_text());
2455
0
        if (is_ending_token(tokens.next_token()))
2456
0
            return create_unicode_range(string_builder.string_view(), local_transaction);
2457
0
    }
2458
2459
    //  u <number-token> '?'* |
2460
    //  u <number-token> <dimension-token> |
2461
    //  u <number-token> <number-token>
2462
0
    if (second_token.is(Token::Type::Number)) {
2463
0
        auto local_transaction = tokens.begin_transaction();
2464
0
        StringBuilder string_builder;
2465
0
        string_builder.append(second_token.token().original_source_text());
2466
2467
0
        if (is_ending_token(tokens.next_token()))
2468
0
            return create_unicode_range(string_builder.string_view(), local_transaction);
2469
2470
0
        auto const& third_token = tokens.consume_a_token();
2471
0
        if (third_token.is_delim('?')) {
2472
0
            string_builder.append(third_token.token().original_source_text());
2473
0
            while (tokens.next_token().is_delim('?'))
2474
0
                string_builder.append(tokens.consume_a_token().token().original_source_text());
2475
0
            if (is_ending_token(tokens.next_token()))
2476
0
                return create_unicode_range(string_builder.string_view(), local_transaction);
2477
0
        } else if (third_token.is(Token::Type::Dimension)) {
2478
0
            string_builder.append(third_token.token().original_source_text());
2479
0
            if (is_ending_token(tokens.next_token()))
2480
0
                return create_unicode_range(string_builder.string_view(), local_transaction);
2481
0
        } else if (third_token.is(Token::Type::Number)) {
2482
0
            string_builder.append(third_token.token().original_source_text());
2483
0
            if (is_ending_token(tokens.next_token()))
2484
0
                return create_unicode_range(string_builder.string_view(), local_transaction);
2485
0
        }
2486
0
    }
2487
2488
    if constexpr (CSS_PARSER_DEBUG) {
2489
        dbgln("CSSParser: Tokens did not match <urange> grammar.");
2490
        tokens.dump_all_tokens();
2491
    }
2492
0
    return {};
2493
0
}
2494
2495
Optional<Gfx::UnicodeRange> Parser::parse_unicode_range(StringView text)
2496
0
{
2497
0
    auto make_valid_unicode_range = [&](u32 start_value, u32 end_value) -> Optional<Gfx::UnicodeRange> {
2498
        // https://www.w3.org/TR/css-syntax-3/#maximum-allowed-code-point
2499
0
        constexpr u32 maximum_allowed_code_point = 0x10FFFF;
2500
2501
        // To determine what codepoints the <urange> represents:
2502
        // 1. If end value is greater than the maximum allowed code point,
2503
        //    the <urange> is invalid and a syntax error.
2504
0
        if (end_value > maximum_allowed_code_point) {
2505
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Invalid <urange>: end_value ({}) > maximum ({})", end_value, maximum_allowed_code_point);
2506
0
            return {};
2507
0
        }
2508
2509
        // 2. If start value is greater than end value, the <urange> is invalid and a syntax error.
2510
0
        if (start_value > end_value) {
2511
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Invalid <urange>: start_value ({}) > end_value ({})", start_value, end_value);
2512
0
            return {};
2513
0
        }
2514
2515
        // 3. Otherwise, the <urange> represents a contiguous range of codepoints from start value to end value, inclusive.
2516
0
        return Gfx::UnicodeRange { start_value, end_value };
2517
0
    };
2518
2519
    // 1. Skipping the first u token, concatenate the representations of all the tokens in the production together.
2520
    //    Let this be text.
2521
    // NOTE: The concatenation is already done by the caller.
2522
0
    GenericLexer lexer { text };
2523
2524
    // 2. If the first character of text is U+002B PLUS SIGN, consume it.
2525
    //    Otherwise, this is an invalid <urange>, and this algorithm must exit.
2526
0
    if (lexer.next_is('+')) {
2527
0
        lexer.consume();
2528
0
    } else {
2529
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Second character of <urange> was not '+'; got: '{}'", lexer.consume());
2530
0
        return {};
2531
0
    }
2532
2533
    // 3. Consume as many hex digits from text as possible.
2534
    //    then consume as many U+003F QUESTION MARK (?) code points as possible.
2535
0
    auto start_position = lexer.tell();
2536
0
    auto hex_digits = lexer.consume_while(is_ascii_hex_digit);
2537
0
    auto question_marks = lexer.consume_while([](auto it) { return it == '?'; });
2538
    //    If zero code points were consumed, or more than six code points were consumed,
2539
    //    this is an invalid <urange>, and this algorithm must exit.
2540
0
    size_t consumed_code_points = hex_digits.length() + question_marks.length();
2541
0
    if (consumed_code_points == 0 || consumed_code_points > 6) {
2542
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> start value had {} digits/?s, expected between 1 and 6.", consumed_code_points);
2543
0
        return {};
2544
0
    }
2545
0
    StringView start_value_code_points = text.substring_view(start_position, consumed_code_points);
2546
2547
    //    If any U+003F QUESTION MARK (?) code points were consumed, then:
2548
0
    if (question_marks.length() > 0) {
2549
        // 1. If there are any code points left in text, this is an invalid <urange>,
2550
        //    and this algorithm must exit.
2551
0
        if (lexer.tell_remaining() != 0) {
2552
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> invalid; had {} code points left over.", lexer.tell_remaining());
2553
0
            return {};
2554
0
        }
2555
2556
        // 2. Interpret the consumed code points as a hexadecimal number,
2557
        //    with the U+003F QUESTION MARK (?) code points replaced by U+0030 DIGIT ZERO (0) code points.
2558
        //    This is the start value.
2559
0
        auto start_value_string = start_value_code_points.replace("?"sv, "0"sv, ReplaceMode::All);
2560
0
        auto maybe_start_value = AK::StringUtils::convert_to_uint_from_hex<u32>(start_value_string);
2561
0
        if (!maybe_start_value.has_value()) {
2562
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> ?-converted start value did not parse as hex number.");
2563
0
            return {};
2564
0
        }
2565
0
        u32 start_value = maybe_start_value.release_value();
2566
2567
        // 3. Interpret the consumed code points as a hexadecimal number again,
2568
        //    with the U+003F QUESTION MARK (?) code points replaced by U+0046 LATIN CAPITAL LETTER F (F) code points.
2569
        //    This is the end value.
2570
0
        auto end_value_string = start_value_code_points.replace("?"sv, "F"sv, ReplaceMode::All);
2571
0
        auto maybe_end_value = AK::StringUtils::convert_to_uint_from_hex<u32>(end_value_string);
2572
0
        if (!maybe_end_value.has_value()) {
2573
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> ?-converted end value did not parse as hex number.");
2574
0
            return {};
2575
0
        }
2576
0
        u32 end_value = maybe_end_value.release_value();
2577
2578
        // 4. Exit this algorithm.
2579
0
        return make_valid_unicode_range(start_value, end_value);
2580
0
    }
2581
    //   Otherwise, interpret the consumed code points as a hexadecimal number. This is the start value.
2582
0
    auto maybe_start_value = AK::StringUtils::convert_to_uint_from_hex<u32>(start_value_code_points);
2583
0
    if (!maybe_start_value.has_value()) {
2584
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> start value did not parse as hex number.");
2585
0
        return {};
2586
0
    }
2587
0
    u32 start_value = maybe_start_value.release_value();
2588
2589
    // 4. If there are no code points left in text, The end value is the same as the start value.
2590
    //    Exit this algorithm.
2591
0
    if (lexer.tell_remaining() == 0)
2592
0
        return make_valid_unicode_range(start_value, start_value);
2593
2594
    // 5. If the next code point in text is U+002D HYPHEN-MINUS (-), consume it.
2595
0
    if (lexer.next_is('-')) {
2596
0
        lexer.consume();
2597
0
    }
2598
    //    Otherwise, this is an invalid <urange>, and this algorithm must exit.
2599
0
    else {
2600
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> start and end values not separated by '-'.");
2601
0
        return {};
2602
0
    }
2603
2604
    // 6. Consume as many hex digits as possible from text.
2605
0
    auto end_hex_digits = lexer.consume_while(is_ascii_hex_digit);
2606
2607
    //   If zero hex digits were consumed, or more than 6 hex digits were consumed,
2608
    //   this is an invalid <urange>, and this algorithm must exit.
2609
0
    if (end_hex_digits.length() == 0 || end_hex_digits.length() > 6) {
2610
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> end value had {} digits, expected between 1 and 6.", end_hex_digits.length());
2611
0
        return {};
2612
0
    }
2613
2614
    //   If there are any code points left in text, this is an invalid <urange>, and this algorithm must exit.
2615
0
    if (lexer.tell_remaining() != 0) {
2616
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> invalid; had {} code points left over.", lexer.tell_remaining());
2617
0
        return {};
2618
0
    }
2619
2620
    // 7. Interpret the consumed code points as a hexadecimal number. This is the end value.
2621
0
    auto maybe_end_value = AK::StringUtils::convert_to_uint_from_hex<u32>(end_hex_digits);
2622
0
    if (!maybe_end_value.has_value()) {
2623
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> end value did not parse as hex number.");
2624
0
        return {};
2625
0
    }
2626
0
    u32 end_value = maybe_end_value.release_value();
2627
2628
0
    return make_valid_unicode_range(start_value, end_value);
2629
0
}
2630
2631
Vector<Gfx::UnicodeRange> Parser::parse_unicode_ranges(TokenStream<ComponentValue>& tokens)
2632
0
{
2633
0
    Vector<Gfx::UnicodeRange> unicode_ranges;
2634
0
    auto range_token_lists = parse_a_comma_separated_list_of_component_values(tokens);
2635
0
    for (auto& range_tokens : range_token_lists) {
2636
0
        TokenStream range_token_stream { range_tokens };
2637
0
        auto maybe_unicode_range = parse_unicode_range(range_token_stream);
2638
0
        if (!maybe_unicode_range.has_value()) {
2639
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: unicode-range format invalid; discarding.");
2640
0
            return {};
2641
0
        }
2642
0
        unicode_ranges.append(maybe_unicode_range.release_value());
2643
0
    }
2644
0
    return unicode_ranges;
2645
0
}
2646
2647
RefPtr<CSSStyleValue> Parser::parse_dimension_value(TokenStream<ComponentValue>& tokens)
2648
0
{
2649
0
    if (auto dimension = parse_dimension(tokens.next_token()); dimension.has_value()) {
2650
0
        tokens.discard_a_token(); // dimension
2651
2652
0
        if (dimension->is_angle())
2653
0
            return AngleStyleValue::create(dimension->angle());
2654
0
        if (dimension->is_frequency())
2655
0
            return FrequencyStyleValue::create(dimension->frequency());
2656
0
        if (dimension->is_length())
2657
0
            return LengthStyleValue::create(dimension->length());
2658
0
        if (dimension->is_percentage())
2659
0
            return PercentageStyleValue::create(dimension->percentage());
2660
0
        if (dimension->is_resolution())
2661
0
            return ResolutionStyleValue::create(dimension->resolution());
2662
0
        if (dimension->is_time())
2663
0
            return TimeStyleValue::create(dimension->time());
2664
0
        VERIFY_NOT_REACHED();
2665
0
    }
2666
2667
0
    if (auto calc = parse_calculated_value(tokens.next_token()); calc && calc->resolves_to_dimension()) {
2668
0
        tokens.discard_a_token(); // calc
2669
0
        return calc;
2670
0
    }
2671
2672
0
    return nullptr;
2673
0
}
2674
2675
RefPtr<CSSStyleValue> Parser::parse_integer_value(TokenStream<ComponentValue>& tokens)
2676
0
{
2677
0
    auto peek_token = tokens.next_token();
2678
0
    if (peek_token.is(Token::Type::Number) && peek_token.token().number().is_integer()) {
2679
0
        tokens.discard_a_token(); // integer
2680
0
        return IntegerStyleValue::create(peek_token.token().number().integer_value());
2681
0
    }
2682
0
    if (auto calc = parse_calculated_value(peek_token); calc && calc->resolves_to_number()) {
2683
0
        tokens.discard_a_token(); // calc
2684
0
        return calc;
2685
0
    }
2686
2687
0
    return nullptr;
2688
0
}
2689
2690
RefPtr<CSSStyleValue> Parser::parse_number_value(TokenStream<ComponentValue>& tokens)
2691
0
{
2692
0
    auto peek_token = tokens.next_token();
2693
0
    if (peek_token.is(Token::Type::Number)) {
2694
0
        tokens.discard_a_token(); // number
2695
0
        return NumberStyleValue::create(peek_token.token().number().value());
2696
0
    }
2697
0
    if (auto calc = parse_calculated_value(peek_token); calc && calc->resolves_to_number()) {
2698
0
        tokens.discard_a_token(); // calc
2699
0
        return calc;
2700
0
    }
2701
2702
0
    return nullptr;
2703
0
}
2704
2705
RefPtr<CSSStyleValue> Parser::parse_number_percentage_value(TokenStream<ComponentValue>& tokens)
2706
0
{
2707
0
    auto peek_token = tokens.next_token();
2708
0
    if (peek_token.is(Token::Type::Number)) {
2709
0
        tokens.discard_a_token(); // number
2710
0
        return NumberStyleValue::create(peek_token.token().number().value());
2711
0
    }
2712
0
    if (peek_token.is(Token::Type::Percentage)) {
2713
0
        tokens.discard_a_token(); // percentage
2714
0
        return PercentageStyleValue::create(Percentage(peek_token.token().percentage()));
2715
0
    }
2716
0
    if (auto calc = parse_calculated_value(peek_token); calc && calc->resolves_to_number_percentage()) {
2717
0
        tokens.discard_a_token(); // calc
2718
0
        return calc;
2719
0
    }
2720
2721
0
    return nullptr;
2722
0
}
2723
2724
RefPtr<CSSStyleValue> Parser::parse_percentage_value(TokenStream<ComponentValue>& tokens)
2725
0
{
2726
0
    auto peek_token = tokens.next_token();
2727
0
    if (peek_token.is(Token::Type::Percentage)) {
2728
0
        tokens.discard_a_token(); // percentage
2729
0
        return PercentageStyleValue::create(Percentage(peek_token.token().percentage()));
2730
0
    }
2731
0
    if (auto calc = parse_calculated_value(peek_token); calc && calc->resolves_to_percentage()) {
2732
0
        tokens.discard_a_token(); // calc
2733
0
        return calc;
2734
0
    }
2735
2736
0
    return nullptr;
2737
0
}
2738
2739
RefPtr<CSSStyleValue> Parser::parse_angle_value(TokenStream<ComponentValue>& tokens)
2740
0
{
2741
0
    auto transaction = tokens.begin_transaction();
2742
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2743
0
        if (dimension_value->is_angle()
2744
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_angle())) {
2745
0
            transaction.commit();
2746
0
            return dimension_value;
2747
0
        }
2748
0
    }
2749
0
    return nullptr;
2750
0
}
2751
2752
RefPtr<CSSStyleValue> Parser::parse_angle_percentage_value(TokenStream<ComponentValue>& tokens)
2753
0
{
2754
0
    auto transaction = tokens.begin_transaction();
2755
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2756
0
        if (dimension_value->is_angle() || dimension_value->is_percentage()
2757
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_angle_percentage())) {
2758
0
            transaction.commit();
2759
0
            return dimension_value;
2760
0
        }
2761
0
    }
2762
0
    return nullptr;
2763
0
}
2764
2765
RefPtr<CSSStyleValue> Parser::parse_flex_value(TokenStream<ComponentValue>& tokens)
2766
0
{
2767
0
    auto transaction = tokens.begin_transaction();
2768
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2769
0
        if (dimension_value->is_flex()
2770
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_flex())) {
2771
0
            transaction.commit();
2772
0
            return dimension_value;
2773
0
        }
2774
0
    }
2775
0
    return nullptr;
2776
0
}
2777
2778
RefPtr<CSSStyleValue> Parser::parse_frequency_value(TokenStream<ComponentValue>& tokens)
2779
0
{
2780
0
    auto transaction = tokens.begin_transaction();
2781
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2782
0
        if (dimension_value->is_frequency()
2783
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_frequency())) {
2784
0
            transaction.commit();
2785
0
            return dimension_value;
2786
0
        }
2787
0
    }
2788
0
    return nullptr;
2789
0
}
2790
2791
RefPtr<CSSStyleValue> Parser::parse_frequency_percentage_value(TokenStream<ComponentValue>& tokens)
2792
0
{
2793
0
    auto transaction = tokens.begin_transaction();
2794
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2795
0
        if (dimension_value->is_frequency() || dimension_value->is_percentage()
2796
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_frequency_percentage())) {
2797
0
            transaction.commit();
2798
0
            return dimension_value;
2799
0
        }
2800
0
    }
2801
0
    return nullptr;
2802
0
}
2803
2804
RefPtr<CSSStyleValue> Parser::parse_length_value(TokenStream<ComponentValue>& tokens)
2805
0
{
2806
0
    auto transaction = tokens.begin_transaction();
2807
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2808
0
        if (dimension_value->is_length()
2809
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_length())) {
2810
0
            transaction.commit();
2811
0
            return dimension_value;
2812
0
        }
2813
0
    }
2814
0
    return nullptr;
2815
0
}
2816
2817
RefPtr<CSSStyleValue> Parser::parse_length_percentage_value(TokenStream<ComponentValue>& tokens)
2818
0
{
2819
0
    auto transaction = tokens.begin_transaction();
2820
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2821
0
        if (dimension_value->is_length() || dimension_value->is_percentage()
2822
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_length_percentage())) {
2823
0
            transaction.commit();
2824
0
            return dimension_value;
2825
0
        }
2826
0
    }
2827
0
    return nullptr;
2828
0
}
2829
2830
RefPtr<CSSStyleValue> Parser::parse_resolution_value(TokenStream<ComponentValue>& tokens)
2831
0
{
2832
0
    auto transaction = tokens.begin_transaction();
2833
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2834
0
        if (dimension_value->is_resolution()
2835
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_resolution())) {
2836
0
            transaction.commit();
2837
0
            return dimension_value;
2838
0
        }
2839
0
    }
2840
0
    return nullptr;
2841
0
}
2842
2843
RefPtr<CSSStyleValue> Parser::parse_time_value(TokenStream<ComponentValue>& tokens)
2844
0
{
2845
0
    auto transaction = tokens.begin_transaction();
2846
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2847
0
        if (dimension_value->is_time()
2848
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_time())) {
2849
0
            transaction.commit();
2850
0
            return dimension_value;
2851
0
        }
2852
0
    }
2853
0
    return nullptr;
2854
0
}
2855
2856
RefPtr<CSSStyleValue> Parser::parse_time_percentage_value(TokenStream<ComponentValue>& tokens)
2857
0
{
2858
0
    auto transaction = tokens.begin_transaction();
2859
0
    if (auto dimension_value = parse_dimension_value(tokens)) {
2860
0
        if (dimension_value->is_time() || dimension_value->is_percentage()
2861
0
            || (dimension_value->is_math() && dimension_value->as_math().resolves_to_time_percentage())) {
2862
0
            transaction.commit();
2863
0
            return dimension_value;
2864
0
        }
2865
0
    }
2866
0
    return nullptr;
2867
0
}
2868
2869
RefPtr<CSSStyleValue> Parser::parse_keyword_value(TokenStream<ComponentValue>& tokens)
2870
0
{
2871
0
    auto peek_token = tokens.next_token();
2872
0
    if (peek_token.is(Token::Type::Ident)) {
2873
0
        auto keyword = keyword_from_string(peek_token.token().ident());
2874
0
        if (keyword.has_value()) {
2875
0
            tokens.discard_a_token(); // ident
2876
0
            return CSSKeywordValue::create(keyword.value());
2877
0
        }
2878
0
    }
2879
2880
0
    return nullptr;
2881
0
}
2882
2883
// https://www.w3.org/TR/CSS2/visufx.html#value-def-shape
2884
RefPtr<CSSStyleValue> Parser::parse_rect_value(TokenStream<ComponentValue>& tokens)
2885
0
{
2886
0
    auto transaction = tokens.begin_transaction();
2887
0
    auto function_token = tokens.consume_a_token();
2888
0
    if (!function_token.is_function("rect"sv))
2889
0
        return nullptr;
2890
2891
0
    Vector<Length, 4> params;
2892
0
    auto argument_tokens = TokenStream { function_token.function().value };
2893
2894
0
    enum class CommaRequirement {
2895
0
        Unknown,
2896
0
        RequiresCommas,
2897
0
        RequiresNoCommas
2898
0
    };
2899
2900
0
    enum class Side {
2901
0
        Top = 0,
2902
0
        Right = 1,
2903
0
        Bottom = 2,
2904
0
        Left = 3
2905
0
    };
2906
2907
0
    auto comma_requirement = CommaRequirement::Unknown;
2908
2909
    // In CSS 2.1, the only valid <shape> value is: rect(<top>, <right>, <bottom>, <left>) where
2910
    // <top> and <bottom> specify offsets from the top border edge of the box, and <right>, and
2911
    //  <left> specify offsets from the left border edge of the box.
2912
0
    for (size_t side = 0; side < 4; side++) {
2913
0
        argument_tokens.discard_whitespace();
2914
2915
        // <top>, <right>, <bottom>, and <left> may either have a <length> value or 'auto'.
2916
        // Negative lengths are permitted.
2917
0
        if (argument_tokens.next_token().is_ident("auto"sv)) {
2918
0
            (void)argument_tokens.consume_a_token(); // `auto`
2919
0
            params.append(Length::make_auto());
2920
0
        } else {
2921
0
            auto maybe_length = parse_length(argument_tokens);
2922
0
            if (!maybe_length.has_value())
2923
0
                return nullptr;
2924
0
            if (maybe_length.value().is_calculated()) {
2925
0
                dbgln("FIXME: Support calculated lengths in rect(): {}", maybe_length.value().calculated()->to_string());
2926
0
                return nullptr;
2927
0
            }
2928
0
            params.append(maybe_length.value().value());
2929
0
        }
2930
0
        argument_tokens.discard_whitespace();
2931
2932
        // The last side, should be no more tokens following it.
2933
0
        if (static_cast<Side>(side) == Side::Left) {
2934
0
            if (argument_tokens.has_next_token())
2935
0
                return nullptr;
2936
0
            break;
2937
0
        }
2938
2939
0
        bool next_is_comma = argument_tokens.next_token().is(Token::Type::Comma);
2940
2941
        // Authors should separate offset values with commas. User agents must support separation
2942
        // with commas, but may also support separation without commas (but not a combination),
2943
        // because a previous revision of this specification was ambiguous in this respect.
2944
0
        if (comma_requirement == CommaRequirement::Unknown)
2945
0
            comma_requirement = next_is_comma ? CommaRequirement::RequiresCommas : CommaRequirement::RequiresNoCommas;
2946
2947
0
        if (comma_requirement == CommaRequirement::RequiresCommas) {
2948
0
            if (next_is_comma)
2949
0
                argument_tokens.discard_a_token();
2950
0
            else
2951
0
                return nullptr;
2952
0
        } else if (comma_requirement == CommaRequirement::RequiresNoCommas) {
2953
0
            if (next_is_comma)
2954
0
                return nullptr;
2955
0
        } else {
2956
0
            VERIFY_NOT_REACHED();
2957
0
        }
2958
0
    }
2959
2960
0
    transaction.commit();
2961
0
    return RectStyleValue::create(EdgeRect { params[0], params[1], params[2], params[3] });
2962
0
}
2963
2964
// https://www.w3.org/TR/css-color-4/#typedef-hue
2965
RefPtr<CSSStyleValue> Parser::parse_hue_value(TokenStream<ComponentValue>& tokens)
2966
0
{
2967
    // <hue> = <number> | <angle>
2968
0
    if (auto number = parse_number_value(tokens))
2969
0
        return number;
2970
0
    if (auto angle = parse_angle_value(tokens))
2971
0
        return angle;
2972
2973
0
    return nullptr;
2974
0
}
2975
2976
RefPtr<CSSStyleValue> Parser::parse_solidus_and_alpha_value(TokenStream<ComponentValue>& tokens)
2977
0
{
2978
    // [ / [<alpha-value> | none] ]?
2979
    // Common to the modern-syntax color functions.
2980
    // TODO: Parse `none`
2981
2982
0
    auto transaction = tokens.begin_transaction();
2983
0
    tokens.discard_whitespace();
2984
0
    if (!tokens.consume_a_token().is_delim('/'))
2985
0
        return {};
2986
0
    tokens.discard_whitespace();
2987
0
    auto alpha = parse_number_percentage_value(tokens);
2988
0
    if (!alpha)
2989
0
        return {};
2990
0
    tokens.discard_whitespace();
2991
2992
0
    transaction.commit();
2993
0
    return alpha;
2994
0
}
2995
2996
// https://www.w3.org/TR/css-color-4/#funcdef-rgb
2997
RefPtr<CSSStyleValue> Parser::parse_rgb_color_value(TokenStream<ComponentValue>& outer_tokens)
2998
0
{
2999
    // rgb() = [ <legacy-rgb-syntax> | <modern-rgb-syntax> ]
3000
    // rgba() = [ <legacy-rgba-syntax> | <modern-rgba-syntax> ]
3001
    // <legacy-rgb-syntax> = rgb( <percentage>#{3} , <alpha-value>? ) |
3002
    //                       rgb( <number>#{3} , <alpha-value>? )
3003
    // <legacy-rgba-syntax> = rgba( <percentage>#{3} , <alpha-value>? ) |
3004
    //                        rgba( <number>#{3} , <alpha-value>? )
3005
    // <modern-rgb-syntax> = rgb(
3006
    //     [ <number> | <percentage> | none]{3}
3007
    //     [ / [<alpha-value> | none] ]?  )
3008
    // <modern-rgba-syntax> = rgba(
3009
    //     [ <number> | <percentage> | none]{3}
3010
    //     [ / [<alpha-value> | none] ]?  )
3011
    // TODO: Handle none values
3012
3013
0
    auto transaction = outer_tokens.begin_transaction();
3014
0
    outer_tokens.discard_whitespace();
3015
3016
0
    auto& function_token = outer_tokens.consume_a_token();
3017
0
    if (!function_token.is_function("rgb"sv) && !function_token.is_function("rgba"sv))
3018
0
        return {};
3019
3020
0
    RefPtr<CSSStyleValue> red;
3021
0
    RefPtr<CSSStyleValue> green;
3022
0
    RefPtr<CSSStyleValue> blue;
3023
0
    RefPtr<CSSStyleValue> alpha;
3024
3025
0
    auto inner_tokens = TokenStream { function_token.function().value };
3026
0
    inner_tokens.discard_whitespace();
3027
3028
0
    red = parse_number_percentage_value(inner_tokens);
3029
0
    if (!red)
3030
0
        return {};
3031
3032
0
    inner_tokens.discard_whitespace();
3033
0
    bool legacy_syntax = inner_tokens.next_token().is(Token::Type::Comma);
3034
0
    if (legacy_syntax) {
3035
        // Legacy syntax
3036
        //   <percentage>#{3} , <alpha-value>?
3037
        //   | <number>#{3} , <alpha-value>?
3038
        // So, r/g/b can be numbers or percentages, as long as they're all the same type.
3039
3040
0
        inner_tokens.discard_a_token(); // comma
3041
0
        inner_tokens.discard_whitespace();
3042
3043
0
        green = parse_number_percentage_value(inner_tokens);
3044
0
        if (!green)
3045
0
            return {};
3046
0
        inner_tokens.discard_whitespace();
3047
3048
0
        if (!inner_tokens.consume_a_token().is(Token::Type::Comma))
3049
0
            return {};
3050
0
        inner_tokens.discard_whitespace();
3051
3052
0
        blue = parse_number_percentage_value(inner_tokens);
3053
0
        if (!blue)
3054
0
            return {};
3055
0
        inner_tokens.discard_whitespace();
3056
3057
0
        if (inner_tokens.has_next_token()) {
3058
            // Try and read comma and alpha
3059
0
            if (!inner_tokens.consume_a_token().is(Token::Type::Comma))
3060
0
                return {};
3061
0
            inner_tokens.discard_whitespace();
3062
3063
0
            alpha = parse_number_percentage_value(inner_tokens);
3064
3065
0
            if (!alpha)
3066
0
                return {};
3067
3068
0
            inner_tokens.discard_whitespace();
3069
3070
0
            if (inner_tokens.has_next_token())
3071
0
                return {};
3072
0
        }
3073
3074
        // Verify we're all percentages or all numbers
3075
0
        auto is_percentage = [](CSSStyleValue const& style_value) {
3076
0
            return style_value.is_percentage()
3077
0
                || (style_value.is_math() && style_value.as_math().resolves_to_percentage());
3078
0
        };
3079
0
        bool red_is_percentage = is_percentage(*red);
3080
0
        bool green_is_percentage = is_percentage(*green);
3081
0
        bool blue_is_percentage = is_percentage(*blue);
3082
0
        if (red_is_percentage != green_is_percentage || red_is_percentage != blue_is_percentage)
3083
0
            return {};
3084
3085
0
    } else {
3086
        // Modern syntax
3087
        //   [ <number> | <percentage> | none]{3}  [ / [<alpha-value> | none] ]?
3088
3089
0
        green = parse_number_percentage_value(inner_tokens);
3090
0
        if (!green)
3091
0
            return {};
3092
0
        inner_tokens.discard_whitespace();
3093
3094
0
        blue = parse_number_percentage_value(inner_tokens);
3095
0
        if (!blue)
3096
0
            return {};
3097
0
        inner_tokens.discard_whitespace();
3098
3099
0
        if (inner_tokens.has_next_token()) {
3100
0
            alpha = parse_solidus_and_alpha_value(inner_tokens);
3101
0
            if (!alpha || inner_tokens.has_next_token())
3102
0
                return {};
3103
0
        }
3104
0
    }
3105
3106
0
    if (!alpha)
3107
0
        alpha = NumberStyleValue::create(1);
3108
3109
0
    transaction.commit();
3110
0
    return CSSRGB::create(red.release_nonnull(), green.release_nonnull(), blue.release_nonnull(), alpha.release_nonnull());
3111
0
}
3112
3113
// https://www.w3.org/TR/css-color-4/#funcdef-hsl
3114
RefPtr<CSSStyleValue> Parser::parse_hsl_color_value(TokenStream<ComponentValue>& outer_tokens)
3115
0
{
3116
    // hsl() = [ <legacy-hsl-syntax> | <modern-hsl-syntax> ]
3117
    // hsla() = [ <legacy-hsla-syntax> | <modern-hsla-syntax> ]
3118
    // <modern-hsl-syntax> = hsl(
3119
    //     [<hue> | none]
3120
    //     [<percentage> | <number> | none]
3121
    //     [<percentage> | <number> | none]
3122
    //     [ / [<alpha-value> | none] ]? )
3123
    // <modern-hsla-syntax> = hsla(
3124
    //     [<hue> | none]
3125
    //     [<percentage> | <number> | none]
3126
    //     [<percentage> | <number> | none]
3127
    //     [ / [<alpha-value> | none] ]? )
3128
    // <legacy-hsl-syntax> = hsl( <hue>, <percentage>, <percentage>, <alpha-value>? )
3129
    // <legacy-hsla-syntax> = hsla( <hue>, <percentage>, <percentage>, <alpha-value>? )
3130
    // TODO: Handle none values
3131
3132
0
    auto transaction = outer_tokens.begin_transaction();
3133
0
    outer_tokens.discard_whitespace();
3134
3135
0
    auto& function_token = outer_tokens.consume_a_token();
3136
0
    if (!function_token.is_function("hsl"sv) && !function_token.is_function("hsla"sv))
3137
0
        return {};
3138
3139
0
    RefPtr<CSSStyleValue> h;
3140
0
    RefPtr<CSSStyleValue> s;
3141
0
    RefPtr<CSSStyleValue> l;
3142
0
    RefPtr<CSSStyleValue> alpha;
3143
3144
0
    auto inner_tokens = TokenStream { function_token.function().value };
3145
0
    inner_tokens.discard_whitespace();
3146
3147
0
    h = parse_hue_value(inner_tokens);
3148
0
    if (!h)
3149
0
        return {};
3150
3151
0
    inner_tokens.discard_whitespace();
3152
0
    bool legacy_syntax = inner_tokens.next_token().is(Token::Type::Comma);
3153
0
    if (legacy_syntax) {
3154
        // Legacy syntax
3155
        //   <hue>, <percentage>, <percentage>, <alpha-value>?
3156
0
        (void)inner_tokens.consume_a_token(); // comma
3157
0
        inner_tokens.discard_whitespace();
3158
3159
0
        s = parse_percentage_value(inner_tokens);
3160
0
        if (!s)
3161
0
            return {};
3162
0
        inner_tokens.discard_whitespace();
3163
3164
0
        if (!inner_tokens.consume_a_token().is(Token::Type::Comma))
3165
0
            return {};
3166
0
        inner_tokens.discard_whitespace();
3167
3168
0
        l = parse_percentage_value(inner_tokens);
3169
0
        if (!l)
3170
0
            return {};
3171
0
        inner_tokens.discard_whitespace();
3172
3173
0
        if (inner_tokens.has_next_token()) {
3174
            // Try and read comma and alpha
3175
0
            if (!inner_tokens.consume_a_token().is(Token::Type::Comma))
3176
0
                return {};
3177
0
            inner_tokens.discard_whitespace();
3178
3179
0
            alpha = parse_number_percentage_value(inner_tokens);
3180
0
            inner_tokens.discard_whitespace();
3181
3182
0
            if (inner_tokens.has_next_token())
3183
0
                return {};
3184
0
        }
3185
0
    } else {
3186
        // Modern syntax
3187
        //   [<hue> | none]
3188
        //   [<percentage> | <number> | none]
3189
        //   [<percentage> | <number> | none]
3190
        //   [ / [<alpha-value> | none] ]?
3191
3192
0
        s = parse_number_percentage_value(inner_tokens);
3193
0
        if (!s)
3194
0
            return {};
3195
0
        inner_tokens.discard_whitespace();
3196
3197
0
        l = parse_number_percentage_value(inner_tokens);
3198
0
        if (!l)
3199
0
            return {};
3200
0
        inner_tokens.discard_whitespace();
3201
3202
0
        if (inner_tokens.has_next_token()) {
3203
0
            alpha = parse_solidus_and_alpha_value(inner_tokens);
3204
0
            if (!alpha || inner_tokens.has_next_token())
3205
0
                return {};
3206
0
        }
3207
0
    }
3208
3209
0
    if (!alpha)
3210
0
        alpha = NumberStyleValue::create(1);
3211
3212
0
    transaction.commit();
3213
0
    return CSSHSL::create(h.release_nonnull(), s.release_nonnull(), l.release_nonnull(), alpha.release_nonnull());
3214
0
}
3215
3216
// https://www.w3.org/TR/css-color-4/#funcdef-hwb
3217
RefPtr<CSSStyleValue> Parser::parse_hwb_color_value(TokenStream<ComponentValue>& outer_tokens)
3218
0
{
3219
    // hwb() = hwb(
3220
    //     [<hue> | none]
3221
    //     [<percentage> | <number> | none]
3222
    //     [<percentage> | <number> | none]
3223
    //     [ / [<alpha-value> | none] ]? )
3224
3225
0
    auto transaction = outer_tokens.begin_transaction();
3226
0
    outer_tokens.discard_whitespace();
3227
3228
0
    auto& function_token = outer_tokens.consume_a_token();
3229
0
    if (!function_token.is_function("hwb"sv))
3230
0
        return {};
3231
3232
0
    RefPtr<CSSStyleValue> h;
3233
0
    RefPtr<CSSStyleValue> w;
3234
0
    RefPtr<CSSStyleValue> b;
3235
0
    RefPtr<CSSStyleValue> alpha;
3236
3237
0
    auto inner_tokens = TokenStream { function_token.function().value };
3238
0
    inner_tokens.discard_whitespace();
3239
3240
0
    h = parse_hue_value(inner_tokens);
3241
0
    if (!h)
3242
0
        return {};
3243
0
    inner_tokens.discard_whitespace();
3244
3245
0
    w = parse_number_percentage_value(inner_tokens);
3246
0
    if (!w)
3247
0
        return {};
3248
0
    inner_tokens.discard_whitespace();
3249
3250
0
    b = parse_number_percentage_value(inner_tokens);
3251
0
    if (!b)
3252
0
        return {};
3253
0
    inner_tokens.discard_whitespace();
3254
3255
0
    if (inner_tokens.has_next_token()) {
3256
0
        alpha = parse_solidus_and_alpha_value(inner_tokens);
3257
0
        if (!alpha || inner_tokens.has_next_token())
3258
0
            return {};
3259
0
    }
3260
3261
0
    if (!alpha)
3262
0
        alpha = NumberStyleValue::create(1);
3263
3264
0
    transaction.commit();
3265
0
    return CSSHWB::create(h.release_nonnull(), w.release_nonnull(), b.release_nonnull(), alpha.release_nonnull());
3266
0
}
3267
3268
Optional<Array<RefPtr<CSSStyleValue>, 4>> Parser::parse_lab_like_color_value(TokenStream<ComponentValue>& outer_tokens, StringView function_name)
3269
0
{
3270
    // This helper is designed to be compatible with lab and oklab and parses a function with a form like:
3271
    // f() = f( [ <percentage> | <number> | none]
3272
    //     [ <percentage> | <number> | none]
3273
    //     [ <percentage> | <number> | none]
3274
    //     [ / [<alpha-value> | none] ]? )
3275
3276
0
    auto transaction = outer_tokens.begin_transaction();
3277
0
    outer_tokens.discard_whitespace();
3278
3279
0
    auto& function_token = outer_tokens.consume_a_token();
3280
0
    if (!function_token.is_function(function_name))
3281
0
        return OptionalNone {};
3282
3283
0
    RefPtr<CSSStyleValue> l;
3284
0
    RefPtr<CSSStyleValue> a;
3285
0
    RefPtr<CSSStyleValue> b;
3286
0
    RefPtr<CSSStyleValue> alpha;
3287
3288
0
    auto inner_tokens = TokenStream { function_token.function().value };
3289
0
    inner_tokens.discard_whitespace();
3290
3291
0
    l = parse_number_percentage_value(inner_tokens);
3292
0
    if (!l)
3293
0
        return OptionalNone {};
3294
0
    inner_tokens.discard_whitespace();
3295
3296
0
    a = parse_number_percentage_value(inner_tokens);
3297
0
    if (!a)
3298
0
        return OptionalNone {};
3299
0
    inner_tokens.discard_whitespace();
3300
3301
0
    b = parse_number_percentage_value(inner_tokens);
3302
0
    if (!b)
3303
0
        return OptionalNone {};
3304
0
    inner_tokens.discard_whitespace();
3305
3306
0
    if (inner_tokens.has_next_token()) {
3307
0
        alpha = parse_solidus_and_alpha_value(inner_tokens);
3308
0
        if (!alpha || inner_tokens.has_next_token())
3309
0
            return OptionalNone {};
3310
0
    }
3311
3312
0
    if (!alpha)
3313
0
        alpha = NumberStyleValue::create(1);
3314
3315
0
    transaction.commit();
3316
3317
0
    return Array { move(l), move(a), move(b), move(alpha) };
3318
0
}
3319
3320
// https://www.w3.org/TR/css-color-4/#funcdef-lab
3321
RefPtr<CSSStyleValue> Parser::parse_lab_color_value(TokenStream<ComponentValue>& outer_tokens)
3322
0
{
3323
    // lab() = lab( [<percentage> | <number> | none]
3324
    //      [ <percentage> | <number> | none]
3325
    //      [ <percentage> | <number> | none]
3326
    //      [ / [<alpha-value> | none] ]? )
3327
3328
0
    auto maybe_color_values = parse_lab_like_color_value(outer_tokens, "lab"sv);
3329
0
    if (!maybe_color_values.has_value())
3330
0
        return {};
3331
3332
0
    auto& color_values = *maybe_color_values;
3333
3334
0
    return CSSLabLike::create<CSSLab>(color_values[0].release_nonnull(),
3335
0
        color_values[1].release_nonnull(),
3336
0
        color_values[2].release_nonnull(),
3337
0
        color_values[3].release_nonnull());
3338
0
}
3339
3340
// https://www.w3.org/TR/css-color-4/#funcdef-oklab
3341
RefPtr<CSSStyleValue> Parser::parse_oklab_color_value(TokenStream<ComponentValue>& outer_tokens)
3342
0
{
3343
    // oklab() = oklab( [ <percentage> | <number> | none]
3344
    //     [ <percentage> | <number> | none]
3345
    //     [ <percentage> | <number> | none]
3346
    //     [ / [<alpha-value> | none] ]? )
3347
3348
0
    auto maybe_color_values = parse_lab_like_color_value(outer_tokens, "oklab"sv);
3349
0
    if (!maybe_color_values.has_value())
3350
0
        return {};
3351
3352
0
    auto& color_values = *maybe_color_values;
3353
3354
0
    return CSSLabLike::create<CSSOKLab>(color_values[0].release_nonnull(),
3355
0
        color_values[1].release_nonnull(),
3356
0
        color_values[2].release_nonnull(),
3357
0
        color_values[3].release_nonnull());
3358
0
}
3359
3360
Optional<Array<RefPtr<CSSStyleValue>, 4>> Parser::parse_lch_like_color_value(TokenStream<ComponentValue>& outer_tokens, StringView function_name)
3361
0
{
3362
    // This helper is designed to be compatible with lch and oklch and parses a function with a form like:
3363
    // f() = f( [<percentage> | <number> | none]
3364
    //     [ <percentage> | <number> | none]
3365
    //     [ <hue> | none]
3366
    //     [ / [<alpha-value> | none] ]? )
3367
3368
0
    auto transaction = outer_tokens.begin_transaction();
3369
0
    outer_tokens.discard_whitespace();
3370
3371
0
    auto const& function_token = outer_tokens.consume_a_token();
3372
0
    if (!function_token.is_function(function_name))
3373
0
        return OptionalNone {};
3374
3375
0
    auto inner_tokens = TokenStream { function_token.function().value };
3376
0
    inner_tokens.discard_whitespace();
3377
3378
0
    auto l = parse_number_percentage_value(inner_tokens);
3379
0
    if (!l)
3380
0
        return OptionalNone {};
3381
0
    inner_tokens.discard_whitespace();
3382
3383
0
    auto c = parse_number_percentage_value(inner_tokens);
3384
0
    if (!c)
3385
0
        return OptionalNone {};
3386
0
    inner_tokens.discard_whitespace();
3387
3388
0
    auto h = parse_hue_value(inner_tokens);
3389
0
    if (!h)
3390
0
        return OptionalNone {};
3391
0
    inner_tokens.discard_whitespace();
3392
3393
0
    RefPtr<CSSStyleValue> alpha;
3394
0
    if (inner_tokens.has_next_token()) {
3395
0
        alpha = parse_solidus_and_alpha_value(inner_tokens);
3396
0
        if (!alpha || inner_tokens.has_next_token())
3397
0
            return OptionalNone {};
3398
0
    }
3399
3400
0
    if (!alpha)
3401
0
        alpha = NumberStyleValue::create(1);
3402
3403
0
    transaction.commit();
3404
3405
0
    return Array { move(l), move(c), move(h), move(alpha) };
3406
0
}
3407
3408
// https://www.w3.org/TR/css-color-4/#funcdef-lch
3409
RefPtr<CSSStyleValue> Parser::parse_lch_color_value(TokenStream<ComponentValue>& outer_tokens)
3410
0
{
3411
    // lch() = lch( [<percentage> | <number> | none]
3412
    //      [ <percentage> | <number> | none]
3413
    //      [ <hue> | none]
3414
    //      [ / [<alpha-value> | none] ]? )
3415
3416
0
    auto maybe_color_values = parse_lch_like_color_value(outer_tokens, "lch"sv);
3417
0
    if (!maybe_color_values.has_value())
3418
0
        return {};
3419
3420
0
    auto& color_values = *maybe_color_values;
3421
3422
0
    return CSSLCHLike::create<CSSLCH>(color_values[0].release_nonnull(),
3423
0
        color_values[1].release_nonnull(),
3424
0
        color_values[2].release_nonnull(),
3425
0
        color_values[3].release_nonnull());
3426
0
}
3427
3428
// https://www.w3.org/TR/css-color-4/#funcdef-oklch
3429
RefPtr<CSSStyleValue> Parser::parse_oklch_color_value(TokenStream<ComponentValue>& outer_tokens)
3430
0
{
3431
    // oklch() = oklch( [ <percentage> | <number> | none]
3432
    //     [ <percentage> | <number> | none]
3433
    //     [ <hue> | none]
3434
    //     [ / [<alpha-value> | none] ]? )
3435
3436
0
    auto maybe_color_values = parse_lch_like_color_value(outer_tokens, "oklch"sv);
3437
0
    if (!maybe_color_values.has_value())
3438
0
        return {};
3439
3440
0
    auto& color_values = *maybe_color_values;
3441
3442
0
    return CSSLCHLike::create<CSSOKLCH>(color_values[0].release_nonnull(),
3443
0
        color_values[1].release_nonnull(),
3444
0
        color_values[2].release_nonnull(),
3445
0
        color_values[3].release_nonnull());
3446
0
}
3447
3448
// https://www.w3.org/TR/css-color-4/#funcdef-color
3449
RefPtr<CSSStyleValue> Parser::parse_color_function(TokenStream<ComponentValue>& outer_tokens)
3450
0
{
3451
    // color() = color( <colorspace-params> [ / [ <alpha-value> | none ] ]? )
3452
    //     <colorspace-params> = [ <predefined-rgb-params> | <xyz-params>]
3453
    //     <predefined-rgb-params> = <predefined-rgb> [ <number> | <percentage> | none ]{3}
3454
    //     <predefined-rgb> = srgb | srgb-linear | display-p3 | a98-rgb | prophoto-rgb | rec2020
3455
    //     <xyz-params> = <xyz-space> [ <number> | <percentage> | none ]{3}
3456
    //     <xyz-space> = xyz | xyz-d50 | xyz-d65
3457
3458
0
    auto transaction = outer_tokens.begin_transaction();
3459
0
    outer_tokens.discard_whitespace();
3460
3461
0
    auto const& function_token = outer_tokens.consume_a_token();
3462
0
    if (!function_token.is_function("color"sv))
3463
0
        return {};
3464
3465
0
    auto inner_tokens = TokenStream { function_token.function().value };
3466
0
    inner_tokens.discard_whitespace();
3467
3468
0
    auto maybe_color_space = inner_tokens.consume_a_token();
3469
0
    inner_tokens.discard_whitespace();
3470
0
    if (!any_of(CSSColor::s_supported_color_space, [&](auto supported) { return maybe_color_space.is_ident(supported); }))
3471
0
        return {};
3472
3473
0
    auto const& color_space = maybe_color_space.token().ident();
3474
3475
0
    auto c1 = parse_number_percentage_value(inner_tokens);
3476
0
    if (!c1)
3477
0
        return {};
3478
0
    inner_tokens.discard_whitespace();
3479
3480
0
    auto c2 = parse_number_percentage_value(inner_tokens);
3481
0
    if (!c2)
3482
0
        return {};
3483
0
    inner_tokens.discard_whitespace();
3484
3485
0
    auto c3 = parse_number_percentage_value(inner_tokens);
3486
0
    if (!c3)
3487
0
        return {};
3488
0
    inner_tokens.discard_whitespace();
3489
3490
0
    RefPtr<CSSStyleValue> alpha;
3491
0
    if (inner_tokens.has_next_token()) {
3492
0
        alpha = parse_solidus_and_alpha_value(inner_tokens);
3493
0
        if (!alpha || inner_tokens.has_next_token())
3494
0
            return {};
3495
0
    }
3496
3497
0
    if (!alpha)
3498
0
        alpha = NumberStyleValue::create(1);
3499
3500
0
    transaction.commit();
3501
0
    return CSSColor::create(color_space.to_ascii_lowercase(),
3502
0
        c1.release_nonnull(),
3503
0
        c2.release_nonnull(),
3504
0
        c3.release_nonnull(),
3505
0
        alpha.release_nonnull());
3506
0
}
3507
3508
// https://www.w3.org/TR/css-color-4/#color-syntax
3509
RefPtr<CSSStyleValue> Parser::parse_color_value(TokenStream<ComponentValue>& tokens)
3510
0
{
3511
3512
    // Keywords: <system-color> | <deprecated-color> | currentColor
3513
0
    {
3514
0
        auto transaction = tokens.begin_transaction();
3515
0
        if (auto keyword = parse_keyword_value(tokens); keyword && keyword->has_color()) {
3516
0
            transaction.commit();
3517
0
            return keyword;
3518
0
        }
3519
0
    }
3520
3521
    // Functions
3522
0
    if (auto color = parse_color_function(tokens))
3523
0
        return color;
3524
3525
0
    if (auto rgb = parse_rgb_color_value(tokens))
3526
0
        return rgb;
3527
0
    if (auto hsl = parse_hsl_color_value(tokens))
3528
0
        return hsl;
3529
0
    if (auto hwb = parse_hwb_color_value(tokens))
3530
0
        return hwb;
3531
0
    if (auto lab = parse_lab_color_value(tokens))
3532
0
        return lab;
3533
0
    if (auto lch = parse_lch_color_value(tokens))
3534
0
        return lch;
3535
0
    if (auto oklab = parse_oklab_color_value(tokens))
3536
0
        return oklab;
3537
0
    if (auto oklch = parse_oklch_color_value(tokens))
3538
0
        return oklch;
3539
3540
0
    auto transaction = tokens.begin_transaction();
3541
0
    tokens.discard_whitespace();
3542
0
    auto component_value = tokens.consume_a_token();
3543
3544
0
    if (component_value.is(Token::Type::Ident)) {
3545
0
        auto ident = component_value.token().ident();
3546
3547
0
        auto color = Color::from_string(ident);
3548
0
        if (color.has_value()) {
3549
0
            transaction.commit();
3550
0
            return CSSColorValue::create_from_color(color.release_value());
3551
0
        }
3552
        // Otherwise, fall through to the hashless-hex-color case
3553
0
    }
3554
3555
0
    if (component_value.is(Token::Type::Hash)) {
3556
0
        auto color = Color::from_string(MUST(String::formatted("#{}", component_value.token().hash_value())));
3557
0
        if (color.has_value()) {
3558
0
            transaction.commit();
3559
0
            return CSSColorValue::create_from_color(color.release_value());
3560
0
        }
3561
0
        return {};
3562
0
    }
3563
3564
    // https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk
3565
0
    if (m_context.in_quirks_mode() && property_has_quirk(m_context.current_property_id(), Quirk::HashlessHexColor)) {
3566
        // The value of a quirky color is obtained from the possible component values using the following algorithm,
3567
        // aborting on the first step that returns a value:
3568
3569
        // 1. Let cv be the component value.
3570
0
        auto const& cv = component_value;
3571
0
        String serialization;
3572
        // 2. If cv is a <number-token> or a <dimension-token>, follow these substeps:
3573
0
        if (cv.is(Token::Type::Number) || cv.is(Token::Type::Dimension)) {
3574
            // 1. If cv’s type flag is not "integer", return an error.
3575
            //    This means that values that happen to use scientific notation, e.g., 5e5e5e, will fail to parse.
3576
0
            if (!cv.token().number().is_integer())
3577
0
                return {};
3578
3579
            // 2. If cv’s value is less than zero, return an error.
3580
0
            auto value = cv.is(Token::Type::Number) ? cv.token().to_integer() : cv.token().dimension_value_int();
3581
0
            if (value < 0)
3582
0
                return {};
3583
3584
            // 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.
3585
0
            StringBuilder serialization_builder;
3586
0
            serialization_builder.appendff("{}", value);
3587
3588
            // 4. If cv is a <dimension-token>, append the unit to serialization.
3589
0
            if (cv.is(Token::Type::Dimension))
3590
0
                serialization_builder.append(cv.token().dimension_unit());
3591
3592
            // 5. If serialization consists of fewer than six characters, prepend zeros (U+0030) so that it becomes six characters.
3593
0
            serialization = MUST(serialization_builder.to_string());
3594
0
            if (serialization_builder.length() < 6) {
3595
0
                StringBuilder builder;
3596
0
                for (size_t i = 0; i < (6 - serialization_builder.length()); i++)
3597
0
                    builder.append('0');
3598
0
                builder.append(serialization_builder.string_view());
3599
0
                serialization = MUST(builder.to_string());
3600
0
            }
3601
0
        }
3602
        // 3. Otherwise, cv is an <ident-token>; let serialization be cv’s value.
3603
0
        else {
3604
0
            if (!cv.is(Token::Type::Ident))
3605
0
                return {};
3606
0
            serialization = cv.token().ident().to_string();
3607
0
        }
3608
3609
        // 4. If serialization does not consist of three or six characters, return an error.
3610
0
        if (serialization.bytes().size() != 3 && serialization.bytes().size() != 6)
3611
0
            return {};
3612
3613
        // 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.
3614
0
        for (auto c : serialization.bytes_as_string_view()) {
3615
0
            if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')))
3616
0
                return {};
3617
0
        }
3618
3619
        // 6. Return the concatenation of "#" (U+0023) and serialization.
3620
0
        auto color = Color::from_string(MUST(String::formatted("#{}", serialization)));
3621
0
        if (color.has_value()) {
3622
0
            transaction.commit();
3623
0
            return CSSColorValue::create_from_color(color.release_value());
3624
0
        }
3625
0
    }
3626
3627
0
    return {};
3628
0
}
3629
3630
// https://drafts.csswg.org/css-lists-3/#counter-functions
3631
RefPtr<CSSStyleValue> Parser::parse_counter_value(TokenStream<ComponentValue>& tokens)
3632
0
{
3633
0
    auto parse_counter_name = [this](TokenStream<ComponentValue>& tokens) -> Optional<FlyString> {
3634
        // https://drafts.csswg.org/css-lists-3/#typedef-counter-name
3635
        // Counters are referred to in CSS syntax using the <counter-name> type, which represents
3636
        // their name as a <custom-ident>. A <counter-name> name cannot match the keyword none;
3637
        // such an identifier is invalid as a <counter-name>.
3638
0
        auto transaction = tokens.begin_transaction();
3639
0
        tokens.discard_whitespace();
3640
3641
0
        auto counter_name = parse_custom_ident_value(tokens, { "none"sv });
3642
0
        if (!counter_name)
3643
0
            return {};
3644
3645
0
        tokens.discard_whitespace();
3646
0
        if (tokens.has_next_token())
3647
0
            return {};
3648
3649
0
        transaction.commit();
3650
0
        return counter_name->custom_ident();
3651
0
    };
3652
3653
0
    auto parse_counter_style = [this](TokenStream<ComponentValue>& tokens) -> RefPtr<CSSStyleValue> {
3654
        // https://drafts.csswg.org/css-counter-styles-3/#typedef-counter-style
3655
        // <counter-style> = <counter-style-name> | <symbols()>
3656
        // For now we just support <counter-style-name>, found here:
3657
        // https://drafts.csswg.org/css-counter-styles-3/#typedef-counter-style-name
3658
        // <counter-style-name> is a <custom-ident> that is not an ASCII case-insensitive match for none.
3659
0
        auto transaction = tokens.begin_transaction();
3660
0
        tokens.discard_whitespace();
3661
3662
0
        auto counter_style_name = parse_custom_ident_value(tokens, { "none"sv });
3663
0
        if (!counter_style_name)
3664
0
            return {};
3665
3666
0
        tokens.discard_whitespace();
3667
0
        if (tokens.has_next_token())
3668
0
            return {};
3669
3670
0
        transaction.commit();
3671
0
        return counter_style_name.release_nonnull();
3672
0
    };
3673
3674
0
    auto transaction = tokens.begin_transaction();
3675
0
    auto token = tokens.consume_a_token();
3676
0
    if (token.is_function("counter"sv)) {
3677
        // counter() = counter( <counter-name>, <counter-style>? )
3678
0
        auto& function = token.function();
3679
0
        TokenStream function_tokens { function.value };
3680
0
        auto function_values = parse_a_comma_separated_list_of_component_values(function_tokens);
3681
0
        if (function_values.is_empty() || function_values.size() > 2)
3682
0
            return nullptr;
3683
3684
0
        TokenStream name_tokens { function_values[0] };
3685
0
        auto counter_name = parse_counter_name(name_tokens);
3686
0
        if (!counter_name.has_value())
3687
0
            return nullptr;
3688
3689
0
        RefPtr<CSSStyleValue> counter_style;
3690
0
        if (function_values.size() > 1) {
3691
0
            TokenStream counter_style_tokens { function_values[1] };
3692
0
            counter_style = parse_counter_style(counter_style_tokens);
3693
0
            if (!counter_style)
3694
0
                return nullptr;
3695
0
        } else {
3696
            // In both cases, if the <counter-style> argument is omitted it defaults to `decimal`.
3697
0
            counter_style = CustomIdentStyleValue::create("decimal"_fly_string);
3698
0
        }
3699
3700
0
        transaction.commit();
3701
0
        return CounterStyleValue::create_counter(counter_name.release_value(), counter_style.release_nonnull());
3702
0
    }
3703
3704
0
    if (token.is_function("counters"sv)) {
3705
        // counters() = counters( <counter-name>, <string>, <counter-style>? )
3706
0
        auto& function = token.function();
3707
0
        TokenStream function_tokens { function.value };
3708
0
        auto function_values = parse_a_comma_separated_list_of_component_values(function_tokens);
3709
0
        if (function_values.size() < 2 || function_values.size() > 3)
3710
0
            return nullptr;
3711
3712
0
        TokenStream name_tokens { function_values[0] };
3713
0
        auto counter_name = parse_counter_name(name_tokens);
3714
0
        if (!counter_name.has_value())
3715
0
            return nullptr;
3716
3717
0
        TokenStream string_tokens { function_values[1] };
3718
0
        string_tokens.discard_whitespace();
3719
0
        auto join_string = parse_string_value(string_tokens);
3720
0
        string_tokens.discard_whitespace();
3721
0
        if (!join_string || string_tokens.has_next_token())
3722
0
            return nullptr;
3723
3724
0
        RefPtr<CSSStyleValue> counter_style;
3725
0
        if (function_values.size() > 2) {
3726
0
            TokenStream counter_style_tokens { function_values[2] };
3727
0
            counter_style = parse_counter_style(counter_style_tokens);
3728
0
            if (!counter_style)
3729
0
                return nullptr;
3730
0
        } else {
3731
            // In both cases, if the <counter-style> argument is omitted it defaults to `decimal`.
3732
0
            counter_style = CustomIdentStyleValue::create("decimal"_fly_string);
3733
0
        }
3734
3735
0
        transaction.commit();
3736
0
        return CounterStyleValue::create_counters(counter_name.release_value(), join_string->string_value(), counter_style.release_nonnull());
3737
0
    }
3738
3739
0
    return nullptr;
3740
0
}
3741
3742
RefPtr<CSSStyleValue> Parser::parse_counter_definitions_value(TokenStream<ComponentValue>& tokens, AllowReversed allow_reversed, i32 default_value_if_not_reversed)
3743
0
{
3744
    // If AllowReversed is Yes, parses:
3745
    //   [ <counter-name> <integer>? | <reversed-counter-name> <integer>? ]+
3746
    // Otherwise parses:
3747
    //   [ <counter-name> <integer>? ]+
3748
3749
    // FIXME: This disabled parsing of `reversed()` counters. Remove this line once they're supported.
3750
0
    allow_reversed = AllowReversed::No;
3751
3752
0
    auto transaction = tokens.begin_transaction();
3753
0
    tokens.discard_whitespace();
3754
3755
0
    Vector<CounterDefinition> counter_definitions;
3756
0
    while (tokens.has_next_token()) {
3757
0
        auto per_item_transaction = tokens.begin_transaction();
3758
0
        CounterDefinition definition {};
3759
3760
        // <counter-name> | <reversed-counter-name>
3761
0
        auto& token = tokens.consume_a_token();
3762
0
        if (token.is(Token::Type::Ident)) {
3763
0
            definition.name = token.token().ident();
3764
0
            definition.is_reversed = false;
3765
0
        } else if (allow_reversed == AllowReversed::Yes && token.is_function("reversed"sv)) {
3766
0
            TokenStream function_tokens { token.function().value };
3767
0
            function_tokens.discard_whitespace();
3768
0
            auto& name_token = function_tokens.consume_a_token();
3769
0
            if (!name_token.is(Token::Type::Ident))
3770
0
                break;
3771
0
            function_tokens.discard_whitespace();
3772
0
            if (function_tokens.has_next_token())
3773
0
                break;
3774
3775
0
            definition.name = name_token.token().ident();
3776
0
            definition.is_reversed = true;
3777
0
        } else {
3778
0
            break;
3779
0
        }
3780
0
        tokens.discard_whitespace();
3781
3782
        // <integer>?
3783
0
        definition.value = parse_integer_value(tokens);
3784
0
        if (!definition.value && !definition.is_reversed)
3785
0
            definition.value = IntegerStyleValue::create(default_value_if_not_reversed);
3786
3787
0
        counter_definitions.append(move(definition));
3788
0
        tokens.discard_whitespace();
3789
0
        per_item_transaction.commit();
3790
0
    }
3791
3792
0
    if (counter_definitions.is_empty())
3793
0
        return {};
3794
3795
0
    transaction.commit();
3796
0
    return CounterDefinitionsStyleValue::create(move(counter_definitions));
3797
0
}
3798
3799
RefPtr<CSSStyleValue> Parser::parse_ratio_value(TokenStream<ComponentValue>& tokens)
3800
0
{
3801
0
    if (auto ratio = parse_ratio(tokens); ratio.has_value())
3802
0
        return RatioStyleValue::create(ratio.release_value());
3803
0
    return nullptr;
3804
0
}
3805
3806
RefPtr<StringStyleValue> Parser::parse_string_value(TokenStream<ComponentValue>& tokens)
3807
0
{
3808
0
    auto peek = tokens.next_token();
3809
0
    if (peek.is(Token::Type::String)) {
3810
0
        tokens.discard_a_token();
3811
0
        return StringStyleValue::create(peek.token().string());
3812
0
    }
3813
3814
0
    return nullptr;
3815
0
}
3816
3817
RefPtr<CSSStyleValue> Parser::parse_image_value(TokenStream<ComponentValue>& tokens)
3818
0
{
3819
0
    if (auto url = parse_url_function(tokens); url.has_value())
3820
0
        return ImageStyleValue::create(url.value());
3821
3822
0
    if (auto linear_gradient = parse_linear_gradient_function(tokens))
3823
0
        return linear_gradient;
3824
3825
0
    if (auto conic_gradient = parse_conic_gradient_function(tokens))
3826
0
        return conic_gradient;
3827
3828
0
    if (auto radial_gradient = parse_radial_gradient_function(tokens))
3829
0
        return radial_gradient;
3830
3831
0
    return nullptr;
3832
0
}
3833
3834
// https://svgwg.org/svg2-draft/painting.html#SpecifyingPaint
3835
RefPtr<CSSStyleValue> Parser::parse_paint_value(TokenStream<ComponentValue>& tokens)
3836
0
{
3837
    // `<paint> = none | <color> | <url> [none | <color>]? | context-fill | context-stroke`
3838
3839
0
    auto parse_color_or_none = [&]() -> Optional<RefPtr<CSSStyleValue>> {
3840
0
        if (auto color = parse_color_value(tokens))
3841
0
            return color;
3842
3843
        // NOTE: <color> also accepts identifiers, so we do this identifier check last.
3844
0
        if (tokens.next_token().is(Token::Type::Ident)) {
3845
0
            auto maybe_keyword = keyword_from_string(tokens.next_token().token().ident());
3846
0
            if (maybe_keyword.has_value()) {
3847
                // FIXME: Accept `context-fill` and `context-stroke`
3848
0
                switch (*maybe_keyword) {
3849
0
                case Keyword::None:
3850
0
                    tokens.discard_a_token();
3851
0
                    return CSSKeywordValue::create(*maybe_keyword);
3852
0
                default:
3853
0
                    return nullptr;
3854
0
                }
3855
0
            }
3856
0
        }
3857
3858
0
        return OptionalNone {};
3859
0
    };
3860
3861
    // FIMXE: Allow context-fill/context-stroke here
3862
0
    if (auto color_or_none = parse_color_or_none(); color_or_none.has_value())
3863
0
        return *color_or_none;
3864
3865
0
    if (auto url = parse_url_value(tokens)) {
3866
0
        tokens.discard_whitespace();
3867
0
        if (auto color_or_none = parse_color_or_none(); color_or_none == nullptr) {
3868
            // Fail to parse if the fallback is invalid, but otherwise ignore it.
3869
            // FIXME: Use fallback color
3870
0
            return nullptr;
3871
0
        }
3872
0
        return url;
3873
0
    }
3874
3875
0
    return nullptr;
3876
0
}
3877
3878
// https://www.w3.org/TR/css-values-4/#position
3879
RefPtr<PositionStyleValue> Parser::parse_position_value(TokenStream<ComponentValue>& tokens, PositionParsingMode position_parsing_mode)
3880
0
{
3881
0
    auto parse_position_edge = [](ComponentValue const& token) -> Optional<PositionEdge> {
3882
0
        if (!token.is(Token::Type::Ident))
3883
0
            return {};
3884
0
        auto keyword = keyword_from_string(token.token().ident());
3885
0
        if (!keyword.has_value())
3886
0
            return {};
3887
0
        return keyword_to_position_edge(*keyword);
3888
0
    };
3889
3890
0
    auto parse_length_percentage = [&](ComponentValue const& token) -> Optional<LengthPercentage> {
3891
0
        if (token.is(Token::Type::EndOfFile))
3892
0
            return {};
3893
3894
0
        if (auto dimension = parse_dimension(token); dimension.has_value()) {
3895
0
            if (dimension->is_length_percentage())
3896
0
                return dimension->length_percentage();
3897
0
            return {};
3898
0
        }
3899
3900
0
        if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_length_percentage())
3901
0
            return LengthPercentage { calc.release_nonnull() };
3902
3903
0
        return {};
3904
0
    };
3905
3906
0
    auto is_horizontal = [](PositionEdge edge, bool accept_center) -> bool {
3907
0
        switch (edge) {
3908
0
        case PositionEdge::Left:
3909
0
        case PositionEdge::Right:
3910
0
            return true;
3911
0
        case PositionEdge::Center:
3912
0
            return accept_center;
3913
0
        default:
3914
0
            return false;
3915
0
        }
3916
0
    };
3917
3918
0
    auto is_vertical = [](PositionEdge edge, bool accept_center) -> bool {
3919
0
        switch (edge) {
3920
0
        case PositionEdge::Top:
3921
0
        case PositionEdge::Bottom:
3922
0
            return true;
3923
0
        case PositionEdge::Center:
3924
0
            return accept_center;
3925
0
        default:
3926
0
            return false;
3927
0
        }
3928
0
    };
3929
3930
0
    auto make_edge_style_value = [](PositionEdge position_edge, bool is_horizontal) -> NonnullRefPtr<EdgeStyleValue> {
3931
0
        if (position_edge == PositionEdge::Center)
3932
0
            return EdgeStyleValue::create(is_horizontal ? PositionEdge::Left : PositionEdge::Top, Percentage { 50 });
3933
0
        return EdgeStyleValue::create(position_edge, Length::make_px(0));
3934
0
    };
3935
3936
    // <position> = [
3937
    //   [ left | center | right | top | bottom | <length-percentage> ]
3938
    // |
3939
    //   [ left | center | right ] && [ top | center | bottom ]
3940
    // |
3941
    //   [ left | center | right | <length-percentage> ]
3942
    //   [ top | center | bottom | <length-percentage> ]
3943
    // |
3944
    //   [ [ left | right ] <length-percentage> ] &&
3945
    //   [ [ top | bottom ] <length-percentage> ]
3946
    // ]
3947
3948
    // [ left | center | right | top | bottom | <length-percentage> ]
3949
0
    auto alternative_1 = [&]() -> RefPtr<PositionStyleValue> {
3950
0
        auto transaction = tokens.begin_transaction();
3951
3952
0
        tokens.discard_whitespace();
3953
0
        auto const& token = tokens.consume_a_token();
3954
3955
        // [ left | center | right | top | bottom ]
3956
0
        if (auto maybe_edge = parse_position_edge(token); maybe_edge.has_value()) {
3957
0
            auto edge = maybe_edge.release_value();
3958
0
            transaction.commit();
3959
3960
            // [ left | right ]
3961
0
            if (is_horizontal(edge, false))
3962
0
                return PositionStyleValue::create(make_edge_style_value(edge, true), make_edge_style_value(PositionEdge::Center, false));
3963
3964
            // [ top | bottom ]
3965
0
            if (is_vertical(edge, false))
3966
0
                return PositionStyleValue::create(make_edge_style_value(PositionEdge::Center, true), make_edge_style_value(edge, false));
3967
3968
            // [ center ]
3969
0
            VERIFY(edge == PositionEdge::Center);
3970
0
            return PositionStyleValue::create(make_edge_style_value(PositionEdge::Center, true), make_edge_style_value(PositionEdge::Center, false));
3971
0
        }
3972
3973
        // [ <length-percentage> ]
3974
0
        if (auto maybe_percentage = parse_length_percentage(token); maybe_percentage.has_value()) {
3975
0
            transaction.commit();
3976
0
            return PositionStyleValue::create(EdgeStyleValue::create(PositionEdge::Left, *maybe_percentage), make_edge_style_value(PositionEdge::Center, false));
3977
0
        }
3978
3979
0
        return nullptr;
3980
0
    };
3981
3982
    // [ left | center | right ] && [ top | center | bottom ]
3983
0
    auto alternative_2 = [&]() -> RefPtr<PositionStyleValue> {
3984
0
        auto transaction = tokens.begin_transaction();
3985
3986
0
        tokens.discard_whitespace();
3987
3988
        // Parse out two position edges
3989
0
        auto maybe_first_edge = parse_position_edge(tokens.consume_a_token());
3990
0
        if (!maybe_first_edge.has_value())
3991
0
            return nullptr;
3992
3993
0
        auto first_edge = maybe_first_edge.release_value();
3994
0
        tokens.discard_whitespace();
3995
3996
0
        auto maybe_second_edge = parse_position_edge(tokens.consume_a_token());
3997
0
        if (!maybe_second_edge.has_value())
3998
0
            return nullptr;
3999
4000
0
        auto second_edge = maybe_second_edge.release_value();
4001
4002
        // If 'left' or 'right' is given, that position is X and the other is Y.
4003
        // Conversely -
4004
        // If 'top' or 'bottom' is given, that position is Y and the other is X.
4005
0
        if (is_vertical(first_edge, false) || is_horizontal(second_edge, false))
4006
0
            swap(first_edge, second_edge);
4007
4008
        // [ left | center | right ] [ top | bottom | center ]
4009
0
        if (is_horizontal(first_edge, true) && is_vertical(second_edge, true)) {
4010
0
            transaction.commit();
4011
0
            return PositionStyleValue::create(make_edge_style_value(first_edge, true), make_edge_style_value(second_edge, false));
4012
0
        }
4013
4014
0
        return nullptr;
4015
0
    };
4016
4017
    // [ left | center | right | <length-percentage> ]
4018
    // [ top | center | bottom | <length-percentage> ]
4019
0
    auto alternative_3 = [&]() -> RefPtr<PositionStyleValue> {
4020
0
        auto transaction = tokens.begin_transaction();
4021
4022
0
        auto parse_position_or_length = [&](bool as_horizontal) -> RefPtr<EdgeStyleValue> {
4023
0
            tokens.discard_whitespace();
4024
0
            auto const& token = tokens.consume_a_token();
4025
4026
0
            if (auto maybe_position = parse_position_edge(token); maybe_position.has_value()) {
4027
0
                auto position = maybe_position.release_value();
4028
0
                bool valid = as_horizontal ? is_horizontal(position, true) : is_vertical(position, true);
4029
0
                if (!valid)
4030
0
                    return nullptr;
4031
0
                return make_edge_style_value(position, as_horizontal);
4032
0
            }
4033
4034
0
            auto maybe_length = parse_length_percentage(token);
4035
0
            if (!maybe_length.has_value())
4036
0
                return nullptr;
4037
4038
0
            return EdgeStyleValue::create(as_horizontal ? PositionEdge::Left : PositionEdge::Top, maybe_length.release_value());
4039
0
        };
4040
4041
        // [ left | center | right | <length-percentage> ]
4042
0
        auto horizontal_edge = parse_position_or_length(true);
4043
0
        if (!horizontal_edge)
4044
0
            return nullptr;
4045
4046
        // [ top | center | bottom | <length-percentage> ]
4047
0
        auto vertical_edge = parse_position_or_length(false);
4048
0
        if (!vertical_edge)
4049
0
            return nullptr;
4050
4051
0
        transaction.commit();
4052
0
        return PositionStyleValue::create(horizontal_edge.release_nonnull(), vertical_edge.release_nonnull());
4053
0
    };
4054
4055
    // [ [ left | right ] <length-percentage> ] &&
4056
    // [ [ top | bottom ] <length-percentage> ]
4057
0
    auto alternative_4 = [&]() -> RefPtr<PositionStyleValue> {
4058
0
        struct PositionAndLength {
4059
0
            PositionEdge position;
4060
0
            LengthPercentage length;
4061
0
        };
4062
4063
0
        auto parse_position_and_length = [&]() -> Optional<PositionAndLength> {
4064
0
            tokens.discard_whitespace();
4065
4066
0
            auto maybe_position = parse_position_edge(tokens.consume_a_token());
4067
0
            if (!maybe_position.has_value())
4068
0
                return {};
4069
4070
0
            tokens.discard_whitespace();
4071
4072
0
            auto maybe_length = parse_length_percentage(tokens.consume_a_token());
4073
0
            if (!maybe_length.has_value())
4074
0
                return {};
4075
4076
0
            return PositionAndLength {
4077
0
                .position = maybe_position.release_value(),
4078
0
                .length = maybe_length.release_value(),
4079
0
            };
4080
0
        };
4081
4082
0
        auto transaction = tokens.begin_transaction();
4083
4084
0
        auto maybe_group1 = parse_position_and_length();
4085
0
        if (!maybe_group1.has_value())
4086
0
            return nullptr;
4087
4088
0
        auto maybe_group2 = parse_position_and_length();
4089
0
        if (!maybe_group2.has_value())
4090
0
            return nullptr;
4091
4092
0
        auto group1 = maybe_group1.release_value();
4093
0
        auto group2 = maybe_group2.release_value();
4094
4095
        // [ [ left | right ] <length-percentage> ] [ [ top | bottom ] <length-percentage> ]
4096
0
        if (is_horizontal(group1.position, false) && is_vertical(group2.position, false)) {
4097
0
            transaction.commit();
4098
0
            return PositionStyleValue::create(EdgeStyleValue::create(group1.position, group1.length), EdgeStyleValue::create(group2.position, group2.length));
4099
0
        }
4100
4101
        // [ [ top | bottom ] <length-percentage> ] [ [ left | right ] <length-percentage> ]
4102
0
        if (is_vertical(group1.position, false) && is_horizontal(group2.position, false)) {
4103
0
            transaction.commit();
4104
0
            return PositionStyleValue::create(EdgeStyleValue::create(group2.position, group2.length), EdgeStyleValue::create(group1.position, group1.length));
4105
0
        }
4106
4107
0
        return nullptr;
4108
0
    };
4109
4110
    // The extra 3-value syntax that's allowed for background-position:
4111
    // [ center | [ left | right ] <length-percentage>? ] &&
4112
    // [ center | [ top | bottom ] <length-percentage>? ]
4113
0
    auto alternative_5_for_background_position = [&]() -> RefPtr<PositionStyleValue> {
4114
0
        auto transaction = tokens.begin_transaction();
4115
4116
0
        struct PositionAndMaybeLength {
4117
0
            PositionEdge position;
4118
0
            Optional<LengthPercentage> length;
4119
0
        };
4120
4121
        // [ <position> <length-percentage>? ]
4122
0
        auto parse_position_and_maybe_length = [&]() -> Optional<PositionAndMaybeLength> {
4123
0
            tokens.discard_whitespace();
4124
4125
0
            auto maybe_position = parse_position_edge(tokens.consume_a_token());
4126
0
            if (!maybe_position.has_value())
4127
0
                return {};
4128
4129
0
            tokens.discard_whitespace();
4130
4131
0
            auto maybe_length = parse_length_percentage(tokens.next_token());
4132
0
            if (maybe_length.has_value()) {
4133
                // 'center' cannot be followed by a <length-percentage>
4134
0
                if (maybe_position.value() == PositionEdge::Center && maybe_length.has_value())
4135
0
                    return {};
4136
0
                tokens.discard_a_token();
4137
0
            }
4138
4139
0
            return PositionAndMaybeLength {
4140
0
                .position = maybe_position.release_value(),
4141
0
                .length = move(maybe_length),
4142
0
            };
4143
0
        };
4144
4145
0
        auto maybe_group1 = parse_position_and_maybe_length();
4146
0
        if (!maybe_group1.has_value())
4147
0
            return nullptr;
4148
4149
0
        auto maybe_group2 = parse_position_and_maybe_length();
4150
0
        if (!maybe_group2.has_value())
4151
0
            return nullptr;
4152
4153
0
        auto group1 = maybe_group1.release_value();
4154
0
        auto group2 = maybe_group2.release_value();
4155
4156
        // 2-value or 4-value if both <length-percentage>s are present or missing.
4157
0
        if (group1.length.has_value() == group2.length.has_value())
4158
0
            return nullptr;
4159
4160
        // If 'left' or 'right' is given, that position is X and the other is Y.
4161
        // Conversely -
4162
        // If 'top' or 'bottom' is given, that position is Y and the other is X.
4163
0
        if (is_vertical(group1.position, false) || is_horizontal(group2.position, false))
4164
0
            swap(group1, group2);
4165
4166
        // [ center | [ left | right ] ]
4167
0
        if (!is_horizontal(group1.position, true))
4168
0
            return nullptr;
4169
4170
        // [ center | [ top | bottom ] ]
4171
0
        if (!is_vertical(group2.position, true))
4172
0
            return nullptr;
4173
4174
0
        auto to_style_value = [&](PositionAndMaybeLength const& group, bool is_horizontal) -> NonnullRefPtr<EdgeStyleValue> {
4175
0
            if (group.position == PositionEdge::Center)
4176
0
                return EdgeStyleValue::create(is_horizontal ? PositionEdge::Left : PositionEdge::Top, Percentage { 50 });
4177
4178
0
            return EdgeStyleValue::create(group.position, group.length.value_or(Length::make_px(0)));
4179
0
        };
4180
4181
0
        transaction.commit();
4182
0
        return PositionStyleValue::create(to_style_value(group1, true), to_style_value(group2, false));
4183
0
    };
4184
4185
    // Note: The alternatives must be attempted in this order since shorter alternatives can match a prefix of longer ones.
4186
0
    if (auto position = alternative_4())
4187
0
        return position;
4188
0
    if (position_parsing_mode == PositionParsingMode::BackgroundPosition) {
4189
0
        if (auto position = alternative_5_for_background_position())
4190
0
            return position;
4191
0
    }
4192
0
    if (auto position = alternative_3())
4193
0
        return position;
4194
0
    if (auto position = alternative_2())
4195
0
        return position;
4196
0
    if (auto position = alternative_1())
4197
0
        return position;
4198
0
    return nullptr;
4199
0
}
4200
4201
template<typename ParseFunction>
4202
RefPtr<CSSStyleValue> Parser::parse_comma_separated_value_list(TokenStream<ComponentValue>& tokens, ParseFunction parse_one_value)
4203
0
{
4204
0
    auto first = parse_one_value(tokens);
4205
0
    if (!first || !tokens.has_next_token())
4206
0
        return first;
4207
4208
0
    StyleValueVector values;
4209
0
    values.append(first.release_nonnull());
4210
4211
0
    while (tokens.has_next_token()) {
4212
0
        if (!tokens.consume_a_token().is(Token::Type::Comma))
4213
0
            return nullptr;
4214
4215
0
        if (auto maybe_value = parse_one_value(tokens)) {
4216
0
            values.append(maybe_value.release_nonnull());
4217
0
            continue;
4218
0
        }
4219
0
        return nullptr;
4220
0
    }
4221
4222
0
    return StyleValueList::create(move(values), StyleValueList::Separator::Comma);
4223
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)
4224
4225
RefPtr<CSSStyleValue> Parser::parse_simple_comma_separated_value_list(PropertyID property_id, TokenStream<ComponentValue>& tokens)
4226
0
{
4227
0
    return parse_comma_separated_value_list(tokens, [this, property_id](auto& tokens) -> RefPtr<CSSStyleValue> {
4228
0
        if (auto value = parse_css_value_for_property(property_id, tokens))
4229
0
            return value;
4230
0
        tokens.reconsume_current_input_token();
4231
0
        return nullptr;
4232
0
    });
4233
0
}
4234
4235
RefPtr<CSSStyleValue> Parser::parse_all_as_single_keyword_value(TokenStream<ComponentValue>& tokens, Keyword keyword)
4236
0
{
4237
0
    auto transaction = tokens.begin_transaction();
4238
0
    tokens.discard_whitespace();
4239
0
    auto keyword_value = parse_keyword_value(tokens);
4240
0
    tokens.discard_whitespace();
4241
4242
0
    if (tokens.has_next_token() || !keyword_value || keyword_value->to_keyword() != keyword)
4243
0
        return {};
4244
4245
0
    transaction.commit();
4246
0
    return keyword_value;
4247
0
}
4248
4249
static void remove_property(Vector<PropertyID>& properties, PropertyID property_to_remove)
4250
0
{
4251
0
    properties.remove_first_matching([&](auto it) { return it == property_to_remove; });
4252
0
}
4253
4254
// https://www.w3.org/TR/css-sizing-4/#aspect-ratio
4255
RefPtr<CSSStyleValue> Parser::parse_aspect_ratio_value(TokenStream<ComponentValue>& tokens)
4256
0
{
4257
    // `auto || <ratio>`
4258
0
    RefPtr<CSSStyleValue> auto_value;
4259
0
    RefPtr<CSSStyleValue> ratio_value;
4260
4261
0
    auto transaction = tokens.begin_transaction();
4262
0
    while (tokens.has_next_token()) {
4263
0
        auto maybe_value = parse_css_value_for_property(PropertyID::AspectRatio, tokens);
4264
0
        if (!maybe_value)
4265
0
            return nullptr;
4266
4267
0
        if (maybe_value->is_ratio()) {
4268
0
            if (ratio_value)
4269
0
                return nullptr;
4270
0
            ratio_value = maybe_value.release_nonnull();
4271
0
            continue;
4272
0
        }
4273
4274
0
        if (maybe_value->is_keyword() && maybe_value->as_keyword().keyword() == Keyword::Auto) {
4275
0
            if (auto_value)
4276
0
                return nullptr;
4277
0
            auto_value = maybe_value.release_nonnull();
4278
0
            continue;
4279
0
        }
4280
4281
0
        return nullptr;
4282
0
    }
4283
4284
0
    if (auto_value && ratio_value) {
4285
0
        transaction.commit();
4286
0
        return StyleValueList::create(
4287
0
            StyleValueVector { auto_value.release_nonnull(), ratio_value.release_nonnull() },
4288
0
            StyleValueList::Separator::Space);
4289
0
    }
4290
4291
0
    if (ratio_value) {
4292
0
        transaction.commit();
4293
0
        return ratio_value.release_nonnull();
4294
0
    }
4295
4296
0
    if (auto_value) {
4297
0
        transaction.commit();
4298
0
        return auto_value.release_nonnull();
4299
0
    }
4300
4301
0
    return nullptr;
4302
0
}
4303
4304
RefPtr<CSSStyleValue> Parser::parse_background_value(TokenStream<ComponentValue>& tokens)
4305
0
{
4306
0
    auto transaction = tokens.begin_transaction();
4307
4308
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) {
4309
0
        return ShorthandStyleValue::create(PropertyID::Background,
4310
0
            { PropertyID::BackgroundColor, PropertyID::BackgroundImage, PropertyID::BackgroundPosition, PropertyID::BackgroundSize, PropertyID::BackgroundRepeat, PropertyID::BackgroundAttachment, PropertyID::BackgroundOrigin, PropertyID::BackgroundClip },
4311
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) });
4312
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
4313
4314
0
    StyleValueVector background_images;
4315
0
    StyleValueVector background_positions;
4316
0
    StyleValueVector background_sizes;
4317
0
    StyleValueVector background_repeats;
4318
0
    StyleValueVector background_attachments;
4319
0
    StyleValueVector background_clips;
4320
0
    StyleValueVector background_origins;
4321
0
    RefPtr<CSSStyleValue> background_color;
4322
4323
0
    auto initial_background_image = property_initial_value(m_context.realm(), PropertyID::BackgroundImage);
4324
0
    auto initial_background_position = property_initial_value(m_context.realm(), PropertyID::BackgroundPosition);
4325
0
    auto initial_background_size = property_initial_value(m_context.realm(), PropertyID::BackgroundSize);
4326
0
    auto initial_background_repeat = property_initial_value(m_context.realm(), PropertyID::BackgroundRepeat);
4327
0
    auto initial_background_attachment = property_initial_value(m_context.realm(), PropertyID::BackgroundAttachment);
4328
0
    auto initial_background_clip = property_initial_value(m_context.realm(), PropertyID::BackgroundClip);
4329
0
    auto initial_background_origin = property_initial_value(m_context.realm(), PropertyID::BackgroundOrigin);
4330
0
    auto initial_background_color = property_initial_value(m_context.realm(), PropertyID::BackgroundColor);
4331
4332
    // Per-layer values
4333
0
    RefPtr<CSSStyleValue> background_image;
4334
0
    RefPtr<CSSStyleValue> background_position;
4335
0
    RefPtr<CSSStyleValue> background_size;
4336
0
    RefPtr<CSSStyleValue> background_repeat;
4337
0
    RefPtr<CSSStyleValue> background_attachment;
4338
0
    RefPtr<CSSStyleValue> background_clip;
4339
0
    RefPtr<CSSStyleValue> background_origin;
4340
4341
0
    bool has_multiple_layers = false;
4342
    // BackgroundSize is always parsed as part of BackgroundPosition, so we don't include it here.
4343
0
    Vector<PropertyID> remaining_layer_properties {
4344
0
        PropertyID::BackgroundAttachment,
4345
0
        PropertyID::BackgroundClip,
4346
0
        PropertyID::BackgroundColor,
4347
0
        PropertyID::BackgroundImage,
4348
0
        PropertyID::BackgroundOrigin,
4349
0
        PropertyID::BackgroundPosition,
4350
0
        PropertyID::BackgroundRepeat,
4351
0
    };
4352
4353
0
    auto background_layer_is_valid = [&](bool allow_background_color) -> bool {
4354
0
        if (allow_background_color) {
4355
0
            if (background_color)
4356
0
                return true;
4357
0
        } else {
4358
0
            if (background_color)
4359
0
                return false;
4360
0
        }
4361
0
        return background_image || background_position || background_size || background_repeat || background_attachment || background_clip || background_origin;
4362
0
    };
4363
4364
0
    auto complete_background_layer = [&]() {
4365
0
        background_images.append(background_image ? background_image.release_nonnull() : initial_background_image);
4366
0
        background_positions.append(background_position ? background_position.release_nonnull() : initial_background_position);
4367
0
        background_sizes.append(background_size ? background_size.release_nonnull() : initial_background_size);
4368
0
        background_repeats.append(background_repeat ? background_repeat.release_nonnull() : initial_background_repeat);
4369
0
        background_attachments.append(background_attachment ? background_attachment.release_nonnull() : initial_background_attachment);
4370
4371
0
        if (!background_origin && !background_clip) {
4372
0
            background_origin = initial_background_origin;
4373
0
            background_clip = initial_background_clip;
4374
0
        } else if (!background_clip) {
4375
0
            background_clip = background_origin;
4376
0
        }
4377
0
        background_origins.append(background_origin.release_nonnull());
4378
0
        background_clips.append(background_clip.release_nonnull());
4379
4380
0
        background_image = nullptr;
4381
0
        background_position = nullptr;
4382
0
        background_size = nullptr;
4383
0
        background_repeat = nullptr;
4384
0
        background_attachment = nullptr;
4385
0
        background_clip = nullptr;
4386
0
        background_origin = nullptr;
4387
4388
0
        remaining_layer_properties.clear_with_capacity();
4389
0
        remaining_layer_properties.unchecked_append(PropertyID::BackgroundAttachment);
4390
0
        remaining_layer_properties.unchecked_append(PropertyID::BackgroundClip);
4391
0
        remaining_layer_properties.unchecked_append(PropertyID::BackgroundColor);
4392
0
        remaining_layer_properties.unchecked_append(PropertyID::BackgroundImage);
4393
0
        remaining_layer_properties.unchecked_append(PropertyID::BackgroundOrigin);
4394
0
        remaining_layer_properties.unchecked_append(PropertyID::BackgroundPosition);
4395
0
        remaining_layer_properties.unchecked_append(PropertyID::BackgroundRepeat);
4396
0
    };
4397
4398
0
    while (tokens.has_next_token()) {
4399
0
        if (tokens.next_token().is(Token::Type::Comma)) {
4400
0
            has_multiple_layers = true;
4401
0
            if (!background_layer_is_valid(false))
4402
0
                return nullptr;
4403
0
            complete_background_layer();
4404
0
            tokens.discard_a_token();
4405
0
            continue;
4406
0
        }
4407
4408
0
        auto value_and_property = parse_css_value_for_properties(remaining_layer_properties, tokens);
4409
0
        if (!value_and_property.has_value())
4410
0
            return nullptr;
4411
0
        auto& value = value_and_property->style_value;
4412
0
        remove_property(remaining_layer_properties, value_and_property->property);
4413
4414
0
        switch (value_and_property->property) {
4415
0
        case PropertyID::BackgroundAttachment:
4416
0
            VERIFY(!background_attachment);
4417
0
            background_attachment = value.release_nonnull();
4418
0
            continue;
4419
0
        case PropertyID::BackgroundColor:
4420
0
            VERIFY(!background_color);
4421
0
            background_color = value.release_nonnull();
4422
0
            continue;
4423
0
        case PropertyID::BackgroundImage:
4424
0
            VERIFY(!background_image);
4425
0
            background_image = value.release_nonnull();
4426
0
            continue;
4427
0
        case PropertyID::BackgroundClip:
4428
0
        case PropertyID::BackgroundOrigin: {
4429
            // background-origin and background-clip accept the same values. From the spec:
4430
            //   "If one <box> value is present then it sets both background-origin and background-clip to that value.
4431
            //    If two values are present, then the first sets background-origin and the second background-clip."
4432
            //        - https://www.w3.org/TR/css-backgrounds-3/#background
4433
            // So, we put the first one in background-origin, then if we get a second, we put it in background-clip.
4434
            // If we only get one, we copy the value before creating the ShorthandStyleValue.
4435
0
            if (!background_origin) {
4436
0
                background_origin = value.release_nonnull();
4437
0
            } else if (!background_clip) {
4438
0
                background_clip = value.release_nonnull();
4439
0
            } else {
4440
0
                VERIFY_NOT_REACHED();
4441
0
            }
4442
0
            continue;
4443
0
        }
4444
0
        case PropertyID::BackgroundPosition: {
4445
0
            VERIFY(!background_position);
4446
0
            background_position = value.release_nonnull();
4447
4448
            // Attempt to parse `/ <background-size>`
4449
0
            auto background_size_transaction = tokens.begin_transaction();
4450
0
            auto& maybe_slash = tokens.consume_a_token();
4451
0
            if (maybe_slash.is_delim('/')) {
4452
0
                if (auto maybe_background_size = parse_single_background_size_value(tokens)) {
4453
0
                    background_size_transaction.commit();
4454
0
                    background_size = maybe_background_size.release_nonnull();
4455
0
                    continue;
4456
0
                }
4457
0
                return nullptr;
4458
0
            }
4459
0
            continue;
4460
0
        }
4461
0
        case PropertyID::BackgroundRepeat: {
4462
0
            VERIFY(!background_repeat);
4463
0
            tokens.reconsume_current_input_token();
4464
0
            if (auto maybe_repeat = parse_single_background_repeat_value(tokens)) {
4465
0
                background_repeat = maybe_repeat.release_nonnull();
4466
0
                continue;
4467
0
            }
4468
0
            return nullptr;
4469
0
        }
4470
0
        default:
4471
0
            VERIFY_NOT_REACHED();
4472
0
        }
4473
4474
0
        return nullptr;
4475
0
    }
4476
4477
0
    if (!background_layer_is_valid(true))
4478
0
        return nullptr;
4479
4480
    // We only need to create StyleValueLists if there are multiple layers.
4481
    // Otherwise, we can pass the single StyleValues directly.
4482
0
    if (has_multiple_layers) {
4483
0
        complete_background_layer();
4484
4485
0
        if (!background_color)
4486
0
            background_color = initial_background_color;
4487
0
        transaction.commit();
4488
0
        return make_background_shorthand(
4489
0
            background_color.release_nonnull(),
4490
0
            StyleValueList::create(move(background_images), StyleValueList::Separator::Comma),
4491
0
            StyleValueList::create(move(background_positions), StyleValueList::Separator::Comma),
4492
0
            StyleValueList::create(move(background_sizes), StyleValueList::Separator::Comma),
4493
0
            StyleValueList::create(move(background_repeats), StyleValueList::Separator::Comma),
4494
0
            StyleValueList::create(move(background_attachments), StyleValueList::Separator::Comma),
4495
0
            StyleValueList::create(move(background_origins), StyleValueList::Separator::Comma),
4496
0
            StyleValueList::create(move(background_clips), StyleValueList::Separator::Comma));
4497
0
    }
4498
4499
0
    if (!background_color)
4500
0
        background_color = initial_background_color;
4501
0
    if (!background_image)
4502
0
        background_image = initial_background_image;
4503
0
    if (!background_position)
4504
0
        background_position = initial_background_position;
4505
0
    if (!background_size)
4506
0
        background_size = initial_background_size;
4507
0
    if (!background_repeat)
4508
0
        background_repeat = initial_background_repeat;
4509
0
    if (!background_attachment)
4510
0
        background_attachment = initial_background_attachment;
4511
4512
0
    if (!background_origin && !background_clip) {
4513
0
        background_origin = initial_background_origin;
4514
0
        background_clip = initial_background_clip;
4515
0
    } else if (!background_clip) {
4516
0
        background_clip = background_origin;
4517
0
    }
4518
4519
0
    transaction.commit();
4520
0
    return make_background_shorthand(
4521
0
        background_color.release_nonnull(),
4522
0
        background_image.release_nonnull(),
4523
0
        background_position.release_nonnull(),
4524
0
        background_size.release_nonnull(),
4525
0
        background_repeat.release_nonnull(),
4526
0
        background_attachment.release_nonnull(),
4527
0
        background_origin.release_nonnull(),
4528
0
        background_clip.release_nonnull());
4529
0
}
4530
4531
static Optional<LengthPercentage> style_value_to_length_percentage(auto value)
4532
0
{
4533
0
    if (value->is_percentage())
4534
0
        return LengthPercentage { value->as_percentage().percentage() };
4535
0
    if (value->is_length())
4536
0
        return LengthPercentage { value->as_length().length() };
4537
0
    if (value->is_math())
4538
0
        return LengthPercentage { value->as_math() };
4539
0
    return {};
4540
0
}
4541
4542
RefPtr<CSSStyleValue> Parser::parse_single_background_position_x_or_y_value(TokenStream<ComponentValue>& tokens, PropertyID property)
4543
0
{
4544
0
    PositionEdge relative_edge {};
4545
0
    if (property == PropertyID::BackgroundPositionX) {
4546
        // [ center | [ [ left | right | x-start | x-end ]? <length-percentage>? ]! ]#
4547
0
        relative_edge = PositionEdge::Left;
4548
0
    } else if (property == PropertyID::BackgroundPositionY) {
4549
        // [ center | [ [ top | bottom | y-start | y-end ]? <length-percentage>? ]! ]#
4550
0
        relative_edge = PositionEdge::Top;
4551
0
    } else {
4552
0
        VERIFY_NOT_REACHED();
4553
0
    }
4554
4555
0
    auto transaction = tokens.begin_transaction();
4556
0
    if (!tokens.has_next_token())
4557
0
        return nullptr;
4558
4559
0
    auto value = parse_css_value_for_property(property, tokens);
4560
0
    if (!value)
4561
0
        return nullptr;
4562
4563
0
    if (value->is_keyword()) {
4564
0
        auto keyword = value->to_keyword();
4565
0
        if (keyword == Keyword::Center) {
4566
0
            transaction.commit();
4567
0
            return EdgeStyleValue::create(relative_edge, Percentage { 50 });
4568
0
        }
4569
0
        if (auto edge = keyword_to_position_edge(keyword); edge.has_value()) {
4570
0
            relative_edge = *edge;
4571
0
        } else {
4572
0
            return nullptr;
4573
0
        }
4574
0
        if (tokens.has_next_token()) {
4575
0
            value = parse_css_value_for_property(property, tokens);
4576
0
            if (!value) {
4577
0
                transaction.commit();
4578
0
                return EdgeStyleValue::create(relative_edge, Length::make_px(0));
4579
0
            }
4580
0
        }
4581
0
    }
4582
4583
0
    auto offset = style_value_to_length_percentage(value);
4584
0
    if (offset.has_value()) {
4585
0
        transaction.commit();
4586
0
        return EdgeStyleValue::create(relative_edge, *offset);
4587
0
    }
4588
4589
    // If no offset is provided create this element but with an offset of default value of zero
4590
0
    transaction.commit();
4591
0
    return EdgeStyleValue::create(relative_edge, Length::make_px(0));
4592
0
}
4593
4594
RefPtr<CSSStyleValue> Parser::parse_single_background_repeat_value(TokenStream<ComponentValue>& tokens)
4595
0
{
4596
0
    auto transaction = tokens.begin_transaction();
4597
4598
0
    auto is_directional_repeat = [](CSSStyleValue const& value) -> bool {
4599
0
        auto keyword = value.to_keyword();
4600
0
        return keyword == Keyword::RepeatX || keyword == Keyword::RepeatY;
4601
0
    };
4602
4603
0
    auto as_repeat = [](Keyword keyword) -> Optional<Repeat> {
4604
0
        switch (keyword) {
4605
0
        case Keyword::NoRepeat:
4606
0
            return Repeat::NoRepeat;
4607
0
        case Keyword::Repeat:
4608
0
            return Repeat::Repeat;
4609
0
        case Keyword::Round:
4610
0
            return Repeat::Round;
4611
0
        case Keyword::Space:
4612
0
            return Repeat::Space;
4613
0
        default:
4614
0
            return {};
4615
0
        }
4616
0
    };
4617
4618
0
    auto maybe_x_value = parse_css_value_for_property(PropertyID::BackgroundRepeat, tokens);
4619
0
    if (!maybe_x_value)
4620
0
        return nullptr;
4621
0
    auto x_value = maybe_x_value.release_nonnull();
4622
4623
0
    if (is_directional_repeat(*x_value)) {
4624
0
        auto keyword = x_value->to_keyword();
4625
0
        transaction.commit();
4626
0
        return BackgroundRepeatStyleValue::create(
4627
0
            keyword == Keyword::RepeatX ? Repeat::Repeat : Repeat::NoRepeat,
4628
0
            keyword == Keyword::RepeatX ? Repeat::NoRepeat : Repeat::Repeat);
4629
0
    }
4630
4631
0
    auto x_repeat = as_repeat(x_value->to_keyword());
4632
0
    if (!x_repeat.has_value())
4633
0
        return nullptr;
4634
4635
    // See if we have a second value for Y
4636
0
    auto maybe_y_value = parse_css_value_for_property(PropertyID::BackgroundRepeat, tokens);
4637
0
    if (!maybe_y_value) {
4638
        // We don't have a second value, so use x for both
4639
0
        transaction.commit();
4640
0
        return BackgroundRepeatStyleValue::create(x_repeat.value(), x_repeat.value());
4641
0
    }
4642
0
    auto y_value = maybe_y_value.release_nonnull();
4643
0
    if (is_directional_repeat(*y_value))
4644
0
        return nullptr;
4645
4646
0
    auto y_repeat = as_repeat(y_value->to_keyword());
4647
0
    if (!y_repeat.has_value())
4648
0
        return nullptr;
4649
4650
0
    transaction.commit();
4651
0
    return BackgroundRepeatStyleValue::create(x_repeat.value(), y_repeat.value());
4652
0
}
4653
4654
RefPtr<CSSStyleValue> Parser::parse_single_background_size_value(TokenStream<ComponentValue>& tokens)
4655
0
{
4656
0
    auto transaction = tokens.begin_transaction();
4657
4658
0
    auto get_length_percentage = [](CSSStyleValue& style_value) -> Optional<LengthPercentage> {
4659
0
        if (style_value.has_auto())
4660
0
            return LengthPercentage { Length::make_auto() };
4661
0
        if (style_value.is_percentage())
4662
0
            return LengthPercentage { style_value.as_percentage().percentage() };
4663
0
        if (style_value.is_length())
4664
0
            return LengthPercentage { style_value.as_length().length() };
4665
0
        if (style_value.is_math())
4666
0
            return LengthPercentage { style_value.as_math() };
4667
0
        return {};
4668
0
    };
4669
4670
0
    auto maybe_x_value = parse_css_value_for_property(PropertyID::BackgroundSize, tokens);
4671
0
    if (!maybe_x_value)
4672
0
        return nullptr;
4673
0
    auto x_value = maybe_x_value.release_nonnull();
4674
4675
0
    if (x_value->to_keyword() == Keyword::Cover || x_value->to_keyword() == Keyword::Contain) {
4676
0
        transaction.commit();
4677
0
        return x_value;
4678
0
    }
4679
4680
0
    auto maybe_y_value = parse_css_value_for_property(PropertyID::BackgroundSize, tokens);
4681
0
    if (!maybe_y_value) {
4682
0
        auto y_value = LengthPercentage { Length::make_auto() };
4683
0
        auto x_size = get_length_percentage(*x_value);
4684
0
        if (!x_size.has_value())
4685
0
            return nullptr;
4686
4687
0
        transaction.commit();
4688
0
        return BackgroundSizeStyleValue::create(x_size.value(), y_value);
4689
0
    }
4690
4691
0
    auto y_value = maybe_y_value.release_nonnull();
4692
0
    auto x_size = get_length_percentage(*x_value);
4693
0
    auto y_size = get_length_percentage(*y_value);
4694
4695
0
    if (!x_size.has_value() || !y_size.has_value())
4696
0
        return nullptr;
4697
4698
0
    transaction.commit();
4699
0
    return BackgroundSizeStyleValue::create(x_size.release_value(), y_size.release_value());
4700
0
}
4701
4702
RefPtr<CSSStyleValue> Parser::parse_border_value(PropertyID property_id, TokenStream<ComponentValue>& tokens)
4703
0
{
4704
0
    RefPtr<CSSStyleValue> border_width;
4705
0
    RefPtr<CSSStyleValue> border_color;
4706
0
    RefPtr<CSSStyleValue> border_style;
4707
4708
0
    auto color_property = PropertyID::Invalid;
4709
0
    auto style_property = PropertyID::Invalid;
4710
0
    auto width_property = PropertyID::Invalid;
4711
4712
0
    switch (property_id) {
4713
0
    case PropertyID::Border:
4714
0
        color_property = PropertyID::BorderColor;
4715
0
        style_property = PropertyID::BorderStyle;
4716
0
        width_property = PropertyID::BorderWidth;
4717
0
        break;
4718
0
    case PropertyID::BorderBottom:
4719
0
        color_property = PropertyID::BorderBottomColor;
4720
0
        style_property = PropertyID::BorderBottomStyle;
4721
0
        width_property = PropertyID::BorderBottomWidth;
4722
0
        break;
4723
0
    case PropertyID::BorderLeft:
4724
0
        color_property = PropertyID::BorderLeftColor;
4725
0
        style_property = PropertyID::BorderLeftStyle;
4726
0
        width_property = PropertyID::BorderLeftWidth;
4727
0
        break;
4728
0
    case PropertyID::BorderRight:
4729
0
        color_property = PropertyID::BorderRightColor;
4730
0
        style_property = PropertyID::BorderRightStyle;
4731
0
        width_property = PropertyID::BorderRightWidth;
4732
0
        break;
4733
0
    case PropertyID::BorderTop:
4734
0
        color_property = PropertyID::BorderTopColor;
4735
0
        style_property = PropertyID::BorderTopStyle;
4736
0
        width_property = PropertyID::BorderTopWidth;
4737
0
        break;
4738
0
    default:
4739
0
        VERIFY_NOT_REACHED();
4740
0
    }
4741
4742
0
    auto remaining_longhands = Vector { width_property, color_property, style_property };
4743
0
    auto transaction = tokens.begin_transaction();
4744
4745
0
    while (tokens.has_next_token()) {
4746
0
        auto property_and_value = parse_css_value_for_properties(remaining_longhands, tokens);
4747
0
        if (!property_and_value.has_value())
4748
0
            return nullptr;
4749
0
        auto& value = property_and_value->style_value;
4750
0
        remove_property(remaining_longhands, property_and_value->property);
4751
4752
0
        if (property_and_value->property == width_property) {
4753
0
            VERIFY(!border_width);
4754
0
            border_width = value.release_nonnull();
4755
0
        } else if (property_and_value->property == color_property) {
4756
0
            VERIFY(!border_color);
4757
0
            border_color = value.release_nonnull();
4758
0
        } else if (property_and_value->property == style_property) {
4759
0
            VERIFY(!border_style);
4760
0
            border_style = value.release_nonnull();
4761
0
        } else {
4762
0
            VERIFY_NOT_REACHED();
4763
0
        }
4764
0
    }
4765
4766
0
    if (!border_width)
4767
0
        border_width = property_initial_value(m_context.realm(), width_property);
4768
0
    if (!border_style)
4769
0
        border_style = property_initial_value(m_context.realm(), style_property);
4770
0
    if (!border_color)
4771
0
        border_color = property_initial_value(m_context.realm(), color_property);
4772
4773
0
    transaction.commit();
4774
0
    return ShorthandStyleValue::create(property_id,
4775
0
        { width_property, style_property, color_property },
4776
0
        { border_width.release_nonnull(), border_style.release_nonnull(), border_color.release_nonnull() });
4777
0
}
4778
4779
RefPtr<CSSStyleValue> Parser::parse_border_radius_value(TokenStream<ComponentValue>& tokens)
4780
0
{
4781
0
    if (tokens.remaining_token_count() == 2) {
4782
0
        auto transaction = tokens.begin_transaction();
4783
0
        auto horizontal = parse_length_percentage(tokens);
4784
0
        auto vertical = parse_length_percentage(tokens);
4785
0
        if (horizontal.has_value() && vertical.has_value()) {
4786
0
            transaction.commit();
4787
0
            return BorderRadiusStyleValue::create(horizontal.release_value(), vertical.release_value());
4788
0
        }
4789
0
    }
4790
4791
0
    if (tokens.remaining_token_count() == 1) {
4792
0
        auto transaction = tokens.begin_transaction();
4793
0
        auto radius = parse_length_percentage(tokens);
4794
0
        if (radius.has_value()) {
4795
0
            transaction.commit();
4796
0
            return BorderRadiusStyleValue::create(radius.value(), radius.value());
4797
0
        }
4798
0
    }
4799
4800
0
    return nullptr;
4801
0
}
4802
4803
RefPtr<CSSStyleValue> Parser::parse_border_radius_shorthand_value(TokenStream<ComponentValue>& tokens)
4804
0
{
4805
0
    auto top_left = [&](Vector<LengthPercentage>& radii) { return radii[0]; };
4806
0
    auto top_right = [&](Vector<LengthPercentage>& radii) {
4807
0
        switch (radii.size()) {
4808
0
        case 4:
4809
0
        case 3:
4810
0
        case 2:
4811
0
            return radii[1];
4812
0
        case 1:
4813
0
            return radii[0];
4814
0
        default:
4815
0
            VERIFY_NOT_REACHED();
4816
0
        }
4817
0
    };
4818
0
    auto bottom_right = [&](Vector<LengthPercentage>& radii) {
4819
0
        switch (radii.size()) {
4820
0
        case 4:
4821
0
        case 3:
4822
0
            return radii[2];
4823
0
        case 2:
4824
0
        case 1:
4825
0
            return radii[0];
4826
0
        default:
4827
0
            VERIFY_NOT_REACHED();
4828
0
        }
4829
0
    };
4830
0
    auto bottom_left = [&](Vector<LengthPercentage>& radii) {
4831
0
        switch (radii.size()) {
4832
0
        case 4:
4833
0
            return radii[3];
4834
0
        case 3:
4835
0
        case 2:
4836
0
            return radii[1];
4837
0
        case 1:
4838
0
            return radii[0];
4839
0
        default:
4840
0
            VERIFY_NOT_REACHED();
4841
0
        }
4842
0
    };
4843
4844
0
    Vector<LengthPercentage> horizontal_radii;
4845
0
    Vector<LengthPercentage> vertical_radii;
4846
0
    bool reading_vertical = false;
4847
0
    auto transaction = tokens.begin_transaction();
4848
4849
0
    while (tokens.has_next_token()) {
4850
0
        if (tokens.next_token().is_delim('/')) {
4851
0
            if (reading_vertical || horizontal_radii.is_empty())
4852
0
                return nullptr;
4853
4854
0
            reading_vertical = true;
4855
0
            tokens.discard_a_token(); // `/`
4856
0
            continue;
4857
0
        }
4858
4859
0
        auto maybe_dimension = parse_length_percentage(tokens);
4860
0
        if (!maybe_dimension.has_value())
4861
0
            return nullptr;
4862
0
        if (reading_vertical) {
4863
0
            vertical_radii.append(maybe_dimension.release_value());
4864
0
        } else {
4865
0
            horizontal_radii.append(maybe_dimension.release_value());
4866
0
        }
4867
0
    }
4868
4869
0
    if (horizontal_radii.size() > 4 || vertical_radii.size() > 4
4870
0
        || horizontal_radii.is_empty()
4871
0
        || (reading_vertical && vertical_radii.is_empty()))
4872
0
        return nullptr;
4873
4874
0
    auto top_left_radius = BorderRadiusStyleValue::create(top_left(horizontal_radii),
4875
0
        vertical_radii.is_empty() ? top_left(horizontal_radii) : top_left(vertical_radii));
4876
0
    auto top_right_radius = BorderRadiusStyleValue::create(top_right(horizontal_radii),
4877
0
        vertical_radii.is_empty() ? top_right(horizontal_radii) : top_right(vertical_radii));
4878
0
    auto bottom_right_radius = BorderRadiusStyleValue::create(bottom_right(horizontal_radii),
4879
0
        vertical_radii.is_empty() ? bottom_right(horizontal_radii) : bottom_right(vertical_radii));
4880
0
    auto bottom_left_radius = BorderRadiusStyleValue::create(bottom_left(horizontal_radii),
4881
0
        vertical_radii.is_empty() ? bottom_left(horizontal_radii) : bottom_left(vertical_radii));
4882
4883
0
    transaction.commit();
4884
0
    return ShorthandStyleValue::create(PropertyID::BorderRadius,
4885
0
        { PropertyID::BorderTopLeftRadius, PropertyID::BorderTopRightRadius, PropertyID::BorderBottomRightRadius, PropertyID::BorderBottomLeftRadius },
4886
0
        { move(top_left_radius), move(top_right_radius), move(bottom_right_radius), move(bottom_left_radius) });
4887
0
}
4888
4889
RefPtr<CSSStyleValue> Parser::parse_columns_value(TokenStream<ComponentValue>& tokens)
4890
0
{
4891
0
    if (tokens.remaining_token_count() > 2)
4892
0
        return nullptr;
4893
4894
0
    RefPtr<CSSStyleValue> column_count;
4895
0
    RefPtr<CSSStyleValue> column_width;
4896
4897
0
    Vector<PropertyID> remaining_longhands { PropertyID::ColumnCount, PropertyID::ColumnWidth };
4898
0
    int found_autos = 0;
4899
4900
0
    auto transaction = tokens.begin_transaction();
4901
0
    while (tokens.has_next_token()) {
4902
0
        auto property_and_value = parse_css_value_for_properties(remaining_longhands, tokens);
4903
0
        if (!property_and_value.has_value())
4904
0
            return nullptr;
4905
0
        auto& value = property_and_value->style_value;
4906
4907
        // since the values can be in either order, we want to skip over autos
4908
0
        if (value->has_auto()) {
4909
0
            found_autos++;
4910
0
            continue;
4911
0
        }
4912
4913
0
        remove_property(remaining_longhands, property_and_value->property);
4914
4915
0
        switch (property_and_value->property) {
4916
0
        case PropertyID::ColumnCount: {
4917
0
            VERIFY(!column_count);
4918
0
            column_count = value.release_nonnull();
4919
0
            continue;
4920
0
        }
4921
0
        case PropertyID::ColumnWidth: {
4922
0
            VERIFY(!column_width);
4923
0
            column_width = value.release_nonnull();
4924
0
            continue;
4925
0
        }
4926
0
        default:
4927
0
            VERIFY_NOT_REACHED();
4928
0
        }
4929
0
    }
4930
4931
0
    if (found_autos > 2)
4932
0
        return nullptr;
4933
4934
0
    if (found_autos == 2) {
4935
0
        column_count = CSSKeywordValue::create(Keyword::Auto);
4936
0
        column_width = CSSKeywordValue::create(Keyword::Auto);
4937
0
    }
4938
4939
0
    if (found_autos == 1) {
4940
0
        if (!column_count)
4941
0
            column_count = CSSKeywordValue::create(Keyword::Auto);
4942
0
        if (!column_width)
4943
0
            column_width = CSSKeywordValue::create(Keyword::Auto);
4944
0
    }
4945
4946
0
    if (!column_count)
4947
0
        column_count = property_initial_value(m_context.realm(), PropertyID::ColumnCount);
4948
0
    if (!column_width)
4949
0
        column_width = property_initial_value(m_context.realm(), PropertyID::ColumnWidth);
4950
4951
0
    transaction.commit();
4952
0
    return ShorthandStyleValue::create(PropertyID::Columns,
4953
0
        { PropertyID::ColumnCount, PropertyID::ColumnWidth },
4954
0
        { column_count.release_nonnull(), column_width.release_nonnull() });
4955
0
}
4956
4957
RefPtr<CSSStyleValue> Parser::parse_shadow_value(TokenStream<ComponentValue>& tokens, AllowInsetKeyword allow_inset_keyword)
4958
0
{
4959
    // "none"
4960
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
4961
0
        return none;
4962
4963
0
    return parse_comma_separated_value_list(tokens, [this, allow_inset_keyword](auto& tokens) {
4964
0
        return parse_single_shadow_value(tokens, allow_inset_keyword);
4965
0
    });
4966
0
}
4967
4968
RefPtr<CSSStyleValue> Parser::parse_single_shadow_value(TokenStream<ComponentValue>& tokens, AllowInsetKeyword allow_inset_keyword)
4969
0
{
4970
0
    auto transaction = tokens.begin_transaction();
4971
4972
0
    RefPtr<CSSStyleValue> color;
4973
0
    RefPtr<CSSStyleValue> offset_x;
4974
0
    RefPtr<CSSStyleValue> offset_y;
4975
0
    RefPtr<CSSStyleValue> blur_radius;
4976
0
    RefPtr<CSSStyleValue> spread_distance;
4977
0
    Optional<ShadowPlacement> placement;
4978
4979
0
    auto possibly_dynamic_length = [&](ComponentValue const& token) -> RefPtr<CSSStyleValue> {
4980
0
        auto tokens = TokenStream<ComponentValue>::of_single_token(token);
4981
0
        auto maybe_length = parse_length(tokens);
4982
0
        if (!maybe_length.has_value())
4983
0
            return nullptr;
4984
0
        return maybe_length->as_style_value();
4985
0
    };
4986
4987
0
    while (tokens.has_next_token()) {
4988
0
        if (auto maybe_color = parse_color_value(tokens); maybe_color) {
4989
0
            if (color)
4990
0
                return nullptr;
4991
0
            color = maybe_color.release_nonnull();
4992
0
            continue;
4993
0
        }
4994
4995
0
        auto const& token = tokens.next_token();
4996
0
        if (auto maybe_offset_x = possibly_dynamic_length(token); maybe_offset_x) {
4997
            // horizontal offset
4998
0
            if (offset_x)
4999
0
                return nullptr;
5000
0
            offset_x = maybe_offset_x;
5001
0
            tokens.discard_a_token();
5002
5003
            // vertical offset
5004
0
            if (!tokens.has_next_token())
5005
0
                return nullptr;
5006
0
            auto maybe_offset_y = possibly_dynamic_length(tokens.next_token());
5007
0
            if (!maybe_offset_y)
5008
0
                return nullptr;
5009
0
            offset_y = maybe_offset_y;
5010
0
            tokens.discard_a_token();
5011
5012
            // blur radius (optional)
5013
0
            if (!tokens.has_next_token())
5014
0
                break;
5015
0
            auto maybe_blur_radius = possibly_dynamic_length(tokens.next_token());
5016
0
            if (!maybe_blur_radius)
5017
0
                continue;
5018
0
            blur_radius = maybe_blur_radius;
5019
0
            tokens.discard_a_token();
5020
5021
            // spread distance (optional)
5022
0
            if (!tokens.has_next_token())
5023
0
                break;
5024
0
            auto maybe_spread_distance = possibly_dynamic_length(tokens.next_token());
5025
0
            if (!maybe_spread_distance)
5026
0
                continue;
5027
0
            spread_distance = maybe_spread_distance;
5028
0
            tokens.discard_a_token();
5029
5030
0
            continue;
5031
0
        }
5032
5033
0
        if (allow_inset_keyword == AllowInsetKeyword::Yes && token.is_ident("inset"sv)) {
5034
0
            if (placement.has_value())
5035
0
                return nullptr;
5036
0
            placement = ShadowPlacement::Inner;
5037
0
            tokens.discard_a_token();
5038
0
            continue;
5039
0
        }
5040
5041
0
        if (token.is(Token::Type::Comma))
5042
0
            break;
5043
5044
0
        return nullptr;
5045
0
    }
5046
5047
    // If color is absent, default to `currentColor`
5048
0
    if (!color)
5049
0
        color = CSSKeywordValue::create(Keyword::Currentcolor);
5050
5051
    // x/y offsets are required
5052
0
    if (!offset_x || !offset_y)
5053
0
        return nullptr;
5054
5055
    // Other lengths default to 0
5056
0
    if (!blur_radius)
5057
0
        blur_radius = LengthStyleValue::create(Length::make_px(0));
5058
0
    if (!spread_distance)
5059
0
        spread_distance = LengthStyleValue::create(Length::make_px(0));
5060
5061
    // Placement is outer by default
5062
0
    if (!placement.has_value())
5063
0
        placement = ShadowPlacement::Outer;
5064
5065
0
    transaction.commit();
5066
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());
5067
0
}
5068
5069
RefPtr<CSSStyleValue> Parser::parse_rotate_value(TokenStream<ComponentValue>& tokens)
5070
0
{
5071
    // Value: none | <angle> | [ x | y | z | <number>{3} ] && <angle>
5072
5073
0
    if (tokens.remaining_token_count() == 1) {
5074
        // "none"
5075
0
        if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
5076
0
            return none;
5077
5078
        // <angle>
5079
0
        if (auto angle = parse_angle_value(tokens))
5080
0
            return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(0), NumberStyleValue::create(0), NumberStyleValue::create(1));
5081
0
    }
5082
5083
0
    auto parse_one_of_xyz = [&]() -> Optional<ComponentValue> {
5084
0
        auto transaction = tokens.begin_transaction();
5085
0
        auto axis = tokens.consume_a_token();
5086
5087
0
        if (axis.is_ident("x"sv) || axis.is_ident("y"sv) || axis.is_ident("z"sv)) {
5088
0
            transaction.commit();
5089
0
            return axis;
5090
0
        }
5091
5092
0
        return {};
5093
0
    };
5094
5095
    // [ x | y | z ] && <angle>
5096
0
    if (tokens.remaining_token_count() == 2) {
5097
        // Try parsing `x <angle>`
5098
0
        if (auto axis = parse_one_of_xyz(); axis.has_value()) {
5099
0
            if (auto angle = parse_angle_value(tokens); angle) {
5100
0
                if (axis->is_ident("x"sv))
5101
0
                    return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(1), NumberStyleValue::create(0), NumberStyleValue::create(0));
5102
0
                if (axis->is_ident("y"sv))
5103
0
                    return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(0), NumberStyleValue::create(1), NumberStyleValue::create(0));
5104
0
                if (axis->is_ident("z"sv))
5105
0
                    return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(0), NumberStyleValue::create(0), NumberStyleValue::create(1));
5106
0
            }
5107
0
        }
5108
5109
        // Try parsing `<angle> x`
5110
0
        if (auto angle = parse_angle_value(tokens); angle) {
5111
0
            if (auto axis = parse_one_of_xyz(); axis.has_value()) {
5112
0
                if (axis->is_ident("x"sv))
5113
0
                    return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(1), NumberStyleValue::create(0), NumberStyleValue::create(0));
5114
0
                if (axis->is_ident("y"sv))
5115
0
                    return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(0), NumberStyleValue::create(1), NumberStyleValue::create(0));
5116
0
                if (axis->is_ident("z"sv))
5117
0
                    return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(0), NumberStyleValue::create(0), NumberStyleValue::create(1));
5118
0
            }
5119
0
        }
5120
0
    }
5121
5122
0
    auto parse_three_numbers = [&]() -> Optional<StyleValueVector> {
5123
0
        auto transaction = tokens.begin_transaction();
5124
0
        StyleValueVector numbers;
5125
0
        for (size_t i = 0; i < 3; ++i) {
5126
0
            if (auto number = parse_number_value(tokens); number) {
5127
0
                numbers.append(number.release_nonnull());
5128
0
            } else {
5129
0
                return {};
5130
0
            }
5131
0
        }
5132
0
        transaction.commit();
5133
0
        return numbers;
5134
0
    };
5135
5136
    // <number>{3} && <angle>
5137
0
    if (tokens.remaining_token_count() == 4) {
5138
        // Try parsing <number>{3} <angle>
5139
0
        if (auto maybe_numbers = parse_three_numbers(); maybe_numbers.has_value()) {
5140
0
            if (auto angle = parse_angle_value(tokens); angle) {
5141
0
                auto numbers = maybe_numbers.release_value();
5142
0
                return RotationStyleValue::create(angle.release_nonnull(), numbers[0], numbers[1], numbers[2]);
5143
0
            }
5144
0
        }
5145
5146
        // Try parsing <angle> <number>{3}
5147
0
        if (auto angle = parse_angle_value(tokens); angle) {
5148
0
            if (auto maybe_numbers = parse_three_numbers(); maybe_numbers.has_value()) {
5149
0
                auto numbers = maybe_numbers.release_value();
5150
0
                return RotationStyleValue::create(angle.release_nonnull(), numbers[0], numbers[1], numbers[2]);
5151
0
            }
5152
0
        }
5153
0
    }
5154
5155
0
    return nullptr;
5156
0
}
5157
5158
RefPtr<CSSStyleValue> Parser::parse_content_value(TokenStream<ComponentValue>& tokens)
5159
0
{
5160
    // FIXME: `content` accepts several kinds of function() type, which we don't handle in property_accepts_value() yet.
5161
5162
0
    auto is_single_value_keyword = [](Keyword keyword) -> bool {
5163
0
        switch (keyword) {
5164
0
        case Keyword::None:
5165
0
        case Keyword::Normal:
5166
0
            return true;
5167
0
        default:
5168
0
            return false;
5169
0
        }
5170
0
    };
5171
5172
0
    if (tokens.remaining_token_count() == 1) {
5173
0
        auto transaction = tokens.begin_transaction();
5174
0
        if (auto keyword = parse_keyword_value(tokens)) {
5175
0
            if (is_single_value_keyword(keyword->to_keyword())) {
5176
0
                transaction.commit();
5177
0
                return keyword;
5178
0
            }
5179
0
        }
5180
0
    }
5181
5182
0
    auto transaction = tokens.begin_transaction();
5183
5184
0
    StyleValueVector content_values;
5185
0
    StyleValueVector alt_text_values;
5186
0
    bool in_alt_text = false;
5187
5188
0
    while (tokens.has_next_token()) {
5189
0
        auto& next = tokens.next_token();
5190
0
        if (next.is_delim('/')) {
5191
0
            if (in_alt_text || content_values.is_empty())
5192
0
                return nullptr;
5193
0
            in_alt_text = true;
5194
0
            tokens.discard_a_token();
5195
0
            continue;
5196
0
        }
5197
5198
0
        if (auto style_value = parse_css_value_for_property(PropertyID::Content, tokens)) {
5199
0
            if (is_single_value_keyword(style_value->to_keyword()))
5200
0
                return nullptr;
5201
5202
0
            if (in_alt_text) {
5203
0
                alt_text_values.append(style_value.release_nonnull());
5204
0
            } else {
5205
0
                content_values.append(style_value.release_nonnull());
5206
0
            }
5207
0
            continue;
5208
0
        }
5209
5210
0
        return nullptr;
5211
0
    }
5212
5213
0
    if (content_values.is_empty())
5214
0
        return nullptr;
5215
0
    if (in_alt_text && alt_text_values.is_empty())
5216
0
        return nullptr;
5217
5218
0
    RefPtr<StyleValueList> alt_text;
5219
0
    if (!alt_text_values.is_empty())
5220
0
        alt_text = StyleValueList::create(move(alt_text_values), StyleValueList::Separator::Space);
5221
5222
0
    transaction.commit();
5223
0
    return ContentStyleValue::create(StyleValueList::create(move(content_values), StyleValueList::Separator::Space), move(alt_text));
5224
0
}
5225
5226
// https://drafts.csswg.org/css-lists-3/#propdef-counter-increment
5227
RefPtr<CSSStyleValue> Parser::parse_counter_increment_value(TokenStream<ComponentValue>& tokens)
5228
0
{
5229
    // [ <counter-name> <integer>? ]+ | none
5230
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
5231
0
        return none;
5232
5233
0
    return parse_counter_definitions_value(tokens, AllowReversed::No, 1);
5234
0
}
5235
5236
// https://drafts.csswg.org/css-lists-3/#propdef-counter-reset
5237
RefPtr<CSSStyleValue> Parser::parse_counter_reset_value(TokenStream<ComponentValue>& tokens)
5238
0
{
5239
    // [ <counter-name> <integer>? | <reversed-counter-name> <integer>? ]+ | none
5240
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
5241
0
        return none;
5242
5243
0
    return parse_counter_definitions_value(tokens, AllowReversed::Yes, 0);
5244
0
}
5245
5246
// https://drafts.csswg.org/css-lists-3/#propdef-counter-set
5247
RefPtr<CSSStyleValue> Parser::parse_counter_set_value(TokenStream<ComponentValue>& tokens)
5248
0
{
5249
    // [ <counter-name> <integer>? ]+ | none
5250
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
5251
0
        return none;
5252
5253
0
    return parse_counter_definitions_value(tokens, AllowReversed::No, 0);
5254
0
}
5255
5256
// https://www.w3.org/TR/css-display-3/#the-display-properties
5257
RefPtr<CSSStyleValue> Parser::parse_display_value(TokenStream<ComponentValue>& tokens)
5258
0
{
5259
0
    auto parse_single_component_display = [this](TokenStream<ComponentValue>& tokens) -> Optional<Display> {
5260
0
        auto transaction = tokens.begin_transaction();
5261
0
        if (auto keyword_value = parse_keyword_value(tokens)) {
5262
0
            auto keyword = keyword_value->to_keyword();
5263
0
            if (keyword == Keyword::ListItem) {
5264
0
                transaction.commit();
5265
0
                return Display::from_short(Display::Short::ListItem);
5266
0
            }
5267
5268
0
            if (auto display_outside = keyword_to_display_outside(keyword); display_outside.has_value()) {
5269
0
                transaction.commit();
5270
0
                switch (display_outside.value()) {
5271
0
                case DisplayOutside::Block:
5272
0
                    return Display::from_short(Display::Short::Block);
5273
0
                case DisplayOutside::Inline:
5274
0
                    return Display::from_short(Display::Short::Inline);
5275
0
                case DisplayOutside::RunIn:
5276
0
                    return Display::from_short(Display::Short::RunIn);
5277
0
                }
5278
0
            }
5279
5280
0
            if (auto display_inside = keyword_to_display_inside(keyword); display_inside.has_value()) {
5281
0
                transaction.commit();
5282
0
                switch (display_inside.value()) {
5283
0
                case DisplayInside::Flow:
5284
0
                    return Display::from_short(Display::Short::Flow);
5285
0
                case DisplayInside::FlowRoot:
5286
0
                    return Display::from_short(Display::Short::FlowRoot);
5287
0
                case DisplayInside::Table:
5288
0
                    return Display::from_short(Display::Short::Table);
5289
0
                case DisplayInside::Flex:
5290
0
                    return Display::from_short(Display::Short::Flex);
5291
0
                case DisplayInside::Grid:
5292
0
                    return Display::from_short(Display::Short::Grid);
5293
0
                case DisplayInside::Ruby:
5294
0
                    return Display::from_short(Display::Short::Ruby);
5295
0
                case DisplayInside::Math:
5296
0
                    return Display::from_short(Display::Short::Math);
5297
0
                }
5298
0
            }
5299
5300
0
            if (auto display_internal = keyword_to_display_internal(keyword); display_internal.has_value()) {
5301
0
                transaction.commit();
5302
0
                return Display { display_internal.value() };
5303
0
            }
5304
5305
0
            if (auto display_box = keyword_to_display_box(keyword); display_box.has_value()) {
5306
0
                transaction.commit();
5307
0
                switch (display_box.value()) {
5308
0
                case DisplayBox::Contents:
5309
0
                    return Display::from_short(Display::Short::Contents);
5310
0
                case DisplayBox::None:
5311
0
                    return Display::from_short(Display::Short::None);
5312
0
                }
5313
0
            }
5314
5315
0
            if (auto display_legacy = keyword_to_display_legacy(keyword); display_legacy.has_value()) {
5316
0
                transaction.commit();
5317
0
                switch (display_legacy.value()) {
5318
0
                case DisplayLegacy::InlineBlock:
5319
0
                    return Display::from_short(Display::Short::InlineBlock);
5320
0
                case DisplayLegacy::InlineTable:
5321
0
                    return Display::from_short(Display::Short::InlineTable);
5322
0
                case DisplayLegacy::InlineFlex:
5323
0
                    return Display::from_short(Display::Short::InlineFlex);
5324
0
                case DisplayLegacy::InlineGrid:
5325
0
                    return Display::from_short(Display::Short::InlineGrid);
5326
0
                }
5327
0
            }
5328
0
        }
5329
0
        return OptionalNone {};
5330
0
    };
5331
5332
0
    auto parse_multi_component_display = [this](TokenStream<ComponentValue>& tokens) -> Optional<Display> {
5333
0
        auto list_item = Display::ListItem::No;
5334
0
        Optional<DisplayInside> inside;
5335
0
        Optional<DisplayOutside> outside;
5336
5337
0
        auto transaction = tokens.begin_transaction();
5338
0
        while (tokens.has_next_token()) {
5339
0
            if (auto value = parse_keyword_value(tokens)) {
5340
0
                auto keyword = value->to_keyword();
5341
0
                if (keyword == Keyword::ListItem) {
5342
0
                    if (list_item == Display::ListItem::Yes)
5343
0
                        return {};
5344
0
                    list_item = Display::ListItem::Yes;
5345
0
                    continue;
5346
0
                }
5347
0
                if (auto inside_value = keyword_to_display_inside(keyword); inside_value.has_value()) {
5348
0
                    if (inside.has_value())
5349
0
                        return {};
5350
0
                    inside = inside_value.value();
5351
0
                    continue;
5352
0
                }
5353
0
                if (auto outside_value = keyword_to_display_outside(keyword); outside_value.has_value()) {
5354
0
                    if (outside.has_value())
5355
0
                        return {};
5356
0
                    outside = outside_value.value();
5357
0
                    continue;
5358
0
                }
5359
0
            }
5360
5361
            // Not a display value, abort.
5362
0
            dbgln_if(CSS_PARSER_DEBUG, "Unrecognized display value: `{}`", tokens.next_token().to_string());
5363
0
            return {};
5364
0
        }
5365
5366
        // The spec does not allow any other inside values to be combined with list-item
5367
        // <display-outside>? && [ flow | flow-root ]? && list-item
5368
0
        if (list_item == Display::ListItem::Yes && inside.has_value() && inside != DisplayInside::Flow && inside != DisplayInside::FlowRoot)
5369
0
            return {};
5370
5371
0
        transaction.commit();
5372
0
        return Display { outside.value_or(DisplayOutside::Block), inside.value_or(DisplayInside::Flow), list_item };
5373
0
    };
5374
5375
0
    Optional<Display> display;
5376
0
    if (tokens.remaining_token_count() == 1)
5377
0
        display = parse_single_component_display(tokens);
5378
0
    else
5379
0
        display = parse_multi_component_display(tokens);
5380
5381
0
    if (display.has_value())
5382
0
        return DisplayStyleValue::create(display.value());
5383
5384
0
    return nullptr;
5385
0
}
5386
5387
RefPtr<CSSStyleValue> Parser::parse_filter_value_list_value(TokenStream<ComponentValue>& tokens)
5388
0
{
5389
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
5390
0
        return none;
5391
5392
0
    auto transaction = tokens.begin_transaction();
5393
5394
    // FIXME: <url>s are ignored for now
5395
    // <filter-value-list> = [ <filter-function> | <url> ]+
5396
5397
0
    enum class FilterToken {
5398
        // Color filters:
5399
0
        Brightness,
5400
0
        Contrast,
5401
0
        Grayscale,
5402
0
        Invert,
5403
0
        Opacity,
5404
0
        Saturate,
5405
0
        Sepia,
5406
        // Special filters:
5407
0
        Blur,
5408
0
        DropShadow,
5409
0
        HueRotate
5410
0
    };
5411
5412
0
    auto filter_token_to_operation = [&](auto filter) {
5413
0
        VERIFY(to_underlying(filter) < to_underlying(FilterToken::Blur));
5414
0
        return static_cast<FilterOperation::Color::Type>(filter);
5415
0
    };
5416
5417
0
    auto parse_number_percentage = [&](auto& token) -> Optional<NumberPercentage> {
5418
0
        if (token.is(Token::Type::Percentage))
5419
0
            return NumberPercentage(Percentage(token.token().percentage()));
5420
0
        if (token.is(Token::Type::Number))
5421
0
            return NumberPercentage(Number(Number::Type::Number, token.token().number_value()));
5422
0
        return {};
5423
0
    };
5424
5425
0
    auto parse_filter_function_name = [&](auto name) -> Optional<FilterToken> {
5426
0
        if (name.equals_ignoring_ascii_case("blur"sv))
5427
0
            return FilterToken::Blur;
5428
0
        if (name.equals_ignoring_ascii_case("brightness"sv))
5429
0
            return FilterToken::Brightness;
5430
0
        if (name.equals_ignoring_ascii_case("contrast"sv))
5431
0
            return FilterToken::Contrast;
5432
0
        if (name.equals_ignoring_ascii_case("drop-shadow"sv))
5433
0
            return FilterToken::DropShadow;
5434
0
        if (name.equals_ignoring_ascii_case("grayscale"sv))
5435
0
            return FilterToken::Grayscale;
5436
0
        if (name.equals_ignoring_ascii_case("hue-rotate"sv))
5437
0
            return FilterToken::HueRotate;
5438
0
        if (name.equals_ignoring_ascii_case("invert"sv))
5439
0
            return FilterToken::Invert;
5440
0
        if (name.equals_ignoring_ascii_case("opacity"sv))
5441
0
            return FilterToken::Opacity;
5442
0
        if (name.equals_ignoring_ascii_case("saturate"sv))
5443
0
            return FilterToken::Saturate;
5444
0
        if (name.equals_ignoring_ascii_case("sepia"sv))
5445
0
            return FilterToken::Sepia;
5446
0
        return {};
5447
0
    };
5448
5449
0
    auto parse_filter_function = [&](auto filter_token, auto function_values) -> Optional<FilterFunction> {
5450
0
        TokenStream tokens { function_values };
5451
0
        tokens.discard_whitespace();
5452
5453
0
        auto if_no_more_tokens_return = [&](auto filter) -> Optional<FilterFunction> {
5454
0
            tokens.discard_whitespace();
5455
0
            if (tokens.has_next_token())
5456
0
                return {};
5457
0
            return filter;
5458
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>&)::$_3::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>&)::$_3::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>&)::$_3::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>&)::$_3::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
5459
5460
0
        if (filter_token == FilterToken::Blur) {
5461
            // blur( <length>? )
5462
0
            if (!tokens.has_next_token())
5463
0
                return FilterOperation::Blur {};
5464
0
            auto blur_radius = parse_length(tokens);
5465
0
            tokens.discard_whitespace();
5466
0
            if (!blur_radius.has_value())
5467
0
                return {};
5468
            // FIXME: Support calculated radius
5469
0
            return if_no_more_tokens_return(FilterOperation::Blur { blur_radius->value() });
5470
0
        } else if (filter_token == FilterToken::DropShadow) {
5471
0
            if (!tokens.has_next_token())
5472
0
                return {};
5473
            // drop-shadow( [ <color>? && <length>{2,3} ] )
5474
            // Note: The following code is a little awkward to allow the color to be before or after the lengths.
5475
0
            Optional<LengthOrCalculated> maybe_radius = {};
5476
0
            auto maybe_color = parse_color_value(tokens);
5477
0
            auto x_offset = parse_length(tokens);
5478
0
            tokens.discard_whitespace();
5479
0
            if (!x_offset.has_value() || !tokens.has_next_token())
5480
0
                return {};
5481
5482
0
            auto y_offset = parse_length(tokens);
5483
0
            tokens.discard_whitespace();
5484
0
            if (!y_offset.has_value())
5485
0
                return {};
5486
5487
0
            if (tokens.has_next_token()) {
5488
0
                maybe_radius = parse_length(tokens);
5489
0
                tokens.discard_whitespace();
5490
0
                if (!maybe_color && (!maybe_radius.has_value() || tokens.has_next_token())) {
5491
0
                    maybe_color = parse_color_value(tokens);
5492
0
                    if (!maybe_color)
5493
0
                        return {};
5494
0
                } else if (!maybe_radius.has_value()) {
5495
0
                    return {};
5496
0
                }
5497
0
            }
5498
            // FIXME: Support calculated offsets and radius
5499
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({}) });
5500
0
        } else if (filter_token == FilterToken::HueRotate) {
5501
            // hue-rotate( [ <angle> | <zero> ]? )
5502
0
            if (!tokens.has_next_token())
5503
0
                return FilterOperation::HueRotate {};
5504
0
            auto& token = tokens.consume_a_token();
5505
0
            if (token.is(Token::Type::Number)) {
5506
                // hue-rotate(0)
5507
0
                auto number = token.token().number();
5508
0
                if (number.is_integer() && number.integer_value() == 0)
5509
0
                    return if_no_more_tokens_return(FilterOperation::HueRotate { FilterOperation::HueRotate::Zero {} });
5510
0
                return {};
5511
0
            }
5512
0
            if (!token.is(Token::Type::Dimension))
5513
0
                return {};
5514
0
            auto angle_value = token.token().dimension_value();
5515
0
            auto angle_unit_name = token.token().dimension_unit();
5516
0
            auto angle_unit = Angle::unit_from_name(angle_unit_name);
5517
0
            if (!angle_unit.has_value())
5518
0
                return {};
5519
0
            Angle angle { angle_value, angle_unit.release_value() };
5520
0
            return if_no_more_tokens_return(FilterOperation::HueRotate { angle });
5521
0
        } else {
5522
            // Simple filters:
5523
            // brightness( <number-percentage>? )
5524
            // contrast( <number-percentage>? )
5525
            // grayscale( <number-percentage>? )
5526
            // invert( <number-percentage>? )
5527
            // opacity( <number-percentage>? )
5528
            // sepia( <number-percentage>? )
5529
            // saturate( <number-percentage>? )
5530
0
            if (!tokens.has_next_token())
5531
0
                return FilterOperation::Color { filter_token_to_operation(filter_token) };
5532
0
            auto amount = parse_number_percentage(tokens.consume_a_token());
5533
0
            if (!amount.has_value())
5534
0
                return {};
5535
0
            return if_no_more_tokens_return(FilterOperation::Color { filter_token_to_operation(filter_token), *amount });
5536
0
        }
5537
0
    };
5538
5539
0
    Vector<FilterFunction> filter_value_list {};
5540
5541
0
    while (tokens.has_next_token()) {
5542
0
        tokens.discard_whitespace();
5543
0
        if (!tokens.has_next_token())
5544
0
            break;
5545
0
        auto& token = tokens.consume_a_token();
5546
0
        if (!token.is_function())
5547
0
            return nullptr;
5548
0
        auto filter_token = parse_filter_function_name(token.function().name);
5549
0
        if (!filter_token.has_value())
5550
0
            return nullptr;
5551
0
        auto filter_function = parse_filter_function(*filter_token, token.function().value);
5552
0
        if (!filter_function.has_value())
5553
0
            return nullptr;
5554
0
        filter_value_list.append(*filter_function);
5555
0
    }
5556
5557
0
    if (filter_value_list.is_empty())
5558
0
        return nullptr;
5559
5560
0
    transaction.commit();
5561
0
    return FilterValueListStyleValue::create(move(filter_value_list));
5562
0
}
5563
5564
RefPtr<CSSStyleValue> Parser::parse_flex_shorthand_value(TokenStream<ComponentValue>& tokens)
5565
0
{
5566
0
    auto transaction = tokens.begin_transaction();
5567
5568
0
    auto make_flex_shorthand = [&](NonnullRefPtr<CSSStyleValue> flex_grow, NonnullRefPtr<CSSStyleValue> flex_shrink, NonnullRefPtr<CSSStyleValue> flex_basis) {
5569
0
        transaction.commit();
5570
0
        return ShorthandStyleValue::create(PropertyID::Flex,
5571
0
            { PropertyID::FlexGrow, PropertyID::FlexShrink, PropertyID::FlexBasis },
5572
0
            { move(flex_grow), move(flex_shrink), move(flex_basis) });
5573
0
    };
5574
5575
0
    if (tokens.remaining_token_count() == 1) {
5576
        // One-value syntax: <flex-grow> | <flex-basis> | none
5577
0
        auto properties = Array { PropertyID::FlexGrow, PropertyID::FlexBasis, PropertyID::Flex };
5578
0
        auto property_and_value = parse_css_value_for_properties(properties, tokens);
5579
0
        if (!property_and_value.has_value())
5580
0
            return nullptr;
5581
5582
0
        auto& value = property_and_value->style_value;
5583
0
        switch (property_and_value->property) {
5584
0
        case PropertyID::FlexGrow: {
5585
            // NOTE: The spec says that flex-basis should be 0 here, but other engines currently use 0%.
5586
            // https://github.com/w3c/csswg-drafts/issues/5742
5587
0
            auto flex_basis = PercentageStyleValue::create(Percentage(0));
5588
0
            auto one = NumberStyleValue::create(1);
5589
0
            return make_flex_shorthand(*value, one, flex_basis);
5590
0
        }
5591
0
        case PropertyID::FlexBasis: {
5592
0
            auto one = NumberStyleValue::create(1);
5593
0
            return make_flex_shorthand(one, one, *value);
5594
0
        }
5595
0
        case PropertyID::Flex: {
5596
0
            if (value->is_keyword() && value->to_keyword() == Keyword::None) {
5597
0
                auto zero = NumberStyleValue::create(0);
5598
0
                return make_flex_shorthand(zero, zero, CSSKeywordValue::create(Keyword::Auto));
5599
0
            }
5600
0
            break;
5601
0
        }
5602
0
        default:
5603
0
            VERIFY_NOT_REACHED();
5604
0
        }
5605
5606
0
        return nullptr;
5607
0
    }
5608
5609
0
    RefPtr<CSSStyleValue> flex_grow;
5610
0
    RefPtr<CSSStyleValue> flex_shrink;
5611
0
    RefPtr<CSSStyleValue> flex_basis;
5612
5613
    // NOTE: FlexGrow has to be before FlexBasis. `0` is a valid FlexBasis, but only
5614
    //       if FlexGrow (along with optional FlexShrink) have already been specified.
5615
0
    auto remaining_longhands = Vector { PropertyID::FlexGrow, PropertyID::FlexBasis };
5616
5617
0
    while (tokens.has_next_token()) {
5618
0
        auto property_and_value = parse_css_value_for_properties(remaining_longhands, tokens);
5619
0
        if (!property_and_value.has_value())
5620
0
            return nullptr;
5621
0
        auto& value = property_and_value->style_value;
5622
0
        remove_property(remaining_longhands, property_and_value->property);
5623
5624
0
        switch (property_and_value->property) {
5625
0
        case PropertyID::FlexGrow: {
5626
0
            VERIFY(!flex_grow);
5627
0
            flex_grow = value.release_nonnull();
5628
5629
            // Flex-shrink may optionally follow directly after.
5630
0
            auto maybe_flex_shrink = parse_css_value_for_property(PropertyID::FlexShrink, tokens);
5631
0
            if (maybe_flex_shrink)
5632
0
                flex_shrink = maybe_flex_shrink.release_nonnull();
5633
0
            continue;
5634
0
        }
5635
0
        case PropertyID::FlexBasis: {
5636
0
            VERIFY(!flex_basis);
5637
0
            flex_basis = value.release_nonnull();
5638
0
            continue;
5639
0
        }
5640
0
        default:
5641
0
            VERIFY_NOT_REACHED();
5642
0
        }
5643
0
    }
5644
5645
0
    if (!flex_grow)
5646
0
        flex_grow = property_initial_value(m_context.realm(), PropertyID::FlexGrow);
5647
0
    if (!flex_shrink)
5648
0
        flex_shrink = property_initial_value(m_context.realm(), PropertyID::FlexShrink);
5649
0
    if (!flex_basis) {
5650
        // NOTE: The spec says that flex-basis should be 0 here, but other engines currently use 0%.
5651
        // https://github.com/w3c/csswg-drafts/issues/5742
5652
0
        flex_basis = PercentageStyleValue::create(Percentage(0));
5653
0
    }
5654
5655
0
    return make_flex_shorthand(flex_grow.release_nonnull(), flex_shrink.release_nonnull(), flex_basis.release_nonnull());
5656
0
}
5657
5658
RefPtr<CSSStyleValue> Parser::parse_flex_flow_value(TokenStream<ComponentValue>& tokens)
5659
0
{
5660
0
    RefPtr<CSSStyleValue> flex_direction;
5661
0
    RefPtr<CSSStyleValue> flex_wrap;
5662
5663
0
    auto remaining_longhands = Vector { PropertyID::FlexDirection, PropertyID::FlexWrap };
5664
0
    auto transaction = tokens.begin_transaction();
5665
5666
0
    while (tokens.has_next_token()) {
5667
0
        auto property_and_value = parse_css_value_for_properties(remaining_longhands, tokens);
5668
0
        if (!property_and_value.has_value())
5669
0
            return nullptr;
5670
0
        auto& value = property_and_value->style_value;
5671
0
        remove_property(remaining_longhands, property_and_value->property);
5672
5673
0
        switch (property_and_value->property) {
5674
0
        case PropertyID::FlexDirection:
5675
0
            VERIFY(!flex_direction);
5676
0
            flex_direction = value.release_nonnull();
5677
0
            continue;
5678
0
        case PropertyID::FlexWrap:
5679
0
            VERIFY(!flex_wrap);
5680
0
            flex_wrap = value.release_nonnull();
5681
0
            continue;
5682
0
        default:
5683
0
            VERIFY_NOT_REACHED();
5684
0
        }
5685
0
    }
5686
5687
0
    if (!flex_direction)
5688
0
        flex_direction = property_initial_value(m_context.realm(), PropertyID::FlexDirection);
5689
0
    if (!flex_wrap)
5690
0
        flex_wrap = property_initial_value(m_context.realm(), PropertyID::FlexWrap);
5691
5692
0
    transaction.commit();
5693
0
    return ShorthandStyleValue::create(PropertyID::FlexFlow,
5694
0
        { PropertyID::FlexDirection, PropertyID::FlexWrap },
5695
0
        { flex_direction.release_nonnull(), flex_wrap.release_nonnull() });
5696
0
}
5697
5698
static bool is_generic_font_family(Keyword keyword)
5699
0
{
5700
0
    switch (keyword) {
5701
0
    case Keyword::Cursive:
5702
0
    case Keyword::Fantasy:
5703
0
    case Keyword::Monospace:
5704
0
    case Keyword::Serif:
5705
0
    case Keyword::SansSerif:
5706
0
    case Keyword::UiMonospace:
5707
0
    case Keyword::UiRounded:
5708
0
    case Keyword::UiSerif:
5709
0
    case Keyword::UiSansSerif:
5710
0
        return true;
5711
0
    default:
5712
0
        return false;
5713
0
    }
5714
0
}
5715
5716
RefPtr<CSSStyleValue> Parser::parse_font_value(TokenStream<ComponentValue>& tokens)
5717
0
{
5718
0
    RefPtr<CSSStyleValue> font_width;
5719
0
    RefPtr<CSSStyleValue> font_style;
5720
0
    RefPtr<CSSStyleValue> font_weight;
5721
0
    RefPtr<CSSStyleValue> font_size;
5722
0
    RefPtr<CSSStyleValue> line_height;
5723
0
    RefPtr<CSSStyleValue> font_families;
5724
0
    RefPtr<CSSStyleValue> font_variant;
5725
5726
    // FIXME: Handle system fonts. (caption, icon, menu, message-box, small-caption, status-bar)
5727
5728
    // Several sub-properties can be "normal", and appear in any order: style, variant, weight, stretch
5729
    // So, we have to handle that separately.
5730
0
    int normal_count = 0;
5731
5732
    // FIXME: `font-variant` allows a lot of different values which aren't allowed in the `font` shorthand.
5733
    // FIXME: `font-width` allows <percentage> values, which aren't allowed in the `font` shorthand.
5734
0
    auto remaining_longhands = Vector { PropertyID::FontSize, PropertyID::FontStyle, PropertyID::FontVariant, PropertyID::FontWeight, PropertyID::FontWidth };
5735
0
    auto transaction = tokens.begin_transaction();
5736
5737
0
    while (tokens.has_next_token()) {
5738
0
        auto& peek_token = tokens.next_token();
5739
0
        if (peek_token.is_ident("normal"sv)) {
5740
0
            normal_count++;
5741
0
            tokens.discard_a_token();
5742
0
            continue;
5743
0
        }
5744
5745
0
        auto property_and_value = parse_css_value_for_properties(remaining_longhands, tokens);
5746
0
        if (!property_and_value.has_value())
5747
0
            return nullptr;
5748
0
        auto& value = property_and_value->style_value;
5749
0
        remove_property(remaining_longhands, property_and_value->property);
5750
5751
0
        switch (property_and_value->property) {
5752
0
        case PropertyID::FontSize: {
5753
0
            VERIFY(!font_size);
5754
0
            font_size = value.release_nonnull();
5755
5756
            // Consume `/ line-height` if present
5757
0
            if (tokens.next_token().is_delim('/')) {
5758
0
                tokens.discard_a_token();
5759
0
                auto maybe_line_height = parse_css_value_for_property(PropertyID::LineHeight, tokens);
5760
0
                if (!maybe_line_height)
5761
0
                    return nullptr;
5762
0
                line_height = maybe_line_height.release_nonnull();
5763
0
            }
5764
5765
            // Consume font-families
5766
0
            auto maybe_font_families = parse_font_family_value(tokens);
5767
            // font-family comes last, so we must not have any tokens left over.
5768
0
            if (!maybe_font_families || tokens.has_next_token())
5769
0
                return nullptr;
5770
0
            font_families = maybe_font_families.release_nonnull();
5771
0
            continue;
5772
0
        }
5773
0
        case PropertyID::FontWidth: {
5774
0
            VERIFY(!font_width);
5775
0
            font_width = value.release_nonnull();
5776
0
            continue;
5777
0
        }
5778
0
        case PropertyID::FontStyle: {
5779
0
            VERIFY(!font_style);
5780
0
            font_style = value.release_nonnull();
5781
0
            continue;
5782
0
        }
5783
0
        case PropertyID::FontVariant: {
5784
0
            VERIFY(!font_variant);
5785
0
            font_variant = value.release_nonnull();
5786
0
            continue;
5787
0
        }
5788
0
        case PropertyID::FontWeight: {
5789
0
            VERIFY(!font_weight);
5790
0
            font_weight = value.release_nonnull();
5791
0
            continue;
5792
0
        }
5793
0
        default:
5794
0
            VERIFY_NOT_REACHED();
5795
0
        }
5796
5797
0
        return nullptr;
5798
0
    }
5799
5800
    // Since normal is the default value for all the properties that can have it, we don't have to actually
5801
    // set anything to normal here. It'll be set when we create the ShorthandStyleValue below.
5802
    // We just need to make sure we were not given more normals than will fit.
5803
0
    int unset_value_count = (font_style ? 0 : 1) + (font_weight ? 0 : 1) + (font_variant ? 0 : 1) + (font_width ? 0 : 1);
5804
0
    if (unset_value_count < normal_count)
5805
0
        return nullptr;
5806
5807
0
    if (!font_size || !font_families)
5808
0
        return nullptr;
5809
5810
0
    if (!font_style)
5811
0
        font_style = property_initial_value(m_context.realm(), PropertyID::FontStyle);
5812
0
    if (!font_variant)
5813
0
        font_variant = property_initial_value(m_context.realm(), PropertyID::FontVariant);
5814
0
    if (!font_weight)
5815
0
        font_weight = property_initial_value(m_context.realm(), PropertyID::FontWeight);
5816
0
    if (!font_width)
5817
0
        font_width = property_initial_value(m_context.realm(), PropertyID::FontWidth);
5818
0
    if (!line_height)
5819
0
        line_height = property_initial_value(m_context.realm(), PropertyID::LineHeight);
5820
5821
0
    transaction.commit();
5822
0
    return ShorthandStyleValue::create(PropertyID::Font,
5823
0
        { PropertyID::FontStyle, PropertyID::FontVariant, PropertyID::FontWeight, PropertyID::FontWidth, PropertyID::FontSize, PropertyID::LineHeight, PropertyID::FontFamily },
5824
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() });
5825
0
}
5826
5827
RefPtr<CSSStyleValue> Parser::parse_font_family_value(TokenStream<ComponentValue>& tokens)
5828
0
{
5829
0
    auto next_is_comma_or_eof = [&]() -> bool {
5830
0
        return !tokens.has_next_token() || tokens.next_token().is(Token::Type::Comma);
5831
0
    };
5832
5833
    // Note: Font-family names can either be a quoted string, or a keyword, or a series of custom-idents.
5834
    // eg, these are equivalent:
5835
    //     font-family: my cool     font\!, serif;
5836
    //     font-family: "my cool font!", serif;
5837
0
    StyleValueVector font_families;
5838
0
    Vector<String> current_name_parts;
5839
0
    while (tokens.has_next_token()) {
5840
0
        auto const& peek = tokens.next_token();
5841
5842
0
        if (peek.is(Token::Type::String)) {
5843
            // `font-family: my cool "font";` is invalid.
5844
0
            if (!current_name_parts.is_empty())
5845
0
                return nullptr;
5846
0
            tokens.discard_a_token(); // String
5847
0
            if (!next_is_comma_or_eof())
5848
0
                return nullptr;
5849
0
            font_families.append(StringStyleValue::create(peek.token().string()));
5850
0
            tokens.discard_a_token(); // Comma
5851
0
            continue;
5852
0
        }
5853
5854
0
        if (peek.is(Token::Type::Ident)) {
5855
            // If this is a valid identifier, it's NOT a custom-ident and can't be part of a larger name.
5856
5857
            // CSS-wide keywords are not allowed
5858
0
            if (auto builtin = parse_builtin_value(tokens))
5859
0
                return nullptr;
5860
5861
0
            auto maybe_keyword = keyword_from_string(peek.token().ident());
5862
            // Can't have a generic-font-name as a token in an unquoted font name.
5863
0
            if (maybe_keyword.has_value() && is_generic_font_family(maybe_keyword.value())) {
5864
0
                if (!current_name_parts.is_empty())
5865
0
                    return nullptr;
5866
0
                tokens.discard_a_token(); // Ident
5867
0
                if (!next_is_comma_or_eof())
5868
0
                    return nullptr;
5869
0
                font_families.append(CSSKeywordValue::create(maybe_keyword.value()));
5870
0
                tokens.discard_a_token(); // Comma
5871
0
                continue;
5872
0
            }
5873
0
            current_name_parts.append(tokens.consume_a_token().token().ident().to_string());
5874
0
            continue;
5875
0
        }
5876
5877
0
        if (peek.is(Token::Type::Comma)) {
5878
0
            if (current_name_parts.is_empty())
5879
0
                return nullptr;
5880
0
            tokens.discard_a_token(); // Comma
5881
            // This is really a series of custom-idents, not just one. But for the sake of simplicity we'll make it one.
5882
0
            font_families.append(CustomIdentStyleValue::create(MUST(String::join(' ', current_name_parts))));
5883
0
            current_name_parts.clear();
5884
            // Can't have a trailing comma
5885
0
            if (!tokens.has_next_token())
5886
0
                return nullptr;
5887
0
            continue;
5888
0
        }
5889
5890
0
        return nullptr;
5891
0
    }
5892
5893
0
    if (!current_name_parts.is_empty()) {
5894
        // This is really a series of custom-idents, not just one. But for the sake of simplicity we'll make it one.
5895
0
        font_families.append(CustomIdentStyleValue::create(MUST(String::join(' ', current_name_parts))));
5896
0
        current_name_parts.clear();
5897
0
    }
5898
5899
0
    if (font_families.is_empty())
5900
0
        return nullptr;
5901
0
    return StyleValueList::create(move(font_families), StyleValueList::Separator::Comma);
5902
0
}
5903
5904
RefPtr<CSSStyleValue> Parser::parse_font_language_override_value(TokenStream<ComponentValue>& tokens)
5905
0
{
5906
    // https://drafts.csswg.org/css-fonts/#propdef-font-language-override
5907
    // This is `normal | <string>` but with the constraint that the string has to be 4 characters long:
5908
    // Shorter strings are right-padded with spaces, and longer strings are invalid.
5909
5910
0
    if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal))
5911
0
        return normal;
5912
5913
0
    auto transaction = tokens.begin_transaction();
5914
0
    tokens.discard_whitespace();
5915
0
    if (auto string = parse_string_value(tokens)) {
5916
0
        auto string_value = string->string_value();
5917
0
        tokens.discard_whitespace();
5918
0
        if (tokens.has_next_token()) {
5919
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-language-override: unexpected trailing tokens");
5920
0
            return nullptr;
5921
0
        }
5922
0
        auto length = string_value.code_points().length();
5923
0
        if (length > 4) {
5924
0
            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-language-override: <string> value \"{}\" is too long", string_value);
5925
0
            return nullptr;
5926
0
        }
5927
0
        transaction.commit();
5928
0
        if (length < 4)
5929
0
            return StringStyleValue::create(MUST(String::formatted("{:<4}", string_value)));
5930
0
        return string;
5931
0
    }
5932
5933
0
    return nullptr;
5934
0
}
5935
5936
RefPtr<CSSStyleValue> Parser::parse_font_feature_settings_value(TokenStream<ComponentValue>& tokens)
5937
0
{
5938
    // https://drafts.csswg.org/css-fonts/#propdef-font-feature-settings
5939
    // normal | <feature-tag-value>#
5940
5941
    // normal
5942
0
    if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal))
5943
0
        return normal;
5944
5945
    // <feature-tag-value>#
5946
0
    auto transaction = tokens.begin_transaction();
5947
0
    auto tag_values = parse_a_comma_separated_list_of_component_values(tokens);
5948
5949
    // "The computed value of font-feature-settings is a map, so any duplicates in the specified value must not be preserved.
5950
    // If the same feature tag appears more than once, the value associated with the last appearance supersedes any previous
5951
    // value for that axis."
5952
    // So, we deduplicate them here using a HashSet.
5953
5954
0
    OrderedHashMap<FlyString, NonnullRefPtr<OpenTypeTaggedStyleValue>> feature_tags_map;
5955
0
    for (auto const& values : tag_values) {
5956
        // <feature-tag-value> = <opentype-tag> [ <integer [0,∞]> | on | off ]?
5957
0
        TokenStream tag_tokens { values };
5958
0
        tag_tokens.discard_whitespace();
5959
0
        auto opentype_tag = parse_opentype_tag_value(tag_tokens);
5960
0
        tag_tokens.discard_whitespace();
5961
0
        RefPtr<CSSStyleValue> value;
5962
0
        if (tag_tokens.has_next_token()) {
5963
0
            if (auto integer = parse_integer_value(tag_tokens)) {
5964
0
                if (integer->is_integer() && integer->as_integer().value() < 0)
5965
0
                    return nullptr;
5966
0
                value = integer;
5967
0
            } else {
5968
                // A value of on is synonymous with 1 and off is synonymous with 0.
5969
0
                auto keyword = parse_keyword_value(tag_tokens);
5970
0
                if (!keyword)
5971
0
                    return nullptr;
5972
0
                switch (keyword->to_keyword()) {
5973
0
                case Keyword::On:
5974
0
                    value = IntegerStyleValue::create(1);
5975
0
                    break;
5976
0
                case Keyword::Off:
5977
0
                    value = IntegerStyleValue::create(0);
5978
0
                    break;
5979
0
                default:
5980
0
                    return nullptr;
5981
0
                }
5982
0
            }
5983
0
            tag_tokens.discard_whitespace();
5984
0
        } else {
5985
            // "If the value is omitted, a value of 1 is assumed."
5986
0
            value = IntegerStyleValue::create(1);
5987
0
        }
5988
5989
0
        if (!opentype_tag || !value || tag_tokens.has_next_token())
5990
0
            return nullptr;
5991
5992
0
        feature_tags_map.set(opentype_tag->string_value(), OpenTypeTaggedStyleValue::create(opentype_tag->string_value(), value.release_nonnull()));
5993
0
    }
5994
5995
    // "The computed value contains the de-duplicated feature tags, sorted in ascending order by code unit."
5996
0
    StyleValueVector feature_tags;
5997
0
    feature_tags.ensure_capacity(feature_tags_map.size());
5998
0
    for (auto const& [key, feature_tag] : feature_tags_map)
5999
0
        feature_tags.append(feature_tag);
6000
6001
0
    quick_sort(feature_tags, [](auto& a, auto& b) {
6002
0
        return a->as_open_type_tagged().tag() < b->as_open_type_tagged().tag();
6003
0
    });
6004
6005
0
    transaction.commit();
6006
0
    return StyleValueList::create(move(feature_tags), StyleValueList::Separator::Comma);
6007
0
}
6008
6009
RefPtr<CSSStyleValue> Parser::parse_font_variation_settings_value(TokenStream<ComponentValue>& tokens)
6010
0
{
6011
    // https://drafts.csswg.org/css-fonts/#propdef-font-variation-settings
6012
    // normal | [ <opentype-tag> <number>]#
6013
6014
    // normal
6015
0
    if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal))
6016
0
        return normal;
6017
6018
    // [ <opentype-tag> <number>]#
6019
0
    auto transaction = tokens.begin_transaction();
6020
0
    auto tag_values = parse_a_comma_separated_list_of_component_values(tokens);
6021
6022
    // "If the same axis name appears more than once, the value associated with the last appearance supersedes any
6023
    // previous value for that axis. This deduplication is observable by accessing the computed value of this property."
6024
    // So, we deduplicate them here using a HashSet.
6025
6026
0
    OrderedHashMap<FlyString, NonnullRefPtr<OpenTypeTaggedStyleValue>> axis_tags_map;
6027
0
    for (auto const& values : tag_values) {
6028
0
        TokenStream tag_tokens { values };
6029
0
        tag_tokens.discard_whitespace();
6030
0
        auto opentype_tag = parse_opentype_tag_value(tag_tokens);
6031
0
        tag_tokens.discard_whitespace();
6032
0
        auto number = parse_number_value(tag_tokens);
6033
0
        tag_tokens.discard_whitespace();
6034
6035
0
        if (!opentype_tag || !number || tag_tokens.has_next_token())
6036
0
            return nullptr;
6037
6038
0
        axis_tags_map.set(opentype_tag->string_value(), OpenTypeTaggedStyleValue::create(opentype_tag->string_value(), number.release_nonnull()));
6039
0
    }
6040
6041
    // "The computed value contains the de-duplicated axis names, sorted in ascending order by code unit."
6042
0
    StyleValueVector axis_tags;
6043
0
    axis_tags.ensure_capacity(axis_tags_map.size());
6044
0
    for (auto const& [key, axis_tag] : axis_tags_map)
6045
0
        axis_tags.append(axis_tag);
6046
6047
0
    quick_sort(axis_tags, [](auto& a, auto& b) {
6048
0
        return a->as_open_type_tagged().tag() < b->as_open_type_tagged().tag();
6049
0
    });
6050
6051
0
    transaction.commit();
6052
0
    return StyleValueList::create(move(axis_tags), StyleValueList::Separator::Comma);
6053
0
}
6054
6055
JS::GCPtr<CSSFontFaceRule> Parser::convert_to_font_face_rule(AtRule const& rule)
6056
0
{
6057
    // https://drafts.csswg.org/css-fonts/#font-face-rule
6058
6059
0
    Optional<FlyString> font_family;
6060
0
    Optional<FlyString> font_named_instance;
6061
0
    Vector<ParsedFontFace::Source> src;
6062
0
    Vector<Gfx::UnicodeRange> unicode_range;
6063
0
    Optional<int> weight;
6064
0
    Optional<int> slope;
6065
0
    Optional<int> width;
6066
0
    Optional<Percentage> ascent_override;
6067
0
    Optional<Percentage> descent_override;
6068
0
    Optional<Percentage> line_gap_override;
6069
0
    FontDisplay font_display = FontDisplay::Auto;
6070
0
    Optional<FlyString> language_override;
6071
0
    Optional<OrderedHashMap<FlyString, i64>> font_feature_settings;
6072
0
    Optional<OrderedHashMap<FlyString, double>> font_variation_settings;
6073
6074
    // "normal" is returned as nullptr
6075
0
    auto parse_as_percentage_or_normal = [&](Vector<ComponentValue> const& values) -> ErrorOr<Optional<Percentage>> {
6076
        // normal | <percentage [0,∞]>
6077
0
        TokenStream tokens { values };
6078
0
        if (auto percentage_value = parse_percentage_value(tokens)) {
6079
0
            tokens.discard_whitespace();
6080
0
            if (tokens.has_next_token())
6081
0
                return Error::from_string_literal("Unexpected trailing tokens");
6082
6083
0
            if (percentage_value->is_percentage() && percentage_value->as_percentage().percentage().value() >= 0)
6084
0
                return percentage_value->as_percentage().percentage();
6085
6086
            // TODO: Once we implement calc-simplification in the parser, we should no longer see math values here,
6087
            //       unless they're impossible to resolve and thus invalid.
6088
0
            if (percentage_value->is_math()) {
6089
0
                if (auto result = percentage_value->as_math().resolve_percentage(); result.has_value())
6090
0
                    return result.value();
6091
0
            }
6092
6093
0
            return Error::from_string_literal("Invalid percentage");
6094
0
        }
6095
6096
0
        tokens.discard_whitespace();
6097
0
        if (!tokens.consume_a_token().is_ident("normal"sv))
6098
0
            return Error::from_string_literal("Expected `normal | <percentage [0,∞]>`");
6099
0
        tokens.discard_whitespace();
6100
0
        if (tokens.has_next_token())
6101
0
            return Error::from_string_literal("Unexpected trailing tokens");
6102
6103
0
        return OptionalNone {};
6104
0
    };
6105
6106
0
    rule.for_each_as_declaration_list([&](auto& declaration) {
6107
0
        if (declaration.name.equals_ignoring_ascii_case("ascent-override"sv)) {
6108
0
            auto value = parse_as_percentage_or_normal(declaration.value);
6109
0
            if (value.is_error()) {
6110
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face ascent-override: {}", value.error());
6111
0
            } else {
6112
0
                ascent_override = value.release_value();
6113
0
            }
6114
0
            return;
6115
0
        }
6116
0
        if (declaration.name.equals_ignoring_ascii_case("descent-override"sv)) {
6117
0
            auto value = parse_as_percentage_or_normal(declaration.value);
6118
0
            if (value.is_error()) {
6119
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face descent-override: {}", value.error());
6120
0
            } else {
6121
0
                descent_override = value.release_value();
6122
0
            }
6123
0
            return;
6124
0
        }
6125
0
        if (declaration.name.equals_ignoring_ascii_case("font-display"sv)) {
6126
0
            TokenStream token_stream { declaration.value };
6127
0
            if (auto keyword_value = parse_keyword_value(token_stream)) {
6128
0
                token_stream.discard_whitespace();
6129
0
                if (token_stream.has_next_token()) {
6130
0
                    dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected trailing tokens in font-display");
6131
0
                } else {
6132
0
                    auto value = keyword_to_font_display(keyword_value->to_keyword());
6133
0
                    if (value.has_value()) {
6134
0
                        font_display = *value;
6135
0
                    } else {
6136
0
                        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: `{}` is not a valid value for font-display", keyword_value->to_string());
6137
0
                    }
6138
0
                }
6139
0
            }
6140
0
            return;
6141
0
        }
6142
0
        if (declaration.name.equals_ignoring_ascii_case("font-family"sv)) {
6143
            // FIXME: This is very similar to, but different from, the logic in parse_font_family_value().
6144
            //        Ideally they could share code.
6145
0
            Vector<FlyString> font_family_parts;
6146
0
            bool had_syntax_error = false;
6147
0
            for (size_t i = 0; i < declaration.value.size(); ++i) {
6148
0
                auto const& part = declaration.value[i];
6149
0
                if (part.is(Token::Type::Whitespace))
6150
0
                    continue;
6151
0
                if (part.is(Token::Type::String)) {
6152
0
                    if (!font_family_parts.is_empty()) {
6153
0
                        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
6154
0
                        had_syntax_error = true;
6155
0
                        break;
6156
0
                    }
6157
0
                    font_family_parts.append(part.token().string());
6158
0
                    continue;
6159
0
                }
6160
0
                if (part.is(Token::Type::Ident)) {
6161
0
                    if (is_css_wide_keyword(part.token().ident())) {
6162
0
                        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
6163
0
                        had_syntax_error = true;
6164
0
                        break;
6165
0
                    }
6166
0
                    auto keyword = keyword_from_string(part.token().ident());
6167
0
                    if (keyword.has_value() && is_generic_font_family(keyword.value())) {
6168
0
                        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
6169
0
                        had_syntax_error = true;
6170
0
                        break;
6171
0
                    }
6172
0
                    font_family_parts.append(part.token().ident());
6173
0
                    continue;
6174
0
                }
6175
6176
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
6177
0
                had_syntax_error = true;
6178
0
                break;
6179
0
            }
6180
0
            if (had_syntax_error || font_family_parts.is_empty())
6181
0
                return;
6182
6183
0
            font_family = String::join(' ', font_family_parts).release_value_but_fixme_should_propagate_errors();
6184
0
            return;
6185
0
        }
6186
0
        if (declaration.name.equals_ignoring_ascii_case("font-feature-settings"sv)) {
6187
0
            TokenStream token_stream { declaration.value };
6188
0
            if (auto value = parse_css_value(CSS::PropertyID::FontFeatureSettings, token_stream); !value.is_error()) {
6189
0
                if (value.value()->to_keyword() == Keyword::Normal) {
6190
0
                    font_feature_settings.clear();
6191
0
                } else if (value.value()->is_value_list()) {
6192
0
                    auto const& feature_tags = value.value()->as_value_list().values();
6193
0
                    OrderedHashMap<FlyString, i64> settings;
6194
0
                    settings.ensure_capacity(feature_tags.size());
6195
0
                    for (auto const& feature_tag : feature_tags) {
6196
0
                        if (!feature_tag->is_open_type_tagged()) {
6197
0
                            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-feature-settings descriptor is not an OpenTypeTaggedStyleValue; skipping");
6198
0
                            continue;
6199
0
                        }
6200
0
                        auto const& setting_value = feature_tag->as_open_type_tagged().value();
6201
0
                        if (setting_value->is_integer()) {
6202
0
                            settings.set(feature_tag->as_open_type_tagged().tag(), setting_value->as_integer().integer());
6203
0
                        } else if (setting_value->is_math() && setting_value->as_math().resolves_to_number()) {
6204
0
                            if (auto integer = setting_value->as_math().resolve_integer(); integer.has_value()) {
6205
0
                                settings.set(feature_tag->as_open_type_tagged().tag(), *integer);
6206
0
                            } else {
6207
0
                                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Calculated value in font-feature-settings descriptor cannot be resolved at parse time; skipping");
6208
0
                            }
6209
0
                        } else {
6210
0
                            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-feature-settings descriptor is not an OpenTypeTaggedStyleValue holding a <integer>; skipping");
6211
0
                        }
6212
0
                    }
6213
0
                    font_feature_settings = move(settings);
6214
0
                } else {
6215
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());
6216
0
                }
6217
0
            }
6218
0
            return;
6219
0
        }
6220
0
        if (declaration.name.equals_ignoring_ascii_case("font-language-override"sv)) {
6221
0
            TokenStream token_stream { declaration.value };
6222
0
            if (auto maybe_value = parse_css_value(CSS::PropertyID::FontLanguageOverride, token_stream); !maybe_value.is_error()) {
6223
0
                auto& value = maybe_value.value();
6224
0
                if (value->is_string()) {
6225
0
                    language_override = value->as_string().string_value();
6226
0
                } else {
6227
0
                    language_override.clear();
6228
0
                }
6229
0
            }
6230
0
            return;
6231
0
        }
6232
0
        if (declaration.name.equals_ignoring_ascii_case("font-named-instance"sv)) {
6233
            // auto | <string>
6234
0
            TokenStream token_stream { declaration.value };
6235
0
            token_stream.discard_whitespace();
6236
0
            auto& token = token_stream.consume_a_token();
6237
0
            token_stream.discard_whitespace();
6238
0
            if (token_stream.has_next_token()) {
6239
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected trailing tokens in font-named-instance");
6240
0
                return;
6241
0
            }
6242
6243
0
            if (token.is_ident("auto"sv)) {
6244
0
                font_named_instance.clear();
6245
0
            } else if (token.is(Token::Type::String)) {
6246
0
                font_named_instance = token.token().string();
6247
0
            } else {
6248
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-named-instance from {}", token.to_debug_string());
6249
0
            }
6250
6251
0
            return;
6252
0
        }
6253
0
        if (declaration.name.equals_ignoring_ascii_case("font-style"sv)) {
6254
0
            TokenStream token_stream { declaration.value };
6255
0
            if (auto value = parse_css_value(CSS::PropertyID::FontStyle, token_stream); !value.is_error()) {
6256
0
                slope = value.value()->to_font_slope();
6257
0
            }
6258
0
            return;
6259
0
        }
6260
0
        if (declaration.name.equals_ignoring_ascii_case("font-variation-settings"sv)) {
6261
0
            TokenStream token_stream { declaration.value };
6262
0
            if (auto value = parse_css_value(CSS::PropertyID::FontVariationSettings, token_stream); !value.is_error()) {
6263
0
                if (value.value()->to_keyword() == Keyword::Normal) {
6264
0
                    font_variation_settings.clear();
6265
0
                } else if (value.value()->is_value_list()) {
6266
0
                    auto const& variation_tags = value.value()->as_value_list().values();
6267
0
                    OrderedHashMap<FlyString, double> settings;
6268
0
                    settings.ensure_capacity(variation_tags.size());
6269
0
                    for (auto const& variation_tag : variation_tags) {
6270
0
                        if (!variation_tag->is_open_type_tagged()) {
6271
0
                            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-variation-settings descriptor is not an OpenTypeTaggedStyleValue; skipping");
6272
0
                            continue;
6273
0
                        }
6274
0
                        auto const& setting_value = variation_tag->as_open_type_tagged().value();
6275
0
                        if (setting_value->is_number()) {
6276
0
                            settings.set(variation_tag->as_open_type_tagged().tag(), setting_value->as_number().number());
6277
0
                        } else if (setting_value->is_math() && setting_value->as_math().resolves_to_number()) {
6278
0
                            if (auto number = setting_value->as_math().resolve_number(); number.has_value()) {
6279
0
                                settings.set(variation_tag->as_open_type_tagged().tag(), *number);
6280
0
                            } else {
6281
0
                                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Calculated value in font-variation-settings descriptor cannot be resolved at parse time; skipping");
6282
0
                            }
6283
0
                        } else {
6284
0
                            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-variation-settings descriptor is not an OpenTypeTaggedStyleValue holding a <number>; skipping");
6285
0
                        }
6286
0
                    }
6287
0
                    font_variation_settings = move(settings);
6288
0
                } else {
6289
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());
6290
0
                }
6291
0
            }
6292
0
            return;
6293
0
        }
6294
0
        if (declaration.name.equals_ignoring_ascii_case("font-weight"sv)) {
6295
0
            TokenStream token_stream { declaration.value };
6296
0
            if (auto value = parse_css_value(CSS::PropertyID::FontWeight, token_stream); !value.is_error()) {
6297
0
                weight = value.value()->to_font_weight();
6298
0
            }
6299
0
            return;
6300
0
        }
6301
0
        if (declaration.name.equals_ignoring_ascii_case("font-width"sv)
6302
0
            || declaration.name.equals_ignoring_ascii_case("font-stretch"sv)) {
6303
0
            TokenStream token_stream { declaration.value };
6304
0
            if (auto value = parse_css_value(CSS::PropertyID::FontWidth, token_stream); !value.is_error()) {
6305
0
                width = value.value()->to_font_width();
6306
0
            }
6307
0
            return;
6308
0
        }
6309
0
        if (declaration.name.equals_ignoring_ascii_case("line-gap-override"sv)) {
6310
0
            auto value = parse_as_percentage_or_normal(declaration.value);
6311
0
            if (value.is_error()) {
6312
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face line-gap-override: {}", value.error());
6313
0
            } else {
6314
0
                line_gap_override = value.release_value();
6315
0
            }
6316
0
            return;
6317
0
        }
6318
0
        if (declaration.name.equals_ignoring_ascii_case("src"sv)) {
6319
0
            TokenStream token_stream { declaration.value };
6320
0
            Vector<ParsedFontFace::Source> supported_sources = parse_font_face_src(token_stream);
6321
0
            if (!supported_sources.is_empty())
6322
0
                src = move(supported_sources);
6323
0
            return;
6324
0
        }
6325
0
        if (declaration.name.equals_ignoring_ascii_case("unicode-range"sv)) {
6326
0
            TokenStream token_stream { declaration.value };
6327
0
            auto unicode_ranges = parse_unicode_ranges(token_stream);
6328
0
            if (unicode_ranges.is_empty())
6329
0
                return;
6330
6331
0
            unicode_range = move(unicode_ranges);
6332
0
            return;
6333
0
        }
6334
6335
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unrecognized descriptor '{}' in @font-face; discarding.", declaration.name);
6336
0
    });
6337
6338
0
    if (!font_family.has_value()) {
6339
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face: no font-family!");
6340
0
        return {};
6341
0
    }
6342
6343
0
    if (unicode_range.is_empty()) {
6344
0
        unicode_range.empend(0x0u, 0x10FFFFu);
6345
0
    }
6346
6347
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) });
6348
0
}
6349
6350
Vector<ParsedFontFace::Source> Parser::parse_as_font_face_src()
6351
0
{
6352
0
    return parse_font_face_src(m_token_stream);
6353
0
}
6354
6355
template<typename T>
6356
Vector<ParsedFontFace::Source> Parser::parse_font_face_src(TokenStream<T>& component_values)
6357
0
{
6358
    // FIXME: Get this information from the system somehow?
6359
    // Format-name table: https://www.w3.org/TR/css-fonts-4/#font-format-definitions
6360
0
    auto font_format_is_supported = [](StringView name) {
6361
        // The spec requires us to treat opentype and truetype as synonymous.
6362
0
        if (name.is_one_of_ignoring_ascii_case("opentype"sv, "truetype"sv, "woff"sv, "woff2"sv))
6363
0
            return true;
6364
0
        return false;
6365
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
6366
6367
0
    Vector<ParsedFontFace::Source> supported_sources;
6368
6369
0
    auto list_of_source_token_lists = parse_a_comma_separated_list_of_component_values(component_values);
6370
0
    for (auto const& source_token_list : list_of_source_token_lists) {
6371
0
        TokenStream source_tokens { source_token_list };
6372
0
        source_tokens.discard_whitespace();
6373
6374
        // <url> [ format(<font-format>)]?
6375
        // FIXME: Implement optional tech() function from CSS-Fonts-4.
6376
0
        if (auto maybe_url = parse_url_function(source_tokens); maybe_url.has_value()) {
6377
0
            auto url = maybe_url.release_value();
6378
0
            if (!url.is_valid()) {
6379
0
                continue;
6380
0
            }
6381
6382
0
            Optional<FlyString> format;
6383
6384
0
            source_tokens.discard_whitespace();
6385
0
            if (!source_tokens.has_next_token()) {
6386
0
                supported_sources.empend(move(url), format);
6387
0
                continue;
6388
0
            }
6389
6390
0
            auto maybe_function = source_tokens.consume_a_token();
6391
0
            if (!maybe_function.is_function()) {
6392
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());
6393
0
                return {};
6394
0
            }
6395
6396
0
            auto const& function = maybe_function.function();
6397
0
            if (function.name.equals_ignoring_ascii_case("format"sv)) {
6398
0
                TokenStream format_tokens { function.value };
6399
0
                format_tokens.discard_whitespace();
6400
0
                auto const& format_name_token = format_tokens.consume_a_token();
6401
0
                StringView format_name;
6402
0
                if (format_name_token.is(Token::Type::Ident)) {
6403
0
                    format_name = format_name_token.token().ident();
6404
0
                } else if (format_name_token.is(Token::Type::String)) {
6405
0
                    format_name = format_name_token.token().string();
6406
0
                } else {
6407
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());
6408
0
                    return {};
6409
0
                }
6410
6411
0
                if (!font_format_is_supported(format_name)) {
6412
0
                    dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src format({}) not supported; skipping.", format_name);
6413
0
                    continue;
6414
0
                }
6415
6416
0
                format = FlyString::from_utf8(format_name).release_value_but_fixme_should_propagate_errors();
6417
0
            } else {
6418
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (unrecognized function token `{}`); discarding.", function.name);
6419
0
                return {};
6420
0
            }
6421
6422
0
            source_tokens.discard_whitespace();
6423
0
            if (source_tokens.has_next_token()) {
6424
0
                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (extra token `{}`); discarding.", source_tokens.next_token().to_debug_string());
6425
0
                return {};
6426
0
            }
6427
6428
0
            supported_sources.empend(move(url), format);
6429
0
            continue;
6430
0
        }
6431
6432
0
        auto const& first = source_tokens.consume_a_token();
6433
0
        if (first.is_function("local"sv)) {
6434
0
            if (first.function().value.is_empty()) {
6435
0
                continue;
6436
0
            }
6437
0
            supported_sources.empend(first.function().value.first().to_string(), Optional<FlyString> {});
6438
0
            continue;
6439
0
        }
6440
6441
0
        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (failed to parse url from: {}); discarding.", first.to_debug_string());
6442
0
        return {};
6443
0
    }
6444
6445
0
    return supported_sources;
6446
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>&)
6447
6448
RefPtr<CSSStyleValue> Parser::parse_list_style_value(TokenStream<ComponentValue>& tokens)
6449
0
{
6450
0
    RefPtr<CSSStyleValue> list_position;
6451
0
    RefPtr<CSSStyleValue> list_image;
6452
0
    RefPtr<CSSStyleValue> list_type;
6453
0
    int found_nones = 0;
6454
6455
0
    Vector<PropertyID> remaining_longhands { PropertyID::ListStyleImage, PropertyID::ListStylePosition, PropertyID::ListStyleType };
6456
6457
0
    auto transaction = tokens.begin_transaction();
6458
0
    while (tokens.has_next_token()) {
6459
0
        if (auto peek = tokens.next_token(); peek.is_ident("none"sv)) {
6460
0
            tokens.discard_a_token();
6461
0
            found_nones++;
6462
0
            continue;
6463
0
        }
6464
6465
0
        auto property_and_value = parse_css_value_for_properties(remaining_longhands, tokens);
6466
0
        if (!property_and_value.has_value())
6467
0
            return nullptr;
6468
0
        auto& value = property_and_value->style_value;
6469
0
        remove_property(remaining_longhands, property_and_value->property);
6470
6471
0
        switch (property_and_value->property) {
6472
0
        case PropertyID::ListStylePosition: {
6473
0
            VERIFY(!list_position);
6474
0
            list_position = value.release_nonnull();
6475
0
            continue;
6476
0
        }
6477
0
        case PropertyID::ListStyleImage: {
6478
0
            VERIFY(!list_image);
6479
0
            list_image = value.release_nonnull();
6480
0
            continue;
6481
0
        }
6482
0
        case PropertyID::ListStyleType: {
6483
0
            VERIFY(!list_type);
6484
0
            list_type = value.release_nonnull();
6485
0
            continue;
6486
0
        }
6487
0
        default:
6488
0
            VERIFY_NOT_REACHED();
6489
0
        }
6490
0
    }
6491
6492
0
    if (found_nones > 2)
6493
0
        return nullptr;
6494
6495
0
    if (found_nones == 2) {
6496
0
        if (list_image || list_type)
6497
0
            return nullptr;
6498
0
        auto none = CSSKeywordValue::create(Keyword::None);
6499
0
        list_image = none;
6500
0
        list_type = none;
6501
6502
0
    } else if (found_nones == 1) {
6503
0
        if (list_image && list_type)
6504
0
            return nullptr;
6505
0
        auto none = CSSKeywordValue::create(Keyword::None);
6506
0
        if (!list_image)
6507
0
            list_image = none;
6508
0
        if (!list_type)
6509
0
            list_type = none;
6510
0
    }
6511
6512
0
    if (!list_position)
6513
0
        list_position = property_initial_value(m_context.realm(), PropertyID::ListStylePosition);
6514
0
    if (!list_image)
6515
0
        list_image = property_initial_value(m_context.realm(), PropertyID::ListStyleImage);
6516
0
    if (!list_type)
6517
0
        list_type = property_initial_value(m_context.realm(), PropertyID::ListStyleType);
6518
6519
0
    transaction.commit();
6520
0
    return ShorthandStyleValue::create(PropertyID::ListStyle,
6521
0
        { PropertyID::ListStylePosition, PropertyID::ListStyleImage, PropertyID::ListStyleType },
6522
0
        { list_position.release_nonnull(), list_image.release_nonnull(), list_type.release_nonnull() });
6523
0
}
6524
6525
RefPtr<CSSStyleValue> Parser::parse_math_depth_value(TokenStream<ComponentValue>& tokens)
6526
0
{
6527
    // https://w3c.github.io/mathml-core/#propdef-math-depth
6528
    // auto-add | add(<integer>) | <integer>
6529
0
    auto transaction = tokens.begin_transaction();
6530
6531
0
    auto token = tokens.consume_a_token();
6532
0
    if (tokens.has_next_token())
6533
0
        return nullptr;
6534
6535
    // auto-add
6536
0
    if (token.is_ident("auto-add"sv)) {
6537
0
        transaction.commit();
6538
0
        return MathDepthStyleValue::create_auto_add();
6539
0
    }
6540
6541
    // FIXME: Make it easier to parse "thing that might be <bar> or literally anything that resolves to it" and get rid of this
6542
0
    auto parse_something_that_resolves_to_integer = [this](ComponentValue& token) -> RefPtr<CSSStyleValue> {
6543
0
        if (token.is(Token::Type::Number) && token.token().number().is_integer())
6544
0
            return IntegerStyleValue::create(token.token().to_integer());
6545
0
        if (auto value = parse_calculated_value(token); value && value->resolves_to_number())
6546
0
            return value;
6547
0
        return nullptr;
6548
0
    };
6549
6550
    // add(<integer>)
6551
0
    if (token.is_function("add"sv)) {
6552
0
        auto add_tokens = TokenStream { token.function().value };
6553
0
        add_tokens.discard_whitespace();
6554
0
        auto integer_token = add_tokens.consume_a_token();
6555
0
        add_tokens.discard_whitespace();
6556
0
        if (add_tokens.has_next_token())
6557
0
            return nullptr;
6558
0
        if (auto integer_value = parse_something_that_resolves_to_integer(integer_token)) {
6559
0
            transaction.commit();
6560
0
            return MathDepthStyleValue::create_add(integer_value.release_nonnull());
6561
0
        }
6562
0
        return nullptr;
6563
0
    }
6564
6565
    // <integer>
6566
0
    if (auto integer_value = parse_something_that_resolves_to_integer(token)) {
6567
0
        transaction.commit();
6568
0
        return MathDepthStyleValue::create_integer(integer_value.release_nonnull());
6569
0
    }
6570
6571
0
    return nullptr;
6572
0
}
6573
6574
RefPtr<CSSStyleValue> Parser::parse_overflow_value(TokenStream<ComponentValue>& tokens)
6575
0
{
6576
0
    auto transaction = tokens.begin_transaction();
6577
0
    auto maybe_x_value = parse_css_value_for_property(PropertyID::OverflowX, tokens);
6578
0
    if (!maybe_x_value)
6579
0
        return nullptr;
6580
0
    auto maybe_y_value = parse_css_value_for_property(PropertyID::OverflowY, tokens);
6581
0
    transaction.commit();
6582
0
    if (maybe_y_value) {
6583
0
        return ShorthandStyleValue::create(PropertyID::Overflow,
6584
0
            { PropertyID::OverflowX, PropertyID::OverflowY },
6585
0
            { maybe_x_value.release_nonnull(), maybe_y_value.release_nonnull() });
6586
0
    }
6587
0
    return ShorthandStyleValue::create(PropertyID::Overflow,
6588
0
        { PropertyID::OverflowX, PropertyID::OverflowY },
6589
0
        { *maybe_x_value, *maybe_x_value });
6590
0
}
6591
6592
RefPtr<CSSStyleValue> Parser::parse_place_content_value(TokenStream<ComponentValue>& tokens)
6593
0
{
6594
0
    auto transaction = tokens.begin_transaction();
6595
0
    auto maybe_align_content_value = parse_css_value_for_property(PropertyID::AlignContent, tokens);
6596
0
    if (!maybe_align_content_value)
6597
0
        return nullptr;
6598
6599
0
    if (!tokens.has_next_token()) {
6600
0
        if (!property_accepts_keyword(PropertyID::JustifyContent, maybe_align_content_value->to_keyword()))
6601
0
            return nullptr;
6602
0
        transaction.commit();
6603
0
        return ShorthandStyleValue::create(PropertyID::PlaceContent,
6604
0
            { PropertyID::AlignContent, PropertyID::JustifyContent },
6605
0
            { *maybe_align_content_value, *maybe_align_content_value });
6606
0
    }
6607
6608
0
    auto maybe_justify_content_value = parse_css_value_for_property(PropertyID::JustifyContent, tokens);
6609
0
    if (!maybe_justify_content_value)
6610
0
        return nullptr;
6611
0
    transaction.commit();
6612
0
    return ShorthandStyleValue::create(PropertyID::PlaceContent,
6613
0
        { PropertyID::AlignContent, PropertyID::JustifyContent },
6614
0
        { maybe_align_content_value.release_nonnull(), maybe_justify_content_value.release_nonnull() });
6615
0
}
6616
6617
RefPtr<CSSStyleValue> Parser::parse_place_items_value(TokenStream<ComponentValue>& tokens)
6618
0
{
6619
0
    auto transaction = tokens.begin_transaction();
6620
0
    auto maybe_align_items_value = parse_css_value_for_property(PropertyID::AlignItems, tokens);
6621
0
    if (!maybe_align_items_value)
6622
0
        return nullptr;
6623
6624
0
    if (!tokens.has_next_token()) {
6625
0
        if (!property_accepts_keyword(PropertyID::JustifyItems, maybe_align_items_value->to_keyword()))
6626
0
            return nullptr;
6627
0
        transaction.commit();
6628
0
        return ShorthandStyleValue::create(PropertyID::PlaceItems,
6629
0
            { PropertyID::AlignItems, PropertyID::JustifyItems },
6630
0
            { *maybe_align_items_value, *maybe_align_items_value });
6631
0
    }
6632
6633
0
    auto maybe_justify_items_value = parse_css_value_for_property(PropertyID::JustifyItems, tokens);
6634
0
    if (!maybe_justify_items_value)
6635
0
        return nullptr;
6636
0
    transaction.commit();
6637
0
    return ShorthandStyleValue::create(PropertyID::PlaceItems,
6638
0
        { PropertyID::AlignItems, PropertyID::JustifyItems },
6639
0
        { *maybe_align_items_value, *maybe_justify_items_value });
6640
0
}
6641
6642
RefPtr<CSSStyleValue> Parser::parse_place_self_value(TokenStream<ComponentValue>& tokens)
6643
0
{
6644
0
    auto transaction = tokens.begin_transaction();
6645
0
    auto maybe_align_self_value = parse_css_value_for_property(PropertyID::AlignSelf, tokens);
6646
0
    if (!maybe_align_self_value)
6647
0
        return nullptr;
6648
6649
0
    if (!tokens.has_next_token()) {
6650
0
        if (!property_accepts_keyword(PropertyID::JustifySelf, maybe_align_self_value->to_keyword()))
6651
0
            return nullptr;
6652
0
        transaction.commit();
6653
0
        return ShorthandStyleValue::create(PropertyID::PlaceSelf,
6654
0
            { PropertyID::AlignSelf, PropertyID::JustifySelf },
6655
0
            { *maybe_align_self_value, *maybe_align_self_value });
6656
0
    }
6657
6658
0
    auto maybe_justify_self_value = parse_css_value_for_property(PropertyID::JustifySelf, tokens);
6659
0
    if (!maybe_justify_self_value)
6660
0
        return nullptr;
6661
0
    transaction.commit();
6662
0
    return ShorthandStyleValue::create(PropertyID::PlaceSelf,
6663
0
        { PropertyID::AlignSelf, PropertyID::JustifySelf },
6664
0
        { *maybe_align_self_value, *maybe_justify_self_value });
6665
0
}
6666
6667
RefPtr<CSSStyleValue> Parser::parse_quotes_value(TokenStream<ComponentValue>& tokens)
6668
0
{
6669
    // https://www.w3.org/TR/css-content-3/#quotes-property
6670
    // auto | none | [ <string> <string> ]+
6671
0
    auto transaction = tokens.begin_transaction();
6672
6673
0
    if (tokens.remaining_token_count() == 1) {
6674
0
        auto keyword = parse_keyword_value(tokens);
6675
0
        if (keyword && property_accepts_keyword(PropertyID::Quotes, keyword->to_keyword())) {
6676
0
            transaction.commit();
6677
0
            return keyword;
6678
0
        }
6679
0
        return nullptr;
6680
0
    }
6681
6682
    // Parse an even number of <string> values.
6683
0
    if (tokens.remaining_token_count() % 2 != 0)
6684
0
        return nullptr;
6685
6686
0
    StyleValueVector string_values;
6687
0
    while (tokens.has_next_token()) {
6688
0
        auto maybe_string = parse_string_value(tokens);
6689
0
        if (!maybe_string)
6690
0
            return nullptr;
6691
6692
0
        string_values.append(maybe_string.release_nonnull());
6693
0
    }
6694
6695
0
    transaction.commit();
6696
0
    return StyleValueList::create(move(string_values), StyleValueList::Separator::Space);
6697
0
}
6698
6699
RefPtr<CSSStyleValue> Parser::parse_text_decoration_value(TokenStream<ComponentValue>& tokens)
6700
0
{
6701
0
    RefPtr<CSSStyleValue> decoration_line;
6702
0
    RefPtr<CSSStyleValue> decoration_thickness;
6703
0
    RefPtr<CSSStyleValue> decoration_style;
6704
0
    RefPtr<CSSStyleValue> decoration_color;
6705
6706
0
    auto remaining_longhands = Vector { PropertyID::TextDecorationColor, PropertyID::TextDecorationLine, PropertyID::TextDecorationStyle, PropertyID::TextDecorationThickness };
6707
6708
0
    auto transaction = tokens.begin_transaction();
6709
0
    while (tokens.has_next_token()) {
6710
0
        auto property_and_value = parse_css_value_for_properties(remaining_longhands, tokens);
6711
0
        if (!property_and_value.has_value())
6712
0
            return nullptr;
6713
0
        auto& value = property_and_value->style_value;
6714
0
        remove_property(remaining_longhands, property_and_value->property);
6715
6716
0
        switch (property_and_value->property) {
6717
0
        case PropertyID::TextDecorationColor: {
6718
0
            VERIFY(!decoration_color);
6719
0
            decoration_color = value.release_nonnull();
6720
0
            continue;
6721
0
        }
6722
0
        case PropertyID::TextDecorationLine: {
6723
0
            VERIFY(!decoration_line);
6724
0
            tokens.reconsume_current_input_token();
6725
0
            auto parsed_decoration_line = parse_text_decoration_line_value(tokens);
6726
0
            if (!parsed_decoration_line)
6727
0
                return nullptr;
6728
0
            decoration_line = parsed_decoration_line.release_nonnull();
6729
0
            continue;
6730
0
        }
6731
0
        case PropertyID::TextDecorationThickness: {
6732
0
            VERIFY(!decoration_thickness);
6733
0
            decoration_thickness = value.release_nonnull();
6734
0
            continue;
6735
0
        }
6736
0
        case PropertyID::TextDecorationStyle: {
6737
0
            VERIFY(!decoration_style);
6738
0
            decoration_style = value.release_nonnull();
6739
0
            continue;
6740
0
        }
6741
0
        default:
6742
0
            VERIFY_NOT_REACHED();
6743
0
        }
6744
0
    }
6745
6746
0
    if (!decoration_line)
6747
0
        decoration_line = property_initial_value(m_context.realm(), PropertyID::TextDecorationLine);
6748
0
    if (!decoration_thickness)
6749
0
        decoration_thickness = property_initial_value(m_context.realm(), PropertyID::TextDecorationThickness);
6750
0
    if (!decoration_style)
6751
0
        decoration_style = property_initial_value(m_context.realm(), PropertyID::TextDecorationStyle);
6752
0
    if (!decoration_color)
6753
0
        decoration_color = property_initial_value(m_context.realm(), PropertyID::TextDecorationColor);
6754
6755
0
    transaction.commit();
6756
0
    return ShorthandStyleValue::create(PropertyID::TextDecoration,
6757
0
        { PropertyID::TextDecorationLine, PropertyID::TextDecorationThickness, PropertyID::TextDecorationStyle, PropertyID::TextDecorationColor },
6758
0
        { decoration_line.release_nonnull(), decoration_thickness.release_nonnull(), decoration_style.release_nonnull(), decoration_color.release_nonnull() });
6759
0
}
6760
6761
RefPtr<CSSStyleValue> Parser::parse_text_decoration_line_value(TokenStream<ComponentValue>& tokens)
6762
0
{
6763
0
    StyleValueVector style_values;
6764
6765
0
    while (tokens.has_next_token()) {
6766
0
        auto maybe_value = parse_css_value_for_property(PropertyID::TextDecorationLine, tokens);
6767
0
        if (!maybe_value)
6768
0
            break;
6769
0
        auto value = maybe_value.release_nonnull();
6770
6771
0
        if (auto maybe_line = keyword_to_text_decoration_line(value->to_keyword()); maybe_line.has_value()) {
6772
0
            if (maybe_line == TextDecorationLine::None) {
6773
0
                if (!style_values.is_empty())
6774
0
                    break;
6775
0
                return value;
6776
0
            }
6777
0
            if (style_values.contains_slow(value))
6778
0
                break;
6779
0
            style_values.append(move(value));
6780
0
            continue;
6781
0
        }
6782
6783
0
        break;
6784
0
    }
6785
6786
0
    if (style_values.is_empty())
6787
0
        return nullptr;
6788
0
    return StyleValueList::create(move(style_values), StyleValueList::Separator::Space);
6789
0
}
6790
6791
RefPtr<CSSStyleValue> Parser::parse_easing_value(TokenStream<ComponentValue>& tokens)
6792
0
{
6793
0
    auto transaction = tokens.begin_transaction();
6794
6795
0
    tokens.discard_whitespace();
6796
6797
0
    auto const& part = tokens.consume_a_token();
6798
6799
0
    if (part.is(Token::Type::Ident)) {
6800
0
        auto name = part.token().ident();
6801
0
        auto maybe_simple_easing = [&] -> RefPtr<EasingStyleValue> {
6802
0
            if (name == "linear"sv)
6803
0
                return EasingStyleValue::create(EasingStyleValue::Linear {});
6804
0
            if (name == "ease"sv)
6805
0
                return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease());
6806
0
            if (name == "ease-in"sv)
6807
0
                return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_in());
6808
0
            if (name == "ease-out"sv)
6809
0
                return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_out());
6810
0
            if (name == "ease-in-out"sv)
6811
0
                return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_in_out());
6812
0
            if (name == "step-start"sv)
6813
0
                return EasingStyleValue::create(EasingStyleValue::Steps::step_start());
6814
0
            if (name == "step-end"sv)
6815
0
                return EasingStyleValue::create(EasingStyleValue::Steps::step_end());
6816
0
            return {};
6817
0
        }();
6818
6819
0
        if (!maybe_simple_easing)
6820
0
            return nullptr;
6821
6822
0
        transaction.commit();
6823
0
        return maybe_simple_easing;
6824
0
    }
6825
6826
0
    if (!part.is_function())
6827
0
        return nullptr;
6828
6829
0
    TokenStream argument_tokens { part.function().value };
6830
0
    auto comma_separated_arguments = parse_a_comma_separated_list_of_component_values(argument_tokens);
6831
6832
    // Remove whitespace
6833
0
    for (auto& argument : comma_separated_arguments)
6834
0
        argument.remove_all_matching([](auto& value) { return value.is(Token::Type::Whitespace); });
6835
6836
0
    auto name = part.function().name;
6837
0
    if (name == "linear"sv) {
6838
0
        Vector<EasingStyleValue::Linear::Stop> stops;
6839
0
        for (auto const& argument : comma_separated_arguments) {
6840
0
            if (argument.is_empty() || argument.size() > 2)
6841
0
                return nullptr;
6842
6843
0
            Optional<double> offset;
6844
0
            Optional<double> position;
6845
6846
0
            for (auto const& part : argument) {
6847
0
                if (part.is(Token::Type::Number)) {
6848
0
                    if (offset.has_value())
6849
0
                        return nullptr;
6850
0
                    offset = part.token().number_value();
6851
0
                } else if (part.is(Token::Type::Percentage)) {
6852
0
                    if (position.has_value())
6853
0
                        return nullptr;
6854
0
                    position = part.token().percentage();
6855
0
                } else {
6856
0
                    return nullptr;
6857
0
                };
6858
0
            }
6859
6860
0
            if (!offset.has_value())
6861
0
                return nullptr;
6862
6863
0
            stops.append({ offset.value(), move(position) });
6864
0
        }
6865
6866
0
        if (stops.is_empty())
6867
0
            return nullptr;
6868
6869
0
        transaction.commit();
6870
0
        return EasingStyleValue::create(EasingStyleValue::Linear { move(stops) });
6871
0
    }
6872
6873
0
    if (name == "cubic-bezier") {
6874
0
        if (comma_separated_arguments.size() != 4)
6875
0
            return nullptr;
6876
6877
0
        for (auto const& argument : comma_separated_arguments) {
6878
0
            if (argument.size() != 1)
6879
0
                return nullptr;
6880
0
            if (!argument[0].is(Token::Type::Number))
6881
0
                return nullptr;
6882
0
        }
6883
6884
0
        EasingStyleValue::CubicBezier bezier {
6885
0
            comma_separated_arguments[0][0].token().number_value(),
6886
0
            comma_separated_arguments[1][0].token().number_value(),
6887
0
            comma_separated_arguments[2][0].token().number_value(),
6888
0
            comma_separated_arguments[3][0].token().number_value(),
6889
0
        };
6890
6891
0
        if (bezier.x1 < 0.0 || bezier.x1 > 1.0 || bezier.x2 < 0.0 || bezier.x2 > 1.0)
6892
0
            return nullptr;
6893
6894
0
        transaction.commit();
6895
0
        return EasingStyleValue::create(bezier);
6896
0
    }
6897
6898
0
    if (name == "steps") {
6899
0
        if (comma_separated_arguments.is_empty() || comma_separated_arguments.size() > 2)
6900
0
            return nullptr;
6901
6902
0
        for (auto const& argument : comma_separated_arguments) {
6903
0
            if (argument.size() != 1)
6904
0
                return nullptr;
6905
0
        }
6906
6907
0
        EasingStyleValue::Steps steps;
6908
6909
0
        auto intervals_argument = comma_separated_arguments[0][0];
6910
0
        if (!intervals_argument.is(Token::Type::Number))
6911
0
            return nullptr;
6912
0
        if (!intervals_argument.token().number().is_integer())
6913
0
            return nullptr;
6914
0
        auto intervals = intervals_argument.token().to_integer();
6915
6916
0
        if (comma_separated_arguments.size() == 2) {
6917
0
            TokenStream identifier_stream { comma_separated_arguments[1] };
6918
0
            auto keyword_value = parse_keyword_value(identifier_stream);
6919
0
            if (!keyword_value)
6920
0
                return nullptr;
6921
0
            switch (keyword_value->to_keyword()) {
6922
0
            case Keyword::JumpStart:
6923
0
                steps.position = EasingStyleValue::Steps::Position::JumpStart;
6924
0
                break;
6925
0
            case Keyword::JumpEnd:
6926
0
                steps.position = EasingStyleValue::Steps::Position::JumpEnd;
6927
0
                break;
6928
0
            case Keyword::JumpBoth:
6929
0
                steps.position = EasingStyleValue::Steps::Position::JumpBoth;
6930
0
                break;
6931
0
            case Keyword::JumpNone:
6932
0
                steps.position = EasingStyleValue::Steps::Position::JumpNone;
6933
0
                break;
6934
0
            case Keyword::Start:
6935
0
                steps.position = EasingStyleValue::Steps::Position::Start;
6936
0
                break;
6937
0
            case Keyword::End:
6938
0
                steps.position = EasingStyleValue::Steps::Position::End;
6939
0
                break;
6940
0
            default:
6941
0
                return nullptr;
6942
0
            }
6943
0
        }
6944
6945
        // Perform extra validation
6946
        // https://drafts.csswg.org/css-easing/#step-easing-functions
6947
        // If the <step-position> is jump-none, the <integer> must be at least 2, or the function is invalid.
6948
        // Otherwise, the <integer> must be at least 1, or the function is invalid.
6949
0
        if (steps.position == EasingStyleValue::Steps::Position::JumpNone) {
6950
0
            if (intervals <= 1)
6951
0
                return nullptr;
6952
0
        } else if (intervals <= 0) {
6953
0
            return nullptr;
6954
0
        }
6955
6956
0
        steps.number_of_intervals = intervals;
6957
0
        transaction.commit();
6958
0
        return EasingStyleValue::create(steps);
6959
0
    }
6960
6961
0
    return nullptr;
6962
0
}
6963
6964
// https://www.w3.org/TR/css-transforms-1/#transform-property
6965
RefPtr<CSSStyleValue> Parser::parse_transform_value(TokenStream<ComponentValue>& tokens)
6966
0
{
6967
    // <transform> = none | <transform-list>
6968
    // <transform-list> = <transform-function>+
6969
6970
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
6971
0
        return none;
6972
6973
0
    StyleValueVector transformations;
6974
0
    auto transaction = tokens.begin_transaction();
6975
0
    while (tokens.has_next_token()) {
6976
0
        auto const& part = tokens.consume_a_token();
6977
0
        if (!part.is_function())
6978
0
            return nullptr;
6979
0
        auto maybe_function = transform_function_from_string(part.function().name);
6980
0
        if (!maybe_function.has_value())
6981
0
            return nullptr;
6982
0
        auto function = maybe_function.release_value();
6983
0
        auto function_metadata = transform_function_metadata(function);
6984
6985
0
        auto function_tokens = TokenStream { part.function().value };
6986
0
        auto arguments = parse_a_comma_separated_list_of_component_values(function_tokens);
6987
6988
0
        if (arguments.size() > function_metadata.parameters.size()) {
6989
0
            dbgln_if(CSS_PARSER_DEBUG, "Too many arguments to {}. max: {}", part.function().name, function_metadata.parameters.size());
6990
0
            return nullptr;
6991
0
        }
6992
6993
0
        if (arguments.size() < function_metadata.parameters.size() && function_metadata.parameters[arguments.size()].required) {
6994
0
            dbgln_if(CSS_PARSER_DEBUG, "Required parameter at position {} is missing", arguments.size());
6995
0
            return nullptr;
6996
0
        }
6997
6998
0
        StyleValueVector values;
6999
0
        for (auto argument_index = 0u; argument_index < arguments.size(); ++argument_index) {
7000
0
            TokenStream argument_tokens { arguments[argument_index] };
7001
0
            argument_tokens.discard_whitespace();
7002
7003
0
            auto const& value = argument_tokens.consume_a_token();
7004
0
            RefPtr<CSSMathValue> maybe_calc_value = parse_calculated_value(value);
7005
7006
0
            switch (function_metadata.parameters[argument_index].type) {
7007
0
            case TransformFunctionParameterType::Angle: {
7008
                // These are `<angle> | <zero>` in the spec, so we have to check for both kinds.
7009
0
                if (maybe_calc_value && maybe_calc_value->resolves_to_angle()) {
7010
0
                    values.append(maybe_calc_value.release_nonnull());
7011
0
                } else if (value.is(Token::Type::Number) && value.token().number_value() == 0) {
7012
0
                    values.append(AngleStyleValue::create(Angle::make_degrees(0)));
7013
0
                } else {
7014
                    // FIXME: Remove this reconsume once all parsing functions are TokenStream-based.
7015
0
                    argument_tokens.reconsume_current_input_token();
7016
0
                    auto dimension_value = parse_dimension_value(argument_tokens);
7017
0
                    if (!dimension_value || !dimension_value->is_angle())
7018
0
                        return nullptr;
7019
0
                    values.append(dimension_value.release_nonnull());
7020
0
                }
7021
0
                break;
7022
0
            }
7023
0
            case TransformFunctionParameterType::Length:
7024
0
            case TransformFunctionParameterType::LengthNone: {
7025
0
                if (maybe_calc_value && maybe_calc_value->resolves_to_length()) {
7026
0
                    argument_tokens.discard_a_token(); // calc()
7027
0
                    values.append(maybe_calc_value.release_nonnull());
7028
0
                } else {
7029
                    // FIXME: Remove this reconsume once all parsing functions are TokenStream-based.
7030
0
                    argument_tokens.reconsume_current_input_token();
7031
7032
0
                    if (function_metadata.parameters[argument_index].type == TransformFunctionParameterType::LengthNone) {
7033
0
                        auto keyword_transaction = argument_tokens.begin_transaction();
7034
0
                        auto keyword_value = parse_keyword_value(argument_tokens);
7035
0
                        if (keyword_value && keyword_value->to_keyword() == Keyword::None) {
7036
0
                            values.append(keyword_value.release_nonnull());
7037
0
                            keyword_transaction.commit();
7038
0
                            break;
7039
0
                        }
7040
0
                    }
7041
7042
0
                    auto dimension_value = parse_dimension_value(argument_tokens);
7043
0
                    if (!dimension_value || !dimension_value->is_length())
7044
0
                        return nullptr;
7045
7046
0
                    values.append(dimension_value.release_nonnull());
7047
0
                }
7048
0
                break;
7049
0
            }
7050
0
            case TransformFunctionParameterType::LengthPercentage: {
7051
0
                if (maybe_calc_value && maybe_calc_value->resolves_to_length_percentage()) {
7052
0
                    values.append(maybe_calc_value.release_nonnull());
7053
0
                } else {
7054
                    // FIXME: Remove this reconsume once all parsing functions are TokenStream-based.
7055
0
                    argument_tokens.reconsume_current_input_token();
7056
0
                    auto dimension_value = parse_dimension_value(argument_tokens);
7057
0
                    if (!dimension_value)
7058
0
                        return nullptr;
7059
7060
0
                    if (dimension_value->is_percentage() || dimension_value->is_length())
7061
0
                        values.append(dimension_value.release_nonnull());
7062
0
                    else
7063
0
                        return nullptr;
7064
0
                }
7065
0
                break;
7066
0
            }
7067
0
            case TransformFunctionParameterType::Number: {
7068
0
                if (maybe_calc_value && maybe_calc_value->resolves_to_number()) {
7069
0
                    values.append(maybe_calc_value.release_nonnull());
7070
0
                } else {
7071
                    // FIXME: Remove this reconsume once all parsing functions are TokenStream-based.
7072
0
                    argument_tokens.reconsume_current_input_token();
7073
0
                    auto number = parse_number_value(argument_tokens);
7074
0
                    if (!number)
7075
0
                        return nullptr;
7076
0
                    values.append(number.release_nonnull());
7077
0
                }
7078
0
                break;
7079
0
            }
7080
0
            case TransformFunctionParameterType::NumberPercentage: {
7081
0
                if (maybe_calc_value && maybe_calc_value->resolves_to_number()) {
7082
0
                    values.append(maybe_calc_value.release_nonnull());
7083
0
                } else {
7084
                    // FIXME: Remove this reconsume once all parsing functions are TokenStream-based.
7085
0
                    argument_tokens.reconsume_current_input_token();
7086
0
                    auto number_or_percentage = parse_number_percentage_value(argument_tokens);
7087
0
                    if (!number_or_percentage)
7088
0
                        return nullptr;
7089
0
                    values.append(number_or_percentage.release_nonnull());
7090
0
                }
7091
0
                break;
7092
0
            }
7093
0
            }
7094
7095
0
            argument_tokens.discard_whitespace();
7096
0
            if (argument_tokens.has_next_token())
7097
0
                return nullptr;
7098
0
        }
7099
7100
0
        transformations.append(TransformationStyleValue::create(function, move(values)));
7101
0
    }
7102
0
    transaction.commit();
7103
0
    return StyleValueList::create(move(transformations), StyleValueList::Separator::Space);
7104
0
}
7105
7106
// https://www.w3.org/TR/css-transforms-1/#propdef-transform-origin
7107
// FIXME: This only supports a 2D position
7108
RefPtr<CSSStyleValue> Parser::parse_transform_origin_value(TokenStream<ComponentValue>& tokens)
7109
0
{
7110
0
    enum class Axis {
7111
0
        None,
7112
0
        X,
7113
0
        Y,
7114
0
    };
7115
7116
0
    struct AxisOffset {
7117
0
        Axis axis;
7118
0
        NonnullRefPtr<CSSStyleValue> offset;
7119
0
    };
7120
7121
0
    auto to_axis_offset = [](RefPtr<CSSStyleValue> value) -> Optional<AxisOffset> {
7122
0
        if (!value)
7123
0
            return OptionalNone {};
7124
0
        if (value->is_percentage())
7125
0
            return AxisOffset { Axis::None, value->as_percentage() };
7126
0
        if (value->is_length())
7127
0
            return AxisOffset { Axis::None, value->as_length() };
7128
0
        if (value->is_keyword()) {
7129
0
            switch (value->to_keyword()) {
7130
0
            case Keyword::Top:
7131
0
                return AxisOffset { Axis::Y, PercentageStyleValue::create(Percentage(0)) };
7132
0
            case Keyword::Left:
7133
0
                return AxisOffset { Axis::X, PercentageStyleValue::create(Percentage(0)) };
7134
0
            case Keyword::Center:
7135
0
                return AxisOffset { Axis::None, PercentageStyleValue::create(Percentage(50)) };
7136
0
            case Keyword::Bottom:
7137
0
                return AxisOffset { Axis::Y, PercentageStyleValue::create(Percentage(100)) };
7138
0
            case Keyword::Right:
7139
0
                return AxisOffset { Axis::X, PercentageStyleValue::create(Percentage(100)) };
7140
0
            default:
7141
0
                return OptionalNone {};
7142
0
            }
7143
0
        }
7144
0
        if (value->is_math()) {
7145
0
            return AxisOffset { Axis::None, value->as_math() };
7146
0
        }
7147
0
        return OptionalNone {};
7148
0
    };
7149
7150
0
    auto transaction = tokens.begin_transaction();
7151
7152
0
    auto make_list = [&transaction](NonnullRefPtr<CSSStyleValue> const& x_value, NonnullRefPtr<CSSStyleValue> const& y_value) -> NonnullRefPtr<StyleValueList> {
7153
0
        transaction.commit();
7154
0
        return StyleValueList::create(StyleValueVector { x_value, y_value }, StyleValueList::Separator::Space);
7155
0
    };
7156
7157
0
    switch (tokens.remaining_token_count()) {
7158
0
    case 1: {
7159
0
        auto single_value = to_axis_offset(parse_css_value_for_property(PropertyID::TransformOrigin, tokens));
7160
0
        if (!single_value.has_value())
7161
0
            return nullptr;
7162
        // If only one value is specified, the second value is assumed to be center.
7163
        // FIXME: If one or two values are specified, the third value is assumed to be 0px.
7164
0
        switch (single_value->axis) {
7165
0
        case Axis::None:
7166
0
        case Axis::X:
7167
0
            return make_list(single_value->offset, PercentageStyleValue::create(Percentage(50)));
7168
0
        case Axis::Y:
7169
0
            return make_list(PercentageStyleValue::create(Percentage(50)), single_value->offset);
7170
0
        }
7171
0
        VERIFY_NOT_REACHED();
7172
0
    }
7173
0
    case 2: {
7174
0
        auto first_value = to_axis_offset(parse_css_value_for_property(PropertyID::TransformOrigin, tokens));
7175
0
        auto second_value = to_axis_offset(parse_css_value_for_property(PropertyID::TransformOrigin, tokens));
7176
0
        if (!first_value.has_value() || !second_value.has_value())
7177
0
            return nullptr;
7178
7179
0
        RefPtr<CSSStyleValue> x_value;
7180
0
        RefPtr<CSSStyleValue> y_value;
7181
7182
0
        if (first_value->axis == Axis::X) {
7183
0
            x_value = first_value->offset;
7184
0
        } else if (first_value->axis == Axis::Y) {
7185
0
            y_value = first_value->offset;
7186
0
        }
7187
7188
0
        if (second_value->axis == Axis::X) {
7189
0
            if (x_value)
7190
0
                return nullptr;
7191
0
            x_value = second_value->offset;
7192
            // Put the other in Y since its axis can't have been X
7193
0
            y_value = first_value->offset;
7194
0
        } else if (second_value->axis == Axis::Y) {
7195
0
            if (y_value)
7196
0
                return nullptr;
7197
0
            y_value = second_value->offset;
7198
            // Put the other in X since its axis can't have been Y
7199
0
            x_value = first_value->offset;
7200
0
        } else {
7201
0
            if (x_value) {
7202
0
                VERIFY(!y_value);
7203
0
                y_value = second_value->offset;
7204
0
            } else {
7205
0
                VERIFY(!x_value);
7206
0
                x_value = second_value->offset;
7207
0
            }
7208
0
        }
7209
        // If two or more values are defined and either no value is a keyword, or the only used keyword is center,
7210
        // then the first value represents the horizontal position (or offset) and the second represents the vertical position (or offset).
7211
        // FIXME: A third value always represents the Z position (or offset) and must be of type <length>.
7212
0
        if (first_value->axis == Axis::None && second_value->axis == Axis::None) {
7213
0
            x_value = first_value->offset;
7214
0
            y_value = second_value->offset;
7215
0
        }
7216
0
        return make_list(x_value.release_nonnull(), y_value.release_nonnull());
7217
0
    }
7218
0
    }
7219
7220
0
    return nullptr;
7221
0
}
7222
7223
RefPtr<CSSStyleValue> Parser::parse_transition_value(TokenStream<ComponentValue>& tokens)
7224
0
{
7225
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
7226
0
        return none;
7227
7228
0
    Vector<TransitionStyleValue::Transition> transitions;
7229
0
    auto transaction = tokens.begin_transaction();
7230
7231
0
    while (tokens.has_next_token()) {
7232
0
        TransitionStyleValue::Transition transition;
7233
0
        auto time_value_count = 0;
7234
7235
0
        while (tokens.has_next_token() && !tokens.next_token().is(Token::Type::Comma)) {
7236
0
            if (auto time = parse_time(tokens); time.has_value()) {
7237
0
                switch (time_value_count) {
7238
0
                case 0:
7239
0
                    transition.duration = time.release_value();
7240
0
                    break;
7241
0
                case 1:
7242
0
                    transition.delay = time.release_value();
7243
0
                    break;
7244
0
                default:
7245
0
                    dbgln_if(CSS_PARSER_DEBUG, "Transition property has more than two time values");
7246
0
                    return {};
7247
0
                }
7248
0
                time_value_count++;
7249
0
                continue;
7250
0
            }
7251
7252
0
            if (auto easing = parse_easing_value(tokens)) {
7253
0
                if (transition.easing) {
7254
0
                    dbgln_if(CSS_PARSER_DEBUG, "Transition property has multiple easing values");
7255
0
                    return {};
7256
0
                }
7257
7258
0
                transition.easing = easing->as_easing();
7259
0
                continue;
7260
0
            }
7261
7262
0
            if (tokens.next_token().is(Token::Type::Ident)) {
7263
0
                if (transition.property_name) {
7264
0
                    dbgln_if(CSS_PARSER_DEBUG, "Transition property has multiple property identifiers");
7265
0
                    return {};
7266
0
                }
7267
7268
0
                auto ident = tokens.consume_a_token().token().ident();
7269
0
                if (auto property = property_id_from_string(ident); property.has_value())
7270
0
                    transition.property_name = CustomIdentStyleValue::create(ident);
7271
7272
0
                continue;
7273
0
            }
7274
7275
0
            dbgln_if(CSS_PARSER_DEBUG, "Transition property has unexpected token \"{}\"", tokens.next_token().to_string());
7276
0
            return {};
7277
0
        }
7278
7279
0
        if (!transition.property_name)
7280
0
            transition.property_name = CustomIdentStyleValue::create("all"_fly_string);
7281
7282
0
        if (!transition.easing)
7283
0
            transition.easing = EasingStyleValue::create(EasingStyleValue::CubicBezier::ease());
7284
7285
0
        transitions.append(move(transition));
7286
7287
0
        if (!tokens.next_token().is(Token::Type::Comma))
7288
0
            break;
7289
7290
0
        tokens.discard_a_token();
7291
0
    }
7292
7293
0
    transaction.commit();
7294
0
    return TransitionStyleValue::create(move(transitions));
7295
0
}
7296
7297
RefPtr<CSSStyleValue> Parser::parse_as_css_value(PropertyID property_id)
7298
0
{
7299
0
    auto component_values = parse_a_list_of_component_values(m_token_stream);
7300
0
    auto tokens = TokenStream(component_values);
7301
0
    auto parsed_value = parse_css_value(property_id, tokens);
7302
0
    if (parsed_value.is_error())
7303
0
        return nullptr;
7304
0
    return parsed_value.release_value();
7305
0
}
7306
7307
Optional<CSS::GridSize> Parser::parse_grid_size(ComponentValue const& component_value)
7308
0
{
7309
0
    if (component_value.is_function()) {
7310
0
        if (auto maybe_calculated = parse_calculated_value(component_value)) {
7311
0
            if (maybe_calculated->resolves_to_length_percentage())
7312
0
                return GridSize(LengthPercentage(maybe_calculated.release_nonnull()));
7313
            // FIXME: Support calculated <flex>
7314
0
        }
7315
7316
0
        return {};
7317
0
    }
7318
0
    if (component_value.is_ident("auto"sv))
7319
0
        return GridSize::make_auto();
7320
0
    if (component_value.is_ident("max-content"sv))
7321
0
        return GridSize(GridSize::Type::MaxContent);
7322
0
    if (component_value.is_ident("min-content"sv))
7323
0
        return GridSize(GridSize::Type::MinContent);
7324
0
    auto dimension = parse_dimension(component_value);
7325
0
    if (!dimension.has_value())
7326
0
        return {};
7327
0
    if (dimension->is_length())
7328
0
        return GridSize(dimension->length());
7329
0
    else if (dimension->is_percentage())
7330
0
        return GridSize(dimension->percentage());
7331
0
    else if (dimension->is_flex())
7332
0
        return GridSize(dimension->flex());
7333
0
    return {};
7334
0
}
7335
7336
Optional<CSS::GridFitContent> Parser::parse_fit_content(Vector<ComponentValue> const& component_values)
7337
0
{
7338
    // https://www.w3.org/TR/css-grid-2/#valdef-grid-template-columns-fit-content
7339
    // 'fit-content( <length-percentage> )'
7340
    // Represents the formula max(minimum, min(limit, max-content)), where minimum represents an auto minimum (which is often, but not always,
7341
    // equal to a min-content minimum), and limit is the track sizing function passed as an argument to fit-content().
7342
    // This is essentially calculated as the smaller of minmax(auto, max-content) and minmax(auto, limit).
7343
0
    auto function_tokens = TokenStream(component_values);
7344
0
    function_tokens.discard_whitespace();
7345
0
    auto maybe_length_percentage = parse_length_percentage(function_tokens);
7346
0
    if (maybe_length_percentage.has_value())
7347
0
        return CSS::GridFitContent(CSS::GridSize(CSS::GridSize::Type::FitContent, maybe_length_percentage.value()));
7348
0
    return {};
7349
0
}
7350
7351
Optional<CSS::GridMinMax> Parser::parse_min_max(Vector<ComponentValue> const& component_values)
7352
0
{
7353
    // https://www.w3.org/TR/css-grid-2/#valdef-grid-template-columns-minmax
7354
    // 'minmax(min, max)'
7355
    // Defines a size range greater than or equal to min and less than or equal to max. If the max is
7356
    // less than the min, then the max will be floored by the min (essentially yielding minmax(min,
7357
    // min)). As a maximum, a <flex> value sets the track’s flex factor; it is invalid as a minimum.
7358
0
    auto function_tokens = TokenStream(component_values);
7359
0
    auto comma_separated_list = parse_a_comma_separated_list_of_component_values(function_tokens);
7360
0
    if (comma_separated_list.size() != 2)
7361
0
        return {};
7362
7363
0
    TokenStream part_one_tokens { comma_separated_list[0] };
7364
0
    part_one_tokens.discard_whitespace();
7365
0
    if (!part_one_tokens.has_next_token())
7366
0
        return {};
7367
0
    auto current_token = part_one_tokens.consume_a_token();
7368
0
    auto min_grid_size = parse_grid_size(current_token);
7369
7370
0
    TokenStream part_two_tokens { comma_separated_list[1] };
7371
0
    part_two_tokens.discard_whitespace();
7372
0
    if (!part_two_tokens.has_next_token())
7373
0
        return {};
7374
0
    current_token = part_two_tokens.consume_a_token();
7375
0
    auto max_grid_size = parse_grid_size(current_token);
7376
7377
0
    if (min_grid_size.has_value() && max_grid_size.has_value()) {
7378
        // https://www.w3.org/TR/css-grid-2/#valdef-grid-template-columns-minmax
7379
        // As a maximum, a <flex> value sets the track’s flex factor; it is invalid as a minimum.
7380
0
        if (min_grid_size.value().is_flexible_length())
7381
0
            return {};
7382
0
        return CSS::GridMinMax(min_grid_size.value(), max_grid_size.value());
7383
0
    }
7384
0
    return {};
7385
0
}
7386
7387
Optional<CSS::GridRepeat> Parser::parse_repeat(Vector<ComponentValue> const& component_values)
7388
0
{
7389
    // https://www.w3.org/TR/css-grid-2/#repeat-syntax
7390
    // 7.2.3.1. Syntax of repeat()
7391
    // The generic form of the repeat() syntax is, approximately,
7392
    // repeat( [ <integer [1,∞]> | auto-fill | auto-fit ] , <track-list> )
7393
0
    auto is_auto_fill = false;
7394
0
    auto is_auto_fit = false;
7395
0
    auto function_tokens = TokenStream(component_values);
7396
0
    auto comma_separated_list = parse_a_comma_separated_list_of_component_values(function_tokens);
7397
0
    if (comma_separated_list.size() != 2)
7398
0
        return {};
7399
    // The first argument specifies the number of repetitions.
7400
0
    TokenStream part_one_tokens { comma_separated_list[0] };
7401
0
    part_one_tokens.discard_whitespace();
7402
0
    if (!part_one_tokens.has_next_token())
7403
0
        return {};
7404
0
    auto& current_token = part_one_tokens.consume_a_token();
7405
7406
0
    auto repeat_count = 0;
7407
0
    if (current_token.is(Token::Type::Number) && current_token.token().number().is_integer() && current_token.token().number_value() > 0)
7408
0
        repeat_count = current_token.token().number_value();
7409
0
    else if (current_token.is_ident("auto-fill"sv))
7410
0
        is_auto_fill = true;
7411
0
    else if (current_token.is_ident("auto-fit"sv))
7412
0
        is_auto_fit = true;
7413
7414
    // The second argument is a track list, which is repeated that number of times.
7415
0
    TokenStream part_two_tokens { comma_separated_list[1] };
7416
0
    part_two_tokens.discard_whitespace();
7417
0
    if (!part_two_tokens.has_next_token())
7418
0
        return {};
7419
7420
0
    Vector<Variant<ExplicitGridTrack, GridLineNames>> repeat_params;
7421
0
    auto last_object_was_line_names = false;
7422
0
    while (part_two_tokens.has_next_token()) {
7423
0
        auto token = part_two_tokens.consume_a_token();
7424
0
        Vector<String> line_names;
7425
0
        if (token.is_block()) {
7426
0
            if (last_object_was_line_names)
7427
0
                return {};
7428
0
            last_object_was_line_names = true;
7429
0
            if (!token.block().is_square())
7430
0
                return {};
7431
0
            TokenStream block_tokens { token.block().value };
7432
0
            while (block_tokens.has_next_token()) {
7433
0
                auto current_block_token = block_tokens.consume_a_token();
7434
0
                line_names.append(current_block_token.token().ident().to_string());
7435
0
                block_tokens.discard_whitespace();
7436
0
            }
7437
0
            repeat_params.append(GridLineNames { move(line_names) });
7438
0
            part_two_tokens.discard_whitespace();
7439
0
        } else {
7440
0
            last_object_was_line_names = false;
7441
0
            auto track_sizing_function = parse_track_sizing_function(token);
7442
0
            if (!track_sizing_function.has_value())
7443
0
                return {};
7444
            // However, there are some restrictions:
7445
            // The repeat() notation can’t be nested.
7446
0
            if (track_sizing_function.value().is_repeat())
7447
0
                return {};
7448
7449
            // Automatic repetitions (auto-fill or auto-fit) cannot be combined with intrinsic or flexible sizes.
7450
            // Note that 'auto' is also an intrinsic size (and thus not permitted) but we can't use
7451
            // track_sizing_function.is_auto(..) to check for it, as it requires AvailableSize, which is why there is
7452
            // a separate check for it below.
7453
            // https://www.w3.org/TR/css-grid-2/#repeat-syntax
7454
            // https://www.w3.org/TR/css-grid-2/#intrinsic-sizing-function
7455
0
            if (track_sizing_function.value().is_default()
7456
0
                && (track_sizing_function.value().grid_size().is_flexible_length() || token.is_ident("auto"sv))
7457
0
                && (is_auto_fill || is_auto_fit))
7458
0
                return {};
7459
7460
0
            repeat_params.append(track_sizing_function.value());
7461
0
            part_two_tokens.discard_whitespace();
7462
0
        }
7463
0
    }
7464
7465
    // Thus the precise syntax of the repeat() notation has several forms:
7466
    // <track-repeat> = repeat( [ <integer [1,∞]> ] , [ <line-names>? <track-size> ]+ <line-names>? )
7467
    // <auto-repeat>  = repeat( [ auto-fill | auto-fit ] , [ <line-names>? <fixed-size> ]+ <line-names>? )
7468
    // <fixed-repeat> = repeat( [ <integer [1,∞]> ] , [ <line-names>? <fixed-size> ]+ <line-names>? )
7469
    // <name-repeat>  = repeat( [ <integer [1,∞]> | auto-fill ], <line-names>+)
7470
7471
    // The <track-repeat> variant can represent the repetition of any <track-size>, but is limited to a
7472
    // fixed number of repetitions.
7473
7474
    // The <auto-repeat> variant can repeat automatically to fill a space, but requires definite track
7475
    // sizes so that the number of repetitions can be calculated. It can only appear once in the track
7476
    // list, but the same track list can also contain <fixed-repeat>s.
7477
7478
    // The <name-repeat> variant is for adding line names to subgrids. It can only be used with the
7479
    // subgrid keyword and cannot specify track sizes, only line names.
7480
7481
    // If a repeat() function that is not a <name-repeat> ends up placing two <line-names> adjacent to
7482
    // each other, the name lists are merged. For example, repeat(2, [a] 1fr [b]) is equivalent to [a]
7483
    // 1fr [b a] 1fr [b].
7484
0
    if (is_auto_fill)
7485
0
        return GridRepeat(GridTrackSizeList(move(repeat_params)), GridRepeat::Type::AutoFill);
7486
0
    else if (is_auto_fit)
7487
0
        return GridRepeat(GridTrackSizeList(move(repeat_params)), GridRepeat::Type::AutoFit);
7488
0
    else
7489
0
        return GridRepeat(GridTrackSizeList(move(repeat_params)), repeat_count);
7490
0
}
7491
7492
Optional<CSS::ExplicitGridTrack> Parser::parse_track_sizing_function(ComponentValue const& token)
7493
0
{
7494
0
    if (token.is_function()) {
7495
0
        auto const& function_token = token.function();
7496
0
        if (function_token.name.equals_ignoring_ascii_case("repeat"sv)) {
7497
0
            auto maybe_repeat = parse_repeat(function_token.value);
7498
0
            if (maybe_repeat.has_value())
7499
0
                return CSS::ExplicitGridTrack(maybe_repeat.value());
7500
0
            else
7501
0
                return {};
7502
0
        } else if (function_token.name.equals_ignoring_ascii_case("minmax"sv)) {
7503
0
            auto maybe_min_max_value = parse_min_max(function_token.value);
7504
0
            if (maybe_min_max_value.has_value())
7505
0
                return CSS::ExplicitGridTrack(maybe_min_max_value.value());
7506
0
            else
7507
0
                return {};
7508
0
        } else if (function_token.name.equals_ignoring_ascii_case("fit-content"sv)) {
7509
0
            auto maybe_fit_content_value = parse_fit_content(function_token.value);
7510
0
            if (maybe_fit_content_value.has_value())
7511
0
                return CSS::ExplicitGridTrack(maybe_fit_content_value.value());
7512
0
            return {};
7513
0
        } else if (auto maybe_calculated = parse_calculated_value(token)) {
7514
0
            return CSS::ExplicitGridTrack(GridSize(LengthPercentage(maybe_calculated.release_nonnull())));
7515
0
        }
7516
0
        return {};
7517
0
    } else if (token.is_ident("auto"sv)) {
7518
0
        return CSS::ExplicitGridTrack(GridSize(Length::make_auto()));
7519
0
    } else if (token.is_block()) {
7520
0
        return {};
7521
0
    } else {
7522
0
        auto grid_size = parse_grid_size(token);
7523
0
        if (!grid_size.has_value())
7524
0
            return {};
7525
0
        return CSS::ExplicitGridTrack(grid_size.value());
7526
0
    }
7527
0
}
7528
7529
RefPtr<CSSStyleValue> Parser::parse_grid_track_size_list(TokenStream<ComponentValue>& tokens, bool allow_separate_line_name_blocks)
7530
0
{
7531
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
7532
0
        return GridTrackSizeListStyleValue::make_none();
7533
7534
0
    auto transaction = tokens.begin_transaction();
7535
7536
0
    Vector<Variant<ExplicitGridTrack, GridLineNames>> track_list;
7537
0
    auto last_object_was_line_names = false;
7538
0
    while (tokens.has_next_token()) {
7539
0
        auto token = tokens.consume_a_token();
7540
0
        if (token.is_block()) {
7541
0
            if (last_object_was_line_names && !allow_separate_line_name_blocks) {
7542
0
                transaction.commit();
7543
0
                return GridTrackSizeListStyleValue::make_auto();
7544
0
            }
7545
0
            last_object_was_line_names = true;
7546
0
            Vector<String> line_names;
7547
0
            if (!token.block().is_square()) {
7548
0
                transaction.commit();
7549
0
                return GridTrackSizeListStyleValue::make_auto();
7550
0
            }
7551
0
            TokenStream block_tokens { token.block().value };
7552
0
            block_tokens.discard_whitespace();
7553
0
            while (block_tokens.has_next_token()) {
7554
0
                auto current_block_token = block_tokens.consume_a_token();
7555
0
                line_names.append(current_block_token.token().ident().to_string());
7556
0
                block_tokens.discard_whitespace();
7557
0
            }
7558
0
            track_list.append(GridLineNames { move(line_names) });
7559
0
        } else {
7560
0
            last_object_was_line_names = false;
7561
0
            auto track_sizing_function = parse_track_sizing_function(token);
7562
0
            if (!track_sizing_function.has_value()) {
7563
0
                transaction.commit();
7564
0
                return GridTrackSizeListStyleValue::make_auto();
7565
0
            }
7566
            // FIXME: Handle multiple repeat values (should combine them here, or remove
7567
            // any other ones if the first one is auto-fill, etc.)
7568
0
            track_list.append(track_sizing_function.value());
7569
0
        }
7570
0
    }
7571
7572
0
    transaction.commit();
7573
0
    return GridTrackSizeListStyleValue::create(GridTrackSizeList(move(track_list)));
7574
0
}
7575
7576
// https://www.w3.org/TR/css-grid-1/#grid-auto-flow-property
7577
RefPtr<GridAutoFlowStyleValue> Parser::parse_grid_auto_flow_value(TokenStream<ComponentValue>& tokens)
7578
0
{
7579
    // [ row | column ] || dense
7580
0
    if (!tokens.has_next_token())
7581
0
        return nullptr;
7582
7583
0
    auto transaction = tokens.begin_transaction();
7584
7585
0
    auto parse_axis = [&]() -> Optional<GridAutoFlowStyleValue::Axis> {
7586
0
        auto transaction = tokens.begin_transaction();
7587
0
        auto token = tokens.consume_a_token();
7588
0
        if (!token.is(Token::Type::Ident))
7589
0
            return {};
7590
0
        auto const& ident = token.token().ident();
7591
0
        if (ident.equals_ignoring_ascii_case("row"sv)) {
7592
0
            transaction.commit();
7593
0
            return GridAutoFlowStyleValue::Axis::Row;
7594
0
        } else if (ident.equals_ignoring_ascii_case("column"sv)) {
7595
0
            transaction.commit();
7596
0
            return GridAutoFlowStyleValue::Axis::Column;
7597
0
        }
7598
0
        return {};
7599
0
    };
7600
7601
0
    auto parse_dense = [&]() -> Optional<GridAutoFlowStyleValue::Dense> {
7602
0
        auto transaction = tokens.begin_transaction();
7603
0
        auto token = tokens.consume_a_token();
7604
0
        if (!token.is(Token::Type::Ident))
7605
0
            return {};
7606
0
        auto const& ident = token.token().ident();
7607
0
        if (ident.equals_ignoring_ascii_case("dense"sv)) {
7608
0
            transaction.commit();
7609
0
            return GridAutoFlowStyleValue::Dense::Yes;
7610
0
        }
7611
0
        return {};
7612
0
    };
7613
7614
0
    Optional<GridAutoFlowStyleValue::Axis> axis;
7615
0
    Optional<GridAutoFlowStyleValue::Dense> dense;
7616
0
    if (axis = parse_axis(); axis.has_value()) {
7617
0
        dense = parse_dense();
7618
0
    } else if (dense = parse_dense(); dense.has_value()) {
7619
0
        axis = parse_axis();
7620
0
    }
7621
7622
0
    if (tokens.has_next_token())
7623
0
        return nullptr;
7624
7625
0
    transaction.commit();
7626
0
    return GridAutoFlowStyleValue::create(axis.value_or(GridAutoFlowStyleValue::Axis::Row), dense.value_or(GridAutoFlowStyleValue::Dense::No));
7627
0
}
7628
7629
// https://drafts.csswg.org/css-overflow/#propdef-scrollbar-gutter
7630
RefPtr<CSSStyleValue> Parser::parse_scrollbar_gutter_value(TokenStream<ComponentValue>& tokens)
7631
0
{
7632
    // auto | stable && both-edges?
7633
0
    if (!tokens.has_next_token())
7634
0
        return nullptr;
7635
7636
0
    auto transaction = tokens.begin_transaction();
7637
7638
0
    auto parse_stable = [&]() -> Optional<bool> {
7639
0
        auto transaction = tokens.begin_transaction();
7640
0
        auto token = tokens.consume_a_token();
7641
0
        if (!token.is(Token::Type::Ident))
7642
0
            return {};
7643
0
        auto const& ident = token.token().ident();
7644
0
        if (ident.equals_ignoring_ascii_case("auto"sv)) {
7645
0
            transaction.commit();
7646
0
            return false;
7647
0
        } else if (ident.equals_ignoring_ascii_case("stable"sv)) {
7648
0
            transaction.commit();
7649
0
            return true;
7650
0
        }
7651
0
        return {};
7652
0
    };
7653
7654
0
    auto parse_both_edges = [&]() -> Optional<bool> {
7655
0
        auto transaction = tokens.begin_transaction();
7656
0
        auto token = tokens.consume_a_token();
7657
0
        if (!token.is(Token::Type::Ident))
7658
0
            return {};
7659
0
        auto const& ident = token.token().ident();
7660
0
        if (ident.equals_ignoring_ascii_case("both-edges"sv)) {
7661
0
            transaction.commit();
7662
0
            return true;
7663
0
        }
7664
0
        return {};
7665
0
    };
7666
7667
0
    Optional<bool> stable;
7668
0
    Optional<bool> both_edges;
7669
0
    if (stable = parse_stable(); stable.has_value()) {
7670
0
        if (stable.value())
7671
0
            both_edges = parse_both_edges();
7672
0
    } else if (both_edges = parse_both_edges(); both_edges.has_value()) {
7673
0
        stable = parse_stable();
7674
0
        if (!stable.has_value() || !stable.value())
7675
0
            return nullptr;
7676
0
    }
7677
7678
0
    if (tokens.has_next_token())
7679
0
        return nullptr;
7680
7681
0
    transaction.commit();
7682
7683
0
    ScrollbarGutter gutter_value;
7684
0
    if (both_edges.has_value())
7685
0
        gutter_value = ScrollbarGutter::BothEdges;
7686
0
    else if (stable.has_value() && stable.value())
7687
0
        gutter_value = ScrollbarGutter::Stable;
7688
0
    else
7689
0
        gutter_value = ScrollbarGutter::Auto;
7690
0
    return ScrollbarGutterStyleValue::create(gutter_value);
7691
0
}
7692
7693
RefPtr<CSSStyleValue> Parser::parse_grid_auto_track_sizes(TokenStream<ComponentValue>& tokens)
7694
0
{
7695
    // https://www.w3.org/TR/css-grid-2/#auto-tracks
7696
    // <track-size>+
7697
0
    Vector<Variant<ExplicitGridTrack, GridLineNames>> track_list;
7698
0
    auto transaction = tokens.begin_transaction();
7699
0
    while (tokens.has_next_token()) {
7700
0
        auto token = tokens.consume_a_token();
7701
0
        auto track_sizing_function = parse_track_sizing_function(token);
7702
0
        if (!track_sizing_function.has_value()) {
7703
0
            transaction.commit();
7704
0
            return GridTrackSizeListStyleValue::make_auto();
7705
0
        }
7706
        // FIXME: Handle multiple repeat values (should combine them here, or remove
7707
        //        any other ones if the first one is auto-fill, etc.)
7708
0
        track_list.append(track_sizing_function.value());
7709
0
    }
7710
0
    transaction.commit();
7711
0
    return GridTrackSizeListStyleValue::create(GridTrackSizeList(move(track_list)));
7712
0
}
7713
7714
RefPtr<GridTrackPlacementStyleValue> Parser::parse_grid_track_placement(TokenStream<ComponentValue>& tokens)
7715
0
{
7716
    // FIXME: This shouldn't be needed. Right now, the below code returns a CSSStyleValue even if no tokens are consumed!
7717
0
    if (!tokens.has_next_token())
7718
0
        return nullptr;
7719
7720
    // https://www.w3.org/TR/css-grid-2/#line-placement
7721
    // Line-based Placement: the grid-row-start, grid-column-start, grid-row-end, and grid-column-end properties
7722
    // <grid-line> =
7723
    //     auto |
7724
    //     <custom-ident> |
7725
    //     [ <integer> && <custom-ident>? ] |
7726
    //     [ span && [ <integer> || <custom-ident> ] ]
7727
0
    auto is_valid_integer = [](auto& token) -> bool {
7728
        // An <integer> value of zero makes the declaration invalid.
7729
0
        if (token.is(Token::Type::Number) && token.token().number().is_integer() && token.token().number_value() != 0)
7730
0
            return true;
7731
0
        return false;
7732
0
    };
7733
0
    auto parse_custom_ident = [this](auto& tokens) {
7734
        // The <custom-ident> additionally excludes the keywords span and auto.
7735
0
        return parse_custom_ident_value(tokens, { "span"sv, "auto"sv });
7736
0
    };
7737
7738
0
    auto transaction = tokens.begin_transaction();
7739
7740
    // FIXME: Handle the single-token case inside the loop instead, so that we can more easily call this from
7741
    //        `parse_grid_area_shorthand_value()` using a single TokenStream.
7742
0
    if (tokens.remaining_token_count() == 1) {
7743
0
        if (auto custom_ident = parse_custom_ident(tokens)) {
7744
0
            transaction.commit();
7745
0
            return GridTrackPlacementStyleValue::create(GridTrackPlacement::make_line({}, custom_ident->custom_ident().to_string()));
7746
0
        }
7747
0
        auto& token = tokens.consume_a_token();
7748
0
        if (auto maybe_calculated = parse_calculated_value(token); maybe_calculated && maybe_calculated->resolves_to_number()) {
7749
0
            transaction.commit();
7750
0
            return GridTrackPlacementStyleValue::create(GridTrackPlacement::make_line(static_cast<int>(maybe_calculated->resolve_integer().value()), {}));
7751
0
        }
7752
0
        if (token.is_ident("auto"sv)) {
7753
0
            transaction.commit();
7754
0
            return GridTrackPlacementStyleValue::create(GridTrackPlacement::make_auto());
7755
0
        }
7756
0
        if (token.is_ident("span"sv)) {
7757
0
            transaction.commit();
7758
0
            return GridTrackPlacementStyleValue::create(GridTrackPlacement::make_span(1));
7759
0
        }
7760
0
        if (is_valid_integer(token)) {
7761
0
            transaction.commit();
7762
0
            return GridTrackPlacementStyleValue::create(GridTrackPlacement::make_line(static_cast<int>(token.token().number_value()), {}));
7763
0
        }
7764
0
        return nullptr;
7765
0
    }
7766
7767
0
    auto span_value = false;
7768
0
    auto span_or_position_value = 0;
7769
0
    String identifier_value;
7770
0
    while (tokens.has_next_token()) {
7771
0
        auto& token = tokens.next_token();
7772
0
        if (token.is_ident("auto"sv))
7773
0
            return nullptr;
7774
0
        if (token.is_ident("span"sv)) {
7775
0
            if (span_value)
7776
0
                return nullptr;
7777
0
            tokens.discard_a_token(); // span
7778
0
            span_value = true;
7779
0
            continue;
7780
0
        }
7781
0
        if (is_valid_integer(token)) {
7782
0
            if (span_or_position_value != 0)
7783
0
                return nullptr;
7784
0
            span_or_position_value = static_cast<int>(tokens.consume_a_token().token().to_integer());
7785
0
            continue;
7786
0
        }
7787
0
        if (auto custom_ident = parse_custom_ident(tokens)) {
7788
0
            if (!identifier_value.is_empty())
7789
0
                return nullptr;
7790
0
            identifier_value = custom_ident->custom_ident().to_string();
7791
0
            continue;
7792
0
        }
7793
0
        break;
7794
0
    }
7795
7796
    // Negative integers or zero are invalid.
7797
0
    if (span_value && span_or_position_value < 1)
7798
0
        return nullptr;
7799
7800
    // If the <integer> is omitted, it defaults to 1.
7801
0
    if (span_or_position_value == 0)
7802
0
        span_or_position_value = 1;
7803
7804
0
    transaction.commit();
7805
0
    if (!identifier_value.is_empty())
7806
0
        return GridTrackPlacementStyleValue::create(GridTrackPlacement::make_line(span_or_position_value, identifier_value));
7807
0
    return GridTrackPlacementStyleValue::create(GridTrackPlacement::make_span(span_or_position_value));
7808
0
}
7809
7810
RefPtr<CSSStyleValue> Parser::parse_grid_track_placement_shorthand_value(PropertyID property_id, TokenStream<ComponentValue>& tokens)
7811
0
{
7812
0
    auto start_property = (property_id == PropertyID::GridColumn) ? PropertyID::GridColumnStart : PropertyID::GridRowStart;
7813
0
    auto end_property = (property_id == PropertyID::GridColumn) ? PropertyID::GridColumnEnd : PropertyID::GridRowEnd;
7814
7815
0
    auto transaction = tokens.begin_transaction();
7816
0
    auto current_token = tokens.consume_a_token();
7817
7818
0
    Vector<ComponentValue> track_start_placement_tokens;
7819
0
    while (true) {
7820
0
        if (current_token.is_delim('/'))
7821
0
            break;
7822
0
        track_start_placement_tokens.append(current_token);
7823
0
        if (!tokens.has_next_token())
7824
0
            break;
7825
0
        current_token = tokens.consume_a_token();
7826
0
    }
7827
7828
0
    Vector<ComponentValue> track_end_placement_tokens;
7829
0
    if (tokens.has_next_token()) {
7830
0
        current_token = tokens.consume_a_token();
7831
0
        while (true) {
7832
0
            track_end_placement_tokens.append(current_token);
7833
0
            if (!tokens.has_next_token())
7834
0
                break;
7835
0
            current_token = tokens.consume_a_token();
7836
0
        }
7837
0
    }
7838
7839
0
    TokenStream track_start_placement_token_stream { track_start_placement_tokens };
7840
0
    auto parsed_start_value = parse_grid_track_placement(track_start_placement_token_stream);
7841
0
    if (parsed_start_value && track_end_placement_tokens.is_empty()) {
7842
0
        transaction.commit();
7843
0
        if (parsed_start_value->grid_track_placement().has_identifier()) {
7844
0
            auto custom_ident = parsed_start_value.release_nonnull();
7845
0
            return ShorthandStyleValue::create(property_id, { start_property, end_property }, { custom_ident, custom_ident });
7846
0
        }
7847
0
        return ShorthandStyleValue::create(property_id,
7848
0
            { start_property, end_property },
7849
0
            { parsed_start_value.release_nonnull(), GridTrackPlacementStyleValue::create(GridTrackPlacement::make_auto()) });
7850
0
    }
7851
7852
0
    TokenStream track_end_placement_token_stream { track_end_placement_tokens };
7853
0
    auto parsed_end_value = parse_grid_track_placement(track_end_placement_token_stream);
7854
0
    if (parsed_start_value && parsed_end_value) {
7855
0
        transaction.commit();
7856
0
        return ShorthandStyleValue::create(property_id,
7857
0
            { start_property, end_property },
7858
0
            { parsed_start_value.release_nonnull(), parsed_end_value.release_nonnull() });
7859
0
    }
7860
7861
0
    return nullptr;
7862
0
}
7863
7864
// https://www.w3.org/TR/css-grid-2/#explicit-grid-shorthand
7865
// 7.4. Explicit Grid Shorthand: the grid-template property
7866
RefPtr<CSSStyleValue> Parser::parse_grid_track_size_list_shorthand_value(PropertyID property_id, TokenStream<ComponentValue>& tokens)
7867
0
{
7868
    // The grid-template property is a shorthand for setting grid-template-columns, grid-template-rows,
7869
    // and grid-template-areas in a single declaration. It has several distinct syntax forms:
7870
    // none
7871
    //    - Sets all three properties to their initial values (none).
7872
    // <'grid-template-rows'> / <'grid-template-columns'>
7873
    //    - Sets grid-template-rows and grid-template-columns to the specified values, respectively, and sets grid-template-areas to none.
7874
    // [ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list> ]?
7875
    //    - Sets grid-template-areas to the strings listed.
7876
    //    - Sets grid-template-rows to the <track-size>s following each string (filling in auto for any missing sizes),
7877
    //      and splicing in the named lines defined before/after each size.
7878
    //    - Sets grid-template-columns to the track listing specified after the slash (or none, if not specified).
7879
0
    auto transaction = tokens.begin_transaction();
7880
7881
    // FIXME: Read the parts in place if possible, instead of constructing separate vectors and streams.
7882
0
    Vector<ComponentValue> template_rows_tokens;
7883
0
    Vector<ComponentValue> template_columns_tokens;
7884
0
    Vector<ComponentValue> template_area_tokens;
7885
7886
0
    bool found_forward_slash = false;
7887
7888
0
    while (tokens.has_next_token()) {
7889
0
        auto& token = tokens.consume_a_token();
7890
0
        if (token.is_delim('/')) {
7891
0
            if (found_forward_slash)
7892
0
                return nullptr;
7893
0
            found_forward_slash = true;
7894
0
            continue;
7895
0
        }
7896
0
        if (found_forward_slash) {
7897
0
            template_columns_tokens.append(token);
7898
0
            continue;
7899
0
        }
7900
0
        if (token.is(Token::Type::String))
7901
0
            template_area_tokens.append(token);
7902
0
        else
7903
0
            template_rows_tokens.append(token);
7904
0
    }
7905
7906
0
    TokenStream template_area_token_stream { template_area_tokens };
7907
0
    TokenStream template_rows_token_stream { template_rows_tokens };
7908
0
    TokenStream template_columns_token_stream { template_columns_tokens };
7909
0
    auto parsed_template_areas_values = parse_grid_template_areas_value(template_area_token_stream);
7910
0
    auto parsed_template_rows_values = parse_grid_track_size_list(template_rows_token_stream, true);
7911
0
    auto parsed_template_columns_values = parse_grid_track_size_list(template_columns_token_stream);
7912
7913
0
    if (template_area_token_stream.has_next_token()
7914
0
        || template_rows_token_stream.has_next_token()
7915
0
        || template_columns_token_stream.has_next_token())
7916
0
        return nullptr;
7917
7918
0
    transaction.commit();
7919
0
    return ShorthandStyleValue::create(property_id,
7920
0
        { PropertyID::GridTemplateAreas, PropertyID::GridTemplateRows, PropertyID::GridTemplateColumns },
7921
0
        { parsed_template_areas_values.release_nonnull(), parsed_template_rows_values.release_nonnull(), parsed_template_columns_values.release_nonnull() });
7922
0
}
7923
7924
RefPtr<CSSStyleValue> Parser::parse_grid_area_shorthand_value(TokenStream<ComponentValue>& tokens)
7925
0
{
7926
0
    auto transaction = tokens.begin_transaction();
7927
7928
0
    auto parse_placement_tokens = [&](Vector<ComponentValue>& placement_tokens, bool check_for_delimiter = true) -> void {
7929
0
        while (tokens.has_next_token()) {
7930
0
            auto& current_token = tokens.consume_a_token();
7931
0
            if (check_for_delimiter && current_token.is_delim('/'))
7932
0
                break;
7933
0
            placement_tokens.append(current_token);
7934
0
        }
7935
0
    };
7936
7937
0
    Vector<ComponentValue> row_start_placement_tokens;
7938
0
    parse_placement_tokens(row_start_placement_tokens);
7939
7940
0
    Vector<ComponentValue> column_start_placement_tokens;
7941
0
    if (tokens.has_next_token())
7942
0
        parse_placement_tokens(column_start_placement_tokens);
7943
7944
0
    Vector<ComponentValue> row_end_placement_tokens;
7945
0
    if (tokens.has_next_token())
7946
0
        parse_placement_tokens(row_end_placement_tokens);
7947
7948
0
    Vector<ComponentValue> column_end_placement_tokens;
7949
0
    if (tokens.has_next_token())
7950
0
        parse_placement_tokens(column_end_placement_tokens, false);
7951
7952
    // https://www.w3.org/TR/css-grid-2/#placement-shorthands
7953
    // The grid-area property is a shorthand for grid-row-start, grid-column-start, grid-row-end and
7954
    // grid-column-end.
7955
0
    TokenStream row_start_placement_token_stream { row_start_placement_tokens };
7956
0
    auto row_start_style_value = parse_grid_track_placement(row_start_placement_token_stream);
7957
0
    if (row_start_placement_token_stream.has_next_token())
7958
0
        return nullptr;
7959
7960
0
    TokenStream column_start_placement_token_stream { column_start_placement_tokens };
7961
0
    auto column_start_style_value = parse_grid_track_placement(column_start_placement_token_stream);
7962
0
    if (column_start_placement_token_stream.has_next_token())
7963
0
        return nullptr;
7964
7965
0
    TokenStream row_end_placement_token_stream { row_end_placement_tokens };
7966
0
    auto row_end_style_value = parse_grid_track_placement(row_end_placement_token_stream);
7967
0
    if (row_end_placement_token_stream.has_next_token())
7968
0
        return nullptr;
7969
7970
0
    TokenStream column_end_placement_token_stream { column_end_placement_tokens };
7971
0
    auto column_end_style_value = parse_grid_track_placement(column_end_placement_token_stream);
7972
0
    if (column_end_placement_token_stream.has_next_token())
7973
0
        return nullptr;
7974
7975
    // If four <grid-line> values are specified, grid-row-start is set to the first value, grid-column-start
7976
    // is set to the second value, grid-row-end is set to the third value, and grid-column-end is set to the
7977
    // fourth value.
7978
0
    auto row_start = GridTrackPlacement::make_auto();
7979
0
    auto column_start = GridTrackPlacement::make_auto();
7980
0
    auto row_end = GridTrackPlacement::make_auto();
7981
0
    auto column_end = GridTrackPlacement::make_auto();
7982
7983
0
    if (row_start_style_value)
7984
0
        row_start = row_start_style_value.release_nonnull()->as_grid_track_placement().grid_track_placement();
7985
7986
    // When grid-column-start is omitted, if grid-row-start is a <custom-ident>, all four longhands are set to
7987
    // that value. Otherwise, it is set to auto.
7988
0
    if (column_start_style_value)
7989
0
        column_start = column_start_style_value.release_nonnull()->as_grid_track_placement().grid_track_placement();
7990
0
    else
7991
0
        column_start = row_start;
7992
7993
    // When grid-row-end is omitted, if grid-row-start is a <custom-ident>, grid-row-end is set to that
7994
    // <custom-ident>; otherwise, it is set to auto.
7995
0
    if (row_end_style_value)
7996
0
        row_end = row_end_style_value.release_nonnull()->as_grid_track_placement().grid_track_placement();
7997
0
    else
7998
0
        row_end = column_start;
7999
8000
    // When grid-column-end is omitted, if grid-column-start is a <custom-ident>, grid-column-end is set to
8001
    // that <custom-ident>; otherwise, it is set to auto.
8002
0
    if (column_end_style_value)
8003
0
        column_end = column_end_style_value.release_nonnull()->as_grid_track_placement().grid_track_placement();
8004
0
    else
8005
0
        column_end = row_end;
8006
8007
0
    transaction.commit();
8008
0
    return ShorthandStyleValue::create(PropertyID::GridArea,
8009
0
        { PropertyID::GridRowStart, PropertyID::GridColumnStart, PropertyID::GridRowEnd, PropertyID::GridColumnEnd },
8010
0
        { GridTrackPlacementStyleValue::create(row_start), GridTrackPlacementStyleValue::create(column_start), GridTrackPlacementStyleValue::create(row_end), GridTrackPlacementStyleValue::create(column_end) });
8011
0
}
8012
8013
RefPtr<CSSStyleValue> Parser::parse_grid_shorthand_value(TokenStream<ComponentValue>& tokens)
8014
0
{
8015
    // <'grid-template'> |
8016
    // FIXME: <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? |
8017
    // FIXME: [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
8018
0
    return parse_grid_track_size_list_shorthand_value(PropertyID::Grid, tokens);
8019
0
}
8020
8021
// https://www.w3.org/TR/css-grid-1/#grid-template-areas-property
8022
RefPtr<CSSStyleValue> Parser::parse_grid_template_areas_value(TokenStream<ComponentValue>& tokens)
8023
0
{
8024
    // none | <string>+
8025
0
    Vector<Vector<String>> grid_area_rows;
8026
8027
0
    if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
8028
0
        return GridTemplateAreaStyleValue::create(move(grid_area_rows));
8029
8030
0
    auto transaction = tokens.begin_transaction();
8031
0
    while (tokens.has_next_token() && tokens.next_token().is(Token::Type::String)) {
8032
0
        Vector<String> grid_area_columns;
8033
0
        auto const parts = MUST(tokens.consume_a_token().token().string().to_string().split(' '));
8034
0
        for (auto& part : parts) {
8035
0
            grid_area_columns.append(part);
8036
0
        }
8037
0
        grid_area_rows.append(move(grid_area_columns));
8038
0
    }
8039
0
    transaction.commit();
8040
0
    return GridTemplateAreaStyleValue::create(grid_area_rows);
8041
0
}
8042
8043
static bool block_contains_var_or_attr(SimpleBlock const& block);
8044
8045
static bool function_contains_var_or_attr(Function const& function)
8046
0
{
8047
0
    if (function.name.equals_ignoring_ascii_case("var"sv) || function.name.equals_ignoring_ascii_case("attr"sv))
8048
0
        return true;
8049
0
    for (auto const& token : function.value) {
8050
0
        if (token.is_function() && function_contains_var_or_attr(token.function()))
8051
0
            return true;
8052
0
        if (token.is_block() && block_contains_var_or_attr(token.block()))
8053
0
            return true;
8054
0
    }
8055
0
    return false;
8056
0
}
8057
8058
bool block_contains_var_or_attr(SimpleBlock const& block)
8059
0
{
8060
0
    for (auto const& token : block.value) {
8061
0
        if (token.is_function() && function_contains_var_or_attr(token.function()))
8062
0
            return true;
8063
0
        if (token.is_block() && block_contains_var_or_attr(token.block()))
8064
0
            return true;
8065
0
    }
8066
0
    return false;
8067
0
}
8068
8069
Parser::ParseErrorOr<NonnullRefPtr<CSSStyleValue>> Parser::parse_css_value(PropertyID property_id, TokenStream<ComponentValue>& unprocessed_tokens, Optional<String> original_source_text)
8070
0
{
8071
0
    m_context.set_current_property_id(property_id);
8072
0
    Vector<ComponentValue> component_values;
8073
0
    bool contains_var_or_attr = false;
8074
0
    bool const property_accepts_custom_ident = property_accepts_type(property_id, ValueType::CustomIdent);
8075
8076
0
    while (unprocessed_tokens.has_next_token()) {
8077
0
        auto const& token = unprocessed_tokens.consume_a_token();
8078
8079
0
        if (token.is(Token::Type::Semicolon)) {
8080
0
            unprocessed_tokens.reconsume_current_input_token();
8081
0
            break;
8082
0
        }
8083
8084
0
        if (property_id != PropertyID::Custom) {
8085
0
            if (token.is(Token::Type::Whitespace))
8086
0
                continue;
8087
8088
0
            if (!property_accepts_custom_ident && token.is(Token::Type::Ident) && has_ignored_vendor_prefix(token.token().ident()))
8089
0
                return ParseError::IncludesIgnoredVendorPrefix;
8090
0
        }
8091
8092
0
        if (!contains_var_or_attr) {
8093
0
            if (token.is_function() && function_contains_var_or_attr(token.function()))
8094
0
                contains_var_or_attr = true;
8095
0
            else if (token.is_block() && block_contains_var_or_attr(token.block()))
8096
0
                contains_var_or_attr = true;
8097
0
        }
8098
8099
0
        component_values.append(token);
8100
0
    }
8101
8102
0
    if (property_id == PropertyID::Custom || contains_var_or_attr)
8103
0
        return UnresolvedStyleValue::create(move(component_values), contains_var_or_attr, original_source_text);
8104
8105
0
    if (component_values.is_empty())
8106
0
        return ParseError::SyntaxError;
8107
8108
0
    auto tokens = TokenStream { component_values };
8109
8110
0
    if (component_values.size() == 1) {
8111
0
        if (auto parsed_value = parse_builtin_value(tokens))
8112
0
            return parsed_value.release_nonnull();
8113
0
    }
8114
8115
    // Special-case property handling
8116
0
    switch (property_id) {
8117
0
    case PropertyID::AspectRatio:
8118
0
        if (auto parsed_value = parse_aspect_ratio_value(tokens); parsed_value && !tokens.has_next_token())
8119
0
            return parsed_value.release_nonnull();
8120
0
        return ParseError::SyntaxError;
8121
0
    case PropertyID::BackdropFilter:
8122
0
    case PropertyID::Filter:
8123
0
        if (auto parsed_value = parse_filter_value_list_value(tokens); parsed_value && !tokens.has_next_token())
8124
0
            return parsed_value.release_nonnull();
8125
0
        return ParseError::SyntaxError;
8126
0
    case PropertyID::Background:
8127
0
        if (auto parsed_value = parse_background_value(tokens); parsed_value && !tokens.has_next_token())
8128
0
            return parsed_value.release_nonnull();
8129
0
        return ParseError::SyntaxError;
8130
0
    case PropertyID::BackgroundAttachment:
8131
0
    case PropertyID::BackgroundClip:
8132
0
    case PropertyID::BackgroundImage:
8133
0
    case PropertyID::BackgroundOrigin:
8134
0
        if (auto parsed_value = parse_simple_comma_separated_value_list(property_id, tokens))
8135
0
            return parsed_value.release_nonnull();
8136
0
        return ParseError::SyntaxError;
8137
0
    case PropertyID::BackgroundPosition:
8138
0
        if (auto parsed_value = parse_comma_separated_value_list(tokens, [this](auto& tokens) { return parse_position_value(tokens, PositionParsingMode::BackgroundPosition); }))
8139
0
            return parsed_value.release_nonnull();
8140
0
        return ParseError::SyntaxError;
8141
0
    case PropertyID::BackgroundPositionX:
8142
0
    case PropertyID::BackgroundPositionY:
8143
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); }))
8144
0
            return parsed_value.release_nonnull();
8145
0
        return ParseError::SyntaxError;
8146
0
    case PropertyID::BackgroundRepeat:
8147
0
        if (auto parsed_value = parse_comma_separated_value_list(tokens, [this](auto& tokens) { return parse_single_background_repeat_value(tokens); }))
8148
0
            return parsed_value.release_nonnull();
8149
0
        return ParseError::SyntaxError;
8150
0
    case PropertyID::BackgroundSize:
8151
0
        if (auto parsed_value = parse_comma_separated_value_list(tokens, [this](auto& tokens) { return parse_single_background_size_value(tokens); }))
8152
0
            return parsed_value.release_nonnull();
8153
0
        return ParseError::SyntaxError;
8154
0
    case PropertyID::Border:
8155
0
    case PropertyID::BorderBottom:
8156
0
    case PropertyID::BorderLeft:
8157
0
    case PropertyID::BorderRight:
8158
0
    case PropertyID::BorderTop:
8159
0
        if (auto parsed_value = parse_border_value(property_id, tokens); parsed_value && !tokens.has_next_token())
8160
0
            return parsed_value.release_nonnull();
8161
0
        return ParseError::SyntaxError;
8162
0
    case PropertyID::BorderTopLeftRadius:
8163
0
    case PropertyID::BorderTopRightRadius:
8164
0
    case PropertyID::BorderBottomRightRadius:
8165
0
    case PropertyID::BorderBottomLeftRadius:
8166
0
        if (auto parsed_value = parse_border_radius_value(tokens); parsed_value && !tokens.has_next_token())
8167
0
            return parsed_value.release_nonnull();
8168
0
        return ParseError::SyntaxError;
8169
0
    case PropertyID::BorderRadius:
8170
0
        if (auto parsed_value = parse_border_radius_shorthand_value(tokens); parsed_value && !tokens.has_next_token())
8171
0
            return parsed_value.release_nonnull();
8172
0
        return ParseError::SyntaxError;
8173
0
    case PropertyID::BoxShadow:
8174
0
        if (auto parsed_value = parse_shadow_value(tokens, AllowInsetKeyword::Yes); parsed_value && !tokens.has_next_token())
8175
0
            return parsed_value.release_nonnull();
8176
0
        return ParseError::SyntaxError;
8177
0
    case PropertyID::Columns:
8178
0
        if (auto parsed_value = parse_columns_value(tokens); parsed_value && !tokens.has_next_token())
8179
0
            return parsed_value.release_nonnull();
8180
0
        return ParseError::SyntaxError;
8181
0
    case PropertyID::Content:
8182
0
        if (auto parsed_value = parse_content_value(tokens); parsed_value && !tokens.has_next_token())
8183
0
            return parsed_value.release_nonnull();
8184
0
        return ParseError::SyntaxError;
8185
0
    case PropertyID::CounterIncrement:
8186
0
        if (auto parsed_value = parse_counter_increment_value(tokens); parsed_value && !tokens.has_next_token())
8187
0
            return parsed_value.release_nonnull();
8188
0
        return ParseError::SyntaxError;
8189
0
    case PropertyID::CounterReset:
8190
0
        if (auto parsed_value = parse_counter_reset_value(tokens); parsed_value && !tokens.has_next_token())
8191
0
            return parsed_value.release_nonnull();
8192
0
        return ParseError::SyntaxError;
8193
0
    case PropertyID::CounterSet:
8194
0
        if (auto parsed_value = parse_counter_set_value(tokens); parsed_value && !tokens.has_next_token())
8195
0
            return parsed_value.release_nonnull();
8196
0
        return ParseError::SyntaxError;
8197
0
    case PropertyID::Display:
8198
0
        if (auto parsed_value = parse_display_value(tokens); parsed_value && !tokens.has_next_token())
8199
0
            return parsed_value.release_nonnull();
8200
0
        return ParseError::SyntaxError;
8201
0
    case PropertyID::Flex:
8202
0
        if (auto parsed_value = parse_flex_shorthand_value(tokens); parsed_value && !tokens.has_next_token())
8203
0
            return parsed_value.release_nonnull();
8204
0
        return ParseError::SyntaxError;
8205
0
    case PropertyID::FlexFlow:
8206
0
        if (auto parsed_value = parse_flex_flow_value(tokens); parsed_value && !tokens.has_next_token())
8207
0
            return parsed_value.release_nonnull();
8208
0
        return ParseError::SyntaxError;
8209
0
    case PropertyID::Font:
8210
0
        if (auto parsed_value = parse_font_value(tokens); parsed_value && !tokens.has_next_token())
8211
0
            return parsed_value.release_nonnull();
8212
0
        return ParseError::SyntaxError;
8213
0
    case PropertyID::FontFamily:
8214
0
        if (auto parsed_value = parse_font_family_value(tokens); parsed_value && !tokens.has_next_token())
8215
0
            return parsed_value.release_nonnull();
8216
0
        return ParseError::SyntaxError;
8217
0
    case PropertyID::FontFeatureSettings:
8218
0
        if (auto parsed_value = parse_font_feature_settings_value(tokens); parsed_value && !tokens.has_next_token())
8219
0
            return parsed_value.release_nonnull();
8220
0
        return ParseError::SyntaxError;
8221
0
    case PropertyID::FontLanguageOverride:
8222
0
        if (auto parsed_value = parse_font_language_override_value(tokens); parsed_value && !tokens.has_next_token())
8223
0
            return parsed_value.release_nonnull();
8224
0
        return ParseError::SyntaxError;
8225
0
    case PropertyID::FontVariationSettings:
8226
0
        if (auto parsed_value = parse_font_variation_settings_value(tokens); parsed_value && !tokens.has_next_token())
8227
0
            return parsed_value.release_nonnull();
8228
0
        return ParseError::SyntaxError;
8229
0
    case PropertyID::GridArea:
8230
0
        if (auto parsed_value = parse_grid_area_shorthand_value(tokens); parsed_value && !tokens.has_next_token())
8231
0
            return parsed_value.release_nonnull();
8232
0
        return ParseError::SyntaxError;
8233
0
    case PropertyID::GridAutoFlow:
8234
0
        if (auto parsed_value = parse_grid_auto_flow_value(tokens); parsed_value && !tokens.has_next_token())
8235
0
            return parsed_value.release_nonnull();
8236
0
        return ParseError::SyntaxError;
8237
0
    case PropertyID::GridColumn:
8238
0
        if (auto parsed_value = parse_grid_track_placement_shorthand_value(property_id, tokens); parsed_value && !tokens.has_next_token())
8239
0
            return parsed_value.release_nonnull();
8240
0
        return ParseError::SyntaxError;
8241
0
    case PropertyID::GridColumnEnd:
8242
0
        if (auto parsed_value = parse_grid_track_placement(tokens); parsed_value && !tokens.has_next_token())
8243
0
            return parsed_value.release_nonnull();
8244
0
        return ParseError::SyntaxError;
8245
0
    case PropertyID::GridColumnStart:
8246
0
        if (auto parsed_value = parse_grid_track_placement(tokens); parsed_value && !tokens.has_next_token())
8247
0
            return parsed_value.release_nonnull();
8248
0
        return ParseError::SyntaxError;
8249
0
    case PropertyID::GridRow:
8250
0
        if (auto parsed_value = parse_grid_track_placement_shorthand_value(property_id, tokens); parsed_value && !tokens.has_next_token())
8251
0
            return parsed_value.release_nonnull();
8252
0
        return ParseError::SyntaxError;
8253
0
    case PropertyID::GridRowEnd:
8254
0
        if (auto parsed_value = parse_grid_track_placement(tokens); parsed_value && !tokens.has_next_token())
8255
0
            return parsed_value.release_nonnull();
8256
0
        return ParseError::SyntaxError;
8257
0
    case PropertyID::GridRowStart:
8258
0
        if (auto parsed_value = parse_grid_track_placement(tokens); parsed_value && !tokens.has_next_token())
8259
0
            return parsed_value.release_nonnull();
8260
0
        return ParseError::SyntaxError;
8261
0
    case PropertyID::Grid:
8262
0
        if (auto parsed_value = parse_grid_shorthand_value(tokens); parsed_value && !tokens.has_next_token())
8263
0
            return parsed_value.release_nonnull();
8264
0
        return ParseError::SyntaxError;
8265
0
    case PropertyID::GridTemplate:
8266
0
        if (auto parsed_value = parse_grid_track_size_list_shorthand_value(property_id, tokens); parsed_value && !tokens.has_next_token())
8267
0
            return parsed_value.release_nonnull();
8268
0
        return ParseError::SyntaxError;
8269
0
    case PropertyID::GridTemplateAreas:
8270
0
        if (auto parsed_value = parse_grid_template_areas_value(tokens); parsed_value && !tokens.has_next_token())
8271
0
            return parsed_value.release_nonnull();
8272
0
        return ParseError::SyntaxError;
8273
0
    case PropertyID::GridTemplateColumns:
8274
0
        if (auto parsed_value = parse_grid_track_size_list(tokens); parsed_value && !tokens.has_next_token())
8275
0
            return parsed_value.release_nonnull();
8276
0
        return ParseError::SyntaxError;
8277
0
    case PropertyID::GridTemplateRows:
8278
0
        if (auto parsed_value = parse_grid_track_size_list(tokens); parsed_value && !tokens.has_next_token())
8279
0
            return parsed_value.release_nonnull();
8280
0
        return ParseError::SyntaxError;
8281
0
    case PropertyID::GridAutoColumns:
8282
0
        if (auto parsed_value = parse_grid_auto_track_sizes(tokens); parsed_value && !tokens.has_next_token())
8283
0
            return parsed_value.release_nonnull();
8284
0
        return ParseError::SyntaxError;
8285
0
    case PropertyID::GridAutoRows:
8286
0
        if (auto parsed_value = parse_grid_auto_track_sizes(tokens); parsed_value && !tokens.has_next_token())
8287
0
            return parsed_value.release_nonnull();
8288
0
        return ParseError::SyntaxError;
8289
0
    case PropertyID::ListStyle:
8290
0
        if (auto parsed_value = parse_list_style_value(tokens); parsed_value && !tokens.has_next_token())
8291
0
            return parsed_value.release_nonnull();
8292
0
        return ParseError::SyntaxError;
8293
0
    case PropertyID::MathDepth:
8294
0
        if (auto parsed_value = parse_math_depth_value(tokens); parsed_value && !tokens.has_next_token())
8295
0
            return parsed_value.release_nonnull();
8296
0
        return ParseError::SyntaxError;
8297
0
    case PropertyID::Overflow:
8298
0
        if (auto parsed_value = parse_overflow_value(tokens); parsed_value && !tokens.has_next_token())
8299
0
            return parsed_value.release_nonnull();
8300
0
        return ParseError::SyntaxError;
8301
0
    case PropertyID::PlaceContent:
8302
0
        if (auto parsed_value = parse_place_content_value(tokens); parsed_value && !tokens.has_next_token())
8303
0
            return parsed_value.release_nonnull();
8304
0
        return ParseError::SyntaxError;
8305
0
    case PropertyID::PlaceItems:
8306
0
        if (auto parsed_value = parse_place_items_value(tokens); parsed_value && !tokens.has_next_token())
8307
0
            return parsed_value.release_nonnull();
8308
0
        return ParseError::SyntaxError;
8309
0
    case PropertyID::PlaceSelf:
8310
0
        if (auto parsed_value = parse_place_self_value(tokens); parsed_value && !tokens.has_next_token())
8311
0
            return parsed_value.release_nonnull();
8312
0
        return ParseError::SyntaxError;
8313
0
    case PropertyID::Quotes:
8314
0
        if (auto parsed_value = parse_quotes_value(tokens); parsed_value && !tokens.has_next_token())
8315
0
            return parsed_value.release_nonnull();
8316
0
        return ParseError::SyntaxError;
8317
0
    case PropertyID::Rotate:
8318
0
        if (auto parsed_value = parse_rotate_value(tokens); parsed_value && !tokens.has_next_token())
8319
0
            return parsed_value.release_nonnull();
8320
0
        return ParseError::SyntaxError;
8321
0
    case PropertyID::ScrollbarGutter:
8322
0
        if (auto parsed_value = parse_scrollbar_gutter_value(tokens); parsed_value && !tokens.has_next_token())
8323
0
            return parsed_value.release_nonnull();
8324
0
        return ParseError::SyntaxError;
8325
0
    case PropertyID::TextDecoration:
8326
0
        if (auto parsed_value = parse_text_decoration_value(tokens); parsed_value && !tokens.has_next_token())
8327
0
            return parsed_value.release_nonnull();
8328
0
        return ParseError::SyntaxError;
8329
0
    case PropertyID::TextDecorationLine:
8330
0
        if (auto parsed_value = parse_text_decoration_line_value(tokens); parsed_value && !tokens.has_next_token())
8331
0
            return parsed_value.release_nonnull();
8332
0
        return ParseError::SyntaxError;
8333
0
    case PropertyID::TextShadow:
8334
0
        if (auto parsed_value = parse_shadow_value(tokens, AllowInsetKeyword::No); parsed_value && !tokens.has_next_token())
8335
0
            return parsed_value.release_nonnull();
8336
0
        return ParseError::SyntaxError;
8337
0
    case PropertyID::Transform:
8338
0
        if (auto parsed_value = parse_transform_value(tokens); parsed_value && !tokens.has_next_token())
8339
0
            return parsed_value.release_nonnull();
8340
0
        return ParseError::SyntaxError;
8341
0
    case PropertyID::TransformOrigin:
8342
0
        if (auto parsed_value = parse_transform_origin_value(tokens); parsed_value && !tokens.has_next_token())
8343
0
            return parsed_value.release_nonnull();
8344
0
        return ParseError::SyntaxError;
8345
0
    case PropertyID::Transition:
8346
0
        if (auto parsed_value = parse_transition_value(tokens); parsed_value && !tokens.has_next_token())
8347
0
            return parsed_value.release_nonnull();
8348
0
        return ParseError::SyntaxError;
8349
0
    default:
8350
0
        break;
8351
0
    }
8352
8353
    // If there's only 1 ComponentValue, we can only produce a single CSSStyleValue.
8354
0
    if (component_values.size() == 1) {
8355
0
        auto stream = TokenStream { component_values };
8356
0
        if (auto parsed_value = parse_css_value_for_property(property_id, stream))
8357
0
            return parsed_value.release_nonnull();
8358
0
    } else {
8359
0
        StyleValueVector parsed_values;
8360
0
        auto stream = TokenStream { component_values };
8361
0
        while (auto parsed_value = parse_css_value_for_property(property_id, stream)) {
8362
0
            parsed_values.append(parsed_value.release_nonnull());
8363
0
            if (!stream.has_next_token())
8364
0
                break;
8365
0
        }
8366
8367
        // Some types (such as <ratio>) can be made from multiple ComponentValues, so if we only made 1 CSSStyleValue, return it directly.
8368
0
        if (parsed_values.size() == 1)
8369
0
            return *parsed_values.take_first();
8370
8371
0
        if (!parsed_values.is_empty() && parsed_values.size() <= property_maximum_value_count(property_id))
8372
0
            return StyleValueList::create(move(parsed_values), StyleValueList::Separator::Space);
8373
0
    }
8374
8375
    // We have multiple values, but the property claims to accept only a single one, check if it's a shorthand property.
8376
0
    auto unassigned_properties = longhands_for_shorthand(property_id);
8377
0
    if (unassigned_properties.is_empty())
8378
0
        return ParseError::SyntaxError;
8379
8380
0
    auto stream = TokenStream { component_values };
8381
8382
0
    HashMap<UnderlyingType<PropertyID>, Vector<ValueComparingNonnullRefPtr<CSSStyleValue const>>> assigned_values;
8383
8384
0
    while (stream.has_next_token() && !unassigned_properties.is_empty()) {
8385
0
        auto property_and_value = parse_css_value_for_properties(unassigned_properties, stream);
8386
0
        if (property_and_value.has_value()) {
8387
0
            auto property = property_and_value->property;
8388
0
            auto value = property_and_value->style_value;
8389
0
            auto& values = assigned_values.ensure(to_underlying(property));
8390
0
            if (values.size() + 1 == property_maximum_value_count(property)) {
8391
                // We're done with this property, move on to the next one.
8392
0
                unassigned_properties.remove_first_matching([&](auto& unassigned_property) { return unassigned_property == property; });
8393
0
            }
8394
8395
0
            values.append(value.release_nonnull());
8396
0
            continue;
8397
0
        }
8398
8399
        // No property matched, so we're done.
8400
0
        dbgln("No property (from {} properties) matched {}", unassigned_properties.size(), stream.next_token().to_debug_string());
8401
0
        for (auto id : unassigned_properties)
8402
0
            dbgln("    {}", string_from_property_id(id));
8403
0
        break;
8404
0
    }
8405
8406
0
    for (auto& property : unassigned_properties)
8407
0
        assigned_values.ensure(to_underlying(property)).append(property_initial_value(m_context.realm(), property));
8408
8409
0
    stream.discard_whitespace();
8410
0
    if (stream.has_next_token())
8411
0
        return ParseError::SyntaxError;
8412
8413
0
    Vector<PropertyID> longhand_properties;
8414
0
    longhand_properties.ensure_capacity(assigned_values.size());
8415
0
    for (auto& it : assigned_values)
8416
0
        longhand_properties.unchecked_append(static_cast<PropertyID>(it.key));
8417
8418
0
    StyleValueVector longhand_values;
8419
0
    longhand_values.ensure_capacity(assigned_values.size());
8420
0
    for (auto& it : assigned_values) {
8421
0
        if (it.value.size() == 1)
8422
0
            longhand_values.unchecked_append(it.value.take_first());
8423
0
        else
8424
0
            longhand_values.unchecked_append(StyleValueList::create(move(it.value), StyleValueList::Separator::Space));
8425
0
    }
8426
8427
0
    return { ShorthandStyleValue::create(property_id, move(longhand_properties), move(longhand_values)) };
8428
0
}
8429
8430
RefPtr<CSSStyleValue> Parser::parse_css_value_for_property(PropertyID property_id, TokenStream<ComponentValue>& tokens)
8431
0
{
8432
0
    return parse_css_value_for_properties({ &property_id, 1 }, tokens)
8433
0
        .map([](auto& it) { return it.style_value; })
8434
0
        .value_or(nullptr);
8435
0
}
8436
8437
Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(ReadonlySpan<PropertyID> property_ids, TokenStream<ComponentValue>& tokens)
8438
0
{
8439
0
    auto any_property_accepts_type = [](ReadonlySpan<PropertyID> property_ids, ValueType value_type) -> Optional<PropertyID> {
8440
0
        for (auto const& property : property_ids) {
8441
0
            if (property_accepts_type(property, value_type))
8442
0
                return property;
8443
0
        }
8444
0
        return {};
8445
0
    };
8446
0
    auto any_property_accepts_type_percentage = [](ReadonlySpan<PropertyID> property_ids, ValueType value_type) -> Optional<PropertyID> {
8447
0
        for (auto const& property : property_ids) {
8448
0
            if (property_accepts_type(property, value_type) && property_accepts_type(property, ValueType::Percentage))
8449
0
                return property;
8450
0
        }
8451
0
        return {};
8452
0
    };
8453
0
    auto any_property_accepts_keyword = [](ReadonlySpan<PropertyID> property_ids, Keyword keyword) -> Optional<PropertyID> {
8454
0
        for (auto const& property : property_ids) {
8455
0
            if (property_accepts_keyword(property, keyword))
8456
0
                return property;
8457
0
        }
8458
0
        return {};
8459
0
    };
8460
8461
0
    auto& peek_token = tokens.next_token();
8462
8463
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::EasingFunction); property.has_value()) {
8464
0
        if (auto maybe_easing_function = parse_easing_value(tokens))
8465
0
            return PropertyAndValue { *property, maybe_easing_function };
8466
0
    }
8467
8468
0
    if (peek_token.is(Token::Type::Ident)) {
8469
        // NOTE: We do not try to parse "CSS-wide keywords" here. https://www.w3.org/TR/css-values-4/#common-keywords
8470
        //       These are only valid on their own, and so should be parsed directly in `parse_css_value()`.
8471
0
        auto keyword = keyword_from_string(peek_token.token().ident());
8472
0
        if (keyword.has_value()) {
8473
0
            if (auto property = any_property_accepts_keyword(property_ids, keyword.value()); property.has_value()) {
8474
0
                tokens.discard_a_token();
8475
0
                return PropertyAndValue { *property, CSSKeywordValue::create(keyword.value()) };
8476
0
            }
8477
0
        }
8478
8479
        // Custom idents
8480
0
        if (auto property = any_property_accepts_type(property_ids, ValueType::CustomIdent); property.has_value()) {
8481
0
            if (auto custom_ident = parse_custom_ident_value(tokens, {}))
8482
0
                return PropertyAndValue { *property, custom_ident };
8483
0
        }
8484
0
    }
8485
8486
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::Color); property.has_value()) {
8487
0
        if (auto maybe_color = parse_color_value(tokens))
8488
0
            return PropertyAndValue { *property, maybe_color };
8489
0
    }
8490
8491
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::Counter); property.has_value()) {
8492
0
        if (auto maybe_counter = parse_counter_value(tokens))
8493
0
            return PropertyAndValue { *property, maybe_counter };
8494
0
    }
8495
8496
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::Image); property.has_value()) {
8497
0
        if (auto maybe_image = parse_image_value(tokens))
8498
0
            return PropertyAndValue { *property, maybe_image };
8499
0
    }
8500
8501
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::Position); property.has_value()) {
8502
0
        if (auto maybe_position = parse_position_value(tokens))
8503
0
            return PropertyAndValue { *property, maybe_position };
8504
0
    }
8505
8506
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::BackgroundPosition); property.has_value()) {
8507
0
        if (auto maybe_position = parse_position_value(tokens, PositionParsingMode::BackgroundPosition))
8508
0
            return PropertyAndValue { *property, maybe_position };
8509
0
    }
8510
8511
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::BasicShape); property.has_value()) {
8512
0
        if (auto maybe_basic_shape = parse_basic_shape_value(tokens))
8513
0
            return PropertyAndValue { *property, maybe_basic_shape };
8514
0
    }
8515
8516
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::Ratio); property.has_value()) {
8517
0
        if (auto maybe_ratio = parse_ratio_value(tokens))
8518
0
            return PropertyAndValue { *property, maybe_ratio };
8519
0
    }
8520
8521
0
    auto property_accepting_integer = any_property_accepts_type(property_ids, ValueType::Integer);
8522
0
    auto property_accepting_number = any_property_accepts_type(property_ids, ValueType::Number);
8523
0
    bool property_accepts_numeric = property_accepting_integer.has_value() || property_accepting_number.has_value();
8524
8525
0
    if (peek_token.is(Token::Type::Number) && property_accepts_numeric) {
8526
0
        if (peek_token.token().number().is_integer() && property_accepting_integer.has_value()) {
8527
0
            auto integer = IntegerStyleValue::create(peek_token.token().number().integer_value());
8528
0
            if (property_accepts_integer(*property_accepting_integer, integer->as_integer().integer())) {
8529
0
                tokens.discard_a_token(); // integer
8530
0
                return PropertyAndValue { *property_accepting_integer, integer };
8531
0
            }
8532
0
        }
8533
0
        if (property_accepting_number.has_value()) {
8534
0
            auto number = NumberStyleValue::create(peek_token.token().number().value());
8535
0
            if (property_accepts_number(*property_accepting_number, number->as_number().number())) {
8536
0
                tokens.discard_a_token(); // number
8537
0
                return PropertyAndValue { *property_accepting_number, number };
8538
0
            }
8539
0
        }
8540
0
    }
8541
8542
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::OpenTypeTag); property.has_value()) {
8543
0
        if (auto maybe_rect = parse_opentype_tag_value(tokens))
8544
0
            return PropertyAndValue { *property, maybe_rect };
8545
0
    }
8546
8547
0
    if (peek_token.is(Token::Type::Percentage)) {
8548
0
        auto percentage = Percentage(peek_token.token().percentage());
8549
0
        if (auto property = any_property_accepts_type(property_ids, ValueType::Percentage); property.has_value() && property_accepts_percentage(*property, percentage)) {
8550
0
            tokens.discard_a_token();
8551
0
            return PropertyAndValue { *property, PercentageStyleValue::create(percentage) };
8552
0
        }
8553
0
    }
8554
8555
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::Rect); property.has_value()) {
8556
0
        if (auto maybe_rect = parse_rect_value(tokens))
8557
0
            return PropertyAndValue { *property, maybe_rect };
8558
0
    }
8559
8560
0
    if (peek_token.is(Token::Type::String)) {
8561
0
        if (auto property = any_property_accepts_type(property_ids, ValueType::String); property.has_value())
8562
0
            return PropertyAndValue { *property, StringStyleValue::create(tokens.consume_a_token().token().string()) };
8563
0
    }
8564
8565
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::Url); property.has_value()) {
8566
0
        if (auto url = parse_url_value(tokens))
8567
0
            return PropertyAndValue { *property, url };
8568
0
    }
8569
8570
0
    bool property_accepts_dimension = any_property_accepts_type(property_ids, ValueType::Angle).has_value()
8571
0
        || any_property_accepts_type(property_ids, ValueType::Flex).has_value()
8572
0
        || any_property_accepts_type(property_ids, ValueType::Frequency).has_value()
8573
0
        || any_property_accepts_type(property_ids, ValueType::Length).has_value()
8574
0
        || any_property_accepts_type(property_ids, ValueType::Percentage).has_value()
8575
0
        || any_property_accepts_type(property_ids, ValueType::Resolution).has_value()
8576
0
        || any_property_accepts_type(property_ids, ValueType::Time).has_value();
8577
8578
0
    if (property_accepts_dimension) {
8579
0
        if (peek_token.is(Token::Type::Number) && m_context.is_parsing_svg_presentation_attribute()) {
8580
0
            auto transaction = tokens.begin_transaction();
8581
0
            auto token = tokens.consume_a_token();
8582
            // https://svgwg.org/svg2-draft/types.html#presentation-attribute-css-value
8583
            // We need to allow <number> in any place that expects a <length> or <angle>.
8584
            // FIXME: How should these numbers be interpreted? https://github.com/w3c/svgwg/issues/792
8585
            //        For now: Convert them to px lengths, or deg angles.
8586
0
            auto angle = Angle::make_degrees(token.token().number_value());
8587
0
            if (auto property = any_property_accepts_type(property_ids, ValueType::Angle); property.has_value() && property_accepts_angle(*property, angle)) {
8588
0
                transaction.commit();
8589
0
                return PropertyAndValue { *property, AngleStyleValue::create(angle) };
8590
0
            }
8591
0
            auto length = Length::make_px(CSSPixels::nearest_value_for(token.token().number_value()));
8592
0
            if (auto property = any_property_accepts_type(property_ids, ValueType::Length); property.has_value() && property_accepts_length(*property, length)) {
8593
0
                transaction.commit();
8594
0
                return PropertyAndValue { *property, LengthStyleValue::create(length) };
8595
0
            }
8596
0
        }
8597
8598
0
        auto transaction = tokens.begin_transaction();
8599
0
        if (auto maybe_dimension = parse_dimension(peek_token); maybe_dimension.has_value()) {
8600
0
            tokens.discard_a_token();
8601
0
            auto dimension = maybe_dimension.release_value();
8602
0
            if (dimension.is_angle()) {
8603
0
                auto angle = dimension.angle();
8604
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Angle); property.has_value() && property_accepts_angle(*property, angle)) {
8605
0
                    transaction.commit();
8606
0
                    return PropertyAndValue { *property, AngleStyleValue::create(angle) };
8607
0
                }
8608
0
            }
8609
0
            if (dimension.is_flex()) {
8610
0
                auto flex = dimension.flex();
8611
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Flex); property.has_value() && property_accepts_flex(*property, flex)) {
8612
0
                    transaction.commit();
8613
0
                    return PropertyAndValue { *property, FlexStyleValue::create(flex) };
8614
0
                }
8615
0
            }
8616
0
            if (dimension.is_frequency()) {
8617
0
                auto frequency = dimension.frequency();
8618
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Frequency); property.has_value() && property_accepts_frequency(*property, frequency)) {
8619
0
                    transaction.commit();
8620
0
                    return PropertyAndValue { *property, FrequencyStyleValue::create(frequency) };
8621
0
                }
8622
0
            }
8623
0
            if (dimension.is_length()) {
8624
0
                auto length = dimension.length();
8625
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Length); property.has_value() && property_accepts_length(*property, length)) {
8626
0
                    transaction.commit();
8627
0
                    return PropertyAndValue { *property, LengthStyleValue::create(length) };
8628
0
                }
8629
0
            }
8630
0
            if (dimension.is_resolution()) {
8631
0
                auto resolution = dimension.resolution();
8632
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Resolution); property.has_value() && property_accepts_resolution(*property, resolution)) {
8633
0
                    transaction.commit();
8634
0
                    return PropertyAndValue { *property, ResolutionStyleValue::create(resolution) };
8635
0
                }
8636
0
            }
8637
0
            if (dimension.is_time()) {
8638
0
                auto time = dimension.time();
8639
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Time); property.has_value() && property_accepts_time(*property, time)) {
8640
0
                    transaction.commit();
8641
0
                    return PropertyAndValue { *property, TimeStyleValue::create(time) };
8642
0
                }
8643
0
            }
8644
0
        }
8645
0
    }
8646
8647
    // In order to not end up parsing `calc()` and other math expressions multiple times,
8648
    // we parse it once, and then see if its resolved type matches what the property accepts.
8649
0
    if (peek_token.is_function() && (property_accepts_dimension || property_accepts_numeric)) {
8650
0
        if (auto maybe_calculated = parse_calculated_value(peek_token); maybe_calculated) {
8651
0
            tokens.discard_a_token();
8652
0
            auto& calculated = *maybe_calculated;
8653
            // This is a bit sensitive to ordering: `<foo>` and `<percentage>` have to be checked before `<foo-percentage>`.
8654
            // FIXME: When parsing SVG presentation attributes, <number> is permitted wherever <length>, <length-percentage>, or <angle> are.
8655
            //        The specifics are unclear, so I'm ignoring this for calculated values for now.
8656
            //        See https://github.com/w3c/svgwg/issues/792
8657
0
            if (calculated.resolves_to_percentage()) {
8658
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Percentage); property.has_value())
8659
0
                    return PropertyAndValue { *property, calculated };
8660
0
            } else if (calculated.resolves_to_angle()) {
8661
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Angle); property.has_value())
8662
0
                    return PropertyAndValue { *property, calculated };
8663
0
            } else if (calculated.resolves_to_angle_percentage()) {
8664
0
                if (auto property = any_property_accepts_type_percentage(property_ids, ValueType::Angle); property.has_value())
8665
0
                    return PropertyAndValue { *property, calculated };
8666
0
            } else if (calculated.resolves_to_flex()) {
8667
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Flex); property.has_value())
8668
0
                    return PropertyAndValue { *property, calculated };
8669
0
            } else if (calculated.resolves_to_frequency()) {
8670
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Frequency); property.has_value())
8671
0
                    return PropertyAndValue { *property, calculated };
8672
0
            } else if (calculated.resolves_to_frequency_percentage()) {
8673
0
                if (auto property = any_property_accepts_type_percentage(property_ids, ValueType::Frequency); property.has_value())
8674
0
                    return PropertyAndValue { *property, calculated };
8675
0
            } else if (calculated.resolves_to_number()) {
8676
0
                if (property_accepts_numeric) {
8677
0
                    auto property_or_resolved = property_accepting_integer.value_or_lazy_evaluated([property_accepting_number]() { return property_accepting_number.value(); });
8678
0
                    return PropertyAndValue { property_or_resolved, calculated };
8679
0
                }
8680
0
            } else if (calculated.resolves_to_number_percentage()) {
8681
0
                if (auto property = any_property_accepts_type_percentage(property_ids, ValueType::Number); property.has_value())
8682
0
                    return PropertyAndValue { *property, calculated };
8683
0
            } else if (calculated.resolves_to_length()) {
8684
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Length); property.has_value())
8685
0
                    return PropertyAndValue { *property, calculated };
8686
0
            } else if (calculated.resolves_to_length_percentage()) {
8687
0
                if (auto property = any_property_accepts_type_percentage(property_ids, ValueType::Length); property.has_value())
8688
0
                    return PropertyAndValue { *property, calculated };
8689
0
            } else if (calculated.resolves_to_time()) {
8690
0
                if (auto property = any_property_accepts_type(property_ids, ValueType::Time); property.has_value())
8691
0
                    return PropertyAndValue { *property, calculated };
8692
0
            } else if (calculated.resolves_to_time_percentage()) {
8693
0
                if (auto property = any_property_accepts_type_percentage(property_ids, ValueType::Time); property.has_value())
8694
0
                    return PropertyAndValue { *property, calculated };
8695
0
            }
8696
0
        }
8697
0
    }
8698
8699
0
    if (auto property = any_property_accepts_type(property_ids, ValueType::Paint); property.has_value()) {
8700
0
        if (auto value = parse_paint_value(tokens))
8701
0
            return PropertyAndValue { *property, value.release_nonnull() };
8702
0
    }
8703
8704
0
    return OptionalNone {};
8705
0
}
8706
8707
class UnparsedCalculationNode final : public CalculationNode {
8708
public:
8709
    static NonnullOwnPtr<UnparsedCalculationNode> create(ComponentValue component_value)
8710
0
    {
8711
0
        return adopt_own(*new (nothrow) UnparsedCalculationNode(move(component_value)));
8712
0
    }
8713
0
    virtual ~UnparsedCalculationNode() = default;
8714
8715
0
    ComponentValue& component_value() { return m_component_value; }
8716
8717
0
    virtual String to_string() const override { VERIFY_NOT_REACHED(); }
8718
0
    virtual Optional<CSSMathValue::ResolvedType> resolved_type() const override { VERIFY_NOT_REACHED(); }
8719
0
    virtual Optional<CSSNumericType> determine_type(Web::CSS::PropertyID) const override { VERIFY_NOT_REACHED(); }
8720
0
    virtual bool contains_percentage() const override { VERIFY_NOT_REACHED(); }
8721
0
    virtual CSSMathValue::CalculationResult resolve(Optional<Length::ResolutionContext const&>, CSSMathValue::PercentageBasis const&) const override { VERIFY_NOT_REACHED(); }
8722
0
    virtual void for_each_child_node(AK::Function<void(NonnullOwnPtr<CalculationNode>&)> const&) override { }
8723
8724
    virtual void dump(StringBuilder& builder, int indent) const override
8725
0
    {
8726
0
        builder.appendff("{: >{}}UNPARSED({})\n", "", indent, m_component_value.to_debug_string());
8727
0
    }
8728
0
    virtual bool equals(CalculationNode const&) const override { return false; }
8729
8730
private:
8731
    UnparsedCalculationNode(ComponentValue component_value)
8732
0
        : CalculationNode(Type::Unparsed)
8733
0
        , m_component_value(move(component_value))
8734
0
    {
8735
0
    }
8736
8737
    ComponentValue m_component_value;
8738
};
8739
8740
// https://html.spec.whatwg.org/multipage/images.html#parsing-a-sizes-attribute
8741
LengthOrCalculated Parser::Parser::parse_as_sizes_attribute()
8742
0
{
8743
    // 1. Let unparsed sizes list be the result of parsing a comma-separated list of component values
8744
    //    from the value of element's sizes attribute (or the empty string, if the attribute is absent).
8745
0
    auto unparsed_sizes_list = parse_a_comma_separated_list_of_component_values(m_token_stream);
8746
8747
    // 2. Let size be null.
8748
0
    Optional<LengthOrCalculated> size;
8749
8750
    // 3. For each unparsed size in unparsed sizes list:
8751
0
    for (auto& unparsed_size : unparsed_sizes_list) {
8752
        // 1. Remove all consecutive <whitespace-token>s from the end of unparsed size.
8753
        //    If unparsed size is now empty, that is a parse error; continue.
8754
0
        while (!unparsed_size.is_empty() && unparsed_size.last().is_token() && unparsed_size.last().token().is(Token::Type::Whitespace))
8755
0
            unparsed_size.take_last();
8756
0
        if (unparsed_size.is_empty()) {
8757
0
            log_parse_error();
8758
0
            continue;
8759
0
        }
8760
8761
        // 2. If the last component value in unparsed size is a valid non-negative <source-size-value>,
8762
        //    let size be its value and remove the component value from unparsed size.
8763
        //    FIXME: Any CSS function other than the math functions is invalid.
8764
        //    Otherwise, there is a parse error; continue.
8765
0
        auto last_value_stream = TokenStream<ComponentValue>::of_single_token(unparsed_size.last());
8766
0
        if (auto source_size_value = parse_source_size_value(last_value_stream); source_size_value.has_value()) {
8767
0
            size = source_size_value.value();
8768
0
            unparsed_size.take_last();
8769
0
        } else {
8770
0
            log_parse_error();
8771
0
            continue;
8772
0
        }
8773
8774
        // 3. Remove all consecutive <whitespace-token>s from the end of unparsed size.
8775
0
        while (!unparsed_size.is_empty() && unparsed_size.last().is_token() && unparsed_size.last().token().is(Token::Type::Whitespace))
8776
0
            unparsed_size.take_last();
8777
8778
        // If unparsed size is now empty, then return size.
8779
0
        if (unparsed_size.is_empty())
8780
0
            return size.value();
8781
8782
        // FIXME: If this was not the keyword auto and it was not the last item in unparsed sizes list, that is a parse error.
8783
8784
        // 4. Parse the remaining component values in unparsed size as a <media-condition>.
8785
        //    If it does not parse correctly, or it does parse correctly but the <media-condition> evaluates to false, continue.
8786
0
        TokenStream<ComponentValue> token_stream { unparsed_size };
8787
0
        auto media_condition = parse_media_condition(token_stream, MediaCondition::AllowOr::Yes);
8788
0
        auto context_window = m_context.window();
8789
0
        if (context_window && media_condition && media_condition->evaluate(*context_window) == MatchResult::True) {
8790
0
            return size.value();
8791
0
        }
8792
8793
        // 5. If size is not auto, then return size.
8794
0
        if (size.value().is_calculated() || !size.value().value().is_auto())
8795
0
            return size.value();
8796
0
    }
8797
8798
0
    return Length(100, Length::Type::Vw);
8799
0
}
8800
8801
// https://www.w3.org/TR/css-values-4/#parse-a-calculation
8802
OwnPtr<CalculationNode> Parser::parse_a_calculation(Vector<ComponentValue> const& original_values)
8803
0
{
8804
    // 1. Discard any <whitespace-token>s from values.
8805
    // 2. An item in values is an “operator” if it’s a <delim-token> with the value "+", "-", "*", or "/". Otherwise, it’s a “value”.
8806
0
    struct Operator {
8807
0
        char delim;
8808
0
    };
8809
0
    using Value = Variant<NonnullOwnPtr<CalculationNode>, Operator>;
8810
0
    Vector<Value> values;
8811
0
    for (auto& value : original_values) {
8812
0
        if (value.is(Token::Type::Whitespace))
8813
0
            continue;
8814
0
        if (value.is(Token::Type::Delim)) {
8815
0
            if (first_is_one_of(value.token().delim(), static_cast<u32>('+'), static_cast<u32>('-'), static_cast<u32>('*'), static_cast<u32>('/'))) {
8816
                // NOTE: Sequential operators are invalid syntax.
8817
0
                if (!values.is_empty() && values.last().has<Operator>())
8818
0
                    return nullptr;
8819
8820
0
                values.append(Operator { static_cast<char>(value.token().delim()) });
8821
0
                continue;
8822
0
            }
8823
0
        }
8824
8825
0
        if (value.is(Token::Type::Ident)) {
8826
0
            auto maybe_constant = CalculationNode::constant_type_from_string(value.token().ident());
8827
0
            if (maybe_constant.has_value()) {
8828
0
                values.append({ ConstantCalculationNode::create(maybe_constant.value()) });
8829
0
                continue;
8830
0
            }
8831
0
        }
8832
8833
0
        if (value.is(Token::Type::Number)) {
8834
0
            values.append({ NumericCalculationNode::create(value.token().number()) });
8835
0
            continue;
8836
0
        }
8837
8838
0
        if (auto dimension = parse_dimension(value); dimension.has_value()) {
8839
0
            if (dimension->is_angle())
8840
0
                values.append({ NumericCalculationNode::create(dimension->angle()) });
8841
0
            else if (dimension->is_frequency())
8842
0
                values.append({ NumericCalculationNode::create(dimension->frequency()) });
8843
0
            else if (dimension->is_length())
8844
0
                values.append({ NumericCalculationNode::create(dimension->length()) });
8845
0
            else if (dimension->is_percentage())
8846
0
                values.append({ NumericCalculationNode::create(dimension->percentage()) });
8847
0
            else if (dimension->is_resolution())
8848
0
                values.append({ NumericCalculationNode::create(dimension->resolution()) });
8849
0
            else if (dimension->is_time())
8850
0
                values.append({ NumericCalculationNode::create(dimension->time()) });
8851
0
            else if (dimension->is_flex()) {
8852
                // https://www.w3.org/TR/css3-grid-layout/#fr-unit
8853
                // NOTE: <flex> values are not <length>s (nor are they compatible with <length>s, like some <percentage> values),
8854
                //       so they cannot be represented in or combined with other unit types in calc() expressions.
8855
0
                return nullptr;
8856
0
            } else {
8857
0
                VERIFY_NOT_REACHED();
8858
0
            }
8859
0
            continue;
8860
0
        }
8861
8862
0
        values.append({ UnparsedCalculationNode::create(value) });
8863
0
    }
8864
8865
    // If we have no values, the syntax is invalid.
8866
0
    if (values.is_empty())
8867
0
        return nullptr;
8868
8869
    // NOTE: If the first or last value is an operator, the syntax is invalid.
8870
0
    if (values.first().has<Operator>() || values.last().has<Operator>())
8871
0
        return nullptr;
8872
8873
    // 3. Collect children into Product and Invert nodes.
8874
    //    For every consecutive run of value items in values separated by "*" or "/" operators:
8875
0
    while (true) {
8876
0
        Optional<size_t> first_product_operator = values.find_first_index_if([](auto const& item) {
8877
0
            return item.template has<Operator>()
8878
0
                && first_is_one_of(item.template get<Operator>().delim, '*', '/');
8879
0
        });
8880
8881
0
        if (!first_product_operator.has_value())
8882
0
            break;
8883
8884
0
        auto start_of_run = first_product_operator.value() - 1;
8885
0
        auto end_of_run = first_product_operator.value() + 1;
8886
0
        for (auto i = start_of_run + 1; i < values.size(); i += 2) {
8887
0
            auto& item = values[i];
8888
0
            if (!item.has<Operator>()) {
8889
0
                end_of_run = i - 1;
8890
0
                break;
8891
0
            }
8892
8893
0
            auto delim = item.get<Operator>().delim;
8894
0
            if (!first_is_one_of(delim, '*', '/')) {
8895
0
                end_of_run = i - 1;
8896
0
                break;
8897
0
            }
8898
0
        }
8899
8900
        // 1. For each "/" operator in the run, replace its right-hand value item rhs with an Invert node containing rhs as its child.
8901
0
        Vector<NonnullOwnPtr<CalculationNode>> run_values;
8902
0
        run_values.append(move(values[start_of_run].get<NonnullOwnPtr<CalculationNode>>()));
8903
0
        for (auto i = start_of_run + 1; i <= end_of_run; i += 2) {
8904
0
            auto& operator_ = values[i].get<Operator>().delim;
8905
0
            auto& rhs = values[i + 1];
8906
0
            if (operator_ == '/') {
8907
0
                run_values.append(InvertCalculationNode::create(move(rhs.get<NonnullOwnPtr<CalculationNode>>())));
8908
0
                continue;
8909
0
            }
8910
0
            VERIFY(operator_ == '*');
8911
0
            run_values.append(move(rhs.get<NonnullOwnPtr<CalculationNode>>()));
8912
0
        }
8913
        // 2. Replace the entire run with a Product node containing the value items of the run as its children.
8914
0
        auto product_node = ProductCalculationNode::create(move(run_values));
8915
0
        values.remove(start_of_run, end_of_run - start_of_run + 1);
8916
0
        values.insert(start_of_run, { move(product_node) });
8917
0
    }
8918
8919
    // 4. Collect children into Sum and Negate nodes.
8920
0
    Optional<NonnullOwnPtr<CalculationNode>> single_value;
8921
0
    {
8922
        // 1. For each "-" operator item in values, replace its right-hand value item rhs with a Negate node containing rhs as its child.
8923
0
        for (auto i = 0u; i < values.size(); ++i) {
8924
0
            auto& maybe_minus_operator = values[i];
8925
0
            if (!maybe_minus_operator.has<Operator>() || maybe_minus_operator.get<Operator>().delim != '-')
8926
0
                continue;
8927
8928
0
            auto rhs_index = ++i;
8929
0
            auto& rhs = values[rhs_index];
8930
8931
0
            NonnullOwnPtr<CalculationNode> negate_node = NegateCalculationNode::create(move(rhs.get<NonnullOwnPtr<CalculationNode>>()));
8932
0
            values.remove(rhs_index);
8933
0
            values.insert(rhs_index, move(negate_node));
8934
0
        }
8935
8936
        // 2. If values has only one item, and it is a Product node or a parenthesized simple block, replace values with that item.
8937
0
        if (values.size() == 1) {
8938
0
            values.first().visit(
8939
0
                [&](ComponentValue& component_value) {
8940
0
                    if (component_value.is_block() && component_value.block().is_paren())
8941
0
                        single_value = UnparsedCalculationNode::create(component_value);
8942
0
                },
8943
0
                [&](NonnullOwnPtr<CalculationNode>& node) {
8944
0
                    if (node->type() == CalculationNode::Type::Product)
8945
0
                        single_value = move(node);
8946
0
                },
8947
0
                [](auto&) {});
8948
0
        }
8949
        //    Otherwise, replace values with a Sum node containing the value items of values as its children.
8950
0
        if (!single_value.has_value()) {
8951
0
            values.remove_all_matching([](Value& value) { return value.has<Operator>(); });
8952
0
            Vector<NonnullOwnPtr<CalculationNode>> value_items;
8953
0
            value_items.ensure_capacity(values.size());
8954
0
            for (auto& value : values) {
8955
0
                if (value.has<Operator>())
8956
0
                    continue;
8957
0
                value_items.unchecked_append(move(value.get<NonnullOwnPtr<CalculationNode>>()));
8958
0
            }
8959
0
            single_value = SumCalculationNode::create(move(value_items));
8960
0
        }
8961
0
    }
8962
8963
    // 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.
8964
    //     For every leaf node leaf in values:
8965
0
    bool parsing_failed_for_child_node = false;
8966
0
    single_value.value()->for_each_child_node([&](NonnullOwnPtr<CalculationNode>& node) {
8967
0
        if (node->type() != CalculationNode::Type::Unparsed)
8968
0
            return;
8969
8970
0
        auto& unparsed_node = static_cast<UnparsedCalculationNode&>(*node);
8971
0
        auto& component_value = unparsed_node.component_value();
8972
8973
        // 1. If leaf is a parenthesized simple block, replace leaf with the result of parsing a calculation from leaf’s contents.
8974
0
        if (component_value.is_block() && component_value.block().is_paren()) {
8975
0
            auto leaf_calculation = parse_a_calculation(component_value.block().value);
8976
0
            if (!leaf_calculation) {
8977
0
                parsing_failed_for_child_node = true;
8978
0
                return;
8979
0
            }
8980
0
            node = leaf_calculation.release_nonnull();
8981
0
            return;
8982
0
        }
8983
8984
        // 2. If leaf is a math function, replace leaf with the internal representation of that math function.
8985
        // NOTE: All function tokens at this point should be math functions.
8986
0
        else if (component_value.is_function()) {
8987
0
            auto& function = component_value.function();
8988
0
            auto leaf_calculation = parse_a_calc_function_node(function);
8989
0
            if (!leaf_calculation) {
8990
0
                parsing_failed_for_child_node = true;
8991
0
                return;
8992
0
            }
8993
8994
0
            node = leaf_calculation.release_nonnull();
8995
0
            return;
8996
0
        }
8997
8998
        // NOTE: If we get here, then we have an UnparsedCalculationNode that didn't get replaced with something else.
8999
        //       So, the calc() is invalid.
9000
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());
9001
0
        parsing_failed_for_child_node = true;
9002
0
        return;
9003
0
    });
9004
9005
0
    if (parsing_failed_for_child_node)
9006
0
        return nullptr;
9007
9008
    // FIXME: 6. Return the result of simplifying a calculation tree from values.
9009
0
    return single_value.release_value();
9010
0
}
9011
9012
bool Parser::has_ignored_vendor_prefix(StringView string)
9013
0
{
9014
0
    if (!string.starts_with('-'))
9015
0
        return false;
9016
0
    if (string.starts_with("--"sv))
9017
0
        return false;
9018
0
    if (string.starts_with("-libweb-"sv))
9019
0
        return false;
9020
0
    return true;
9021
0
}
9022
9023
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)
9024
0
{
9025
    // Unresolved always contains a var() or attr(), unless it is a custom property's value, in which case we shouldn't be trying
9026
    // to produce a different CSSStyleValue from it.
9027
0
    VERIFY(unresolved.contains_var_or_attr());
9028
9029
    // If the value is invalid, we fall back to `unset`: https://www.w3.org/TR/css-variables-1/#invalid-at-computed-value-time
9030
9031
0
    auto parser = Parser::create(context, ""sv);
9032
0
    return parser.resolve_unresolved_style_value(element, pseudo_element, property_id, unresolved);
9033
0
}
9034
9035
class PropertyDependencyNode : public RefCounted<PropertyDependencyNode> {
9036
public:
9037
    static NonnullRefPtr<PropertyDependencyNode> create(FlyString name)
9038
0
    {
9039
0
        return adopt_ref(*new PropertyDependencyNode(move(name)));
9040
0
    }
9041
9042
    void add_child(NonnullRefPtr<PropertyDependencyNode> new_child)
9043
0
    {
9044
0
        for (auto const& child : m_children) {
9045
0
            if (child->m_name == new_child->m_name)
9046
0
                return;
9047
0
        }
9048
9049
        // We detect self-reference already.
9050
0
        VERIFY(new_child->m_name != m_name);
9051
0
        m_children.append(move(new_child));
9052
0
    }
9053
9054
    bool has_cycles()
9055
0
    {
9056
0
        if (m_marked)
9057
0
            return true;
9058
9059
0
        TemporaryChange change { m_marked, true };
9060
0
        for (auto& child : m_children) {
9061
0
            if (child->has_cycles())
9062
0
                return true;
9063
0
        }
9064
0
        return false;
9065
0
    }
9066
9067
private:
9068
    explicit PropertyDependencyNode(FlyString name)
9069
0
        : m_name(move(name))
9070
0
    {
9071
0
    }
9072
9073
    FlyString m_name;
9074
    Vector<NonnullRefPtr<PropertyDependencyNode>> m_children;
9075
    bool m_marked { false };
9076
};
9077
9078
NonnullRefPtr<CSSStyleValue> Parser::resolve_unresolved_style_value(DOM::Element& element, Optional<Selector::PseudoElement::Type> pseudo_element, PropertyID property_id, UnresolvedStyleValue const& unresolved)
9079
0
{
9080
0
    TokenStream unresolved_values_without_variables_expanded { unresolved.values() };
9081
0
    Vector<ComponentValue> values_with_variables_expanded;
9082
9083
0
    HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>> dependencies;
9084
0
    if (!expand_variables(element, pseudo_element, string_from_property_id(property_id), dependencies, unresolved_values_without_variables_expanded, values_with_variables_expanded))
9085
0
        return CSSKeywordValue::create(Keyword::Unset);
9086
9087
0
    TokenStream unresolved_values_with_variables_expanded { values_with_variables_expanded };
9088
0
    Vector<ComponentValue> expanded_values;
9089
0
    if (!expand_unresolved_values(element, string_from_property_id(property_id), unresolved_values_with_variables_expanded, expanded_values))
9090
0
        return CSSKeywordValue::create(Keyword::Unset);
9091
9092
0
    auto expanded_value_tokens = TokenStream { expanded_values };
9093
0
    if (auto parsed_value = parse_css_value(property_id, expanded_value_tokens); !parsed_value.is_error())
9094
0
        return parsed_value.release_value();
9095
9096
0
    return CSSKeywordValue::create(Keyword::Unset);
9097
0
}
9098
9099
static RefPtr<CSSStyleValue const> get_custom_property(DOM::Element const& element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, FlyString const& custom_property_name)
9100
0
{
9101
0
    if (pseudo_element.has_value()) {
9102
0
        if (auto it = element.custom_properties(pseudo_element).find(custom_property_name); it != element.custom_properties(pseudo_element).end())
9103
0
            return it->value.value;
9104
0
    }
9105
9106
0
    for (auto const* current_element = &element; current_element; current_element = current_element->parent_or_shadow_host_element()) {
9107
0
        if (auto it = current_element->custom_properties({}).find(custom_property_name); it != current_element->custom_properties({}).end())
9108
0
            return it->value.value;
9109
0
    }
9110
0
    return nullptr;
9111
0
}
9112
9113
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)
9114
0
{
9115
    // Arbitrary large value chosen to avoid the billion-laughs attack.
9116
    // https://www.w3.org/TR/css-variables-1/#long-variables
9117
0
    size_t const MAX_VALUE_COUNT = 16384;
9118
0
    if (source.remaining_token_count() + dest.size() > MAX_VALUE_COUNT) {
9119
0
        dbgln("Stopped expanding CSS variables: maximum length reached.");
9120
0
        return false;
9121
0
    }
9122
9123
0
    auto get_dependency_node = [&](FlyString const& name) -> NonnullRefPtr<PropertyDependencyNode> {
9124
0
        if (auto existing = dependencies.get(name); existing.has_value())
9125
0
            return *existing.value();
9126
0
        auto new_node = PropertyDependencyNode::create(name);
9127
0
        dependencies.set(name, new_node);
9128
0
        return new_node;
9129
0
    };
9130
9131
0
    while (source.has_next_token()) {
9132
0
        auto const& value = source.consume_a_token();
9133
0
        if (value.is_block()) {
9134
0
            auto const& source_block = value.block();
9135
0
            Vector<ComponentValue> block_values;
9136
0
            TokenStream source_block_contents { source_block.value };
9137
0
            if (!expand_variables(element, pseudo_element, property_name, dependencies, source_block_contents, block_values))
9138
0
                return false;
9139
0
            dest.empend(SimpleBlock { source_block.token, move(block_values) });
9140
0
            continue;
9141
0
        }
9142
0
        if (!value.is_function()) {
9143
0
            dest.empend(value);
9144
0
            continue;
9145
0
        }
9146
0
        if (!value.function().name.equals_ignoring_ascii_case("var"sv)) {
9147
0
            auto const& source_function = value.function();
9148
0
            Vector<ComponentValue> function_values;
9149
0
            TokenStream source_function_contents { source_function.value };
9150
0
            if (!expand_variables(element, pseudo_element, property_name, dependencies, source_function_contents, function_values))
9151
0
                return false;
9152
0
            dest.empend(Function { source_function.name, move(function_values) });
9153
0
            continue;
9154
0
        }
9155
9156
0
        TokenStream var_contents { value.function().value };
9157
0
        var_contents.discard_whitespace();
9158
0
        if (!var_contents.has_next_token())
9159
0
            return false;
9160
9161
0
        auto const& custom_property_name_token = var_contents.consume_a_token();
9162
0
        if (!custom_property_name_token.is(Token::Type::Ident))
9163
0
            return false;
9164
0
        auto custom_property_name = custom_property_name_token.token().ident();
9165
0
        if (!custom_property_name.bytes_as_string_view().starts_with("--"sv))
9166
0
            return false;
9167
9168
        // Detect dependency cycles. https://www.w3.org/TR/css-variables-1/#cycles
9169
        // We do not do this by the spec, since we are not keeping a graph of var dependencies around,
9170
        // but rebuilding it every time.
9171
0
        if (custom_property_name == property_name)
9172
0
            return false;
9173
0
        auto parent = get_dependency_node(property_name);
9174
0
        auto child = get_dependency_node(custom_property_name);
9175
0
        parent->add_child(child);
9176
0
        if (parent->has_cycles())
9177
0
            return false;
9178
9179
0
        if (auto custom_property_value = get_custom_property(element, pseudo_element, custom_property_name)) {
9180
0
            VERIFY(custom_property_value->is_unresolved());
9181
0
            TokenStream custom_property_tokens { custom_property_value->as_unresolved().values() };
9182
0
            if (!expand_variables(element, pseudo_element, custom_property_name, dependencies, custom_property_tokens, dest))
9183
0
                return false;
9184
0
            continue;
9185
0
        }
9186
9187
        // Use the provided fallback value, if any.
9188
0
        var_contents.discard_whitespace();
9189
0
        if (var_contents.has_next_token()) {
9190
0
            auto const& comma_token = var_contents.consume_a_token();
9191
0
            if (!comma_token.is(Token::Type::Comma))
9192
0
                return false;
9193
0
            var_contents.discard_whitespace();
9194
0
            if (!expand_variables(element, pseudo_element, property_name, dependencies, var_contents, dest))
9195
0
                return false;
9196
0
        }
9197
0
    }
9198
0
    return true;
9199
0
}
9200
9201
bool Parser::expand_unresolved_values(DOM::Element& element, FlyString const& property_name, TokenStream<ComponentValue>& source, Vector<ComponentValue>& dest)
9202
0
{
9203
0
    auto property = property_id_from_string(property_name);
9204
9205
0
    while (source.has_next_token()) {
9206
0
        auto const& value = source.consume_a_token();
9207
0
        if (value.is_function()) {
9208
0
            if (value.function().name.equals_ignoring_ascii_case("attr"sv)) {
9209
0
                if (!substitute_attr_function(element, property_name, value.function(), dest))
9210
0
                    return false;
9211
0
                continue;
9212
0
            }
9213
9214
0
            if (property.has_value()) {
9215
0
                if (auto maybe_calc_value = parse_calculated_value(value); maybe_calc_value && maybe_calc_value->is_math()) {
9216
                    // FIXME: Run the actual simplification algorithm
9217
0
                    auto& calc_value = maybe_calc_value->as_math();
9218
0
                    if (property_accepts_type(*property, ValueType::Angle) && calc_value.resolves_to_angle()) {
9219
0
                        auto resolved_value = calc_value.resolve_angle();
9220
0
                        dest.empend(Token::create_dimension(resolved_value->to_degrees(), "deg"_fly_string));
9221
0
                        continue;
9222
0
                    }
9223
0
                    if (property_accepts_type(*property, ValueType::Frequency) && calc_value.resolves_to_frequency()) {
9224
0
                        auto resolved_value = calc_value.resolve_frequency();
9225
0
                        dest.empend(Token::create_dimension(resolved_value->to_hertz(), "hz"_fly_string));
9226
0
                        continue;
9227
0
                    }
9228
0
                    if (property_accepts_type(*property, ValueType::Length) && calc_value.resolves_to_length()) {
9229
                        // FIXME: In order to resolve lengths, we need to know the font metrics in case a font-relative unit
9230
                        //  is used. So... we can't do that until style is computed?
9231
                        //  This might be easier once we have calc-simplification implemented.
9232
0
                    }
9233
0
                    if (property_accepts_type(*property, ValueType::Percentage) && calc_value.resolves_to_percentage()) {
9234
0
                        auto resolved_value = calc_value.resolve_percentage();
9235
0
                        dest.empend(Token::create_percentage(resolved_value.value().value()));
9236
0
                        continue;
9237
0
                    }
9238
0
                    if (property_accepts_type(*property, ValueType::Time) && calc_value.resolves_to_time()) {
9239
0
                        auto resolved_value = calc_value.resolve_time();
9240
0
                        dest.empend(Token::create_dimension(resolved_value->to_seconds(), "s"_fly_string));
9241
0
                        continue;
9242
0
                    }
9243
0
                    if (property_accepts_type(*property, ValueType::Number) && calc_value.resolves_to_number()) {
9244
0
                        auto resolved_value = calc_value.resolve_number();
9245
0
                        dest.empend(Token::create_number(resolved_value.value(), Number::Type::Number));
9246
0
                        continue;
9247
0
                    }
9248
0
                    if (property_accepts_type(*property, ValueType::Integer) && calc_value.resolves_to_number()) {
9249
0
                        auto resolved_value = calc_value.resolve_integer();
9250
0
                        dest.empend(Token::create_number(resolved_value.value(), Number::Type::Integer));
9251
0
                        continue;
9252
0
                    }
9253
0
                }
9254
0
            }
9255
9256
0
            auto const& source_function = value.function();
9257
0
            Vector<ComponentValue> function_values;
9258
0
            TokenStream source_function_contents { source_function.value };
9259
0
            if (!expand_unresolved_values(element, property_name, source_function_contents, function_values))
9260
0
                return false;
9261
0
            dest.empend(Function { source_function.name, move(function_values) });
9262
0
            continue;
9263
0
        }
9264
0
        if (value.is_block()) {
9265
0
            auto const& source_block = value.block();
9266
0
            TokenStream source_block_values { source_block.value };
9267
0
            Vector<ComponentValue> block_values;
9268
0
            if (!expand_unresolved_values(element, property_name, source_block_values, block_values))
9269
0
                return false;
9270
0
            dest.empend(SimpleBlock { source_block.token, move(block_values) });
9271
0
            continue;
9272
0
        }
9273
0
        dest.empend(value.token());
9274
0
    }
9275
9276
0
    return true;
9277
0
}
9278
9279
// https://drafts.csswg.org/css-values-5/#attr-substitution
9280
bool Parser::substitute_attr_function(DOM::Element& element, FlyString const& property_name, Function const& attr_function, Vector<ComponentValue>& dest)
9281
0
{
9282
    // First, parse the arguments to attr():
9283
    // attr() = attr( <q-name> <attr-type>? , <declaration-value>?)
9284
    // <attr-type> = string | url | ident | color | number | percentage | length | angle | time | frequency | flex | <dimension-unit>
9285
0
    TokenStream attr_contents { attr_function.value };
9286
0
    attr_contents.discard_whitespace();
9287
0
    if (!attr_contents.has_next_token())
9288
0
        return false;
9289
9290
    // - Attribute name
9291
    // FIXME: Support optional attribute namespace
9292
0
    if (!attr_contents.next_token().is(Token::Type::Ident))
9293
0
        return false;
9294
0
    auto attribute_name = attr_contents.consume_a_token().token().ident();
9295
0
    attr_contents.discard_whitespace();
9296
9297
    // - Attribute type (optional)
9298
0
    auto attribute_type = "string"_fly_string;
9299
0
    if (attr_contents.next_token().is(Token::Type::Ident)) {
9300
0
        attribute_type = attr_contents.consume_a_token().token().ident();
9301
0
        attr_contents.discard_whitespace();
9302
0
    }
9303
9304
    // - Comma, then fallback values (optional)
9305
0
    bool has_fallback_values = false;
9306
0
    if (attr_contents.has_next_token()) {
9307
0
        if (!attr_contents.next_token().is(Token::Type::Comma))
9308
0
            return false;
9309
0
        (void)attr_contents.consume_a_token(); // Comma
9310
0
        has_fallback_values = true;
9311
0
    }
9312
9313
    // Then, run the substitution algorithm:
9314
9315
    // 1. If the attr() function has a substitution value, replace the attr() function by the substitution value.
9316
    // https://drafts.csswg.org/css-values-5/#attr-types
9317
0
    if (element.has_attribute(attribute_name)) {
9318
0
        auto attribute_value = element.get_attribute_value(attribute_name);
9319
0
        if (attribute_type.equals_ignoring_ascii_case("angle"_fly_string)) {
9320
            // Parse a component value from the attribute’s value.
9321
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9322
            // If the result is a <dimension-token> whose unit matches the given type, the result is the substitution value.
9323
            // Otherwise, there is no substitution value.
9324
0
            if (component_value.has_value() && component_value->is(Token::Type::Dimension)) {
9325
0
                if (Angle::unit_from_name(component_value->token().dimension_unit()).has_value()) {
9326
0
                    dest.append(component_value.release_value());
9327
0
                    return true;
9328
0
                }
9329
0
            }
9330
0
        } else if (attribute_type.equals_ignoring_ascii_case("color"_fly_string)) {
9331
            // Parse a component value from the attribute’s value.
9332
            // If the result is a <hex-color> or a named color ident, the substitution value is that result as a <color>.
9333
            // Otherwise there is no substitution value.
9334
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9335
0
            if (component_value.has_value()) {
9336
0
                if ((component_value->is(Token::Type::Hash)
9337
0
                        && Color::from_string(MUST(String::formatted("#{}", component_value->token().hash_value()))).has_value())
9338
0
                    || (component_value->is(Token::Type::Ident)
9339
0
                        && Color::from_string(component_value->token().ident()).has_value())) {
9340
0
                    dest.append(component_value.release_value());
9341
0
                    return true;
9342
0
                }
9343
0
            }
9344
0
        } else if (attribute_type.equals_ignoring_ascii_case("flex"_fly_string)) {
9345
            // Parse a component value from the attribute’s value.
9346
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9347
            // If the result is a <dimension-token> whose unit matches the given type, the result is the substitution value.
9348
            // Otherwise, there is no substitution value.
9349
0
            if (component_value.has_value() && component_value->is(Token::Type::Dimension)) {
9350
0
                if (Flex::unit_from_name(component_value->token().dimension_unit()).has_value()) {
9351
0
                    dest.append(component_value.release_value());
9352
0
                    return true;
9353
0
                }
9354
0
            }
9355
0
        } else if (attribute_type.equals_ignoring_ascii_case("frequency"_fly_string)) {
9356
            // Parse a component value from the attribute’s value.
9357
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9358
            // If the result is a <dimension-token> whose unit matches the given type, the result is the substitution value.
9359
            // Otherwise, there is no substitution value.
9360
0
            if (component_value.has_value() && component_value->is(Token::Type::Dimension)) {
9361
0
                if (Frequency::unit_from_name(component_value->token().dimension_unit()).has_value()) {
9362
0
                    dest.append(component_value.release_value());
9363
0
                    return true;
9364
0
                }
9365
0
            }
9366
0
        } else if (attribute_type.equals_ignoring_ascii_case("ident"_fly_string)) {
9367
            // The substitution value is a CSS <custom-ident>, whose value is the literal value of the attribute,
9368
            // with leading and trailing ASCII whitespace stripped. (No CSS parsing of the value is performed.)
9369
            // If the attribute value, after trimming, is the empty string, there is instead no substitution value.
9370
            // If the <custom-ident>’s value is a CSS-wide keyword or `default`, there is instead no substitution value.
9371
0
            auto substitution_value = MUST(attribute_value.trim(Infra::ASCII_WHITESPACE));
9372
0
            if (!substitution_value.is_empty()
9373
0
                && !substitution_value.equals_ignoring_ascii_case("default"sv)
9374
0
                && !is_css_wide_keyword(substitution_value)) {
9375
0
                dest.empend(Token::create_ident(substitution_value));
9376
0
                return true;
9377
0
            }
9378
0
        } else if (attribute_type.equals_ignoring_ascii_case("length"_fly_string)) {
9379
            // Parse a component value from the attribute’s value.
9380
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9381
            // If the result is a <dimension-token> whose unit matches the given type, the result is the substitution value.
9382
            // Otherwise, there is no substitution value.
9383
0
            if (component_value.has_value() && component_value->is(Token::Type::Dimension)) {
9384
0
                if (Length::unit_from_name(component_value->token().dimension_unit()).has_value()) {
9385
0
                    dest.append(component_value.release_value());
9386
0
                    return true;
9387
0
                }
9388
0
            }
9389
0
        } else if (attribute_type.equals_ignoring_ascii_case("number"_fly_string)) {
9390
            // Parse a component value from the attribute’s value.
9391
            // If the result is a <number-token>, the result is the substitution value.
9392
            // Otherwise, there is no substitution value.
9393
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9394
0
            if (component_value.has_value() && component_value->is(Token::Type::Number)) {
9395
0
                dest.append(component_value.release_value());
9396
0
                return true;
9397
0
            }
9398
0
        } else if (attribute_type.equals_ignoring_ascii_case("percentage"_fly_string)) {
9399
            // Parse a component value from the attribute’s value.
9400
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9401
            // If the result is a <percentage-token>, the result is the substitution value.
9402
            // Otherwise, there is no substitution value.
9403
0
            if (component_value.has_value() && component_value->is(Token::Type::Percentage)) {
9404
0
                dest.append(component_value.release_value());
9405
0
                return true;
9406
0
            }
9407
0
        } else if (attribute_type.equals_ignoring_ascii_case("string"_fly_string)) {
9408
            // The substitution value is a CSS string, whose value is the literal value of the attribute.
9409
            // (No CSS parsing or "cleanup" of the value is performed.)
9410
            // No value triggers fallback.
9411
0
            dest.empend(Token::create_string(attribute_value));
9412
0
            return true;
9413
0
        } else if (attribute_type.equals_ignoring_ascii_case("time"_fly_string)) {
9414
            // Parse a component value from the attribute’s value.
9415
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9416
            // If the result is a <dimension-token> whose unit matches the given type, the result is the substitution value.
9417
            // Otherwise, there is no substitution value.
9418
0
            if (component_value.has_value() && component_value->is(Token::Type::Dimension)) {
9419
0
                if (Time::unit_from_name(component_value->token().dimension_unit()).has_value()) {
9420
0
                    dest.append(component_value.release_value());
9421
0
                    return true;
9422
0
                }
9423
0
            }
9424
0
        } else if (attribute_type.equals_ignoring_ascii_case("url"_fly_string)) {
9425
            // The substitution value is a CSS <url> value, whose url is the literal value of the attribute.
9426
            // (No CSS parsing or "cleanup" of the value is performed.)
9427
            // No value triggers fallback.
9428
0
            dest.empend(Token::create_url(attribute_value));
9429
0
            return true;
9430
0
        } else {
9431
            // Dimension units
9432
            // Parse a component value from the attribute’s value.
9433
            // If the result is a <number-token>, the substitution value is a dimension with the result’s value, and the given unit.
9434
            // Otherwise, there is no substitution value.
9435
0
            auto component_value = Parser::Parser::create(m_context, attribute_value).parse_as_component_value();
9436
0
            if (component_value.has_value() && component_value->is(Token::Type::Number)) {
9437
0
                if (attribute_value == "%"sv) {
9438
0
                    dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
9439
0
                    return true;
9440
0
                } else if (auto angle_unit = Angle::unit_from_name(attribute_type); angle_unit.has_value()) {
9441
0
                    dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
9442
0
                    return true;
9443
0
                } else if (auto flex_unit = Flex::unit_from_name(attribute_type); flex_unit.has_value()) {
9444
0
                    dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
9445
0
                    return true;
9446
0
                } else if (auto frequency_unit = Frequency::unit_from_name(attribute_type); frequency_unit.has_value()) {
9447
0
                    dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
9448
0
                    return true;
9449
0
                } else if (auto length_unit = Length::unit_from_name(attribute_type); length_unit.has_value()) {
9450
0
                    dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
9451
0
                    return true;
9452
0
                } else if (auto time_unit = Time::unit_from_name(attribute_type); time_unit.has_value()) {
9453
0
                    dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
9454
0
                    return true;
9455
0
                } else {
9456
                    // Not a dimension unit.
9457
0
                    return false;
9458
0
                }
9459
0
            }
9460
0
        }
9461
0
    }
9462
9463
    // 2. Otherwise, if the attr() function has a fallback value as its last argument, replace the attr() function by the fallback value.
9464
    //    If there are any var() or attr() references in the fallback, substitute them as well.
9465
0
    if (has_fallback_values)
9466
0
        return expand_unresolved_values(element, property_name, attr_contents, dest);
9467
9468
0
    if (attribute_type.equals_ignoring_ascii_case("string"_fly_string)) {
9469
        // If the <attr-type> argument is string, defaults to the empty string if omitted
9470
0
        dest.empend(Token::create_string({}));
9471
0
        return true;
9472
0
    }
9473
9474
    // 3. Otherwise, the property containing the attr() function is invalid at computed-value time.
9475
0
    return false;
9476
0
}
9477
9478
// https://drafts.csswg.org/css-fonts/#typedef-opentype-tag
9479
RefPtr<StringStyleValue> Parser::parse_opentype_tag_value(TokenStream<ComponentValue>& tokens)
9480
0
{
9481
    // <opentype-tag> = <string>
9482
    // The <opentype-tag> is a case-sensitive OpenType feature tag.
9483
    // As specified in the OpenType specification [OPENTYPE], feature tags contain four ASCII characters.
9484
    // Tag strings longer or shorter than four characters, or containing characters outside the U+20–7E codepoint range are invalid.
9485
9486
0
    auto transaction = tokens.begin_transaction();
9487
0
    auto string_value = parse_string_value(tokens);
9488
0
    if (string_value == nullptr)
9489
0
        return nullptr;
9490
9491
0
    auto string = string_value->string_value().bytes_as_string_view();
9492
0
    if (string.length() != 4)
9493
0
        return nullptr;
9494
0
    for (char c : string) {
9495
0
        if (c < 0x20 || c > 0x7E)
9496
0
            return nullptr;
9497
0
    }
9498
9499
0
    transaction.commit();
9500
0
    return string_value;
9501
0
}
9502
9503
}