Serenity Operating System
1/*
2 * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2020-2021, the SerenityOS developers.
4 * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
5 * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
6 * Copyright (c) 2022, MacDue <macdue@dueutil.tech>
7 *
8 * SPDX-License-Identifier: BSD-2-Clause
9 */
10
11#include <AK/CharacterTypes.h>
12#include <AK/Debug.h>
13#include <AK/GenericLexer.h>
14#include <AK/SourceLocation.h>
15#include <LibWeb/Bindings/MainThreadVM.h>
16#include <LibWeb/CSS/CSSFontFaceRule.h>
17#include <LibWeb/CSS/CSSImportRule.h>
18#include <LibWeb/CSS/CSSMediaRule.h>
19#include <LibWeb/CSS/CSSStyleDeclaration.h>
20#include <LibWeb/CSS/CSSStyleRule.h>
21#include <LibWeb/CSS/CSSStyleSheet.h>
22#include <LibWeb/CSS/CSSSupportsRule.h>
23#include <LibWeb/CSS/MediaList.h>
24#include <LibWeb/CSS/Parser/Block.h>
25#include <LibWeb/CSS/Parser/ComponentValue.h>
26#include <LibWeb/CSS/Parser/DeclarationOrAtRule.h>
27#include <LibWeb/CSS/Parser/Function.h>
28#include <LibWeb/CSS/Parser/Parser.h>
29#include <LibWeb/CSS/Parser/Rule.h>
30#include <LibWeb/CSS/Selector.h>
31#include <LibWeb/CSS/StyleValue.h>
32#include <LibWeb/DOM/Document.h>
33#include <LibWeb/Dump.h>
34#include <LibWeb/Infra/Strings.h>
35
36static void log_parse_error(SourceLocation const& location = SourceLocation::current())
37{
38 dbgln_if(CSS_PARSER_DEBUG, "Parse error (CSS) {}", location);
39}
40
41namespace Web::CSS::Parser {
42
43ParsingContext::ParsingContext(JS::Realm& realm)
44 : m_realm(realm)
45{
46}
47
48ParsingContext::ParsingContext(DOM::Document const& document, AK::URL url)
49 : m_realm(const_cast<JS::Realm&>(document.realm()))
50 , m_document(&document)
51 , m_url(move(url))
52{
53}
54
55ParsingContext::ParsingContext(DOM::Document const& document)
56 : m_realm(const_cast<JS::Realm&>(document.realm()))
57 , m_document(&document)
58 , m_url(document.url())
59{
60}
61
62ParsingContext::ParsingContext(DOM::ParentNode& parent_node)
63 : m_realm(parent_node.realm())
64 , m_document(&parent_node.document())
65 , m_url(parent_node.document().url())
66{
67}
68
69bool ParsingContext::in_quirks_mode() const
70{
71 return m_document ? m_document->in_quirks_mode() : false;
72}
73
74// https://www.w3.org/TR/css-values-4/#relative-urls
75AK::URL ParsingContext::complete_url(StringView relative_url) const
76{
77 return m_url.complete_url(relative_url);
78}
79
80ErrorOr<Parser> Parser::create(ParsingContext const& context, StringView input, StringView encoding)
81{
82 auto tokens = TRY(Tokenizer::tokenize(input, encoding));
83 return Parser { context, move(tokens) };
84}
85
86Parser::Parser(ParsingContext const& context, Vector<Token> tokens)
87 : m_context(context)
88 , m_tokens(move(tokens))
89 , m_token_stream(m_tokens)
90{
91}
92
93Parser::Parser(Parser&& other)
94 : m_context(other.m_context)
95 , m_tokens(move(other.m_tokens))
96 , m_token_stream(m_tokens)
97{
98 // Moving the TokenStream directly from `other` would break it, because TokenStream holds
99 // a reference to the Vector<Token>, so it would be pointing at the old Parser's tokens.
100 // So instead, we create a new TokenStream from this Parser's tokens, and then tell it to
101 // copy the other TokenStream's state. This is quite hacky.
102 m_token_stream.copy_state({}, other.m_token_stream);
103}
104
105// 5.3.3. Parse a stylesheet
106// https://www.w3.org/TR/css-syntax-3/#parse-stylesheet
107template<typename T>
108Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream<T>& tokens, Optional<AK::URL> location)
109{
110 // To parse a stylesheet from an input given an optional url location:
111
112 // 1. If input is a byte stream for stylesheet, decode bytes from input, and set input to the result.
113 // 2. Normalize input, and set input to the result.
114 // NOTE: These are done automatically when creating the Parser.
115
116 // 3. Create a new stylesheet, with its location set to location (or null, if location was not passed).
117 ParsedStyleSheet style_sheet;
118 style_sheet.location = move(location);
119
120 // 4. Consume a list of rules from input, with the top-level flag set, and set the stylesheet’s value to the result.
121 style_sheet.rules = consume_a_list_of_rules(tokens, TopLevel::Yes);
122
123 // 5. Return the stylesheet.
124 return style_sheet;
125}
126
127// https://www.w3.org/TR/css-syntax-3/#parse-a-css-stylesheet
128CSSStyleSheet* Parser::parse_as_css_stylesheet(Optional<AK::URL> location)
129{
130 // To parse a CSS stylesheet, first parse a stylesheet.
131 auto style_sheet = parse_a_stylesheet(m_token_stream, {});
132
133 // Interpret all of the resulting top-level qualified rules as style rules, defined below.
134 JS::MarkedVector<CSSRule*> rules(m_context.realm().heap());
135 for (auto& raw_rule : style_sheet.rules) {
136 auto* rule = convert_to_rule(raw_rule);
137 // 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. Discard that rule.
138 if (rule)
139 rules.append(rule);
140 }
141
142 auto rule_list = CSSRuleList::create(m_context.realm(), rules).release_value_but_fixme_should_propagate_errors();
143 auto media_list = MediaList::create(m_context.realm(), {}).release_value_but_fixme_should_propagate_errors();
144 return CSSStyleSheet::create(m_context.realm(), rule_list, media_list, move(location)).release_value_but_fixme_should_propagate_errors();
145}
146
147Optional<SelectorList> Parser::parse_as_selector(SelectorParsingMode parsing_mode)
148{
149 auto selector_list = parse_a_selector_list(m_token_stream, SelectorType::Standalone, parsing_mode);
150 if (!selector_list.is_error())
151 return selector_list.release_value();
152
153 return {};
154}
155
156Optional<SelectorList> Parser::parse_as_relative_selector(SelectorParsingMode parsing_mode)
157{
158 auto selector_list = parse_a_selector_list(m_token_stream, SelectorType::Relative, parsing_mode);
159 if (!selector_list.is_error())
160 return selector_list.release_value();
161
162 return {};
163}
164
165template<typename T>
166Parser::ParseErrorOr<SelectorList> Parser::parse_a_selector_list(TokenStream<T>& tokens, SelectorType mode, SelectorParsingMode parsing_mode)
167{
168 auto comma_separated_lists = parse_a_comma_separated_list_of_component_values(tokens);
169
170 Vector<NonnullRefPtr<Selector>> selectors;
171 for (auto& selector_parts : comma_separated_lists) {
172 auto stream = TokenStream(selector_parts);
173 auto selector = parse_complex_selector(stream, mode);
174 if (selector.is_error()) {
175 if (parsing_mode == SelectorParsingMode::Forgiving)
176 continue;
177 return selector.error();
178 }
179 selectors.append(selector.release_value());
180 }
181
182 if (selectors.is_empty() && parsing_mode != SelectorParsingMode::Forgiving)
183 return ParseError::SyntaxError;
184
185 return selectors;
186}
187
188Parser::ParseErrorOr<NonnullRefPtr<Selector>> Parser::parse_complex_selector(TokenStream<ComponentValue>& tokens, SelectorType mode)
189{
190 Vector<Selector::CompoundSelector> compound_selectors;
191
192 auto first_selector = TRY(parse_compound_selector(tokens));
193 if (!first_selector.has_value())
194 return ParseError::SyntaxError;
195
196 if (mode == SelectorType::Standalone) {
197 if (first_selector->combinator != Selector::Combinator::Descendant)
198 return ParseError::SyntaxError;
199 first_selector->combinator = Selector::Combinator::None;
200 }
201 compound_selectors.append(first_selector.release_value());
202
203 while (tokens.has_next_token()) {
204 auto compound_selector = TRY(parse_compound_selector(tokens));
205 if (!compound_selector.has_value())
206 break;
207 compound_selectors.append(compound_selector.release_value());
208 }
209
210 if (compound_selectors.is_empty())
211 return ParseError::SyntaxError;
212
213 return Selector::create(move(compound_selectors));
214}
215
216Parser::ParseErrorOr<Optional<Selector::CompoundSelector>> Parser::parse_compound_selector(TokenStream<ComponentValue>& tokens)
217{
218 tokens.skip_whitespace();
219
220 auto combinator = parse_selector_combinator(tokens).value_or(Selector::Combinator::Descendant);
221
222 tokens.skip_whitespace();
223
224 Vector<Selector::SimpleSelector> simple_selectors;
225
226 while (tokens.has_next_token()) {
227 auto component = TRY(parse_simple_selector(tokens));
228 if (!component.has_value())
229 break;
230 simple_selectors.append(component.release_value());
231 }
232
233 if (simple_selectors.is_empty())
234 return Optional<Selector::CompoundSelector> {};
235
236 return Selector::CompoundSelector { combinator, move(simple_selectors) };
237}
238
239Optional<Selector::Combinator> Parser::parse_selector_combinator(TokenStream<ComponentValue>& tokens)
240{
241 auto const& current_value = tokens.next_token();
242 if (current_value.is(Token::Type::Delim)) {
243 switch (current_value.token().delim()) {
244 case '>':
245 return Selector::Combinator::ImmediateChild;
246 case '+':
247 return Selector::Combinator::NextSibling;
248 case '~':
249 return Selector::Combinator::SubsequentSibling;
250 case '|': {
251 auto const& next = tokens.peek_token();
252 if (next.is(Token::Type::EndOfFile))
253 return {};
254
255 if (next.is(Token::Type::Delim) && next.token().delim() == '|') {
256 tokens.next_token();
257 return Selector::Combinator::Column;
258 }
259 }
260 }
261 }
262
263 tokens.reconsume_current_input_token();
264 return {};
265}
266
267Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_attribute_simple_selector(ComponentValue const& first_value)
268{
269 auto attribute_tokens = TokenStream { first_value.block().values() };
270
271 attribute_tokens.skip_whitespace();
272
273 if (!attribute_tokens.has_next_token()) {
274 dbgln_if(CSS_PARSER_DEBUG, "CSS attribute selector is empty!");
275 return ParseError::SyntaxError;
276 }
277
278 // FIXME: Handle namespace prefix for attribute name.
279 auto const& attribute_part = attribute_tokens.next_token();
280 if (!attribute_part.is(Token::Type::Ident)) {
281 dbgln_if(CSS_PARSER_DEBUG, "Expected ident for attribute name, got: '{}'", attribute_part.to_debug_string());
282 return ParseError::SyntaxError;
283 }
284
285 Selector::SimpleSelector simple_selector {
286 .type = Selector::SimpleSelector::Type::Attribute,
287 .value = Selector::SimpleSelector::Attribute {
288 .match_type = Selector::SimpleSelector::Attribute::MatchType::HasAttribute,
289 // FIXME: Case-sensitivity is defined by the document language.
290 // HTML is insensitive with attribute names, and our code generally assumes
291 // they are converted to lowercase, so we do that here too. If we want to be
292 // correct with XML later, we'll need to keep the original case and then do
293 // a case-insensitive compare later.
294 .name = FlyString::from_deprecated_fly_string(attribute_part.token().ident().to_lowercase_string()).release_value_but_fixme_should_propagate_errors(),
295 .case_type = Selector::SimpleSelector::Attribute::CaseType::DefaultMatch,
296 }
297 };
298
299 attribute_tokens.skip_whitespace();
300 if (!attribute_tokens.has_next_token())
301 return simple_selector;
302
303 auto const& delim_part = attribute_tokens.next_token();
304 if (!delim_part.is(Token::Type::Delim)) {
305 dbgln_if(CSS_PARSER_DEBUG, "Expected a delim for attribute comparison, got: '{}'", delim_part.to_debug_string());
306 return ParseError::SyntaxError;
307 }
308
309 if (delim_part.token().delim() == '=') {
310 simple_selector.attribute().match_type = Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch;
311 } else {
312 if (!attribute_tokens.has_next_token()) {
313 dbgln_if(CSS_PARSER_DEBUG, "Attribute selector ended part way through a match type.");
314 return ParseError::SyntaxError;
315 }
316
317 auto const& delim_second_part = attribute_tokens.next_token();
318 if (!(delim_second_part.is(Token::Type::Delim) && delim_second_part.token().delim() == '=')) {
319 dbgln_if(CSS_PARSER_DEBUG, "Expected a double delim for attribute comparison, got: '{}{}'", delim_part.to_debug_string(), delim_second_part.to_debug_string());
320 return ParseError::SyntaxError;
321 }
322 switch (delim_part.token().delim()) {
323 case '~':
324 simple_selector.attribute().match_type = Selector::SimpleSelector::Attribute::MatchType::ContainsWord;
325 break;
326 case '*':
327 simple_selector.attribute().match_type = Selector::SimpleSelector::Attribute::MatchType::ContainsString;
328 break;
329 case '|':
330 simple_selector.attribute().match_type = Selector::SimpleSelector::Attribute::MatchType::StartsWithSegment;
331 break;
332 case '^':
333 simple_selector.attribute().match_type = Selector::SimpleSelector::Attribute::MatchType::StartsWithString;
334 break;
335 case '$':
336 simple_selector.attribute().match_type = Selector::SimpleSelector::Attribute::MatchType::EndsWithString;
337 break;
338 default:
339 attribute_tokens.reconsume_current_input_token();
340 }
341 }
342
343 attribute_tokens.skip_whitespace();
344 if (!attribute_tokens.has_next_token()) {
345 dbgln_if(CSS_PARSER_DEBUG, "Attribute selector ended without a value to match.");
346 return ParseError::SyntaxError;
347 }
348
349 auto const& value_part = attribute_tokens.next_token();
350 if (!value_part.is(Token::Type::Ident) && !value_part.is(Token::Type::String)) {
351 dbgln_if(CSS_PARSER_DEBUG, "Expected a string or ident for the value to match attribute against, got: '{}'", value_part.to_debug_string());
352 return ParseError::SyntaxError;
353 }
354 auto value_string_view = value_part.token().is(Token::Type::Ident) ? value_part.token().ident() : value_part.token().string();
355 simple_selector.attribute().value = String::from_utf8(value_string_view).release_value_but_fixme_should_propagate_errors();
356
357 attribute_tokens.skip_whitespace();
358 // Handle case-sensitivity suffixes. https://www.w3.org/TR/selectors-4/#attribute-case
359 if (attribute_tokens.has_next_token()) {
360 auto const& case_sensitivity_part = attribute_tokens.next_token();
361 if (case_sensitivity_part.is(Token::Type::Ident)) {
362 auto case_sensitivity = case_sensitivity_part.token().ident();
363 if (case_sensitivity.equals_ignoring_ascii_case("i"sv)) {
364 simple_selector.attribute().case_type = Selector::SimpleSelector::Attribute::CaseType::CaseInsensitiveMatch;
365 } else if (case_sensitivity.equals_ignoring_ascii_case("s"sv)) {
366 simple_selector.attribute().case_type = Selector::SimpleSelector::Attribute::CaseType::CaseSensitiveMatch;
367 } else {
368 dbgln_if(CSS_PARSER_DEBUG, "Expected a \"i\" or \"s\" attribute selector case sensitivity identifier, got: '{}'", case_sensitivity_part.to_debug_string());
369 return ParseError::SyntaxError;
370 }
371 } else {
372 dbgln_if(CSS_PARSER_DEBUG, "Expected an attribute selector case sensitivity identifier, got: '{}'", case_sensitivity_part.to_debug_string());
373 return ParseError::SyntaxError;
374 }
375 }
376
377 if (attribute_tokens.has_next_token()) {
378 dbgln_if(CSS_PARSER_DEBUG, "Was not expecting anything else inside attribute selector.");
379 return ParseError::SyntaxError;
380 }
381
382 return simple_selector;
383}
384
385Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selector(TokenStream<ComponentValue>& tokens)
386{
387 auto peek_token_ends_selector = [&]() -> bool {
388 auto const& value = tokens.peek_token();
389 return (value.is(Token::Type::EndOfFile) || value.is(Token::Type::Whitespace) || value.is(Token::Type::Comma));
390 };
391
392 if (peek_token_ends_selector())
393 return ParseError::SyntaxError;
394
395 bool is_pseudo = false;
396 if (tokens.peek_token().is(Token::Type::Colon)) {
397 is_pseudo = true;
398 tokens.next_token();
399 if (peek_token_ends_selector())
400 return ParseError::SyntaxError;
401 }
402
403 if (is_pseudo) {
404 auto const& name_token = tokens.next_token();
405 if (!name_token.is(Token::Type::Ident)) {
406 dbgln_if(CSS_PARSER_DEBUG, "Expected an ident for pseudo-element, got: '{}'", name_token.to_debug_string());
407 return ParseError::SyntaxError;
408 }
409
410 auto pseudo_name = name_token.token().ident();
411 auto pseudo_element = pseudo_element_from_string(pseudo_name);
412
413 // Note: We allow the "ignored" -webkit prefix here for -webkit-progress-bar/-webkit-progress-bar
414 if (!pseudo_element.has_value() && has_ignored_vendor_prefix(pseudo_name))
415 return ParseError::IncludesIgnoredVendorPrefix;
416
417 if (!pseudo_element.has_value()) {
418 dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-element: '::{}'", pseudo_name);
419 return ParseError::SyntaxError;
420 }
421
422 return Selector::SimpleSelector {
423 .type = Selector::SimpleSelector::Type::PseudoElement,
424 .value = pseudo_element.value()
425 };
426 }
427
428 if (peek_token_ends_selector())
429 return ParseError::SyntaxError;
430
431 auto const& pseudo_class_token = tokens.next_token();
432
433 if (pseudo_class_token.is(Token::Type::Ident)) {
434 auto pseudo_name = pseudo_class_token.token().ident();
435 if (has_ignored_vendor_prefix(pseudo_name))
436 return ParseError::IncludesIgnoredVendorPrefix;
437
438 auto make_pseudo_class_selector = [](auto pseudo_class) {
439 return Selector::SimpleSelector {
440 .type = Selector::SimpleSelector::Type::PseudoClass,
441 .value = Selector::SimpleSelector::PseudoClass {
442 .type = pseudo_class }
443 };
444 };
445
446 if (pseudo_name.equals_ignoring_ascii_case("active"sv))
447 return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Active);
448 if (pseudo_name.equals_ignoring_ascii_case("checked"sv))
449 return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Checked);
450 if (pseudo_name.equals_ignoring_ascii_case("disabled"sv))
451 return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Disabled);
452 if (pseudo_name.equals_ignoring_ascii_case("empty"sv))
453 return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Empty);
454 if (pseudo_name.equals_ignoring_ascii_case("enabled"sv))
455 return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Enabled);
456 if (pseudo_name.equals_ignoring_ascii_case("first-child"sv))
457 return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::FirstChild);
458 if (pseudo_name.equals_ignoring_ascii_case("first-of-type"sv))
459 return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::FirstOfType);
460 if (pseudo_name.equals_ignoring_ascii_case("focus"sv))
461 return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Focus);
462 if (pseudo_name.equals_ignoring_ascii_case("focus-within"sv))
463 return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::FocusWithin);
464 if (pseudo_name.equals_ignoring_ascii_case("hover"sv))
465 return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Hover);
466 if (pseudo_name.equals_ignoring_ascii_case("last-child"sv))
467 return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::LastChild);
468 if (pseudo_name.equals_ignoring_ascii_case("last-of-type"sv))
469 return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::LastOfType);
470 if (pseudo_name.equals_ignoring_ascii_case("link"sv))
471 return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Link);
472 if (pseudo_name.equals_ignoring_ascii_case("only-child"sv))
473 return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::OnlyChild);
474 if (pseudo_name.equals_ignoring_ascii_case("only-of-type"sv))
475 return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::OnlyOfType);
476 if (pseudo_name.equals_ignoring_ascii_case("root"sv))
477 return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Root);
478 if (pseudo_name.equals_ignoring_ascii_case("visited"sv))
479 return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Visited);
480
481 // Single-colon syntax allowed for ::after, ::before, ::first-letter and ::first-line for compatibility.
482 // https://www.w3.org/TR/selectors/#pseudo-element-syntax
483 if (auto pseudo_element = pseudo_element_from_string(pseudo_name); pseudo_element.has_value()) {
484 switch (pseudo_element.value()) {
485 case Selector::PseudoElement::After:
486 case Selector::PseudoElement::Before:
487 case Selector::PseudoElement::FirstLetter:
488 case Selector::PseudoElement::FirstLine:
489 return Selector::SimpleSelector {
490 .type = Selector::SimpleSelector::Type::PseudoElement,
491 .value = pseudo_element.value()
492 };
493 default:
494 break;
495 }
496 }
497
498 dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class: ':{}'", pseudo_name);
499 return ParseError::SyntaxError;
500 }
501
502 if (pseudo_class_token.is_function()) {
503 auto parse_nth_child_selector = [this](auto pseudo_class, Vector<ComponentValue> const& function_values, bool allow_of = false) -> ParseErrorOr<Selector::SimpleSelector> {
504 auto tokens = TokenStream<ComponentValue>(function_values);
505 auto nth_child_pattern = parse_a_n_plus_b_pattern(tokens);
506 if (!nth_child_pattern.has_value()) {
507 dbgln_if(CSS_PARSER_DEBUG, "!!! Invalid An+B format for {}", pseudo_class_name(pseudo_class));
508 return ParseError::SyntaxError;
509 }
510
511 tokens.skip_whitespace();
512 if (!tokens.has_next_token()) {
513 return Selector::SimpleSelector {
514 .type = Selector::SimpleSelector::Type::PseudoClass,
515 .value = Selector::SimpleSelector::PseudoClass {
516 .type = pseudo_class,
517 .nth_child_pattern = nth_child_pattern.release_value() }
518 };
519 }
520
521 if (!allow_of)
522 return ParseError::SyntaxError;
523
524 // Parse the `of <selector-list>` syntax
525 auto const& maybe_of = tokens.next_token();
526 if (!(maybe_of.is(Token::Type::Ident) && maybe_of.token().ident().equals_ignoring_ascii_case("of"sv)))
527 return ParseError::SyntaxError;
528
529 tokens.skip_whitespace();
530 auto selector_list = TRY(parse_a_selector_list(tokens, SelectorType::Standalone));
531
532 tokens.skip_whitespace();
533 if (tokens.has_next_token())
534 return ParseError::SyntaxError;
535
536 return Selector::SimpleSelector {
537 .type = Selector::SimpleSelector::Type::PseudoClass,
538 .value = Selector::SimpleSelector::PseudoClass {
539 .type = pseudo_class,
540 .nth_child_pattern = nth_child_pattern.release_value(),
541 .argument_selector_list = move(selector_list) }
542 };
543 };
544
545 auto const& pseudo_function = pseudo_class_token.function();
546 if (pseudo_function.name().equals_ignoring_ascii_case("is"sv)
547 || pseudo_function.name().equals_ignoring_ascii_case("where"sv)) {
548 auto function_token_stream = TokenStream(pseudo_function.values());
549 // NOTE: Because it's forgiving, even complete garbage will parse OK as an empty selector-list.
550 auto argument_selector_list = MUST(parse_a_selector_list(function_token_stream, SelectorType::Standalone, SelectorParsingMode::Forgiving));
551
552 return Selector::SimpleSelector {
553 .type = Selector::SimpleSelector::Type::PseudoClass,
554 .value = Selector::SimpleSelector::PseudoClass {
555 .type = pseudo_function.name().equals_ignoring_ascii_case("is"sv)
556 ? Selector::SimpleSelector::PseudoClass::Type::Is
557 : Selector::SimpleSelector::PseudoClass::Type::Where,
558 .argument_selector_list = move(argument_selector_list) }
559 };
560 }
561 if (pseudo_function.name().equals_ignoring_ascii_case("not"sv)) {
562 auto function_token_stream = TokenStream(pseudo_function.values());
563 auto not_selector = TRY(parse_a_selector_list(function_token_stream, SelectorType::Standalone));
564
565 return Selector::SimpleSelector {
566 .type = Selector::SimpleSelector::Type::PseudoClass,
567 .value = Selector::SimpleSelector::PseudoClass {
568 .type = Selector::SimpleSelector::PseudoClass::Type::Not,
569 .argument_selector_list = move(not_selector) }
570 };
571 }
572 if (pseudo_function.name().equals_ignoring_ascii_case("lang"sv)) {
573 if (pseudo_function.values().is_empty()) {
574 dbgln_if(CSS_PARSER_DEBUG, "Empty :lang() selector");
575 return ParseError::SyntaxError;
576 }
577 // FIXME: Support multiple, comma-separated, language ranges.
578 Vector<FlyString> languages;
579 languages.append(pseudo_function.values().first().token().to_string().release_value_but_fixme_should_propagate_errors());
580 return Selector::SimpleSelector {
581 .type = Selector::SimpleSelector::Type::PseudoClass,
582 .value = Selector::SimpleSelector::PseudoClass {
583 .type = Selector::SimpleSelector::PseudoClass::Type::Lang,
584 .languages = move(languages) }
585 };
586 }
587 if (pseudo_function.name().equals_ignoring_ascii_case("nth-child"sv))
588 return parse_nth_child_selector(Selector::SimpleSelector::PseudoClass::Type::NthChild, pseudo_function.values(), true);
589 if (pseudo_function.name().equals_ignoring_ascii_case("nth-last-child"sv))
590 return parse_nth_child_selector(Selector::SimpleSelector::PseudoClass::Type::NthLastChild, pseudo_function.values(), true);
591 if (pseudo_function.name().equals_ignoring_ascii_case("nth-of-type"sv))
592 return parse_nth_child_selector(Selector::SimpleSelector::PseudoClass::Type::NthOfType, pseudo_function.values(), false);
593 if (pseudo_function.name().equals_ignoring_ascii_case("nth-last-of-type"sv))
594 return parse_nth_child_selector(Selector::SimpleSelector::PseudoClass::Type::NthLastOfType, pseudo_function.values(), false);
595
596 dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class function: ':{}'()", pseudo_function.name());
597 return ParseError::SyntaxError;
598 }
599 dbgln_if(CSS_PARSER_DEBUG, "Unexpected Block in pseudo-class name, expected a function or identifier. '{}'", pseudo_class_token.to_debug_string());
600 return ParseError::SyntaxError;
601}
602
603Parser::ParseErrorOr<Optional<Selector::SimpleSelector>> Parser::parse_simple_selector(TokenStream<ComponentValue>& tokens)
604{
605 auto peek_token_ends_selector = [&]() -> bool {
606 auto const& value = tokens.peek_token();
607 return (value.is(Token::Type::EndOfFile) || value.is(Token::Type::Whitespace) || value.is(Token::Type::Comma));
608 };
609
610 if (peek_token_ends_selector())
611 return Optional<Selector::SimpleSelector> {};
612
613 auto const& first_value = tokens.next_token();
614
615 if (first_value.is(Token::Type::Delim)) {
616 u32 delim = first_value.token().delim();
617 switch (delim) {
618 case '*':
619 return Selector::SimpleSelector {
620 .type = Selector::SimpleSelector::Type::Universal
621 };
622 case '.': {
623 if (peek_token_ends_selector())
624 return ParseError::SyntaxError;
625
626 auto const& class_name_value = tokens.next_token();
627 if (!class_name_value.is(Token::Type::Ident)) {
628 dbgln_if(CSS_PARSER_DEBUG, "Expected an ident after '.', got: {}", class_name_value.to_debug_string());
629 return ParseError::SyntaxError;
630 }
631 return Selector::SimpleSelector {
632 .type = Selector::SimpleSelector::Type::Class,
633 .value = Selector::SimpleSelector::Name { FlyString::from_utf8(class_name_value.token().ident()).release_value_but_fixme_should_propagate_errors() }
634 };
635 }
636 case '>':
637 case '+':
638 case '~':
639 case '|':
640 // Whitespace is not required between the compound-selector and a combinator.
641 // So, if we see a combinator, return that this compound-selector is done, instead of a syntax error.
642 tokens.reconsume_current_input_token();
643 return Optional<Selector::SimpleSelector> {};
644 default:
645 dbgln_if(CSS_PARSER_DEBUG, "!!! Invalid simple selector!");
646 return ParseError::SyntaxError;
647 }
648 }
649
650 if (first_value.is(Token::Type::Hash)) {
651 if (first_value.token().hash_type() != Token::HashType::Id) {
652 dbgln_if(CSS_PARSER_DEBUG, "Selector contains hash token that is not an id: {}", first_value.to_debug_string());
653 return ParseError::SyntaxError;
654 }
655 return Selector::SimpleSelector {
656 .type = Selector::SimpleSelector::Type::Id,
657 .value = Selector::SimpleSelector::Name { FlyString::from_utf8(first_value.token().hash_value()).release_value_but_fixme_should_propagate_errors() }
658 };
659 }
660 if (first_value.is(Token::Type::Ident)) {
661 return Selector::SimpleSelector {
662 .type = Selector::SimpleSelector::Type::TagName,
663 .value = Selector::SimpleSelector::Name { FlyString::from_utf8(first_value.token().ident()).release_value_but_fixme_should_propagate_errors() }
664 };
665 }
666 if (first_value.is_block() && first_value.block().is_square())
667 return TRY(parse_attribute_simple_selector(first_value));
668
669 if (first_value.is(Token::Type::Colon))
670 return TRY(parse_pseudo_simple_selector(tokens));
671
672 dbgln_if(CSS_PARSER_DEBUG, "!!! Invalid simple selector!");
673 return ParseError::SyntaxError;
674}
675
676Vector<NonnullRefPtr<MediaQuery>> Parser::parse_as_media_query_list()
677{
678 return parse_a_media_query_list(m_token_stream);
679}
680
681template<typename T>
682Vector<NonnullRefPtr<MediaQuery>> Parser::parse_a_media_query_list(TokenStream<T>& tokens)
683{
684 // https://www.w3.org/TR/mediaqueries-4/#mq-list
685
686 auto comma_separated_lists = parse_a_comma_separated_list_of_component_values(tokens);
687
688 AK::Vector<NonnullRefPtr<MediaQuery>> media_queries;
689 for (auto& media_query_parts : comma_separated_lists) {
690 auto stream = TokenStream(media_query_parts);
691 media_queries.append(parse_media_query(stream));
692 }
693
694 return media_queries;
695}
696
697RefPtr<MediaQuery> Parser::parse_as_media_query()
698{
699 // https://www.w3.org/TR/cssom-1/#parse-a-media-query
700 auto media_query_list = parse_as_media_query_list();
701 if (media_query_list.is_empty())
702 return MediaQuery::create_not_all();
703 if (media_query_list.size() == 1)
704 return media_query_list.first();
705 return nullptr;
706}
707
708// `<media-query>`, https://www.w3.org/TR/mediaqueries-4/#typedef-media-query
709NonnullRefPtr<MediaQuery> Parser::parse_media_query(TokenStream<ComponentValue>& tokens)
710{
711 // `<media-query> = <media-condition>
712 // | [ not | only ]? <media-type> [ and <media-condition-without-or> ]?`
713
714 // `[ not | only ]?`, Returns whether to negate the query
715 auto parse_initial_modifier = [](auto& tokens) -> Optional<bool> {
716 auto transaction = tokens.begin_transaction();
717 tokens.skip_whitespace();
718 auto& token = tokens.next_token();
719 if (!token.is(Token::Type::Ident))
720 return {};
721
722 auto ident = token.token().ident();
723 if (ident.equals_ignoring_ascii_case("not"sv)) {
724 transaction.commit();
725 return true;
726 }
727 if (ident.equals_ignoring_ascii_case("only"sv)) {
728 transaction.commit();
729 return false;
730 }
731 return {};
732 };
733
734 auto invalid_media_query = [&]() {
735 // "A media query that does not match the grammar in the previous section must be replaced by `not all`
736 // during parsing." - https://www.w3.org/TR/mediaqueries-5/#error-handling
737 if constexpr (CSS_PARSER_DEBUG) {
738 dbgln("Invalid media query:");
739 tokens.dump_all_tokens();
740 }
741 return MediaQuery::create_not_all();
742 };
743
744 auto media_query = MediaQuery::create();
745 tokens.skip_whitespace();
746
747 // `<media-condition>`
748 if (auto media_condition = parse_media_condition(tokens, MediaCondition::AllowOr::Yes)) {
749 tokens.skip_whitespace();
750 if (tokens.has_next_token())
751 return invalid_media_query();
752 media_query->m_media_condition = move(media_condition);
753 return media_query;
754 }
755
756 // `[ not | only ]?`
757 if (auto modifier = parse_initial_modifier(tokens); modifier.has_value()) {
758 media_query->m_negated = modifier.value();
759 tokens.skip_whitespace();
760 }
761
762 // `<media-type>`
763 if (auto media_type = parse_media_type(tokens); media_type.has_value()) {
764 media_query->m_media_type = media_type.value();
765 tokens.skip_whitespace();
766 } else {
767 return invalid_media_query();
768 }
769
770 if (!tokens.has_next_token())
771 return media_query;
772
773 // `[ and <media-condition-without-or> ]?`
774 if (auto maybe_and = tokens.next_token(); maybe_and.is(Token::Type::Ident) && maybe_and.token().ident().equals_ignoring_ascii_case("and"sv)) {
775 if (auto media_condition = parse_media_condition(tokens, MediaCondition::AllowOr::No)) {
776 tokens.skip_whitespace();
777 if (tokens.has_next_token())
778 return invalid_media_query();
779 media_query->m_media_condition = move(media_condition);
780 return media_query;
781 }
782 return invalid_media_query();
783 }
784
785 return invalid_media_query();
786}
787
788// `<media-condition>`, https://www.w3.org/TR/mediaqueries-4/#typedef-media-condition
789// `<media-condition-widthout-or>`, https://www.w3.org/TR/mediaqueries-4/#typedef-media-condition-without-or
790// (We distinguish between these two with the `allow_or` parameter.)
791OwnPtr<MediaCondition> Parser::parse_media_condition(TokenStream<ComponentValue>& tokens, MediaCondition::AllowOr allow_or)
792{
793 // `<media-not> | <media-in-parens> [ <media-and>* | <media-or>* ]`
794 auto transaction = tokens.begin_transaction();
795 tokens.skip_whitespace();
796
797 // `<media-not> = not <media-in-parens>`
798 auto parse_media_not = [&](auto& tokens) -> OwnPtr<MediaCondition> {
799 auto local_transaction = tokens.begin_transaction();
800 tokens.skip_whitespace();
801
802 auto& first_token = tokens.next_token();
803 if (first_token.is(Token::Type::Ident) && first_token.token().ident().equals_ignoring_ascii_case("not"sv)) {
804 if (auto child_condition = parse_media_condition(tokens, MediaCondition::AllowOr::Yes)) {
805 local_transaction.commit();
806 return MediaCondition::from_not(child_condition.release_nonnull());
807 }
808 }
809
810 return {};
811 };
812
813 auto parse_media_with_combinator = [&](auto& tokens, StringView combinator) -> OwnPtr<MediaCondition> {
814 auto local_transaction = tokens.begin_transaction();
815 tokens.skip_whitespace();
816
817 auto& first = tokens.next_token();
818 if (first.is(Token::Type::Ident) && first.token().ident().equals_ignoring_ascii_case(combinator)) {
819 tokens.skip_whitespace();
820 if (auto media_in_parens = parse_media_in_parens(tokens)) {
821 local_transaction.commit();
822 return media_in_parens;
823 }
824 }
825
826 return {};
827 };
828
829 // `<media-and> = and <media-in-parens>`
830 auto parse_media_and = [&](auto& tokens) { return parse_media_with_combinator(tokens, "and"sv); };
831 // `<media-or> = or <media-in-parens>`
832 auto parse_media_or = [&](auto& tokens) { return parse_media_with_combinator(tokens, "or"sv); };
833
834 // `<media-not>`
835 if (auto maybe_media_not = parse_media_not(tokens)) {
836 transaction.commit();
837 return maybe_media_not.release_nonnull();
838 }
839
840 // `<media-in-parens> [ <media-and>* | <media-or>* ]`
841 if (auto maybe_media_in_parens = parse_media_in_parens(tokens)) {
842 tokens.skip_whitespace();
843 // Only `<media-in-parens>`
844 if (!tokens.has_next_token()) {
845 transaction.commit();
846 return maybe_media_in_parens.release_nonnull();
847 }
848
849 Vector<NonnullOwnPtr<MediaCondition>> child_conditions;
850 child_conditions.append(maybe_media_in_parens.release_nonnull());
851
852 // `<media-and>*`
853 if (auto media_and = parse_media_and(tokens)) {
854 child_conditions.append(media_and.release_nonnull());
855
856 tokens.skip_whitespace();
857 while (tokens.has_next_token()) {
858 if (auto next_media_and = parse_media_and(tokens)) {
859 child_conditions.append(next_media_and.release_nonnull());
860 tokens.skip_whitespace();
861 continue;
862 }
863 // We failed - invalid syntax!
864 return {};
865 }
866
867 transaction.commit();
868 return MediaCondition::from_and_list(move(child_conditions));
869 }
870
871 // `<media-or>*`
872 if (allow_or == MediaCondition::AllowOr::Yes) {
873 if (auto media_or = parse_media_or(tokens)) {
874 child_conditions.append(media_or.release_nonnull());
875
876 tokens.skip_whitespace();
877 while (tokens.has_next_token()) {
878 if (auto next_media_or = parse_media_or(tokens)) {
879 child_conditions.append(next_media_or.release_nonnull());
880 tokens.skip_whitespace();
881 continue;
882 }
883 // We failed - invalid syntax!
884 return {};
885 }
886
887 transaction.commit();
888 return MediaCondition::from_or_list(move(child_conditions));
889 }
890 }
891 }
892
893 return {};
894}
895
896// `<media-feature>`, https://www.w3.org/TR/mediaqueries-4/#typedef-media-feature
897Optional<MediaFeature> Parser::parse_media_feature(TokenStream<ComponentValue>& tokens)
898{
899 // `[ <mf-plain> | <mf-boolean> | <mf-range> ]`
900 tokens.skip_whitespace();
901
902 // `<mf-name> = <ident>`
903 struct MediaFeatureName {
904 enum Type {
905 Normal,
906 Min,
907 Max
908 } type;
909 MediaFeatureID id;
910 };
911 auto parse_mf_name = [](auto& tokens, bool allow_min_max_prefix) -> Optional<MediaFeatureName> {
912 auto transaction = tokens.begin_transaction();
913 auto& token = tokens.next_token();
914 if (token.is(Token::Type::Ident)) {
915 auto name = token.token().ident();
916 if (auto id = media_feature_id_from_string(name); id.has_value()) {
917 transaction.commit();
918 return MediaFeatureName { MediaFeatureName::Type::Normal, id.value() };
919 }
920
921 if (allow_min_max_prefix && (name.starts_with("min-"sv, CaseSensitivity::CaseInsensitive) || name.starts_with("max-"sv, CaseSensitivity::CaseInsensitive))) {
922 auto adjusted_name = name.substring_view(4);
923 if (auto id = media_feature_id_from_string(adjusted_name); id.has_value() && media_feature_type_is_range(id.value())) {
924 transaction.commit();
925 return MediaFeatureName {
926 name.starts_with("min-"sv, CaseSensitivity::CaseInsensitive) ? MediaFeatureName::Type::Min : MediaFeatureName::Type::Max,
927 id.value()
928 };
929 }
930 }
931 }
932 return {};
933 };
934
935 // `<mf-boolean> = <mf-name>`
936 auto parse_mf_boolean = [&](auto& tokens) -> Optional<MediaFeature> {
937 auto transaction = tokens.begin_transaction();
938 tokens.skip_whitespace();
939
940 if (auto maybe_name = parse_mf_name(tokens, false); maybe_name.has_value()) {
941 tokens.skip_whitespace();
942 if (!tokens.has_next_token()) {
943 transaction.commit();
944 return MediaFeature::boolean(maybe_name->id);
945 }
946 }
947
948 return {};
949 };
950
951 // `<mf-plain> = <mf-name> : <mf-value>`
952 auto parse_mf_plain = [&](auto& tokens) -> Optional<MediaFeature> {
953 auto transaction = tokens.begin_transaction();
954 tokens.skip_whitespace();
955
956 if (auto maybe_name = parse_mf_name(tokens, true); maybe_name.has_value()) {
957 tokens.skip_whitespace();
958 if (tokens.next_token().is(Token::Type::Colon)) {
959 tokens.skip_whitespace();
960 if (auto maybe_value = parse_media_feature_value(maybe_name->id, tokens); maybe_value.has_value()) {
961 tokens.skip_whitespace();
962 if (!tokens.has_next_token()) {
963 transaction.commit();
964 switch (maybe_name->type) {
965 case MediaFeatureName::Type::Normal:
966 return MediaFeature::plain(maybe_name->id, maybe_value.release_value());
967 case MediaFeatureName::Type::Min:
968 return MediaFeature::min(maybe_name->id, maybe_value.release_value());
969 case MediaFeatureName::Type::Max:
970 return MediaFeature::max(maybe_name->id, maybe_value.release_value());
971 }
972 VERIFY_NOT_REACHED();
973 }
974 }
975 }
976 }
977 return {};
978 };
979
980 // `<mf-lt> = '<' '='?
981 // <mf-gt> = '>' '='?
982 // <mf-eq> = '='
983 // <mf-comparison> = <mf-lt> | <mf-gt> | <mf-eq>`
984 auto parse_comparison = [](auto& tokens) -> Optional<MediaFeature::Comparison> {
985 auto transaction = tokens.begin_transaction();
986 tokens.skip_whitespace();
987
988 auto& first = tokens.next_token();
989 if (first.is(Token::Type::Delim)) {
990 auto first_delim = first.token().delim();
991 if (first_delim == '=') {
992 transaction.commit();
993 return MediaFeature::Comparison::Equal;
994 }
995 if (first_delim == '<') {
996 auto& second = tokens.peek_token();
997 if (second.is(Token::Type::Delim) && second.token().delim() == '=') {
998 tokens.next_token();
999 transaction.commit();
1000 return MediaFeature::Comparison::LessThanOrEqual;
1001 }
1002 transaction.commit();
1003 return MediaFeature::Comparison::LessThan;
1004 }
1005 if (first_delim == '>') {
1006 auto& second = tokens.peek_token();
1007 if (second.is(Token::Type::Delim) && second.token().delim() == '=') {
1008 tokens.next_token();
1009 transaction.commit();
1010 return MediaFeature::Comparison::GreaterThanOrEqual;
1011 }
1012 transaction.commit();
1013 return MediaFeature::Comparison::GreaterThan;
1014 }
1015 }
1016
1017 return {};
1018 };
1019
1020 auto flip = [](MediaFeature::Comparison comparison) {
1021 switch (comparison) {
1022 case MediaFeature::Comparison::Equal:
1023 return MediaFeature::Comparison::Equal;
1024 case MediaFeature::Comparison::LessThan:
1025 return MediaFeature::Comparison::GreaterThan;
1026 case MediaFeature::Comparison::LessThanOrEqual:
1027 return MediaFeature::Comparison::GreaterThanOrEqual;
1028 case MediaFeature::Comparison::GreaterThan:
1029 return MediaFeature::Comparison::LessThan;
1030 case MediaFeature::Comparison::GreaterThanOrEqual:
1031 return MediaFeature::Comparison::LessThanOrEqual;
1032 }
1033 VERIFY_NOT_REACHED();
1034 };
1035
1036 auto comparisons_match = [](MediaFeature::Comparison a, MediaFeature::Comparison b) -> bool {
1037 switch (a) {
1038 case MediaFeature::Comparison::Equal:
1039 return b == MediaFeature::Comparison::Equal;
1040 case MediaFeature::Comparison::LessThan:
1041 case MediaFeature::Comparison::LessThanOrEqual:
1042 return b == MediaFeature::Comparison::LessThan || b == MediaFeature::Comparison::LessThanOrEqual;
1043 case MediaFeature::Comparison::GreaterThan:
1044 case MediaFeature::Comparison::GreaterThanOrEqual:
1045 return b == MediaFeature::Comparison::GreaterThan || b == MediaFeature::Comparison::GreaterThanOrEqual;
1046 }
1047 VERIFY_NOT_REACHED();
1048 };
1049
1050 // `<mf-range> = <mf-name> <mf-comparison> <mf-value>
1051 // | <mf-value> <mf-comparison> <mf-name>
1052 // | <mf-value> <mf-lt> <mf-name> <mf-lt> <mf-value>
1053 // | <mf-value> <mf-gt> <mf-name> <mf-gt> <mf-value>`
1054 auto parse_mf_range = [&](auto& tokens) -> Optional<MediaFeature> {
1055 auto transaction = tokens.begin_transaction();
1056 tokens.skip_whitespace();
1057
1058 // `<mf-name> <mf-comparison> <mf-value>`
1059 // NOTE: We have to check for <mf-name> first, since all <mf-name>s will also parse as <mf-value>.
1060 if (auto maybe_name = parse_mf_name(tokens, false); maybe_name.has_value() && media_feature_type_is_range(maybe_name->id)) {
1061 tokens.skip_whitespace();
1062 if (auto maybe_comparison = parse_comparison(tokens); maybe_comparison.has_value()) {
1063 tokens.skip_whitespace();
1064 if (auto maybe_value = parse_media_feature_value(maybe_name->id, tokens); maybe_value.has_value()) {
1065 tokens.skip_whitespace();
1066 if (!tokens.has_next_token() && !maybe_value->is_ident()) {
1067 transaction.commit();
1068 return MediaFeature::half_range(maybe_value.release_value(), flip(maybe_comparison.release_value()), maybe_name->id);
1069 }
1070 }
1071 }
1072 }
1073
1074 // `<mf-value> <mf-comparison> <mf-name>
1075 // | <mf-value> <mf-lt> <mf-name> <mf-lt> <mf-value>
1076 // | <mf-value> <mf-gt> <mf-name> <mf-gt> <mf-value>`
1077 // NOTE: To parse the first value, we need to first find and parse the <mf-name> so we know what value types to parse.
1078 // To allow for <mf-value> to be any number of tokens long, we scan forward until we find a comparison, and then
1079 // treat the next non-whitespace token as the <mf-name>, which should be correct as long as they don't add a value
1080 // type that can include a comparison in it. :^)
1081 Optional<MediaFeatureName> maybe_name;
1082 {
1083 // This transaction is never committed, we just use it to rewind automatically.
1084 auto temp_transaction = tokens.begin_transaction();
1085 while (tokens.has_next_token() && !maybe_name.has_value()) {
1086 if (auto maybe_comparison = parse_comparison(tokens); maybe_comparison.has_value()) {
1087 // We found a comparison, so the next non-whitespace token should be the <mf-name>
1088 tokens.skip_whitespace();
1089 maybe_name = parse_mf_name(tokens, false);
1090 break;
1091 }
1092 tokens.next_token();
1093 tokens.skip_whitespace();
1094 }
1095 }
1096
1097 // Now, we can parse the range properly.
1098 if (maybe_name.has_value() && media_feature_type_is_range(maybe_name->id)) {
1099 if (auto maybe_left_value = parse_media_feature_value(maybe_name->id, tokens); maybe_left_value.has_value()) {
1100 tokens.skip_whitespace();
1101 if (auto maybe_left_comparison = parse_comparison(tokens); maybe_left_comparison.has_value()) {
1102 tokens.skip_whitespace();
1103 tokens.next_token(); // The <mf-name> which we already parsed above.
1104 tokens.skip_whitespace();
1105
1106 if (!tokens.has_next_token()) {
1107 transaction.commit();
1108 return MediaFeature::half_range(maybe_left_value.release_value(), maybe_left_comparison.release_value(), maybe_name->id);
1109 }
1110
1111 if (auto maybe_right_comparison = parse_comparison(tokens); maybe_right_comparison.has_value()) {
1112 tokens.skip_whitespace();
1113 if (auto maybe_right_value = parse_media_feature_value(maybe_name->id, tokens); maybe_right_value.has_value()) {
1114 tokens.skip_whitespace();
1115 // For this to be valid, the following must be true:
1116 // - Comparisons must either both be >/>= or both be </<=.
1117 // - Neither comparison can be `=`.
1118 // - Neither value can be an ident.
1119 auto left_comparison = maybe_left_comparison.release_value();
1120 auto right_comparison = maybe_right_comparison.release_value();
1121
1122 if (!tokens.has_next_token()
1123 && comparisons_match(left_comparison, right_comparison)
1124 && left_comparison != MediaFeature::Comparison::Equal
1125 && !maybe_left_value->is_ident() && !maybe_right_value->is_ident()) {
1126 transaction.commit();
1127 return MediaFeature::range(maybe_left_value.release_value(), left_comparison, maybe_name->id, right_comparison, maybe_right_value.release_value());
1128 }
1129 }
1130 }
1131 }
1132 }
1133 }
1134
1135 return {};
1136 };
1137
1138 if (auto maybe_mf_boolean = parse_mf_boolean(tokens); maybe_mf_boolean.has_value())
1139 return maybe_mf_boolean.release_value();
1140
1141 if (auto maybe_mf_plain = parse_mf_plain(tokens); maybe_mf_plain.has_value())
1142 return maybe_mf_plain.release_value();
1143
1144 if (auto maybe_mf_range = parse_mf_range(tokens); maybe_mf_range.has_value())
1145 return maybe_mf_range.release_value();
1146
1147 return {};
1148}
1149
1150Optional<MediaQuery::MediaType> Parser::parse_media_type(TokenStream<ComponentValue>& tokens)
1151{
1152 auto transaction = tokens.begin_transaction();
1153 tokens.skip_whitespace();
1154 auto const& token = tokens.next_token();
1155
1156 if (!token.is(Token::Type::Ident))
1157 return {};
1158
1159 transaction.commit();
1160
1161 auto ident = token.token().ident();
1162 return media_type_from_string(ident);
1163}
1164
1165// `<media-in-parens>`, https://www.w3.org/TR/mediaqueries-4/#typedef-media-in-parens
1166OwnPtr<MediaCondition> Parser::parse_media_in_parens(TokenStream<ComponentValue>& tokens)
1167{
1168 // `<media-in-parens> = ( <media-condition> ) | ( <media-feature> ) | <general-enclosed>`
1169 auto transaction = tokens.begin_transaction();
1170 tokens.skip_whitespace();
1171
1172 // `( <media-condition> ) | ( <media-feature> )`
1173 auto const& first_token = tokens.peek_token();
1174 if (first_token.is_block() && first_token.block().is_paren()) {
1175 TokenStream inner_token_stream { first_token.block().values() };
1176 if (auto maybe_media_condition = parse_media_condition(inner_token_stream, MediaCondition::AllowOr::Yes)) {
1177 tokens.next_token();
1178 transaction.commit();
1179 return maybe_media_condition.release_nonnull();
1180 }
1181 if (auto maybe_media_feature = parse_media_feature(inner_token_stream); maybe_media_feature.has_value()) {
1182 tokens.next_token();
1183 transaction.commit();
1184 return MediaCondition::from_feature(maybe_media_feature.release_value());
1185 }
1186 }
1187
1188 // `<general-enclosed>`
1189 // FIXME: We should only be taking this branch if the grammar doesn't match the above options.
1190 // Currently we take it if the above fail to parse, which is different.
1191 // eg, `@media (min-width: 76yaks)` is valid grammar, but does not parse because `yaks` isn't a unit.
1192 if (auto maybe_general_enclosed = parse_general_enclosed(tokens); maybe_general_enclosed.has_value()) {
1193 transaction.commit();
1194 return MediaCondition::from_general_enclosed(maybe_general_enclosed.release_value());
1195 }
1196
1197 return {};
1198}
1199
1200// `<mf-value>`, https://www.w3.org/TR/mediaqueries-4/#typedef-mf-value
1201Optional<MediaFeatureValue> Parser::parse_media_feature_value(MediaFeatureID media_feature, TokenStream<ComponentValue>& tokens)
1202{
1203 // Identifiers
1204 if (tokens.peek_token().is(Token::Type::Ident)) {
1205 auto transaction = tokens.begin_transaction();
1206 tokens.skip_whitespace();
1207 auto ident = value_id_from_string(tokens.next_token().token().ident());
1208 if (ident != ValueID::Invalid && media_feature_accepts_identifier(media_feature, ident)) {
1209 transaction.commit();
1210 return MediaFeatureValue(ident);
1211 }
1212 }
1213
1214 // One branch for each member of the MediaFeatureValueType enum:
1215
1216 // Boolean (<mq-boolean> in the spec: a 1 or 0)
1217 if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Boolean)) {
1218 auto transaction = tokens.begin_transaction();
1219 tokens.skip_whitespace();
1220 auto const& first = tokens.next_token();
1221 if (first.is(Token::Type::Number) && first.token().number().is_integer()
1222 && (first.token().number_value() == 0 || first.token().number_value() == 1)) {
1223 transaction.commit();
1224 return MediaFeatureValue(first.token().number_value());
1225 }
1226 }
1227
1228 // Integer
1229 if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Integer)) {
1230 auto transaction = tokens.begin_transaction();
1231 tokens.skip_whitespace();
1232 auto const& first = tokens.next_token();
1233 if (first.is(Token::Type::Number) && first.token().number().is_integer()) {
1234 transaction.commit();
1235 return MediaFeatureValue(first.token().number_value());
1236 }
1237 }
1238
1239 // Length
1240 if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Length)) {
1241 auto transaction = tokens.begin_transaction();
1242 tokens.skip_whitespace();
1243 auto const& first = tokens.next_token();
1244 if (auto length = parse_length(first); length.has_value()) {
1245 transaction.commit();
1246 return MediaFeatureValue(length.release_value());
1247 }
1248 }
1249
1250 // Ratio
1251 if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Ratio)) {
1252 auto transaction = tokens.begin_transaction();
1253 tokens.skip_whitespace();
1254 if (auto ratio = parse_ratio(tokens); ratio.has_value()) {
1255 transaction.commit();
1256 return MediaFeatureValue(ratio.release_value());
1257 }
1258 }
1259
1260 // Resolution
1261 if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Resolution)) {
1262 auto transaction = tokens.begin_transaction();
1263 tokens.skip_whitespace();
1264 auto const& first = tokens.next_token();
1265 if (auto resolution = parse_dimension(first); resolution.has_value() && resolution->is_resolution()) {
1266 transaction.commit();
1267 return MediaFeatureValue(resolution->resolution());
1268 }
1269 }
1270
1271 return {};
1272}
1273
1274RefPtr<Supports> Parser::parse_as_supports()
1275{
1276 return parse_a_supports(m_token_stream);
1277}
1278
1279template<typename T>
1280RefPtr<Supports> Parser::parse_a_supports(TokenStream<T>& tokens)
1281{
1282 auto component_values = parse_a_list_of_component_values(tokens);
1283 TokenStream<ComponentValue> token_stream { component_values };
1284 auto maybe_condition = parse_supports_condition(token_stream);
1285 token_stream.skip_whitespace();
1286 if (maybe_condition && !token_stream.has_next_token())
1287 return Supports::create(maybe_condition.release_nonnull());
1288
1289 return {};
1290}
1291
1292OwnPtr<Supports::Condition> Parser::parse_supports_condition(TokenStream<ComponentValue>& tokens)
1293{
1294 auto transaction = tokens.begin_transaction();
1295 tokens.skip_whitespace();
1296
1297 auto const& peeked_token = tokens.peek_token();
1298 // `not <supports-in-parens>`
1299 if (peeked_token.is(Token::Type::Ident) && peeked_token.token().ident().equals_ignoring_ascii_case("not"sv)) {
1300 tokens.next_token();
1301 tokens.skip_whitespace();
1302 auto child = parse_supports_in_parens(tokens);
1303 if (!child.has_value())
1304 return {};
1305
1306 transaction.commit();
1307 auto condition = make<Supports::Condition>();
1308 condition->type = Supports::Condition::Type::Not;
1309 condition->children.append(child.release_value());
1310 return condition;
1311 }
1312
1313 // ` <supports-in-parens> [ and <supports-in-parens> ]*
1314 // | <supports-in-parens> [ or <supports-in-parens> ]*`
1315 Vector<Supports::InParens> children;
1316 Optional<Supports::Condition::Type> condition_type {};
1317 auto as_condition_type = [](auto& token) -> Optional<Supports::Condition::Type> {
1318 if (!token.is(Token::Type::Ident))
1319 return {};
1320 auto ident = token.token().ident();
1321 if (ident.equals_ignoring_ascii_case("and"sv))
1322 return Supports::Condition::Type::And;
1323 if (ident.equals_ignoring_ascii_case("or"sv))
1324 return Supports::Condition::Type::Or;
1325 return {};
1326 };
1327
1328 while (tokens.has_next_token()) {
1329 if (!children.is_empty()) {
1330 // Expect `and` or `or` here
1331 auto maybe_combination = as_condition_type(tokens.next_token());
1332 if (!maybe_combination.has_value())
1333 return {};
1334 if (!condition_type.has_value()) {
1335 condition_type = maybe_combination.value();
1336 } else if (maybe_combination != condition_type) {
1337 return {};
1338 }
1339 }
1340
1341 tokens.skip_whitespace();
1342
1343 if (auto in_parens = parse_supports_in_parens(tokens); in_parens.has_value()) {
1344 children.append(in_parens.release_value());
1345 } else {
1346 return {};
1347 }
1348
1349 tokens.skip_whitespace();
1350 }
1351
1352 if (children.is_empty())
1353 return {};
1354
1355 transaction.commit();
1356 auto condition = make<Supports::Condition>();
1357 condition->type = condition_type.value_or(Supports::Condition::Type::Or);
1358 condition->children = move(children);
1359 return condition;
1360}
1361
1362Optional<Supports::InParens> Parser::parse_supports_in_parens(TokenStream<ComponentValue>& tokens)
1363{
1364 // `( <supports-condition> )`
1365 auto const& first_token = tokens.peek_token();
1366 if (first_token.is_block() && first_token.block().is_paren()) {
1367 auto transaction = tokens.begin_transaction();
1368 tokens.next_token();
1369 tokens.skip_whitespace();
1370
1371 TokenStream child_tokens { first_token.block().values() };
1372 if (auto condition = parse_supports_condition(child_tokens)) {
1373 if (child_tokens.has_next_token())
1374 return {};
1375 transaction.commit();
1376 return Supports::InParens {
1377 .value = { condition.release_nonnull() }
1378 };
1379 }
1380 }
1381
1382 // `<supports-feature>`
1383 if (auto feature = parse_supports_feature(tokens); feature.has_value()) {
1384 return Supports::InParens {
1385 .value = { feature.release_value() }
1386 };
1387 }
1388
1389 // `<general-enclosed>`
1390 if (auto general_enclosed = parse_general_enclosed(tokens); general_enclosed.has_value()) {
1391 return Supports::InParens {
1392 .value = general_enclosed.release_value()
1393 };
1394 }
1395
1396 return {};
1397}
1398
1399Optional<Supports::Feature> Parser::parse_supports_feature(TokenStream<ComponentValue>& tokens)
1400{
1401 auto transaction = tokens.begin_transaction();
1402 tokens.skip_whitespace();
1403 auto const& first_token = tokens.next_token();
1404
1405 // `<supports-decl>`
1406 if (first_token.is_block() && first_token.block().is_paren()) {
1407 TokenStream block_tokens { first_token.block().values() };
1408 // FIXME: Parsing and then converting back to a string is weird.
1409 if (auto declaration = consume_a_declaration(block_tokens); declaration.has_value()) {
1410 transaction.commit();
1411 return Supports::Feature {
1412 Supports::Declaration { declaration->to_string().release_value_but_fixme_should_propagate_errors(), JS::make_handle(m_context.realm()) }
1413 };
1414 }
1415 }
1416
1417 // `<supports-selector-fn>`
1418 if (first_token.is_function() && first_token.function().name().equals_ignoring_ascii_case("selector"sv)) {
1419 // FIXME: Parsing and then converting back to a string is weird.
1420 StringBuilder builder;
1421 for (auto const& item : first_token.function().values())
1422 builder.append(item.to_string().release_value_but_fixme_should_propagate_errors());
1423 transaction.commit();
1424 return Supports::Feature {
1425 Supports::Selector { builder.to_string().release_value_but_fixme_should_propagate_errors(), JS::make_handle(m_context.realm()) }
1426 };
1427 }
1428
1429 return {};
1430}
1431
1432// https://www.w3.org/TR/mediaqueries-4/#typedef-general-enclosed
1433Optional<GeneralEnclosed> Parser::parse_general_enclosed(TokenStream<ComponentValue>& tokens)
1434{
1435 auto transaction = tokens.begin_transaction();
1436 tokens.skip_whitespace();
1437 auto const& first_token = tokens.next_token();
1438
1439 // `[ <function-token> <any-value>? ) ]`
1440 if (first_token.is_function()) {
1441 transaction.commit();
1442 return GeneralEnclosed { first_token.to_string().release_value_but_fixme_should_propagate_errors() };
1443 }
1444
1445 // `( <any-value>? )`
1446 if (first_token.is_block() && first_token.block().is_paren()) {
1447 transaction.commit();
1448 return GeneralEnclosed { first_token.to_string().release_value_but_fixme_should_propagate_errors() };
1449 }
1450
1451 return {};
1452}
1453
1454// 5.4.1. Consume a list of rules
1455// https://www.w3.org/TR/css-syntax-3/#consume-list-of-rules
1456template<typename T>
1457Vector<NonnullRefPtr<Rule>> Parser::consume_a_list_of_rules(TokenStream<T>& tokens, TopLevel top_level)
1458{
1459 // To consume a list of rules, given a top-level flag:
1460
1461 // Create an initially empty list of rules.
1462 Vector<NonnullRefPtr<Rule>> rules;
1463
1464 // Repeatedly consume the next input token:
1465 for (;;) {
1466 auto& token = tokens.next_token();
1467
1468 // <whitespace-token>
1469 if (token.is(Token::Type::Whitespace)) {
1470 // Do nothing.
1471 continue;
1472 }
1473
1474 // <EOF-token>
1475 if (token.is(Token::Type::EndOfFile)) {
1476 // Return the list of rules.
1477 return rules;
1478 }
1479
1480 // <CDO-token>
1481 // <CDC-token>
1482 if (token.is(Token::Type::CDO) || token.is(Token::Type::CDC)) {
1483 // If the top-level flag is set, do nothing.
1484 if (top_level == TopLevel::Yes)
1485 continue;
1486
1487 // Otherwise, reconsume the current input token.
1488 tokens.reconsume_current_input_token();
1489
1490 // Consume a qualified rule. If anything is returned, append it to the list of rules.
1491 if (auto maybe_qualified = consume_a_qualified_rule(tokens))
1492 rules.append(maybe_qualified.release_nonnull());
1493
1494 continue;
1495 }
1496
1497 // <at-keyword-token>
1498 if (token.is(Token::Type::AtKeyword)) {
1499 // Reconsume the current input token.
1500 tokens.reconsume_current_input_token();
1501
1502 // Consume an at-rule, and append the returned value to the list of rules.
1503 rules.append(consume_an_at_rule(tokens));
1504
1505 continue;
1506 }
1507
1508 // anything else
1509 {
1510 // Reconsume the current input token.
1511 tokens.reconsume_current_input_token();
1512
1513 // Consume a qualified rule. If anything is returned, append it to the list of rules.
1514 if (auto maybe_qualified = consume_a_qualified_rule(tokens))
1515 rules.append(maybe_qualified.release_nonnull());
1516
1517 continue;
1518 }
1519 }
1520}
1521
1522// 5.4.2. Consume an at-rule
1523// https://www.w3.org/TR/css-syntax-3/#consume-at-rule
1524template<typename T>
1525NonnullRefPtr<Rule> Parser::consume_an_at_rule(TokenStream<T>& tokens)
1526{
1527 // To consume an at-rule:
1528
1529 // Consume the next input token.
1530 auto& name_ident = tokens.next_token();
1531 VERIFY(name_ident.is(Token::Type::AtKeyword));
1532
1533 // Create a new at-rule with its name set to the value of the current input token, its prelude initially set to an empty list, and its value initially set to nothing.
1534 // NOTE: We create the Rule fully initialized when we return it instead.
1535 auto at_rule_name = FlyString::from_utf8(((Token)name_ident).at_keyword()).release_value_but_fixme_should_propagate_errors();
1536 Vector<ComponentValue> prelude;
1537 RefPtr<Block> block;
1538
1539 // Repeatedly consume the next input token:
1540 for (;;) {
1541 auto& token = tokens.next_token();
1542
1543 // <semicolon-token>
1544 if (token.is(Token::Type::Semicolon)) {
1545 // Return the at-rule.
1546 return Rule::make_at_rule(move(at_rule_name), move(prelude), move(block));
1547 }
1548
1549 // <EOF-token>
1550 if (token.is(Token::Type::EndOfFile)) {
1551 // This is a parse error. Return the at-rule.
1552 log_parse_error();
1553 return Rule::make_at_rule(move(at_rule_name), move(prelude), move(block));
1554 }
1555
1556 // <{-token>
1557 if (token.is(Token::Type::OpenCurly)) {
1558 // Consume a simple block and assign it to the at-rule’s block. Return the at-rule.
1559 block = consume_a_simple_block(tokens);
1560 return Rule::make_at_rule(move(at_rule_name), move(prelude), move(block));
1561 }
1562
1563 // simple block with an associated token of <{-token>
1564 if constexpr (IsSame<T, ComponentValue>) {
1565 ComponentValue const& component_value = token;
1566 if (component_value.is_block() && component_value.block().is_curly()) {
1567 // Assign the block to the at-rule’s block. Return the at-rule.
1568 block = component_value.block();
1569 return Rule::make_at_rule(move(at_rule_name), move(prelude), move(block));
1570 }
1571 }
1572
1573 // anything else
1574 {
1575 // Reconsume the current input token.
1576 tokens.reconsume_current_input_token();
1577 // Consume a component value. Append the returned value to the at-rule’s prelude.
1578 prelude.append(consume_a_component_value(tokens));
1579 }
1580 }
1581}
1582
1583// 5.4.3. Consume a qualified rule
1584// https://www.w3.org/TR/css-syntax-3/#consume-qualified-rule
1585template<typename T>
1586RefPtr<Rule> Parser::consume_a_qualified_rule(TokenStream<T>& tokens)
1587{
1588 // To consume a qualified rule:
1589
1590 // Create a new qualified rule with its prelude initially set to an empty list, and its value initially set to nothing.
1591 // NOTE: We create the Rule fully initialized when we return it instead.
1592 Vector<ComponentValue> prelude;
1593 RefPtr<Block> block;
1594
1595 // Repeatedly consume the next input token:
1596 for (;;) {
1597 auto& token = tokens.next_token();
1598
1599 // <EOF-token>
1600 if (token.is(Token::Type::EndOfFile)) {
1601 // This is a parse error. Return nothing.
1602 log_parse_error();
1603 return {};
1604 }
1605
1606 // <{-token>
1607 if (token.is(Token::Type::OpenCurly)) {
1608 // Consume a simple block and assign it to the qualified rule’s block. Return the qualified rule.
1609 block = consume_a_simple_block(tokens);
1610 return Rule::make_qualified_rule(move(prelude), move(block));
1611 }
1612
1613 // simple block with an associated token of <{-token>
1614 if constexpr (IsSame<T, ComponentValue>) {
1615 ComponentValue const& component_value = token;
1616 if (component_value.is_block() && component_value.block().is_curly()) {
1617 // Assign the block to the qualified rule’s block. Return the qualified rule.
1618 block = component_value.block();
1619 return Rule::make_qualified_rule(move(prelude), move(block));
1620 }
1621 }
1622
1623 // anything else
1624 {
1625 // Reconsume the current input token.
1626 tokens.reconsume_current_input_token();
1627
1628 // Consume a component value. Append the returned value to the qualified rule’s prelude.
1629 prelude.append(consume_a_component_value(tokens));
1630 }
1631 }
1632}
1633
1634// 5.4.4. Consume a style block’s contents
1635// https://www.w3.org/TR/css-syntax-3/#consume-a-style-blocks-contents
1636template<typename T>
1637Vector<DeclarationOrAtRule> Parser::consume_a_style_blocks_contents(TokenStream<T>& tokens)
1638{
1639 // To consume a style block’s contents:
1640 // Create an initially empty list of declarations decls, and an initially empty list of rules rules.
1641 Vector<DeclarationOrAtRule> declarations;
1642 Vector<DeclarationOrAtRule> rules;
1643
1644 // Repeatedly consume the next input token:
1645 for (;;) {
1646 auto& token = tokens.next_token();
1647
1648 // <whitespace-token>
1649 // <semicolon-token>
1650 if (token.is(Token::Type::Whitespace) || token.is(Token::Type::Semicolon)) {
1651 // Do nothing.
1652 continue;
1653 }
1654
1655 // <EOF-token>
1656 if (token.is(Token::Type::EndOfFile)) {
1657 // Extend decls with rules, then return decls.
1658 declarations.extend(move(rules));
1659 return declarations;
1660 }
1661
1662 // <at-keyword-token>
1663 if (token.is(Token::Type::AtKeyword)) {
1664 // Reconsume the current input token.
1665 tokens.reconsume_current_input_token();
1666
1667 // Consume an at-rule, and append the result to rules.
1668 rules.empend(consume_an_at_rule(tokens));
1669 continue;
1670 }
1671
1672 // <ident-token>
1673 if (token.is(Token::Type::Ident)) {
1674 // Initialize a temporary list initially filled with the current input token.
1675 Vector<ComponentValue> temporary_list;
1676 temporary_list.append(token);
1677
1678 // As long as the next input token is anything other than a <semicolon-token> or <EOF-token>,
1679 // consume a component value and append it to the temporary list.
1680 for (;;) {
1681 auto& next_input_token = tokens.peek_token();
1682 if (next_input_token.is(Token::Type::Semicolon) || next_input_token.is(Token::Type::EndOfFile))
1683 break;
1684 temporary_list.append(consume_a_component_value(tokens));
1685 }
1686
1687 // Consume a declaration from the temporary list. If anything was returned, append it to decls.
1688 auto token_stream = TokenStream(temporary_list);
1689 if (auto maybe_declaration = consume_a_declaration(token_stream); maybe_declaration.has_value())
1690 declarations.empend(maybe_declaration.release_value());
1691
1692 continue;
1693 }
1694
1695 // <delim-token> with a value of "&" (U+0026 AMPERSAND)
1696 if (token.is(Token::Type::Delim) && token.token().delim() == '&') {
1697 // Reconsume the current input token.
1698 tokens.reconsume_current_input_token();
1699
1700 // Consume a qualified rule. If anything was returned, append it to rules.
1701 if (auto qualified_rule = consume_a_qualified_rule(tokens))
1702 rules.empend(qualified_rule);
1703
1704 continue;
1705 }
1706
1707 // anything else
1708 {
1709 // This is a parse error.
1710 log_parse_error();
1711
1712 // Reconsume the current input token.
1713 tokens.reconsume_current_input_token();
1714
1715 // As long as the next input token is anything other than a <semicolon-token> or <EOF-token>,
1716 // consume a component value and throw away the returned value.
1717 for (;;) {
1718 auto& peek = tokens.peek_token();
1719 if (peek.is(Token::Type::Semicolon) || peek.is(Token::Type::EndOfFile))
1720 break;
1721 (void)consume_a_component_value(tokens);
1722 }
1723 }
1724 }
1725}
1726
1727template<>
1728ComponentValue Parser::consume_a_component_value(TokenStream<ComponentValue>& tokens)
1729{
1730 // Note: This overload is called once tokens have already been converted into component values,
1731 // so we do not need to do the work in the more general overload.
1732 return tokens.next_token();
1733}
1734
1735// 5.4.7. Consume a component value
1736// https://www.w3.org/TR/css-syntax-3/#consume-component-value
1737template<typename T>
1738ComponentValue Parser::consume_a_component_value(TokenStream<T>& tokens)
1739{
1740 // To consume a component value:
1741
1742 // Consume the next input token.
1743 auto& token = tokens.next_token();
1744
1745 // If the current input token is a <{-token>, <[-token>, or <(-token>, consume a simple block and return it.
1746 if (token.is(Token::Type::OpenCurly) || token.is(Token::Type::OpenSquare) || token.is(Token::Type::OpenParen))
1747 return ComponentValue(consume_a_simple_block(tokens));
1748
1749 // Otherwise, if the current input token is a <function-token>, consume a function and return it.
1750 if (token.is(Token::Type::Function))
1751 return ComponentValue(consume_a_function(tokens));
1752
1753 // Otherwise, return the current input token.
1754 return ComponentValue(token);
1755}
1756
1757// 5.4.8. Consume a simple block
1758// https://www.w3.org/TR/css-syntax-3/#consume-simple-block
1759template<typename T>
1760NonnullRefPtr<Block> Parser::consume_a_simple_block(TokenStream<T>& tokens)
1761{
1762 // Note: This algorithm assumes that the current input token has already been checked
1763 // to be an <{-token>, <[-token>, or <(-token>.
1764
1765 // To consume a simple block:
1766
1767 // The ending token is the mirror variant of the current input token.
1768 // (E.g. if it was called with <[-token>, the ending token is <]-token>.)
1769 auto ending_token = ((Token)tokens.current_token()).mirror_variant();
1770
1771 // Create a simple block with its associated token set to the current input token
1772 // and with its value initially set to an empty list.
1773 // NOTE: We create the Block fully initialized when we return it instead.
1774 Token block_token = tokens.current_token();
1775 Vector<ComponentValue> block_values;
1776
1777 // Repeatedly consume the next input token and process it as follows:
1778 for (;;) {
1779 auto& token = tokens.next_token();
1780
1781 // ending token
1782 if (token.is(ending_token)) {
1783 // Return the block.
1784 return Block::create(move(block_token), move(block_values));
1785 }
1786 // <EOF-token>
1787 if (token.is(Token::Type::EndOfFile)) {
1788 // This is a parse error. Return the block.
1789 log_parse_error();
1790 return Block::create(move(block_token), move(block_values));
1791 }
1792
1793 // anything else
1794 {
1795 // Reconsume the current input token.
1796 tokens.reconsume_current_input_token();
1797
1798 // Consume a component value and append it to the value of the block.
1799 block_values.empend(consume_a_component_value(tokens));
1800 }
1801 }
1802}
1803
1804// 5.4.9. Consume a function
1805// https://www.w3.org/TR/css-syntax-3/#consume-function
1806template<typename T>
1807NonnullRefPtr<Function> Parser::consume_a_function(TokenStream<T>& tokens)
1808{
1809 // Note: This algorithm assumes that the current input token has already been checked to be a <function-token>.
1810 auto name_ident = tokens.current_token();
1811 VERIFY(name_ident.is(Token::Type::Function));
1812
1813 // To consume a function:
1814
1815 // Create a function with its name equal to the value of the current input token
1816 // and with its value initially set to an empty list.
1817 // NOTE: We create the Function fully initialized when we return it instead.
1818 auto function_name = FlyString::from_utf8(((Token)name_ident).function()).release_value_but_fixme_should_propagate_errors();
1819 Vector<ComponentValue> function_values;
1820
1821 // Repeatedly consume the next input token and process it as follows:
1822 for (;;) {
1823 auto& token = tokens.next_token();
1824
1825 // <)-token>
1826 if (token.is(Token::Type::CloseParen)) {
1827 // Return the function.
1828 return Function::create(move(function_name), move(function_values));
1829 }
1830
1831 // <EOF-token>
1832 if (token.is(Token::Type::EndOfFile)) {
1833 // This is a parse error. Return the function.
1834 log_parse_error();
1835 return Function::create(move(function_name), move(function_values));
1836 }
1837
1838 // anything else
1839 {
1840 // Reconsume the current input token.
1841 tokens.reconsume_current_input_token();
1842
1843 // Consume a component value and append the returned value to the function’s value.
1844 function_values.append(consume_a_component_value(tokens));
1845 }
1846 }
1847}
1848
1849// 5.4.6. Consume a declaration
1850// https://www.w3.org/TR/css-syntax-3/#consume-declaration
1851template<typename T>
1852Optional<Declaration> Parser::consume_a_declaration(TokenStream<T>& tokens)
1853{
1854 // Note: This algorithm assumes that the next input token has already been checked to
1855 // be an <ident-token>.
1856 // NOTE: This is not true in our implementation! For convenience, we both skip whitespace
1857 // and gracefully handle the first token not being an <ident-token>.
1858
1859 // To consume a declaration:
1860
1861 // Consume the next input token.
1862 auto transaction = tokens.begin_transaction();
1863 tokens.skip_whitespace();
1864 auto& token = tokens.next_token();
1865
1866 // NOTE: Not to spec, handle the case where the input token *isn't* an <ident-token>.
1867 if (!token.is(Token::Type::Ident))
1868 return {};
1869
1870 // Create a new declaration with its name set to the value of the current input token
1871 // and its value initially set to the empty list.
1872 // NOTE: We create a fully-initialized Declaration just before returning it instead.
1873 auto declaration_name = FlyString::from_utf8(((Token)token).ident()).release_value_but_fixme_should_propagate_errors();
1874 Vector<ComponentValue> declaration_values;
1875 Important declaration_important = Important::No;
1876
1877 // 1. While the next input token is a <whitespace-token>, consume the next input token.
1878 tokens.skip_whitespace();
1879
1880 // 2. If the next input token is anything other than a <colon-token>, this is a parse error.
1881 // Return nothing.
1882 auto& maybe_colon = tokens.peek_token();
1883 if (!maybe_colon.is(Token::Type::Colon)) {
1884 log_parse_error();
1885 return {};
1886 }
1887 // Otherwise, consume the next input token.
1888 tokens.next_token();
1889
1890 // 3. While the next input token is a <whitespace-token>, consume the next input token.
1891 tokens.skip_whitespace();
1892
1893 // 4. As long as the next input token is anything other than an <EOF-token>, consume a
1894 // component value and append it to the declaration’s value.
1895 for (;;) {
1896 if (tokens.peek_token().is(Token::Type::EndOfFile)) {
1897 break;
1898 }
1899 declaration_values.append(consume_a_component_value(tokens));
1900 }
1901
1902 // 5. If the last two non-<whitespace-token>s in the declaration’s value are a <delim-token>
1903 // with the value "!" followed by an <ident-token> with a value that is an ASCII case-insensitive
1904 // match for "important", remove them from the declaration’s value and set the declaration’s
1905 // important flag to true.
1906 if (declaration_values.size() >= 2) {
1907 // Walk backwards from the end until we find "important"
1908 Optional<size_t> important_index;
1909 for (size_t i = declaration_values.size() - 1; i > 0; i--) {
1910 auto value = declaration_values[i];
1911 if (value.is(Token::Type::Ident) && Infra::is_ascii_case_insensitive_match(value.token().ident(), "important"sv)) {
1912 important_index = i;
1913 break;
1914 }
1915 if (value.is(Token::Type::Whitespace))
1916 continue;
1917 break;
1918 }
1919
1920 // Walk backwards from important until we find "!"
1921 if (important_index.has_value()) {
1922 Optional<size_t> bang_index;
1923 for (size_t i = important_index.value() - 1; i > 0; i--) {
1924 auto value = declaration_values[i];
1925 if (value.is(Token::Type::Delim) && value.token().delim() == '!') {
1926 bang_index = i;
1927 break;
1928 }
1929 if (value.is(Token::Type::Whitespace))
1930 continue;
1931 break;
1932 }
1933
1934 if (bang_index.has_value()) {
1935 declaration_values.remove(important_index.value());
1936 declaration_values.remove(bang_index.value());
1937 declaration_important = Important::Yes;
1938 }
1939 }
1940 }
1941
1942 // 6. While the last token in the declaration’s value is a <whitespace-token>, remove that token.
1943 while (!declaration_values.is_empty()) {
1944 auto maybe_whitespace = declaration_values.last();
1945 if (!(maybe_whitespace.is(Token::Type::Whitespace))) {
1946 break;
1947 }
1948 declaration_values.take_last();
1949 }
1950
1951 // 7. Return the declaration.
1952 transaction.commit();
1953 return Declaration { move(declaration_name), move(declaration_values), declaration_important };
1954}
1955
1956// 5.4.5. Consume a list of declarations
1957// https://www.w3.org/TR/css-syntax-3/#consume-list-of-declarations
1958template<typename T>
1959Vector<DeclarationOrAtRule> Parser::consume_a_list_of_declarations(TokenStream<T>& tokens)
1960{
1961 // To consume a list of declarations:
1962
1963 // Create an initially empty list of declarations.
1964 Vector<DeclarationOrAtRule> list_of_declarations;
1965
1966 // Repeatedly consume the next input token:
1967 for (;;) {
1968 auto& token = tokens.next_token();
1969
1970 // <whitespace-token>
1971 // <semicolon-token>
1972 if (token.is(Token::Type::Whitespace) || token.is(Token::Type::Semicolon)) {
1973 // Do nothing.
1974 continue;
1975 }
1976
1977 // <EOF-token>
1978 if (token.is(Token::Type::EndOfFile)) {
1979 // Return the list of declarations.
1980 return list_of_declarations;
1981 }
1982
1983 // <at-keyword-token>
1984 if (token.is(Token::Type::AtKeyword)) {
1985 // Reconsume the current input token.
1986 tokens.reconsume_current_input_token();
1987
1988 // Consume an at-rule. Append the returned rule to the list of declarations.
1989 list_of_declarations.empend(consume_an_at_rule(tokens));
1990 continue;
1991 }
1992
1993 // <ident-token>
1994 if (token.is(Token::Type::Ident)) {
1995 // Initialize a temporary list initially filled with the current input token.
1996 Vector<ComponentValue> temporary_list;
1997 temporary_list.append(token);
1998
1999 // As long as the next input token is anything other than a <semicolon-token> or <EOF-token>,
2000 // consume a component value and append it to the temporary list.
2001 for (;;) {
2002 auto& peek = tokens.peek_token();
2003 if (peek.is(Token::Type::Semicolon) || peek.is(Token::Type::EndOfFile))
2004 break;
2005 temporary_list.append(consume_a_component_value(tokens));
2006 }
2007
2008 // Consume a declaration from the temporary list. If anything was returned, append it to the list of declarations.
2009 auto token_stream = TokenStream(temporary_list);
2010 if (auto maybe_declaration = consume_a_declaration(token_stream); maybe_declaration.has_value())
2011 list_of_declarations.empend(maybe_declaration.value());
2012
2013 continue;
2014 }
2015
2016 // anything else
2017 {
2018 // This is a parse error.
2019 log_parse_error();
2020
2021 // Reconsume the current input token.
2022 tokens.reconsume_current_input_token();
2023
2024 // As long as the next input token is anything other than a <semicolon-token> or <EOF-token>,
2025 // consume a component value and throw away the returned value.
2026 for (;;) {
2027 auto& peek = tokens.peek_token();
2028 if (peek.is(Token::Type::Semicolon) || peek.is(Token::Type::EndOfFile))
2029 break;
2030 dbgln_if(CSS_PARSER_DEBUG, "Discarding token: '{}'", peek.to_debug_string());
2031 (void)consume_a_component_value(tokens);
2032 }
2033 }
2034 }
2035}
2036
2037CSSRule* Parser::parse_as_css_rule()
2038{
2039 auto maybe_rule = parse_a_rule(m_token_stream);
2040 if (maybe_rule)
2041 return convert_to_rule(maybe_rule.release_nonnull());
2042 return {};
2043}
2044
2045// 5.3.5. Parse a rule
2046// https://www.w3.org/TR/css-syntax-3/#parse-rule
2047template<typename T>
2048RefPtr<Rule> Parser::parse_a_rule(TokenStream<T>& tokens)
2049{
2050 // To parse a rule from input:
2051 RefPtr<Rule> rule;
2052
2053 // 1. Normalize input, and set input to the result.
2054 // Note: This is done when initializing the Parser.
2055
2056 // 2. While the next input token from input is a <whitespace-token>, consume the next input token from input.
2057 tokens.skip_whitespace();
2058
2059 // 3. If the next input token from input is an <EOF-token>, return a syntax error.
2060 auto& token = tokens.peek_token();
2061 if (token.is(Token::Type::EndOfFile)) {
2062 return {};
2063 }
2064 // Otherwise, if the next input token from input is an <at-keyword-token>, consume an at-rule from input, and let rule be the return value.
2065 else if (token.is(Token::Type::AtKeyword)) {
2066 rule = consume_an_at_rule(m_token_stream);
2067 }
2068 // Otherwise, consume a qualified rule from input and let rule be the return value. If nothing was returned, return a syntax error.
2069 else {
2070 auto qualified_rule = consume_a_qualified_rule(tokens);
2071 if (!qualified_rule)
2072 return {};
2073
2074 rule = qualified_rule;
2075 }
2076
2077 // 4. While the next input token from input is a <whitespace-token>, consume the next input token from input.
2078 tokens.skip_whitespace();
2079
2080 // 5. If the next input token from input is an <EOF-token>, return rule. Otherwise, return a syntax error.
2081 if (tokens.peek_token().is(Token::Type::EndOfFile))
2082 return rule;
2083 return {};
2084}
2085
2086// 5.3.4. Parse a list of rules
2087// https://www.w3.org/TR/css-syntax-3/#parse-list-of-rules
2088template<typename T>
2089Vector<NonnullRefPtr<Rule>> Parser::parse_a_list_of_rules(TokenStream<T>& tokens)
2090{
2091 // To parse a list of rules from input:
2092
2093 // 1. Normalize input, and set input to the result.
2094 // Note: This is done when initializing the Parser.
2095
2096 // 2. Consume a list of rules from the input, with the top-level flag unset.
2097 auto list_of_rules = consume_a_list_of_rules(tokens, TopLevel::No);
2098
2099 // 3. Return the returned list.
2100 return list_of_rules;
2101}
2102
2103Optional<StyleProperty> Parser::parse_as_supports_condition()
2104{
2105 auto maybe_declaration = parse_a_declaration(m_token_stream);
2106 if (maybe_declaration.has_value())
2107 return convert_to_style_property(maybe_declaration.release_value());
2108 return {};
2109}
2110
2111// 5.3.6. Parse a declaration
2112// https://www.w3.org/TR/css-syntax-3/#parse-a-declaration
2113template<typename T>
2114Optional<Declaration> Parser::parse_a_declaration(TokenStream<T>& tokens)
2115{
2116 // To parse a declaration from input:
2117
2118 // 1. Normalize input, and set input to the result.
2119 // Note: This is done when initializing the Parser.
2120
2121 // 2. While the next input token from input is a <whitespace-token>, consume the next input token.
2122 tokens.skip_whitespace();
2123
2124 // 3. If the next input token from input is not an <ident-token>, return a syntax error.
2125 auto& token = tokens.peek_token();
2126 if (!token.is(Token::Type::Ident)) {
2127 return {};
2128 }
2129
2130 // 4. Consume a declaration from input. If anything was returned, return it. Otherwise, return a syntax error.
2131 if (auto declaration = consume_a_declaration(tokens); declaration.has_value())
2132 return declaration.release_value();
2133 return {};
2134}
2135
2136// 5.3.7. Parse a style block’s contents
2137// https://www.w3.org/TR/css-syntax-3/#parse-style-blocks-contents
2138template<typename T>
2139Vector<DeclarationOrAtRule> Parser::parse_a_style_blocks_contents(TokenStream<T>& tokens)
2140{
2141 // To parse a style block’s contents from input:
2142
2143 // 1. Normalize input, and set input to the result.
2144 // Note: This is done when initializing the Parser.
2145
2146 // 2. Consume a style block’s contents from input, and return the result.
2147 return consume_a_style_blocks_contents(tokens);
2148}
2149
2150// 5.3.8. Parse a list of declarations
2151// https://www.w3.org/TR/css-syntax-3/#parse-list-of-declarations
2152template<typename T>
2153Vector<DeclarationOrAtRule> Parser::parse_a_list_of_declarations(TokenStream<T>& tokens)
2154{
2155 // To parse a list of declarations from input:
2156
2157 // 1. Normalize input, and set input to the result.
2158 // Note: This is done when initializing the Parser.
2159
2160 // 2. Consume a list of declarations from input, and return the result.
2161 return consume_a_list_of_declarations(tokens);
2162}
2163
2164// 5.3.9. Parse a component value
2165// https://www.w3.org/TR/css-syntax-3/#parse-component-value
2166template<typename T>
2167Optional<ComponentValue> Parser::parse_a_component_value(TokenStream<T>& tokens)
2168{
2169 // To parse a component value from input:
2170
2171 // 1. Normalize input, and set input to the result.
2172 // Note: This is done when initializing the Parser.
2173
2174 // 2. While the next input token from input is a <whitespace-token>, consume the next input token from input.
2175 tokens.skip_whitespace();
2176
2177 // 3. If the next input token from input is an <EOF-token>, return a syntax error.
2178 if (tokens.peek_token().is(Token::Type::EndOfFile))
2179 return {};
2180
2181 // 4. Consume a component value from input and let value be the return value.
2182 auto value = consume_a_component_value(tokens);
2183
2184 // 5. While the next input token from input is a <whitespace-token>, consume the next input token.
2185 tokens.skip_whitespace();
2186
2187 // 6. If the next input token from input is an <EOF-token>, return value. Otherwise, return a syntax error.
2188 if (tokens.peek_token().is(Token::Type::EndOfFile))
2189 return value;
2190 return {};
2191}
2192
2193// 5.3.10. Parse a list of component values
2194// https://www.w3.org/TR/css-syntax-3/#parse-list-of-component-values
2195template<typename T>
2196Vector<ComponentValue> Parser::parse_a_list_of_component_values(TokenStream<T>& tokens)
2197{
2198 // To parse a list of component values from input:
2199
2200 // 1. Normalize input, and set input to the result.
2201 // Note: This is done when initializing the Parser.
2202
2203 // 2. Repeatedly consume a component value from input until an <EOF-token> is returned, appending the returned values (except the final <EOF-token>) into a list. Return the list.
2204 Vector<ComponentValue> component_values;
2205
2206 for (;;) {
2207 if (tokens.peek_token().is(Token::Type::EndOfFile)) {
2208 break;
2209 }
2210
2211 component_values.append(consume_a_component_value(tokens));
2212 }
2213
2214 return component_values;
2215}
2216
2217// 5.3.11. Parse a comma-separated list of component values
2218// https://www.w3.org/TR/css-syntax-3/#parse-comma-separated-list-of-component-values
2219template<typename T>
2220Vector<Vector<ComponentValue>> Parser::parse_a_comma_separated_list_of_component_values(TokenStream<T>& tokens)
2221{
2222 // To parse a comma-separated list of component values from input:
2223
2224 // 1. Normalize input, and set input to the result.
2225 // Note: This is done when initializing the Parser.
2226
2227 // 2. Let list of cvls be an initially empty list of component value lists.
2228 Vector<Vector<ComponentValue>> list_of_component_value_lists;
2229
2230 // 3. Repeatedly consume a component value from input until an <EOF-token> or <comma-token> is returned,
2231 // appending the returned values (except the final <EOF-token> or <comma-token>) into a list.
2232 // Append the list to list of cvls.
2233 // If it was a <comma-token> that was returned, repeat this step.
2234 Vector<ComponentValue> current_list;
2235 for (;;) {
2236 auto component_value = consume_a_component_value(tokens);
2237
2238 if (component_value.is(Token::Type::EndOfFile)) {
2239 list_of_component_value_lists.append(move(current_list));
2240 break;
2241 }
2242 if (component_value.is(Token::Type::Comma)) {
2243 list_of_component_value_lists.append(move(current_list));
2244 current_list = {};
2245 continue;
2246 }
2247
2248 current_list.append(component_value);
2249 }
2250
2251 // 4. Return list of cvls.
2252 return list_of_component_value_lists;
2253}
2254
2255ElementInlineCSSStyleDeclaration* Parser::parse_as_style_attribute(DOM::Element& element)
2256{
2257 auto declarations_and_at_rules = parse_a_list_of_declarations(m_token_stream);
2258 auto [properties, custom_properties] = extract_properties(declarations_and_at_rules);
2259 return ElementInlineCSSStyleDeclaration::create(element, move(properties), move(custom_properties)).release_value_but_fixme_should_propagate_errors();
2260}
2261
2262Optional<AK::URL> Parser::parse_url_function(ComponentValue const& component_value, AllowedDataUrlType allowed_data_url_type)
2263{
2264 // FIXME: Handle list of media queries. https://www.w3.org/TR/css-cascade-3/#conditional-import
2265 // FIXME: Handle data: urls (RFC2397)
2266
2267 auto convert_string_to_url = [&](StringView& url_string) -> Optional<AK::URL> {
2268 if (url_string.starts_with("data:"sv, CaseSensitivity::CaseInsensitive)) {
2269 auto data_url = AK::URL(url_string);
2270
2271 switch (allowed_data_url_type) {
2272 case AllowedDataUrlType::Image:
2273 if (data_url.data_mime_type().starts_with("image"sv, CaseSensitivity::CaseInsensitive))
2274 return data_url;
2275 break;
2276 case AllowedDataUrlType::Font:
2277 if (data_url.data_mime_type().starts_with("font"sv, CaseSensitivity::CaseInsensitive))
2278 return data_url;
2279 break;
2280 default:
2281 break;
2282 }
2283
2284 return {};
2285 }
2286
2287 return m_context.complete_url(url_string);
2288 };
2289
2290 if (component_value.is(Token::Type::Url)) {
2291 auto url_string = component_value.token().url();
2292 return convert_string_to_url(url_string);
2293 }
2294 if (component_value.is_function() && component_value.function().name().equals_ignoring_ascii_case("url"sv)) {
2295 auto const& function_values = component_value.function().values();
2296 // FIXME: Handle url-modifiers. https://www.w3.org/TR/css-values-4/#url-modifiers
2297 for (size_t i = 0; i < function_values.size(); ++i) {
2298 auto const& value = function_values[i];
2299 if (value.is(Token::Type::Whitespace))
2300 continue;
2301 if (value.is(Token::Type::String)) {
2302 auto url_string = value.token().string();
2303 return convert_string_to_url(url_string);
2304 }
2305 break;
2306 }
2307 }
2308
2309 return {};
2310}
2311
2312template<typename TElement>
2313static Optional<Vector<TElement>> parse_color_stop_list(auto& tokens, auto is_position, auto get_position, auto parse_color, auto parse_dimension)
2314{
2315 enum class ElementType {
2316 Garbage,
2317 ColorStop,
2318 ColorHint
2319 };
2320
2321 auto parse_color_stop_list_element = [&](TElement& element) -> ElementType {
2322 tokens.skip_whitespace();
2323 if (!tokens.has_next_token())
2324 return ElementType::Garbage;
2325 auto const& token = tokens.next_token();
2326
2327 Gfx::Color color;
2328 Optional<typename TElement::PositionType> position;
2329 Optional<typename TElement::PositionType> second_position;
2330 auto dimension = parse_dimension(token);
2331 if (dimension.has_value() && is_position(*dimension)) {
2332 // [<T-percentage> <color>] or [<T-percentage>]
2333 position = get_position(*dimension);
2334 tokens.skip_whitespace();
2335 // <T-percentage>
2336 if (!tokens.has_next_token() || tokens.peek_token().is(Token::Type::Comma)) {
2337 element.transition_hint = typename TElement::ColorHint { *position };
2338 return ElementType::ColorHint;
2339 }
2340 // <T-percentage> <color>
2341 auto maybe_color = parse_color(tokens.next_token());
2342 if (!maybe_color.has_value())
2343 return ElementType::Garbage;
2344 color = *maybe_color;
2345 } else {
2346 // [<color> <T-percentage>?]
2347 auto maybe_color = parse_color(token);
2348 if (!maybe_color.has_value())
2349 return ElementType::Garbage;
2350 color = *maybe_color;
2351 tokens.skip_whitespace();
2352 // Allow up to [<color> <T-percentage> <T-percentage>] (double-position color stops)
2353 // Note: Double-position color stops only appear to be valid in this order.
2354 for (auto stop_position : Array { &position, &second_position }) {
2355 if (tokens.has_next_token() && !tokens.peek_token().is(Token::Type::Comma)) {
2356 auto token = tokens.next_token();
2357 auto dimension = parse_dimension(token);
2358 if (!dimension.has_value() || !is_position(*dimension))
2359 return ElementType::Garbage;
2360 *stop_position = get_position(*dimension);
2361 tokens.skip_whitespace();
2362 }
2363 }
2364 }
2365
2366 element.color_stop = typename TElement::ColorStop { color, position, second_position };
2367 return ElementType::ColorStop;
2368 };
2369
2370 TElement first_element {};
2371 if (parse_color_stop_list_element(first_element) != ElementType::ColorStop)
2372 return {};
2373
2374 if (!tokens.has_next_token())
2375 return {};
2376
2377 Vector<TElement> color_stops { first_element };
2378 while (tokens.has_next_token()) {
2379 TElement list_element {};
2380 tokens.skip_whitespace();
2381 if (!tokens.next_token().is(Token::Type::Comma))
2382 return {};
2383 auto element_type = parse_color_stop_list_element(list_element);
2384 if (element_type == ElementType::ColorHint) {
2385 // <color-hint>, <color-stop>
2386 tokens.skip_whitespace();
2387 if (!tokens.next_token().is(Token::Type::Comma))
2388 return {};
2389 // Note: This fills in the color stop on the same list_element as the color hint (it does not overwrite it).
2390 if (parse_color_stop_list_element(list_element) != ElementType::ColorStop)
2391 return {};
2392 } else if (element_type == ElementType::ColorStop) {
2393 // <color-stop>
2394 } else {
2395 return {};
2396 }
2397 color_stops.append(list_element);
2398 }
2399
2400 return color_stops;
2401}
2402
2403Optional<Vector<LinearColorStopListElement>> Parser::parse_linear_color_stop_list(TokenStream<ComponentValue>& tokens)
2404{
2405 // <color-stop-list> =
2406 // <linear-color-stop> , [ <linear-color-hint>? , <linear-color-stop> ]#
2407 return parse_color_stop_list<LinearColorStopListElement>(
2408 tokens,
2409 [](Dimension& dimension) { return dimension.is_length_percentage(); },
2410 [](Dimension& dimension) { return dimension.length_percentage(); },
2411 [&](auto& token) { return parse_color(token); },
2412 [&](auto& token) { return parse_dimension(token); });
2413}
2414
2415Optional<Vector<AngularColorStopListElement>> Parser::parse_angular_color_stop_list(TokenStream<ComponentValue>& tokens)
2416{
2417 // <angular-color-stop-list> =
2418 // <angular-color-stop> , [ <angular-color-hint>? , <angular-color-stop> ]#
2419 return parse_color_stop_list<AngularColorStopListElement>(
2420 tokens,
2421 [](Dimension& dimension) { return dimension.is_angle_percentage(); },
2422 [](Dimension& dimension) { return dimension.angle_percentage(); },
2423 [&](auto& token) { return parse_color(token); },
2424 [&](auto& token) { return parse_dimension(token); });
2425}
2426
2427static StringView consume_if_starts_with(StringView str, StringView start, auto found_callback)
2428{
2429 if (str.starts_with(start, CaseSensitivity::CaseInsensitive)) {
2430 found_callback();
2431 return str.substring_view(start.length());
2432 }
2433 return str;
2434};
2435
2436RefPtr<StyleValue> Parser::parse_linear_gradient_function(ComponentValue const& component_value)
2437{
2438 using GradientType = LinearGradientStyleValue::GradientType;
2439
2440 if (!component_value.is_function())
2441 return {};
2442
2443 GradientRepeating repeating_gradient = GradientRepeating::No;
2444 GradientType gradient_type { GradientType::Standard };
2445
2446 auto function_name = component_value.function().name();
2447
2448 function_name = consume_if_starts_with(function_name, "-webkit-"sv, [&] {
2449 gradient_type = GradientType::WebKit;
2450 });
2451
2452 function_name = consume_if_starts_with(function_name, "repeating-"sv, [&] {
2453 repeating_gradient = GradientRepeating::Yes;
2454 });
2455
2456 if (!function_name.equals_ignoring_ascii_case("linear-gradient"sv))
2457 return {};
2458
2459 // linear-gradient() = linear-gradient([ <angle> | to <side-or-corner> ]?, <color-stop-list>)
2460
2461 TokenStream tokens { component_value.function().values() };
2462 tokens.skip_whitespace();
2463
2464 if (!tokens.has_next_token())
2465 return {};
2466
2467 bool has_direction_param = true;
2468 LinearGradientStyleValue::GradientDirection gradient_direction = gradient_type == GradientType::Standard
2469 ? SideOrCorner::Bottom
2470 : SideOrCorner::Top;
2471
2472 auto to_side = [](StringView value) -> Optional<SideOrCorner> {
2473 if (value.equals_ignoring_ascii_case("top"sv))
2474 return SideOrCorner::Top;
2475 if (value.equals_ignoring_ascii_case("bottom"sv))
2476 return SideOrCorner::Bottom;
2477 if (value.equals_ignoring_ascii_case("left"sv))
2478 return SideOrCorner::Left;
2479 if (value.equals_ignoring_ascii_case("right"sv))
2480 return SideOrCorner::Right;
2481 return {};
2482 };
2483
2484 auto is_to_side_or_corner = [&](auto const& token) {
2485 if (!token.is(Token::Type::Ident))
2486 return false;
2487 if (gradient_type == GradientType::WebKit)
2488 return to_side(token.token().ident()).has_value();
2489 return token.token().ident().equals_ignoring_ascii_case("to"sv);
2490 };
2491
2492 auto const& first_param = tokens.peek_token();
2493 if (first_param.is(Token::Type::Dimension)) {
2494 // <angle>
2495 tokens.next_token();
2496 float angle_value = first_param.token().dimension_value();
2497 auto unit_string = first_param.token().dimension_unit();
2498 auto angle_type = Angle::unit_from_name(unit_string);
2499
2500 if (!angle_type.has_value())
2501 return {};
2502
2503 gradient_direction = Angle { angle_value, angle_type.release_value() };
2504 } else if (is_to_side_or_corner(first_param)) {
2505 // <side-or-corner> = [left | right] || [top | bottom]
2506
2507 // Note: -webkit-linear-gradient does not include to the "to" prefix on the side or corner
2508 if (gradient_type == GradientType::Standard) {
2509 tokens.next_token();
2510 tokens.skip_whitespace();
2511
2512 if (!tokens.has_next_token())
2513 return {};
2514 }
2515
2516 // [left | right] || [top | bottom]
2517 auto const& first_side = tokens.next_token();
2518 if (!first_side.is(Token::Type::Ident))
2519 return {};
2520
2521 auto side_a = to_side(first_side.token().ident());
2522 tokens.skip_whitespace();
2523 Optional<SideOrCorner> side_b;
2524 if (tokens.has_next_token() && tokens.peek_token().is(Token::Type::Ident))
2525 side_b = to_side(tokens.next_token().token().ident());
2526
2527 if (side_a.has_value() && !side_b.has_value()) {
2528 gradient_direction = *side_a;
2529 } else if (side_a.has_value() && side_b.has_value()) {
2530 // Convert two sides to a corner
2531 if (to_underlying(*side_b) < to_underlying(*side_a))
2532 swap(side_a, side_b);
2533 if (side_a == SideOrCorner::Top && side_b == SideOrCorner::Left)
2534 gradient_direction = SideOrCorner::TopLeft;
2535 else if (side_a == SideOrCorner::Top && side_b == SideOrCorner::Right)
2536 gradient_direction = SideOrCorner::TopRight;
2537 else if (side_a == SideOrCorner::Bottom && side_b == SideOrCorner::Left)
2538 gradient_direction = SideOrCorner::BottomLeft;
2539 else if (side_a == SideOrCorner::Bottom && side_b == SideOrCorner::Right)
2540 gradient_direction = SideOrCorner::BottomRight;
2541 else
2542 return {};
2543 } else {
2544 return {};
2545 }
2546 } else {
2547 has_direction_param = false;
2548 }
2549
2550 tokens.skip_whitespace();
2551 if (!tokens.has_next_token())
2552 return {};
2553
2554 if (has_direction_param && !tokens.next_token().is(Token::Type::Comma))
2555 return {};
2556
2557 auto color_stops = parse_linear_color_stop_list(tokens);
2558 if (!color_stops.has_value())
2559 return {};
2560
2561 return LinearGradientStyleValue::create(gradient_direction, move(*color_stops), gradient_type, repeating_gradient);
2562}
2563
2564RefPtr<StyleValue> Parser::parse_conic_gradient_function(ComponentValue const& component_value)
2565{
2566 if (!component_value.is_function())
2567 return {};
2568
2569 GradientRepeating repeating_gradient = GradientRepeating::No;
2570
2571 auto function_name = component_value.function().name();
2572
2573 function_name = consume_if_starts_with(function_name, "repeating-"sv, [&] {
2574 repeating_gradient = GradientRepeating::Yes;
2575 });
2576
2577 if (!function_name.equals_ignoring_ascii_case("conic-gradient"sv))
2578 return {};
2579
2580 TokenStream tokens { component_value.function().values() };
2581 tokens.skip_whitespace();
2582
2583 if (!tokens.has_next_token())
2584 return {};
2585
2586 Angle from_angle(0, Angle::Type::Deg);
2587 PositionValue at_position = PositionValue::center();
2588
2589 // conic-gradient( [ [ from <angle> ]? [ at <position> ]? ] ||
2590 // <color-interpolation-method> , <angular-color-stop-list> )
2591 auto token = tokens.peek_token();
2592 bool got_from_angle = false;
2593 bool got_color_interpolation_method = false;
2594 bool got_at_position = false;
2595 while (token.is(Token::Type::Ident)) {
2596 auto consume_identifier = [&](auto identifier) {
2597 auto token_string = token.token().ident();
2598 if (token_string.equals_ignoring_ascii_case(identifier)) {
2599 (void)tokens.next_token();
2600 tokens.skip_whitespace();
2601 return true;
2602 }
2603 return false;
2604 };
2605
2606 if (consume_identifier("from"sv)) {
2607 // from <angle>
2608 if (got_from_angle || got_at_position)
2609 return {};
2610 if (!tokens.has_next_token())
2611 return {};
2612
2613 auto angle_token = tokens.next_token();
2614 if (!angle_token.is(Token::Type::Dimension))
2615 return {};
2616 float angle = angle_token.token().dimension_value();
2617 auto angle_unit = angle_token.token().dimension_unit();
2618 auto angle_type = Angle::unit_from_name(angle_unit);
2619 if (!angle_type.has_value())
2620 return {};
2621
2622 from_angle = Angle(angle, *angle_type);
2623 got_from_angle = true;
2624 } else if (consume_identifier("at"sv)) {
2625 // at <position>
2626 if (got_at_position)
2627 return {};
2628 auto position = parse_position(tokens);
2629 if (!position.has_value())
2630 return {};
2631 at_position = *position;
2632 got_at_position = true;
2633 } else if (consume_identifier("in"sv)) {
2634 // <color-interpolation-method>
2635 if (got_color_interpolation_method)
2636 return {};
2637 dbgln("FIXME: Parse color interpolation method for conic-gradient()");
2638 got_color_interpolation_method = true;
2639 } else {
2640 break;
2641 }
2642 tokens.skip_whitespace();
2643 if (!tokens.has_next_token())
2644 return {};
2645 token = tokens.peek_token();
2646 }
2647
2648 tokens.skip_whitespace();
2649 if (!tokens.has_next_token())
2650 return {};
2651 if ((got_from_angle || got_at_position || got_color_interpolation_method) && !tokens.next_token().is(Token::Type::Comma))
2652 return {};
2653
2654 auto color_stops = parse_angular_color_stop_list(tokens);
2655 if (!color_stops.has_value())
2656 return {};
2657
2658 return ConicGradientStyleValue::create(from_angle, at_position, move(*color_stops), repeating_gradient);
2659}
2660
2661RefPtr<StyleValue> Parser::parse_radial_gradient_function(ComponentValue const& component_value)
2662{
2663 using EndingShape = RadialGradientStyleValue::EndingShape;
2664 using Extent = RadialGradientStyleValue::Extent;
2665 using CircleSize = RadialGradientStyleValue::CircleSize;
2666 using EllipseSize = RadialGradientStyleValue::EllipseSize;
2667 using Size = RadialGradientStyleValue::Size;
2668
2669 if (!component_value.is_function())
2670 return {};
2671
2672 auto repeating_gradient = GradientRepeating::No;
2673
2674 auto function_name = component_value.function().name();
2675
2676 function_name = consume_if_starts_with(function_name, "repeating-"sv, [&] {
2677 repeating_gradient = GradientRepeating::Yes;
2678 });
2679
2680 if (!function_name.equals_ignoring_ascii_case("radial-gradient"sv))
2681 return {};
2682
2683 TokenStream tokens { component_value.function().values() };
2684 tokens.skip_whitespace();
2685 if (!tokens.has_next_token())
2686 return {};
2687
2688 bool expect_comma = false;
2689
2690 auto commit_value = [&]<typename... T>(auto value, T&... transactions) {
2691 (transactions.commit(), ...);
2692 return value;
2693 };
2694
2695 // radial-gradient( [ <ending-shape> || <size> ]? [ at <position> ]? , <color-stop-list> )
2696
2697 Size size = Extent::FarthestCorner;
2698 EndingShape ending_shape = EndingShape::Circle;
2699 PositionValue at_position = PositionValue::center();
2700
2701 auto parse_ending_shape = [&]() -> Optional<EndingShape> {
2702 auto transaction = tokens.begin_transaction();
2703 tokens.skip_whitespace();
2704 auto& token = tokens.next_token();
2705 if (!token.is(Token::Type::Ident))
2706 return {};
2707 auto ident = token.token().ident();
2708 if (ident.equals_ignoring_ascii_case("circle"sv))
2709 return commit_value(EndingShape::Circle, transaction);
2710 if (ident.equals_ignoring_ascii_case("ellipse"sv))
2711 return commit_value(EndingShape::Ellipse, transaction);
2712 return {};
2713 };
2714
2715 auto parse_extent_keyword = [](StringView keyword) -> Optional<Extent> {
2716 if (keyword.equals_ignoring_ascii_case("closest-corner"sv))
2717 return Extent::ClosestCorner;
2718 if (keyword.equals_ignoring_ascii_case("closest-side"sv))
2719 return Extent::ClosestSide;
2720 if (keyword.equals_ignoring_ascii_case("farthest-corner"sv))
2721 return Extent::FarthestCorner;
2722 if (keyword.equals_ignoring_ascii_case("farthest-side"sv))
2723 return Extent::FarthestSide;
2724 return {};
2725 };
2726
2727 auto parse_size = [&]() -> Optional<Size> {
2728 // <size> =
2729 // <extent-keyword> |
2730 // <length [0,∞]> |
2731 // <length-percentage [0,∞]>{2}
2732 auto transaction_size = tokens.begin_transaction();
2733 tokens.skip_whitespace();
2734 if (!tokens.has_next_token())
2735 return {};
2736 auto& token = tokens.next_token();
2737 if (token.is(Token::Type::Ident)) {
2738 auto extent = parse_extent_keyword(token.token().ident());
2739 if (!extent.has_value())
2740 return {};
2741 return commit_value(*extent, transaction_size);
2742 }
2743 auto first_dimension = parse_dimension(token);
2744 if (!first_dimension.has_value())
2745 return {};
2746 if (!first_dimension->is_length_percentage())
2747 return {};
2748 auto transaction_second_dimension = tokens.begin_transaction();
2749 tokens.skip_whitespace();
2750 if (tokens.has_next_token()) {
2751 auto& second_token = tokens.next_token();
2752 auto second_dimension = parse_dimension(second_token);
2753 if (second_dimension.has_value() && second_dimension->is_length_percentage())
2754 return commit_value(EllipseSize { first_dimension->length_percentage(), second_dimension->length_percentage() },
2755 transaction_size, transaction_second_dimension);
2756 }
2757 if (first_dimension->is_length())
2758 return commit_value(CircleSize { first_dimension->length() }, transaction_size);
2759 return {};
2760 };
2761
2762 {
2763 // [ <ending-shape> || <size> ]?
2764 auto maybe_ending_shape = parse_ending_shape();
2765 auto maybe_size = parse_size();
2766 if (!maybe_ending_shape.has_value() && maybe_size.has_value())
2767 maybe_ending_shape = parse_ending_shape();
2768 if (maybe_size.has_value()) {
2769 size = *maybe_size;
2770 expect_comma = true;
2771 }
2772 if (maybe_ending_shape.has_value()) {
2773 expect_comma = true;
2774 ending_shape = *maybe_ending_shape;
2775 if (ending_shape == EndingShape::Circle && size.has<EllipseSize>())
2776 return {};
2777 if (ending_shape == EndingShape::Ellipse && size.has<CircleSize>())
2778 return {};
2779 } else {
2780 ending_shape = size.has<CircleSize>() ? EndingShape::Circle : EndingShape::Ellipse;
2781 }
2782 }
2783
2784 tokens.skip_whitespace();
2785 if (!tokens.has_next_token())
2786 return {};
2787
2788 auto& token = tokens.peek_token();
2789 if (token.is(Token::Type::Ident) && token.token().ident().equals_ignoring_ascii_case("at"sv)) {
2790 (void)tokens.next_token();
2791 auto position = parse_position(tokens);
2792 if (!position.has_value())
2793 return {};
2794 at_position = *position;
2795 expect_comma = true;
2796 }
2797
2798 tokens.skip_whitespace();
2799 if (!tokens.has_next_token())
2800 return {};
2801 if (expect_comma && !tokens.next_token().is(Token::Type::Comma))
2802 return {};
2803
2804 // <color-stop-list>
2805 auto color_stops = parse_linear_color_stop_list(tokens);
2806 if (!color_stops.has_value())
2807 return {};
2808
2809 return RadialGradientStyleValue::create(ending_shape, size, at_position, move(*color_stops), repeating_gradient);
2810}
2811
2812Optional<PositionValue> Parser::parse_position(TokenStream<ComponentValue>& tokens, PositionValue initial_value)
2813{
2814 auto transaction = tokens.begin_transaction();
2815 tokens.skip_whitespace();
2816 if (!tokens.has_next_token())
2817 return {};
2818
2819 auto parse_horizontal_preset = [&](auto ident) -> Optional<PositionValue::HorizontalPreset> {
2820 if (ident.equals_ignoring_ascii_case("left"sv))
2821 return PositionValue::HorizontalPreset::Left;
2822 if (ident.equals_ignoring_ascii_case("center"sv))
2823 return PositionValue::HorizontalPreset::Center;
2824 if (ident.equals_ignoring_ascii_case("right"sv))
2825 return PositionValue::HorizontalPreset::Right;
2826 return {};
2827 };
2828
2829 auto parse_vertical_preset = [&](auto ident) -> Optional<PositionValue::VerticalPreset> {
2830 if (ident.equals_ignoring_ascii_case("top"sv))
2831 return PositionValue::VerticalPreset::Top;
2832 if (ident.equals_ignoring_ascii_case("center"sv))
2833 return PositionValue::VerticalPreset::Center;
2834 if (ident.equals_ignoring_ascii_case("bottom"sv))
2835 return PositionValue::VerticalPreset::Bottom;
2836 return {};
2837 };
2838
2839 auto parse_horizontal_edge = [&](auto ident) -> Optional<PositionValue::HorizontalEdge> {
2840 if (ident.equals_ignoring_ascii_case("left"sv))
2841 return PositionValue::HorizontalEdge::Left;
2842 if (ident.equals_ignoring_ascii_case("right"sv))
2843 return PositionValue::HorizontalEdge::Right;
2844 return {};
2845 };
2846
2847 auto parse_vertical_edge = [&](auto ident) -> Optional<PositionValue::VerticalEdge> {
2848 if (ident.equals_ignoring_ascii_case("top"sv))
2849 return PositionValue::VerticalEdge::Top;
2850 if (ident.equals_ignoring_ascii_case("bottom"sv))
2851 return PositionValue::VerticalEdge::Bottom;
2852 return {};
2853 };
2854
2855 // <position> = [
2856 // [ left | center | right ] || [ top | center | bottom ]
2857 // |
2858 // [ left | center | right | <length-percentage> ]
2859 // [ top | center | bottom | <length-percentage> ]?
2860 // |
2861 // [ [ left | right ] <length-percentage> ] &&
2862 // [ [ top | bottom ] <length-percentage> ]
2863 // ]
2864
2865 // [ left | center | right ] || [ top | center | bottom ]
2866 auto alternation_1 = [&]() -> Optional<PositionValue> {
2867 auto transaction = tokens.begin_transaction();
2868 PositionValue position = initial_value;
2869 auto& first_token = tokens.next_token();
2870 if (!first_token.is(Token::Type::Ident))
2871 return {};
2872 auto ident = first_token.token().ident();
2873 // <horizontal-position> <vertical-position>?
2874 auto horizontal_position = parse_horizontal_preset(ident);
2875 if (horizontal_position.has_value()) {
2876 position.horizontal_position = *horizontal_position;
2877 auto transaction_optional_parse = tokens.begin_transaction();
2878 tokens.skip_whitespace();
2879 if (tokens.has_next_token()) {
2880 auto& second_token = tokens.next_token();
2881 if (second_token.is(Token::Type::Ident)) {
2882 auto vertical_position = parse_vertical_preset(second_token.token().ident());
2883 if (vertical_position.has_value()) {
2884 transaction_optional_parse.commit();
2885 position.vertical_position = *vertical_position;
2886 }
2887 }
2888 }
2889 } else {
2890 // <vertical-position> <horizontal-position>?
2891 auto vertical_position = parse_vertical_preset(ident);
2892 if (!vertical_position.has_value())
2893 return {};
2894 position.vertical_position = *vertical_position;
2895 auto transaction_optional_parse = tokens.begin_transaction();
2896 tokens.skip_whitespace();
2897 if (tokens.has_next_token()) {
2898 auto& second_token = tokens.next_token();
2899 if (second_token.is(Token::Type::Ident)) {
2900 auto horizontal_position = parse_horizontal_preset(second_token.token().ident());
2901 if (horizontal_position.has_value()) {
2902 transaction_optional_parse.commit();
2903 position.horizontal_position = *horizontal_position;
2904 }
2905 }
2906 }
2907 }
2908 transaction.commit();
2909 return position;
2910 };
2911
2912 // [ left | center | right | <length-percentage> ]
2913 // [ top | center | bottom | <length-percentage> ]?
2914 auto alternation_2 = [&]() -> Optional<PositionValue> {
2915 auto transaction = tokens.begin_transaction();
2916 PositionValue position = initial_value;
2917 auto& first_token = tokens.next_token();
2918 if (first_token.is(Token::Type::Ident)) {
2919 auto horizontal_position = parse_horizontal_preset(first_token.token().ident());
2920 if (!horizontal_position.has_value())
2921 return {};
2922 position.horizontal_position = *horizontal_position;
2923 } else {
2924 auto dimension = parse_dimension(first_token);
2925 if (!dimension.has_value() || !dimension->is_length_percentage())
2926 return {};
2927 position.horizontal_position = dimension->length_percentage();
2928 }
2929 auto transaction_optional_parse = tokens.begin_transaction();
2930 tokens.skip_whitespace();
2931 if (tokens.has_next_token()) {
2932 auto& second_token = tokens.next_token();
2933 if (second_token.is(Token::Type::Ident)) {
2934 auto vertical_position = parse_vertical_preset(second_token.token().ident());
2935 if (vertical_position.has_value()) {
2936 transaction_optional_parse.commit();
2937 position.vertical_position = *vertical_position;
2938 }
2939 } else {
2940 auto dimension = parse_dimension(second_token);
2941 if (dimension.has_value() && dimension->is_length_percentage()) {
2942 transaction_optional_parse.commit();
2943 position.vertical_position = dimension->length_percentage();
2944 }
2945 }
2946 }
2947 transaction.commit();
2948 return position;
2949 };
2950
2951 // [ [ left | right ] <length-percentage> ] &&
2952 // [ [ top | bottom ] <length-percentage> ]
2953 auto alternation_3 = [&]() -> Optional<PositionValue> {
2954 auto transaction = tokens.begin_transaction();
2955 PositionValue position {};
2956
2957 auto parse_horizontal = [&] {
2958 // [ left | right ] <length-percentage> ]
2959 auto transaction = tokens.begin_transaction();
2960 tokens.skip_whitespace();
2961 if (!tokens.has_next_token())
2962 return false;
2963 auto& first_token = tokens.next_token();
2964 if (!first_token.is(Token::Type::Ident))
2965 return false;
2966 auto horizontal_egde = parse_horizontal_edge(first_token.token().ident());
2967 if (!horizontal_egde.has_value())
2968 return false;
2969 position.x_relative_to = *horizontal_egde;
2970 tokens.skip_whitespace();
2971 if (!tokens.has_next_token())
2972 return false;
2973 auto& second_token = tokens.next_token();
2974 auto dimension = parse_dimension(second_token);
2975 if (!dimension.has_value() || !dimension->is_length_percentage())
2976 return false;
2977 position.horizontal_position = dimension->length_percentage();
2978 transaction.commit();
2979 return true;
2980 };
2981
2982 auto parse_vertical = [&] {
2983 // [ top | bottom ] <length-percentage> ]
2984 auto transaction = tokens.begin_transaction();
2985 tokens.skip_whitespace();
2986 if (!tokens.has_next_token())
2987 return false;
2988 auto& first_token = tokens.next_token();
2989 if (!first_token.is(Token::Type::Ident))
2990 return false;
2991 auto vertical_edge = parse_vertical_edge(first_token.token().ident());
2992 if (!vertical_edge.has_value())
2993 return false;
2994 position.y_relative_to = *vertical_edge;
2995 tokens.skip_whitespace();
2996 if (!tokens.has_next_token())
2997 return false;
2998 auto& second_token = tokens.next_token();
2999 auto dimension = parse_dimension(second_token);
3000 if (!dimension.has_value() || !dimension->is_length_percentage())
3001 return false;
3002 position.vertical_position = dimension->length_percentage();
3003 transaction.commit();
3004 return true;
3005 };
3006
3007 if ((parse_horizontal() && parse_vertical()) || (parse_vertical() && parse_horizontal())) {
3008 transaction.commit();
3009 return position;
3010 }
3011
3012 return {};
3013 };
3014
3015 // Note: The alternatives must be attempted in this order since `alternation_2' can match a prefix of `alternation_3'
3016 auto position = alternation_3();
3017 if (!position.has_value())
3018 position = alternation_2();
3019 if (!position.has_value())
3020 position = alternation_1();
3021 if (position.has_value())
3022 transaction.commit();
3023 return position;
3024}
3025
3026CSSRule* Parser::convert_to_rule(NonnullRefPtr<Rule> rule)
3027{
3028 if (rule->is_at_rule()) {
3029 if (has_ignored_vendor_prefix(rule->at_rule_name()))
3030 return {};
3031 if (rule->at_rule_name().equals_ignoring_ascii_case("font-face"sv)) {
3032 if (!rule->block() || !rule->block()->is_curly()) {
3033 dbgln_if(CSS_PARSER_DEBUG, "@font-face rule is malformed.");
3034 return {};
3035 }
3036 TokenStream tokens { rule->block()->values() };
3037 return parse_font_face_rule(tokens);
3038 }
3039 if (rule->at_rule_name().equals_ignoring_ascii_case("import"sv) && !rule->prelude().is_empty()) {
3040 Optional<AK::URL> url;
3041 for (auto const& token : rule->prelude()) {
3042 if (token.is(Token::Type::Whitespace))
3043 continue;
3044
3045 if (token.is(Token::Type::String)) {
3046 url = m_context.complete_url(token.token().string());
3047 } else {
3048 url = parse_url_function(token);
3049 }
3050
3051 // FIXME: Handle list of media queries. https://www.w3.org/TR/css-cascade-3/#conditional-import
3052 if (url.has_value())
3053 break;
3054 }
3055
3056 if (url.has_value())
3057 return CSSImportRule::create(url.value(), const_cast<DOM::Document&>(*m_context.document())).release_value_but_fixme_should_propagate_errors();
3058 dbgln_if(CSS_PARSER_DEBUG, "Unable to parse url from @import rule");
3059 return {};
3060 }
3061 if (rule->at_rule_name().equals_ignoring_ascii_case("media"sv)) {
3062 auto media_query_tokens = TokenStream { rule->prelude() };
3063 auto media_query_list = parse_a_media_query_list(media_query_tokens);
3064 if (media_query_list.is_empty() || !rule->block())
3065 return {};
3066
3067 auto child_tokens = TokenStream { rule->block()->values() };
3068 auto parser_rules = parse_a_list_of_rules(child_tokens);
3069 JS::MarkedVector<CSSRule*> child_rules(m_context.realm().heap());
3070 for (auto& raw_rule : parser_rules) {
3071 if (auto* child_rule = convert_to_rule(raw_rule))
3072 child_rules.append(child_rule);
3073 }
3074 auto media_list = MediaList::create(m_context.realm(), move(media_query_list)).release_value_but_fixme_should_propagate_errors();
3075 auto rule_list = CSSRuleList::create(m_context.realm(), child_rules).release_value_but_fixme_should_propagate_errors();
3076 return CSSMediaRule::create(m_context.realm(), media_list, rule_list).release_value_but_fixme_should_propagate_errors();
3077 }
3078 if (rule->at_rule_name().equals_ignoring_ascii_case("supports"sv)) {
3079 auto supports_tokens = TokenStream { rule->prelude() };
3080 auto supports = parse_a_supports(supports_tokens);
3081 if (!supports) {
3082 if constexpr (CSS_PARSER_DEBUG) {
3083 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @supports rule invalid; discarding.");
3084 supports_tokens.dump_all_tokens();
3085 }
3086 return {};
3087 }
3088
3089 if (!rule->block())
3090 return {};
3091 auto child_tokens = TokenStream { rule->block()->values() };
3092 auto parser_rules = parse_a_list_of_rules(child_tokens);
3093 JS::MarkedVector<CSSRule*> child_rules(m_context.realm().heap());
3094 for (auto& raw_rule : parser_rules) {
3095 if (auto* child_rule = convert_to_rule(raw_rule))
3096 child_rules.append(child_rule);
3097 }
3098
3099 auto rule_list = CSSRuleList::create(m_context.realm(), child_rules).release_value_but_fixme_should_propagate_errors();
3100 return CSSSupportsRule::create(m_context.realm(), supports.release_nonnull(), rule_list).release_value_but_fixme_should_propagate_errors();
3101 }
3102
3103 // FIXME: More at rules!
3104 dbgln_if(CSS_PARSER_DEBUG, "Unrecognized CSS at-rule: @{}", rule->at_rule_name());
3105 return {};
3106 }
3107
3108 auto prelude_stream = TokenStream(rule->prelude());
3109 auto selectors = parse_a_selector_list(prelude_stream, SelectorType::Standalone);
3110
3111 if (selectors.is_error()) {
3112 if (selectors.error() != ParseError::IncludesIgnoredVendorPrefix) {
3113 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule selectors invalid; discarding.");
3114 if constexpr (CSS_PARSER_DEBUG) {
3115 prelude_stream.dump_all_tokens();
3116 }
3117 }
3118 return {};
3119 }
3120
3121 if (selectors.value().is_empty()) {
3122 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: empty selector; discarding.");
3123 return {};
3124 }
3125
3126 if (!rule->block()->is_curly())
3127 return {};
3128
3129 auto stream = TokenStream(rule->block()->values());
3130 auto declarations_and_at_rules = parse_a_style_blocks_contents(stream);
3131
3132 auto* declaration = convert_to_style_declaration(declarations_and_at_rules);
3133 if (!declaration) {
3134 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule declaration invalid; discarding.");
3135 return {};
3136 }
3137
3138 return CSSStyleRule::create(m_context.realm(), move(selectors.value()), *declaration).release_value_but_fixme_should_propagate_errors();
3139}
3140
3141auto Parser::extract_properties(Vector<DeclarationOrAtRule> const& declarations_and_at_rules) -> PropertiesAndCustomProperties
3142{
3143 PropertiesAndCustomProperties result;
3144 for (auto const& declaration_or_at_rule : declarations_and_at_rules) {
3145 if (declaration_or_at_rule.is_at_rule()) {
3146 dbgln_if(CSS_PARSER_DEBUG, "!!! CSS at-rule is not allowed here!");
3147 continue;
3148 }
3149
3150 auto const& declaration = declaration_or_at_rule.declaration();
3151
3152 if (auto maybe_property = convert_to_style_property(declaration); maybe_property.has_value()) {
3153 auto property = maybe_property.release_value();
3154 if (property.property_id == PropertyID::Custom) {
3155 result.custom_properties.set(property.custom_name, property);
3156 } else {
3157 result.properties.append(move(property));
3158 }
3159 }
3160 }
3161 return result;
3162}
3163
3164PropertyOwningCSSStyleDeclaration* Parser::convert_to_style_declaration(Vector<DeclarationOrAtRule> const& declarations_and_at_rules)
3165{
3166 auto [properties, custom_properties] = extract_properties(declarations_and_at_rules);
3167 return PropertyOwningCSSStyleDeclaration::create(m_context.realm(), move(properties), move(custom_properties)).release_value_but_fixme_should_propagate_errors();
3168}
3169
3170Optional<StyleProperty> Parser::convert_to_style_property(Declaration const& declaration)
3171{
3172 auto property_name = declaration.name();
3173 auto property_id = property_id_from_string(property_name);
3174
3175 if (property_id == PropertyID::Invalid) {
3176 if (property_name.starts_with("--"sv)) {
3177 property_id = PropertyID::Custom;
3178 } else if (has_ignored_vendor_prefix(property_name)) {
3179 return {};
3180 } else if (!property_name.starts_with('-')) {
3181 dbgln_if(CSS_PARSER_DEBUG, "Unrecognized CSS property '{}'", property_name);
3182 return {};
3183 }
3184 }
3185
3186 auto value_token_stream = TokenStream(declaration.values());
3187 auto value = parse_css_value(property_id, value_token_stream);
3188 if (value.is_error()) {
3189 if (value.error() != ParseError::IncludesIgnoredVendorPrefix) {
3190 dbgln_if(CSS_PARSER_DEBUG, "Unable to parse value for CSS property '{}'.", property_name);
3191 if constexpr (CSS_PARSER_DEBUG) {
3192 value_token_stream.dump_all_tokens();
3193 }
3194 }
3195 return {};
3196 }
3197
3198 if (property_id == PropertyID::Custom)
3199 return StyleProperty { declaration.importance(), property_id, value.release_value(), declaration.name() };
3200
3201 return StyleProperty { declaration.importance(), property_id, value.release_value(), {} };
3202}
3203
3204RefPtr<StyleValue> Parser::parse_builtin_value(ComponentValue const& component_value)
3205{
3206 if (component_value.is(Token::Type::Ident)) {
3207 auto ident = component_value.token().ident();
3208 if (ident.equals_ignoring_ascii_case("inherit"sv))
3209 return InheritStyleValue::the();
3210 if (ident.equals_ignoring_ascii_case("initial"sv))
3211 return InitialStyleValue::the();
3212 if (ident.equals_ignoring_ascii_case("unset"sv))
3213 return UnsetStyleValue::the();
3214 // FIXME: Implement `revert` and `revert-layer` keywords, from Cascade4 and Cascade5 respectively
3215 }
3216
3217 return {};
3218}
3219
3220RefPtr<CalculatedStyleValue> Parser::parse_calculated_value(Vector<ComponentValue> const& component_values)
3221{
3222 auto calc_expression = parse_calc_expression(component_values);
3223 if (calc_expression == nullptr)
3224 return nullptr;
3225
3226 auto calc_type = calc_expression->resolved_type();
3227 if (!calc_type.has_value()) {
3228 dbgln_if(CSS_PARSER_DEBUG, "calc() resolved as invalid!!!");
3229 return nullptr;
3230 }
3231
3232 [[maybe_unused]] auto to_string = [](CalculatedStyleValue::ResolvedType type) {
3233 switch (type) {
3234 case CalculatedStyleValue::ResolvedType::Angle:
3235 return "Angle"sv;
3236 case CalculatedStyleValue::ResolvedType::Frequency:
3237 return "Frequency"sv;
3238 case CalculatedStyleValue::ResolvedType::Integer:
3239 return "Integer"sv;
3240 case CalculatedStyleValue::ResolvedType::Length:
3241 return "Length"sv;
3242 case CalculatedStyleValue::ResolvedType::Number:
3243 return "Number"sv;
3244 case CalculatedStyleValue::ResolvedType::Percentage:
3245 return "Percentage"sv;
3246 case CalculatedStyleValue::ResolvedType::Time:
3247 return "Time"sv;
3248 }
3249 VERIFY_NOT_REACHED();
3250 };
3251 dbgln_if(CSS_PARSER_DEBUG, "Deduced calc() resolved type as: {}", to_string(calc_type.value()));
3252
3253 return CalculatedStyleValue::create(calc_expression.release_nonnull(), calc_type.release_value());
3254}
3255
3256RefPtr<StyleValue> Parser::parse_dynamic_value(ComponentValue const& component_value)
3257{
3258 if (component_value.is_function()) {
3259 auto const& function = component_value.function();
3260
3261 if (function.name().equals_ignoring_ascii_case("calc"sv))
3262 return parse_calculated_value(function.values());
3263
3264 if (function.name().equals_ignoring_ascii_case("var"sv)) {
3265 // Declarations using `var()` should already be parsed as an UnresolvedStyleValue before this point.
3266 VERIFY_NOT_REACHED();
3267 }
3268 }
3269
3270 return {};
3271}
3272
3273Optional<Parser::Dimension> Parser::parse_dimension(ComponentValue const& component_value)
3274{
3275 if (component_value.is(Token::Type::Dimension)) {
3276 float numeric_value = component_value.token().dimension_value();
3277 auto unit_string = component_value.token().dimension_unit();
3278
3279 if (auto length_type = Length::unit_from_name(unit_string); length_type.has_value())
3280 return Length { numeric_value, length_type.release_value() };
3281
3282 if (auto angle_type = Angle::unit_from_name(unit_string); angle_type.has_value())
3283 return Angle { numeric_value, angle_type.release_value() };
3284
3285 if (auto frequency_type = Frequency::unit_from_name(unit_string); frequency_type.has_value())
3286 return Frequency { numeric_value, frequency_type.release_value() };
3287
3288 if (auto resolution_type = Resolution::unit_from_name(unit_string); resolution_type.has_value())
3289 return Resolution { numeric_value, resolution_type.release_value() };
3290
3291 if (auto time_type = Time::unit_from_name(unit_string); time_type.has_value())
3292 return Time { numeric_value, time_type.release_value() };
3293 }
3294
3295 if (component_value.is(Token::Type::Percentage))
3296 return Percentage { static_cast<float>(component_value.token().percentage()) };
3297
3298 if (component_value.is(Token::Type::Number)) {
3299 float numeric_value = component_value.token().number_value();
3300 if (numeric_value == 0)
3301 return Length::make_px(0);
3302 if (m_context.in_quirks_mode() && property_has_quirk(m_context.current_property_id(), Quirk::UnitlessLength)) {
3303 // https://quirks.spec.whatwg.org/#quirky-length-value
3304 // FIXME: Disallow quirk when inside a CSS sub-expression (like `calc()`)
3305 // "The <quirky-length> value must not be supported in arguments to CSS expressions other than the rect()
3306 // expression, and must not be supported in the supports() static method of the CSS interface."
3307 return Length::make_px(numeric_value);
3308 }
3309 }
3310
3311 return {};
3312}
3313
3314Optional<Length> Parser::parse_length(ComponentValue const& component_value)
3315{
3316 auto dimension = parse_dimension(component_value);
3317 if (!dimension.has_value())
3318 return {};
3319
3320 if (dimension->is_length())
3321 return dimension->length();
3322
3323 // FIXME: auto isn't a length!
3324 if (component_value.is(Token::Type::Ident) && component_value.token().ident().equals_ignoring_ascii_case("auto"sv))
3325 return Length::make_auto();
3326
3327 return {};
3328}
3329
3330Optional<Ratio> Parser::parse_ratio(TokenStream<ComponentValue>& tokens)
3331{
3332 auto transaction = tokens.begin_transaction();
3333 tokens.skip_whitespace();
3334
3335 // `<ratio> = <number [0,∞]> [ / <number [0,∞]> ]?`
3336 // FIXME: I think either part is allowed to be calc(), which makes everything complicated.
3337 auto first_number = tokens.next_token();
3338 if (!first_number.is(Token::Type::Number) || first_number.token().number_value() < 0)
3339 return {};
3340
3341 {
3342 auto two_value_transaction = tokens.begin_transaction();
3343 tokens.skip_whitespace();
3344 auto solidus = tokens.next_token();
3345 tokens.skip_whitespace();
3346 auto second_number = tokens.next_token();
3347 if (solidus.is(Token::Type::Delim) && solidus.token().delim() == '/'
3348 && second_number.is(Token::Type::Number) && second_number.token().number_value() > 0) {
3349 // Two-value ratio
3350 two_value_transaction.commit();
3351 transaction.commit();
3352 return Ratio { static_cast<float>(first_number.token().number_value()), static_cast<float>(second_number.token().number_value()) };
3353 }
3354 }
3355
3356 // Single-value ratio
3357 transaction.commit();
3358 return Ratio { static_cast<float>(first_number.token().number_value()) };
3359}
3360
3361// https://www.w3.org/TR/css-syntax-3/#urange-syntax
3362Optional<UnicodeRange> Parser::parse_unicode_range(TokenStream<ComponentValue>& tokens)
3363{
3364 auto transaction = tokens.begin_transaction();
3365 tokens.skip_whitespace();
3366
3367 // <urange> =
3368 // u '+' <ident-token> '?'* |
3369 // u <dimension-token> '?'* |
3370 // u <number-token> '?'* |
3371 // u <number-token> <dimension-token> |
3372 // u <number-token> <number-token> |
3373 // u '+' '?'+
3374 // (All with no whitespace in between tokens.)
3375
3376 // NOTE: Parsing this is different from usual. We take these steps:
3377 // 1. Match the grammar above against the tokens, concatenating them into a string using their original representation.
3378 // 2. Then, parse that string according to the spec algorithm.
3379 // Step 2 is performed by calling the other parse_unicode_range() overload.
3380
3381 auto is_question_mark = [](ComponentValue const& component_value) {
3382 return component_value.is(Token::Type::Delim) && component_value.token().delim() == '?';
3383 };
3384
3385 auto is_ending_token = [](ComponentValue const& component_value) {
3386 return component_value.is(Token::Type::EndOfFile)
3387 || component_value.is(Token::Type::Comma)
3388 || component_value.is(Token::Type::Semicolon)
3389 || component_value.is(Token::Type::Whitespace);
3390 };
3391
3392 auto representation_of = [](ComponentValue const& component_value) {
3393 // FIXME: This should use the "representation", that is, the original text that produced the token.
3394 // See: https://www.w3.org/TR/css-syntax-3/#representation
3395 // We don't have a way to get that, so instead, we're relying on Token::to_string(), and
3396 // handling specific cases where that's not enough.
3397 // Integers like `+34` get serialized as `34`, so manually include the `+` sign.
3398 if (component_value.is(Token::Type::Number) && component_value.token().number().is_integer_with_explicit_sign()) {
3399 auto int_value = component_value.token().number().integer_value();
3400 return DeprecatedString::formatted("{:+}", int_value);
3401 }
3402
3403 return component_value.to_string().release_value_but_fixme_should_propagate_errors().to_deprecated_string();
3404 };
3405
3406 auto create_unicode_range = [&](StringView text, auto& local_transaction) -> Optional<UnicodeRange> {
3407 auto maybe_unicode_range = parse_unicode_range(text);
3408 if (maybe_unicode_range.has_value()) {
3409 local_transaction.commit();
3410 transaction.commit();
3411 }
3412 return maybe_unicode_range;
3413 };
3414
3415 // All options start with 'u'/'U'.
3416 auto const& u = tokens.next_token();
3417 if (!(u.is(Token::Type::Ident) && u.token().ident().equals_ignoring_ascii_case("u"sv))) {
3418 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> does not start with 'u'");
3419 return {};
3420 }
3421
3422 auto const& second_token = tokens.next_token();
3423
3424 // u '+' <ident-token> '?'* |
3425 // u '+' '?'+
3426 if (second_token.is(Token::Type::Delim) && second_token.token().delim() == '+') {
3427 auto local_transaction = tokens.begin_transaction();
3428 StringBuilder string_builder;
3429 string_builder.append(representation_of(second_token));
3430
3431 auto const& third_token = tokens.next_token();
3432 if (third_token.is(Token::Type::Ident) || is_question_mark(third_token)) {
3433 string_builder.append(representation_of(third_token));
3434 while (is_question_mark(tokens.peek_token()))
3435 string_builder.append(representation_of(tokens.next_token()));
3436 if (is_ending_token(tokens.peek_token()))
3437 return create_unicode_range(string_builder.string_view(), local_transaction);
3438 }
3439 }
3440
3441 // u <dimension-token> '?'*
3442 if (second_token.is(Token::Type::Dimension)) {
3443 auto local_transaction = tokens.begin_transaction();
3444 StringBuilder string_builder;
3445 string_builder.append(representation_of(second_token));
3446 while (is_question_mark(tokens.peek_token()))
3447 string_builder.append(representation_of(tokens.next_token()));
3448 if (is_ending_token(tokens.peek_token()))
3449 return create_unicode_range(string_builder.string_view(), local_transaction);
3450 }
3451
3452 // u <number-token> '?'* |
3453 // u <number-token> <dimension-token> |
3454 // u <number-token> <number-token>
3455 if (second_token.is(Token::Type::Number)) {
3456 auto local_transaction = tokens.begin_transaction();
3457 StringBuilder string_builder;
3458 string_builder.append(representation_of(second_token));
3459
3460 if (is_ending_token(tokens.peek_token()))
3461 return create_unicode_range(string_builder.string_view(), local_transaction);
3462
3463 auto const& third_token = tokens.next_token();
3464 string_builder.append(representation_of(third_token));
3465 if (is_question_mark(third_token)) {
3466 while (is_question_mark(tokens.peek_token()))
3467 string_builder.append(representation_of(tokens.next_token()));
3468 if (is_ending_token(tokens.peek_token()))
3469 return create_unicode_range(string_builder.string_view(), local_transaction);
3470 } else if (third_token.is(Token::Type::Dimension)) {
3471 if (is_ending_token(tokens.peek_token()))
3472 return create_unicode_range(string_builder.string_view(), local_transaction);
3473 } else if (third_token.is(Token::Type::Number)) {
3474 if (is_ending_token(tokens.peek_token()))
3475 return create_unicode_range(string_builder.string_view(), local_transaction);
3476 }
3477 }
3478
3479 if constexpr (CSS_PARSER_DEBUG) {
3480 dbgln("CSSParser: Tokens did not match <urange> grammar.");
3481 tokens.dump_all_tokens();
3482 }
3483 return {};
3484}
3485
3486Optional<UnicodeRange> Parser::parse_unicode_range(StringView text)
3487{
3488 auto make_valid_unicode_range = [&](u32 start_value, u32 end_value) -> Optional<UnicodeRange> {
3489 // https://www.w3.org/TR/css-syntax-3/#maximum-allowed-code-point
3490 constexpr u32 maximum_allowed_code_point = 0x10FFFF;
3491
3492 // To determine what codepoints the <urange> represents:
3493 // 1. If end value is greater than the maximum allowed code point,
3494 // the <urange> is invalid and a syntax error.
3495 if (end_value > maximum_allowed_code_point) {
3496 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Invalid <urange>: end_value ({}) > maximum ({})", end_value, maximum_allowed_code_point);
3497 return {};
3498 }
3499
3500 // 2. If start value is greater than end value, the <urange> is invalid and a syntax error.
3501 if (start_value > end_value) {
3502 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Invalid <urange>: start_value ({}) > end_value ({})", start_value, end_value);
3503 return {};
3504 }
3505
3506 // 3. Otherwise, the <urange> represents a contiguous range of codepoints from start value to end value, inclusive.
3507 return UnicodeRange { start_value, end_value };
3508 };
3509
3510 // 1. Skipping the first u token, concatenate the representations of all the tokens in the production together.
3511 // Let this be text.
3512 // NOTE: The concatenation is already done by the caller.
3513 GenericLexer lexer { text };
3514
3515 // 2. If the first character of text is U+002B PLUS SIGN, consume it.
3516 // Otherwise, this is an invalid <urange>, and this algorithm must exit.
3517 if (lexer.next_is('+')) {
3518 lexer.consume();
3519 } else {
3520 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Second character of <urange> was not '+'; got: '{}'", lexer.consume());
3521 return {};
3522 }
3523
3524 // 3. Consume as many hex digits from text as possible.
3525 // then consume as many U+003F QUESTION MARK (?) code points as possible.
3526 auto hex_digits = lexer.consume_while(is_ascii_hex_digit);
3527 auto question_marks = lexer.consume_while([](auto it) { return it == '?'; });
3528 // If zero code points were consumed, or more than six code points were consumed,
3529 // this is an invalid <urange>, and this algorithm must exit.
3530 size_t consumed_code_points = hex_digits.length() + question_marks.length();
3531 if (consumed_code_points == 0 || consumed_code_points > 6) {
3532 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> start value had {} digits/?s, expected between 1 and 6.", consumed_code_points);
3533 return {};
3534 }
3535 StringView start_value_code_points { hex_digits.characters_without_null_termination(), consumed_code_points };
3536
3537 // If any U+003F QUESTION MARK (?) code points were consumed, then:
3538 if (question_marks.length() > 0) {
3539 // 1. If there are any code points left in text, this is an invalid <urange>,
3540 // and this algorithm must exit.
3541 if (lexer.tell_remaining() != 0) {
3542 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> invalid; had {} code points left over.", lexer.tell_remaining());
3543 return {};
3544 }
3545
3546 // 2. Interpret the consumed code points as a hexadecimal number,
3547 // with the U+003F QUESTION MARK (?) code points replaced by U+0030 DIGIT ZERO (0) code points.
3548 // This is the start value.
3549 auto start_value_string = start_value_code_points.replace("?"sv, "0"sv, ReplaceMode::All);
3550 auto maybe_start_value = AK::StringUtils::convert_to_uint_from_hex<u32>(start_value_string);
3551 if (!maybe_start_value.has_value()) {
3552 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> ?-converted start value did not parse as hex number.");
3553 return {};
3554 }
3555 u32 start_value = maybe_start_value.release_value();
3556
3557 // 3. Interpret the consumed code points as a hexadecimal number again,
3558 // with the U+003F QUESTION MARK (?) code points replaced by U+0046 LATIN CAPITAL LETTER F (F) code points.
3559 // This is the end value.
3560 auto end_value_string = start_value_code_points.replace("?"sv, "F"sv, ReplaceMode::All);
3561 auto maybe_end_value = AK::StringUtils::convert_to_uint_from_hex<u32>(end_value_string);
3562 if (!maybe_end_value.has_value()) {
3563 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> ?-converted end value did not parse as hex number.");
3564 return {};
3565 }
3566 u32 end_value = maybe_end_value.release_value();
3567
3568 // 4. Exit this algorithm.
3569 return make_valid_unicode_range(start_value, end_value);
3570 }
3571 // Otherwise, interpret the consumed code points as a hexadecimal number. This is the start value.
3572 auto maybe_start_value = AK::StringUtils::convert_to_uint_from_hex<u32>(start_value_code_points);
3573 if (!maybe_start_value.has_value()) {
3574 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> start value did not parse as hex number.");
3575 return {};
3576 }
3577 u32 start_value = maybe_start_value.release_value();
3578
3579 // 4. If there are no code points left in text, The end value is the same as the start value.
3580 // Exit this algorithm.
3581 if (lexer.tell_remaining() == 0)
3582 return make_valid_unicode_range(start_value, start_value);
3583
3584 // 5. If the next code point in text is U+002D HYPHEN-MINUS (-), consume it.
3585 if (lexer.next_is('-')) {
3586 lexer.consume();
3587 }
3588 // Otherwise, this is an invalid <urange>, and this algorithm must exit.
3589 else {
3590 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> start and end values not separated by '-'.");
3591 return {};
3592 }
3593
3594 // 6. Consume as many hex digits as possible from text.
3595 auto end_hex_digits = lexer.consume_while(is_ascii_hex_digit);
3596
3597 // If zero hex digits were consumed, or more than 6 hex digits were consumed,
3598 // this is an invalid <urange>, and this algorithm must exit.
3599 if (end_hex_digits.length() == 0 || end_hex_digits.length() > 6) {
3600 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> end value had {} digits, expected between 1 and 6.", end_hex_digits.length());
3601 return {};
3602 }
3603
3604 // If there are any code points left in text, this is an invalid <urange>, and this algorithm must exit.
3605 if (lexer.tell_remaining() != 0) {
3606 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> invalid; had {} code points left over.", lexer.tell_remaining());
3607 return {};
3608 }
3609
3610 // 7. Interpret the consumed code points as a hexadecimal number. This is the end value.
3611 auto maybe_end_value = AK::StringUtils::convert_to_uint_from_hex<u32>(end_hex_digits);
3612 if (!maybe_end_value.has_value()) {
3613 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: <urange> end value did not parse as hex number.");
3614 return {};
3615 }
3616 u32 end_value = maybe_end_value.release_value();
3617
3618 return make_valid_unicode_range(start_value, end_value);
3619}
3620
3621RefPtr<StyleValue> Parser::parse_dimension_value(ComponentValue const& component_value)
3622{
3623 // Numbers with no units can be lengths, in two situations:
3624 // 1) We're in quirks mode, and it's an integer.
3625 // 2) It's a 0.
3626 // We handle case 1 here. Case 2 is handled by NumericStyleValue pretending to be a LengthStyleValue if it is 0.
3627
3628 if (component_value.is(Token::Type::Number) && !(m_context.in_quirks_mode() && property_has_quirk(m_context.current_property_id(), Quirk::UnitlessLength)))
3629 return {};
3630
3631 if (component_value.is(Token::Type::Ident) && component_value.token().ident().equals_ignoring_ascii_case("auto"sv))
3632 return LengthStyleValue::create(Length::make_auto());
3633
3634 auto dimension = parse_dimension(component_value);
3635 if (!dimension.has_value())
3636 return {};
3637
3638 if (dimension->is_angle())
3639 return AngleStyleValue::create(dimension->angle());
3640 if (dimension->is_frequency())
3641 return FrequencyStyleValue::create(dimension->frequency());
3642 if (dimension->is_length())
3643 return LengthStyleValue::create(dimension->length());
3644 if (dimension->is_percentage())
3645 return PercentageStyleValue::create(dimension->percentage());
3646 if (dimension->is_resolution())
3647 return ResolutionStyleValue::create(dimension->resolution());
3648 if (dimension->is_time())
3649 return TimeStyleValue::create(dimension->time());
3650 VERIFY_NOT_REACHED();
3651}
3652
3653RefPtr<StyleValue> Parser::parse_numeric_value(ComponentValue const& component_value)
3654{
3655 if (component_value.is(Token::Type::Number)) {
3656 auto const& number = component_value.token();
3657 if (number.number().is_integer())
3658 return NumericStyleValue::create_integer(number.to_integer());
3659 return NumericStyleValue::create_float(number.number_value());
3660 }
3661
3662 return {};
3663}
3664
3665RefPtr<StyleValue> Parser::parse_identifier_value(ComponentValue const& component_value)
3666{
3667 if (component_value.is(Token::Type::Ident)) {
3668 auto value_id = value_id_from_string(component_value.token().ident());
3669 if (value_id != ValueID::Invalid)
3670 return IdentifierStyleValue::create(value_id);
3671 }
3672
3673 return {};
3674}
3675
3676Optional<Color> Parser::parse_rgb_or_hsl_color(StringView function_name, Vector<ComponentValue> const& component_values)
3677{
3678 Token params[4];
3679 bool legacy_syntax = false;
3680 auto tokens = TokenStream { component_values };
3681
3682 tokens.skip_whitespace();
3683 auto const& component1 = tokens.next_token();
3684
3685 if (!component1.is(Token::Type::Number)
3686 && !component1.is(Token::Type::Percentage)
3687 && !component1.is(Token::Type::Dimension))
3688 return {};
3689 params[0] = component1.token();
3690
3691 tokens.skip_whitespace();
3692 if (tokens.peek_token().is(Token::Type::Comma)) {
3693 legacy_syntax = true;
3694 tokens.next_token();
3695 }
3696
3697 tokens.skip_whitespace();
3698 auto const& component2 = tokens.next_token();
3699 if (!component2.is(Token::Type::Number) && !component2.is(Token::Type::Percentage))
3700 return {};
3701 params[1] = component2.token();
3702
3703 tokens.skip_whitespace();
3704 if (legacy_syntax && !tokens.next_token().is(Token::Type::Comma))
3705 return {};
3706
3707 tokens.skip_whitespace();
3708 auto const& component3 = tokens.next_token();
3709 if (!component3.is(Token::Type::Number) && !component3.is(Token::Type::Percentage))
3710 return {};
3711 params[2] = component3.token();
3712
3713 tokens.skip_whitespace();
3714 auto const& alpha_separator = tokens.peek_token();
3715 bool has_comma = alpha_separator.is(Token::Type::Comma);
3716 bool has_slash = alpha_separator.is(Token::Type::Delim) && alpha_separator.token().delim() == '/';
3717 if (legacy_syntax ? has_comma : has_slash) {
3718 tokens.next_token();
3719
3720 tokens.skip_whitespace();
3721 auto const& component4 = tokens.next_token();
3722 if (!component4.is(Token::Type::Number) && !component4.is(Token::Type::Percentage))
3723 return {};
3724 params[3] = component4.token();
3725 }
3726
3727 tokens.skip_whitespace();
3728 if (tokens.has_next_token())
3729 return {};
3730
3731 if (function_name.equals_ignoring_ascii_case("rgb"sv)
3732 || function_name.equals_ignoring_ascii_case("rgba"sv)) {
3733
3734 // https://www.w3.org/TR/css-color-4/#rgb-functions
3735
3736 u8 a_val = 255;
3737 if (params[3].is(Token::Type::Number))
3738 a_val = clamp(lroundf(params[3].number_value() * 255.0f), 0, 255);
3739 else if (params[3].is(Token::Type::Percentage))
3740 a_val = clamp(lroundf(params[3].percentage() * 2.55f), 0, 255);
3741
3742 if (params[0].is(Token::Type::Number)
3743 && params[1].is(Token::Type::Number)
3744 && params[2].is(Token::Type::Number)) {
3745
3746 u8 r_val = clamp(llroundf(params[0].number_value()), 0, 255);
3747 u8 g_val = clamp(llroundf(params[1].number_value()), 0, 255);
3748 u8 b_val = clamp(llroundf(params[2].number_value()), 0, 255);
3749
3750 return Color(r_val, g_val, b_val, a_val);
3751 }
3752
3753 if (params[0].is(Token::Type::Percentage)
3754 && params[1].is(Token::Type::Percentage)
3755 && params[2].is(Token::Type::Percentage)) {
3756
3757 u8 r_val = lroundf(clamp(params[0].percentage() * 2.55f, 0, 255));
3758 u8 g_val = lroundf(clamp(params[1].percentage() * 2.55f, 0, 255));
3759 u8 b_val = lroundf(clamp(params[2].percentage() * 2.55f, 0, 255));
3760
3761 return Color(r_val, g_val, b_val, a_val);
3762 }
3763 } else if (function_name.equals_ignoring_ascii_case("hsl"sv)
3764 || function_name.equals_ignoring_ascii_case("hsla"sv)) {
3765
3766 // https://www.w3.org/TR/css-color-4/#the-hsl-notation
3767
3768 float a_val = 1.0f;
3769 if (params[3].is(Token::Type::Number))
3770 a_val = params[3].number_value();
3771 else if (params[3].is(Token::Type::Percentage))
3772 a_val = params[3].percentage() / 100.0f;
3773
3774 if (params[0].is(Token::Type::Dimension)
3775 && params[1].is(Token::Type::Percentage)
3776 && params[2].is(Token::Type::Percentage)) {
3777
3778 float numeric_value = params[0].dimension_value();
3779 auto unit_string = params[0].dimension_unit();
3780 auto angle_type = Angle::unit_from_name(unit_string);
3781
3782 if (!angle_type.has_value())
3783 return {};
3784
3785 auto angle = Angle { numeric_value, angle_type.release_value() };
3786
3787 float h_val = fmodf(angle.to_degrees(), 360.0f);
3788 float s_val = params[1].percentage() / 100.0f;
3789 float l_val = params[2].percentage() / 100.0f;
3790
3791 return Color::from_hsla(h_val, s_val, l_val, a_val);
3792 }
3793
3794 if (params[0].is(Token::Type::Number)
3795 && params[1].is(Token::Type::Percentage)
3796 && params[2].is(Token::Type::Percentage)) {
3797
3798 float h_val = fmodf(params[0].number_value(), 360.0f);
3799 float s_val = params[1].percentage() / 100.0f;
3800 float l_val = params[2].percentage() / 100.0f;
3801
3802 return Color::from_hsla(h_val, s_val, l_val, a_val);
3803 }
3804 }
3805
3806 return {};
3807}
3808
3809// https://www.w3.org/TR/CSS2/visufx.html#value-def-shape
3810RefPtr<StyleValue> Parser::parse_rect_value(ComponentValue const& component_value)
3811{
3812 if (!component_value.is_function())
3813 return {};
3814 auto const& function = component_value.function();
3815 if (!function.name().equals_ignoring_ascii_case("rect"sv))
3816 return {};
3817
3818 Vector<Length, 4> params;
3819 auto tokens = TokenStream { function.values() };
3820
3821 enum class CommaRequirement {
3822 Unknown,
3823 RequiresCommas,
3824 RequiresNoCommas
3825 };
3826
3827 enum class Side {
3828 Top = 0,
3829 Right = 1,
3830 Bottom = 2,
3831 Left = 3
3832 };
3833
3834 auto comma_requirement = CommaRequirement::Unknown;
3835
3836 // In CSS 2.1, the only valid <shape> value is: rect(<top>, <right>, <bottom>, <left>) where
3837 // <top> and <bottom> specify offsets from the top border edge of the box, and <right>, and
3838 // <left> specify offsets from the left border edge of the box.
3839 for (size_t side = 0; side < 4; side++) {
3840 tokens.skip_whitespace();
3841
3842 // <top>, <right>, <bottom>, and <left> may either have a <length> value or 'auto'.
3843 // Negative lengths are permitted.
3844 auto current_token = tokens.next_token().token();
3845 if (current_token.is(Token::Type::Ident) && current_token.ident().equals_ignoring_ascii_case("auto"sv)) {
3846 params.append(Length::make_auto());
3847 } else {
3848 auto maybe_length = parse_length(current_token);
3849 if (!maybe_length.has_value())
3850 return {};
3851 params.append(maybe_length.value());
3852 }
3853 tokens.skip_whitespace();
3854
3855 // The last side, should be no more tokens following it.
3856 if (static_cast<Side>(side) == Side::Left) {
3857 if (tokens.has_next_token())
3858 return {};
3859 break;
3860 }
3861
3862 bool next_is_comma = tokens.peek_token().is(Token::Type::Comma);
3863
3864 // Authors should separate offset values with commas. User agents must support separation
3865 // with commas, but may also support separation without commas (but not a combination),
3866 // because a previous revision of this specification was ambiguous in this respect.
3867 if (comma_requirement == CommaRequirement::Unknown)
3868 comma_requirement = next_is_comma ? CommaRequirement::RequiresCommas : CommaRequirement::RequiresNoCommas;
3869
3870 if (comma_requirement == CommaRequirement::RequiresCommas) {
3871 if (next_is_comma)
3872 tokens.next_token();
3873 else
3874 return {};
3875 } else if (comma_requirement == CommaRequirement::RequiresNoCommas) {
3876 if (next_is_comma)
3877 return {};
3878 } else {
3879 VERIFY_NOT_REACHED();
3880 }
3881 }
3882
3883 return RectStyleValue::create(EdgeRect { params[0], params[1], params[2], params[3] });
3884}
3885
3886Optional<Color> Parser::parse_color(ComponentValue const& component_value)
3887{
3888 // https://www.w3.org/TR/css-color-4/
3889 if (component_value.is(Token::Type::Ident)) {
3890 auto ident = component_value.token().ident();
3891
3892 auto color = Color::from_string(ident);
3893 if (color.has_value())
3894 return color;
3895
3896 } else if (component_value.is(Token::Type::Hash)) {
3897 auto color = Color::from_string(DeprecatedString::formatted("#{}", component_value.token().hash_value()));
3898 if (color.has_value())
3899 return color;
3900 return {};
3901
3902 } else if (component_value.is_function()) {
3903 auto const& function = component_value.function();
3904 auto const& values = function.values();
3905
3906 return parse_rgb_or_hsl_color(function.name(), values);
3907 }
3908
3909 // https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk
3910 if (m_context.in_quirks_mode() && property_has_quirk(m_context.current_property_id(), Quirk::HashlessHexColor)) {
3911 // The value of a quirky color is obtained from the possible component values using the following algorithm,
3912 // aborting on the first step that returns a value:
3913
3914 // 1. Let cv be the component value.
3915 auto const& cv = component_value;
3916 DeprecatedString serialization;
3917 // 2. If cv is a <number-token> or a <dimension-token>, follow these substeps:
3918 if (cv.is(Token::Type::Number) || cv.is(Token::Type::Dimension)) {
3919 // 1. If cv’s type flag is not "integer", return an error.
3920 // This means that values that happen to use scientific notation, e.g., 5e5e5e, will fail to parse.
3921 if (!cv.token().number().is_integer())
3922 return {};
3923
3924 // 2. If cv’s value is less than zero, return an error.
3925 auto value = cv.is(Token::Type::Number) ? cv.token().to_integer() : cv.token().dimension_value_int();
3926 if (value < 0)
3927 return {};
3928
3929 // 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.
3930 StringBuilder serialization_builder;
3931 serialization_builder.appendff("{}", value);
3932
3933 // 4. If cv is a <dimension-token>, append the unit to serialization.
3934 if (cv.is(Token::Type::Dimension))
3935 serialization_builder.append(cv.token().dimension_unit());
3936
3937 // 5. If serialization consists of fewer than six characters, prepend zeros (U+0030) so that it becomes six characters.
3938 serialization = serialization_builder.to_deprecated_string();
3939 if (serialization_builder.length() < 6) {
3940 StringBuilder builder;
3941 for (size_t i = 0; i < (6 - serialization_builder.length()); i++)
3942 builder.append('0');
3943 builder.append(serialization_builder.string_view());
3944 serialization = builder.to_deprecated_string();
3945 }
3946 }
3947 // 3. Otherwise, cv is an <ident-token>; let serialization be cv’s value.
3948 else {
3949 if (!cv.is(Token::Type::Ident))
3950 return {};
3951 serialization = cv.token().ident();
3952 }
3953
3954 // 4. If serialization does not consist of three or six characters, return an error.
3955 if (serialization.length() != 3 && serialization.length() != 6)
3956 return {};
3957
3958 // 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.
3959 for (auto c : serialization) {
3960 if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')))
3961 return {};
3962 }
3963
3964 // 6. Return the concatenation of "#" (U+0023) and serialization.
3965 DeprecatedString concatenation = DeprecatedString::formatted("#{}", serialization);
3966 return Color::from_string(concatenation);
3967 }
3968
3969 return {};
3970}
3971
3972RefPtr<StyleValue> Parser::parse_color_value(ComponentValue const& component_value)
3973{
3974 auto color = parse_color(component_value);
3975 if (color.has_value())
3976 return ColorStyleValue::create(color.value());
3977
3978 return {};
3979}
3980
3981RefPtr<StyleValue> Parser::parse_string_value(ComponentValue const& component_value)
3982{
3983 if (component_value.is(Token::Type::String))
3984 return StringStyleValue::create(String::from_utf8(component_value.token().string()).release_value_but_fixme_should_propagate_errors());
3985
3986 return {};
3987}
3988
3989RefPtr<StyleValue> Parser::parse_image_value(ComponentValue const& component_value)
3990{
3991 auto url = parse_url_function(component_value, AllowedDataUrlType::Image);
3992 if (url.has_value())
3993 return ImageStyleValue::create(url.value());
3994 auto linear_gradient = parse_linear_gradient_function(component_value);
3995 if (linear_gradient)
3996 return linear_gradient;
3997 auto conic_gradient = parse_conic_gradient_function(component_value);
3998 if (conic_gradient)
3999 return conic_gradient;
4000 return parse_radial_gradient_function(component_value);
4001}
4002
4003template<typename ParseFunction>
4004RefPtr<StyleValue> Parser::parse_comma_separated_value_list(Vector<ComponentValue> const& component_values, ParseFunction parse_one_value)
4005{
4006 auto tokens = TokenStream { component_values };
4007 auto first = parse_one_value(tokens);
4008 if (!first || !tokens.has_next_token())
4009 return first;
4010
4011 StyleValueVector values;
4012 values.append(first.release_nonnull());
4013
4014 while (tokens.has_next_token()) {
4015 if (!tokens.next_token().is(Token::Type::Comma))
4016 return {};
4017
4018 if (auto maybe_value = parse_one_value(tokens)) {
4019 values.append(maybe_value.release_nonnull());
4020 continue;
4021 }
4022 return {};
4023 }
4024
4025 return StyleValueList::create(move(values), StyleValueList::Separator::Comma);
4026}
4027
4028RefPtr<StyleValue> Parser::parse_simple_comma_separated_value_list(Vector<ComponentValue> const& component_values)
4029{
4030 return parse_comma_separated_value_list(component_values, [=, this](auto& tokens) -> RefPtr<StyleValue> {
4031 auto& token = tokens.next_token();
4032 if (auto value = parse_css_value(token); value && property_accepts_value(m_context.current_property_id(), *value))
4033 return value;
4034 tokens.reconsume_current_input_token();
4035 return nullptr;
4036 });
4037}
4038
4039RefPtr<StyleValue> Parser::parse_background_value(Vector<ComponentValue> const& component_values)
4040{
4041 StyleValueVector background_images;
4042 StyleValueVector background_positions;
4043 StyleValueVector background_sizes;
4044 StyleValueVector background_repeats;
4045 StyleValueVector background_attachments;
4046 StyleValueVector background_clips;
4047 StyleValueVector background_origins;
4048 RefPtr<StyleValue> background_color;
4049
4050 // Per-layer values
4051 RefPtr<StyleValue> background_image;
4052 RefPtr<StyleValue> background_position;
4053 RefPtr<StyleValue> background_size;
4054 RefPtr<StyleValue> background_repeat;
4055 RefPtr<StyleValue> background_attachment;
4056 RefPtr<StyleValue> background_clip;
4057 RefPtr<StyleValue> background_origin;
4058
4059 bool has_multiple_layers = false;
4060
4061 auto background_layer_is_valid = [&](bool allow_background_color) -> bool {
4062 if (allow_background_color) {
4063 if (background_color)
4064 return true;
4065 } else {
4066 if (background_color)
4067 return false;
4068 }
4069 return background_image || background_position || background_size || background_repeat || background_attachment || background_clip || background_origin;
4070 };
4071
4072 auto complete_background_layer = [&]() {
4073 background_images.append(background_image ? background_image.release_nonnull() : property_initial_value(m_context.realm(), PropertyID::BackgroundImage));
4074 background_positions.append(background_position ? background_position.release_nonnull() : property_initial_value(m_context.realm(), PropertyID::BackgroundPosition));
4075 background_sizes.append(background_size ? background_size.release_nonnull() : property_initial_value(m_context.realm(), PropertyID::BackgroundSize));
4076 background_repeats.append(background_repeat ? background_repeat.release_nonnull() : property_initial_value(m_context.realm(), PropertyID::BackgroundRepeat));
4077 background_attachments.append(background_attachment ? background_attachment.release_nonnull() : property_initial_value(m_context.realm(), PropertyID::BackgroundAttachment));
4078
4079 if (!background_origin && !background_clip) {
4080 background_origin = property_initial_value(m_context.realm(), PropertyID::BackgroundOrigin);
4081 background_clip = property_initial_value(m_context.realm(), PropertyID::BackgroundClip);
4082 } else if (!background_clip) {
4083 background_clip = background_origin;
4084 }
4085 background_origins.append(background_origin.release_nonnull());
4086 background_clips.append(background_clip.release_nonnull());
4087
4088 background_image = nullptr;
4089 background_position = nullptr;
4090 background_size = nullptr;
4091 background_repeat = nullptr;
4092 background_attachment = nullptr;
4093 background_clip = nullptr;
4094 background_origin = nullptr;
4095 };
4096
4097 auto tokens = TokenStream { component_values };
4098 while (tokens.has_next_token()) {
4099 auto const& part = tokens.next_token();
4100
4101 if (part.is(Token::Type::Comma)) {
4102 has_multiple_layers = true;
4103 if (!background_layer_is_valid(false))
4104 return nullptr;
4105 complete_background_layer();
4106 continue;
4107 }
4108
4109 auto value = parse_css_value(part);
4110 if (!value)
4111 return nullptr;
4112
4113 if (property_accepts_value(PropertyID::BackgroundAttachment, *value)) {
4114 if (background_attachment)
4115 return nullptr;
4116 background_attachment = value.release_nonnull();
4117 continue;
4118 }
4119 if (property_accepts_value(PropertyID::BackgroundColor, *value)) {
4120 if (background_color)
4121 return nullptr;
4122 background_color = value.release_nonnull();
4123 continue;
4124 }
4125 if (property_accepts_value(PropertyID::BackgroundImage, *value)) {
4126 if (background_image)
4127 return nullptr;
4128 background_image = value.release_nonnull();
4129 continue;
4130 }
4131 if (property_accepts_value(PropertyID::BackgroundOrigin, *value)) {
4132 // background-origin and background-clip accept the same values. From the spec:
4133 // "If one <box> value is present then it sets both background-origin and background-clip to that value.
4134 // If two values are present, then the first sets background-origin and the second background-clip."
4135 // - https://www.w3.org/TR/css-backgrounds-3/#background
4136 // So, we put the first one in background-origin, then if we get a second, we put it in background-clip.
4137 // If we only get one, we copy the value before creating the BackgroundStyleValue.
4138 if (!background_origin) {
4139 background_origin = value.release_nonnull();
4140 continue;
4141 }
4142 if (!background_clip) {
4143 background_clip = value.release_nonnull();
4144 continue;
4145 }
4146 return nullptr;
4147 }
4148 if (property_accepts_value(PropertyID::BackgroundPosition, *value)) {
4149 if (background_position)
4150 return nullptr;
4151 tokens.reconsume_current_input_token();
4152 if (auto maybe_background_position = parse_single_background_position_value(tokens)) {
4153 background_position = maybe_background_position.release_nonnull();
4154
4155 // Attempt to parse `/ <background-size>`
4156 auto transaction = tokens.begin_transaction();
4157 auto& maybe_slash = tokens.next_token();
4158 if (maybe_slash.is(Token::Type::Delim) && maybe_slash.token().delim() == '/') {
4159 if (auto maybe_background_size = parse_single_background_size_value(tokens)) {
4160 transaction.commit();
4161 background_size = maybe_background_size.release_nonnull();
4162 continue;
4163 }
4164 return nullptr;
4165 }
4166 continue;
4167 }
4168 return nullptr;
4169 }
4170 if (property_accepts_value(PropertyID::BackgroundRepeat, *value)) {
4171 if (background_repeat)
4172 return nullptr;
4173 tokens.reconsume_current_input_token();
4174 if (auto maybe_repeat = parse_single_background_repeat_value(tokens)) {
4175 background_repeat = maybe_repeat.release_nonnull();
4176 continue;
4177 }
4178 return nullptr;
4179 }
4180
4181 return nullptr;
4182 }
4183
4184 if (!background_layer_is_valid(true))
4185 return nullptr;
4186
4187 // We only need to create StyleValueLists if there are multiple layers.
4188 // Otherwise, we can pass the single StyleValues directly.
4189 if (has_multiple_layers) {
4190 complete_background_layer();
4191
4192 if (!background_color)
4193 background_color = property_initial_value(m_context.realm(), PropertyID::BackgroundColor);
4194 return BackgroundStyleValue::create(
4195 background_color.release_nonnull(),
4196 StyleValueList::create(move(background_images), StyleValueList::Separator::Comma),
4197 StyleValueList::create(move(background_positions), StyleValueList::Separator::Comma),
4198 StyleValueList::create(move(background_sizes), StyleValueList::Separator::Comma),
4199 StyleValueList::create(move(background_repeats), StyleValueList::Separator::Comma),
4200 StyleValueList::create(move(background_attachments), StyleValueList::Separator::Comma),
4201 StyleValueList::create(move(background_origins), StyleValueList::Separator::Comma),
4202 StyleValueList::create(move(background_clips), StyleValueList::Separator::Comma));
4203 }
4204
4205 if (!background_color)
4206 background_color = property_initial_value(m_context.realm(), PropertyID::BackgroundColor);
4207 if (!background_image)
4208 background_image = property_initial_value(m_context.realm(), PropertyID::BackgroundImage);
4209 if (!background_position)
4210 background_position = property_initial_value(m_context.realm(), PropertyID::BackgroundPosition);
4211 if (!background_size)
4212 background_size = property_initial_value(m_context.realm(), PropertyID::BackgroundSize);
4213 if (!background_repeat)
4214 background_repeat = property_initial_value(m_context.realm(), PropertyID::BackgroundRepeat);
4215 if (!background_attachment)
4216 background_attachment = property_initial_value(m_context.realm(), PropertyID::BackgroundAttachment);
4217
4218 if (!background_origin && !background_clip) {
4219 background_origin = property_initial_value(m_context.realm(), PropertyID::BackgroundOrigin);
4220 background_clip = property_initial_value(m_context.realm(), PropertyID::BackgroundClip);
4221 } else if (!background_clip) {
4222 background_clip = background_origin;
4223 }
4224
4225 return BackgroundStyleValue::create(
4226 background_color.release_nonnull(),
4227 background_image.release_nonnull(),
4228 background_position.release_nonnull(),
4229 background_size.release_nonnull(),
4230 background_repeat.release_nonnull(),
4231 background_attachment.release_nonnull(),
4232 background_origin.release_nonnull(),
4233 background_clip.release_nonnull());
4234}
4235
4236RefPtr<StyleValue> Parser::parse_single_background_position_value(TokenStream<ComponentValue>& tokens)
4237{
4238 // NOTE: This *looks* like it parses a <position>, but it doesn't. From the spec:
4239 // "Note: The background-position property also accepts a three-value syntax.
4240 // This has been disallowed generically because it creates parsing ambiguities
4241 // when combined with other length or percentage components in a property value."
4242 // - https://www.w3.org/TR/css-values-4/#typedef-position
4243 // So, we'll need a separate function to parse <position> later.
4244
4245 auto transaction = tokens.begin_transaction();
4246
4247 auto to_edge = [](ValueID identifier) -> Optional<PositionEdge> {
4248 switch (identifier) {
4249 case ValueID::Top:
4250 return PositionEdge::Top;
4251 case ValueID::Bottom:
4252 return PositionEdge::Bottom;
4253 case ValueID::Left:
4254 return PositionEdge::Left;
4255 case ValueID::Right:
4256 return PositionEdge::Right;
4257 default:
4258 return {};
4259 }
4260 };
4261 auto is_horizontal = [](ValueID identifier) -> bool {
4262 switch (identifier) {
4263 case ValueID::Left:
4264 case ValueID::Right:
4265 return true;
4266 default:
4267 return false;
4268 }
4269 };
4270 auto is_vertical = [](ValueID identifier) -> bool {
4271 switch (identifier) {
4272 case ValueID::Top:
4273 case ValueID::Bottom:
4274 return true;
4275 default:
4276 return false;
4277 }
4278 };
4279
4280 struct EdgeOffset {
4281 PositionEdge edge;
4282 LengthPercentage offset;
4283 bool edge_provided;
4284 bool offset_provided;
4285 };
4286
4287 Optional<EdgeOffset> horizontal;
4288 Optional<EdgeOffset> vertical;
4289 bool found_center = false;
4290
4291 auto const center_offset = Percentage { 50 };
4292 auto const zero_offset = Length::make_px(0);
4293
4294 auto value_to_length_percentage = [&](auto value) -> Optional<LengthPercentage> {
4295 if (value->is_percentage())
4296 return LengthPercentage { value->as_percentage().percentage() };
4297 if (value->has_length())
4298 return LengthPercentage { value->to_length() };
4299 if (value->is_calculated())
4300 return LengthPercentage { value->as_calculated() };
4301 return {};
4302 };
4303
4304 while (tokens.has_next_token()) {
4305 // Check if we're done
4306 auto seen_items = (horizontal.has_value() ? 1 : 0) + (vertical.has_value() ? 1 : 0) + (found_center ? 1 : 0);
4307 if (seen_items == 2)
4308 break;
4309
4310 auto const& token = tokens.peek_token();
4311 auto maybe_value = parse_css_value(token);
4312 if (!maybe_value || !property_accepts_value(PropertyID::BackgroundPosition, *maybe_value))
4313 break;
4314 tokens.next_token();
4315 auto value = maybe_value.release_nonnull();
4316
4317 auto offset = value_to_length_percentage(value);
4318 if (offset.has_value()) {
4319 if (!horizontal.has_value()) {
4320 horizontal = EdgeOffset { PositionEdge::Left, *offset, false, true };
4321 } else if (!vertical.has_value()) {
4322 vertical = EdgeOffset { PositionEdge::Top, *offset, false, true };
4323 } else {
4324 return nullptr;
4325 }
4326 continue;
4327 }
4328
4329 auto try_parse_offset = [&](bool& offset_provided) -> LengthPercentage {
4330 if (tokens.has_next_token()) {
4331 auto& token = tokens.peek_token();
4332 auto maybe_value = parse_css_value(token);
4333 if (!maybe_value)
4334 return zero_offset;
4335 auto offset = value_to_length_percentage(maybe_value.release_nonnull());
4336 if (offset.has_value()) {
4337 offset_provided = true;
4338 tokens.next_token();
4339 return *offset;
4340 }
4341 }
4342 return zero_offset;
4343 };
4344
4345 if (value->has_identifier()) {
4346 auto identifier = value->to_identifier();
4347 if (is_horizontal(identifier)) {
4348 bool offset_provided = false;
4349 auto offset = try_parse_offset(offset_provided);
4350 horizontal = EdgeOffset { *to_edge(identifier), offset, true, offset_provided };
4351 } else if (is_vertical(identifier)) {
4352 bool offset_provided = false;
4353 auto offset = try_parse_offset(offset_provided);
4354 vertical = EdgeOffset { *to_edge(identifier), offset, true, offset_provided };
4355 } else if (identifier == ValueID::Center) {
4356 found_center = true;
4357 } else {
4358 return nullptr;
4359 }
4360 continue;
4361 }
4362
4363 tokens.reconsume_current_input_token();
4364 break;
4365 }
4366
4367 if (found_center) {
4368 if (horizontal.has_value() && vertical.has_value())
4369 return nullptr;
4370 if (!horizontal.has_value())
4371 horizontal = EdgeOffset { PositionEdge::Left, center_offset, true, false };
4372 if (!vertical.has_value())
4373 vertical = EdgeOffset { PositionEdge::Top, center_offset, true, false };
4374 }
4375
4376 if (!horizontal.has_value() && !vertical.has_value())
4377 return nullptr;
4378
4379 // Unpack `<edge> <length>`:
4380 // The loop above reads this pattern as a single EdgeOffset, when actually, it should be treated
4381 // as `x y` if the edge is horizontal, and `y` (with the second token reconsumed) otherwise.
4382 if (!vertical.has_value() && horizontal->edge_provided && horizontal->offset_provided) {
4383 // Split into `x y`
4384 vertical = EdgeOffset { PositionEdge::Top, horizontal->offset, false, true };
4385 horizontal->offset = zero_offset;
4386 horizontal->offset_provided = false;
4387 } else if (!horizontal.has_value() && vertical->edge_provided && vertical->offset_provided) {
4388 // `y`, reconsume
4389 vertical->offset = zero_offset;
4390 vertical->offset_provided = false;
4391 tokens.reconsume_current_input_token();
4392 }
4393
4394 // If only one value is specified, the second value is assumed to be center.
4395 if (!horizontal.has_value())
4396 horizontal = EdgeOffset { PositionEdge::Left, center_offset, false, false };
4397 if (!vertical.has_value())
4398 vertical = EdgeOffset { PositionEdge::Top, center_offset, false, false };
4399
4400 transaction.commit();
4401 return PositionStyleValue::create(
4402 horizontal->edge, horizontal->offset,
4403 vertical->edge, vertical->offset);
4404}
4405
4406RefPtr<StyleValue> Parser::parse_single_background_repeat_value(TokenStream<ComponentValue>& tokens)
4407{
4408 auto transaction = tokens.begin_transaction();
4409
4410 auto is_directional_repeat = [](StyleValue const& value) -> bool {
4411 auto value_id = value.to_identifier();
4412 return value_id == ValueID::RepeatX || value_id == ValueID::RepeatY;
4413 };
4414
4415 auto as_repeat = [](ValueID identifier) -> Optional<Repeat> {
4416 switch (identifier) {
4417 case ValueID::NoRepeat:
4418 return Repeat::NoRepeat;
4419 case ValueID::Repeat:
4420 return Repeat::Repeat;
4421 case ValueID::Round:
4422 return Repeat::Round;
4423 case ValueID::Space:
4424 return Repeat::Space;
4425 default:
4426 return {};
4427 }
4428 };
4429
4430 auto const& token = tokens.next_token();
4431 auto maybe_x_value = parse_css_value(token);
4432 if (!maybe_x_value || !property_accepts_value(PropertyID::BackgroundRepeat, *maybe_x_value))
4433 return nullptr;
4434 auto x_value = maybe_x_value.release_nonnull();
4435
4436 if (is_directional_repeat(*x_value)) {
4437 auto value_id = x_value->to_identifier();
4438 transaction.commit();
4439 return BackgroundRepeatStyleValue::create(
4440 value_id == ValueID::RepeatX ? Repeat::Repeat : Repeat::NoRepeat,
4441 value_id == ValueID::RepeatX ? Repeat::NoRepeat : Repeat::Repeat);
4442 }
4443
4444 auto x_repeat = as_repeat(x_value->to_identifier());
4445 if (!x_repeat.has_value())
4446 return nullptr;
4447
4448 // See if we have a second value for Y
4449 auto const& second_token = tokens.peek_token();
4450 auto maybe_y_value = parse_css_value(second_token);
4451 if (!maybe_y_value || !property_accepts_value(PropertyID::BackgroundRepeat, *maybe_y_value)) {
4452 // We don't have a second value, so use x for both
4453 transaction.commit();
4454 return BackgroundRepeatStyleValue::create(x_repeat.value(), x_repeat.value());
4455 }
4456 tokens.next_token();
4457 auto y_value = maybe_y_value.release_nonnull();
4458 if (is_directional_repeat(*y_value))
4459 return nullptr;
4460
4461 auto y_repeat = as_repeat(y_value->to_identifier());
4462 if (!y_repeat.has_value())
4463 return nullptr;
4464
4465 transaction.commit();
4466 return BackgroundRepeatStyleValue::create(x_repeat.value(), y_repeat.value());
4467}
4468
4469RefPtr<StyleValue> Parser::parse_single_background_size_value(TokenStream<ComponentValue>& tokens)
4470{
4471 auto transaction = tokens.begin_transaction();
4472
4473 auto get_length_percentage = [](StyleValue& style_value) -> Optional<LengthPercentage> {
4474 if (style_value.is_percentage())
4475 return LengthPercentage { style_value.as_percentage().percentage() };
4476 if (style_value.has_length())
4477 return LengthPercentage { style_value.to_length() };
4478 return {};
4479 };
4480
4481 auto maybe_x_value = parse_css_value(tokens.next_token());
4482 if (!maybe_x_value || !property_accepts_value(PropertyID::BackgroundSize, *maybe_x_value))
4483 return nullptr;
4484 auto x_value = maybe_x_value.release_nonnull();
4485
4486 if (x_value->to_identifier() == ValueID::Cover || x_value->to_identifier() == ValueID::Contain) {
4487 transaction.commit();
4488 return x_value;
4489 }
4490
4491 auto maybe_y_value = parse_css_value(tokens.peek_token());
4492 if (!maybe_y_value || !property_accepts_value(PropertyID::BackgroundSize, *maybe_y_value)) {
4493 auto x_size = get_length_percentage(*x_value);
4494 if (!x_size.has_value())
4495 return nullptr;
4496
4497 transaction.commit();
4498 return BackgroundSizeStyleValue::create(x_size.value(), x_size.value());
4499 }
4500 tokens.next_token();
4501
4502 auto y_value = maybe_y_value.release_nonnull();
4503 auto x_size = get_length_percentage(*x_value);
4504 auto y_size = get_length_percentage(*y_value);
4505
4506 if (!x_size.has_value() || !y_size.has_value())
4507 return nullptr;
4508
4509 transaction.commit();
4510 return BackgroundSizeStyleValue::create(x_size.release_value(), y_size.release_value());
4511}
4512
4513RefPtr<StyleValue> Parser::parse_border_value(Vector<ComponentValue> const& component_values)
4514{
4515 if (component_values.size() > 3)
4516 return nullptr;
4517
4518 RefPtr<StyleValue> border_width;
4519 RefPtr<StyleValue> border_color;
4520 RefPtr<StyleValue> border_style;
4521
4522 for (auto const& part : component_values) {
4523 auto value = parse_css_value(part);
4524 if (!value)
4525 return nullptr;
4526
4527 if (property_accepts_value(PropertyID::BorderWidth, *value)) {
4528 if (border_width)
4529 return nullptr;
4530 border_width = value.release_nonnull();
4531 continue;
4532 }
4533 if (property_accepts_value(PropertyID::BorderColor, *value)) {
4534 if (border_color)
4535 return nullptr;
4536 border_color = value.release_nonnull();
4537 continue;
4538 }
4539 if (property_accepts_value(PropertyID::BorderStyle, *value)) {
4540 if (border_style)
4541 return nullptr;
4542 border_style = value.release_nonnull();
4543 continue;
4544 }
4545
4546 return nullptr;
4547 }
4548
4549 if (!border_width)
4550 border_width = property_initial_value(m_context.realm(), PropertyID::BorderWidth);
4551 if (!border_style)
4552 border_style = property_initial_value(m_context.realm(), PropertyID::BorderStyle);
4553 if (!border_color)
4554 border_color = property_initial_value(m_context.realm(), PropertyID::BorderColor);
4555
4556 return BorderStyleValue::create(border_width.release_nonnull(), border_style.release_nonnull(), border_color.release_nonnull());
4557}
4558
4559RefPtr<StyleValue> Parser::parse_border_radius_value(Vector<ComponentValue> const& component_values)
4560{
4561 if (component_values.size() == 2) {
4562 auto horizontal = parse_dimension(component_values[0]);
4563 auto vertical = parse_dimension(component_values[1]);
4564 if (horizontal.has_value() && horizontal->is_length_percentage() && vertical.has_value() && vertical->is_length_percentage())
4565 return BorderRadiusStyleValue::create(horizontal->length_percentage(), vertical->length_percentage());
4566
4567 return nullptr;
4568 }
4569
4570 if (component_values.size() == 1) {
4571 auto radius = parse_dimension(component_values[0]);
4572 if (radius.has_value() && radius->is_length_percentage())
4573 return BorderRadiusStyleValue::create(radius->length_percentage(), radius->length_percentage());
4574 return nullptr;
4575 }
4576
4577 return nullptr;
4578}
4579
4580RefPtr<StyleValue> Parser::parse_border_radius_shorthand_value(Vector<ComponentValue> const& component_values)
4581{
4582 auto top_left = [&](Vector<LengthPercentage>& radii) { return radii[0]; };
4583 auto top_right = [&](Vector<LengthPercentage>& radii) {
4584 switch (radii.size()) {
4585 case 4:
4586 case 3:
4587 case 2:
4588 return radii[1];
4589 case 1:
4590 return radii[0];
4591 default:
4592 VERIFY_NOT_REACHED();
4593 }
4594 };
4595 auto bottom_right = [&](Vector<LengthPercentage>& radii) {
4596 switch (radii.size()) {
4597 case 4:
4598 case 3:
4599 return radii[2];
4600 case 2:
4601 case 1:
4602 return radii[0];
4603 default:
4604 VERIFY_NOT_REACHED();
4605 }
4606 };
4607 auto bottom_left = [&](Vector<LengthPercentage>& radii) {
4608 switch (radii.size()) {
4609 case 4:
4610 return radii[3];
4611 case 3:
4612 case 2:
4613 return radii[1];
4614 case 1:
4615 return radii[0];
4616 default:
4617 VERIFY_NOT_REACHED();
4618 }
4619 };
4620
4621 Vector<LengthPercentage> horizontal_radii;
4622 Vector<LengthPercentage> vertical_radii;
4623 bool reading_vertical = false;
4624
4625 for (auto const& value : component_values) {
4626 if (value.is(Token::Type::Delim) && value.token().delim() == '/') {
4627 if (reading_vertical || horizontal_radii.is_empty())
4628 return nullptr;
4629
4630 reading_vertical = true;
4631 continue;
4632 }
4633
4634 auto maybe_dimension = parse_dimension(value);
4635 if (!maybe_dimension.has_value() || !maybe_dimension->is_length_percentage())
4636 return nullptr;
4637 if (reading_vertical) {
4638 vertical_radii.append(maybe_dimension->length_percentage());
4639 } else {
4640 horizontal_radii.append(maybe_dimension->length_percentage());
4641 }
4642 }
4643
4644 if (horizontal_radii.size() > 4 || vertical_radii.size() > 4
4645 || horizontal_radii.is_empty()
4646 || (reading_vertical && vertical_radii.is_empty()))
4647 return nullptr;
4648
4649 auto top_left_radius = BorderRadiusStyleValue::create(top_left(horizontal_radii),
4650 vertical_radii.is_empty() ? top_left(horizontal_radii) : top_left(vertical_radii));
4651 auto top_right_radius = BorderRadiusStyleValue::create(top_right(horizontal_radii),
4652 vertical_radii.is_empty() ? top_right(horizontal_radii) : top_right(vertical_radii));
4653 auto bottom_right_radius = BorderRadiusStyleValue::create(bottom_right(horizontal_radii),
4654 vertical_radii.is_empty() ? bottom_right(horizontal_radii) : bottom_right(vertical_radii));
4655 auto bottom_left_radius = BorderRadiusStyleValue::create(bottom_left(horizontal_radii),
4656 vertical_radii.is_empty() ? bottom_left(horizontal_radii) : bottom_left(vertical_radii));
4657
4658 return BorderRadiusShorthandStyleValue::create(move(top_left_radius), move(top_right_radius), move(bottom_right_radius), move(bottom_left_radius));
4659}
4660
4661RefPtr<StyleValue> Parser::parse_shadow_value(Vector<ComponentValue> const& component_values, AllowInsetKeyword allow_inset_keyword)
4662{
4663 // "none"
4664 if (component_values.size() == 1 && component_values.first().is(Token::Type::Ident)) {
4665 auto ident = parse_identifier_value(component_values.first());
4666 if (ident && ident->to_identifier() == ValueID::None)
4667 return ident;
4668 }
4669
4670 return parse_comma_separated_value_list(component_values, [this, allow_inset_keyword](auto& tokens) {
4671 return parse_single_shadow_value(tokens, allow_inset_keyword);
4672 });
4673}
4674
4675RefPtr<StyleValue> Parser::parse_single_shadow_value(TokenStream<ComponentValue>& tokens, AllowInsetKeyword allow_inset_keyword)
4676{
4677 auto transaction = tokens.begin_transaction();
4678
4679 Optional<Color> color;
4680 Optional<Length> offset_x;
4681 Optional<Length> offset_y;
4682 Optional<Length> blur_radius;
4683 Optional<Length> spread_distance;
4684 Optional<ShadowPlacement> placement;
4685
4686 while (tokens.has_next_token()) {
4687 auto const& token = tokens.peek_token();
4688
4689 if (auto maybe_color = parse_color(token); maybe_color.has_value()) {
4690 if (color.has_value())
4691 return nullptr;
4692 color = maybe_color.release_value();
4693 tokens.next_token();
4694 continue;
4695 }
4696
4697 if (auto maybe_offset_x = parse_length(token); maybe_offset_x.has_value()) {
4698 // horizontal offset
4699 if (offset_x.has_value())
4700 return nullptr;
4701 offset_x = maybe_offset_x.release_value();
4702 tokens.next_token();
4703
4704 // vertical offset
4705 if (!tokens.has_next_token())
4706 return nullptr;
4707 auto maybe_offset_y = parse_length(tokens.peek_token());
4708 if (!maybe_offset_y.has_value())
4709 return nullptr;
4710 offset_y = maybe_offset_y.release_value();
4711 tokens.next_token();
4712
4713 // blur radius (optional)
4714 if (!tokens.has_next_token())
4715 break;
4716 auto maybe_blur_radius = parse_length(tokens.peek_token());
4717 if (!maybe_blur_radius.has_value())
4718 continue;
4719 blur_radius = maybe_blur_radius.release_value();
4720 tokens.next_token();
4721
4722 // spread distance (optional)
4723 if (!tokens.has_next_token())
4724 break;
4725 auto maybe_spread_distance = parse_length(tokens.peek_token());
4726 if (!maybe_spread_distance.has_value())
4727 continue;
4728 spread_distance = maybe_spread_distance.release_value();
4729 tokens.next_token();
4730
4731 continue;
4732 }
4733
4734 if (allow_inset_keyword == AllowInsetKeyword::Yes
4735 && token.is(Token::Type::Ident) && token.token().ident().equals_ignoring_ascii_case("inset"sv)) {
4736 if (placement.has_value())
4737 return nullptr;
4738 placement = ShadowPlacement::Inner;
4739 tokens.next_token();
4740 continue;
4741 }
4742
4743 if (token.is(Token::Type::Comma))
4744 break;
4745
4746 return nullptr;
4747 }
4748
4749 // FIXME: If color is absent, default to `currentColor`
4750 if (!color.has_value())
4751 color = Color::NamedColor::Black;
4752
4753 // x/y offsets are required
4754 if (!offset_x.has_value() || !offset_y.has_value())
4755 return nullptr;
4756
4757 // Other lengths default to 0
4758 if (!blur_radius.has_value())
4759 blur_radius = Length::make_px(0);
4760 if (!spread_distance.has_value())
4761 spread_distance = Length::make_px(0);
4762
4763 // Placement is outer by default
4764 if (!placement.has_value())
4765 placement = ShadowPlacement::Outer;
4766
4767 transaction.commit();
4768 return ShadowStyleValue::create(color.release_value(), offset_x.release_value(), offset_y.release_value(), blur_radius.release_value(), spread_distance.release_value(), placement.release_value());
4769}
4770
4771RefPtr<StyleValue> Parser::parse_content_value(Vector<ComponentValue> const& component_values)
4772{
4773 // FIXME: `content` accepts several kinds of function() type, which we don't handle in property_accepts_value() yet.
4774
4775 auto is_single_value_identifier = [](ValueID identifier) -> bool {
4776 switch (identifier) {
4777 case ValueID::None:
4778 case ValueID::Normal:
4779 return true;
4780 default:
4781 return false;
4782 }
4783 };
4784
4785 if (component_values.size() == 1) {
4786 if (auto identifier = parse_identifier_value(component_values.first())) {
4787 if (is_single_value_identifier(identifier->to_identifier()))
4788 return identifier;
4789 }
4790 }
4791
4792 StyleValueVector content_values;
4793 StyleValueVector alt_text_values;
4794 bool in_alt_text = false;
4795
4796 for (auto const& value : component_values) {
4797 if (value.is(Token::Type::Delim) && value.token().delim() == '/') {
4798 if (in_alt_text || content_values.is_empty())
4799 return {};
4800 in_alt_text = true;
4801 continue;
4802 }
4803 auto style_value = parse_css_value(value);
4804 if (style_value && property_accepts_value(PropertyID::Content, *style_value)) {
4805 if (is_single_value_identifier(style_value->to_identifier()))
4806 return {};
4807
4808 if (in_alt_text) {
4809 alt_text_values.append(style_value.release_nonnull());
4810 } else {
4811 content_values.append(style_value.release_nonnull());
4812 }
4813 continue;
4814 }
4815
4816 return {};
4817 }
4818
4819 if (content_values.is_empty())
4820 return {};
4821 if (in_alt_text && alt_text_values.is_empty())
4822 return {};
4823
4824 RefPtr<StyleValueList> alt_text;
4825 if (!alt_text_values.is_empty())
4826 alt_text = StyleValueList::create(move(alt_text_values), StyleValueList::Separator::Space);
4827
4828 return ContentStyleValue::create(StyleValueList::create(move(content_values), StyleValueList::Separator::Space), move(alt_text));
4829}
4830
4831RefPtr<StyleValue> Parser::parse_filter_value_list_value(Vector<ComponentValue> const& component_values)
4832{
4833 if (component_values.size() == 1 && component_values.first().is(Token::Type::Ident)) {
4834 auto ident = parse_identifier_value(component_values.first());
4835 if (ident && ident->to_identifier() == ValueID::None)
4836 return ident;
4837 }
4838
4839 TokenStream tokens { component_values };
4840
4841 // FIXME: <url>s are ignored for now
4842 // <filter-value-list> = [ <filter-function> | <url> ]+
4843
4844 enum class FilterToken {
4845 // Color filters:
4846 Brightness,
4847 Contrast,
4848 Grayscale,
4849 Invert,
4850 Opacity,
4851 Saturate,
4852 Sepia,
4853 // Special filters:
4854 Blur,
4855 DropShadow,
4856 HueRotate
4857 };
4858
4859 auto filter_token_to_operation = [&](auto filter) {
4860 VERIFY(to_underlying(filter) < to_underlying(FilterToken::Blur));
4861 return static_cast<Filter::Color::Operation>(filter);
4862 };
4863
4864 auto parse_number_percentage = [&](auto& token) -> Optional<NumberPercentage> {
4865 if (token.is(Token::Type::Percentage))
4866 return NumberPercentage(Percentage(token.token().percentage()));
4867 if (token.is(Token::Type::Number))
4868 return NumberPercentage(Number(Number::Type::Number, token.token().number_value()));
4869 return {};
4870 };
4871
4872 auto parse_filter_function_name = [&](auto name) -> Optional<FilterToken> {
4873 if (name.equals_ignoring_ascii_case("blur"sv))
4874 return FilterToken::Blur;
4875 if (name.equals_ignoring_ascii_case("brightness"sv))
4876 return FilterToken::Brightness;
4877 if (name.equals_ignoring_ascii_case("contrast"sv))
4878 return FilterToken::Contrast;
4879 if (name.equals_ignoring_ascii_case("drop-shadow"sv))
4880 return FilterToken::DropShadow;
4881 if (name.equals_ignoring_ascii_case("grayscale"sv))
4882 return FilterToken::Grayscale;
4883 if (name.equals_ignoring_ascii_case("hue-rotate"sv))
4884 return FilterToken::HueRotate;
4885 if (name.equals_ignoring_ascii_case("invert"sv))
4886 return FilterToken::Invert;
4887 if (name.equals_ignoring_ascii_case("opacity"sv))
4888 return FilterToken::Opacity;
4889 if (name.equals_ignoring_ascii_case("saturate"sv))
4890 return FilterToken::Saturate;
4891 if (name.equals_ignoring_ascii_case("sepia"sv))
4892 return FilterToken::Sepia;
4893 return {};
4894 };
4895
4896 auto parse_filter_function = [&](auto filter_token, auto function_values) -> Optional<FilterFunction> {
4897 TokenStream tokens { function_values };
4898 tokens.skip_whitespace();
4899
4900 auto if_no_more_tokens_return = [&](auto filter) -> Optional<FilterFunction> {
4901 tokens.skip_whitespace();
4902 if (tokens.has_next_token())
4903 return {};
4904 return filter;
4905 };
4906
4907 if (filter_token == FilterToken::Blur) {
4908 // blur( <length>? )
4909 if (!tokens.has_next_token())
4910 return Filter::Blur {};
4911 auto blur_radius = parse_length(tokens.next_token());
4912 if (!blur_radius.has_value())
4913 return {};
4914 return if_no_more_tokens_return(Filter::Blur { *blur_radius });
4915 } else if (filter_token == FilterToken::DropShadow) {
4916 if (!tokens.has_next_token())
4917 return {};
4918 auto next_token = [&]() -> auto&
4919 {
4920 auto& token = tokens.next_token();
4921 tokens.skip_whitespace();
4922 return token;
4923 };
4924 // drop-shadow( [ <color>? && <length>{2,3} ] )
4925 // Note: The following code is a little awkward to allow the color to be before or after the lengths.
4926 auto& first_param = next_token();
4927 Optional<Length> maybe_radius = {};
4928 auto maybe_color = parse_color(first_param);
4929 auto x_offset = parse_length(maybe_color.has_value() ? next_token() : first_param);
4930 if (!x_offset.has_value() || !tokens.has_next_token()) {
4931 return {};
4932 }
4933 auto y_offset = parse_length(next_token());
4934 if (!y_offset.has_value()) {
4935 return {};
4936 }
4937 if (tokens.has_next_token()) {
4938 auto& token = next_token();
4939 maybe_radius = parse_length(token);
4940 if (!maybe_color.has_value() && (!maybe_radius.has_value() || tokens.has_next_token())) {
4941 maybe_color = parse_color(!maybe_radius.has_value() ? token : next_token());
4942 if (!maybe_color.has_value()) {
4943 return {};
4944 }
4945 } else if (!maybe_radius.has_value()) {
4946 return {};
4947 }
4948 }
4949 return if_no_more_tokens_return(Filter::DropShadow { *x_offset, *y_offset, maybe_radius, maybe_color });
4950 } else if (filter_token == FilterToken::HueRotate) {
4951 // hue-rotate( [ <angle> | <zero> ]? )
4952 if (!tokens.has_next_token())
4953 return Filter::HueRotate {};
4954 auto& token = tokens.next_token();
4955 if (token.is(Token::Type::Number)) {
4956 // hue-rotate(0)
4957 auto number = token.token().number();
4958 if (number.is_integer() && number.integer_value() == 0)
4959 return if_no_more_tokens_return(Filter::HueRotate { Filter::HueRotate::Zero {} });
4960 return {};
4961 }
4962 if (!token.is(Token::Type::Dimension))
4963 return {};
4964 float angle_value = token.token().dimension_value();
4965 auto angle_unit_name = token.token().dimension_unit();
4966 auto angle_unit = Angle::unit_from_name(angle_unit_name);
4967 if (!angle_unit.has_value())
4968 return {};
4969 Angle angle { angle_value, angle_unit.release_value() };
4970 return if_no_more_tokens_return(Filter::HueRotate { angle });
4971 } else {
4972 // Simple filters:
4973 // brightness( <number-percentage>? )
4974 // contrast( <number-percentage>? )
4975 // grayscale( <number-percentage>? )
4976 // invert( <number-percentage>? )
4977 // opacity( <number-percentage>? )
4978 // sepia( <number-percentage>? )
4979 // saturate( <number-percentage>? )
4980 if (!tokens.has_next_token())
4981 return Filter::Color { filter_token_to_operation(filter_token) };
4982 auto amount = parse_number_percentage(tokens.next_token());
4983 if (!amount.has_value())
4984 return {};
4985 return if_no_more_tokens_return(Filter::Color { filter_token_to_operation(filter_token), *amount });
4986 }
4987 };
4988
4989 Vector<FilterFunction> filter_value_list {};
4990
4991 while (tokens.has_next_token()) {
4992 tokens.skip_whitespace();
4993 if (!tokens.has_next_token())
4994 break;
4995 auto& token = tokens.next_token();
4996 if (!token.is_function())
4997 return {};
4998 auto filter_token = parse_filter_function_name(token.function().name());
4999 if (!filter_token.has_value())
5000 return {};
5001 auto filter_function = parse_filter_function(*filter_token, token.function().values());
5002 if (!filter_function.has_value())
5003 return {};
5004 filter_value_list.append(*filter_function);
5005 }
5006
5007 if (filter_value_list.is_empty())
5008 return {};
5009
5010 return FilterValueListStyleValue::create(move(filter_value_list));
5011}
5012
5013RefPtr<StyleValue> Parser::parse_flex_value(Vector<ComponentValue> const& component_values)
5014{
5015 if (component_values.size() == 1) {
5016 auto value = parse_css_value(component_values[0]);
5017 if (!value)
5018 return nullptr;
5019
5020 switch (value->to_identifier()) {
5021 case ValueID::Auto: {
5022 auto one = NumericStyleValue::create_integer(1);
5023 return FlexStyleValue::create(one, one, IdentifierStyleValue::create(ValueID::Auto));
5024 }
5025 case ValueID::None: {
5026 auto zero = NumericStyleValue::create_integer(0);
5027 return FlexStyleValue::create(zero, zero, IdentifierStyleValue::create(ValueID::Auto));
5028 }
5029 default:
5030 break;
5031 }
5032 }
5033
5034 RefPtr<StyleValue> flex_grow;
5035 RefPtr<StyleValue> flex_shrink;
5036 RefPtr<StyleValue> flex_basis;
5037
5038 for (size_t i = 0; i < component_values.size(); ++i) {
5039 auto value = parse_css_value(component_values[i]);
5040 if (!value)
5041 return nullptr;
5042
5043 // Zero is a valid value for basis, but only if grow and shrink are already specified.
5044 if (value->has_number() && value->to_number() == 0) {
5045 if (flex_grow && flex_shrink && !flex_basis) {
5046 flex_basis = LengthStyleValue::create(Length::make_px(0));
5047 continue;
5048 }
5049 }
5050
5051 if (property_accepts_value(PropertyID::FlexGrow, *value)) {
5052 if (flex_grow)
5053 return nullptr;
5054 flex_grow = value.release_nonnull();
5055
5056 // Flex-shrink may optionally follow directly after.
5057 if (i + 1 < component_values.size()) {
5058 auto second_value = parse_css_value(component_values[i + 1]);
5059 if (second_value && property_accepts_value(PropertyID::FlexShrink, *second_value)) {
5060 flex_shrink = second_value.release_nonnull();
5061 i++;
5062 }
5063 }
5064 continue;
5065 }
5066
5067 if (property_accepts_value(PropertyID::FlexBasis, *value)) {
5068 if (flex_basis)
5069 return nullptr;
5070 flex_basis = value.release_nonnull();
5071 continue;
5072 }
5073
5074 return nullptr;
5075 }
5076
5077 if (!flex_grow)
5078 flex_grow = property_initial_value(m_context.realm(), PropertyID::FlexGrow);
5079 if (!flex_shrink)
5080 flex_shrink = property_initial_value(m_context.realm(), PropertyID::FlexShrink);
5081 if (!flex_basis)
5082 flex_basis = property_initial_value(m_context.realm(), PropertyID::FlexBasis);
5083
5084 return FlexStyleValue::create(flex_grow.release_nonnull(), flex_shrink.release_nonnull(), flex_basis.release_nonnull());
5085}
5086
5087RefPtr<StyleValue> Parser::parse_flex_flow_value(Vector<ComponentValue> const& component_values)
5088{
5089 if (component_values.size() > 2)
5090 return nullptr;
5091
5092 RefPtr<StyleValue> flex_direction;
5093 RefPtr<StyleValue> flex_wrap;
5094
5095 for (auto const& part : component_values) {
5096 auto value = parse_css_value(part);
5097 if (!value)
5098 return nullptr;
5099 if (property_accepts_value(PropertyID::FlexDirection, *value)) {
5100 if (flex_direction)
5101 return nullptr;
5102 flex_direction = value.release_nonnull();
5103 continue;
5104 }
5105 if (property_accepts_value(PropertyID::FlexWrap, *value)) {
5106 if (flex_wrap)
5107 return nullptr;
5108 flex_wrap = value.release_nonnull();
5109 continue;
5110 }
5111 }
5112
5113 if (!flex_direction)
5114 flex_direction = property_initial_value(m_context.realm(), PropertyID::FlexDirection);
5115 if (!flex_wrap)
5116 flex_wrap = property_initial_value(m_context.realm(), PropertyID::FlexWrap);
5117
5118 return FlexFlowStyleValue::create(flex_direction.release_nonnull(), flex_wrap.release_nonnull());
5119}
5120
5121static bool is_generic_font_family(ValueID identifier)
5122{
5123 switch (identifier) {
5124 case ValueID::Cursive:
5125 case ValueID::Fantasy:
5126 case ValueID::Monospace:
5127 case ValueID::Serif:
5128 case ValueID::SansSerif:
5129 case ValueID::UiMonospace:
5130 case ValueID::UiRounded:
5131 case ValueID::UiSerif:
5132 case ValueID::UiSansSerif:
5133 return true;
5134 default:
5135 return false;
5136 }
5137}
5138
5139RefPtr<StyleValue> Parser::parse_font_value(Vector<ComponentValue> const& component_values)
5140{
5141 RefPtr<StyleValue> font_stretch;
5142 RefPtr<StyleValue> font_style;
5143 RefPtr<StyleValue> font_weight;
5144 RefPtr<StyleValue> font_size;
5145 RefPtr<StyleValue> line_height;
5146 RefPtr<StyleValue> font_families;
5147 RefPtr<StyleValue> font_variant;
5148
5149 // FIXME: Handle system fonts. (caption, icon, menu, message-box, small-caption, status-bar)
5150
5151 // Several sub-properties can be "normal", and appear in any order: style, variant, weight, stretch
5152 // So, we have to handle that separately.
5153 int normal_count = 0;
5154
5155 for (size_t i = 0; i < component_values.size(); ++i) {
5156 auto value = parse_css_value(component_values[i]);
5157 if (!value)
5158 return nullptr;
5159
5160 if (value->to_identifier() == ValueID::Normal) {
5161 normal_count++;
5162 continue;
5163 }
5164 // FIXME: Handle angle parameter to `oblique`: https://www.w3.org/TR/css-fonts-4/#font-style-prop
5165 if (property_accepts_value(PropertyID::FontStyle, *value)) {
5166 if (font_style)
5167 return nullptr;
5168 font_style = value.release_nonnull();
5169 continue;
5170 }
5171 if (property_accepts_value(PropertyID::FontWeight, *value)) {
5172 if (font_weight)
5173 return nullptr;
5174 font_weight = value.release_nonnull();
5175 continue;
5176 }
5177 if (property_accepts_value(PropertyID::FontVariant, *value)) {
5178 if (font_variant)
5179 return nullptr;
5180 font_variant = value.release_nonnull();
5181 continue;
5182 }
5183 if (property_accepts_value(PropertyID::FontSize, *value)) {
5184 if (font_size)
5185 return nullptr;
5186 font_size = value.release_nonnull();
5187
5188 // Consume `/ line-height` if present
5189 if (i + 2 < component_values.size()) {
5190 auto const& maybe_solidus = component_values[i + 1];
5191 if (maybe_solidus.is(Token::Type::Delim) && maybe_solidus.token().delim() == '/') {
5192 auto maybe_line_height = parse_css_value(component_values[i + 2]);
5193 if (!(maybe_line_height && property_accepts_value(PropertyID::LineHeight, *maybe_line_height)))
5194 return nullptr;
5195 line_height = maybe_line_height.release_nonnull();
5196 i += 2;
5197 }
5198 }
5199
5200 // Consume font-families
5201 auto maybe_font_families = parse_font_family_value(component_values, i + 1);
5202 if (!maybe_font_families)
5203 return nullptr;
5204 font_families = maybe_font_families.release_nonnull();
5205 break;
5206 }
5207 if (property_accepts_value(PropertyID::FontStretch, *value)) {
5208 if (font_stretch)
5209 return nullptr;
5210 font_stretch = value.release_nonnull();
5211 continue;
5212 }
5213 return nullptr;
5214 }
5215
5216 // Since normal is the default value for all the properties that can have it, we don't have to actually
5217 // set anything to normal here. It'll be set when we create the FontStyleValue below.
5218 // We just need to make sure we were not given more normals than will fit.
5219 int unset_value_count = (font_style ? 0 : 1) + (font_weight ? 0 : 1);
5220 if (unset_value_count < normal_count)
5221 return nullptr;
5222
5223 if (!font_size || !font_families)
5224 return nullptr;
5225
5226 if (!font_stretch)
5227 font_stretch = property_initial_value(m_context.realm(), PropertyID::FontStretch);
5228 if (!font_style)
5229 font_style = property_initial_value(m_context.realm(), PropertyID::FontStyle);
5230 if (!font_weight)
5231 font_weight = property_initial_value(m_context.realm(), PropertyID::FontWeight);
5232 if (!line_height)
5233 line_height = property_initial_value(m_context.realm(), PropertyID::LineHeight);
5234
5235 return FontStyleValue::create(font_stretch.release_nonnull(), font_style.release_nonnull(), font_weight.release_nonnull(), font_size.release_nonnull(), line_height.release_nonnull(), font_families.release_nonnull());
5236}
5237
5238RefPtr<StyleValue> Parser::parse_font_family_value(Vector<ComponentValue> const& component_values, size_t start_index)
5239{
5240 auto is_comma_or_eof = [&](size_t i) -> bool {
5241 if (i < component_values.size()) {
5242 auto const& maybe_comma = component_values[i];
5243 if (!maybe_comma.is(Token::Type::Comma))
5244 return false;
5245 }
5246 return true;
5247 };
5248
5249 // Note: Font-family names can either be a quoted string, or a keyword, or a series of custom-idents.
5250 // eg, these are equivalent:
5251 // font-family: my cool font\!, serif;
5252 // font-family: "my cool font!", serif;
5253 StyleValueVector font_families;
5254 Vector<DeprecatedString> current_name_parts;
5255 for (size_t i = start_index; i < component_values.size(); ++i) {
5256 auto const& part = component_values[i];
5257
5258 if (part.is(Token::Type::String)) {
5259 // `font-family: my cool "font";` is invalid.
5260 if (!current_name_parts.is_empty())
5261 return nullptr;
5262 if (!is_comma_or_eof(i + 1))
5263 return nullptr;
5264 font_families.append(StringStyleValue::create(String::from_utf8(part.token().string()).release_value_but_fixme_should_propagate_errors()));
5265 i++;
5266 continue;
5267 }
5268 if (part.is(Token::Type::Ident)) {
5269 // If this is a valid identifier, it's NOT a custom-ident and can't be part of a larger name.
5270 auto maybe_ident = parse_css_value(part);
5271 if (maybe_ident) {
5272 // CSS-wide keywords are not allowed
5273 if (maybe_ident->is_builtin())
5274 return nullptr;
5275 if (is_generic_font_family(maybe_ident->to_identifier())) {
5276 // Can't have a generic-font-name as a token in an unquoted font name.
5277 if (!current_name_parts.is_empty())
5278 return nullptr;
5279 if (!is_comma_or_eof(i + 1))
5280 return nullptr;
5281 font_families.append(maybe_ident.release_nonnull());
5282 i++;
5283 continue;
5284 }
5285 }
5286 current_name_parts.append(part.token().ident());
5287 continue;
5288 }
5289 if (part.is(Token::Type::Comma)) {
5290 if (current_name_parts.is_empty())
5291 return nullptr;
5292 font_families.append(StringStyleValue::create(String::from_utf8(DeprecatedString::join(' ', current_name_parts)).release_value_but_fixme_should_propagate_errors()));
5293 current_name_parts.clear();
5294 // Can't have a trailing comma
5295 if (i + 1 == component_values.size())
5296 return nullptr;
5297 continue;
5298 }
5299 }
5300
5301 if (!current_name_parts.is_empty()) {
5302 font_families.append(StringStyleValue::create(String::from_utf8(DeprecatedString::join(' ', current_name_parts)).release_value_but_fixme_should_propagate_errors()));
5303 current_name_parts.clear();
5304 }
5305
5306 if (font_families.is_empty())
5307 return nullptr;
5308 return StyleValueList::create(move(font_families), StyleValueList::Separator::Comma);
5309}
5310
5311CSSRule* Parser::parse_font_face_rule(TokenStream<ComponentValue>& tokens)
5312{
5313 auto declarations_and_at_rules = parse_a_list_of_declarations(tokens);
5314
5315 Optional<FlyString> font_family;
5316 Vector<FontFace::Source> src;
5317 Vector<UnicodeRange> unicode_range;
5318
5319 for (auto& declaration_or_at_rule : declarations_and_at_rules) {
5320 if (declaration_or_at_rule.is_at_rule()) {
5321 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: CSS at-rules are not allowed in @font-family; discarding.");
5322 continue;
5323 }
5324
5325 auto const& declaration = declaration_or_at_rule.declaration();
5326 if (declaration.name().equals_ignoring_ascii_case("font-family"sv)) {
5327 // FIXME: This is very similar to, but different from, the logic in parse_font_family_value().
5328 // Ideally they could share code.
5329 Vector<DeprecatedString> font_family_parts;
5330 bool had_syntax_error = false;
5331 for (size_t i = 0; i < declaration.values().size(); ++i) {
5332 auto const& part = declaration.values()[i];
5333 if (part.is(Token::Type::Whitespace))
5334 continue;
5335 if (part.is(Token::Type::String)) {
5336 if (!font_family_parts.is_empty()) {
5337 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
5338 had_syntax_error = true;
5339 break;
5340 }
5341 font_family_parts.append(part.token().string());
5342 continue;
5343 }
5344 if (part.is(Token::Type::Ident)) {
5345 if (is_builtin(part.token().ident())) {
5346 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
5347 had_syntax_error = true;
5348 break;
5349 }
5350 auto value_id = value_id_from_string(part.token().ident());
5351 if (is_generic_font_family(value_id)) {
5352 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
5353 had_syntax_error = true;
5354 break;
5355 }
5356 font_family_parts.append(part.token().ident());
5357 continue;
5358 }
5359
5360 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
5361 had_syntax_error = true;
5362 break;
5363 }
5364 if (had_syntax_error || font_family_parts.is_empty())
5365 continue;
5366
5367 font_family = String::join(' ', font_family_parts).release_value_but_fixme_should_propagate_errors();
5368 continue;
5369 }
5370 if (declaration.name().equals_ignoring_ascii_case("src"sv)) {
5371 TokenStream token_stream { declaration.values() };
5372 Vector<FontFace::Source> supported_sources = parse_font_face_src(token_stream);
5373 if (!supported_sources.is_empty())
5374 src = move(supported_sources);
5375 continue;
5376 }
5377 if (declaration.name().equals_ignoring_ascii_case("unicode-range"sv)) {
5378 Vector<UnicodeRange> unicode_ranges;
5379 bool unicode_range_invalid = false;
5380 TokenStream all_tokens { declaration.values() };
5381 auto range_token_lists = parse_a_comma_separated_list_of_component_values(all_tokens);
5382 for (auto& range_tokens : range_token_lists) {
5383 TokenStream range_token_stream { range_tokens };
5384 auto maybe_unicode_range = parse_unicode_range(range_token_stream);
5385 if (!maybe_unicode_range.has_value()) {
5386 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face unicode-range format invalid; discarding.");
5387 unicode_range_invalid = true;
5388 break;
5389 }
5390 unicode_ranges.append(maybe_unicode_range.release_value());
5391 }
5392
5393 if (unicode_range_invalid || unicode_ranges.is_empty())
5394 continue;
5395
5396 unicode_range = move(unicode_ranges);
5397 continue;
5398 }
5399
5400 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unrecognized descriptor '{}' in @font-family; discarding.", declaration.name());
5401 }
5402
5403 if (!font_family.has_value()) {
5404 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face: no font-family!");
5405 return {};
5406 }
5407
5408 if (unicode_range.is_empty()) {
5409 unicode_range.empend(0x0u, 0x10FFFFu);
5410 }
5411
5412 return CSSFontFaceRule::create(m_context.realm(), FontFace { font_family.release_value(), move(src), move(unicode_range) }).release_value_but_fixme_should_propagate_errors();
5413}
5414
5415Vector<FontFace::Source> Parser::parse_font_face_src(TokenStream<ComponentValue>& component_values)
5416{
5417 // FIXME: Get this information from the system somehow?
5418 // Format-name table: https://www.w3.org/TR/css-fonts-4/#font-format-definitions
5419 auto font_format_is_supported = [](StringView name) {
5420 // The spec requires us to treat opentype and truetype as synonymous.
5421 if (name.is_one_of_ignoring_ascii_case("opentype"sv, "truetype"sv, "woff"sv))
5422 return true;
5423 return false;
5424 };
5425
5426 Vector<FontFace::Source> supported_sources;
5427
5428 auto list_of_source_token_lists = parse_a_comma_separated_list_of_component_values(component_values);
5429 for (auto const& source_token_list : list_of_source_token_lists) {
5430 TokenStream source_tokens { source_token_list };
5431 source_tokens.skip_whitespace();
5432 auto const& first = source_tokens.next_token();
5433
5434 // <url> [ format(<font-format>)]?
5435 // FIXME: Implement optional tech() function from CSS-Fonts-4.
5436 if (auto maybe_url = parse_url_function(first, AllowedDataUrlType::Font); maybe_url.has_value()) {
5437 auto url = maybe_url.release_value();
5438 Optional<FlyString> format;
5439
5440 source_tokens.skip_whitespace();
5441 if (!source_tokens.has_next_token()) {
5442 supported_sources.empend(move(url), format);
5443 continue;
5444 }
5445
5446 auto maybe_function = source_tokens.next_token();
5447 if (!maybe_function.is_function()) {
5448 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (token after `url()` that isn't a function: {}); discarding.", maybe_function.to_debug_string());
5449 return {};
5450 }
5451
5452 auto const& function = maybe_function.function();
5453 if (function.name().equals_ignoring_ascii_case("format"sv)) {
5454 TokenStream format_tokens { function.values() };
5455 format_tokens.skip_whitespace();
5456 auto const& format_name_token = format_tokens.next_token();
5457 StringView format_name;
5458 if (format_name_token.is(Token::Type::Ident)) {
5459 format_name = format_name_token.token().ident();
5460 } else if (format_name_token.is(Token::Type::String)) {
5461 format_name = format_name_token.token().string();
5462 } else {
5463 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());
5464 return {};
5465 }
5466
5467 if (!font_format_is_supported(format_name)) {
5468 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src format({}) not supported; skipping.", format_name);
5469 continue;
5470 }
5471
5472 format = FlyString::from_utf8(format_name).release_value_but_fixme_should_propagate_errors();
5473 } else {
5474 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (unrecognized function token `{}`); discarding.", function.name());
5475 return {};
5476 }
5477
5478 source_tokens.skip_whitespace();
5479 if (source_tokens.has_next_token()) {
5480 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (extra token `{}`); discarding.", source_tokens.peek_token().to_debug_string());
5481 return {};
5482 }
5483
5484 supported_sources.empend(move(url), format);
5485 continue;
5486 }
5487
5488 // FIXME: Implement `local()`.
5489 dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (failed to parse url from: {}); discarding.", first.to_debug_string());
5490 return {};
5491 }
5492
5493 return supported_sources;
5494}
5495
5496RefPtr<StyleValue> Parser::parse_list_style_value(Vector<ComponentValue> const& component_values)
5497{
5498 if (component_values.size() > 3)
5499 return nullptr;
5500
5501 RefPtr<StyleValue> list_position;
5502 RefPtr<StyleValue> list_image;
5503 RefPtr<StyleValue> list_type;
5504 int found_nones = 0;
5505
5506 for (auto const& part : component_values) {
5507 auto value = parse_css_value(part);
5508 if (!value)
5509 return nullptr;
5510
5511 if (value->to_identifier() == ValueID::None) {
5512 found_nones++;
5513 continue;
5514 }
5515
5516 if (property_accepts_value(PropertyID::ListStylePosition, *value)) {
5517 if (list_position)
5518 return nullptr;
5519 list_position = value.release_nonnull();
5520 continue;
5521 }
5522 if (property_accepts_value(PropertyID::ListStyleImage, *value)) {
5523 if (list_image)
5524 return nullptr;
5525 list_image = value.release_nonnull();
5526 continue;
5527 }
5528 if (property_accepts_value(PropertyID::ListStyleType, *value)) {
5529 if (list_type)
5530 return nullptr;
5531 list_type = value.release_nonnull();
5532 continue;
5533 }
5534 }
5535
5536 if (found_nones > 2)
5537 return nullptr;
5538
5539 if (found_nones == 2) {
5540 if (list_image || list_type)
5541 return nullptr;
5542 auto none = IdentifierStyleValue::create(ValueID::None);
5543 list_image = none;
5544 list_type = none;
5545
5546 } else if (found_nones == 1) {
5547 if (list_image && list_type)
5548 return nullptr;
5549 auto none = IdentifierStyleValue::create(ValueID::None);
5550 if (!list_image)
5551 list_image = none;
5552 if (!list_type)
5553 list_type = none;
5554 }
5555
5556 if (!list_position)
5557 list_position = property_initial_value(m_context.realm(), PropertyID::ListStylePosition);
5558 if (!list_image)
5559 list_image = property_initial_value(m_context.realm(), PropertyID::ListStyleImage);
5560 if (!list_type)
5561 list_type = property_initial_value(m_context.realm(), PropertyID::ListStyleType);
5562
5563 return ListStyleStyleValue::create(list_position.release_nonnull(), list_image.release_nonnull(), list_type.release_nonnull());
5564}
5565
5566RefPtr<StyleValue> Parser::parse_overflow_value(Vector<ComponentValue> const& component_values)
5567{
5568 if (component_values.size() == 1) {
5569 auto maybe_value = parse_css_value(component_values.first());
5570 if (!maybe_value)
5571 return nullptr;
5572 auto value = maybe_value.release_nonnull();
5573 if (property_accepts_value(PropertyID::Overflow, *value))
5574 return OverflowStyleValue::create(value, value);
5575 return nullptr;
5576 }
5577
5578 if (component_values.size() == 2) {
5579 auto maybe_x_value = parse_css_value(component_values[0]);
5580 auto maybe_y_value = parse_css_value(component_values[1]);
5581
5582 if (!maybe_x_value || !maybe_y_value)
5583 return nullptr;
5584 auto x_value = maybe_x_value.release_nonnull();
5585 auto y_value = maybe_y_value.release_nonnull();
5586 if (!property_accepts_value(PropertyID::OverflowX, x_value) || !property_accepts_value(PropertyID::OverflowY, y_value)) {
5587 return nullptr;
5588 }
5589 return OverflowStyleValue::create(x_value, y_value);
5590 }
5591
5592 return nullptr;
5593}
5594
5595RefPtr<StyleValue> Parser::parse_text_decoration_value(Vector<ComponentValue> const& component_values)
5596{
5597 RefPtr<StyleValue> decoration_line;
5598 RefPtr<StyleValue> decoration_thickness;
5599 RefPtr<StyleValue> decoration_style;
5600 RefPtr<StyleValue> decoration_color;
5601
5602 auto tokens = TokenStream { component_values };
5603
5604 while (tokens.has_next_token()) {
5605 auto const& part = tokens.next_token();
5606 auto value = parse_css_value(part);
5607 if (!value)
5608 return nullptr;
5609
5610 if (property_accepts_value(PropertyID::TextDecorationColor, *value)) {
5611 if (decoration_color)
5612 return nullptr;
5613 decoration_color = value.release_nonnull();
5614 continue;
5615 }
5616 if (property_accepts_value(PropertyID::TextDecorationLine, *value)) {
5617 if (decoration_line)
5618 return nullptr;
5619 tokens.reconsume_current_input_token();
5620 auto parsed_decoration_line = parse_text_decoration_line_value(tokens);
5621 if (!parsed_decoration_line)
5622 return nullptr;
5623 decoration_line = parsed_decoration_line.release_nonnull();
5624 continue;
5625 }
5626 if (property_accepts_value(PropertyID::TextDecorationThickness, *value)) {
5627 if (decoration_thickness)
5628 return nullptr;
5629 decoration_thickness = value.release_nonnull();
5630 continue;
5631 }
5632 if (property_accepts_value(PropertyID::TextDecorationStyle, *value)) {
5633 if (decoration_style)
5634 return nullptr;
5635 decoration_style = value.release_nonnull();
5636 continue;
5637 }
5638
5639 return nullptr;
5640 }
5641
5642 if (!decoration_line)
5643 decoration_line = property_initial_value(m_context.realm(), PropertyID::TextDecorationLine);
5644 if (!decoration_thickness)
5645 decoration_thickness = property_initial_value(m_context.realm(), PropertyID::TextDecorationThickness);
5646 if (!decoration_style)
5647 decoration_style = property_initial_value(m_context.realm(), PropertyID::TextDecorationStyle);
5648 if (!decoration_color)
5649 decoration_color = property_initial_value(m_context.realm(), PropertyID::TextDecorationColor);
5650
5651 return TextDecorationStyleValue::create(decoration_line.release_nonnull(), decoration_thickness.release_nonnull(), decoration_style.release_nonnull(), decoration_color.release_nonnull());
5652}
5653
5654RefPtr<StyleValue> Parser::parse_text_decoration_line_value(TokenStream<ComponentValue>& tokens)
5655{
5656 StyleValueVector style_values;
5657
5658 while (tokens.has_next_token()) {
5659 auto const& token = tokens.next_token();
5660 auto maybe_value = parse_css_value(token);
5661 if (!maybe_value || !property_accepts_value(PropertyID::TextDecorationLine, *maybe_value)) {
5662 tokens.reconsume_current_input_token();
5663 break;
5664 }
5665 auto value = maybe_value.release_nonnull();
5666
5667 if (auto maybe_line = value_id_to_text_decoration_line(value->to_identifier()); maybe_line.has_value()) {
5668 auto line = maybe_line.release_value();
5669 if (line == TextDecorationLine::None) {
5670 if (!style_values.is_empty()) {
5671 tokens.reconsume_current_input_token();
5672 break;
5673 }
5674 return value;
5675 }
5676 if (style_values.contains_slow(value)) {
5677 tokens.reconsume_current_input_token();
5678 break;
5679 }
5680 style_values.append(move(value));
5681 continue;
5682 }
5683
5684 tokens.reconsume_current_input_token();
5685 break;
5686 }
5687
5688 if (style_values.is_empty())
5689 return nullptr;
5690 return StyleValueList::create(move(style_values), StyleValueList::Separator::Space);
5691}
5692
5693RefPtr<StyleValue> Parser::parse_transform_value(Vector<ComponentValue> const& component_values)
5694{
5695 StyleValueVector transformations;
5696 auto tokens = TokenStream { component_values };
5697 tokens.skip_whitespace();
5698
5699 while (tokens.has_next_token()) {
5700 tokens.skip_whitespace();
5701 auto const& part = tokens.next_token();
5702
5703 if (part.is(Token::Type::Ident) && part.token().ident().equals_ignoring_ascii_case("none"sv)) {
5704 if (!transformations.is_empty())
5705 return nullptr;
5706 tokens.skip_whitespace();
5707 if (tokens.has_next_token())
5708 return nullptr;
5709 return IdentifierStyleValue::create(ValueID::None);
5710 }
5711
5712 if (!part.is_function())
5713 return nullptr;
5714 auto maybe_function = transform_function_from_string(part.function().name());
5715 if (!maybe_function.has_value())
5716 return nullptr;
5717 auto function = maybe_function.release_value();
5718 auto function_metadata = transform_function_metadata(function);
5719
5720 StyleValueVector values;
5721 auto argument_tokens = TokenStream { part.function().values() };
5722 argument_tokens.skip_whitespace();
5723 size_t argument_index = 0;
5724 while (argument_tokens.has_next_token()) {
5725 if (argument_index == function_metadata.parameters.size()) {
5726 dbgln_if(CSS_PARSER_DEBUG, "Too many arguments to {}. max: {}", part.function().name(), function_metadata.parameters.size());
5727 return nullptr;
5728 }
5729
5730 auto const& value = argument_tokens.next_token();
5731 RefPtr<CalculatedStyleValue> maybe_calc_value;
5732 if (auto maybe_dynamic_value = parse_dynamic_value(value)) {
5733 // TODO: calc() is the only dynamic value we support for now, but more will come later.
5734 // FIXME: Actually, calc() should probably be parsed inside parse_dimension_value() etc,
5735 // so that it affects every use instead of us having to manually implement it.
5736 VERIFY(maybe_dynamic_value->is_calculated());
5737 maybe_calc_value = maybe_dynamic_value->as_calculated();
5738 }
5739
5740 switch (function_metadata.parameters[argument_index].type) {
5741 case TransformFunctionParameterType::Angle: {
5742 // These are `<angle> | <zero>` in the spec, so we have to check for both kinds.
5743 if (maybe_calc_value && maybe_calc_value->resolves_to_angle()) {
5744 values.append(AngleStyleValue::create(Angle::make_calculated(maybe_calc_value.release_nonnull())));
5745 } else if (value.is(Token::Type::Number) && value.token().number_value() == 0) {
5746 values.append(AngleStyleValue::create(Angle::make_degrees(0)));
5747 } else {
5748 auto dimension_value = parse_dimension_value(value);
5749 if (!dimension_value || !dimension_value->is_angle())
5750 return nullptr;
5751 values.append(dimension_value.release_nonnull());
5752 }
5753 break;
5754 }
5755 case TransformFunctionParameterType::Length: {
5756 if (maybe_calc_value && maybe_calc_value->resolves_to_length()) {
5757 values.append(LengthStyleValue::create(Length::make_calculated(maybe_calc_value.release_nonnull())));
5758 } else {
5759 auto dimension_value = parse_dimension_value(value);
5760 if (!dimension_value)
5761 return nullptr;
5762
5763 if (dimension_value->is_length())
5764 values.append(dimension_value.release_nonnull());
5765 else
5766 return nullptr;
5767 }
5768 break;
5769 }
5770 case TransformFunctionParameterType::LengthPercentage: {
5771 if (maybe_calc_value && maybe_calc_value->resolves_to_length()) {
5772 values.append(LengthStyleValue::create(Length::make_calculated(maybe_calc_value.release_nonnull())));
5773 } else {
5774 auto dimension_value = parse_dimension_value(value);
5775 if (!dimension_value)
5776 return nullptr;
5777
5778 if (dimension_value->is_percentage() || dimension_value->is_length())
5779 values.append(dimension_value.release_nonnull());
5780 else
5781 return nullptr;
5782 }
5783 break;
5784 }
5785 case TransformFunctionParameterType::Number: {
5786 if (maybe_calc_value && maybe_calc_value->resolves_to_number()) {
5787 values.append(LengthStyleValue::create(Length::make_calculated(maybe_calc_value.release_nonnull())));
5788 } else {
5789 auto number = parse_numeric_value(value);
5790 if (!number)
5791 return nullptr;
5792 values.append(number.release_nonnull());
5793 }
5794 break;
5795 }
5796 }
5797
5798 argument_tokens.skip_whitespace();
5799 if (argument_tokens.has_next_token()) {
5800 // Arguments must be separated by commas.
5801 if (!argument_tokens.next_token().is(Token::Type::Comma))
5802 return nullptr;
5803 argument_tokens.skip_whitespace();
5804
5805 // If there are no more parameters after the comma, this is invalid.
5806 if (!argument_tokens.has_next_token())
5807 return nullptr;
5808 }
5809
5810 argument_index++;
5811 }
5812
5813 if (argument_index < function_metadata.parameters.size() && function_metadata.parameters[argument_index].required) {
5814 dbgln_if(CSS_PARSER_DEBUG, "Required parameter at position {} is missing", argument_index);
5815 return nullptr;
5816 }
5817
5818 transformations.append(TransformationStyleValue::create(function, move(values)));
5819 }
5820 return StyleValueList::create(move(transformations), StyleValueList::Separator::Space);
5821}
5822
5823// https://www.w3.org/TR/css-transforms-1/#propdef-transform-origin
5824// FIXME: This only supports a 2D position
5825RefPtr<StyleValue> Parser::parse_transform_origin_value(Vector<ComponentValue> const& component_values)
5826{
5827 enum class Axis {
5828 None,
5829 X,
5830 Y,
5831 };
5832
5833 struct AxisOffset {
5834 Axis axis;
5835 NonnullRefPtr<StyleValue> offset;
5836 };
5837
5838 auto to_axis_offset = [](RefPtr<StyleValue> value) -> Optional<AxisOffset> {
5839 if (value->is_percentage())
5840 return AxisOffset { Axis::None, value->as_percentage() };
5841 if (value->is_length())
5842 return AxisOffset { Axis::None, value->as_length() };
5843 if (value->has_length())
5844 return AxisOffset { Axis::None, LengthStyleValue::create(value->to_length()) };
5845 if (value->is_identifier()) {
5846 switch (value->to_identifier()) {
5847 case ValueID::Top:
5848 return AxisOffset { Axis::Y, PercentageStyleValue::create(Percentage(0)) };
5849 case ValueID::Left:
5850 return AxisOffset { Axis::X, PercentageStyleValue::create(Percentage(0)) };
5851 case ValueID::Center:
5852 return AxisOffset { Axis::None, PercentageStyleValue::create(Percentage(50)) };
5853 case ValueID::Bottom:
5854 return AxisOffset { Axis::Y, PercentageStyleValue::create(Percentage(100)) };
5855 case ValueID::Right:
5856 return AxisOffset { Axis::X, PercentageStyleValue::create(Percentage(100)) };
5857 default:
5858 return {};
5859 }
5860 }
5861 return {};
5862 };
5863
5864 auto make_list = [](NonnullRefPtr<StyleValue> const& x_value, NonnullRefPtr<StyleValue> const& y_value) -> NonnullRefPtr<StyleValueList> {
5865 StyleValueVector values;
5866 values.append(x_value);
5867 values.append(y_value);
5868 return StyleValueList::create(move(values), StyleValueList::Separator::Space);
5869 };
5870
5871 switch (component_values.size()) {
5872 case 1: {
5873 auto single_value = to_axis_offset(parse_css_value(component_values[0]));
5874 if (!single_value.has_value())
5875 return nullptr;
5876 // If only one value is specified, the second value is assumed to be center.
5877 // FIXME: If one or two values are specified, the third value is assumed to be 0px.
5878 switch (single_value->axis) {
5879 case Axis::None:
5880 case Axis::X:
5881 return make_list(single_value->offset, PercentageStyleValue::create(Percentage(50)));
5882 case Axis::Y:
5883 return make_list(PercentageStyleValue::create(Percentage(50)), single_value->offset);
5884 }
5885 VERIFY_NOT_REACHED();
5886 }
5887 case 2: {
5888 auto first_value = to_axis_offset(parse_css_value(component_values[0]));
5889 auto second_value = to_axis_offset(parse_css_value(component_values[1]));
5890 if (!first_value.has_value() || !second_value.has_value())
5891 return nullptr;
5892
5893 RefPtr<StyleValue> x_value;
5894 RefPtr<StyleValue> y_value;
5895
5896 if (first_value->axis == Axis::X) {
5897 x_value = first_value->offset;
5898 } else if (first_value->axis == Axis::Y) {
5899 y_value = first_value->offset;
5900 }
5901
5902 if (second_value->axis == Axis::X) {
5903 if (x_value)
5904 return nullptr;
5905 x_value = second_value->offset;
5906 // Put the other in Y since its axis can't have been X
5907 y_value = first_value->offset;
5908 } else if (second_value->axis == Axis::Y) {
5909 if (y_value)
5910 return nullptr;
5911 y_value = second_value->offset;
5912 // Put the other in X since its axis can't have been Y
5913 x_value = first_value->offset;
5914 } else {
5915 if (x_value) {
5916 VERIFY(!y_value);
5917 y_value = second_value->offset;
5918 } else {
5919 VERIFY(!x_value);
5920 x_value = second_value->offset;
5921 }
5922 }
5923 // If two or more values are defined and either no value is a keyword, or the only used keyword is center,
5924 // then the first value represents the horizontal position (or offset) and the second represents the vertical position (or offset).
5925 // FIXME: A third value always represents the Z position (or offset) and must be of type <length>.
5926 if (first_value->axis == Axis::None && second_value->axis == Axis::None) {
5927 x_value = first_value->offset;
5928 y_value = second_value->offset;
5929 }
5930 return make_list(x_value.release_nonnull(), y_value.release_nonnull());
5931 }
5932 }
5933
5934 return nullptr;
5935}
5936
5937RefPtr<StyleValue> Parser::parse_as_css_value(PropertyID property_id)
5938{
5939 auto component_values = parse_a_list_of_component_values(m_token_stream);
5940 auto tokens = TokenStream(component_values);
5941 auto parsed_value = parse_css_value(property_id, tokens);
5942 if (parsed_value.is_error())
5943 return {};
5944 return parsed_value.release_value();
5945}
5946
5947Optional<CSS::GridSize> Parser::parse_grid_size(ComponentValue const& component_value)
5948{
5949 // FIXME: Parse calc here if necessary
5950 if (component_value.is_function())
5951 return {};
5952 auto token = component_value.token();
5953 if (token.is(Token::Type::Dimension) && token.dimension_unit().equals_ignoring_ascii_case("fr"sv)) {
5954 float numeric_value = token.dimension_value();
5955 if (numeric_value)
5956 return GridSize(numeric_value);
5957 }
5958 if (token.is(Token::Type::Ident) && token.ident().equals_ignoring_ascii_case("auto"sv))
5959 return GridSize::make_auto();
5960 if (token.is(Token::Type::Ident) && token.ident().equals_ignoring_ascii_case("max-content"sv))
5961 return GridSize(GridSize::Type::MaxContent);
5962 if (token.is(Token::Type::Ident) && token.ident().equals_ignoring_ascii_case("min-content"sv))
5963 return GridSize(GridSize::Type::MinContent);
5964 auto dimension = parse_dimension(token);
5965 if (!dimension.has_value())
5966 return {};
5967 if (dimension->is_length())
5968 return GridSize(dimension->length());
5969 else if (dimension->is_percentage())
5970 return GridSize(dimension->percentage());
5971 return {};
5972}
5973
5974Optional<CSS::GridMinMax> Parser::parse_min_max(Vector<ComponentValue> const& component_values)
5975{
5976 // https://www.w3.org/TR/css-grid-2/#valdef-grid-template-columns-minmax
5977 // 'minmax(min, max)'
5978 // Defines a size range greater than or equal to min and less than or equal to max. If the max is
5979 // less than the min, then the max will be floored by the min (essentially yielding minmax(min,
5980 // min)). As a maximum, a <flex> value sets the track’s flex factor; it is invalid as a minimum.
5981 auto function_tokens = TokenStream(component_values);
5982 auto comma_separated_list = parse_a_comma_separated_list_of_component_values(function_tokens);
5983 if (comma_separated_list.size() != 2)
5984 return {};
5985
5986 TokenStream part_one_tokens { comma_separated_list[0] };
5987 part_one_tokens.skip_whitespace();
5988 if (!part_one_tokens.has_next_token())
5989 return {};
5990 auto current_token = part_one_tokens.next_token();
5991 auto min_grid_size = parse_grid_size(current_token);
5992
5993 TokenStream part_two_tokens { comma_separated_list[1] };
5994 part_two_tokens.skip_whitespace();
5995 if (!part_two_tokens.has_next_token())
5996 return {};
5997 current_token = part_two_tokens.next_token();
5998 auto max_grid_size = parse_grid_size(current_token);
5999
6000 if (min_grid_size.has_value() && max_grid_size.has_value()) {
6001 // https://www.w3.org/TR/css-grid-2/#valdef-grid-template-columns-minmax
6002 // As a maximum, a <flex> value sets the track’s flex factor; it is invalid as a minimum.
6003 if (min_grid_size.value().is_flexible_length())
6004 return {};
6005 return CSS::GridMinMax(min_grid_size.value(), max_grid_size.value());
6006 }
6007 return {};
6008}
6009
6010Optional<CSS::GridRepeat> Parser::parse_repeat(Vector<ComponentValue> const& component_values)
6011{
6012 // https://www.w3.org/TR/css-grid-2/#repeat-syntax
6013 // 7.2.3.1. Syntax of repeat()
6014 // The generic form of the repeat() syntax is, approximately,
6015 // repeat( [ <integer [1,∞]> | auto-fill | auto-fit ] , <track-list> )
6016 auto is_auto_fill = false;
6017 auto is_auto_fit = false;
6018 auto function_tokens = TokenStream(component_values);
6019 auto comma_separated_list = parse_a_comma_separated_list_of_component_values(function_tokens);
6020 if (comma_separated_list.size() != 2)
6021 return {};
6022 // The first argument specifies the number of repetitions.
6023 TokenStream part_one_tokens { comma_separated_list[0] };
6024 part_one_tokens.skip_whitespace();
6025 if (!part_one_tokens.has_next_token())
6026 return {};
6027 auto current_token = part_one_tokens.next_token().token();
6028
6029 auto repeat_count = 0;
6030 if (current_token.is(Token::Type::Number) && current_token.number().is_integer() && current_token.number_value() > 0)
6031 repeat_count = current_token.number_value();
6032 else if (current_token.is(Token::Type::Ident) && current_token.ident().equals_ignoring_ascii_case("auto-fill"sv))
6033 is_auto_fill = true;
6034 else if (current_token.is(Token::Type::Ident) && current_token.ident().equals_ignoring_ascii_case("auto-fit"sv))
6035 is_auto_fit = true;
6036
6037 // The second argument is a track list, which is repeated that number of times.
6038 TokenStream part_two_tokens { comma_separated_list[1] };
6039 part_two_tokens.skip_whitespace();
6040 if (!part_two_tokens.has_next_token())
6041 return {};
6042
6043 Vector<CSS::ExplicitGridTrack> repeat_params;
6044 Vector<Vector<String>> line_names_list;
6045 auto last_object_was_line_names = false;
6046 while (part_two_tokens.has_next_token()) {
6047 auto token = part_two_tokens.next_token();
6048 Vector<String> line_names;
6049 if (token.is_block()) {
6050 if (last_object_was_line_names)
6051 return {};
6052 last_object_was_line_names = true;
6053 if (!token.block().is_square())
6054 return {};
6055 TokenStream block_tokens { token.block().values() };
6056 while (block_tokens.has_next_token()) {
6057 auto current_block_token = block_tokens.next_token();
6058 auto maybe_string = String::from_utf8(current_block_token.token().ident());
6059 if (maybe_string.is_error())
6060 return {};
6061 line_names.append(maybe_string.value());
6062 block_tokens.skip_whitespace();
6063 }
6064 line_names_list.append(line_names);
6065 part_two_tokens.skip_whitespace();
6066 } else {
6067 last_object_was_line_names = false;
6068 auto track_sizing_function = parse_track_sizing_function(token);
6069 if (!track_sizing_function.has_value())
6070 return {};
6071 // However, there are some restrictions:
6072 // The repeat() notation can’t be nested.
6073 if (track_sizing_function.value().is_repeat())
6074 return {};
6075 // Automatic repetitions (auto-fill or auto-fit) cannot be combined with intrinsic or flexible sizes.
6076 if (track_sizing_function.value().is_default() && track_sizing_function.value().grid_size().is_flexible_length() && (is_auto_fill || is_auto_fit))
6077 return {};
6078 repeat_params.append(track_sizing_function.value());
6079 part_two_tokens.skip_whitespace();
6080 }
6081 }
6082 while (line_names_list.size() <= repeat_params.size())
6083 line_names_list.append({});
6084
6085 // Thus the precise syntax of the repeat() notation has several forms:
6086 // <track-repeat> = repeat( [ <integer [1,∞]> ] , [ <line-names>? <track-size> ]+ <line-names>? )
6087 // <auto-repeat> = repeat( [ auto-fill | auto-fit ] , [ <line-names>? <fixed-size> ]+ <line-names>? )
6088 // <fixed-repeat> = repeat( [ <integer [1,∞]> ] , [ <line-names>? <fixed-size> ]+ <line-names>? )
6089 // <name-repeat> = repeat( [ <integer [1,∞]> | auto-fill ], <line-names>+)
6090
6091 // The <track-repeat> variant can represent the repetition of any <track-size>, but is limited to a
6092 // fixed number of repetitions.
6093
6094 // The <auto-repeat> variant can repeat automatically to fill a space, but requires definite track
6095 // sizes so that the number of repetitions can be calculated. It can only appear once in the track
6096 // list, but the same track list can also contain <fixed-repeat>s.
6097
6098 // The <name-repeat> variant is for adding line names to subgrids. It can only be used with the
6099 // subgrid keyword and cannot specify track sizes, only line names.
6100
6101 // If a repeat() function that is not a <name-repeat> ends up placing two <line-names> adjacent to
6102 // each other, the name lists are merged. For example, repeat(2, [a] 1fr [b]) is equivalent to [a]
6103 // 1fr [b a] 1fr [b].
6104 if (is_auto_fill)
6105 return CSS::GridRepeat(CSS::GridTrackSizeList(repeat_params, line_names_list), CSS::GridRepeat::Type::AutoFill);
6106 else if (is_auto_fit)
6107 return CSS::GridRepeat(CSS::GridTrackSizeList(repeat_params, line_names_list), CSS::GridRepeat::Type::AutoFit);
6108 else
6109 return CSS::GridRepeat(CSS::GridTrackSizeList(repeat_params, line_names_list), repeat_count);
6110}
6111
6112Optional<CSS::ExplicitGridTrack> Parser::parse_track_sizing_function(ComponentValue const& token)
6113{
6114 if (token.is_function()) {
6115 auto const& function_token = token.function();
6116 if (function_token.name().equals_ignoring_ascii_case("repeat"sv)) {
6117 auto maybe_repeat = parse_repeat(function_token.values());
6118 if (maybe_repeat.has_value())
6119 return CSS::ExplicitGridTrack(maybe_repeat.value());
6120 else
6121 return {};
6122 } else if (function_token.name().equals_ignoring_ascii_case("minmax"sv)) {
6123 auto maybe_min_max_value = parse_min_max(function_token.values());
6124 if (maybe_min_max_value.has_value())
6125 return CSS::ExplicitGridTrack(maybe_min_max_value.value());
6126 else
6127 return {};
6128 }
6129 return {};
6130 } else if (token.is(Token::Type::Ident) && token.token().ident().equals_ignoring_ascii_case("auto"sv)) {
6131 return CSS::ExplicitGridTrack(GridSize(Length::make_auto()));
6132 } else if (token.is_block()) {
6133 return {};
6134 } else {
6135 auto grid_size = parse_grid_size(token);
6136 if (!grid_size.has_value())
6137 return {};
6138 return CSS::ExplicitGridTrack(grid_size.value());
6139 }
6140}
6141
6142RefPtr<StyleValue> Parser::parse_grid_track_sizes(Vector<ComponentValue> const& component_values)
6143{
6144 Vector<CSS::ExplicitGridTrack> track_list;
6145 Vector<Vector<String>> line_names_list;
6146 auto last_object_was_line_names = false;
6147 TokenStream tokens { component_values };
6148 while (tokens.has_next_token()) {
6149 auto token = tokens.next_token();
6150 if (token.is_block()) {
6151 if (last_object_was_line_names)
6152 return GridTrackSizeStyleValue::make_auto();
6153 last_object_was_line_names = true;
6154 Vector<String> line_names;
6155 if (!token.block().is_square())
6156 return GridTrackSizeStyleValue::make_auto();
6157 TokenStream block_tokens { token.block().values() };
6158 while (block_tokens.has_next_token()) {
6159 auto current_block_token = block_tokens.next_token();
6160 auto maybe_string = String::from_utf8(current_block_token.token().ident());
6161 if (maybe_string.is_error())
6162 return {};
6163 line_names.append(maybe_string.value());
6164 block_tokens.skip_whitespace();
6165 }
6166 line_names_list.append(line_names);
6167 } else {
6168 last_object_was_line_names = false;
6169 auto track_sizing_function = parse_track_sizing_function(token);
6170 if (!track_sizing_function.has_value())
6171 return GridTrackSizeStyleValue::make_auto();
6172 // FIXME: Handle multiple repeat values (should combine them here, or remove
6173 // any other ones if the first one is auto-fill, etc.)
6174 track_list.append(track_sizing_function.value());
6175 }
6176 }
6177 while (line_names_list.size() <= track_list.size())
6178 line_names_list.append({});
6179 return GridTrackSizeStyleValue::create(CSS::GridTrackSizeList(track_list, line_names_list));
6180}
6181
6182RefPtr<StyleValue> Parser::parse_grid_track_placement(Vector<ComponentValue> const& component_values)
6183{
6184 // https://www.w3.org/TR/css-grid-2/#line-placement
6185 // Line-based Placement: the grid-row-start, grid-column-start, grid-row-end, and grid-column-end properties
6186 // <grid-line> =
6187 // auto |
6188 // <custom-ident> |
6189 // [ <integer> && <custom-ident>? ] |
6190 // [ span && [ <integer> || <custom-ident> ] ]
6191 auto is_auto = [](Token token) -> bool {
6192 if (token.is(Token::Type::Ident) && token.ident().equals_ignoring_ascii_case("auto"sv))
6193 return true;
6194 return false;
6195 };
6196 auto is_span = [](Token token) -> bool {
6197 if (token.is(Token::Type::Ident) && token.ident().equals_ignoring_ascii_case("span"sv))
6198 return true;
6199 return false;
6200 };
6201 auto is_valid_integer = [](Token token) -> bool {
6202 // An <integer> value of zero makes the declaration invalid.
6203 if (token.is(Token::Type::Number) && token.number().is_integer() && token.number_value() != 0)
6204 return true;
6205 return false;
6206 };
6207 auto is_line_name = [](Token token) -> bool {
6208 // The <custom-ident> additionally excludes the keywords span and auto.
6209 if (token.is(Token::Type::Ident) && !token.ident().equals_ignoring_ascii_case("span"sv) && !token.ident().equals_ignoring_ascii_case("auto"sv))
6210 return true;
6211 return false;
6212 };
6213
6214 auto tokens = TokenStream { component_values };
6215 tokens.skip_whitespace();
6216 auto current_token = tokens.next_token().token();
6217
6218 if (!tokens.has_next_token()) {
6219 if (is_auto(current_token))
6220 return GridTrackPlacementStyleValue::create(CSS::GridTrackPlacement());
6221 if (is_span(current_token))
6222 return GridTrackPlacementStyleValue::create(CSS::GridTrackPlacement(1, true));
6223 if (is_valid_integer(current_token))
6224 return GridTrackPlacementStyleValue::create(CSS::GridTrackPlacement(static_cast<int>(current_token.number_value())));
6225 if (is_line_name(current_token)) {
6226 auto maybe_string = String::from_utf8(current_token.ident());
6227 if (!maybe_string.is_error())
6228 return GridTrackPlacementStyleValue::create(CSS::GridTrackPlacement(maybe_string.value()));
6229 }
6230 return {};
6231 }
6232
6233 auto span_value = false;
6234 auto span_or_position_value = 0;
6235 String line_name_value;
6236 while (true) {
6237 if (is_auto(current_token))
6238 return {};
6239 if (is_span(current_token)) {
6240 if (span_value == false)
6241 span_value = true;
6242 else
6243 return {};
6244 }
6245 if (is_valid_integer(current_token)) {
6246 if (span_or_position_value == 0)
6247 span_or_position_value = static_cast<int>(current_token.number_value());
6248 else
6249 return {};
6250 }
6251 if (is_line_name(current_token)) {
6252 if (line_name_value.is_empty()) {
6253 auto maybe_string = String::from_utf8(current_token.ident());
6254 if (maybe_string.is_error())
6255 return {};
6256 line_name_value = maybe_string.release_value();
6257 } else {
6258 return {};
6259 }
6260 }
6261 tokens.skip_whitespace();
6262 if (tokens.has_next_token())
6263 current_token = tokens.next_token().token();
6264 else
6265 break;
6266 }
6267
6268 // Negative integers or zero are invalid.
6269 if (span_value && span_or_position_value < 1)
6270 return {};
6271
6272 // If the <integer> is omitted, it defaults to 1.
6273 if (span_or_position_value == 0)
6274 span_or_position_value = 1;
6275
6276 if (!line_name_value.is_empty())
6277 return GridTrackPlacementStyleValue::create(CSS::GridTrackPlacement(line_name_value, span_or_position_value, span_value));
6278 return GridTrackPlacementStyleValue::create(CSS::GridTrackPlacement(span_or_position_value, span_value));
6279}
6280
6281RefPtr<StyleValue> Parser::parse_grid_track_placement_shorthand_value(Vector<ComponentValue> const& component_values)
6282{
6283 auto tokens = TokenStream { component_values };
6284 auto current_token = tokens.next_token().token();
6285
6286 Vector<ComponentValue> track_start_placement_tokens;
6287 while (true) {
6288 if (current_token.is(Token::Type::Delim) && current_token.delim() == "/"sv)
6289 break;
6290 track_start_placement_tokens.append(current_token);
6291 if (!tokens.has_next_token())
6292 break;
6293 current_token = tokens.next_token().token();
6294 }
6295
6296 Vector<ComponentValue> track_end_placement_tokens;
6297 if (tokens.has_next_token()) {
6298 current_token = tokens.next_token().token();
6299 while (true) {
6300 track_end_placement_tokens.append(current_token);
6301 if (!tokens.has_next_token())
6302 break;
6303 current_token = tokens.next_token().token();
6304 }
6305 }
6306
6307 auto parsed_start_value = parse_grid_track_placement(track_start_placement_tokens);
6308 if (parsed_start_value && track_end_placement_tokens.is_empty())
6309 return GridTrackPlacementShorthandStyleValue::create(parsed_start_value.release_nonnull()->as_grid_track_placement().grid_track_placement());
6310
6311 auto parsed_end_value = parse_grid_track_placement(track_end_placement_tokens);
6312 if (parsed_start_value && parsed_end_value)
6313 return GridTrackPlacementShorthandStyleValue::create(parsed_start_value.release_nonnull()->as_grid_track_placement(), parsed_end_value.release_nonnull()->as_grid_track_placement());
6314
6315 return {};
6316}
6317
6318RefPtr<StyleValue> Parser::parse_grid_area_shorthand_value(Vector<ComponentValue> const& component_values)
6319{
6320 auto tokens = TokenStream { component_values };
6321 Token current_token;
6322
6323 auto parse_placement_tokens = [&](Vector<ComponentValue>& placement_tokens, bool check_for_delimiter = true) -> void {
6324 current_token = tokens.next_token().token();
6325 while (true) {
6326 if (check_for_delimiter && current_token.is(Token::Type::Delim) && current_token.delim() == "/"sv)
6327 break;
6328 placement_tokens.append(current_token);
6329 tokens.skip_whitespace();
6330 if (!tokens.has_next_token())
6331 break;
6332 current_token = tokens.next_token().token();
6333 }
6334 };
6335
6336 Vector<ComponentValue> row_start_placement_tokens;
6337 parse_placement_tokens(row_start_placement_tokens);
6338
6339 Vector<ComponentValue> column_start_placement_tokens;
6340 if (tokens.has_next_token())
6341 parse_placement_tokens(column_start_placement_tokens);
6342
6343 Vector<ComponentValue> row_end_placement_tokens;
6344 if (tokens.has_next_token())
6345 parse_placement_tokens(row_end_placement_tokens);
6346
6347 Vector<ComponentValue> column_end_placement_tokens;
6348 if (tokens.has_next_token())
6349 parse_placement_tokens(column_end_placement_tokens, false);
6350
6351 // https://www.w3.org/TR/css-grid-2/#placement-shorthands
6352 // The grid-area property is a shorthand for grid-row-start, grid-column-start, grid-row-end and
6353 // grid-column-end.
6354 auto row_start_style_value = parse_grid_track_placement(row_start_placement_tokens);
6355 auto column_start_style_value = parse_grid_track_placement(column_start_placement_tokens);
6356 auto row_end_style_value = parse_grid_track_placement(row_end_placement_tokens);
6357 auto column_end_style_value = parse_grid_track_placement(column_end_placement_tokens);
6358
6359 // If four <grid-line> values are specified, grid-row-start is set to the first value, grid-column-start
6360 // is set to the second value, grid-row-end is set to the third value, and grid-column-end is set to the
6361 // fourth value.
6362 auto row_start = GridTrackPlacement::make_auto();
6363 auto column_start = GridTrackPlacement::make_auto();
6364 auto row_end = GridTrackPlacement::make_auto();
6365 auto column_end = GridTrackPlacement::make_auto();
6366
6367 if (row_start_style_value)
6368 row_start = row_start_style_value.release_nonnull()->as_grid_track_placement().grid_track_placement();
6369
6370 // When grid-column-start is omitted, if grid-row-start is a <custom-ident>, all four longhands are set to
6371 // that value. Otherwise, it is set to auto.
6372 if (column_start_style_value)
6373 column_start = column_start_style_value.release_nonnull()->as_grid_track_placement().grid_track_placement();
6374 else
6375 column_start = row_start;
6376
6377 // When grid-row-end is omitted, if grid-row-start is a <custom-ident>, grid-row-end is set to that
6378 // <custom-ident>; otherwise, it is set to auto.
6379 if (row_end_style_value)
6380 row_end = row_end_style_value.release_nonnull()->as_grid_track_placement().grid_track_placement();
6381 else
6382 row_end = column_start;
6383
6384 // When grid-column-end is omitted, if grid-column-start is a <custom-ident>, grid-column-end is set to
6385 // that <custom-ident>; otherwise, it is set to auto.
6386 if (column_end_style_value)
6387 column_end = column_end_style_value.release_nonnull()->as_grid_track_placement().grid_track_placement();
6388 else
6389 column_end = row_end;
6390
6391 return GridAreaShorthandStyleValue::create(row_start, column_start, row_end, column_end);
6392}
6393
6394RefPtr<StyleValue> Parser::parse_grid_template_areas_value(Vector<ComponentValue> const& component_values)
6395{
6396 Vector<Vector<String>> grid_area_rows;
6397 for (auto& component_value : component_values) {
6398 Vector<String> grid_area_columns;
6399 if (component_value.is(Token::Type::String)) {
6400 auto const parts = String::from_utf8(component_value.token().string()).release_value_but_fixme_should_propagate_errors().split(' ').release_value_but_fixme_should_propagate_errors();
6401 for (auto& part : parts) {
6402 grid_area_columns.append(part);
6403 }
6404 }
6405 grid_area_rows.append(move(grid_area_columns));
6406 }
6407 return GridTemplateAreaStyleValue::create(grid_area_rows);
6408}
6409
6410Parser::ParseErrorOr<NonnullRefPtr<StyleValue>> Parser::parse_css_value(PropertyID property_id, TokenStream<ComponentValue>& tokens)
6411{
6412 auto function_contains_var_or_attr = [](Function const& function, auto&& recurse) -> bool {
6413 if (function.name().equals_ignoring_ascii_case("var"sv) || function.name().equals_ignoring_ascii_case("attr"sv))
6414 return true;
6415 for (auto const& token : function.values()) {
6416 if (token.is_function() && recurse(token.function(), recurse))
6417 return true;
6418 }
6419 return false;
6420 };
6421 auto block_contains_var_or_attr = [function_contains_var_or_attr](Block const& block, auto&& recurse) -> bool {
6422 for (auto const& token : block.values()) {
6423 if (token.is_function() && function_contains_var_or_attr(token.function(), function_contains_var_or_attr))
6424 return true;
6425 if (token.is_block() && recurse(token.block(), recurse))
6426 return true;
6427 }
6428 return false;
6429 };
6430
6431 m_context.set_current_property_id(property_id);
6432 Vector<ComponentValue> component_values;
6433 bool contains_var_or_attr = false;
6434
6435 while (tokens.has_next_token()) {
6436 auto const& token = tokens.next_token();
6437
6438 if (token.is(Token::Type::Semicolon)) {
6439 tokens.reconsume_current_input_token();
6440 break;
6441 }
6442
6443 if (property_id != PropertyID::Custom) {
6444 if (token.is(Token::Type::Whitespace))
6445 continue;
6446
6447 if (token.is(Token::Type::Ident) && has_ignored_vendor_prefix(token.token().ident()))
6448 return ParseError::IncludesIgnoredVendorPrefix;
6449 }
6450
6451 if (!contains_var_or_attr) {
6452 if (token.is_function() && function_contains_var_or_attr(token.function(), function_contains_var_or_attr))
6453 contains_var_or_attr = true;
6454 else if (token.is_block() && block_contains_var_or_attr(token.block(), block_contains_var_or_attr))
6455 contains_var_or_attr = true;
6456 }
6457
6458 component_values.append(token);
6459 }
6460
6461 if (property_id == PropertyID::Custom || contains_var_or_attr)
6462 return { UnresolvedStyleValue::create(move(component_values), contains_var_or_attr) };
6463
6464 if (component_values.is_empty())
6465 return ParseError::SyntaxError;
6466
6467 if (component_values.size() == 1) {
6468 if (auto parsed_value = parse_builtin_value(component_values.first()))
6469 return parsed_value.release_nonnull();
6470 }
6471
6472 // Special-case property handling
6473 switch (property_id) {
6474 case PropertyID::BackdropFilter:
6475 if (auto parsed_value = parse_filter_value_list_value(component_values))
6476 return parsed_value.release_nonnull();
6477 return ParseError::SyntaxError;
6478 case PropertyID::Background:
6479 if (auto parsed_value = parse_background_value(component_values))
6480 return parsed_value.release_nonnull();
6481 return ParseError::SyntaxError;
6482 case PropertyID::BackgroundAttachment:
6483 case PropertyID::BackgroundClip:
6484 case PropertyID::BackgroundImage:
6485 case PropertyID::BackgroundOrigin:
6486 if (auto parsed_value = parse_simple_comma_separated_value_list(component_values))
6487 return parsed_value.release_nonnull();
6488 return ParseError::SyntaxError;
6489 case PropertyID::BackgroundPosition:
6490 if (auto parsed_value = parse_comma_separated_value_list(component_values, [this](auto& tokens) { return parse_single_background_position_value(tokens); }))
6491 return parsed_value.release_nonnull();
6492 return ParseError::SyntaxError;
6493 case PropertyID::BackgroundRepeat:
6494 if (auto parsed_value = parse_comma_separated_value_list(component_values, [this](auto& tokens) { return parse_single_background_repeat_value(tokens); }))
6495 return parsed_value.release_nonnull();
6496 return ParseError::SyntaxError;
6497 case PropertyID::BackgroundSize:
6498 if (auto parsed_value = parse_comma_separated_value_list(component_values, [this](auto& tokens) { return parse_single_background_size_value(tokens); }))
6499 return parsed_value.release_nonnull();
6500 return ParseError::SyntaxError;
6501 case PropertyID::Border:
6502 case PropertyID::BorderBottom:
6503 case PropertyID::BorderLeft:
6504 case PropertyID::BorderRight:
6505 case PropertyID::BorderTop:
6506 if (auto parsed_value = parse_border_value(component_values))
6507 return parsed_value.release_nonnull();
6508 return ParseError::SyntaxError;
6509 case PropertyID::BorderTopLeftRadius:
6510 case PropertyID::BorderTopRightRadius:
6511 case PropertyID::BorderBottomRightRadius:
6512 case PropertyID::BorderBottomLeftRadius:
6513 if (auto parsed_value = parse_border_radius_value(component_values))
6514 return parsed_value.release_nonnull();
6515 return ParseError::SyntaxError;
6516 case PropertyID::BorderRadius:
6517 if (auto parsed_value = parse_border_radius_shorthand_value(component_values))
6518 return parsed_value.release_nonnull();
6519 return ParseError::SyntaxError;
6520 case PropertyID::BoxShadow:
6521 if (auto parsed_value = parse_shadow_value(component_values, AllowInsetKeyword::Yes))
6522 return parsed_value.release_nonnull();
6523 return ParseError::SyntaxError;
6524 case PropertyID::Content:
6525 if (auto parsed_value = parse_content_value(component_values))
6526 return parsed_value.release_nonnull();
6527 return ParseError::SyntaxError;
6528 case PropertyID::Flex:
6529 if (auto parsed_value = parse_flex_value(component_values))
6530 return parsed_value.release_nonnull();
6531 return ParseError::SyntaxError;
6532 case PropertyID::FlexFlow:
6533 if (auto parsed_value = parse_flex_flow_value(component_values))
6534 return parsed_value.release_nonnull();
6535 return ParseError::SyntaxError;
6536 case PropertyID::Font:
6537 if (auto parsed_value = parse_font_value(component_values))
6538 return parsed_value.release_nonnull();
6539 return ParseError::SyntaxError;
6540 case PropertyID::FontFamily:
6541 if (auto parsed_value = parse_font_family_value(component_values))
6542 return parsed_value.release_nonnull();
6543 return ParseError::SyntaxError;
6544 case PropertyID::GridColumn:
6545 if (auto parsed_value = parse_grid_track_placement_shorthand_value(component_values))
6546 return parsed_value.release_nonnull();
6547 return ParseError::SyntaxError;
6548 case PropertyID::GridArea:
6549 if (auto parsed_value = parse_grid_area_shorthand_value(component_values))
6550 return parsed_value.release_nonnull();
6551 return ParseError::SyntaxError;
6552 case PropertyID::GridTemplateAreas:
6553 if (auto parsed_value = parse_grid_template_areas_value(component_values))
6554 return parsed_value.release_nonnull();
6555 return ParseError::SyntaxError;
6556 case PropertyID::GridColumnEnd:
6557 if (auto parsed_value = parse_grid_track_placement(component_values))
6558 return parsed_value.release_nonnull();
6559 return ParseError::SyntaxError;
6560 case PropertyID::GridColumnStart:
6561 if (auto parsed_value = parse_grid_track_placement(component_values))
6562 return parsed_value.release_nonnull();
6563 return ParseError::SyntaxError;
6564 case PropertyID::GridRow:
6565 if (auto parsed_value = parse_grid_track_placement_shorthand_value(component_values))
6566 return parsed_value.release_nonnull();
6567 return ParseError::SyntaxError;
6568 case PropertyID::GridRowEnd:
6569 if (auto parsed_value = parse_grid_track_placement(component_values))
6570 return parsed_value.release_nonnull();
6571 return ParseError::SyntaxError;
6572 case PropertyID::GridRowStart:
6573 if (auto parsed_value = parse_grid_track_placement(component_values))
6574 return parsed_value.release_nonnull();
6575 return ParseError::SyntaxError;
6576 case PropertyID::GridTemplateColumns:
6577 if (auto parsed_value = parse_grid_track_sizes(component_values))
6578 return parsed_value.release_nonnull();
6579 return ParseError::SyntaxError;
6580 case PropertyID::GridTemplateRows:
6581 if (auto parsed_value = parse_grid_track_sizes(component_values))
6582 return parsed_value.release_nonnull();
6583 return ParseError::SyntaxError;
6584 case PropertyID::ListStyle:
6585 if (auto parsed_value = parse_list_style_value(component_values))
6586 return parsed_value.release_nonnull();
6587 return ParseError::SyntaxError;
6588 case PropertyID::Overflow:
6589 if (auto parsed_value = parse_overflow_value(component_values))
6590 return parsed_value.release_nonnull();
6591 return ParseError::SyntaxError;
6592 case PropertyID::TextDecoration:
6593 if (auto parsed_value = parse_text_decoration_value(component_values))
6594 return parsed_value.release_nonnull();
6595 return ParseError::SyntaxError;
6596 case PropertyID::TextDecorationLine: {
6597 TokenStream value_tokens { component_values };
6598 auto parsed_value = parse_text_decoration_line_value(value_tokens);
6599 if (parsed_value && !value_tokens.has_next_token())
6600 return parsed_value.release_nonnull();
6601 return ParseError::SyntaxError;
6602 }
6603 case PropertyID::TextShadow:
6604 if (auto parsed_value = parse_shadow_value(component_values, AllowInsetKeyword::No))
6605 return parsed_value.release_nonnull();
6606 return ParseError::SyntaxError;
6607 case PropertyID::Transform:
6608 if (auto parsed_value = parse_transform_value(component_values))
6609 return parsed_value.release_nonnull();
6610 return ParseError::SyntaxError;
6611 case PropertyID::TransformOrigin:
6612 if (auto parse_value = parse_transform_origin_value(component_values))
6613 return parse_value.release_nonnull();
6614 return ParseError ::SyntaxError;
6615 default:
6616 break;
6617 }
6618
6619 if (component_values.size() == 1) {
6620 if (auto parsed_value = parse_css_value(component_values.first())) {
6621 if (property_accepts_value(property_id, *parsed_value))
6622 return parsed_value.release_nonnull();
6623 }
6624 return ParseError::SyntaxError;
6625 }
6626
6627 // We have multiple values, so treat them as a StyleValueList.
6628 if (property_maximum_value_count(property_id) > 1) {
6629 StyleValueVector parsed_values;
6630 for (auto& component_value : component_values) {
6631 auto parsed_value = parse_css_value(component_value);
6632 if (!parsed_value || !property_accepts_value(property_id, *parsed_value))
6633 return ParseError::SyntaxError;
6634 parsed_values.append(parsed_value.release_nonnull());
6635 }
6636 if (!parsed_values.is_empty() && parsed_values.size() <= property_maximum_value_count(property_id))
6637 return { StyleValueList::create(move(parsed_values), StyleValueList::Separator::Space) };
6638 }
6639
6640 return ParseError::SyntaxError;
6641}
6642
6643RefPtr<StyleValue> Parser::parse_css_value(ComponentValue const& component_value)
6644{
6645 if (auto builtin = parse_builtin_value(component_value))
6646 return builtin;
6647
6648 if (auto dynamic = parse_dynamic_value(component_value))
6649 return dynamic;
6650
6651 // We parse colors before numbers, to catch hashless hex colors.
6652 if (auto color = parse_color_value(component_value))
6653 return color;
6654
6655 if (auto dimension = parse_dimension_value(component_value))
6656 return dimension;
6657
6658 if (auto numeric = parse_numeric_value(component_value))
6659 return numeric;
6660
6661 if (auto identifier = parse_identifier_value(component_value))
6662 return identifier;
6663
6664 if (auto string = parse_string_value(component_value))
6665 return string;
6666
6667 if (auto image = parse_image_value(component_value))
6668 return image;
6669
6670 if (auto rect = parse_rect_value(component_value))
6671 return rect;
6672
6673 return {};
6674}
6675
6676Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_pattern(TokenStream<ComponentValue>& values)
6677{
6678 auto transaction = values.begin_transaction();
6679 auto syntax_error = [&]() -> Optional<Selector::SimpleSelector::ANPlusBPattern> {
6680 if constexpr (CSS_PARSER_DEBUG) {
6681 dbgln_if(CSS_PARSER_DEBUG, "Invalid An+B value:");
6682 values.dump_all_tokens();
6683 }
6684 return {};
6685 };
6686
6687 auto is_n = [](ComponentValue const& value) -> bool {
6688 return value.is(Token::Type::Ident) && value.token().ident().equals_ignoring_ascii_case("n"sv);
6689 };
6690 auto is_ndash = [](ComponentValue const& value) -> bool {
6691 return value.is(Token::Type::Ident) && value.token().ident().equals_ignoring_ascii_case("n-"sv);
6692 };
6693 auto is_dashn = [](ComponentValue const& value) -> bool {
6694 return value.is(Token::Type::Ident) && value.token().ident().equals_ignoring_ascii_case("-n"sv);
6695 };
6696 auto is_dashndash = [](ComponentValue const& value) -> bool {
6697 return value.is(Token::Type::Ident) && value.token().ident().equals_ignoring_ascii_case("-n-"sv);
6698 };
6699 auto is_delim = [](ComponentValue const& value, u32 delim) -> bool {
6700 return value.is(Token::Type::Delim) && value.token().delim() == delim;
6701 };
6702 auto is_sign = [](ComponentValue const& value) -> bool {
6703 return value.is(Token::Type::Delim) && (value.token().delim() == '+' || value.token().delim() == '-');
6704 };
6705 auto is_n_dimension = [](ComponentValue const& value) -> bool {
6706 if (!value.is(Token::Type::Dimension))
6707 return false;
6708 if (!value.token().number().is_integer())
6709 return false;
6710 if (!value.token().dimension_unit().equals_ignoring_ascii_case("n"sv))
6711 return false;
6712 return true;
6713 };
6714 auto is_ndash_dimension = [](ComponentValue const& value) -> bool {
6715 if (!value.is(Token::Type::Dimension))
6716 return false;
6717 if (!value.token().number().is_integer())
6718 return false;
6719 if (!value.token().dimension_unit().equals_ignoring_ascii_case("n-"sv))
6720 return false;
6721 return true;
6722 };
6723 auto is_ndashdigit_dimension = [](ComponentValue const& value) -> bool {
6724 if (!value.is(Token::Type::Dimension))
6725 return false;
6726 if (!value.token().number().is_integer())
6727 return false;
6728 auto dimension_unit = value.token().dimension_unit();
6729 if (!dimension_unit.starts_with("n-"sv, CaseSensitivity::CaseInsensitive))
6730 return false;
6731 for (size_t i = 2; i < dimension_unit.length(); ++i) {
6732 if (!is_ascii_digit(dimension_unit[i]))
6733 return false;
6734 }
6735 return true;
6736 };
6737 auto is_ndashdigit_ident = [](ComponentValue const& value) -> bool {
6738 if (!value.is(Token::Type::Ident))
6739 return false;
6740 auto ident = value.token().ident();
6741 if (!ident.starts_with("n-"sv, CaseSensitivity::CaseInsensitive))
6742 return false;
6743 for (size_t i = 2; i < ident.length(); ++i) {
6744 if (!is_ascii_digit(ident[i]))
6745 return false;
6746 }
6747 return true;
6748 };
6749 auto is_dashndashdigit_ident = [](ComponentValue const& value) -> bool {
6750 if (!value.is(Token::Type::Ident))
6751 return false;
6752 auto ident = value.token().ident();
6753 if (!ident.starts_with("-n-"sv, CaseSensitivity::CaseInsensitive))
6754 return false;
6755 if (ident.length() == 3)
6756 return false;
6757 for (size_t i = 3; i < ident.length(); ++i) {
6758 if (!is_ascii_digit(ident[i]))
6759 return false;
6760 }
6761 return true;
6762 };
6763 auto is_integer = [](ComponentValue const& value) -> bool {
6764 return value.is(Token::Type::Number) && value.token().number().is_integer();
6765 };
6766 auto is_signed_integer = [](ComponentValue const& value) -> bool {
6767 return value.is(Token::Type::Number) && value.token().number().is_integer_with_explicit_sign();
6768 };
6769 auto is_signless_integer = [](ComponentValue const& value) -> bool {
6770 return value.is(Token::Type::Number) && !value.token().number().is_integer_with_explicit_sign();
6771 };
6772
6773 // https://www.w3.org/TR/css-syntax-3/#the-anb-type
6774 // Unfortunately these can't be in the same order as in the spec.
6775
6776 values.skip_whitespace();
6777 auto const& first_value = values.next_token();
6778
6779 // odd | even
6780 if (first_value.is(Token::Type::Ident)) {
6781 auto ident = first_value.token().ident();
6782 if (ident.equals_ignoring_ascii_case("odd"sv)) {
6783 transaction.commit();
6784 return Selector::SimpleSelector::ANPlusBPattern { 2, 1 };
6785 }
6786 if (ident.equals_ignoring_ascii_case("even"sv)) {
6787 transaction.commit();
6788 return Selector::SimpleSelector::ANPlusBPattern { 2, 0 };
6789 }
6790 }
6791 // <integer>
6792 if (is_integer(first_value)) {
6793 int b = first_value.token().to_integer();
6794 transaction.commit();
6795 return Selector::SimpleSelector::ANPlusBPattern { 0, b };
6796 }
6797 // <n-dimension>
6798 // <n-dimension> <signed-integer>
6799 // <n-dimension> ['+' | '-'] <signless-integer>
6800 if (is_n_dimension(first_value)) {
6801 int a = first_value.token().dimension_value_int();
6802 values.skip_whitespace();
6803
6804 // <n-dimension> <signed-integer>
6805 if (is_signed_integer(values.peek_token())) {
6806 int b = values.next_token().token().to_integer();
6807 transaction.commit();
6808 return Selector::SimpleSelector::ANPlusBPattern { a, b };
6809 }
6810
6811 // <n-dimension> ['+' | '-'] <signless-integer>
6812 {
6813 auto child_transaction = transaction.create_child();
6814 auto const& second_value = values.next_token();
6815 values.skip_whitespace();
6816 auto const& third_value = values.next_token();
6817
6818 if (is_sign(second_value) && is_signless_integer(third_value)) {
6819 int b = third_value.token().to_integer() * (is_delim(second_value, '+') ? 1 : -1);
6820 child_transaction.commit();
6821 return Selector::SimpleSelector::ANPlusBPattern { a, b };
6822 }
6823 }
6824
6825 // <n-dimension>
6826 transaction.commit();
6827 return Selector::SimpleSelector::ANPlusBPattern { a, 0 };
6828 }
6829 // <ndash-dimension> <signless-integer>
6830 if (is_ndash_dimension(first_value)) {
6831 values.skip_whitespace();
6832 auto const& second_value = values.next_token();
6833 if (is_signless_integer(second_value)) {
6834 int a = first_value.token().dimension_value_int();
6835 int b = -second_value.token().to_integer();
6836 transaction.commit();
6837 return Selector::SimpleSelector::ANPlusBPattern { a, b };
6838 }
6839
6840 return syntax_error();
6841 }
6842 // <ndashdigit-dimension>
6843 if (is_ndashdigit_dimension(first_value)) {
6844 auto const& dimension = first_value.token();
6845 int a = dimension.dimension_value_int();
6846 auto maybe_b = dimension.dimension_unit().substring_view(1).to_int();
6847 if (maybe_b.has_value()) {
6848 transaction.commit();
6849 return Selector::SimpleSelector::ANPlusBPattern { a, maybe_b.value() };
6850 }
6851
6852 return syntax_error();
6853 }
6854 // <dashndashdigit-ident>
6855 if (is_dashndashdigit_ident(first_value)) {
6856 auto maybe_b = first_value.token().ident().substring_view(2).to_int();
6857 if (maybe_b.has_value()) {
6858 transaction.commit();
6859 return Selector::SimpleSelector::ANPlusBPattern { -1, maybe_b.value() };
6860 }
6861
6862 return syntax_error();
6863 }
6864 // -n
6865 // -n <signed-integer>
6866 // -n ['+' | '-'] <signless-integer>
6867 if (is_dashn(first_value)) {
6868 values.skip_whitespace();
6869
6870 // -n <signed-integer>
6871 if (is_signed_integer(values.peek_token())) {
6872 int b = values.next_token().token().to_integer();
6873 transaction.commit();
6874 return Selector::SimpleSelector::ANPlusBPattern { -1, b };
6875 }
6876
6877 // -n ['+' | '-'] <signless-integer>
6878 {
6879 auto child_transaction = transaction.create_child();
6880 auto const& second_value = values.next_token();
6881 values.skip_whitespace();
6882 auto const& third_value = values.next_token();
6883
6884 if (is_sign(second_value) && is_signless_integer(third_value)) {
6885 int b = third_value.token().to_integer() * (is_delim(second_value, '+') ? 1 : -1);
6886 child_transaction.commit();
6887 return Selector::SimpleSelector::ANPlusBPattern { -1, b };
6888 }
6889 }
6890
6891 // -n
6892 transaction.commit();
6893 return Selector::SimpleSelector::ANPlusBPattern { -1, 0 };
6894 }
6895 // -n- <signless-integer>
6896 if (is_dashndash(first_value)) {
6897 values.skip_whitespace();
6898 auto const& second_value = values.next_token();
6899 if (is_signless_integer(second_value)) {
6900 int b = -second_value.token().to_integer();
6901 transaction.commit();
6902 return Selector::SimpleSelector::ANPlusBPattern { -1, b };
6903 }
6904
6905 return syntax_error();
6906 }
6907
6908 // All that's left now are these:
6909 // '+'?† n
6910 // '+'?† n <signed-integer>
6911 // '+'?† n ['+' | '-'] <signless-integer>
6912 // '+'?† n- <signless-integer>
6913 // '+'?† <ndashdigit-ident>
6914 // In all of these cases, the + is optional, and has no effect.
6915 // So, we just skip the +, and carry on.
6916 if (!is_delim(first_value, '+')) {
6917 values.reconsume_current_input_token();
6918 // We do *not* skip whitespace here.
6919 }
6920
6921 auto const& first_after_plus = values.next_token();
6922 // '+'?† n
6923 // '+'?† n <signed-integer>
6924 // '+'?† n ['+' | '-'] <signless-integer>
6925 if (is_n(first_after_plus)) {
6926 values.skip_whitespace();
6927
6928 // '+'?† n <signed-integer>
6929 if (is_signed_integer(values.peek_token())) {
6930 int b = values.next_token().token().to_integer();
6931 transaction.commit();
6932 return Selector::SimpleSelector::ANPlusBPattern { 1, b };
6933 }
6934
6935 // '+'?† n ['+' | '-'] <signless-integer>
6936 {
6937 auto child_transaction = transaction.create_child();
6938 auto const& second_value = values.next_token();
6939 values.skip_whitespace();
6940 auto const& third_value = values.next_token();
6941
6942 if (is_sign(second_value) && is_signless_integer(third_value)) {
6943 int b = third_value.token().to_integer() * (is_delim(second_value, '+') ? 1 : -1);
6944 child_transaction.commit();
6945 return Selector::SimpleSelector::ANPlusBPattern { 1, b };
6946 }
6947 }
6948
6949 // '+'?† n
6950 transaction.commit();
6951 return Selector::SimpleSelector::ANPlusBPattern { 1, 0 };
6952 }
6953
6954 // '+'?† n- <signless-integer>
6955 if (is_ndash(first_after_plus)) {
6956 values.skip_whitespace();
6957 auto const& second_value = values.next_token();
6958 if (is_signless_integer(second_value)) {
6959 int b = -second_value.token().to_integer();
6960 transaction.commit();
6961 return Selector::SimpleSelector::ANPlusBPattern { 1, b };
6962 }
6963
6964 return syntax_error();
6965 }
6966
6967 // '+'?† <ndashdigit-ident>
6968 if (is_ndashdigit_ident(first_after_plus)) {
6969 auto maybe_b = first_after_plus.token().ident().substring_view(1).to_int();
6970 if (maybe_b.has_value()) {
6971 transaction.commit();
6972 return Selector::SimpleSelector::ANPlusBPattern { 1, maybe_b.value() };
6973 }
6974
6975 return syntax_error();
6976 }
6977
6978 return syntax_error();
6979}
6980
6981OwnPtr<CalculatedStyleValue::CalcSum> Parser::parse_calc_expression(Vector<ComponentValue> const& values)
6982{
6983 auto tokens = TokenStream(values);
6984 return parse_calc_sum(tokens);
6985}
6986
6987Optional<CalculatedStyleValue::CalcValue> Parser::parse_calc_value(TokenStream<ComponentValue>& tokens)
6988{
6989 auto current_token = tokens.next_token();
6990
6991 if (current_token.is_block() && current_token.block().is_paren()) {
6992 auto block_values = TokenStream(current_token.block().values());
6993 auto parsed_calc_sum = parse_calc_sum(block_values);
6994 if (!parsed_calc_sum)
6995 return {};
6996 return CalculatedStyleValue::CalcValue { parsed_calc_sum.release_nonnull() };
6997 }
6998
6999 if (current_token.is(Token::Type::Number))
7000 return CalculatedStyleValue::CalcValue { current_token.token().number() };
7001
7002 if (current_token.is(Token::Type::Dimension) || current_token.is(Token::Type::Percentage)) {
7003 auto maybe_dimension = parse_dimension(current_token);
7004 if (!maybe_dimension.has_value())
7005 return {};
7006 auto& dimension = maybe_dimension.value();
7007
7008 if (dimension.is_angle())
7009 return CalculatedStyleValue::CalcValue { dimension.angle() };
7010 if (dimension.is_frequency())
7011 return CalculatedStyleValue::CalcValue { dimension.frequency() };
7012 if (dimension.is_length())
7013 return CalculatedStyleValue::CalcValue { dimension.length() };
7014 if (dimension.is_percentage())
7015 return CalculatedStyleValue::CalcValue { dimension.percentage() };
7016 if (dimension.is_resolution()) {
7017 // Resolution is not allowed in calc()
7018 return {};
7019 }
7020 if (dimension.is_time())
7021 return CalculatedStyleValue::CalcValue { dimension.time() };
7022 VERIFY_NOT_REACHED();
7023 }
7024
7025 return {};
7026}
7027
7028OwnPtr<CalculatedStyleValue::CalcProductPartWithOperator> Parser::parse_calc_product_part_with_operator(TokenStream<ComponentValue>& tokens)
7029{
7030 // Note: The default value is not used or passed around.
7031 auto product_with_operator = make<CalculatedStyleValue::CalcProductPartWithOperator>(
7032 CalculatedStyleValue::ProductOperation::Multiply,
7033 CalculatedStyleValue::CalcNumberValue { Number {} });
7034
7035 tokens.skip_whitespace();
7036
7037 auto const& op_token = tokens.peek_token();
7038 if (!op_token.is(Token::Type::Delim))
7039 return nullptr;
7040
7041 auto op = op_token.token().delim();
7042 if (op == '*') {
7043 tokens.next_token();
7044 tokens.skip_whitespace();
7045 product_with_operator->op = CalculatedStyleValue::ProductOperation::Multiply;
7046 auto parsed_calc_value = parse_calc_value(tokens);
7047 if (!parsed_calc_value.has_value())
7048 return nullptr;
7049 product_with_operator->value = { parsed_calc_value.release_value() };
7050
7051 } else if (op == '/') {
7052 // FIXME: Detect divide-by-zero if possible
7053 tokens.next_token();
7054 tokens.skip_whitespace();
7055 product_with_operator->op = CalculatedStyleValue::ProductOperation::Divide;
7056 auto parsed_calc_number_value = parse_calc_number_value(tokens);
7057 if (!parsed_calc_number_value.has_value())
7058 return nullptr;
7059 product_with_operator->value = { parsed_calc_number_value.release_value() };
7060 } else {
7061 return nullptr;
7062 }
7063
7064 return product_with_operator;
7065}
7066
7067OwnPtr<CalculatedStyleValue::CalcNumberProductPartWithOperator> Parser::parse_calc_number_product_part_with_operator(TokenStream<ComponentValue>& tokens)
7068{
7069 // Note: The default value is not used or passed around.
7070 auto number_product_with_operator = make<CalculatedStyleValue::CalcNumberProductPartWithOperator>(
7071 CalculatedStyleValue::ProductOperation::Multiply,
7072 CalculatedStyleValue::CalcNumberValue { Number {} });
7073
7074 tokens.skip_whitespace();
7075
7076 auto const& op_token = tokens.peek_token();
7077 if (!op_token.is(Token::Type::Delim))
7078 return nullptr;
7079
7080 auto op = op_token.token().delim();
7081 if (op == '*') {
7082 tokens.next_token();
7083 tokens.skip_whitespace();
7084 number_product_with_operator->op = CalculatedStyleValue::ProductOperation::Multiply;
7085 } else if (op == '/') {
7086 // FIXME: Detect divide-by-zero if possible
7087 tokens.next_token();
7088 tokens.skip_whitespace();
7089 number_product_with_operator->op = CalculatedStyleValue::ProductOperation::Divide;
7090 } else {
7091 return nullptr;
7092 }
7093
7094 auto parsed_calc_value = parse_calc_number_value(tokens);
7095 if (!parsed_calc_value.has_value())
7096 return nullptr;
7097 number_product_with_operator->value = parsed_calc_value.release_value();
7098
7099 return number_product_with_operator;
7100}
7101
7102OwnPtr<CalculatedStyleValue::CalcNumberProduct> Parser::parse_calc_number_product(TokenStream<ComponentValue>& tokens)
7103{
7104 auto calc_number_product = make<CalculatedStyleValue::CalcNumberProduct>(
7105 CalculatedStyleValue::CalcNumberValue { Number {} },
7106 Vector<NonnullOwnPtr<CalculatedStyleValue::CalcNumberProductPartWithOperator>> {});
7107
7108 auto first_calc_number_value_or_error = parse_calc_number_value(tokens);
7109 if (!first_calc_number_value_or_error.has_value())
7110 return nullptr;
7111 calc_number_product->first_calc_number_value = first_calc_number_value_or_error.release_value();
7112
7113 while (tokens.has_next_token()) {
7114 auto number_product_with_operator = parse_calc_number_product_part_with_operator(tokens);
7115 if (!number_product_with_operator)
7116 break;
7117 calc_number_product->zero_or_more_additional_calc_number_values.append(number_product_with_operator.release_nonnull());
7118 }
7119
7120 return calc_number_product;
7121}
7122
7123OwnPtr<CalculatedStyleValue::CalcNumberSumPartWithOperator> Parser::parse_calc_number_sum_part_with_operator(TokenStream<ComponentValue>& tokens)
7124{
7125 if (!(tokens.peek_token().is(Token::Type::Delim)
7126 && (tokens.peek_token().token().delim() == '+' || tokens.peek_token().token().delim() == '-')
7127 && tokens.peek_token(1).is(Token::Type::Whitespace)))
7128 return nullptr;
7129
7130 auto const& token = tokens.next_token();
7131 tokens.skip_whitespace();
7132
7133 CalculatedStyleValue::SumOperation op;
7134 auto delim = token.token().delim();
7135 if (delim == '+')
7136 op = CalculatedStyleValue::SumOperation::Add;
7137 else if (delim == '-')
7138 op = CalculatedStyleValue::SumOperation::Subtract;
7139 else
7140 return nullptr;
7141
7142 auto calc_number_product = parse_calc_number_product(tokens);
7143 if (!calc_number_product)
7144 return nullptr;
7145 return make<CalculatedStyleValue::CalcNumberSumPartWithOperator>(op, calc_number_product.release_nonnull());
7146}
7147
7148OwnPtr<CalculatedStyleValue::CalcNumberSum> Parser::parse_calc_number_sum(TokenStream<ComponentValue>& tokens)
7149{
7150 auto first_calc_number_product_or_error = parse_calc_number_product(tokens);
7151 if (!first_calc_number_product_or_error)
7152 return nullptr;
7153
7154 Vector<NonnullOwnPtr<CalculatedStyleValue::CalcNumberSumPartWithOperator>> additional {};
7155 while (tokens.has_next_token()) {
7156 auto calc_sum_part = parse_calc_number_sum_part_with_operator(tokens);
7157 if (!calc_sum_part)
7158 return nullptr;
7159 additional.append(calc_sum_part.release_nonnull());
7160 }
7161
7162 tokens.skip_whitespace();
7163
7164 auto calc_number_sum = make<CalculatedStyleValue::CalcNumberSum>(first_calc_number_product_or_error.release_nonnull(), move(additional));
7165 return calc_number_sum;
7166}
7167
7168Optional<CalculatedStyleValue::CalcNumberValue> Parser::parse_calc_number_value(TokenStream<ComponentValue>& tokens)
7169{
7170 auto const& first = tokens.peek_token();
7171 if (first.is_block() && first.block().is_paren()) {
7172 tokens.next_token();
7173 auto block_values = TokenStream(first.block().values());
7174 auto calc_number_sum = parse_calc_number_sum(block_values);
7175 if (calc_number_sum)
7176 return CalculatedStyleValue::CalcNumberValue { calc_number_sum.release_nonnull() };
7177 }
7178
7179 if (!first.is(Token::Type::Number))
7180 return {};
7181 tokens.next_token();
7182
7183 return CalculatedStyleValue::CalcNumberValue { first.token().number() };
7184}
7185
7186OwnPtr<CalculatedStyleValue::CalcProduct> Parser::parse_calc_product(TokenStream<ComponentValue>& tokens)
7187{
7188 auto calc_product = make<CalculatedStyleValue::CalcProduct>(
7189 CalculatedStyleValue::CalcValue { Number {} },
7190 Vector<NonnullOwnPtr<CalculatedStyleValue::CalcProductPartWithOperator>> {});
7191
7192 auto first_calc_value_or_error = parse_calc_value(tokens);
7193 if (!first_calc_value_or_error.has_value())
7194 return nullptr;
7195 calc_product->first_calc_value = first_calc_value_or_error.release_value();
7196
7197 while (tokens.has_next_token()) {
7198 auto product_with_operator = parse_calc_product_part_with_operator(tokens);
7199 if (!product_with_operator)
7200 break;
7201 calc_product->zero_or_more_additional_calc_values.append(product_with_operator.release_nonnull());
7202 }
7203
7204 return calc_product;
7205}
7206
7207OwnPtr<CalculatedStyleValue::CalcSumPartWithOperator> Parser::parse_calc_sum_part_with_operator(TokenStream<ComponentValue>& tokens)
7208{
7209 // The following has to have the shape of <Whitespace><+ or -><Whitespace>
7210 // But the first whitespace gets eaten in parse_calc_product_part_with_operator().
7211 if (!(tokens.peek_token().is(Token::Type::Delim)
7212 && (tokens.peek_token().token().delim() == '+' || tokens.peek_token().token().delim() == '-')
7213 && tokens.peek_token(1).is(Token::Type::Whitespace)))
7214 return nullptr;
7215
7216 auto const& token = tokens.next_token();
7217 tokens.skip_whitespace();
7218
7219 CalculatedStyleValue::SumOperation op;
7220 auto delim = token.token().delim();
7221 if (delim == '+')
7222 op = CalculatedStyleValue::SumOperation::Add;
7223 else if (delim == '-')
7224 op = CalculatedStyleValue::SumOperation::Subtract;
7225 else
7226 return nullptr;
7227
7228 auto calc_product = parse_calc_product(tokens);
7229 if (!calc_product)
7230 return nullptr;
7231 return make<CalculatedStyleValue::CalcSumPartWithOperator>(op, calc_product.release_nonnull());
7232};
7233
7234OwnPtr<CalculatedStyleValue::CalcSum> Parser::parse_calc_sum(TokenStream<ComponentValue>& tokens)
7235{
7236 auto parsed_calc_product = parse_calc_product(tokens);
7237 if (!parsed_calc_product)
7238 return nullptr;
7239
7240 Vector<NonnullOwnPtr<CalculatedStyleValue::CalcSumPartWithOperator>> additional {};
7241 while (tokens.has_next_token()) {
7242 auto calc_sum_part = parse_calc_sum_part_with_operator(tokens);
7243 if (!calc_sum_part)
7244 return nullptr;
7245 additional.append(calc_sum_part.release_nonnull());
7246 }
7247
7248 tokens.skip_whitespace();
7249
7250 return make<CalculatedStyleValue::CalcSum>(parsed_calc_product.release_nonnull(), move(additional));
7251}
7252
7253bool Parser::has_ignored_vendor_prefix(StringView string)
7254{
7255 if (!string.starts_with('-'))
7256 return false;
7257 if (string.starts_with("--"sv))
7258 return false;
7259 if (string.starts_with("-libweb-"sv))
7260 return false;
7261 return true;
7262}
7263
7264bool Parser::is_builtin(StringView name)
7265{
7266 return name.equals_ignoring_ascii_case("inherit"sv)
7267 || name.equals_ignoring_ascii_case("initial"sv)
7268 || name.equals_ignoring_ascii_case("unset"sv);
7269}
7270
7271RefPtr<CalculatedStyleValue> Parser::parse_calculated_value(Badge<StyleComputer>, ParsingContext const& context, Vector<ComponentValue> const& tokens)
7272{
7273 if (tokens.is_empty())
7274 return {};
7275
7276 auto parser = Parser::create(context, ""sv).release_value_but_fixme_should_propagate_errors();
7277 return parser.parse_calculated_value(tokens);
7278}
7279
7280RefPtr<StyleValue> Parser::parse_css_value(Badge<StyleComputer>, ParsingContext const& context, PropertyID property_id, Vector<ComponentValue> const& tokens)
7281{
7282 if (tokens.is_empty() || property_id == CSS::PropertyID::Invalid || property_id == CSS::PropertyID::Custom)
7283 return {};
7284
7285 auto parser = Parser::create(context, ""sv).release_value_but_fixme_should_propagate_errors();
7286 TokenStream<ComponentValue> token_stream { tokens };
7287 auto result = parser.parse_css_value(property_id, token_stream);
7288 if (result.is_error())
7289 return {};
7290 return result.release_value();
7291}
7292
7293bool Parser::Dimension::is_angle() const
7294{
7295 return m_value.has<Angle>();
7296}
7297
7298Angle Parser::Dimension::angle() const
7299{
7300 return m_value.get<Angle>();
7301}
7302
7303bool Parser::Dimension::is_angle_percentage() const
7304{
7305 return is_angle() || is_percentage();
7306}
7307
7308AnglePercentage Parser::Dimension::angle_percentage() const
7309{
7310 if (is_angle())
7311 return angle();
7312 if (is_percentage())
7313 return percentage();
7314 VERIFY_NOT_REACHED();
7315}
7316
7317bool Parser::Dimension::is_frequency() const
7318{
7319 return m_value.has<Frequency>();
7320}
7321
7322Frequency Parser::Dimension::frequency() const
7323{
7324 return m_value.get<Frequency>();
7325}
7326
7327bool Parser::Dimension::is_frequency_percentage() const
7328{
7329 return is_frequency() || is_percentage();
7330}
7331
7332FrequencyPercentage Parser::Dimension::frequency_percentage() const
7333{
7334 if (is_frequency())
7335 return frequency();
7336 if (is_percentage())
7337 return percentage();
7338 VERIFY_NOT_REACHED();
7339}
7340
7341bool Parser::Dimension::is_length() const
7342{
7343 return m_value.has<Length>();
7344}
7345
7346Length Parser::Dimension::length() const
7347{
7348 return m_value.get<Length>();
7349}
7350
7351bool Parser::Dimension::is_length_percentage() const
7352{
7353 return is_length() || is_percentage();
7354}
7355
7356LengthPercentage Parser::Dimension::length_percentage() const
7357{
7358 if (is_length())
7359 return length();
7360 if (is_percentage())
7361 return percentage();
7362 VERIFY_NOT_REACHED();
7363}
7364
7365bool Parser::Dimension::is_percentage() const
7366{
7367 return m_value.has<Percentage>();
7368}
7369
7370Percentage Parser::Dimension::percentage() const
7371{
7372 return m_value.get<Percentage>();
7373}
7374
7375bool Parser::Dimension::is_resolution() const
7376{
7377 return m_value.has<Resolution>();
7378}
7379
7380Resolution Parser::Dimension::resolution() const
7381{
7382 return m_value.get<Resolution>();
7383}
7384
7385bool Parser::Dimension::is_time() const
7386{
7387 return m_value.has<Time>();
7388}
7389
7390Time Parser::Dimension::time() const
7391{
7392 return m_value.get<Time>();
7393}
7394
7395bool Parser::Dimension::is_time_percentage() const
7396{
7397 return is_time() || is_percentage();
7398}
7399
7400TimePercentage Parser::Dimension::time_percentage() const
7401{
7402 if (is_time())
7403 return time();
7404 if (is_percentage())
7405 return percentage();
7406 VERIFY_NOT_REACHED();
7407}
7408}
7409
7410namespace Web {
7411
7412CSS::CSSStyleSheet* parse_css_stylesheet(CSS::Parser::ParsingContext const& context, StringView css, Optional<AK::URL> location)
7413{
7414 if (css.is_empty()) {
7415 auto rule_list = CSS::CSSRuleList::create_empty(context.realm()).release_value_but_fixme_should_propagate_errors();
7416 auto media_list = CSS::MediaList::create(context.realm(), {}).release_value_but_fixme_should_propagate_errors();
7417 return CSS::CSSStyleSheet::create(context.realm(), rule_list, media_list, location).release_value_but_fixme_should_propagate_errors();
7418 }
7419 auto parser = CSS::Parser::Parser::create(context, css).release_value_but_fixme_should_propagate_errors();
7420 return parser.parse_as_css_stylesheet(location);
7421}
7422
7423CSS::ElementInlineCSSStyleDeclaration* parse_css_style_attribute(CSS::Parser::ParsingContext const& context, StringView css, DOM::Element& element)
7424{
7425 if (css.is_empty())
7426 return CSS::ElementInlineCSSStyleDeclaration::create(element, {}, {}).release_value_but_fixme_should_propagate_errors();
7427 auto parser = CSS::Parser::Parser::create(context, css).release_value_but_fixme_should_propagate_errors();
7428 return parser.parse_as_style_attribute(element);
7429}
7430
7431RefPtr<CSS::StyleValue> parse_css_value(CSS::Parser::ParsingContext const& context, StringView string, CSS::PropertyID property_id)
7432{
7433 if (string.is_empty())
7434 return {};
7435 auto parser = CSS::Parser::Parser::create(context, string).release_value_but_fixme_should_propagate_errors();
7436 return parser.parse_as_css_value(property_id);
7437}
7438
7439CSS::CSSRule* parse_css_rule(CSS::Parser::ParsingContext const& context, StringView css_text)
7440{
7441 auto parser = CSS::Parser::Parser::create(context, css_text).release_value_but_fixme_should_propagate_errors();
7442 return parser.parse_as_css_rule();
7443}
7444
7445Optional<CSS::SelectorList> parse_selector(CSS::Parser::ParsingContext const& context, StringView selector_text)
7446{
7447 auto parser = CSS::Parser::Parser::create(context, selector_text).release_value_but_fixme_should_propagate_errors();
7448 return parser.parse_as_selector();
7449}
7450
7451RefPtr<CSS::MediaQuery> parse_media_query(CSS::Parser::ParsingContext const& context, StringView string)
7452{
7453 auto parser = CSS::Parser::Parser::create(context, string).release_value_but_fixme_should_propagate_errors();
7454 return parser.parse_as_media_query();
7455}
7456
7457Vector<NonnullRefPtr<CSS::MediaQuery>> parse_media_query_list(CSS::Parser::ParsingContext const& context, StringView string)
7458{
7459 auto parser = CSS::Parser::Parser::create(context, string).release_value_but_fixme_should_propagate_errors();
7460 return parser.parse_as_media_query_list();
7461}
7462
7463RefPtr<CSS::Supports> parse_css_supports(CSS::Parser::ParsingContext const& context, StringView string)
7464{
7465 if (string.is_empty())
7466 return {};
7467 auto parser = CSS::Parser::Parser::create(context, string).release_value_but_fixme_should_propagate_errors();
7468 return parser.parse_as_supports();
7469}
7470
7471Optional<CSS::StyleProperty> parse_css_supports_condition(CSS::Parser::ParsingContext const& context, StringView string)
7472{
7473 if (string.is_empty())
7474 return {};
7475 auto parser = CSS::Parser::Parser::create(context, string).release_value_but_fixme_should_propagate_errors();
7476 return parser.parse_as_supports_condition();
7477}
7478
7479}