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