Add modern API for overriding the page's specified viewport configuration
[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/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