MediaQuery.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. /*
  2. * Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibWeb/CSS/MediaQuery.h>
  7. #include <LibWeb/CSS/Serialize.h>
  8. #include <LibWeb/DOM/Document.h>
  9. #include <LibWeb/DOM/Window.h>
  10. namespace Web::CSS {
  11. NonnullRefPtr<MediaQuery> MediaQuery::create_not_all()
  12. {
  13. auto media_query = new MediaQuery;
  14. media_query->m_negated = true;
  15. media_query->m_media_type = MediaType::All;
  16. return adopt_ref(*media_query);
  17. }
  18. String MediaFeatureValue::to_string() const
  19. {
  20. return m_value.visit(
  21. [](String& ident) { return serialize_an_identifier(ident); },
  22. [](Length& length) { return length.to_string(); },
  23. [](double number) { return String::number(number); });
  24. }
  25. bool MediaFeatureValue::is_same_type(MediaFeatureValue const& other) const
  26. {
  27. return m_value.visit(
  28. [&](String&) { return other.is_ident(); },
  29. [&](Length&) { return other.is_length(); },
  30. [&](double) { return other.is_number(); });
  31. }
  32. bool MediaFeatureValue::equals(MediaFeatureValue const& other) const
  33. {
  34. if (!is_same_type(other))
  35. return false;
  36. if (is_ident() && other.is_ident())
  37. return m_value.get<String>().equals_ignoring_case(other.m_value.get<String>());
  38. if (is_length() && other.is_length()) {
  39. // FIXME: Handle relative lengths. https://www.w3.org/TR/mediaqueries-4/#ref-for-relative-length
  40. auto& my_length = m_value.get<Length>();
  41. auto& other_length = other.m_value.get<Length>();
  42. if (!my_length.is_absolute() || !other_length.is_absolute()) {
  43. dbgln("TODO: Support relative lengths in media queries!");
  44. return false;
  45. }
  46. return my_length.absolute_length_to_px() == other_length.absolute_length_to_px();
  47. }
  48. if (is_number() && other.is_number())
  49. return m_value.get<double>() == other.m_value.get<double>();
  50. VERIFY_NOT_REACHED();
  51. }
  52. String MediaFeature::to_string() const
  53. {
  54. switch (m_type) {
  55. case Type::IsTrue:
  56. return m_name;
  57. case Type::ExactValue:
  58. return String::formatted("{}:{}", m_name, m_value->to_string());
  59. case Type::MinValue:
  60. return String::formatted("min-{}:{}", m_name, m_value->to_string());
  61. case Type::MaxValue:
  62. return String::formatted("max-{}:{}", m_name, m_value->to_string());
  63. }
  64. VERIFY_NOT_REACHED();
  65. }
  66. bool MediaFeature::evaluate(DOM::Window const& window) const
  67. {
  68. auto maybe_queried_value = window.query_media_feature(m_name);
  69. if (!maybe_queried_value.has_value())
  70. return false;
  71. auto queried_value = maybe_queried_value.release_value();
  72. switch (m_type) {
  73. case Type::IsTrue:
  74. if (queried_value.is_number())
  75. return queried_value.number() != 0;
  76. if (queried_value.is_length())
  77. return queried_value.length().raw_value() != 0;
  78. if (queried_value.is_ident())
  79. return queried_value.ident() != "none";
  80. return false;
  81. case Type::ExactValue:
  82. return queried_value.equals(*m_value);
  83. case Type::MinValue:
  84. if (!m_value->is_same_type(queried_value))
  85. return false;
  86. if (m_value->is_number())
  87. return queried_value.number() >= m_value->number();
  88. if (m_value->is_length()) {
  89. auto& queried_length = queried_value.length();
  90. auto& value_length = m_value->length();
  91. // FIXME: Handle relative lengths. https://www.w3.org/TR/mediaqueries-4/#ref-for-relative-length
  92. if (!value_length.is_absolute()) {
  93. dbgln("Media feature was given a non-absolute length! {}", value_length.to_string());
  94. return false;
  95. }
  96. return queried_length.absolute_length_to_px() >= value_length.absolute_length_to_px();
  97. }
  98. return false;
  99. case Type::MaxValue:
  100. if (!m_value->is_same_type(queried_value))
  101. return false;
  102. if (m_value->is_number())
  103. return queried_value.number() <= m_value->number();
  104. if (m_value->is_length()) {
  105. auto& queried_length = queried_value.length();
  106. auto& value_length = m_value->length();
  107. // FIXME: Handle relative lengths. https://www.w3.org/TR/mediaqueries-4/#ref-for-relative-length
  108. if (!value_length.is_absolute()) {
  109. dbgln("Media feature was given a non-absolute length! {}", value_length.to_string());
  110. return false;
  111. }
  112. return queried_length.absolute_length_to_px() <= value_length.absolute_length_to_px();
  113. }
  114. return false;
  115. }
  116. VERIFY_NOT_REACHED();
  117. }
  118. NonnullOwnPtr<MediaCondition> MediaCondition::from_general_enclosed(GeneralEnclosed&& general_enclosed)
  119. {
  120. auto result = new MediaCondition;
  121. result->type = Type::GeneralEnclosed;
  122. result->general_enclosed = move(general_enclosed);
  123. return adopt_own(*result);
  124. }
  125. NonnullOwnPtr<MediaCondition> MediaCondition::from_feature(MediaFeature&& feature)
  126. {
  127. auto result = new MediaCondition;
  128. result->type = Type::Single;
  129. result->feature = move(feature);
  130. return adopt_own(*result);
  131. }
  132. NonnullOwnPtr<MediaCondition> MediaCondition::from_not(NonnullOwnPtr<MediaCondition>&& condition)
  133. {
  134. auto result = new MediaCondition;
  135. result->type = Type::Not;
  136. result->conditions.append(move(condition));
  137. return adopt_own(*result);
  138. }
  139. NonnullOwnPtr<MediaCondition> MediaCondition::from_and_list(NonnullOwnPtrVector<MediaCondition>&& conditions)
  140. {
  141. auto result = new MediaCondition;
  142. result->type = Type::And;
  143. result->conditions = move(conditions);
  144. return adopt_own(*result);
  145. }
  146. NonnullOwnPtr<MediaCondition> MediaCondition::from_or_list(NonnullOwnPtrVector<MediaCondition>&& conditions)
  147. {
  148. auto result = new MediaCondition;
  149. result->type = Type::Or;
  150. result->conditions = move(conditions);
  151. return adopt_own(*result);
  152. }
  153. String MediaCondition::to_string() const
  154. {
  155. StringBuilder builder;
  156. builder.append('(');
  157. switch (type) {
  158. case Type::Single:
  159. builder.append(feature->to_string());
  160. break;
  161. case Type::Not:
  162. builder.append("not ");
  163. builder.append(conditions.first().to_string());
  164. break;
  165. case Type::And:
  166. builder.join(" and ", conditions);
  167. break;
  168. case Type::Or:
  169. builder.join(" or ", conditions);
  170. break;
  171. case Type::GeneralEnclosed:
  172. builder.append(general_enclosed->to_string());
  173. break;
  174. }
  175. builder.append(')');
  176. return builder.to_string();
  177. }
  178. MatchResult MediaCondition::evaluate(DOM::Window const& window) const
  179. {
  180. switch (type) {
  181. case Type::Single:
  182. return as_match_result(feature->evaluate(window));
  183. case Type::Not:
  184. return negate(conditions.first().evaluate(window));
  185. case Type::And:
  186. return evaluate_and(conditions, [&](auto& child) { return child.evaluate(window); });
  187. case Type::Or:
  188. return evaluate_or(conditions, [&](auto& child) { return child.evaluate(window); });
  189. case Type::GeneralEnclosed:
  190. return general_enclosed->evaluate();
  191. }
  192. VERIFY_NOT_REACHED();
  193. }
  194. String MediaQuery::to_string() const
  195. {
  196. StringBuilder builder;
  197. if (m_negated)
  198. builder.append("not ");
  199. if (m_negated || m_media_type != MediaType::All || !m_media_condition) {
  200. switch (m_media_type) {
  201. case MediaType::All:
  202. builder.append("all");
  203. break;
  204. case MediaType::Aural:
  205. builder.append("aural");
  206. break;
  207. case MediaType::Braille:
  208. builder.append("braille");
  209. break;
  210. case MediaType::Embossed:
  211. builder.append("embossed");
  212. break;
  213. case MediaType::Handheld:
  214. builder.append("handheld");
  215. break;
  216. case MediaType::Print:
  217. builder.append("print");
  218. break;
  219. case MediaType::Projection:
  220. builder.append("projection");
  221. break;
  222. case MediaType::Screen:
  223. builder.append("screen");
  224. break;
  225. case MediaType::Speech:
  226. builder.append("speech");
  227. break;
  228. case MediaType::TTY:
  229. builder.append("tty");
  230. break;
  231. case MediaType::TV:
  232. builder.append("tv");
  233. break;
  234. }
  235. if (m_media_condition)
  236. builder.append(" and ");
  237. }
  238. if (m_media_condition) {
  239. builder.append(m_media_condition->to_string());
  240. }
  241. return builder.to_string();
  242. }
  243. bool MediaQuery::evaluate(DOM::Window const& window)
  244. {
  245. auto matches_media = [](MediaType media) -> MatchResult {
  246. switch (media) {
  247. case MediaType::All:
  248. return MatchResult::True;
  249. case MediaType::Print:
  250. // FIXME: Enable for printing, when we have printing!
  251. return MatchResult::False;
  252. case MediaType::Screen:
  253. // FIXME: Disable for printing, when we have printing!
  254. return MatchResult::True;
  255. // Deprecated, must never match:
  256. case MediaType::TTY:
  257. case MediaType::TV:
  258. case MediaType::Projection:
  259. case MediaType::Handheld:
  260. case MediaType::Braille:
  261. case MediaType::Embossed:
  262. case MediaType::Aural:
  263. case MediaType::Speech:
  264. return MatchResult::False;
  265. }
  266. VERIFY_NOT_REACHED();
  267. };
  268. MatchResult result = matches_media(m_media_type);
  269. if ((result == MatchResult::True) && m_media_condition)
  270. result = m_media_condition->evaluate(window);
  271. if (m_negated)
  272. result = negate(result);
  273. m_matches = result == MatchResult::True;
  274. return m_matches;
  275. }
  276. // https://www.w3.org/TR/cssom-1/#serialize-a-media-query-list
  277. String serialize_a_media_query_list(NonnullRefPtrVector<MediaQuery> const& media_queries)
  278. {
  279. // 1. If the media query list is empty, then return the empty string.
  280. if (media_queries.is_empty())
  281. return "";
  282. // 2. Serialize each media query in the list of media queries, in the same order as they
  283. // appear in the media query list, and then serialize the list.
  284. StringBuilder builder;
  285. builder.join(", ", media_queries);
  286. return builder.to_string();
  287. }
  288. }