DeprecatedCSSParser.cpp 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021, Tobias Christiansen <tobi@tobyase.de>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/GenericLexer.h>
  8. #include <AK/HashMap.h>
  9. #include <AK/SourceLocation.h>
  10. #include <LibWeb/CSS/CSSImportRule.h>
  11. #include <LibWeb/CSS/CSSRule.h>
  12. #include <LibWeb/CSS/CSSStyleRule.h>
  13. #include <LibWeb/CSS/Parser/DeprecatedCSSParser.h>
  14. #include <LibWeb/CSS/PropertyID.h>
  15. #include <LibWeb/CSS/Selector.h>
  16. #include <LibWeb/DOM/Document.h>
  17. #include <ctype.h>
  18. #include <stdlib.h>
  19. #include <string.h>
  20. #define PARSE_VERIFY(x) \
  21. if (!(x)) { \
  22. dbgln("CSS PARSER ASSERTION FAILED: {}", #x); \
  23. dbgln("At character# {} in CSS: _{}_", index, css); \
  24. VERIFY_NOT_REACHED(); \
  25. }
  26. static inline void log_parse_error(const SourceLocation& location = SourceLocation::current())
  27. {
  28. dbgln("CSS Parse error! {}", location);
  29. }
  30. namespace Web {
  31. namespace CSS {
  32. DeprecatedParsingContext::DeprecatedParsingContext()
  33. {
  34. }
  35. DeprecatedParsingContext::DeprecatedParsingContext(const DOM::Document& document)
  36. : m_document(&document)
  37. {
  38. }
  39. DeprecatedParsingContext::DeprecatedParsingContext(const DOM::ParentNode& parent_node)
  40. : m_document(&parent_node.document())
  41. {
  42. }
  43. bool DeprecatedParsingContext::in_quirks_mode() const
  44. {
  45. return m_document ? m_document->in_quirks_mode() : false;
  46. }
  47. URL DeprecatedParsingContext::complete_url(const String& addr) const
  48. {
  49. return m_document ? m_document->url().complete_url(addr) : URL::create_with_url_or_path(addr);
  50. }
  51. }
  52. static Optional<Color> parse_css_color(const CSS::DeprecatedParsingContext&, const StringView& view)
  53. {
  54. if (view.equals_ignoring_case("transparent"))
  55. return Color::from_rgba(0x00000000);
  56. auto color = Color::from_string(view.to_string().to_lowercase());
  57. if (color.has_value())
  58. return color;
  59. return {};
  60. }
  61. static Optional<float> try_parse_float(const StringView& string)
  62. {
  63. const char* str = string.characters_without_null_termination();
  64. size_t len = string.length();
  65. size_t weight = 1;
  66. int exp_val = 0;
  67. float value = 0.0f;
  68. float fraction = 0.0f;
  69. bool has_sign = false;
  70. bool is_negative = false;
  71. bool is_fractional = false;
  72. bool is_scientific = false;
  73. if (str[0] == '-') {
  74. is_negative = true;
  75. has_sign = true;
  76. }
  77. if (str[0] == '+') {
  78. has_sign = true;
  79. }
  80. for (size_t i = has_sign; i < len; i++) {
  81. // Looks like we're about to start working on the fractional part
  82. if (str[i] == '.') {
  83. is_fractional = true;
  84. continue;
  85. }
  86. if (str[i] == 'e' || str[i] == 'E') {
  87. if (str[i + 1] == '-' || str[i + 1] == '+')
  88. exp_val = atoi(str + i + 2);
  89. else
  90. exp_val = atoi(str + i + 1);
  91. is_scientific = true;
  92. continue;
  93. }
  94. if (str[i] < '0' || str[i] > '9' || exp_val != 0) {
  95. return {};
  96. continue;
  97. }
  98. if (is_fractional) {
  99. fraction *= 10;
  100. fraction += str[i] - '0';
  101. weight *= 10;
  102. } else {
  103. value = value * 10;
  104. value += str[i] - '0';
  105. }
  106. }
  107. fraction /= weight;
  108. value += fraction;
  109. if (is_scientific) {
  110. bool divide = exp_val < 0;
  111. if (divide)
  112. exp_val *= -1;
  113. for (int i = 0; i < exp_val; i++) {
  114. if (divide)
  115. value /= 10;
  116. else
  117. value *= 10;
  118. }
  119. }
  120. return is_negative ? -value : value;
  121. }
  122. static CSS::Length::Type length_type_from_unit(const StringView& view)
  123. {
  124. if (view.ends_with('%'))
  125. return CSS::Length::Type::Percentage;
  126. if (view.ends_with("px", CaseSensitivity::CaseInsensitive))
  127. return CSS::Length::Type::Px;
  128. if (view.ends_with("pt", CaseSensitivity::CaseInsensitive))
  129. return CSS::Length::Type::Pt;
  130. if (view.ends_with("pc", CaseSensitivity::CaseInsensitive))
  131. return CSS::Length::Type::Pc;
  132. if (view.ends_with("mm", CaseSensitivity::CaseInsensitive))
  133. return CSS::Length::Type::Mm;
  134. if (view.ends_with("rem", CaseSensitivity::CaseInsensitive))
  135. return CSS::Length::Type::Rem;
  136. if (view.ends_with("em", CaseSensitivity::CaseInsensitive))
  137. return CSS::Length::Type::Em;
  138. if (view.ends_with("ex", CaseSensitivity::CaseInsensitive))
  139. return CSS::Length::Type::Ex;
  140. if (view.ends_with("vw", CaseSensitivity::CaseInsensitive))
  141. return CSS::Length::Type::Vw;
  142. if (view.ends_with("vh", CaseSensitivity::CaseInsensitive))
  143. return CSS::Length::Type::Vh;
  144. if (view.ends_with("vmax", CaseSensitivity::CaseInsensitive))
  145. return CSS::Length::Type::Vmax;
  146. if (view.ends_with("vmin", CaseSensitivity::CaseInsensitive))
  147. return CSS::Length::Type::Vmin;
  148. if (view.ends_with("cm", CaseSensitivity::CaseInsensitive))
  149. return CSS::Length::Type::Cm;
  150. if (view.ends_with("in", CaseSensitivity::CaseInsensitive))
  151. return CSS::Length::Type::In;
  152. if (view.ends_with("Q", CaseSensitivity::CaseInsensitive))
  153. return CSS::Length::Type::Q;
  154. if (view == "0")
  155. return CSS::Length::Type::Px;
  156. return CSS::Length::Type::Undefined;
  157. }
  158. static CSS::Length parse_length(const CSS::DeprecatedParsingContext& context, const StringView& view, bool& is_bad_length)
  159. {
  160. CSS::Length::Type type = length_type_from_unit(view);
  161. Optional<float> value;
  162. switch (type) {
  163. case CSS::Length::Type::Percentage:
  164. value = try_parse_float(view.substring_view(0, view.length() - 1));
  165. break;
  166. case CSS::Length::Type::Px:
  167. if (view == "0")
  168. value = 0;
  169. else
  170. value = try_parse_float(view.substring_view(0, view.length() - 2));
  171. break;
  172. case CSS::Length::Type::Pt:
  173. value = try_parse_float(view.substring_view(0, view.length() - 2));
  174. break;
  175. case CSS::Length::Type::Pc:
  176. value = try_parse_float(view.substring_view(0, view.length() - 2));
  177. break;
  178. case CSS::Length::Type::Mm:
  179. value = try_parse_float(view.substring_view(0, view.length() - 2));
  180. break;
  181. case CSS::Length::Type::Rem:
  182. value = try_parse_float(view.substring_view(0, view.length() - 3));
  183. break;
  184. case CSS::Length::Type::Em:
  185. value = try_parse_float(view.substring_view(0, view.length() - 2));
  186. break;
  187. case CSS::Length::Type::Ex:
  188. value = try_parse_float(view.substring_view(0, view.length() - 2));
  189. break;
  190. case CSS::Length::Type::Vw:
  191. value = try_parse_float(view.substring_view(0, view.length() - 2));
  192. break;
  193. case CSS::Length::Type::Vh:
  194. value = try_parse_float(view.substring_view(0, view.length() - 2));
  195. break;
  196. case CSS::Length::Type::Vmax:
  197. value = try_parse_float(view.substring_view(0, view.length() - 4));
  198. break;
  199. case CSS::Length::Type::Vmin:
  200. value = try_parse_float(view.substring_view(0, view.length() - 4));
  201. break;
  202. case CSS::Length::Type::Cm:
  203. value = try_parse_float(view.substring_view(0, view.length() - 2));
  204. break;
  205. case CSS::Length::Type::In:
  206. value = try_parse_float(view.substring_view(0, view.length() - 2));
  207. break;
  208. case CSS::Length::Type::Q:
  209. value = try_parse_float(view.substring_view(0, view.length() - 1));
  210. break;
  211. default:
  212. if (context.in_quirks_mode()) {
  213. type = CSS::Length::Type::Px;
  214. value = try_parse_float(view);
  215. } else {
  216. value = try_parse_float(view);
  217. if (value.has_value())
  218. is_bad_length = true;
  219. }
  220. }
  221. if (!value.has_value())
  222. return {};
  223. return CSS::Length(value.value(), type);
  224. }
  225. static bool takes_integer_value(CSS::PropertyID property_id)
  226. {
  227. return property_id == CSS::PropertyID::ZIndex || property_id == CSS::PropertyID::FontWeight || property_id == CSS::PropertyID::Custom;
  228. }
  229. static StringView parse_custom_property_name(const StringView& value)
  230. {
  231. if (!value.starts_with("var(") || !value.ends_with(")"))
  232. return {};
  233. // FIXME: Allow for fallback
  234. auto first_comma_index = value.find(',');
  235. auto length = value.length();
  236. auto substring_length = first_comma_index.has_value() ? first_comma_index.value() - 4 - 1 : length - 4 - 1;
  237. return value.substring_view(4, substring_length);
  238. }
  239. static StringView isolate_calc_expression(const StringView& value)
  240. {
  241. if (!value.starts_with("calc(") || !value.ends_with(")"))
  242. return {};
  243. auto substring_length = value.length() - 5 - 1;
  244. return value.substring_view(5, substring_length);
  245. }
  246. static Optional<NonnullOwnPtr<CSS::CalculatedStyleValue::CalcSum>> parse_calc_expression(const StringView& expression_string)
  247. {
  248. // First, tokenize
  249. struct CalcToken {
  250. enum class Type {
  251. Undefined,
  252. Number,
  253. Unit,
  254. Whitespace,
  255. Plus,
  256. Minus,
  257. Asterisk,
  258. Slash,
  259. OpenBracket,
  260. CloseBracket,
  261. } type { Type::Undefined };
  262. String value {};
  263. };
  264. Vector<CalcToken> tokens;
  265. GenericLexer lexer(expression_string);
  266. while (!lexer.is_eof()) {
  267. // Number
  268. if (((lexer.next_is('+') || lexer.next_is('-')) && !isspace(lexer.peek(1)))
  269. || lexer.next_is('.')
  270. || lexer.next_is(isdigit)) {
  271. auto number = lexer.consume_while(is_any_of("+-.0123456789"));
  272. tokens.append(CalcToken { CalcToken::Type ::Number, number.to_string() });
  273. continue;
  274. }
  275. auto ch = lexer.consume();
  276. if (isspace(ch)) {
  277. tokens.append(CalcToken { CalcToken::Type::Whitespace });
  278. continue;
  279. }
  280. if (ch == '%') {
  281. tokens.append(CalcToken { CalcToken::Type::Unit, "%" });
  282. continue;
  283. }
  284. if (ch == '+') {
  285. tokens.append(CalcToken { CalcToken::Type::Plus });
  286. continue;
  287. }
  288. if (ch == '-') {
  289. tokens.append(CalcToken { CalcToken::Type::Minus });
  290. continue;
  291. }
  292. if (ch == '*') {
  293. tokens.append(CalcToken { CalcToken::Type::Asterisk });
  294. continue;
  295. }
  296. if (ch == '/') {
  297. tokens.append(CalcToken { CalcToken::Type::Slash });
  298. continue;
  299. }
  300. if (ch == '(') {
  301. tokens.append(CalcToken { CalcToken::Type::OpenBracket });
  302. continue;
  303. }
  304. if (ch == ')') {
  305. tokens.append(CalcToken { CalcToken::Type::CloseBracket });
  306. continue;
  307. }
  308. // Unit
  309. if (isalpha(ch)) {
  310. lexer.retreat();
  311. auto unit = lexer.consume_while(isalpha);
  312. tokens.append(CalcToken { CalcToken::Type::Unit, unit.to_string() });
  313. continue;
  314. }
  315. VERIFY_NOT_REACHED();
  316. }
  317. return {};
  318. }
  319. RefPtr<CSS::StyleValue> parse_css_value(const CSS::DeprecatedParsingContext& context, const StringView& string, CSS::PropertyID property_id)
  320. {
  321. bool is_bad_length = false;
  322. if (takes_integer_value(property_id)) {
  323. auto integer = string.to_int();
  324. if (integer.has_value())
  325. return CSS::LengthStyleValue::create(CSS::Length::make_px(integer.value()));
  326. }
  327. auto length = parse_length(context, string, is_bad_length);
  328. if (is_bad_length) {
  329. auto float_number = try_parse_float(string);
  330. if (float_number.has_value())
  331. return CSS::NumericStyleValue::create(float_number.value());
  332. return nullptr;
  333. }
  334. if (!length.is_undefined())
  335. return CSS::LengthStyleValue::create(length);
  336. if (string.equals_ignoring_case("inherit"))
  337. return CSS::InheritStyleValue::create();
  338. if (string.equals_ignoring_case("initial"))
  339. return CSS::InitialStyleValue::create();
  340. if (string.equals_ignoring_case("auto"))
  341. return CSS::LengthStyleValue::create(CSS::Length::make_auto());
  342. if (string.starts_with("var("))
  343. return CSS::CustomStyleValue::create(parse_custom_property_name(string));
  344. if (string.starts_with("calc(")) {
  345. auto calc_expression_string = isolate_calc_expression(string);
  346. auto calc_expression = parse_calc_expression(calc_expression_string);
  347. if (calc_expression.has_value())
  348. return CSS::CalculatedStyleValue::create(calc_expression_string, calc_expression.release_value());
  349. }
  350. auto value_id = CSS::value_id_from_string(string);
  351. if (value_id != CSS::ValueID::Invalid)
  352. return CSS::IdentifierStyleValue::create(value_id);
  353. auto color = parse_css_color(context, string);
  354. if (color.has_value())
  355. return CSS::ColorStyleValue::create(color.value());
  356. return CSS::StringStyleValue::create(string);
  357. }
  358. RefPtr<CSS::LengthStyleValue> parse_line_width(const CSS::DeprecatedParsingContext& context, const StringView& part)
  359. {
  360. auto value = parse_css_value(context, part);
  361. if (value && value->is_length())
  362. return static_ptr_cast<CSS::LengthStyleValue>(value);
  363. return nullptr;
  364. }
  365. RefPtr<CSS::ColorStyleValue> parse_color(const CSS::DeprecatedParsingContext& context, const StringView& part)
  366. {
  367. auto value = parse_css_value(context, part);
  368. if (value && value->is_color())
  369. return static_ptr_cast<CSS::ColorStyleValue>(value);
  370. return nullptr;
  371. }
  372. RefPtr<CSS::IdentifierStyleValue> parse_line_style(const CSS::DeprecatedParsingContext& context, const StringView& part)
  373. {
  374. auto parsed_value = parse_css_value(context, part);
  375. if (!parsed_value || parsed_value->type() != CSS::StyleValue::Type::Identifier)
  376. return nullptr;
  377. auto value = static_ptr_cast<CSS::IdentifierStyleValue>(parsed_value);
  378. if (value->id() == CSS::ValueID::Dotted)
  379. return value;
  380. if (value->id() == CSS::ValueID::Dashed)
  381. return value;
  382. if (value->id() == CSS::ValueID::Solid)
  383. return value;
  384. if (value->id() == CSS::ValueID::Double)
  385. return value;
  386. if (value->id() == CSS::ValueID::Groove)
  387. return value;
  388. if (value->id() == CSS::ValueID::Ridge)
  389. return value;
  390. if (value->id() == CSS::ValueID::None)
  391. return value;
  392. if (value->id() == CSS::ValueID::Hidden)
  393. return value;
  394. if (value->id() == CSS::ValueID::Inset)
  395. return value;
  396. if (value->id() == CSS::ValueID::Outset)
  397. return value;
  398. return nullptr;
  399. }
  400. class CSSParser {
  401. public:
  402. CSSParser(const CSS::DeprecatedParsingContext& context, const StringView& input)
  403. : m_context(context)
  404. , css(input)
  405. {
  406. }
  407. bool next_is(const char* str) const
  408. {
  409. size_t len = strlen(str);
  410. for (size_t i = 0; i < len; ++i) {
  411. if (peek(i) != str[i])
  412. return false;
  413. }
  414. return true;
  415. }
  416. char peek(size_t offset = 0) const
  417. {
  418. if ((index + offset) < css.length())
  419. return css[index + offset];
  420. return 0;
  421. }
  422. bool consume_specific(char ch)
  423. {
  424. if (peek() != ch) {
  425. dbgln("CSSParser: Peeked '{:c}' wanted specific '{:c}'", peek(), ch);
  426. }
  427. if (!peek()) {
  428. log_parse_error();
  429. return false;
  430. }
  431. if (peek() != ch) {
  432. log_parse_error();
  433. ++index;
  434. return false;
  435. }
  436. ++index;
  437. return true;
  438. }
  439. char consume_one()
  440. {
  441. PARSE_VERIFY(index < css.length());
  442. return css[index++];
  443. };
  444. bool consume_whitespace_or_comments()
  445. {
  446. size_t original_index = index;
  447. bool in_comment = false;
  448. for (; index < css.length(); ++index) {
  449. char ch = peek();
  450. if (isspace(ch))
  451. continue;
  452. if (!in_comment && ch == '/' && peek(1) == '*') {
  453. in_comment = true;
  454. ++index;
  455. continue;
  456. }
  457. if (in_comment && ch == '*' && peek(1) == '/') {
  458. in_comment = false;
  459. ++index;
  460. continue;
  461. }
  462. if (in_comment)
  463. continue;
  464. break;
  465. }
  466. return original_index != index;
  467. }
  468. static bool is_valid_selector_char(char ch)
  469. {
  470. return isalnum(ch) || ch == '-' || ch == '+' || ch == '_' || ch == '(' || ch == ')' || ch == '@';
  471. }
  472. static bool is_valid_selector_args_char(char ch)
  473. {
  474. return is_valid_selector_char(ch) || ch == ' ' || ch == '\t';
  475. }
  476. bool is_combinator(char ch) const
  477. {
  478. return ch == '~' || ch == '>' || ch == '+';
  479. }
  480. static StringView capture_selector_args(const String& pseudo_name)
  481. {
  482. if (const auto start_pos = pseudo_name.find('('); start_pos.has_value()) {
  483. const auto start = start_pos.value() + 1;
  484. if (const auto end_pos = pseudo_name.find(')', start); end_pos.has_value()) {
  485. return pseudo_name.substring_view(start, end_pos.value() - start).trim_whitespace();
  486. }
  487. }
  488. return {};
  489. }
  490. Optional<CSS::Selector::SimpleSelector> parse_simple_selector()
  491. {
  492. auto index_at_start = index;
  493. if (consume_whitespace_or_comments())
  494. return {};
  495. if (!peek() || peek() == '{' || peek() == ',' || is_combinator(peek()))
  496. return {};
  497. CSS::Selector::SimpleSelector simple_selector;
  498. if (peek() == '*') {
  499. simple_selector.type = CSS::Selector::SimpleSelector::Type::Universal;
  500. consume_one();
  501. return simple_selector;
  502. }
  503. if (peek() == '.') {
  504. simple_selector.type = CSS::Selector::SimpleSelector::Type::Class;
  505. consume_one();
  506. } else if (peek() == '#') {
  507. simple_selector.type = CSS::Selector::SimpleSelector::Type::Id;
  508. consume_one();
  509. } else if (isalpha(peek())) {
  510. simple_selector.type = CSS::Selector::SimpleSelector::Type::TagName;
  511. } else if (peek() == '[') {
  512. simple_selector.type = CSS::Selector::SimpleSelector::Type::Attribute;
  513. } else if (peek() == ':') {
  514. simple_selector.type = CSS::Selector::SimpleSelector::Type::PseudoClass;
  515. } else {
  516. simple_selector.type = CSS::Selector::SimpleSelector::Type::Universal;
  517. }
  518. if ((simple_selector.type != CSS::Selector::SimpleSelector::Type::Universal)
  519. && (simple_selector.type != CSS::Selector::SimpleSelector::Type::Attribute)
  520. && (simple_selector.type != CSS::Selector::SimpleSelector::Type::PseudoClass)) {
  521. while (is_valid_selector_char(peek()))
  522. buffer.append(consume_one());
  523. PARSE_VERIFY(!buffer.is_empty());
  524. }
  525. auto value = String::copy(buffer);
  526. if (simple_selector.type == CSS::Selector::SimpleSelector::Type::TagName) {
  527. // Some stylesheets use uppercase tag names, so here's a hack to just lowercase them internally.
  528. value = value.to_lowercase();
  529. }
  530. simple_selector.value = value;
  531. buffer.clear();
  532. if (simple_selector.type == CSS::Selector::SimpleSelector::Type::Attribute) {
  533. CSS::Selector::SimpleSelector::Attribute::MatchType attribute_match_type = CSS::Selector::SimpleSelector::Attribute::MatchType::HasAttribute;
  534. String attribute_name;
  535. String attribute_value;
  536. bool in_value = false;
  537. consume_specific('[');
  538. char expected_end_of_attribute_selector = ']';
  539. while (peek() != expected_end_of_attribute_selector) {
  540. char ch = consume_one();
  541. if (ch == '=' || (ch == '~' && peek() == '=')) {
  542. if (ch == '=') {
  543. attribute_match_type = CSS::Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch;
  544. } else if (ch == '~') {
  545. consume_one();
  546. attribute_match_type = CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsWord;
  547. }
  548. attribute_name = String::copy(buffer);
  549. buffer.clear();
  550. in_value = true;
  551. consume_whitespace_or_comments();
  552. if (peek() == '\'') {
  553. expected_end_of_attribute_selector = '\'';
  554. consume_one();
  555. } else if (peek() == '"') {
  556. expected_end_of_attribute_selector = '"';
  557. consume_one();
  558. }
  559. continue;
  560. }
  561. // FIXME: This is a hack that will go away when we replace this with a big boy CSS parser.
  562. if (ch == '\\')
  563. ch = consume_one();
  564. buffer.append(ch);
  565. }
  566. if (in_value)
  567. attribute_value = String::copy(buffer);
  568. else
  569. attribute_name = String::copy(buffer);
  570. buffer.clear();
  571. simple_selector.attribute.match_type = attribute_match_type;
  572. simple_selector.attribute.name = attribute_name;
  573. simple_selector.attribute.value = attribute_value;
  574. if (expected_end_of_attribute_selector != ']') {
  575. if (!consume_specific(expected_end_of_attribute_selector))
  576. return {};
  577. }
  578. consume_whitespace_or_comments();
  579. if (!consume_specific(']'))
  580. return {};
  581. }
  582. if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoClass) {
  583. // FIXME: Implement pseudo elements.
  584. [[maybe_unused]] bool is_pseudo_element = false;
  585. consume_one();
  586. if (peek() == ':') {
  587. is_pseudo_element = true;
  588. consume_one();
  589. }
  590. if (next_is("not")) {
  591. buffer.append(consume_one());
  592. buffer.append(consume_one());
  593. buffer.append(consume_one());
  594. if (!consume_specific('('))
  595. return {};
  596. buffer.append('(');
  597. while (peek() != ')')
  598. buffer.append(consume_one());
  599. if (!consume_specific(')'))
  600. return {};
  601. buffer.append(')');
  602. } else {
  603. int nesting_level = 0;
  604. while (true) {
  605. const auto ch = peek();
  606. if (ch == '(')
  607. ++nesting_level;
  608. else if (ch == ')' && nesting_level > 0)
  609. --nesting_level;
  610. if (nesting_level > 0 ? is_valid_selector_args_char(ch) : is_valid_selector_char(ch))
  611. buffer.append(consume_one());
  612. else
  613. break;
  614. };
  615. }
  616. auto pseudo_name = String::copy(buffer);
  617. buffer.clear();
  618. // Ignore for now, otherwise we produce a "false positive" selector
  619. // and apply styles to the element itself, not its pseudo element
  620. if (is_pseudo_element)
  621. return {};
  622. auto& pseudo_class = simple_selector.pseudo_class;
  623. if (pseudo_name.equals_ignoring_case("link")) {
  624. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Link;
  625. } else if (pseudo_name.equals_ignoring_case("visited")) {
  626. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Visited;
  627. } else if (pseudo_name.equals_ignoring_case("active")) {
  628. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Active;
  629. } else if (pseudo_name.equals_ignoring_case("hover")) {
  630. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Hover;
  631. } else if (pseudo_name.equals_ignoring_case("focus")) {
  632. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Focus;
  633. } else if (pseudo_name.equals_ignoring_case("first-child")) {
  634. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::FirstChild;
  635. } else if (pseudo_name.equals_ignoring_case("last-child")) {
  636. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::LastChild;
  637. } else if (pseudo_name.equals_ignoring_case("only-child")) {
  638. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::OnlyChild;
  639. } else if (pseudo_name.equals_ignoring_case("empty")) {
  640. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Empty;
  641. } else if (pseudo_name.equals_ignoring_case("root")) {
  642. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Root;
  643. } else if (pseudo_name.equals_ignoring_case("first-of-type")) {
  644. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::FirstOfType;
  645. } else if (pseudo_name.equals_ignoring_case("last-of-type")) {
  646. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::LastOfType;
  647. } else if (pseudo_name.starts_with("nth-child", CaseSensitivity::CaseInsensitive)) {
  648. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild;
  649. pseudo_class.nth_child_pattern = CSS::Selector::SimpleSelector::NthChildPattern::parse(capture_selector_args(pseudo_name));
  650. } else if (pseudo_name.starts_with("nth-last-child", CaseSensitivity::CaseInsensitive)) {
  651. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild;
  652. pseudo_class.nth_child_pattern = CSS::Selector::SimpleSelector::NthChildPattern::parse(capture_selector_args(pseudo_name));
  653. } else if (pseudo_name.equals_ignoring_case("before")) {
  654. simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::Before;
  655. } else if (pseudo_name.equals_ignoring_case("after")) {
  656. simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::After;
  657. } else if (pseudo_name.equals_ignoring_case("disabled")) {
  658. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Disabled;
  659. } else if (pseudo_name.equals_ignoring_case("enabled")) {
  660. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Enabled;
  661. } else if (pseudo_name.equals_ignoring_case("checked")) {
  662. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Checked;
  663. } else if (pseudo_name.starts_with("not", CaseSensitivity::CaseInsensitive)) {
  664. pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Not;
  665. auto not_selector = Web::parse_selector(m_context, capture_selector_args(pseudo_name));
  666. if (not_selector) {
  667. pseudo_class.not_selector.clear();
  668. pseudo_class.not_selector.append(not_selector.release_nonnull());
  669. }
  670. } else {
  671. dbgln("Unknown pseudo class: '{}'", pseudo_name);
  672. return {};
  673. }
  674. }
  675. if (index == index_at_start) {
  676. // We consumed nothing.
  677. return {};
  678. }
  679. return simple_selector;
  680. }
  681. Optional<CSS::Selector::ComplexSelector> parse_complex_selector()
  682. {
  683. auto relation = CSS::Selector::ComplexSelector::Relation::Descendant;
  684. if (peek() == '{' || peek() == ',')
  685. return {};
  686. if (is_combinator(peek())) {
  687. switch (peek()) {
  688. case '>':
  689. relation = CSS::Selector::ComplexSelector::Relation::ImmediateChild;
  690. break;
  691. case '+':
  692. relation = CSS::Selector::ComplexSelector::Relation::AdjacentSibling;
  693. break;
  694. case '~':
  695. relation = CSS::Selector::ComplexSelector::Relation::GeneralSibling;
  696. break;
  697. }
  698. consume_one();
  699. consume_whitespace_or_comments();
  700. }
  701. consume_whitespace_or_comments();
  702. Vector<CSS::Selector::SimpleSelector> simple_selectors;
  703. for (;;) {
  704. auto component = parse_simple_selector();
  705. if (!component.has_value())
  706. break;
  707. simple_selectors.append(component.value());
  708. // If this assert triggers, we're most likely up to no good.
  709. PARSE_VERIFY(simple_selectors.size() < 100);
  710. }
  711. if (simple_selectors.is_empty())
  712. return {};
  713. return CSS::Selector::ComplexSelector { relation, move(simple_selectors) };
  714. }
  715. void parse_selector()
  716. {
  717. Vector<CSS::Selector::ComplexSelector> complex_selectors;
  718. for (;;) {
  719. auto index_before = index;
  720. auto complex_selector = parse_complex_selector();
  721. if (complex_selector.has_value())
  722. complex_selectors.append(complex_selector.value());
  723. consume_whitespace_or_comments();
  724. if (!peek() || peek() == ',' || peek() == '{')
  725. break;
  726. // HACK: If we didn't move forward, just let go.
  727. if (index == index_before)
  728. break;
  729. }
  730. if (complex_selectors.is_empty())
  731. return;
  732. complex_selectors.first().relation = CSS::Selector::ComplexSelector::Relation::None;
  733. current_rule.selectors.append(CSS::Selector::create(move(complex_selectors)));
  734. }
  735. RefPtr<CSS::Selector> parse_individual_selector()
  736. {
  737. parse_selector();
  738. if (current_rule.selectors.is_empty())
  739. return {};
  740. return current_rule.selectors.last();
  741. }
  742. void parse_selector_list()
  743. {
  744. for (;;) {
  745. auto index_before = index;
  746. parse_selector();
  747. consume_whitespace_or_comments();
  748. if (peek() == ',') {
  749. consume_one();
  750. continue;
  751. }
  752. if (peek() == '{')
  753. break;
  754. // HACK: If we didn't move forward, just let go.
  755. if (index_before == index)
  756. break;
  757. }
  758. }
  759. bool is_valid_property_name_char(char ch) const
  760. {
  761. return ch && !isspace(ch) && ch != ':';
  762. }
  763. bool is_valid_property_value_char(char ch) const
  764. {
  765. return ch && ch != '!' && ch != ';' && ch != '}';
  766. }
  767. bool is_valid_string_quotes_char(char ch) const
  768. {
  769. return ch == '\'' || ch == '\"';
  770. }
  771. struct ValueAndImportant {
  772. String value;
  773. bool important { false };
  774. };
  775. ValueAndImportant consume_css_value()
  776. {
  777. buffer.clear();
  778. int paren_nesting_level = 0;
  779. bool important = false;
  780. for (;;) {
  781. char ch = peek();
  782. if (ch == '(') {
  783. ++paren_nesting_level;
  784. buffer.append(consume_one());
  785. continue;
  786. }
  787. if (ch == ')') {
  788. PARSE_VERIFY(paren_nesting_level > 0);
  789. --paren_nesting_level;
  790. buffer.append(consume_one());
  791. continue;
  792. }
  793. if (paren_nesting_level > 0) {
  794. buffer.append(consume_one());
  795. continue;
  796. }
  797. if (next_is("!important")) {
  798. consume_specific('!');
  799. consume_specific('i');
  800. consume_specific('m');
  801. consume_specific('p');
  802. consume_specific('o');
  803. consume_specific('r');
  804. consume_specific('t');
  805. consume_specific('a');
  806. consume_specific('n');
  807. consume_specific('t');
  808. important = true;
  809. continue;
  810. }
  811. if (next_is("/*")) {
  812. consume_whitespace_or_comments();
  813. continue;
  814. }
  815. if (!ch)
  816. break;
  817. if (ch == '\\') {
  818. consume_one();
  819. buffer.append(consume_one());
  820. continue;
  821. }
  822. if (ch == '}')
  823. break;
  824. if (ch == ';')
  825. break;
  826. buffer.append(consume_one());
  827. }
  828. // Remove trailing whitespace.
  829. while (!buffer.is_empty() && isspace(buffer.last()))
  830. buffer.take_last();
  831. auto string = String::copy(buffer);
  832. buffer.clear();
  833. return { string, important };
  834. }
  835. Optional<CSS::StyleProperty> parse_property()
  836. {
  837. consume_whitespace_or_comments();
  838. if (peek() == ';') {
  839. consume_one();
  840. return {};
  841. }
  842. if (peek() == '}')
  843. return {};
  844. buffer.clear();
  845. while (is_valid_property_name_char(peek()))
  846. buffer.append(consume_one());
  847. auto property_name = String::copy(buffer);
  848. buffer.clear();
  849. consume_whitespace_or_comments();
  850. if (!consume_specific(':'))
  851. return {};
  852. consume_whitespace_or_comments();
  853. auto [property_value, important] = consume_css_value();
  854. consume_whitespace_or_comments();
  855. if (peek() && peek() != '}') {
  856. if (!consume_specific(';'))
  857. return {};
  858. }
  859. auto property_id = CSS::property_id_from_string(property_name);
  860. if (property_id == CSS::PropertyID::Invalid && property_name.starts_with("--"))
  861. property_id = CSS::PropertyID::Custom;
  862. if (property_id == CSS::PropertyID::Invalid && !property_name.starts_with("-")) {
  863. dbgln("CSSParser: Unrecognized property '{}'", property_name);
  864. }
  865. auto value = parse_css_value(m_context, property_value, property_id);
  866. if (!value)
  867. return {};
  868. if (property_id == CSS::PropertyID::Custom) {
  869. return CSS::StyleProperty { property_id, value.release_nonnull(), property_name, important };
  870. }
  871. return CSS::StyleProperty { property_id, value.release_nonnull(), {}, important };
  872. }
  873. void parse_declaration()
  874. {
  875. for (;;) {
  876. auto property = parse_property();
  877. if (property.has_value()) {
  878. auto property_value = property.value();
  879. if (property_value.property_id == CSS::PropertyID::Custom)
  880. current_rule.custom_properties.set(property_value.custom_name, property_value);
  881. else
  882. current_rule.properties.append(property_value);
  883. }
  884. consume_whitespace_or_comments();
  885. if (!peek() || peek() == '}')
  886. break;
  887. }
  888. }
  889. void parse_style_rule()
  890. {
  891. parse_selector_list();
  892. if (!consume_specific('{')) {
  893. log_parse_error();
  894. return;
  895. }
  896. parse_declaration();
  897. if (!consume_specific('}')) {
  898. log_parse_error();
  899. return;
  900. }
  901. rules.append(CSS::CSSStyleRule::create(move(current_rule.selectors), CSS::CSSStyleDeclaration::create(move(current_rule.properties), move(current_rule.custom_properties))));
  902. }
  903. Optional<String> parse_string()
  904. {
  905. if (!is_valid_string_quotes_char(peek())) {
  906. log_parse_error();
  907. return {};
  908. }
  909. char end_char = consume_one();
  910. buffer.clear();
  911. while (peek() && peek() != end_char) {
  912. if (peek() == '\\') {
  913. consume_specific('\\');
  914. if (peek() == 0)
  915. break;
  916. }
  917. buffer.append(consume_one());
  918. }
  919. String string_value(String::copy(buffer));
  920. buffer.clear();
  921. if (consume_specific(end_char)) {
  922. return { string_value };
  923. }
  924. return {};
  925. }
  926. Optional<String> parse_url()
  927. {
  928. if (is_valid_string_quotes_char(peek()))
  929. return parse_string();
  930. buffer.clear();
  931. while (peek() && peek() != ')')
  932. buffer.append(consume_one());
  933. String url_value(String::copy(buffer));
  934. buffer.clear();
  935. if (peek() == ')')
  936. return { url_value };
  937. return {};
  938. }
  939. void parse_at_import_rule()
  940. {
  941. consume_whitespace_or_comments();
  942. Optional<String> imported_address;
  943. if (is_valid_string_quotes_char(peek())) {
  944. imported_address = parse_string();
  945. } else if (next_is("url")) {
  946. consume_specific('u');
  947. consume_specific('r');
  948. consume_specific('l');
  949. consume_whitespace_or_comments();
  950. if (!consume_specific('('))
  951. return;
  952. imported_address = parse_url();
  953. if (!consume_specific(')'))
  954. return;
  955. } else {
  956. log_parse_error();
  957. return;
  958. }
  959. if (imported_address.has_value())
  960. rules.append(CSS::CSSImportRule::create(m_context.complete_url(imported_address.value())));
  961. // FIXME: We ignore possible media query list
  962. while (peek() && peek() != ';')
  963. consume_one();
  964. consume_specific(';');
  965. }
  966. void parse_at_rule()
  967. {
  968. HashMap<String, void (CSSParser::*)()> at_rules_parsers({ { "@import", &CSSParser::parse_at_import_rule } });
  969. for (const auto& rule_parser_pair : at_rules_parsers) {
  970. if (next_is(rule_parser_pair.key.characters())) {
  971. for (char c : rule_parser_pair.key) {
  972. consume_specific(c);
  973. }
  974. (this->*(rule_parser_pair.value))();
  975. return;
  976. }
  977. }
  978. // FIXME: We ignore other @-rules completely for now.
  979. while (peek() != 0 && peek() != '{')
  980. consume_one();
  981. int level = 0;
  982. for (;;) {
  983. auto ch = consume_one();
  984. if (ch == '{') {
  985. ++level;
  986. } else if (ch == '}') {
  987. --level;
  988. if (level == 0)
  989. break;
  990. }
  991. }
  992. }
  993. void parse_rule()
  994. {
  995. consume_whitespace_or_comments();
  996. if (!peek())
  997. return;
  998. if (peek() == '@') {
  999. parse_at_rule();
  1000. } else {
  1001. parse_style_rule();
  1002. }
  1003. consume_whitespace_or_comments();
  1004. }
  1005. RefPtr<CSS::CSSStyleSheet> parse_sheet()
  1006. {
  1007. if (peek(0) == (char)0xef && peek(1) == (char)0xbb && peek(2) == (char)0xbf) {
  1008. // HACK: Skip UTF-8 BOM.
  1009. index += 3;
  1010. }
  1011. while (peek()) {
  1012. parse_rule();
  1013. }
  1014. return CSS::CSSStyleSheet::create(move(rules));
  1015. }
  1016. RefPtr<CSS::CSSStyleDeclaration> parse_standalone_declaration()
  1017. {
  1018. consume_whitespace_or_comments();
  1019. for (;;) {
  1020. auto property = parse_property();
  1021. if (property.has_value()) {
  1022. auto property_value = property.value();
  1023. if (property_value.property_id == CSS::PropertyID::Custom)
  1024. current_rule.custom_properties.set(property_value.custom_name, property_value);
  1025. else
  1026. current_rule.properties.append(property_value);
  1027. }
  1028. consume_whitespace_or_comments();
  1029. if (!peek())
  1030. break;
  1031. }
  1032. return CSS::CSSStyleDeclaration::create(move(current_rule.properties), move(current_rule.custom_properties));
  1033. }
  1034. private:
  1035. CSS::DeprecatedParsingContext m_context;
  1036. NonnullRefPtrVector<CSS::CSSRule> rules;
  1037. struct CurrentRule {
  1038. NonnullRefPtrVector<CSS::Selector> selectors;
  1039. Vector<CSS::StyleProperty> properties;
  1040. HashMap<String, CSS::StyleProperty> custom_properties;
  1041. };
  1042. CurrentRule current_rule;
  1043. Vector<char> buffer;
  1044. size_t index = 0;
  1045. StringView css;
  1046. };
  1047. RefPtr<CSS::Selector> parse_selector(const CSS::DeprecatedParsingContext& context, const StringView& selector_text)
  1048. {
  1049. CSSParser parser(context, selector_text);
  1050. return parser.parse_individual_selector();
  1051. }
  1052. RefPtr<CSS::CSSStyleSheet> parse_css(const CSS::DeprecatedParsingContext& context, const StringView& css)
  1053. {
  1054. if (css.is_empty())
  1055. return CSS::CSSStyleSheet::create({});
  1056. CSSParser parser(context, css);
  1057. return parser.parse_sheet();
  1058. }
  1059. RefPtr<CSS::CSSStyleDeclaration> parse_css_declaration(const CSS::DeprecatedParsingContext& context, const StringView& css)
  1060. {
  1061. if (css.is_empty())
  1062. return CSS::CSSStyleDeclaration::create({}, {});
  1063. CSSParser parser(context, css);
  1064. return parser.parse_standalone_declaration();
  1065. }
  1066. RefPtr<CSS::StyleValue> parse_html_length(const DOM::Document& document, const StringView& string)
  1067. {
  1068. auto integer = string.to_int();
  1069. if (integer.has_value())
  1070. return CSS::LengthStyleValue::create(CSS::Length::make_px(integer.value()));
  1071. return parse_css_value(CSS::DeprecatedParsingContext(document), string);
  1072. }
  1073. }