RuleParsing.cpp 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. /*
  2. * Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
  3. * Copyright (c) 2020-2021, the SerenityOS developers.
  4. * Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
  5. * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
  6. * Copyright (c) 2022, MacDue <macdue@dueutil.tech>
  7. * Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
  8. * Copyright (c) 2024, Tommy van der Vorst <tommy@pixelspark.nl>
  9. * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
  10. * Copyright (c) 2024, Glenn Skrzypczak <glenn.skrzypczak@gmail.com>
  11. *
  12. * SPDX-License-Identifier: BSD-2-Clause
  13. */
  14. #include <LibWeb/CSS/CSSFontFaceRule.h>
  15. #include <LibWeb/CSS/CSSImportRule.h>
  16. #include <LibWeb/CSS/CSSKeyframeRule.h>
  17. #include <LibWeb/CSS/CSSKeyframesRule.h>
  18. #include <LibWeb/CSS/CSSLayerBlockRule.h>
  19. #include <LibWeb/CSS/CSSLayerStatementRule.h>
  20. #include <LibWeb/CSS/CSSMediaRule.h>
  21. #include <LibWeb/CSS/CSSNamespaceRule.h>
  22. #include <LibWeb/CSS/CSSNestedDeclarations.h>
  23. #include <LibWeb/CSS/CSSPropertyRule.h>
  24. #include <LibWeb/CSS/CSSStyleRule.h>
  25. #include <LibWeb/CSS/CSSSupportsRule.h>
  26. #include <LibWeb/CSS/Parser/Parser.h>
  27. #include <LibWeb/CSS/PropertyName.h>
  28. #include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
  29. #include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
  30. #include <LibWeb/CSS/StyleValues/OpenTypeTaggedStyleValue.h>
  31. #include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
  32. #include <LibWeb/CSS/StyleValues/StringStyleValue.h>
  33. #include <LibWeb/CSS/StyleValues/StyleValueList.h>
  34. namespace Web::CSS::Parser {
  35. JS::GCPtr<CSSRule> Parser::convert_to_rule(Rule const& rule, Nested nested)
  36. {
  37. return rule.visit(
  38. [this, nested](AtRule const& at_rule) -> JS::GCPtr<CSSRule> {
  39. if (has_ignored_vendor_prefix(at_rule.name))
  40. return {};
  41. if (at_rule.name.equals_ignoring_ascii_case("font-face"sv))
  42. return convert_to_font_face_rule(at_rule);
  43. if (at_rule.name.equals_ignoring_ascii_case("import"sv))
  44. return convert_to_import_rule(at_rule);
  45. if (at_rule.name.equals_ignoring_ascii_case("keyframes"sv))
  46. return convert_to_keyframes_rule(at_rule);
  47. if (at_rule.name.equals_ignoring_ascii_case("layer"sv))
  48. return convert_to_layer_rule(at_rule, nested);
  49. if (at_rule.name.equals_ignoring_ascii_case("media"sv))
  50. return convert_to_media_rule(at_rule, nested);
  51. if (at_rule.name.equals_ignoring_ascii_case("namespace"sv))
  52. return convert_to_namespace_rule(at_rule);
  53. if (at_rule.name.equals_ignoring_ascii_case("supports"sv))
  54. return convert_to_supports_rule(at_rule, nested);
  55. if (at_rule.name.equals_ignoring_ascii_case("property"sv))
  56. return convert_to_property_rule(at_rule);
  57. // FIXME: More at rules!
  58. dbgln_if(CSS_PARSER_DEBUG, "Unrecognized CSS at-rule: @{}", at_rule.name);
  59. return {};
  60. },
  61. [this, nested](QualifiedRule const& qualified_rule) -> JS::GCPtr<CSSRule> {
  62. return convert_to_style_rule(qualified_rule, nested);
  63. });
  64. }
  65. JS::GCPtr<CSSStyleRule> Parser::convert_to_style_rule(QualifiedRule const& qualified_rule, Nested nested)
  66. {
  67. TokenStream prelude_stream { qualified_rule.prelude };
  68. auto maybe_selectors = parse_a_selector_list(prelude_stream,
  69. nested == Nested::Yes ? SelectorType::Relative : SelectorType::Standalone);
  70. if (maybe_selectors.is_error()) {
  71. if (maybe_selectors.error() == ParseError::SyntaxError) {
  72. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule selectors invalid; discarding.");
  73. if constexpr (CSS_PARSER_DEBUG) {
  74. prelude_stream.dump_all_tokens();
  75. }
  76. }
  77. return {};
  78. }
  79. if (maybe_selectors.value().is_empty()) {
  80. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: empty selector; discarding.");
  81. return {};
  82. }
  83. SelectorList selectors = maybe_selectors.release_value();
  84. if (nested == Nested::Yes) {
  85. // "Nested style rules differ from non-nested rules in the following ways:
  86. // - A nested style rule accepts a <relative-selector-list> as its prelude (rather than just a <selector-list>).
  87. // Any relative selectors are relative to the elements represented by the nesting selector.
  88. // - If a selector in the <relative-selector-list> does not start with a combinator but does contain the nesting
  89. // selector, it is interpreted as a non-relative selector."
  90. // https://drafts.csswg.org/css-nesting-1/#syntax
  91. // NOTE: We already parsed the selectors as a <relative-selector-list>
  92. // Nested relative selectors get a `&` inserted at the beginning.
  93. // This is, handily, how the spec wants them serialized:
  94. // "When serializing a relative selector in a nested style rule, the selector must be absolutized,
  95. // with the implied nesting selector inserted."
  96. // - https://drafts.csswg.org/css-nesting-1/#cssom
  97. SelectorList new_list;
  98. new_list.ensure_capacity(selectors.size());
  99. for (auto const& selector : selectors) {
  100. auto first_combinator = selector->compound_selectors().first().combinator;
  101. if (!first_is_one_of(first_combinator, Selector::Combinator::None, Selector::Combinator::Descendant)
  102. || !selector->contains_the_nesting_selector()) {
  103. new_list.append(selector->relative_to(Selector::SimpleSelector { .type = Selector::SimpleSelector::Type::Nesting }));
  104. } else if (first_combinator == Selector::Combinator::Descendant) {
  105. // Replace leading descendant combinator (whitespace) with none, because we're not actually relative.
  106. auto copied_compound_selectors = selector->compound_selectors();
  107. copied_compound_selectors.first().combinator = Selector::Combinator::None;
  108. new_list.append(Selector::create(move(copied_compound_selectors)));
  109. } else {
  110. new_list.append(selector);
  111. }
  112. }
  113. selectors = move(new_list);
  114. }
  115. auto* declaration = convert_to_style_declaration(qualified_rule.declarations);
  116. if (!declaration) {
  117. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule declaration invalid; discarding.");
  118. return {};
  119. }
  120. JS::MarkedVector<CSSRule*> child_rules { m_context.realm().heap() };
  121. for (auto& child : qualified_rule.child_rules) {
  122. child.visit(
  123. [&](Rule const& rule) {
  124. // "In addition to nested style rules, this specification allows nested group rules inside of style rules:
  125. // any at-rule whose body contains style rules can be nested inside of a style rule as well."
  126. // https://drafts.csswg.org/css-nesting-1/#nested-group-rules
  127. if (auto converted_rule = convert_to_rule(rule, Nested::Yes)) {
  128. if (is<CSSGroupingRule>(*converted_rule)) {
  129. child_rules.append(converted_rule);
  130. } else {
  131. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: nested {} is not allowed inside style rule; discarding.", converted_rule->class_name());
  132. }
  133. }
  134. },
  135. [&](Vector<Declaration> const& declarations) {
  136. auto* declaration = convert_to_style_declaration(declarations);
  137. if (!declaration) {
  138. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: nested declarations invalid; discarding.");
  139. return;
  140. }
  141. child_rules.append(CSSNestedDeclarations::create(m_context.realm(), *declaration));
  142. });
  143. }
  144. auto nested_rules = CSSRuleList::create(m_context.realm(), move(child_rules));
  145. return CSSStyleRule::create(m_context.realm(), move(selectors), *declaration, *nested_rules);
  146. }
  147. JS::GCPtr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
  148. {
  149. // https://drafts.csswg.org/css-cascade-5/#at-import
  150. // @import [ <url> | <string> ]
  151. // [ layer | layer(<layer-name>) ]?
  152. // <import-conditions> ;
  153. //
  154. // <import-conditions> = [ supports( [ <supports-condition> | <declaration> ] ) ]?
  155. // <media-query-list>?
  156. if (rule.prelude.is_empty()) {
  157. dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Empty prelude.");
  158. return {};
  159. }
  160. if (!rule.child_rules_and_lists_of_declarations.is_empty()) {
  161. dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Block is not allowed.");
  162. return {};
  163. }
  164. TokenStream tokens { rule.prelude };
  165. tokens.discard_whitespace();
  166. Optional<URL::URL> url = parse_url_function(tokens);
  167. if (!url.has_value() && tokens.next_token().is(Token::Type::String))
  168. url = m_context.complete_url(tokens.consume_a_token().token().string());
  169. if (!url.has_value()) {
  170. dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Unable to parse `{}` as URL.", tokens.next_token().to_debug_string());
  171. return {};
  172. }
  173. tokens.discard_whitespace();
  174. // TODO: Support layers and import-conditions
  175. if (tokens.has_next_token()) {
  176. if constexpr (CSS_PARSER_DEBUG) {
  177. dbgln("Failed to parse @import rule: Trailing tokens after URL are not yet supported.");
  178. tokens.dump_all_tokens();
  179. }
  180. return {};
  181. }
  182. return CSSImportRule::create(url.value(), const_cast<DOM::Document&>(*m_context.document()));
  183. }
  184. Optional<FlyString> Parser::parse_layer_name(TokenStream<ComponentValue>& tokens, AllowBlankLayerName allow_blank_layer_name)
  185. {
  186. // https://drafts.csswg.org/css-cascade-5/#typedef-layer-name
  187. // <layer-name> = <ident> [ '.' <ident> ]*
  188. // "The CSS-wide keywords are reserved for future use, and cause the rule to be invalid at parse time if used as an <ident> in the <layer-name>."
  189. auto is_valid_layer_name_part = [](auto& token) {
  190. return token.is(Token::Type::Ident) && !is_css_wide_keyword(token.token().ident());
  191. };
  192. auto transaction = tokens.begin_transaction();
  193. tokens.discard_whitespace();
  194. if (!tokens.has_next_token() && allow_blank_layer_name == AllowBlankLayerName::Yes) {
  195. // No name present, just return a blank one
  196. return FlyString();
  197. }
  198. auto& first_name_token = tokens.consume_a_token();
  199. if (!is_valid_layer_name_part(first_name_token))
  200. return {};
  201. StringBuilder builder;
  202. builder.append(first_name_token.token().ident());
  203. while (tokens.has_next_token()) {
  204. // Repeatedly parse `'.' <ident>`
  205. if (!tokens.next_token().is_delim('.'))
  206. break;
  207. tokens.discard_a_token(); // '.'
  208. auto& name_token = tokens.consume_a_token();
  209. if (!is_valid_layer_name_part(name_token))
  210. return {};
  211. builder.appendff(".{}", name_token.token().ident());
  212. }
  213. transaction.commit();
  214. return builder.to_fly_string_without_validation();
  215. }
  216. JS::GCPtr<CSSRule> Parser::convert_to_layer_rule(AtRule const& rule, Nested nested)
  217. {
  218. // https://drafts.csswg.org/css-cascade-5/#at-layer
  219. if (!rule.child_rules_and_lists_of_declarations.is_empty()) {
  220. // CSSLayerBlockRule
  221. // @layer <layer-name>? {
  222. // <rule-list>
  223. // }
  224. // First, the name
  225. FlyString layer_name = {};
  226. auto prelude_tokens = TokenStream { rule.prelude };
  227. if (auto maybe_name = parse_layer_name(prelude_tokens, AllowBlankLayerName::Yes); maybe_name.has_value()) {
  228. layer_name = maybe_name.release_value();
  229. } else {
  230. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer has invalid prelude, (not a valid layer name) prelude = {}; discarding.", rule.prelude);
  231. return {};
  232. }
  233. prelude_tokens.discard_whitespace();
  234. if (prelude_tokens.has_next_token()) {
  235. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer has invalid prelude, (tokens after layer name) prelude = {}; discarding.", rule.prelude);
  236. return {};
  237. }
  238. // Then the rules
  239. JS::MarkedVector<CSSRule*> child_rules { m_context.realm().heap() };
  240. rule.for_each_as_rule_list([&](auto& rule) {
  241. if (auto child_rule = convert_to_rule(rule, nested))
  242. child_rules.append(child_rule);
  243. });
  244. auto rule_list = CSSRuleList::create(m_context.realm(), child_rules);
  245. return CSSLayerBlockRule::create(m_context.realm(), layer_name, rule_list);
  246. }
  247. // CSSLayerStatementRule
  248. // @layer <layer-name>#;
  249. auto tokens = TokenStream { rule.prelude };
  250. tokens.discard_whitespace();
  251. Vector<FlyString> layer_names;
  252. while (tokens.has_next_token()) {
  253. // Comma
  254. if (!layer_names.is_empty()) {
  255. if (auto comma = tokens.consume_a_token(); !comma.is(Token::Type::Comma)) {
  256. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer missing separating comma, ({}) prelude = {}; discarding.", comma.to_debug_string(), rule.prelude);
  257. return {};
  258. }
  259. tokens.discard_whitespace();
  260. }
  261. if (auto name = parse_layer_name(tokens, AllowBlankLayerName::No); name.has_value()) {
  262. layer_names.append(name.release_value());
  263. } else {
  264. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer contains invalid name, prelude = {}; discarding.", rule.prelude);
  265. return {};
  266. }
  267. tokens.discard_whitespace();
  268. }
  269. if (layer_names.is_empty()) {
  270. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer statement has no layer names, prelude = {}; discarding.", rule.prelude);
  271. return {};
  272. }
  273. return CSSLayerStatementRule::create(m_context.realm(), move(layer_names));
  274. }
  275. JS::GCPtr<CSSKeyframesRule> Parser::convert_to_keyframes_rule(AtRule const& rule)
  276. {
  277. // https://drafts.csswg.org/css-animations/#keyframes
  278. // @keyframes = @keyframes <keyframes-name> { <qualified-rule-list> }
  279. // <keyframes-name> = <custom-ident> | <string>
  280. // <keyframe-block> = <keyframe-selector># { <declaration-list> }
  281. // <keyframe-selector> = from | to | <percentage [0,100]>
  282. if (rule.prelude.is_empty()) {
  283. dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @keyframes rule: Empty prelude.");
  284. return {};
  285. }
  286. // FIXME: Is there some way of detecting if there is a block or not?
  287. auto prelude_stream = TokenStream { rule.prelude };
  288. prelude_stream.discard_whitespace();
  289. auto& token = prelude_stream.consume_a_token();
  290. if (!token.is_token()) {
  291. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes has invalid prelude, prelude = {}; discarding.", rule.prelude);
  292. return {};
  293. }
  294. auto name_token = token.token();
  295. prelude_stream.discard_whitespace();
  296. if (prelude_stream.has_next_token()) {
  297. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes has invalid prelude, prelude = {}; discarding.", rule.prelude);
  298. return {};
  299. }
  300. if (name_token.is(Token::Type::Ident) && (is_css_wide_keyword(name_token.ident()) || name_token.ident().equals_ignoring_ascii_case("none"sv))) {
  301. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes rule name is invalid: {}; discarding.", name_token.ident());
  302. return {};
  303. }
  304. if (!name_token.is(Token::Type::String) && !name_token.is(Token::Type::Ident)) {
  305. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes rule name is invalid: {}; discarding.", name_token.to_debug_string());
  306. return {};
  307. }
  308. auto name = name_token.to_string();
  309. JS::MarkedVector<CSSRule*> keyframes(m_context.realm().heap());
  310. rule.for_each_as_qualified_rule_list([&](auto& qualified_rule) {
  311. if (!qualified_rule.child_rules.is_empty()) {
  312. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes keyframe rule contains at-rules; discarding them.");
  313. }
  314. auto selectors = Vector<CSS::Percentage> {};
  315. TokenStream child_tokens { qualified_rule.prelude };
  316. while (child_tokens.has_next_token()) {
  317. child_tokens.discard_whitespace();
  318. if (!child_tokens.has_next_token())
  319. break;
  320. auto tok = child_tokens.consume_a_token();
  321. if (!tok.is_token()) {
  322. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes rule has invalid selector: {}; discarding.", tok.to_debug_string());
  323. child_tokens.reconsume_current_input_token();
  324. break;
  325. }
  326. auto token = tok.token();
  327. auto read_a_selector = false;
  328. if (token.is(Token::Type::Ident)) {
  329. if (token.ident().equals_ignoring_ascii_case("from"sv)) {
  330. selectors.append(CSS::Percentage(0));
  331. read_a_selector = true;
  332. }
  333. if (token.ident().equals_ignoring_ascii_case("to"sv)) {
  334. selectors.append(CSS::Percentage(100));
  335. read_a_selector = true;
  336. }
  337. } else if (token.is(Token::Type::Percentage)) {
  338. selectors.append(CSS::Percentage(token.percentage()));
  339. read_a_selector = true;
  340. }
  341. if (read_a_selector) {
  342. child_tokens.discard_whitespace();
  343. if (child_tokens.consume_a_token().is(Token::Type::Comma))
  344. continue;
  345. }
  346. child_tokens.reconsume_current_input_token();
  347. break;
  348. }
  349. PropertiesAndCustomProperties properties;
  350. qualified_rule.for_each_as_declaration_list([&](auto const& declaration) {
  351. extract_property(declaration, properties);
  352. });
  353. auto style = PropertyOwningCSSStyleDeclaration::create(m_context.realm(), move(properties.properties), move(properties.custom_properties));
  354. for (auto& selector : selectors) {
  355. auto keyframe_rule = CSSKeyframeRule::create(m_context.realm(), selector, *style);
  356. keyframes.append(keyframe_rule);
  357. }
  358. });
  359. return CSSKeyframesRule::create(m_context.realm(), name, CSSRuleList::create(m_context.realm(), move(keyframes)));
  360. }
  361. JS::GCPtr<CSSNamespaceRule> Parser::convert_to_namespace_rule(AtRule const& rule)
  362. {
  363. // https://drafts.csswg.org/css-namespaces/#syntax
  364. // @namespace <namespace-prefix>? [ <string> | <url> ] ;
  365. // <namespace-prefix> = <ident>
  366. if (rule.prelude.is_empty()) {
  367. dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @namespace rule: Empty prelude.");
  368. return {};
  369. }
  370. if (!rule.child_rules_and_lists_of_declarations.is_empty()) {
  371. dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @namespace rule: Block is not allowed.");
  372. return {};
  373. }
  374. auto tokens = TokenStream { rule.prelude };
  375. tokens.discard_whitespace();
  376. Optional<FlyString> prefix = {};
  377. if (tokens.next_token().is(Token::Type::Ident)) {
  378. prefix = tokens.consume_a_token().token().ident();
  379. tokens.discard_whitespace();
  380. }
  381. FlyString namespace_uri;
  382. if (auto url = parse_url_function(tokens); url.has_value()) {
  383. namespace_uri = MUST(url.value().to_string());
  384. } else if (auto& url_token = tokens.consume_a_token(); url_token.is(Token::Type::String)) {
  385. namespace_uri = url_token.token().string();
  386. } else {
  387. dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @namespace rule: Unable to parse `{}` as URL.", tokens.next_token().to_debug_string());
  388. return {};
  389. }
  390. tokens.discard_whitespace();
  391. if (tokens.has_next_token()) {
  392. if constexpr (CSS_PARSER_DEBUG) {
  393. dbgln("Failed to parse @namespace rule: Trailing tokens after URL.");
  394. tokens.dump_all_tokens();
  395. }
  396. return {};
  397. }
  398. return CSSNamespaceRule::create(m_context.realm(), prefix, namespace_uri);
  399. }
  400. JS::GCPtr<CSSSupportsRule> Parser::convert_to_supports_rule(AtRule const& rule, Nested nested)
  401. {
  402. // https://drafts.csswg.org/css-conditional-3/#at-supports
  403. // @supports <supports-condition> {
  404. // <rule-list>
  405. // }
  406. if (rule.prelude.is_empty()) {
  407. dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @supports rule: Empty prelude.");
  408. return {};
  409. }
  410. auto supports_tokens = TokenStream { rule.prelude };
  411. auto supports = parse_a_supports(supports_tokens);
  412. if (!supports) {
  413. if constexpr (CSS_PARSER_DEBUG) {
  414. dbgln("Failed to parse @supports rule: supports clause invalid.");
  415. supports_tokens.dump_all_tokens();
  416. }
  417. return {};
  418. }
  419. JS::MarkedVector<CSSRule*> child_rules { m_context.realm().heap() };
  420. rule.for_each_as_rule_list([&](auto& rule) {
  421. if (auto child_rule = convert_to_rule(rule, nested))
  422. child_rules.append(child_rule);
  423. });
  424. auto rule_list = CSSRuleList::create(m_context.realm(), child_rules);
  425. return CSSSupportsRule::create(m_context.realm(), supports.release_nonnull(), rule_list);
  426. }
  427. JS::GCPtr<CSSPropertyRule> Parser::convert_to_property_rule(AtRule const& rule)
  428. {
  429. // https://drafts.css-houdini.org/css-properties-values-api-1/#at-ruledef-property
  430. // @property <custom-property-name> {
  431. // <declaration-list>
  432. // }
  433. if (rule.prelude.is_empty()) {
  434. dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @property rule: Empty prelude.");
  435. return {};
  436. }
  437. auto prelude_stream = TokenStream { rule.prelude };
  438. prelude_stream.discard_whitespace();
  439. auto const& token = prelude_stream.consume_a_token();
  440. if (!token.is_token()) {
  441. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @property has invalid prelude, prelude = {}; discarding.", rule.prelude);
  442. return {};
  443. }
  444. auto name_token = token.token();
  445. prelude_stream.discard_whitespace();
  446. if (prelude_stream.has_next_token()) {
  447. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @property has invalid prelude, prelude = {}; discarding.", rule.prelude);
  448. return {};
  449. }
  450. if (!name_token.is(Token::Type::Ident)) {
  451. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @property name is invalid: {}; discarding.", name_token.to_debug_string());
  452. return {};
  453. }
  454. if (!is_a_custom_property_name_string(name_token.ident())) {
  455. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @property name doesn't start with '--': {}; discarding.", name_token.ident());
  456. return {};
  457. }
  458. auto const& name = name_token.ident();
  459. Optional<FlyString> syntax_maybe;
  460. Optional<bool> inherits_maybe;
  461. Optional<String> initial_value_maybe;
  462. rule.for_each_as_declaration_list([&](auto& declaration) {
  463. if (declaration.name.equals_ignoring_ascii_case("syntax"sv)) {
  464. TokenStream token_stream { declaration.value };
  465. token_stream.discard_whitespace();
  466. auto const& syntax_token = token_stream.consume_a_token();
  467. if (syntax_token.is(Token::Type::String)) {
  468. token_stream.discard_whitespace();
  469. if (token_stream.has_next_token()) {
  470. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected trailing tokens in syntax");
  471. } else {
  472. syntax_maybe = syntax_token.token().string();
  473. }
  474. } else {
  475. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected value for @property \"syntax\": {}; discarding.", declaration.to_string());
  476. }
  477. return;
  478. }
  479. if (declaration.name.equals_ignoring_ascii_case("inherits"sv)) {
  480. TokenStream token_stream { declaration.value };
  481. token_stream.discard_whitespace();
  482. auto const& inherits_token = token_stream.consume_a_token();
  483. if (inherits_token.is_ident("true"sv) || inherits_token.is_ident("false"sv)) {
  484. auto const& ident = inherits_token.token().ident();
  485. token_stream.discard_whitespace();
  486. if (token_stream.has_next_token()) {
  487. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected trailing tokens in inherits");
  488. } else {
  489. inherits_maybe = (ident == "true");
  490. }
  491. } else {
  492. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Expected true/false for @property \"inherits\" value, got: {}; discarding.", inherits_token.to_debug_string());
  493. }
  494. return;
  495. }
  496. if (declaration.name.equals_ignoring_ascii_case("initial-value"sv)) {
  497. // FIXME: Ensure that the initial value matches the syntax, and parse the correct CSSValue out
  498. StringBuilder initial_value_sb;
  499. for (auto const& component : declaration.value) {
  500. initial_value_sb.append(component.to_string());
  501. }
  502. initial_value_maybe = MUST(initial_value_sb.to_string());
  503. return;
  504. }
  505. });
  506. if (syntax_maybe.has_value() && inherits_maybe.has_value()) {
  507. return CSSPropertyRule::create(m_context.realm(), name, syntax_maybe.value(), inherits_maybe.value(), std::move(initial_value_maybe));
  508. }
  509. return {};
  510. }
  511. JS::GCPtr<CSSFontFaceRule> Parser::convert_to_font_face_rule(AtRule const& rule)
  512. {
  513. // https://drafts.csswg.org/css-fonts/#font-face-rule
  514. Optional<FlyString> font_family;
  515. Optional<FlyString> font_named_instance;
  516. Vector<ParsedFontFace::Source> src;
  517. Vector<Gfx::UnicodeRange> unicode_range;
  518. Optional<int> weight;
  519. Optional<int> slope;
  520. Optional<int> width;
  521. Optional<Percentage> ascent_override;
  522. Optional<Percentage> descent_override;
  523. Optional<Percentage> line_gap_override;
  524. FontDisplay font_display = FontDisplay::Auto;
  525. Optional<FlyString> language_override;
  526. Optional<OrderedHashMap<FlyString, i64>> font_feature_settings;
  527. Optional<OrderedHashMap<FlyString, double>> font_variation_settings;
  528. // "normal" is returned as nullptr
  529. auto parse_as_percentage_or_normal = [&](Vector<ComponentValue> const& values) -> ErrorOr<Optional<Percentage>> {
  530. // normal | <percentage [0,∞]>
  531. TokenStream tokens { values };
  532. if (auto percentage_value = parse_percentage_value(tokens)) {
  533. tokens.discard_whitespace();
  534. if (tokens.has_next_token())
  535. return Error::from_string_literal("Unexpected trailing tokens");
  536. if (percentage_value->is_percentage() && percentage_value->as_percentage().percentage().value() >= 0)
  537. return percentage_value->as_percentage().percentage();
  538. // TODO: Once we implement calc-simplification in the parser, we should no longer see math values here,
  539. // unless they're impossible to resolve and thus invalid.
  540. if (percentage_value->is_math()) {
  541. if (auto result = percentage_value->as_math().resolve_percentage(); result.has_value())
  542. return result.value();
  543. }
  544. return Error::from_string_literal("Invalid percentage");
  545. }
  546. tokens.discard_whitespace();
  547. if (!tokens.consume_a_token().is_ident("normal"sv))
  548. return Error::from_string_literal("Expected `normal | <percentage [0,∞]>`");
  549. tokens.discard_whitespace();
  550. if (tokens.has_next_token())
  551. return Error::from_string_literal("Unexpected trailing tokens");
  552. return OptionalNone {};
  553. };
  554. rule.for_each_as_declaration_list([&](auto& declaration) {
  555. if (declaration.name.equals_ignoring_ascii_case("ascent-override"sv)) {
  556. auto value = parse_as_percentage_or_normal(declaration.value);
  557. if (value.is_error()) {
  558. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face ascent-override: {}", value.error());
  559. } else {
  560. ascent_override = value.release_value();
  561. }
  562. return;
  563. }
  564. if (declaration.name.equals_ignoring_ascii_case("descent-override"sv)) {
  565. auto value = parse_as_percentage_or_normal(declaration.value);
  566. if (value.is_error()) {
  567. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face descent-override: {}", value.error());
  568. } else {
  569. descent_override = value.release_value();
  570. }
  571. return;
  572. }
  573. if (declaration.name.equals_ignoring_ascii_case("font-display"sv)) {
  574. TokenStream token_stream { declaration.value };
  575. if (auto keyword_value = parse_keyword_value(token_stream)) {
  576. token_stream.discard_whitespace();
  577. if (token_stream.has_next_token()) {
  578. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected trailing tokens in font-display");
  579. } else {
  580. auto value = keyword_to_font_display(keyword_value->to_keyword());
  581. if (value.has_value()) {
  582. font_display = *value;
  583. } else {
  584. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: `{}` is not a valid value for font-display", keyword_value->to_string());
  585. }
  586. }
  587. }
  588. return;
  589. }
  590. if (declaration.name.equals_ignoring_ascii_case("font-family"sv)) {
  591. // FIXME: This is very similar to, but different from, the logic in parse_font_family_value().
  592. // Ideally they could share code.
  593. Vector<FlyString> font_family_parts;
  594. bool had_syntax_error = false;
  595. for (size_t i = 0; i < declaration.value.size(); ++i) {
  596. auto const& part = declaration.value[i];
  597. if (part.is(Token::Type::Whitespace))
  598. continue;
  599. if (part.is(Token::Type::String)) {
  600. if (!font_family_parts.is_empty()) {
  601. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
  602. had_syntax_error = true;
  603. break;
  604. }
  605. font_family_parts.append(part.token().string());
  606. continue;
  607. }
  608. if (part.is(Token::Type::Ident)) {
  609. if (is_css_wide_keyword(part.token().ident())) {
  610. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
  611. had_syntax_error = true;
  612. break;
  613. }
  614. auto keyword = keyword_from_string(part.token().ident());
  615. if (keyword.has_value() && is_generic_font_family(keyword.value())) {
  616. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
  617. had_syntax_error = true;
  618. break;
  619. }
  620. font_family_parts.append(part.token().ident());
  621. continue;
  622. }
  623. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
  624. had_syntax_error = true;
  625. break;
  626. }
  627. if (had_syntax_error || font_family_parts.is_empty())
  628. return;
  629. font_family = String::join(' ', font_family_parts).release_value_but_fixme_should_propagate_errors();
  630. return;
  631. }
  632. if (declaration.name.equals_ignoring_ascii_case("font-feature-settings"sv)) {
  633. TokenStream token_stream { declaration.value };
  634. if (auto value = parse_css_value(CSS::PropertyID::FontFeatureSettings, token_stream); !value.is_error()) {
  635. if (value.value()->to_keyword() == Keyword::Normal) {
  636. font_feature_settings.clear();
  637. } else if (value.value()->is_value_list()) {
  638. auto const& feature_tags = value.value()->as_value_list().values();
  639. OrderedHashMap<FlyString, i64> settings;
  640. settings.ensure_capacity(feature_tags.size());
  641. for (auto const& feature_tag : feature_tags) {
  642. if (!feature_tag->is_open_type_tagged()) {
  643. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-feature-settings descriptor is not an OpenTypeTaggedStyleValue; skipping");
  644. continue;
  645. }
  646. auto const& setting_value = feature_tag->as_open_type_tagged().value();
  647. if (setting_value->is_integer()) {
  648. settings.set(feature_tag->as_open_type_tagged().tag(), setting_value->as_integer().integer());
  649. } else if (setting_value->is_math() && setting_value->as_math().resolves_to_number()) {
  650. if (auto integer = setting_value->as_math().resolve_integer(); integer.has_value()) {
  651. settings.set(feature_tag->as_open_type_tagged().tag(), *integer);
  652. } else {
  653. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Calculated value in font-feature-settings descriptor cannot be resolved at parse time; skipping");
  654. }
  655. } else {
  656. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-feature-settings descriptor is not an OpenTypeTaggedStyleValue holding a <integer>; skipping");
  657. }
  658. }
  659. font_feature_settings = move(settings);
  660. } else {
  661. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-feature-settings descriptor, not compatible with value returned from parsing font-feature-settings property: {}", value.value()->to_string());
  662. }
  663. }
  664. return;
  665. }
  666. if (declaration.name.equals_ignoring_ascii_case("font-language-override"sv)) {
  667. TokenStream token_stream { declaration.value };
  668. if (auto maybe_value = parse_css_value(CSS::PropertyID::FontLanguageOverride, token_stream); !maybe_value.is_error()) {
  669. auto& value = maybe_value.value();
  670. if (value->is_string()) {
  671. language_override = value->as_string().string_value();
  672. } else {
  673. language_override.clear();
  674. }
  675. }
  676. return;
  677. }
  678. if (declaration.name.equals_ignoring_ascii_case("font-named-instance"sv)) {
  679. // auto | <string>
  680. TokenStream token_stream { declaration.value };
  681. token_stream.discard_whitespace();
  682. auto& token = token_stream.consume_a_token();
  683. token_stream.discard_whitespace();
  684. if (token_stream.has_next_token()) {
  685. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected trailing tokens in font-named-instance");
  686. return;
  687. }
  688. if (token.is_ident("auto"sv)) {
  689. font_named_instance.clear();
  690. } else if (token.is(Token::Type::String)) {
  691. font_named_instance = token.token().string();
  692. } else {
  693. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-named-instance from {}", token.to_debug_string());
  694. }
  695. return;
  696. }
  697. if (declaration.name.equals_ignoring_ascii_case("font-style"sv)) {
  698. TokenStream token_stream { declaration.value };
  699. if (auto value = parse_css_value(CSS::PropertyID::FontStyle, token_stream); !value.is_error()) {
  700. slope = value.value()->to_font_slope();
  701. }
  702. return;
  703. }
  704. if (declaration.name.equals_ignoring_ascii_case("font-variation-settings"sv)) {
  705. TokenStream token_stream { declaration.value };
  706. if (auto value = parse_css_value(CSS::PropertyID::FontVariationSettings, token_stream); !value.is_error()) {
  707. if (value.value()->to_keyword() == Keyword::Normal) {
  708. font_variation_settings.clear();
  709. } else if (value.value()->is_value_list()) {
  710. auto const& variation_tags = value.value()->as_value_list().values();
  711. OrderedHashMap<FlyString, double> settings;
  712. settings.ensure_capacity(variation_tags.size());
  713. for (auto const& variation_tag : variation_tags) {
  714. if (!variation_tag->is_open_type_tagged()) {
  715. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-variation-settings descriptor is not an OpenTypeTaggedStyleValue; skipping");
  716. continue;
  717. }
  718. auto const& setting_value = variation_tag->as_open_type_tagged().value();
  719. if (setting_value->is_number()) {
  720. settings.set(variation_tag->as_open_type_tagged().tag(), setting_value->as_number().number());
  721. } else if (setting_value->is_math() && setting_value->as_math().resolves_to_number()) {
  722. if (auto number = setting_value->as_math().resolve_number(); number.has_value()) {
  723. settings.set(variation_tag->as_open_type_tagged().tag(), *number);
  724. } else {
  725. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Calculated value in font-variation-settings descriptor cannot be resolved at parse time; skipping");
  726. }
  727. } else {
  728. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-variation-settings descriptor is not an OpenTypeTaggedStyleValue holding a <number>; skipping");
  729. }
  730. }
  731. font_variation_settings = move(settings);
  732. } else {
  733. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-variation-settings descriptor, not compatible with value returned from parsing font-variation-settings property: {}", value.value()->to_string());
  734. }
  735. }
  736. return;
  737. }
  738. if (declaration.name.equals_ignoring_ascii_case("font-weight"sv)) {
  739. TokenStream token_stream { declaration.value };
  740. if (auto value = parse_css_value(CSS::PropertyID::FontWeight, token_stream); !value.is_error()) {
  741. weight = value.value()->to_font_weight();
  742. }
  743. return;
  744. }
  745. if (declaration.name.equals_ignoring_ascii_case("font-width"sv)
  746. || declaration.name.equals_ignoring_ascii_case("font-stretch"sv)) {
  747. TokenStream token_stream { declaration.value };
  748. if (auto value = parse_css_value(CSS::PropertyID::FontWidth, token_stream); !value.is_error()) {
  749. width = value.value()->to_font_width();
  750. }
  751. return;
  752. }
  753. if (declaration.name.equals_ignoring_ascii_case("line-gap-override"sv)) {
  754. auto value = parse_as_percentage_or_normal(declaration.value);
  755. if (value.is_error()) {
  756. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face line-gap-override: {}", value.error());
  757. } else {
  758. line_gap_override = value.release_value();
  759. }
  760. return;
  761. }
  762. if (declaration.name.equals_ignoring_ascii_case("src"sv)) {
  763. TokenStream token_stream { declaration.value };
  764. Vector<ParsedFontFace::Source> supported_sources = parse_font_face_src(token_stream);
  765. if (!supported_sources.is_empty())
  766. src = move(supported_sources);
  767. return;
  768. }
  769. if (declaration.name.equals_ignoring_ascii_case("unicode-range"sv)) {
  770. TokenStream token_stream { declaration.value };
  771. auto unicode_ranges = parse_unicode_ranges(token_stream);
  772. if (unicode_ranges.is_empty())
  773. return;
  774. unicode_range = move(unicode_ranges);
  775. return;
  776. }
  777. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unrecognized descriptor '{}' in @font-face; discarding.", declaration.name);
  778. });
  779. if (!font_family.has_value()) {
  780. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face: no font-family!");
  781. return {};
  782. }
  783. if (unicode_range.is_empty()) {
  784. unicode_range.empend(0x0u, 0x10FFFFu);
  785. }
  786. return CSSFontFaceRule::create(m_context.realm(), ParsedFontFace { font_family.release_value(), move(weight), move(slope), move(width), move(src), move(unicode_range), move(ascent_override), move(descent_override), move(line_gap_override), font_display, move(font_named_instance), move(language_override), move(font_feature_settings), move(font_variation_settings) });
  787. }
  788. }