Serenity Operating System
at master 7479 lines 307 kB view raw
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}