MediaParsing.cpp 26 KB

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