Serenity Operating System
at hosted 688 lines 21 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <AK/HashMap.h> 28#include <LibWeb/CSS/PropertyID.h> 29#include <LibWeb/CSS/StyleSheet.h> 30#include <LibWeb/Parser/CSSParser.h> 31#include <ctype.h> 32#include <stdio.h> 33#include <stdlib.h> 34 35#define PARSE_ASSERT(x) \ 36 if (!(x)) { \ 37 dbg() << "CSS PARSER ASSERTION FAILED: " << #x; \ 38 dbg() << "At character# " << index << " in CSS: _" << css << "_"; \ 39 ASSERT_NOT_REACHED(); \ 40 } 41 42namespace Web { 43 44static Optional<Color> parse_css_color(const StringView& view) 45{ 46 auto color = Color::from_string(view); 47 if (color.has_value()) 48 return color; 49 50 return {}; 51} 52 53static Optional<float> try_parse_float(const StringView& string) 54{ 55 const char* str = string.characters_without_null_termination(); 56 size_t len = string.length(); 57 size_t weight = 1; 58 int exp_val = 0; 59 float value = 0.0f; 60 float fraction = 0.0f; 61 bool has_sign = false; 62 bool is_negative = false; 63 bool is_fractional = false; 64 bool is_scientific = false; 65 66 if (str[0] == '-') { 67 is_negative = true; 68 has_sign = true; 69 } 70 if (str[0] == '+') { 71 has_sign = true; 72 } 73 74 for (size_t i = has_sign; i < len; i++) { 75 76 // Looks like we're about to start working on the fractional part 77 if (str[i] == '.') { 78 is_fractional = true; 79 continue; 80 } 81 82 if (str[i] == 'e' || str[i] == 'E') { 83 if (str[i + 1] == '-' || str[i + 1] == '+') 84 exp_val = atoi(str + i + 2); 85 else 86 exp_val = atoi(str + i + 1); 87 88 is_scientific = true; 89 continue; 90 } 91 92 if (str[i] < '0' || str[i] > '9' || exp_val != 0) { 93 return {}; 94 continue; 95 } 96 97 if (is_fractional) { 98 fraction *= 10; 99 fraction += str[i] - '0'; 100 weight *= 10; 101 } else { 102 value = value * 10; 103 value += str[i] - '0'; 104 } 105 } 106 107 fraction /= weight; 108 value += fraction; 109 110 if (is_scientific) { 111 bool divide = exp_val < 0; 112 if (divide) 113 exp_val *= -1; 114 115 for (int i = 0; i < exp_val; i++) { 116 if (divide) 117 value /= 10; 118 else 119 value *= 10; 120 } 121 } 122 123 return is_negative ? -value : value; 124} 125 126static Optional<float> parse_number(const StringView& view) 127{ 128 if (view.length() >= 2 && view[view.length() - 2] == 'p' && view[view.length() - 1] == 'x') 129 return parse_number(view.substring_view(0, view.length() - 2)); 130 131 return try_parse_float(view); 132} 133 134NonnullRefPtr<StyleValue> parse_css_value(const StringView& string) 135{ 136 auto number = parse_number(string); 137 if (number.has_value()) 138 return LengthStyleValue::create(Length(number.value(), Length::Type::Absolute)); 139 if (string == "inherit") 140 return InheritStyleValue::create(); 141 if (string == "initial") 142 return InitialStyleValue::create(); 143 if (string == "auto") 144 return LengthStyleValue::create(Length()); 145 146 auto color = parse_css_color(string); 147 if (color.has_value()) 148 return ColorStyleValue::create(color.value()); 149 150 if (string == "-libhtml-link") 151 return IdentifierStyleValue::create(CSS::ValueID::VendorSpecificLink); 152 153 return StringStyleValue::create(string); 154} 155 156RefPtr<LengthStyleValue> parse_line_width(const StringView& part) 157{ 158 NonnullRefPtr<StyleValue> value = parse_css_value(part); 159 if (value->is_length()) 160 return static_ptr_cast<LengthStyleValue>(value); 161 return nullptr; 162} 163 164RefPtr<ColorStyleValue> parse_color(const StringView& part) 165{ 166 NonnullRefPtr<StyleValue> value = parse_css_value(part); 167 if (value->is_color()) 168 return static_ptr_cast<ColorStyleValue>(value); 169 return nullptr; 170} 171 172RefPtr<StringStyleValue> parse_line_style(const StringView& part) 173{ 174 NonnullRefPtr<StyleValue> parsed_value = parse_css_value(part); 175 if (!parsed_value->is_string()) 176 return nullptr; 177 auto value = static_ptr_cast<StringStyleValue>(parsed_value); 178 if (value->to_string() == "dotted") 179 return value; 180 if (value->to_string() == "dashed") 181 return value; 182 if (value->to_string() == "solid") 183 return value; 184 if (value->to_string() == "double") 185 return value; 186 if (value->to_string() == "groove") 187 return value; 188 if (value->to_string() == "ridge") 189 return value; 190 return nullptr; 191} 192 193class CSSParser { 194public: 195 CSSParser(const StringView& input) 196 : css(input) 197 { 198 } 199 200 bool next_is(const char* str) const 201 { 202 size_t len = strlen(str); 203 for (size_t i = 0; i < len; ++i) { 204 if (peek(i) != str[i]) 205 return false; 206 } 207 return true; 208 } 209 210 char peek(size_t offset = 0) const 211 { 212 if ((index + offset) < css.length()) 213 return css[index + offset]; 214 return 0; 215 } 216 217 char consume_specific(char ch) 218 { 219 if (peek() != ch) { 220 dbg() << "peek() != '" << ch << "'"; 221 } 222 PARSE_ASSERT(peek() == ch); 223 PARSE_ASSERT(index < css.length()); 224 ++index; 225 return ch; 226 } 227 228 char consume_one() 229 { 230 PARSE_ASSERT(index < css.length()); 231 return css[index++]; 232 }; 233 234 bool consume_whitespace_or_comments() 235 { 236 size_t original_index = index; 237 bool in_comment = false; 238 for (; index < css.length(); ++index) { 239 char ch = peek(); 240 if (isspace(ch)) 241 continue; 242 if (!in_comment && ch == '/' && peek(1) == '*') { 243 in_comment = true; 244 ++index; 245 continue; 246 } 247 if (in_comment && ch == '*' && peek(1) == '/') { 248 in_comment = false; 249 ++index; 250 continue; 251 } 252 if (in_comment) 253 continue; 254 break; 255 } 256 return original_index != index; 257 } 258 259 bool is_valid_selector_char(char ch) const 260 { 261 return isalnum(ch) || ch == '-' || ch == '_' || ch == '(' || ch == ')' || ch == '@'; 262 } 263 264 bool is_combinator(char ch) const 265 { 266 return ch == '~' || ch == '>' || ch == '+'; 267 } 268 269 Optional<Selector::SimpleSelector> parse_simple_selector() 270 { 271 if (consume_whitespace_or_comments()) 272 return {}; 273 274 if (!peek() || peek() == '{' || peek() == ',' || is_combinator(peek())) 275 return {}; 276 277 Selector::SimpleSelector::Type type; 278 279 if (peek() == '*') { 280 type = Selector::SimpleSelector::Type::Universal; 281 consume_one(); 282 return Selector::SimpleSelector { 283 type, 284 Selector::SimpleSelector::PseudoClass::None, 285 String(), 286 Selector::SimpleSelector::AttributeMatchType::None, 287 String(), 288 String() 289 }; 290 } 291 292 if (peek() == '.') { 293 type = Selector::SimpleSelector::Type::Class; 294 consume_one(); 295 } else if (peek() == '#') { 296 type = Selector::SimpleSelector::Type::Id; 297 consume_one(); 298 } else if (isalpha(peek())) { 299 type = Selector::SimpleSelector::Type::TagName; 300 } else { 301 type = Selector::SimpleSelector::Type::Universal; 302 } 303 304 if (type != Selector::SimpleSelector::Type::Universal) { 305 while (is_valid_selector_char(peek())) 306 buffer.append(consume_one()); 307 PARSE_ASSERT(!buffer.is_null()); 308 } 309 310 Selector::SimpleSelector simple_selector { 311 type, 312 Selector::SimpleSelector::PseudoClass::None, 313 String::copy(buffer), 314 Selector::SimpleSelector::AttributeMatchType::None, 315 String(), 316 String() 317 }; 318 buffer.clear(); 319 320 if (peek() == '[') { 321 Selector::SimpleSelector::AttributeMatchType attribute_match_type = Selector::SimpleSelector::AttributeMatchType::HasAttribute; 322 String attribute_name; 323 String attribute_value; 324 bool in_value = false; 325 consume_specific('['); 326 char expected_end_of_attribute_selector = ']'; 327 while (peek() != expected_end_of_attribute_selector) { 328 char ch = consume_one(); 329 if (ch == '=') { 330 attribute_match_type = Selector::SimpleSelector::AttributeMatchType::ExactValueMatch; 331 attribute_name = String::copy(buffer); 332 buffer.clear(); 333 in_value = true; 334 consume_whitespace_or_comments(); 335 if (peek() == '\'') { 336 expected_end_of_attribute_selector = '\''; 337 consume_one(); 338 } else if (peek() == '"') { 339 expected_end_of_attribute_selector = '"'; 340 consume_one(); 341 } 342 continue; 343 } 344 buffer.append(ch); 345 } 346 if (in_value) 347 attribute_value = String::copy(buffer); 348 else 349 attribute_name = String::copy(buffer); 350 buffer.clear(); 351 simple_selector.attribute_match_type = attribute_match_type; 352 simple_selector.attribute_name = attribute_name; 353 simple_selector.attribute_value = attribute_value; 354 if (expected_end_of_attribute_selector != ']') 355 consume_specific(expected_end_of_attribute_selector); 356 consume_whitespace_or_comments(); 357 consume_specific(']'); 358 } 359 360 if (peek() == ':') { 361 // FIXME: Implement pseudo elements. 362 [[maybe_unused]] bool is_pseudo_element = false; 363 consume_one(); 364 if (peek() == ':') { 365 is_pseudo_element = true; 366 consume_one(); 367 } 368 if (next_is("not")) { 369 buffer.append(consume_one()); 370 buffer.append(consume_one()); 371 buffer.append(consume_one()); 372 buffer.append(consume_specific('(')); 373 while (peek() != ')') 374 buffer.append(consume_one()); 375 buffer.append(consume_specific(')')); 376 } else { 377 while (is_valid_selector_char(peek())) 378 buffer.append(consume_one()); 379 } 380 381 auto pseudo_name = String::copy(buffer); 382 buffer.clear(); 383 384 if (pseudo_name == "link") 385 simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Link; 386 else if (pseudo_name == "hover") 387 simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Hover; 388 else if (pseudo_name == "first-child") 389 simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::FirstChild; 390 else if (pseudo_name == "last-child") 391 simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::LastChild; 392 else if (pseudo_name == "only-child") 393 simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::OnlyChild; 394 else if (pseudo_name == "empty") 395 simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Empty; 396 } 397 398 return simple_selector; 399 } 400 401 Optional<Selector::ComplexSelector> parse_complex_selector() 402 { 403 auto relation = Selector::ComplexSelector::Relation::Descendant; 404 405 if (peek() == '{' || peek() == ',') 406 return {}; 407 408 if (is_combinator(peek())) { 409 switch (peek()) { 410 case '>': 411 relation = Selector::ComplexSelector::Relation::ImmediateChild; 412 break; 413 case '+': 414 relation = Selector::ComplexSelector::Relation::AdjacentSibling; 415 break; 416 case '~': 417 relation = Selector::ComplexSelector::Relation::GeneralSibling; 418 break; 419 } 420 consume_one(); 421 consume_whitespace_or_comments(); 422 } 423 424 consume_whitespace_or_comments(); 425 426 Vector<Selector::SimpleSelector> simple_selectors; 427 for (;;) { 428 auto component = parse_simple_selector(); 429 if (!component.has_value()) 430 break; 431 simple_selectors.append(component.value()); 432 // If this assert triggers, we're most likely up to no good. 433 PARSE_ASSERT(simple_selectors.size() < 100); 434 } 435 436 return Selector::ComplexSelector { relation, move(simple_selectors) }; 437 } 438 439 void parse_selector() 440 { 441 Vector<Selector::ComplexSelector> complex_selectors; 442 443 for (;;) { 444 auto complex_selector = parse_complex_selector(); 445 if (complex_selector.has_value()) 446 complex_selectors.append(complex_selector.value()); 447 consume_whitespace_or_comments(); 448 if (!peek() || peek() == ',' || peek() == '{') 449 break; 450 } 451 452 if (complex_selectors.is_empty()) 453 return; 454 complex_selectors.first().relation = Selector::ComplexSelector::Relation::None; 455 456 current_rule.selectors.append(Selector(move(complex_selectors))); 457 } 458 459 Optional<Selector> parse_individual_selector() 460 { 461 parse_selector(); 462 if (current_rule.selectors.is_empty()) 463 return {}; 464 return current_rule.selectors.last(); 465 } 466 467 void parse_selector_list() 468 { 469 for (;;) { 470 parse_selector(); 471 consume_whitespace_or_comments(); 472 if (peek() == ',') { 473 consume_one(); 474 continue; 475 } 476 if (peek() == '{') 477 break; 478 } 479 } 480 481 bool is_valid_property_name_char(char ch) const 482 { 483 return ch && !isspace(ch) && ch != ':'; 484 } 485 486 bool is_valid_property_value_char(char ch) const 487 { 488 return ch && ch != '!' && ch != ';' && ch != '}'; 489 } 490 491 struct ValueAndImportant { 492 String value; 493 bool important { false }; 494 }; 495 496 ValueAndImportant consume_css_value() 497 { 498 buffer.clear(); 499 500 int paren_nesting_level = 0; 501 bool important = false; 502 503 for (;;) { 504 char ch = peek(); 505 if (ch == '(') { 506 ++paren_nesting_level; 507 buffer.append(consume_one()); 508 continue; 509 } 510 if (ch == ')') { 511 PARSE_ASSERT(paren_nesting_level > 0); 512 --paren_nesting_level; 513 buffer.append(consume_one()); 514 continue; 515 } 516 if (paren_nesting_level > 0) { 517 buffer.append(consume_one()); 518 continue; 519 } 520 if (next_is("!important")) { 521 consume_specific('!'); 522 consume_specific('i'); 523 consume_specific('m'); 524 consume_specific('p'); 525 consume_specific('o'); 526 consume_specific('r'); 527 consume_specific('t'); 528 consume_specific('a'); 529 consume_specific('n'); 530 consume_specific('t'); 531 important = true; 532 continue; 533 } 534 if (next_is("/*")) { 535 consume_whitespace_or_comments(); 536 continue; 537 } 538 if (!ch) 539 break; 540 if (ch == '}') 541 break; 542 if (ch == ';') 543 break; 544 buffer.append(consume_one()); 545 } 546 547 // Remove trailing whitespace. 548 while (!buffer.is_empty() && isspace(buffer.last())) 549 buffer.take_last(); 550 551 auto string = String::copy(buffer); 552 buffer.clear(); 553 554 return { string, important }; 555 } 556 557 Optional<StyleProperty> parse_property() 558 { 559 consume_whitespace_or_comments(); 560 if (peek() == ';') { 561 consume_one(); 562 return {}; 563 } 564 if (peek() == '}') 565 return {}; 566 buffer.clear(); 567 while (is_valid_property_name_char(peek())) 568 buffer.append(consume_one()); 569 auto property_name = String::copy(buffer); 570 buffer.clear(); 571 consume_whitespace_or_comments(); 572 consume_specific(':'); 573 consume_whitespace_or_comments(); 574 575 auto [property_value, important] = consume_css_value(); 576 577 consume_whitespace_or_comments(); 578 579 if (peek() && peek() != '}') 580 consume_specific(';'); 581 582 auto property_id = CSS::property_id_from_string(property_name); 583 return StyleProperty { property_id, parse_css_value(property_value), important }; 584 } 585 586 void parse_declaration() 587 { 588 for (;;) { 589 auto property = parse_property(); 590 if (property.has_value()) 591 current_rule.properties.append(property.value()); 592 consume_whitespace_or_comments(); 593 if (peek() == '}') 594 break; 595 } 596 } 597 598 void parse_rule() 599 { 600 consume_whitespace_or_comments(); 601 if (index >= css.length()) 602 return; 603 604 // FIXME: We ignore @-rules for now. 605 if (peek() == '@') { 606 while (peek() != '{') 607 consume_one(); 608 int level = 0; 609 for (;;) { 610 auto ch = consume_one(); 611 if (ch == '{') { 612 ++level; 613 } else if (ch == '}') { 614 --level; 615 if (level == 0) 616 break; 617 } 618 } 619 consume_whitespace_or_comments(); 620 return; 621 } 622 623 parse_selector_list(); 624 consume_specific('{'); 625 parse_declaration(); 626 consume_specific('}'); 627 rules.append(StyleRule::create(move(current_rule.selectors), StyleDeclaration::create(move(current_rule.properties)))); 628 consume_whitespace_or_comments(); 629 } 630 631 RefPtr<StyleSheet> parse_sheet() 632 { 633 while (index < css.length()) { 634 parse_rule(); 635 } 636 637 return StyleSheet::create(move(rules)); 638 } 639 640 RefPtr<StyleDeclaration> parse_standalone_declaration() 641 { 642 consume_whitespace_or_comments(); 643 for (;;) { 644 auto property = parse_property(); 645 if (property.has_value()) 646 current_rule.properties.append(property.value()); 647 consume_whitespace_or_comments(); 648 if (!peek()) 649 break; 650 } 651 return StyleDeclaration::create(move(current_rule.properties)); 652 } 653 654private: 655 NonnullRefPtrVector<StyleRule> rules; 656 657 struct CurrentRule { 658 Vector<Selector> selectors; 659 Vector<StyleProperty> properties; 660 }; 661 662 CurrentRule current_rule; 663 Vector<char> buffer; 664 665 size_t index = 0; 666 667 StringView css; 668}; 669 670Optional<Selector> parse_selector(const StringView& selector_text) 671{ 672 CSSParser parser(selector_text); 673 return parser.parse_individual_selector(); 674} 675 676RefPtr<StyleSheet> parse_css(const StringView& css) 677{ 678 CSSParser parser(css); 679 return parser.parse_sheet(); 680} 681 682RefPtr<StyleDeclaration> parse_css_declaration(const StringView& css) 683{ 684 CSSParser parser(css); 685 return parser.parse_standalone_declaration(); 686} 687 688}