MediaQuery.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  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. String MediaQuery::MediaCondition::to_string() const
  119. {
  120. StringBuilder builder;
  121. builder.append('(');
  122. switch (type) {
  123. case Type::Single:
  124. builder.append(feature->to_string());
  125. break;
  126. case Type::Not:
  127. builder.append("not ");
  128. builder.append(conditions.first().to_string());
  129. break;
  130. case Type::And:
  131. builder.join(" and ", conditions);
  132. break;
  133. case Type::Or:
  134. builder.join(" or ", conditions);
  135. break;
  136. case Type::GeneralEnclosed:
  137. builder.append(general_enclosed->to_string());
  138. break;
  139. }
  140. builder.append(')');
  141. return builder.to_string();
  142. }
  143. MatchResult MediaQuery::MediaCondition::evaluate(DOM::Window const& window) const
  144. {
  145. switch (type) {
  146. case Type::Single:
  147. return as_match_result(feature->evaluate(window));
  148. case Type::Not:
  149. return negate(conditions.first().evaluate(window));
  150. case Type::And:
  151. return evaluate_and(conditions, [&](auto& child) { return child.evaluate(window); });
  152. case Type::Or:
  153. return evaluate_or(conditions, [&](auto& child) { return child.evaluate(window); });
  154. case Type::GeneralEnclosed:
  155. return general_enclosed->evaluate();
  156. }
  157. VERIFY_NOT_REACHED();
  158. }
  159. String MediaQuery::to_string() const
  160. {
  161. StringBuilder builder;
  162. if (m_negated)
  163. builder.append("not ");
  164. if (m_negated || m_media_type != MediaType::All || !m_media_condition) {
  165. switch (m_media_type) {
  166. case MediaType::All:
  167. builder.append("all");
  168. break;
  169. case MediaType::Aural:
  170. builder.append("aural");
  171. break;
  172. case MediaType::Braille:
  173. builder.append("braille");
  174. break;
  175. case MediaType::Embossed:
  176. builder.append("embossed");
  177. break;
  178. case MediaType::Handheld:
  179. builder.append("handheld");
  180. break;
  181. case MediaType::Print:
  182. builder.append("print");
  183. break;
  184. case MediaType::Projection:
  185. builder.append("projection");
  186. break;
  187. case MediaType::Screen:
  188. builder.append("screen");
  189. break;
  190. case MediaType::Speech:
  191. builder.append("speech");
  192. break;
  193. case MediaType::TTY:
  194. builder.append("tty");
  195. break;
  196. case MediaType::TV:
  197. builder.append("tv");
  198. break;
  199. }
  200. if (m_media_condition)
  201. builder.append(" and ");
  202. }
  203. if (m_media_condition) {
  204. builder.append(m_media_condition->to_string());
  205. }
  206. return builder.to_string();
  207. }
  208. bool MediaQuery::evaluate(DOM::Window const& window)
  209. {
  210. auto matches_media = [](MediaType media) -> MatchResult {
  211. switch (media) {
  212. case MediaType::All:
  213. return MatchResult::True;
  214. case MediaType::Print:
  215. // FIXME: Enable for printing, when we have printing!
  216. return MatchResult::False;
  217. case MediaType::Screen:
  218. // FIXME: Disable for printing, when we have printing!
  219. return MatchResult::True;
  220. // Deprecated, must never match:
  221. case MediaType::TTY:
  222. case MediaType::TV:
  223. case MediaType::Projection:
  224. case MediaType::Handheld:
  225. case MediaType::Braille:
  226. case MediaType::Embossed:
  227. case MediaType::Aural:
  228. case MediaType::Speech:
  229. return MatchResult::False;
  230. }
  231. VERIFY_NOT_REACHED();
  232. };
  233. MatchResult result = matches_media(m_media_type);
  234. if ((result == MatchResult::True) && m_media_condition)
  235. result = m_media_condition->evaluate(window);
  236. if (m_negated)
  237. result = negate(result);
  238. m_matches = result == MatchResult::True;
  239. return m_matches;
  240. }
  241. // https://www.w3.org/TR/cssom-1/#serialize-a-media-query-list
  242. String serialize_a_media_query_list(NonnullRefPtrVector<MediaQuery> const& media_queries)
  243. {
  244. // 1. If the media query list is empty, then return the empty string.
  245. if (media_queries.is_empty())
  246. return "";
  247. // 2. Serialize each media query in the list of media queries, in the same order as they
  248. // appear in the media query list, and then serialize the list.
  249. StringBuilder builder;
  250. builder.join(", ", media_queries);
  251. return builder.to_string();
  252. }
  253. }