DeprecatedCSSParser.cpp 51 KB

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