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