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
60namespace WebCore {
61
62enum MediaFeaturePrefix { MinPrefix, MaxPrefix, NoPrefix };
63
64#ifndef LOG_DISABLED
65static 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
76typedef bool (*MediaQueryFunction)(CSSValue*, const CSSToLengthConversionData&, Frame&, MediaFeaturePrefix);
77typedef HashMap<AtomicStringImpl*, MediaQueryFunction> MediaQueryFunctionMap;
78
79static 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
88static 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
102static 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
111MediaQueryEvaluator::MediaQueryEvaluator(bool mediaFeatureResult)
112 : m_fallbackResult(mediaFeatureResult)
113{
114}
115
116MediaQueryEvaluator::MediaQueryEvaluator(const String& acceptedMediaType, bool mediaFeatureResult)
117 : m_mediaType(acceptedMediaType)
118 , m_fallbackResult(mediaFeatureResult)
119{
120}
121
122MediaQueryEvaluator::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
129bool MediaQueryEvaluator::mediaTypeMatch(const String& mediaTypeToMatch) const
130{
131 return mediaTypeToMatch.isEmpty()
132 || equalLettersIgnoringASCIICase(mediaTypeToMatch, "all")
133 || equalIgnoringASCIICase(mediaTypeToMatch, m_mediaType);
134}
135
136bool 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
145static bool applyRestrictor(MediaQuery::Restrictor r, bool value)
146{
147 return r == MediaQuery::Not ? !value : value;
148}
149
150bool 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
194bool 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
227template<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
242static 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
253static 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
261static 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
268static bool zeroEvaluate(CSSValue* value, MediaFeaturePrefix op)
269{
270 auto numericValue = doubleValue(value);
271 return numericValue && compareValue(0, numericValue.value(), op);
272}
273
274static 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
282static 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
291static bool colorIndexEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame&, MediaFeaturePrefix op)
292{
293 // Always return false for indexed display.
294 return zeroEvaluate(value, op);
295}
296
297static 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
316static 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
332static 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
349static 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
374static 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
388static 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
401static 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
437static 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
442static 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
454static bool gridEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame&, MediaFeaturePrefix op)
455{
456 return zeroEvaluate(value, op);
457}
458
459static 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
479static 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
495static 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
511static 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
531static 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
551static bool minColorEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
552{
553 return colorEvaluate(value, conversionData, frame, MinPrefix);
554}
555
556static bool maxColorEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
557{
558 return colorEvaluate(value, conversionData, frame, MaxPrefix);
559}
560
561static bool minColorIndexEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
562{
563 return colorIndexEvaluate(value, conversionData, frame, MinPrefix);
564}
565
566static bool maxColorIndexEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
567{
568 return colorIndexEvaluate(value, conversionData, frame, MaxPrefix);
569}
570
571static bool minMonochromeEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
572{
573 return monochromeEvaluate(value, conversionData, frame, MinPrefix);
574}
575
576static bool maxMonochromeEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
577{
578 return monochromeEvaluate(value, conversionData, frame, MaxPrefix);
579}
580
581static bool minAspectRatioEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
582{
583 return aspectRatioEvaluate(value, conversionData, frame, MinPrefix);
584}
585
586static bool maxAspectRatioEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
587{
588 return aspectRatioEvaluate(value, conversionData, frame, MaxPrefix);
589}
590
591static bool minDeviceAspectRatioEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
592{
593 return deviceAspectRatioEvaluate(value, conversionData, frame, MinPrefix);
594}
595
596static bool maxDeviceAspectRatioEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
597{
598 return deviceAspectRatioEvaluate(value, conversionData, frame, MaxPrefix);
599}
600
601static bool minDevicePixelRatioEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
602{
603 return devicePixelRatioEvaluate(value, conversionData, frame, MinPrefix);
604}
605
606static bool maxDevicePixelRatioEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
607{
608 return devicePixelRatioEvaluate(value, conversionData, frame, MaxPrefix);
609}
610
611static bool minHeightEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
612{
613 return heightEvaluate(value, conversionData, frame, MinPrefix);
614}
615
616static bool maxHeightEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
617{
618 return heightEvaluate(value, conversionData, frame, MaxPrefix);
619}
620
621static bool minWidthEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
622{
623 return widthEvaluate(value, conversionData, frame, MinPrefix);
624}
625
626static bool maxWidthEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
627{
628 return widthEvaluate(value, conversionData, frame, MaxPrefix);
629}
630
631static bool minDeviceHeightEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
632{
633 return deviceHeightEvaluate(value, conversionData, frame, MinPrefix);
634}
635
636static bool maxDeviceHeightEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
637{
638 return deviceHeightEvaluate(value, conversionData, frame, MaxPrefix);
639}
640
641static bool minDeviceWidthEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
642{
643 return deviceWidthEvaluate(value, conversionData, frame, MinPrefix);
644}
645
646static bool maxDeviceWidthEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
647{
648 return deviceWidthEvaluate(value, conversionData, frame, MaxPrefix);
649}
650
651static bool minResolutionEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
652{
653 return resolutionEvaluate(value, conversionData, frame, MinPrefix);
654}
655
656static bool maxResolutionEvaluate(CSSValue* value, const CSSToLengthConversionData& conversionData, Frame& frame, MediaFeaturePrefix)
657{
658 return resolutionEvaluate(value, conversionData, frame, MaxPrefix);
659}
660
661static bool animationEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame&, MediaFeaturePrefix op)
662{
663 return oneEvaluate(value, op);
664}
665
666static bool transitionEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame&, MediaFeaturePrefix op)
667{
668 return oneEvaluate(value, op);
669}
670
671static bool transform2dEvaluate(CSSValue* value, const CSSToLengthConversionData&, Frame&, MediaFeaturePrefix op)
672{
673 return oneEvaluate(value, op);
674}
675
676static 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
687static bool videoPlayableInlineEvaluate(CSSValue*, const CSSToLengthConversionData&, Frame& frame, MediaFeaturePrefix)
688{
689 return frame.settings().allowsInlineMediaPlayback();
690}
691
692static 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
710static bool anyHoverEvaluate(CSSValue* value, const CSSToLengthConversionData& cssToLengthConversionData, Frame& frame, MediaFeaturePrefix prefix)
711{
712 return hoverEvaluate(value, cssToLengthConversionData, frame, prefix);
713}
714
715static 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
728static bool anyPointerEvaluate(CSSValue* value, const CSSToLengthConversionData& cssToLengthConversionData, Frame& frame, MediaFeaturePrefix prefix)
729{
730 return pointerEvaluate(value, cssToLengthConversionData, frame, prefix);
731}
732
733static 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)
747static 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
773static 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)
797static 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.
824static void add(MediaQueryFunctionMap& map, AtomicStringImpl* key, MediaQueryFunction value)
825{
826 map.add(key, value);
827}
828
829bool 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
859bool MediaQueryEvaluator::mediaAttributeMatches(Document& document, const String& attributeValue)
860{
861 ASSERT(document.renderView());
862 auto mediaQueries = MediaQuerySet::create(attributeValue, MediaQueryParserContext(document));
863 return MediaQueryEvaluator { "screen", document, &document.renderView()->style() }.evaluate(mediaQueries.get());
864}
865
866} // WebCore
867