source: webkit/trunk/Source/WebCore/css/MediaQueryEvaluator.cpp

Last change on this file was 293700, checked in by Alan Bujtas, 3 years ago

[Quirks] REGRESSION (r286874): Menu is Cut Off After Pressing Search Icon on Hotels.com
https://bugs.webkit.org/show_bug.cgi?id=239980
<rdar://91992835>

Reviewed by Simon Fraser.

Disable resolution media query on www.hotels.com.

  • css/MediaQueryEvaluator.cpp:

(WebCore::resolutionEvaluate):

  • page/Quirks.cpp:

(WebCore::Quirks::shouldUseResolutionMedia const):

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