MediaParsing.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  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. *
  8. * SPDX-License-Identifier: BSD-2-Clause
  9. */
  10. #include <AK/Debug.h>
  11. #include <LibWeb/CSS/CSSMediaRule.h>
  12. #include <LibWeb/CSS/CSSNestedDeclarations.h>
  13. #include <LibWeb/CSS/CalculatedOr.h>
  14. #include <LibWeb/CSS/MediaList.h>
  15. #include <LibWeb/CSS/MediaQuery.h>
  16. #include <LibWeb/CSS/Parser/Parser.h>
  17. namespace Web::CSS::Parser {
  18. Vector<NonnullRefPtr<MediaQuery>> Parser::parse_as_media_query_list()
  19. {
  20. return parse_a_media_query_list(m_token_stream);
  21. }
  22. template<typename T>
  23. Vector<NonnullRefPtr<MediaQuery>> Parser::parse_a_media_query_list(TokenStream<T>& tokens)
  24. {
  25. // https://www.w3.org/TR/mediaqueries-4/#mq-list
  26. // AD-HOC: Ignore whitespace-only queries
  27. // to make `@media {..}` equivalent to `@media all {..}`
  28. tokens.discard_whitespace();
  29. if (!tokens.has_next_token())
  30. return {};
  31. auto comma_separated_lists = parse_a_comma_separated_list_of_component_values(tokens);
  32. AK::Vector<NonnullRefPtr<MediaQuery>> media_queries;
  33. for (auto& media_query_parts : comma_separated_lists) {
  34. auto stream = TokenStream(media_query_parts);
  35. media_queries.append(parse_media_query(stream));
  36. }
  37. return media_queries;
  38. }
  39. RefPtr<MediaQuery> Parser::parse_as_media_query()
  40. {
  41. // https://www.w3.org/TR/cssom-1/#parse-a-media-query
  42. auto media_query_list = parse_as_media_query_list();
  43. if (media_query_list.is_empty())
  44. return MediaQuery::create_not_all();
  45. if (media_query_list.size() == 1)
  46. return media_query_list.first();
  47. return nullptr;
  48. }
  49. // `<media-query>`, https://www.w3.org/TR/mediaqueries-4/#typedef-media-query
  50. NonnullRefPtr<MediaQuery> Parser::parse_media_query(TokenStream<ComponentValue>& tokens)
  51. {
  52. // `<media-query> = <media-condition>
  53. // | [ not | only ]? <media-type> [ and <media-condition-without-or> ]?`
  54. // `[ not | only ]?`, Returns whether to negate the query
  55. auto parse_initial_modifier = [](auto& tokens) -> Optional<bool> {
  56. auto transaction = tokens.begin_transaction();
  57. tokens.discard_whitespace();
  58. auto& token = tokens.consume_a_token();
  59. if (!token.is(Token::Type::Ident))
  60. return {};
  61. auto ident = token.token().ident();
  62. if (ident.equals_ignoring_ascii_case("not"sv)) {
  63. transaction.commit();
  64. return true;
  65. }
  66. if (ident.equals_ignoring_ascii_case("only"sv)) {
  67. transaction.commit();
  68. return false;
  69. }
  70. return {};
  71. };
  72. auto invalid_media_query = [&]() {
  73. // "A media query that does not match the grammar in the previous section must be replaced by `not all`
  74. // during parsing." - https://www.w3.org/TR/mediaqueries-5/#error-handling
  75. if constexpr (CSS_PARSER_DEBUG) {
  76. dbgln("Invalid media query:");
  77. tokens.dump_all_tokens();
  78. }
  79. return MediaQuery::create_not_all();
  80. };
  81. auto media_query = MediaQuery::create();
  82. tokens.discard_whitespace();
  83. // `<media-condition>`
  84. if (auto media_condition = parse_media_condition(tokens, MediaCondition::AllowOr::Yes)) {
  85. tokens.discard_whitespace();
  86. if (tokens.has_next_token())
  87. return invalid_media_query();
  88. media_query->m_media_condition = move(media_condition);
  89. return media_query;
  90. }
  91. // `[ not | only ]?`
  92. if (auto modifier = parse_initial_modifier(tokens); modifier.has_value()) {
  93. media_query->m_negated = modifier.value();
  94. tokens.discard_whitespace();
  95. }
  96. // `<media-type>`
  97. if (auto media_type = parse_media_type(tokens); media_type.has_value()) {
  98. media_query->m_media_type = media_type.value();
  99. tokens.discard_whitespace();
  100. } else {
  101. return invalid_media_query();
  102. }
  103. if (!tokens.has_next_token())
  104. return media_query;
  105. // `[ and <media-condition-without-or> ]?`
  106. if (auto const& maybe_and = tokens.consume_a_token(); maybe_and.is_ident("and"sv)) {
  107. if (auto media_condition = parse_media_condition(tokens, MediaCondition::AllowOr::No)) {
  108. tokens.discard_whitespace();
  109. if (tokens.has_next_token())
  110. return invalid_media_query();
  111. media_query->m_media_condition = move(media_condition);
  112. return media_query;
  113. }
  114. return invalid_media_query();
  115. }
  116. return invalid_media_query();
  117. }
  118. // `<media-condition>`, https://www.w3.org/TR/mediaqueries-4/#typedef-media-condition
  119. // `<media-condition-widthout-or>`, https://www.w3.org/TR/mediaqueries-4/#typedef-media-condition-without-or
  120. // (We distinguish between these two with the `allow_or` parameter.)
  121. OwnPtr<MediaCondition> Parser::parse_media_condition(TokenStream<ComponentValue>& tokens, MediaCondition::AllowOr allow_or)
  122. {
  123. // `<media-not> | <media-in-parens> [ <media-and>* | <media-or>* ]`
  124. auto transaction = tokens.begin_transaction();
  125. tokens.discard_whitespace();
  126. // `<media-not> = not <media-in-parens>`
  127. auto parse_media_not = [&](auto& tokens) -> OwnPtr<MediaCondition> {
  128. auto local_transaction = tokens.begin_transaction();
  129. tokens.discard_whitespace();
  130. auto& first_token = tokens.consume_a_token();
  131. if (first_token.is_ident("not"sv)) {
  132. if (auto child_condition = parse_media_condition(tokens, MediaCondition::AllowOr::Yes)) {
  133. local_transaction.commit();
  134. return MediaCondition::from_not(child_condition.release_nonnull());
  135. }
  136. }
  137. return {};
  138. };
  139. auto parse_media_with_combinator = [&](auto& tokens, StringView combinator) -> OwnPtr<MediaCondition> {
  140. auto local_transaction = tokens.begin_transaction();
  141. tokens.discard_whitespace();
  142. auto& first = tokens.consume_a_token();
  143. if (first.is_ident(combinator)) {
  144. tokens.discard_whitespace();
  145. if (auto media_in_parens = parse_media_in_parens(tokens)) {
  146. local_transaction.commit();
  147. return media_in_parens;
  148. }
  149. }
  150. return {};
  151. };
  152. // `<media-and> = and <media-in-parens>`
  153. auto parse_media_and = [&](auto& tokens) { return parse_media_with_combinator(tokens, "and"sv); };
  154. // `<media-or> = or <media-in-parens>`
  155. auto parse_media_or = [&](auto& tokens) { return parse_media_with_combinator(tokens, "or"sv); };
  156. // `<media-not>`
  157. if (auto maybe_media_not = parse_media_not(tokens)) {
  158. transaction.commit();
  159. return maybe_media_not.release_nonnull();
  160. }
  161. // `<media-in-parens> [ <media-and>* | <media-or>* ]`
  162. if (auto maybe_media_in_parens = parse_media_in_parens(tokens)) {
  163. tokens.discard_whitespace();
  164. // Only `<media-in-parens>`
  165. if (!tokens.has_next_token()) {
  166. transaction.commit();
  167. return maybe_media_in_parens.release_nonnull();
  168. }
  169. Vector<NonnullOwnPtr<MediaCondition>> child_conditions;
  170. child_conditions.append(maybe_media_in_parens.release_nonnull());
  171. // `<media-and>*`
  172. if (auto media_and = parse_media_and(tokens)) {
  173. child_conditions.append(media_and.release_nonnull());
  174. tokens.discard_whitespace();
  175. while (tokens.has_next_token()) {
  176. if (auto next_media_and = parse_media_and(tokens)) {
  177. child_conditions.append(next_media_and.release_nonnull());
  178. tokens.discard_whitespace();
  179. continue;
  180. }
  181. // We failed - invalid syntax!
  182. return {};
  183. }
  184. transaction.commit();
  185. return MediaCondition::from_and_list(move(child_conditions));
  186. }
  187. // `<media-or>*`
  188. if (allow_or == MediaCondition::AllowOr::Yes) {
  189. if (auto media_or = parse_media_or(tokens)) {
  190. child_conditions.append(media_or.release_nonnull());
  191. tokens.discard_whitespace();
  192. while (tokens.has_next_token()) {
  193. if (auto next_media_or = parse_media_or(tokens)) {
  194. child_conditions.append(next_media_or.release_nonnull());
  195. tokens.discard_whitespace();
  196. continue;
  197. }
  198. // We failed - invalid syntax!
  199. return {};
  200. }
  201. transaction.commit();
  202. return MediaCondition::from_or_list(move(child_conditions));
  203. }
  204. }
  205. }
  206. return {};
  207. }
  208. // `<media-feature>`, https://www.w3.org/TR/mediaqueries-4/#typedef-media-feature
  209. Optional<MediaFeature> Parser::parse_media_feature(TokenStream<ComponentValue>& tokens)
  210. {
  211. // `[ <mf-plain> | <mf-boolean> | <mf-range> ]`
  212. tokens.discard_whitespace();
  213. // `<mf-name> = <ident>`
  214. struct MediaFeatureName {
  215. enum Type {
  216. Normal,
  217. Min,
  218. Max
  219. } type;
  220. MediaFeatureID id;
  221. };
  222. auto parse_mf_name = [](auto& tokens, bool allow_min_max_prefix) -> Optional<MediaFeatureName> {
  223. auto transaction = tokens.begin_transaction();
  224. auto& token = tokens.consume_a_token();
  225. if (token.is(Token::Type::Ident)) {
  226. auto name = token.token().ident();
  227. if (auto id = media_feature_id_from_string(name); id.has_value()) {
  228. transaction.commit();
  229. return MediaFeatureName { MediaFeatureName::Type::Normal, id.value() };
  230. }
  231. if (allow_min_max_prefix && (name.starts_with_bytes("min-"sv, CaseSensitivity::CaseInsensitive) || name.starts_with_bytes("max-"sv, CaseSensitivity::CaseInsensitive))) {
  232. auto adjusted_name = name.bytes_as_string_view().substring_view(4);
  233. if (auto id = media_feature_id_from_string(adjusted_name); id.has_value() && media_feature_type_is_range(id.value())) {
  234. transaction.commit();
  235. return MediaFeatureName {
  236. name.starts_with_bytes("min-"sv, CaseSensitivity::CaseInsensitive) ? MediaFeatureName::Type::Min : MediaFeatureName::Type::Max,
  237. id.value()
  238. };
  239. }
  240. }
  241. }
  242. return {};
  243. };
  244. // `<mf-boolean> = <mf-name>`
  245. auto parse_mf_boolean = [&](auto& tokens) -> Optional<MediaFeature> {
  246. auto transaction = tokens.begin_transaction();
  247. tokens.discard_whitespace();
  248. if (auto maybe_name = parse_mf_name(tokens, false); maybe_name.has_value()) {
  249. tokens.discard_whitespace();
  250. if (!tokens.has_next_token()) {
  251. transaction.commit();
  252. return MediaFeature::boolean(maybe_name->id);
  253. }
  254. }
  255. return {};
  256. };
  257. // `<mf-plain> = <mf-name> : <mf-value>`
  258. auto parse_mf_plain = [&](auto& tokens) -> Optional<MediaFeature> {
  259. auto transaction = tokens.begin_transaction();
  260. tokens.discard_whitespace();
  261. if (auto maybe_name = parse_mf_name(tokens, true); maybe_name.has_value()) {
  262. tokens.discard_whitespace();
  263. if (tokens.consume_a_token().is(Token::Type::Colon)) {
  264. tokens.discard_whitespace();
  265. if (auto maybe_value = parse_media_feature_value(maybe_name->id, tokens); maybe_value.has_value()) {
  266. tokens.discard_whitespace();
  267. if (!tokens.has_next_token()) {
  268. transaction.commit();
  269. switch (maybe_name->type) {
  270. case MediaFeatureName::Type::Normal:
  271. return MediaFeature::plain(maybe_name->id, maybe_value.release_value());
  272. case MediaFeatureName::Type::Min:
  273. return MediaFeature::min(maybe_name->id, maybe_value.release_value());
  274. case MediaFeatureName::Type::Max:
  275. return MediaFeature::max(maybe_name->id, maybe_value.release_value());
  276. }
  277. VERIFY_NOT_REACHED();
  278. }
  279. }
  280. }
  281. }
  282. return {};
  283. };
  284. // `<mf-lt> = '<' '='?
  285. // <mf-gt> = '>' '='?
  286. // <mf-eq> = '='
  287. // <mf-comparison> = <mf-lt> | <mf-gt> | <mf-eq>`
  288. auto parse_comparison = [](auto& tokens) -> Optional<MediaFeature::Comparison> {
  289. auto transaction = tokens.begin_transaction();
  290. tokens.discard_whitespace();
  291. auto& first = tokens.consume_a_token();
  292. if (first.is(Token::Type::Delim)) {
  293. auto first_delim = first.token().delim();
  294. if (first_delim == '=') {
  295. transaction.commit();
  296. return MediaFeature::Comparison::Equal;
  297. }
  298. if (first_delim == '<') {
  299. auto& second = tokens.next_token();
  300. if (second.is_delim('=')) {
  301. tokens.discard_a_token();
  302. transaction.commit();
  303. return MediaFeature::Comparison::LessThanOrEqual;
  304. }
  305. transaction.commit();
  306. return MediaFeature::Comparison::LessThan;
  307. }
  308. if (first_delim == '>') {
  309. auto& second = tokens.next_token();
  310. if (second.is_delim('=')) {
  311. tokens.discard_a_token();
  312. transaction.commit();
  313. return MediaFeature::Comparison::GreaterThanOrEqual;
  314. }
  315. transaction.commit();
  316. return MediaFeature::Comparison::GreaterThan;
  317. }
  318. }
  319. return {};
  320. };
  321. auto flip = [](MediaFeature::Comparison comparison) {
  322. switch (comparison) {
  323. case MediaFeature::Comparison::Equal:
  324. return MediaFeature::Comparison::Equal;
  325. case MediaFeature::Comparison::LessThan:
  326. return MediaFeature::Comparison::GreaterThan;
  327. case MediaFeature::Comparison::LessThanOrEqual:
  328. return MediaFeature::Comparison::GreaterThanOrEqual;
  329. case MediaFeature::Comparison::GreaterThan:
  330. return MediaFeature::Comparison::LessThan;
  331. case MediaFeature::Comparison::GreaterThanOrEqual:
  332. return MediaFeature::Comparison::LessThanOrEqual;
  333. }
  334. VERIFY_NOT_REACHED();
  335. };
  336. auto comparisons_match = [](MediaFeature::Comparison a, MediaFeature::Comparison b) -> bool {
  337. switch (a) {
  338. case MediaFeature::Comparison::Equal:
  339. return b == MediaFeature::Comparison::Equal;
  340. case MediaFeature::Comparison::LessThan:
  341. case MediaFeature::Comparison::LessThanOrEqual:
  342. return b == MediaFeature::Comparison::LessThan || b == MediaFeature::Comparison::LessThanOrEqual;
  343. case MediaFeature::Comparison::GreaterThan:
  344. case MediaFeature::Comparison::GreaterThanOrEqual:
  345. return b == MediaFeature::Comparison::GreaterThan || b == MediaFeature::Comparison::GreaterThanOrEqual;
  346. }
  347. VERIFY_NOT_REACHED();
  348. };
  349. // `<mf-range> = <mf-name> <mf-comparison> <mf-value>
  350. // | <mf-value> <mf-comparison> <mf-name>
  351. // | <mf-value> <mf-lt> <mf-name> <mf-lt> <mf-value>
  352. // | <mf-value> <mf-gt> <mf-name> <mf-gt> <mf-value>`
  353. auto parse_mf_range = [&](auto& tokens) -> Optional<MediaFeature> {
  354. auto transaction = tokens.begin_transaction();
  355. tokens.discard_whitespace();
  356. // `<mf-name> <mf-comparison> <mf-value>`
  357. // NOTE: We have to check for <mf-name> first, since all <mf-name>s will also parse as <mf-value>.
  358. if (auto maybe_name = parse_mf_name(tokens, false); maybe_name.has_value() && media_feature_type_is_range(maybe_name->id)) {
  359. tokens.discard_whitespace();
  360. if (auto maybe_comparison = parse_comparison(tokens); maybe_comparison.has_value()) {
  361. tokens.discard_whitespace();
  362. if (auto maybe_value = parse_media_feature_value(maybe_name->id, tokens); maybe_value.has_value()) {
  363. tokens.discard_whitespace();
  364. if (!tokens.has_next_token() && !maybe_value->is_ident()) {
  365. transaction.commit();
  366. return MediaFeature::half_range(maybe_value.release_value(), flip(maybe_comparison.release_value()), maybe_name->id);
  367. }
  368. }
  369. }
  370. }
  371. // `<mf-value> <mf-comparison> <mf-name>
  372. // | <mf-value> <mf-lt> <mf-name> <mf-lt> <mf-value>
  373. // | <mf-value> <mf-gt> <mf-name> <mf-gt> <mf-value>`
  374. // NOTE: To parse the first value, we need to first find and parse the <mf-name> so we know what value types to parse.
  375. // To allow for <mf-value> to be any number of tokens long, we scan forward until we find a comparison, and then
  376. // treat the next non-whitespace token as the <mf-name>, which should be correct as long as they don't add a value
  377. // type that can include a comparison in it. :^)
  378. Optional<MediaFeatureName> maybe_name;
  379. {
  380. // This transaction is never committed, we just use it to rewind automatically.
  381. auto temp_transaction = tokens.begin_transaction();
  382. while (tokens.has_next_token() && !maybe_name.has_value()) {
  383. if (auto maybe_comparison = parse_comparison(tokens); maybe_comparison.has_value()) {
  384. // We found a comparison, so the next non-whitespace token should be the <mf-name>
  385. tokens.discard_whitespace();
  386. maybe_name = parse_mf_name(tokens, false);
  387. break;
  388. }
  389. tokens.discard_a_token();
  390. tokens.discard_whitespace();
  391. }
  392. }
  393. // Now, we can parse the range properly.
  394. if (maybe_name.has_value() && media_feature_type_is_range(maybe_name->id)) {
  395. if (auto maybe_left_value = parse_media_feature_value(maybe_name->id, tokens); maybe_left_value.has_value()) {
  396. tokens.discard_whitespace();
  397. if (auto maybe_left_comparison = parse_comparison(tokens); maybe_left_comparison.has_value()) {
  398. tokens.discard_whitespace();
  399. tokens.discard_a_token(); // The <mf-name> which we already parsed above.
  400. tokens.discard_whitespace();
  401. if (!tokens.has_next_token()) {
  402. transaction.commit();
  403. return MediaFeature::half_range(maybe_left_value.release_value(), maybe_left_comparison.release_value(), maybe_name->id);
  404. }
  405. if (auto maybe_right_comparison = parse_comparison(tokens); maybe_right_comparison.has_value()) {
  406. tokens.discard_whitespace();
  407. if (auto maybe_right_value = parse_media_feature_value(maybe_name->id, tokens); maybe_right_value.has_value()) {
  408. tokens.discard_whitespace();
  409. // For this to be valid, the following must be true:
  410. // - Comparisons must either both be >/>= or both be </<=.
  411. // - Neither comparison can be `=`.
  412. // - Neither value can be an ident.
  413. auto left_comparison = maybe_left_comparison.release_value();
  414. auto right_comparison = maybe_right_comparison.release_value();
  415. if (!tokens.has_next_token()
  416. && comparisons_match(left_comparison, right_comparison)
  417. && left_comparison != MediaFeature::Comparison::Equal
  418. && !maybe_left_value->is_ident() && !maybe_right_value->is_ident()) {
  419. transaction.commit();
  420. return MediaFeature::range(maybe_left_value.release_value(), left_comparison, maybe_name->id, right_comparison, maybe_right_value.release_value());
  421. }
  422. }
  423. }
  424. }
  425. }
  426. }
  427. return {};
  428. };
  429. if (auto maybe_mf_boolean = parse_mf_boolean(tokens); maybe_mf_boolean.has_value())
  430. return maybe_mf_boolean.release_value();
  431. if (auto maybe_mf_plain = parse_mf_plain(tokens); maybe_mf_plain.has_value())
  432. return maybe_mf_plain.release_value();
  433. if (auto maybe_mf_range = parse_mf_range(tokens); maybe_mf_range.has_value())
  434. return maybe_mf_range.release_value();
  435. return {};
  436. }
  437. Optional<MediaQuery::MediaType> Parser::parse_media_type(TokenStream<ComponentValue>& tokens)
  438. {
  439. auto transaction = tokens.begin_transaction();
  440. tokens.discard_whitespace();
  441. auto const& token = tokens.consume_a_token();
  442. if (!token.is(Token::Type::Ident))
  443. return {};
  444. transaction.commit();
  445. auto ident = token.token().ident();
  446. return media_type_from_string(ident);
  447. }
  448. // `<media-in-parens>`, https://www.w3.org/TR/mediaqueries-4/#typedef-media-in-parens
  449. OwnPtr<MediaCondition> Parser::parse_media_in_parens(TokenStream<ComponentValue>& tokens)
  450. {
  451. // `<media-in-parens> = ( <media-condition> ) | ( <media-feature> ) | <general-enclosed>`
  452. auto transaction = tokens.begin_transaction();
  453. tokens.discard_whitespace();
  454. // `( <media-condition> ) | ( <media-feature> )`
  455. auto const& first_token = tokens.next_token();
  456. if (first_token.is_block() && first_token.block().is_paren()) {
  457. TokenStream inner_token_stream { first_token.block().value };
  458. if (auto maybe_media_condition = parse_media_condition(inner_token_stream, MediaCondition::AllowOr::Yes)) {
  459. tokens.discard_a_token();
  460. transaction.commit();
  461. return maybe_media_condition.release_nonnull();
  462. }
  463. if (auto maybe_media_feature = parse_media_feature(inner_token_stream); maybe_media_feature.has_value()) {
  464. tokens.discard_a_token();
  465. transaction.commit();
  466. return MediaCondition::from_feature(maybe_media_feature.release_value());
  467. }
  468. }
  469. // `<general-enclosed>`
  470. // FIXME: We should only be taking this branch if the grammar doesn't match the above options.
  471. // Currently we take it if the above fail to parse, which is different.
  472. // eg, `@media (min-width: 76yaks)` is valid grammar, but does not parse because `yaks` isn't a unit.
  473. if (auto maybe_general_enclosed = parse_general_enclosed(tokens); maybe_general_enclosed.has_value()) {
  474. transaction.commit();
  475. return MediaCondition::from_general_enclosed(maybe_general_enclosed.release_value());
  476. }
  477. return {};
  478. }
  479. // `<mf-value>`, https://www.w3.org/TR/mediaqueries-4/#typedef-mf-value
  480. Optional<MediaFeatureValue> Parser::parse_media_feature_value(MediaFeatureID media_feature, TokenStream<ComponentValue>& tokens)
  481. {
  482. // Identifiers
  483. if (tokens.next_token().is(Token::Type::Ident)) {
  484. auto transaction = tokens.begin_transaction();
  485. tokens.discard_whitespace();
  486. auto keyword = keyword_from_string(tokens.consume_a_token().token().ident());
  487. if (keyword.has_value() && media_feature_accepts_keyword(media_feature, keyword.value())) {
  488. transaction.commit();
  489. return MediaFeatureValue(keyword.value());
  490. }
  491. }
  492. // One branch for each member of the MediaFeatureValueType enum:
  493. // Boolean (<mq-boolean> in the spec: a 1 or 0)
  494. if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Boolean)) {
  495. auto transaction = tokens.begin_transaction();
  496. tokens.discard_whitespace();
  497. if (auto integer = parse_integer(tokens); integer.has_value()) {
  498. if (integer.value().is_calculated() || integer->value() == 0 || integer->value() == 1) {
  499. transaction.commit();
  500. return MediaFeatureValue(integer.release_value());
  501. }
  502. }
  503. }
  504. // Integer
  505. if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Integer)) {
  506. auto transaction = tokens.begin_transaction();
  507. if (auto integer = parse_integer(tokens); integer.has_value()) {
  508. transaction.commit();
  509. return MediaFeatureValue(integer.release_value());
  510. }
  511. }
  512. // Length
  513. if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Length)) {
  514. auto transaction = tokens.begin_transaction();
  515. tokens.discard_whitespace();
  516. if (auto length = parse_length(tokens); length.has_value()) {
  517. transaction.commit();
  518. return MediaFeatureValue(length.release_value());
  519. }
  520. }
  521. // Ratio
  522. if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Ratio)) {
  523. auto transaction = tokens.begin_transaction();
  524. tokens.discard_whitespace();
  525. if (auto ratio = parse_ratio(tokens); ratio.has_value()) {
  526. transaction.commit();
  527. return MediaFeatureValue(ratio.release_value());
  528. }
  529. }
  530. // Resolution
  531. if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Resolution)) {
  532. auto transaction = tokens.begin_transaction();
  533. tokens.discard_whitespace();
  534. if (auto resolution = parse_resolution(tokens); resolution.has_value()) {
  535. transaction.commit();
  536. return MediaFeatureValue(resolution.release_value());
  537. }
  538. }
  539. return {};
  540. }
  541. GC::Ptr<CSSMediaRule> Parser::convert_to_media_rule(AtRule const& rule, Nested nested)
  542. {
  543. auto media_query_tokens = TokenStream { rule.prelude };
  544. auto media_query_list = parse_a_media_query_list(media_query_tokens);
  545. auto media_list = MediaList::create(m_context.realm(), move(media_query_list));
  546. GC::RootVector<CSSRule*> child_rules { m_context.realm().heap() };
  547. for (auto const& child : rule.child_rules_and_lists_of_declarations) {
  548. child.visit(
  549. [&](Rule const& rule) {
  550. if (auto child_rule = convert_to_rule(rule, nested))
  551. child_rules.append(child_rule);
  552. },
  553. [&](Vector<Declaration> const& declarations) {
  554. auto* declaration = convert_to_style_declaration(declarations);
  555. if (!declaration) {
  556. dbgln_if(CSS_PARSER_DEBUG, "CSSParser: nested declarations invalid; discarding.");
  557. return;
  558. }
  559. child_rules.append(CSSNestedDeclarations::create(m_context.realm(), *declaration));
  560. });
  561. }
  562. auto rule_list = CSSRuleList::create(m_context.realm(), child_rules);
  563. return CSSMediaRule::create(m_context.realm(), media_list, rule_list);
  564. }
  565. }