1 | /* |
2 | * CSS Media Query Evaluator |
3 | * |
4 | * Copyright (C) 2006 Kimmo Kinnunen <kimmo.t.kinnunen@nokia.com>. |
5 | * Copyright (C) 2013 Apple Inc. All rights reserved. |
6 | * |
7 | * Redistribution and use in source and binary forms, with or without |
8 | * modification, are permitted provided that the following conditions |
9 | * are met: |
10 | * 1. Redistributions of source code must retain the above copyright |
11 | * notice, this list of conditions and the following disclaimer. |
12 | * 2. Redistributions in binary form must reproduce the above copyright |
13 | * notice, this list of conditions and the following disclaimer in the |
14 | * documentation and/or other materials provided with the distribution. |
15 | * |
16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY |
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
19 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
20 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
21 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
22 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
23 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
24 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
27 | */ |
28 | |
29 | #include "config.h" |
30 | #include "MediaQueryEvaluator.h" |
31 | |
32 | #include "CSSAspectRatioValue.h" |
33 | #include "CSSPrimitiveValue.h" |
34 | #include "CSSToLengthConversionData.h" |
35 | #include "CSSValueKeywords.h" |
36 | #include "Frame.h" |
37 | #include "FrameView.h" |
38 | #include "Logging.h" |
39 | #include "MediaFeatureNames.h" |
40 | #include "MediaList.h" |
41 | #include "MediaQuery.h" |
42 | #include "MediaQueryParserContext.h" |
43 | #include "NodeRenderStyle.h" |
44 | #include "Page.h" |
45 | #include "PlatformScreen.h" |
46 | #include "RenderStyle.h" |
47 | #include "RenderView.h" |
48 | #include "RuntimeEnabledFeatures.h" |
49 | #include "Settings.h" |
50 | #include "StyleResolver.h" |
51 | #include "Theme.h" |
52 | #include <wtf/HashMap.h> |
53 | #include <wtf/text/StringConcatenateNumbers.h> |
54 | #include <wtf/text/TextStream.h> |
55 | |
56 | #if ENABLE(3D_TRANSFORMS) |
57 | #include "RenderLayerCompositor.h" |
58 | #endif |
59 | |
60 | namespace WebCore { |
61 | |
62 | enum MediaFeaturePrefix { MinPrefix, MaxPrefix, NoPrefix }; |
63 | |
64 | #ifndef LOG_DISABLED |
65 | static TextStream& operator<<(TextStream& ts, MediaFeaturePrefix op) |
66 | { |
67 | switch (op) { |
68 | case MinPrefix: ts << "min" ; break; |
69 | case MaxPrefix: ts << "max" ; break; |
70 | case NoPrefix: ts << "" ; break; |
71 | } |
72 | return ts; |
73 | } |
74 | #endif |
75 | |
76 | typedef bool (*MediaQueryFunction)(CSSValue*, const CSSToLengthConversionData&, Frame&, MediaFeaturePrefix); |
77 | typedef HashMap<AtomicStringImpl*, MediaQueryFunction> MediaQueryFunctionMap; |
78 | |
79 | static bool isAccessibilitySettingsDependent(const AtomicString& mediaFeature) |
80 | { |
81 | return mediaFeature == MediaFeatureNames::invertedColors |
82 | || mediaFeature == MediaFeatureNames::maxMonochrome |
83 | || mediaFeature == MediaFeatureNames::minMonochrome |
84 | || mediaFeature == MediaFeatureNames::monochrome |
85 | || mediaFeature == MediaFeatureNames::prefersReducedMotion; |
86 | } |
87 | |
88 | static bool isViewportDependent(const AtomicString& mediaFeature) |
89 | { |
90 | return mediaFeature == MediaFeatureNames::width |
91 | || mediaFeature == MediaFeatureNames::height |
92 | || mediaFeature == MediaFeatureNames::minWidth |
93 | || mediaFeature == MediaFeatureNames::minHeight |
94 | || mediaFeature == MediaFeatureNames::maxWidth |
95 | || mediaFeature == MediaFeatureNames::maxHeight |
96 | || mediaFeature == MediaFeatureNames::orientation |
97 | || mediaFeature == MediaFeatureNames::aspectRatio |
98 | || mediaFeature == MediaFeatureNames::minAspectRatio |
99 | || mediaFeature == MediaFeatureNames::maxAspectRatio; |
100 | } |
101 | |
102 | static bool isAppearanceDependent(const AtomicString& mediaFeature) |
103 | { |
104 | return mediaFeature == MediaFeatureNames::prefersDarkInterface |
105 | #if ENABLE(DARK_MODE_CSS) |
106 | || mediaFeature == MediaFeatureNames::prefersColorScheme |
107 | #endif |
108 | ; |
109 | } |
110 | |
111 | MediaQueryEvaluator::MediaQueryEvaluator(bool mediaFeatureResult) |
112 | : m_fallbackResult(mediaFeatureResult) |
113 | { |
114 | } |
115 | |
116 | MediaQueryEvaluator::MediaQueryEvaluator(const String& acceptedMediaType, bool mediaFeatureResult) |
117 | : m_mediaType(acceptedMediaType) |
118 | , m_fallbackResult(mediaFeatureResult) |
119 | { |
120 | } |
121 | |
122 | MediaQueryEvaluator::MediaQueryEvaluator(const String& acceptedMediaType, const Document& document, const RenderStyle* style) |
123 | : m_mediaType(acceptedMediaType) |
124 | , m_document(makeWeakPtr(document)) |
125 | , m_style(style) |
126 | { |
127 | } |
128 | |
129 | bool MediaQueryEvaluator::mediaTypeMatch(const String& mediaTypeToMatch) const |
130 | { |
131 | return mediaTypeToMatch.isEmpty() |
132 | || equalLettersIgnoringASCIICase(mediaTypeToMatch, "all" ) |
133 | || equalIgnoringASCIICase(mediaTypeToMatch, m_mediaType); |
134 | } |
135 | |
136 | bool MediaQueryEvaluator::mediaTypeMatchSpecific(const char* mediaTypeToMatch) const |
137 | { |
138 | // Like mediaTypeMatch, but without the special cases for "" and "all". |
139 | ASSERT(mediaTypeToMatch); |
140 | ASSERT(mediaTypeToMatch[0] != '\0'); |
141 | ASSERT(!equalLettersIgnoringASCIICase(StringView(mediaTypeToMatch), "all" )); |
142 | return equalIgnoringASCIICase(m_mediaType, mediaTypeToMatch); |
143 | } |
144 | |
145 | static bool applyRestrictor(MediaQuery::Restrictor r, bool value) |
146 | { |
147 | return r == MediaQuery::Not ? !value : value; |
148 | } |
149 | |
150 | bool MediaQueryEvaluator::evaluate(const MediaQuerySet& querySet, StyleResolver* styleResolver) const |
151 | { |
152 | LOG_WITH_STREAM(MediaQueries, stream << "MediaQueryEvaluator::evaluate on " << (m_document ? m_document->url().string() : emptyString())); |
153 | |
154 | auto& queries = querySet.queryVector(); |
155 | if (!queries.size()) { |
156 | LOG_WITH_STREAM(MediaQueries, stream << "MediaQueryEvaluator::evaluate " << querySet << " returning true" ); |
157 | return true; // Empty query list evaluates to true. |
158 | } |
159 | |
160 | // Iterate over queries, stop if any of them eval to true (OR semantics). |
161 | bool result = false; |
162 | for (size_t i = 0; i < queries.size() && !result; ++i) { |
163 | auto& query = queries[i]; |
164 | |
165 | if (query.ignored() || (!query.expressions().size() && query.mediaType().isEmpty())) |
166 | continue; |
167 | |
168 | if (mediaTypeMatch(query.mediaType())) { |
169 | auto& expressions = query.expressions(); |
170 | // Iterate through expressions, stop if any of them eval to false (AND semantics). |
171 | size_t j = 0; |
172 | for (; j < expressions.size(); ++j) { |
173 | bool expressionResult = evaluate(expressions[j]); |
174 | if (styleResolver && isViewportDependent(expressions[j].mediaFeature())) |
175 | styleResolver->addViewportDependentMediaQueryResult(expressions[j], expressionResult); |
176 | if (styleResolver && isAccessibilitySettingsDependent(expressions[j].mediaFeature())) |
177 | styleResolver->addAccessibilitySettingsDependentMediaQueryResult(expressions[j], expressionResult); |
178 | if (styleResolver && isAppearanceDependent(expressions[j].mediaFeature())) |
179 | styleResolver->addAppearanceDependentMediaQueryResult(expressions[j], expressionResult); |
180 | if (!expressionResult) |
181 | break; |
182 | } |
183 | |
184 | // Assume true if we are at the end of the list, otherwise assume false. |
185 | result = applyRestrictor(query.restrictor(), expressions.size() == j); |
186 | } else |
187 | result = applyRestrictor(query.restrictor(), false); |
188 | } |
189 | |
190 | LOG_WITH_STREAM(MediaQueries, stream << "MediaQueryEvaluator::evaluate " << querySet << " returning " << result); |
191 | return result; |
192 | } |
193 | |
194 | bool MediaQueryEvaluator::evaluate(const MediaQuerySet& querySet, Vector<MediaQueryResult>& viewportDependentResults, Vector<MediaQueryResult>& appearanceDependentResults) const |
195 | { |
196 | auto& queries = querySet.queryVector(); |
197 | if (!queries.size()) |
198 | return true; |
199 | |
200 | bool result = false; |
201 | for (size_t i = 0; i < queries.size() && !result; ++i) { |
202 | auto& query = queries[i]; |
203 | |
204 | if (query.ignored()) |
205 | continue; |
206 | |
207 | if (mediaTypeMatch(query.mediaType())) { |
208 | auto& expressions = query.expressions(); |
209 | size_t j = 0; |
210 | for (; j < expressions.size(); ++j) { |
211 | bool expressionResult = evaluate(expressions[j]); |
212 | if (isViewportDependent(expressions[j].mediaFeature())) |
213 | viewportDependentResults.append({ expressions[j], expressionResult }); |
214 | if (isAppearanceDependent(expressions[j].mediaFeature())) |
215 | appearanceDependentResults.append({ expressions[j], expressionResult }); |
216 | if (!expressionResult) |
217 | break; |
218 | } |
219 | result = applyRestrictor(query.restrictor(), expressions.size() == j); |
220 | } else |
221 | result = applyRestrictor(query.restrictor(), false); |
222 | } |
223 | |
224 | return result; |
225 | } |
226 | |
227 | template<typename T, typename U> bool compareValue(T a, U b, MediaFeaturePrefix op) |
228 | { |
229 | switch (op) { |
230 | case MinPrefix: |
231 | return a >= b; |
232 | case MaxPrefix: |
233 | return a <= b; |
234 | case NoPrefix: |
235 | return a == b; |
236 | } |
237 | return false; |
238 | } |
239 | |
240 | #if !LOG_DISABLED |
241 | |
242 | static String aspectRatioValueAsString(CSSValue* value) |
243 | { |
244 | if (!is<CSSAspectRatioValue>(value)) |
245 | return emptyString(); |
246 | |
247 | auto& aspectRatio = downcast<CSSAspectRatioValue>(*value); |
248 | return makeString(FormattedNumber::fixedWidth(aspectRatio.numeratorValue(), 6), '/', FormattedNumber::fixedWidth(aspectRatio.denominatorValue(), 6)); |
249 | } |
250 | |
251 | #endif |
252 | |
253 | static bool compareAspectRatioValue(CSSValue* value, int width, int height, MediaFeaturePrefix op) |
254 | { |
255 | if (!is<CSSAspectRatioValue>(value)) |
256 | return false; |
257 | auto& aspectRatio = downcast<CSSAspectRatioValue>(*value); |
258 | return compareValue(width * aspectRatio.denominatorValue(), height * aspectRatio.numeratorValue(), op); |
259 | } |
260 | |
261 | static Optional<double> doubleValue(CSSValue* value) |
262 | { |
263 | if (!is<CSSPrimitiveValue>(value) || !downcast<CSSPrimitiveValue>(*value).isNumber()) |
264 | return WTF::nullopt; |
265 | return downcast<CSSPrimitiveValue>(*value).doubleValue(CSSPrimitiveValue::CSS_NUMBER); |
266 | } |
267 | |
268 | static bool zeroEvaluate(CSSValue* value, MediaFeaturePrefix op) |
269 | { |
270 | auto numericValue = doubleValue(value); |
271 | return numericValue && compareValue(0, numericValue.value(), op); |
272 | } |
273 | |
274 | static bool oneEvaluate(CSSValue* value, MediaFeaturePrefix op) |
275 | { |
276 | if (!value) |
277 | return true; |
278 | auto numericValue = doubleValue(value); |
279 | return numericValue && compareValue(1, numericValue.value(), op); |
280 | } |
281 | |
282 | static bool colorEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame& frame, MediaFeaturePrefix op) |
283 | { |
284 | int bitsPerComponent = screenDepthPerComponent(frame.mainFrame().view()); |
285 | auto numericValue = doubleValue(value); |
286 | if (!numericValue) |
287 | return bitsPerComponent; |
288 | return compareValue(bitsPerComponent, numericValue.value(), op); |
289 | } |
290 | |
291 | static bool colorIndexEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame&, MediaFeaturePrefix op) |
292 | { |
293 | // Always return false for indexed display. |
294 | return zeroEvaluate(value, op); |
295 | } |
296 | |
297 | static bool colorGamutEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame& frame, MediaFeaturePrefix) |
298 | { |
299 | if (!value) |
300 | return true; |
301 | |
302 | switch (downcast<CSSPrimitiveValue>(*value).valueID()) { |
303 | case CSSValueSRGB: |
304 | return true; |
305 | case CSSValueP3: |
306 | // FIXME: For the moment we just assume any "extended color" display is at least as good as P3. |
307 | return screenSupportsExtendedColor(frame.mainFrame().view()); |
308 | case CSSValueRec2020: |
309 | // FIXME: At some point we should start detecting displays that support more colors. |
310 | return false; |
311 | default: |
312 | return false; // Any unknown value should not be considered a match. |
313 | } |
314 | } |
315 | |
316 | static bool monochromeEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix op) |
317 | { |
318 | bool isMonochrome; |
319 | |
320 | if (frame.settings().forcedDisplayIsMonochromeAccessibilityValue() == Settings::ForcedAccessibilityValue::On) |
321 | isMonochrome = true; |
322 | else if (frame.settings().forcedDisplayIsMonochromeAccessibilityValue() == Settings::ForcedAccessibilityValue::Off) |
323 | isMonochrome = false; |
324 | else |
325 | isMonochrome = screenIsMonochrome(frame.mainFrame().view()); |
326 | |
327 | if (!isMonochrome) |
328 | return zeroEvaluate(value, op); |
329 | return colorEvaluate(value, conversionData, frame, op); |
330 | } |
331 | |
332 | static bool invertedColorsEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame& frame, MediaFeaturePrefix) |
333 | { |
334 | bool isInverted; |
335 | |
336 | if (frame.settings().forcedColorsAreInvertedAccessibilityValue() == Settings::ForcedAccessibilityValue::On) |
337 | isInverted = true; |
338 | else if (frame.settings().forcedColorsAreInvertedAccessibilityValue() == Settings::ForcedAccessibilityValue::Off) |
339 | isInverted = false; |
340 | else |
341 | isInverted = screenHasInvertedColors(); |
342 | |
343 | if (!value) |
344 | return isInverted; |
345 | |
346 | return downcast<CSSPrimitiveValue>(*value).valueID() == (isInverted ? CSSValueInverted : CSSValueNone); |
347 | } |
348 | |
349 | static bool orientationEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame& frame, MediaFeaturePrefix) |
350 | { |
351 | FrameView* view = frame.view(); |
352 | if (!view) |
353 | return false; |
354 | |
355 | auto width = view->layoutWidth(); |
356 | auto height = view->layoutHeight(); |
357 | |
358 | if (!is<CSSPrimitiveValue>(value)) { |
359 | // Expression (orientation) evaluates to true if width and height >= 0. |
360 | return height >= 0 && width >= 0; |
361 | } |
362 | |
363 | auto keyword = downcast<CSSPrimitiveValue>(*value).valueID(); |
364 | bool result; |
365 | if (width > height) // Square viewport is portrait. |
366 | result = keyword == CSSValueLandscape; |
367 | else |
368 | result = keyword == CSSValuePortrait; |
369 | |
370 | LOG_WITH_STREAM(MediaQueries, stream << " orientationEvaluate: view size " << width << "x" << height << " is " << value->cssText() << ": " << result); |
371 | return result; |
372 | } |
373 | |
374 | static bool aspectRatioEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame& frame, MediaFeaturePrefix op) |
375 | { |
376 | // ({,min-,max-}aspect-ratio) |
377 | // assume if we have a device, its aspect ratio is non-zero |
378 | if (!value) |
379 | return true; |
380 | FrameView* view = frame.view(); |
381 | if (!view) |
382 | return true; |
383 | bool result = compareAspectRatioValue(value, view->layoutWidth(), view->layoutHeight(), op); |
384 | LOG_WITH_STREAM(MediaQueries, stream << " aspectRatioEvaluate: " << op << " " << aspectRatioValueAsString(value) << " actual view size " << view->layoutWidth() << "x" << view->layoutHeight() << " : " << result); |
385 | return result; |
386 | } |
387 | |
388 | static bool deviceAspectRatioEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame& frame, MediaFeaturePrefix op) |
389 | { |
390 | // ({,min-,max-}device-aspect-ratio) |
391 | // assume if we have a device, its aspect ratio is non-zero |
392 | if (!value) |
393 | return true; |
394 | |
395 | auto size = screenRect(frame.mainFrame().view()).size(); |
396 | bool result = compareAspectRatioValue(value, size.width(), size.height(), op); |
397 | LOG_WITH_STREAM(MediaQueries, stream << " deviceAspectRatioEvaluate: " << op << " " << aspectRatioValueAsString(value) << " actual screen size " << size << ": " << result); |
398 | return result; |
399 | } |
400 | |
401 | static bool evaluateResolution(CSSValue* value, Frame& frame, MediaFeaturePrefix op) |
402 | { |
403 | // FIXME: Possible handle other media types than 'screen' and 'print'. |
404 | FrameView* view = frame.view(); |
405 | if (!view) |
406 | return false; |
407 | |
408 | float deviceScaleFactor = 0; |
409 | |
410 | // This checks the actual media type applied to the document, and we know |
411 | // this method only got called if this media type matches the one defined |
412 | // in the query. Thus, if if the document's media type is "print", the |
413 | // media type of the query will either be "print" or "all". |
414 | String mediaType = view->mediaType(); |
415 | if (equalLettersIgnoringASCIICase(mediaType, "screen" )) |
416 | deviceScaleFactor = frame.page() ? frame.page()->deviceScaleFactor() : 1; |
417 | else if (equalLettersIgnoringASCIICase(mediaType, "print" )) { |
418 | // The resolution of images while printing should not depend on the dpi |
419 | // of the screen. Until we support proper ways of querying this info |
420 | // we use 300px which is considered minimum for current printers. |
421 | deviceScaleFactor = 3.125; // 300dpi / 96dpi; |
422 | } |
423 | |
424 | if (!value) |
425 | return !!deviceScaleFactor; |
426 | |
427 | if (!is<CSSPrimitiveValue>(value)) |
428 | return false; |
429 | |
430 | auto& resolution = downcast<CSSPrimitiveValue>(*value); |
431 | float resolutionValue = resolution.isNumber() ? resolution.floatValue() : resolution.floatValue(CSSPrimitiveValue::CSS_DPPX); |
432 | bool result = compareValue(deviceScaleFactor, resolutionValue, op); |
433 | LOG_WITH_STREAM(MediaQueries, stream << " evaluateResolution: " << op << " " << resolutionValue << " device scale factor " << deviceScaleFactor << ": " << result); |
434 | return result; |
435 | } |
436 | |
437 | static bool devicePixelRatioEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame& frame, MediaFeaturePrefix op) |
438 | { |
439 | return (!value || (is<CSSPrimitiveValue>(*value) && downcast<CSSPrimitiveValue>(*value).isNumber())) && evaluateResolution(value, frame, op); |
440 | } |
441 | |
442 | static bool resolutionEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame& frame, MediaFeaturePrefix op) |
443 | { |
444 | #if ENABLE(RESOLUTION_MEDIA_QUERY) |
445 | return (!value || (is<CSSPrimitiveValue>(*value) && downcast<CSSPrimitiveValue>(*value).isResolution())) && evaluateResolution(value, frame, op); |
446 | #else |
447 | UNUSED_PARAM(value); |
448 | UNUSED_PARAM(frame); |
449 | UNUSED_PARAM(op); |
450 | return false; |
451 | #endif |
452 | } |
453 | |
454 | static bool gridEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame&, MediaFeaturePrefix op) |
455 | { |
456 | return zeroEvaluate(value, op); |
457 | } |
458 | |
459 | static bool computeLength(CSSValue* value, bool strict, const CSSToLengthConversionData& conversionData, int& result) |
460 | { |
461 | if (!is<CSSPrimitiveValue>(value)) |
462 | return false; |
463 | |
464 | auto& primitiveValue = downcast<CSSPrimitiveValue>(*value); |
465 | |
466 | if (primitiveValue.isNumber()) { |
467 | result = primitiveValue.intValue(); |
468 | return !strict || !result; |
469 | } |
470 | |
471 | if (primitiveValue.isLength()) { |
472 | result = primitiveValue.computeLength<int>(conversionData); |
473 | return true; |
474 | } |
475 | |
476 | return false; |
477 | } |
478 | |
479 | static bool deviceHeightEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix op) |
480 | { |
481 | // ({,min-,max-}device-height) |
482 | // assume if we have a device, assume non-zero |
483 | if (!value) |
484 | return true; |
485 | int length; |
486 | auto height = screenRect(frame.mainFrame().view()).height(); |
487 | if (!computeLength(value, !frame.document()->inQuirksMode(), conversionData, length)) |
488 | return false; |
489 | |
490 | LOG_WITH_STREAM(MediaQueries, stream << " deviceHeightEvaluate: query " << op << " height " << length << ", actual height " << height << " result: " << compareValue(height, length, op)); |
491 | |
492 | return compareValue(height, length, op); |
493 | } |
494 | |
495 | static bool deviceWidthEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix op) |
496 | { |
497 | // ({,min-,max-}device-width) |
498 | // assume if we have a device, assume non-zero |
499 | if (!value) |
500 | return true; |
501 | int length; |
502 | auto width = screenRect(frame.mainFrame().view()).width(); |
503 | if (!computeLength(value, !frame.document()->inQuirksMode(), conversionData, length)) |
504 | return false; |
505 | |
506 | LOG_WITH_STREAM(MediaQueries, stream << " deviceWidthEvaluate: query " << op << " width " << length << ", actual width " << width << " result: " << compareValue(width, length, op)); |
507 | |
508 | return compareValue(width, length, op); |
509 | } |
510 | |
511 | static bool heightEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix op) |
512 | { |
513 | FrameView* view = frame.view(); |
514 | if (!view) |
515 | return false; |
516 | int height = view->layoutHeight(); |
517 | if (!value) |
518 | return height; |
519 | if (auto* renderView = frame.document()->renderView()) |
520 | height = adjustForAbsoluteZoom(height, *renderView); |
521 | |
522 | int length; |
523 | if (!computeLength(value, !frame.document()->inQuirksMode(), conversionData, length)) |
524 | return false; |
525 | |
526 | LOG_WITH_STREAM(MediaQueries, stream << " heightEvaluate: query " << op << " height " << length << ", actual height " << height << " result: " << compareValue(height, length, op)); |
527 | |
528 | return compareValue(height, length, op); |
529 | } |
530 | |
531 | static bool widthEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix op) |
532 | { |
533 | FrameView* view = frame.view(); |
534 | if (!view) |
535 | return false; |
536 | int width = view->layoutWidth(); |
537 | if (!value) |
538 | return width; |
539 | if (auto* renderView = frame.document()->renderView()) |
540 | width = adjustForAbsoluteZoom(width, *renderView); |
541 | |
542 | int length; |
543 | if (!computeLength(value, !frame.document()->inQuirksMode(), conversionData, length)) |
544 | return false; |
545 | |
546 | LOG_WITH_STREAM(MediaQueries, stream << " widthEvaluate: query " << op << " width " << length << ", actual width " << width << " result: " << compareValue(width, length, op)); |
547 | |
548 | return compareValue(width, length, op); |
549 | } |
550 | |
551 | static bool minColorEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
552 | { |
553 | return colorEvaluate(value, conversionData, frame, MinPrefix); |
554 | } |
555 | |
556 | static bool maxColorEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
557 | { |
558 | return colorEvaluate(value, conversionData, frame, MaxPrefix); |
559 | } |
560 | |
561 | static bool minColorIndexEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
562 | { |
563 | return colorIndexEvaluate(value, conversionData, frame, MinPrefix); |
564 | } |
565 | |
566 | static bool maxColorIndexEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
567 | { |
568 | return colorIndexEvaluate(value, conversionData, frame, MaxPrefix); |
569 | } |
570 | |
571 | static bool minMonochromeEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
572 | { |
573 | return monochromeEvaluate(value, conversionData, frame, MinPrefix); |
574 | } |
575 | |
576 | static bool maxMonochromeEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
577 | { |
578 | return monochromeEvaluate(value, conversionData, frame, MaxPrefix); |
579 | } |
580 | |
581 | static bool minAspectRatioEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
582 | { |
583 | return aspectRatioEvaluate(value, conversionData, frame, MinPrefix); |
584 | } |
585 | |
586 | static bool maxAspectRatioEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
587 | { |
588 | return aspectRatioEvaluate(value, conversionData, frame, MaxPrefix); |
589 | } |
590 | |
591 | static bool minDeviceAspectRatioEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
592 | { |
593 | return deviceAspectRatioEvaluate(value, conversionData, frame, MinPrefix); |
594 | } |
595 | |
596 | static bool maxDeviceAspectRatioEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
597 | { |
598 | return deviceAspectRatioEvaluate(value, conversionData, frame, MaxPrefix); |
599 | } |
600 | |
601 | static bool minDevicePixelRatioEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
602 | { |
603 | return devicePixelRatioEvaluate(value, conversionData, frame, MinPrefix); |
604 | } |
605 | |
606 | static bool maxDevicePixelRatioEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
607 | { |
608 | return devicePixelRatioEvaluate(value, conversionData, frame, MaxPrefix); |
609 | } |
610 | |
611 | static bool minHeightEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
612 | { |
613 | return heightEvaluate(value, conversionData, frame, MinPrefix); |
614 | } |
615 | |
616 | static bool maxHeightEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
617 | { |
618 | return heightEvaluate(value, conversionData, frame, MaxPrefix); |
619 | } |
620 | |
621 | static bool minWidthEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
622 | { |
623 | return widthEvaluate(value, conversionData, frame, MinPrefix); |
624 | } |
625 | |
626 | static bool maxWidthEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
627 | { |
628 | return widthEvaluate(value, conversionData, frame, MaxPrefix); |
629 | } |
630 | |
631 | static bool minDeviceHeightEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
632 | { |
633 | return deviceHeightEvaluate(value, conversionData, frame, MinPrefix); |
634 | } |
635 | |
636 | static bool maxDeviceHeightEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
637 | { |
638 | return deviceHeightEvaluate(value, conversionData, frame, MaxPrefix); |
639 | } |
640 | |
641 | static bool minDeviceWidthEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
642 | { |
643 | return deviceWidthEvaluate(value, conversionData, frame, MinPrefix); |
644 | } |
645 | |
646 | static bool maxDeviceWidthEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
647 | { |
648 | return deviceWidthEvaluate(value, conversionData, frame, MaxPrefix); |
649 | } |
650 | |
651 | static bool minResolutionEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
652 | { |
653 | return resolutionEvaluate(value, conversionData, frame, MinPrefix); |
654 | } |
655 | |
656 | static bool maxResolutionEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix) |
657 | { |
658 | return resolutionEvaluate(value, conversionData, frame, MaxPrefix); |
659 | } |
660 | |
661 | static bool animationEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame&, MediaFeaturePrefix op) |
662 | { |
663 | return oneEvaluate(value, op); |
664 | } |
665 | |
666 | static bool transitionEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame&, MediaFeaturePrefix op) |
667 | { |
668 | return oneEvaluate(value, op); |
669 | } |
670 | |
671 | static bool transform2dEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame&, MediaFeaturePrefix op) |
672 | { |
673 | return oneEvaluate(value, op); |
674 | } |
675 | |
676 | static bool transform3dEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame& frame, MediaFeaturePrefix op) |
677 | { |
678 | #if ENABLE(3D_TRANSFORMS) |
679 | auto* view = frame.contentRenderer(); |
680 | return view && view->compositor().canRender3DTransforms() ? oneEvaluate(value, op) : zeroEvaluate(value, op); |
681 | #else |
682 | UNUSED_PARAM(frame); |
683 | return zeroEvaluate(value, op); |
684 | #endif |
685 | } |
686 | |
687 | static bool videoPlayableInlineEvaluate(CSSValue*, const CSSToLengthConversionData&, Frame& frame, MediaFeaturePrefix) |
688 | { |
689 | return frame.settings().allowsInlineMediaPlayback(); |
690 | } |
691 | |
692 | static bool hoverEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame&, MediaFeaturePrefix) |
693 | { |
694 | if (!is<CSSPrimitiveValue>(value)) { |
695 | #if ENABLE(TOUCH_EVENTS) |
696 | return false; |
697 | #else |
698 | return true; |
699 | #endif |
700 | } |
701 | |
702 | auto keyword = downcast<CSSPrimitiveValue>(*value).valueID(); |
703 | #if ENABLE(TOUCH_EVENTS) |
704 | return keyword == CSSValueNone; |
705 | #else |
706 | return keyword == CSSValueHover; |
707 | #endif |
708 | } |
709 | |
710 | static bool anyHoverEvaluate(CSSValue* value, const CSSToLengthConversionData& cssToLengthConversionData, Frame& frame, MediaFeaturePrefix prefix) |
711 | { |
712 | return hoverEvaluate(value, cssToLengthConversionData, frame, prefix); |
713 | } |
714 | |
715 | static bool pointerEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame&, MediaFeaturePrefix) |
716 | { |
717 | if (!is<CSSPrimitiveValue>(value)) |
718 | return true; |
719 | |
720 | auto keyword = downcast<CSSPrimitiveValue>(*value).valueID(); |
721 | #if ENABLE(TOUCH_EVENTS) |
722 | return keyword == CSSValueCoarse; |
723 | #else |
724 | return keyword == CSSValueFine; |
725 | #endif |
726 | } |
727 | |
728 | static bool anyPointerEvaluate(CSSValue* value, const CSSToLengthConversionData& cssToLengthConversionData, Frame& frame, MediaFeaturePrefix prefix) |
729 | { |
730 | return pointerEvaluate(value, cssToLengthConversionData, frame, prefix); |
731 | } |
732 | |
733 | static bool prefersDarkInterfaceEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame& frame, MediaFeaturePrefix) |
734 | { |
735 | bool prefersDarkInterface = false; |
736 | |
737 | if (frame.page()->useSystemAppearance() && frame.page()->useDarkAppearance()) |
738 | prefersDarkInterface = true; |
739 | |
740 | if (!value) |
741 | return prefersDarkInterface; |
742 | |
743 | return downcast<CSSPrimitiveValue>(*value).valueID() == (prefersDarkInterface ? CSSValuePrefers : CSSValueNoPreference); |
744 | } |
745 | |
746 | #if ENABLE(DARK_MODE_CSS) |
747 | static bool prefersColorSchemeEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame& frame, MediaFeaturePrefix) |
748 | { |
749 | ASSERT(RuntimeEnabledFeatures::sharedFeatures().darkModeCSSEnabled()); |
750 | |
751 | if (!value) |
752 | return true; |
753 | |
754 | if (!is<CSSPrimitiveValue>(value)) |
755 | return false; |
756 | |
757 | auto keyword = downcast<CSSPrimitiveValue>(*value).valueID(); |
758 | bool useDarkAppearance = frame.page()->useDarkAppearance(); |
759 | |
760 | switch (keyword) { |
761 | case CSSValueNoPreference: |
762 | return false; |
763 | case CSSValueDark: |
764 | return useDarkAppearance; |
765 | case CSSValueLight: |
766 | return !useDarkAppearance; |
767 | default: |
768 | return false; |
769 | } |
770 | } |
771 | #endif |
772 | |
773 | static bool prefersReducedMotionEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame& frame, MediaFeaturePrefix) |
774 | { |
775 | bool userPrefersReducedMotion = false; |
776 | |
777 | switch (frame.settings().forcedPrefersReducedMotionAccessibilityValue()) { |
778 | case Settings::ForcedAccessibilityValue::On: |
779 | userPrefersReducedMotion = true; |
780 | break; |
781 | case Settings::ForcedAccessibilityValue::Off: |
782 | break; |
783 | case Settings::ForcedAccessibilityValue::System: |
784 | #if USE(NEW_THEME) || PLATFORM(IOS_FAMILY) |
785 | userPrefersReducedMotion = Theme::singleton().userPrefersReducedMotion(); |
786 | #endif |
787 | break; |
788 | } |
789 | |
790 | if (!value) |
791 | return userPrefersReducedMotion; |
792 | |
793 | return downcast<CSSPrimitiveValue>(*value).valueID() == (userPrefersReducedMotion ? CSSValueReduce : CSSValueNoPreference); |
794 | } |
795 | |
796 | #if ENABLE(APPLICATION_MANIFEST) |
797 | static bool displayModeEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame& frame, MediaFeaturePrefix) |
798 | { |
799 | if (!value) |
800 | return true; |
801 | |
802 | auto keyword = downcast<CSSPrimitiveValue>(*value).valueID(); |
803 | |
804 | auto manifest = frame.page() ? frame.page()->applicationManifest() : WTF::nullopt; |
805 | if (!manifest) |
806 | return keyword == CSSValueBrowser; |
807 | |
808 | switch (manifest->display) { |
809 | case ApplicationManifest::Display::Fullscreen: |
810 | return keyword == CSSValueFullscreen; |
811 | case ApplicationManifest::Display::Standalone: |
812 | return keyword == CSSValueStandalone; |
813 | case ApplicationManifest::Display::MinimalUI: |
814 | return keyword == CSSValueMinimalUi; |
815 | case ApplicationManifest::Display::Browser: |
816 | return keyword == CSSValueBrowser; |
817 | } |
818 | |
819 | return false; |
820 | } |
821 | #endif // ENABLE(APPLICATION_MANIFEST) |
822 | |
823 | // Use this function instead of calling add directly to avoid inlining. |
824 | static void add(MediaQueryFunctionMap& map, AtomicStringImpl* key, MediaQueryFunction value) |
825 | { |
826 | map.add(key, value); |
827 | } |
828 | |
829 | bool MediaQueryEvaluator::evaluate(const MediaQueryExpression& expression) const |
830 | { |
831 | if (!m_document) |
832 | return m_fallbackResult; |
833 | |
834 | auto& document = *m_document; |
835 | auto* frame = document.frame(); |
836 | if (!frame || !frame->view() || !m_style) |
837 | return m_fallbackResult; |
838 | |
839 | if (!expression.isValid()) |
840 | return false; |
841 | |
842 | static NeverDestroyed<MediaQueryFunctionMap> map = [] { |
843 | MediaQueryFunctionMap map; |
844 | #define ADD_TO_FUNCTIONMAP(name, str) add(map, MediaFeatureNames::name->impl(), name##Evaluate); |
845 | CSS_MEDIAQUERY_NAMES_FOR_EACH_MEDIAFEATURE(ADD_TO_FUNCTIONMAP); |
846 | #undef ADD_TO_FUNCTIONMAP |
847 | return map; |
848 | }(); |
849 | |
850 | auto function = map.get().get(expression.mediaFeature().impl()); |
851 | if (!function) |
852 | return false; |
853 | |
854 | if (!document.documentElement()) |
855 | return false; |
856 | return function(expression.value(), { m_style, document.documentElement()->renderStyle(), document.renderView(), 1, false }, *frame, NoPrefix); |
857 | } |
858 | |
859 | bool MediaQueryEvaluator::mediaAttributeMatches(Document& document, const String& attributeValue) |
860 | { |
861 | ASSERT(document.renderView()); |
862 | auto mediaQueries = MediaQuerySet::create(attributeValue, MediaQueryParserContext(document)); |
863 | return MediaQueryEvaluator { "screen" , document, &document.renderView()->style() }.evaluate(mediaQueries.get()); |
864 | } |
865 | |
866 | } // WebCore |
867 | |