2 * CSS Media Query Evaluator
4 * Copyright (C) 2006 Kimmo Kinnunen <kimmo.t.kinnunen@nokia.com>.
5 * Copyright (C) 2013 Apple Inc. All rights reserved.
6 * Copyright (C) 2013 Intel Corporation. All rights reserved.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
25 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #include "core/css/MediaQueryEvaluator.h"
33 #include "core/CSSValueKeywords.h"
34 #include "core/MediaFeatureNames.h"
35 #include "core/MediaFeatures.h"
36 #include "core/MediaTypeNames.h"
37 #include "core/css/CSSAspectRatioValue.h"
38 #include "core/css/CSSHelper.h"
39 #include "core/css/CSSPrimitiveValue.h"
40 #include "core/css/CSSToLengthConversionData.h"
41 #include "core/css/MediaList.h"
42 #include "core/css/MediaQuery.h"
43 #include "core/css/MediaValuesDynamic.h"
44 #include "core/css/resolver/MediaQueryResult.h"
45 #include "core/dom/NodeRenderStyle.h"
46 #include "core/frame/FrameHost.h"
47 #include "core/frame/FrameView.h"
48 #include "core/frame/LocalFrame.h"
49 #include "core/frame/Settings.h"
50 #include "core/inspector/InspectorInstrumentation.h"
51 #include "core/rendering/RenderView.h"
52 #include "core/rendering/compositing/RenderLayerCompositor.h"
53 #include "core/rendering/style/RenderStyle.h"
54 #include "platform/PlatformScreen.h"
55 #include "platform/geometry/FloatRect.h"
56 #include "wtf/HashMap.h"
60 using namespace MediaFeatureNames;
62 enum MediaFeaturePrefix { MinPrefix, MaxPrefix, NoPrefix };
64 typedef bool (*EvalFunc)(const MediaQueryExpValue&, MediaFeaturePrefix, const MediaValues&);
65 typedef HashMap<StringImpl*, EvalFunc> FunctionMap;
66 static FunctionMap* gFunctionMap;
68 MediaQueryEvaluator::MediaQueryEvaluator(bool mediaFeatureResult)
69 : m_expectedResult(mediaFeatureResult)
73 MediaQueryEvaluator::MediaQueryEvaluator(const String& acceptedMediaType, bool mediaFeatureResult)
74 : m_mediaType(acceptedMediaType)
75 , m_expectedResult(mediaFeatureResult)
79 MediaQueryEvaluator::MediaQueryEvaluator(const char* acceptedMediaType, bool mediaFeatureResult)
80 : m_mediaType(acceptedMediaType)
81 , m_expectedResult(mediaFeatureResult)
85 MediaQueryEvaluator::MediaQueryEvaluator(const String& acceptedMediaType, LocalFrame* frame)
86 : m_mediaType(acceptedMediaType)
87 , m_expectedResult(false) // Doesn't matter when we have m_frame and m_style.
88 , m_mediaValues(MediaValues::createDynamicIfFrameExists(frame))
92 MediaQueryEvaluator::MediaQueryEvaluator(const String& acceptedMediaType, const MediaValues& mediaValues)
93 : m_mediaType(acceptedMediaType)
94 , m_expectedResult(false) // Doesn't matter when we have mediaValues.
95 , m_mediaValues(mediaValues.copy())
99 MediaQueryEvaluator::~MediaQueryEvaluator()
103 bool MediaQueryEvaluator::mediaTypeMatch(const String& mediaTypeToMatch) const
105 return mediaTypeToMatch.isEmpty()
106 || equalIgnoringCase(mediaTypeToMatch, MediaTypeNames::all)
107 || equalIgnoringCase(mediaTypeToMatch, m_mediaType);
110 bool MediaQueryEvaluator::mediaTypeMatchSpecific(const char* mediaTypeToMatch) const
112 // Like mediaTypeMatch, but without the special cases for "" and "all".
113 ASSERT(mediaTypeToMatch);
114 ASSERT(mediaTypeToMatch[0] != '\0');
115 ASSERT(!equalIgnoringCase(mediaTypeToMatch, MediaTypeNames::all));
116 return equalIgnoringCase(mediaTypeToMatch, m_mediaType);
119 static bool applyRestrictor(MediaQuery::Restrictor r, bool value)
121 return r == MediaQuery::Not ? !value : value;
124 bool MediaQueryEvaluator::eval(const MediaQuerySet* querySet, MediaQueryResultList* viewportDependentMediaQueryResults) const
129 const WillBeHeapVector<OwnPtrWillBeMember<MediaQuery> >& queries = querySet->queryVector();
131 return true; // Empty query list evaluates to true.
133 // Iterate over queries, stop if any of them eval to true (OR semantics).
135 for (size_t i = 0; i < queries.size() && !result; ++i) {
136 MediaQuery* query = queries[i].get();
138 if (mediaTypeMatch(query->mediaType())) {
139 const ExpressionHeapVector& expressions = query->expressions();
140 // Iterate through expressions, stop if any of them eval to false (AND semantics).
142 for (; j < expressions.size(); ++j) {
143 bool exprResult = eval(expressions.at(j).get());
144 if (viewportDependentMediaQueryResults && expressions.at(j)->isViewportDependent())
145 viewportDependentMediaQueryResults->append(adoptRefWillBeNoop(new MediaQueryResult(*expressions.at(j), exprResult)));
150 // Assume true if we are at the end of the list, otherwise assume false.
151 result = applyRestrictor(query->restrictor(), expressions.size() == j);
153 result = applyRestrictor(query->restrictor(), false);
161 bool compareValue(T a, T b, MediaFeaturePrefix op)
174 static bool compareAspectRatioValue(const MediaQueryExpValue& value, int width, int height, MediaFeaturePrefix op)
177 return compareValue(width * static_cast<int>(value.denominator), height * static_cast<int>(value.numerator), op);
182 static bool numberValue(const MediaQueryExpValue& value, float& result)
184 if (value.isValue && value.unit == CSSPrimitiveValue::CSS_NUMBER) {
185 result = value.value;
191 static bool colorMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues)
194 int bitsPerComponent = mediaValues.colorBitsPerComponent();
196 return numberValue(value, number) && compareValue(bitsPerComponent, static_cast<int>(number), op);
198 return bitsPerComponent != 0;
201 static bool colorIndexMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues&)
203 // FIXME: We currently assume that we do not support indexed displays, as it is unknown
204 // how to retrieve the information if the display mode is indexed. This matches Firefox.
205 if (!value.isValid())
208 // Acording to spec, if the device does not use a color lookup table, the value is zero.
210 return numberValue(value, number) && compareValue(0, static_cast<int>(number), op);
213 static bool monochromeMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues)
215 if (!mediaValues.monochromeBitsPerComponent()) {
216 if (value.isValid()) {
218 return numberValue(value, number) && compareValue(0, static_cast<int>(number), op);
223 return colorMediaFeatureEval(value, op, mediaValues);
226 static bool orientationMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
228 int width = mediaValues.viewportWidth();
229 int height = mediaValues.viewportHeight();
232 if (width > height) // Square viewport is portrait.
233 return CSSValueLandscape == value.id;
234 return CSSValuePortrait == value.id;
237 // Expression (orientation) evaluates to true if width and height >= 0.
238 return height >= 0 && width >= 0;
241 static bool aspectRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues)
244 return compareAspectRatioValue(value, mediaValues.viewportWidth(), mediaValues.viewportHeight(), op);
246 // ({,min-,max-}aspect-ratio)
247 // assume if we have a device, its aspect ratio is non-zero.
251 static bool deviceAspectRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues)
254 return compareAspectRatioValue(value, mediaValues.deviceWidth(), mediaValues.deviceHeight(), op);
256 // ({,min-,max-}device-aspect-ratio)
257 // assume if we have a device, its aspect ratio is non-zero.
261 static bool evalResolution(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues)
263 // According to MQ4, only 'screen', 'print' and 'speech' may match.
264 // FIXME: What should speech match? https://www.w3.org/Style/CSS/Tracker/issues/348
265 float actualResolution = 0;
267 // This checks the actual media type applied to the document, and we know
268 // this method only got called if this media type matches the one defined
269 // in the query. Thus, if if the document's media type is "print", the
270 // media type of the query will either be "print" or "all".
271 if (mediaValues.screenMediaType()) {
272 actualResolution = clampTo<float>(mediaValues.devicePixelRatio());
273 } else if (mediaValues.printMediaType()) {
274 // The resolution of images while printing should not depend on the DPI
275 // of the screen. Until we support proper ways of querying this info
276 // we use 300px which is considered minimum for current printers.
277 actualResolution = 300 / cssPixelsPerInch;
280 if (!value.isValid())
281 return !!actualResolution;
286 if (value.unit == CSSPrimitiveValue::CSS_NUMBER)
287 return compareValue(actualResolution, clampTo<float>(value.value), op);
289 if (!CSSPrimitiveValue::isResolution(value.unit))
292 double canonicalFactor = CSSPrimitiveValue::conversionToCanonicalUnitsScaleFactor(value.unit);
293 double dppxFactor = CSSPrimitiveValue::conversionToCanonicalUnitsScaleFactor(CSSPrimitiveValue::CSS_DPPX);
294 float valueInDppx = clampTo<float>(value.value * (canonicalFactor / dppxFactor));
295 if (CSSPrimitiveValue::isDotsPerCentimeter(value.unit)) {
296 // To match DPCM to DPPX values, we limit to 2 decimal points.
297 // The http://dev.w3.org/csswg/css3-values/#absolute-lengths recommends
298 // "that the pixel unit refer to the whole number of device pixels that best
299 // approximates the reference pixel". With that in mind, allowing 2 decimal
300 // point precision seems appropriate.
302 floorf(0.5 + 100 * actualResolution) / 100,
303 floorf(0.5 + 100 * valueInDppx) / 100, op);
306 return compareValue(actualResolution, valueInDppx, op);
309 static bool devicePixelRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues)
311 UseCounter::count(mediaValues.document(), UseCounter::PrefixedDevicePixelRatioMediaFeature);
313 return (!value.isValid() || value.unit == CSSPrimitiveValue::CSS_NUMBER) && evalResolution(value, op, mediaValues);
316 static bool resolutionMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& MediaValues)
318 return (!value.isValid() || CSSPrimitiveValue::isResolution(value.unit)) && evalResolution(value, op, MediaValues);
321 static bool gridMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues&)
323 // if output device is bitmap, grid: 0 == true
324 // assume we have bitmap device
326 if (value.isValid() && numberValue(value, number))
327 return compareValue(static_cast<int>(number), 0, op);
331 static bool computeLength(const MediaQueryExpValue& value, const MediaValues& mediaValues, int& result)
336 if (value.unit == CSSPrimitiveValue::CSS_NUMBER) {
337 result = clampTo<int>(value.value);
338 return !mediaValues.strictMode() || !result;
341 if (CSSPrimitiveValue::isLength(value.unit))
342 return mediaValues.computeLength(value.value, value.unit, result);
346 static bool deviceHeightMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues)
348 if (value.isValid()) {
350 return computeLength(value, mediaValues, length) && compareValue(static_cast<int>(mediaValues.deviceHeight()), length, op);
352 // ({,min-,max-}device-height)
353 // assume if we have a device, assume non-zero
357 static bool deviceWidthMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues)
359 if (value.isValid()) {
361 return computeLength(value, mediaValues, length) && compareValue(static_cast<int>(mediaValues.deviceWidth()), length, op);
363 // ({,min-,max-}device-width)
364 // assume if we have a device, assume non-zero
368 static bool heightMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues)
370 int height = mediaValues.viewportHeight();
371 if (value.isValid()) {
373 return computeLength(value, mediaValues, length) && compareValue(height, length, op);
379 static bool widthMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues)
381 int width = mediaValues.viewportWidth();
382 if (value.isValid()) {
384 return computeLength(value, mediaValues, length) && compareValue(width, length, op);
390 // Rest of the functions are trampolines which set the prefix according to the media feature expression used.
392 static bool minColorMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
394 return colorMediaFeatureEval(value, MinPrefix, mediaValues);
397 static bool maxColorMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
399 return colorMediaFeatureEval(value, MaxPrefix, mediaValues);
402 static bool minColorIndexMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
404 return colorIndexMediaFeatureEval(value, MinPrefix, mediaValues);
407 static bool maxColorIndexMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
409 return colorIndexMediaFeatureEval(value, MaxPrefix, mediaValues);
412 static bool minMonochromeMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
414 return monochromeMediaFeatureEval(value, MinPrefix, mediaValues);
417 static bool maxMonochromeMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
419 return monochromeMediaFeatureEval(value, MaxPrefix, mediaValues);
422 static bool minAspectRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
424 return aspectRatioMediaFeatureEval(value, MinPrefix, mediaValues);
427 static bool maxAspectRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
429 return aspectRatioMediaFeatureEval(value, MaxPrefix, mediaValues);
432 static bool minDeviceAspectRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
434 return deviceAspectRatioMediaFeatureEval(value, MinPrefix, mediaValues);
437 static bool maxDeviceAspectRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
439 return deviceAspectRatioMediaFeatureEval(value, MaxPrefix, mediaValues);
442 static bool minDevicePixelRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
444 UseCounter::count(mediaValues.document(), UseCounter::PrefixedMinDevicePixelRatioMediaFeature);
446 return devicePixelRatioMediaFeatureEval(value, MinPrefix, mediaValues);
449 static bool maxDevicePixelRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
451 UseCounter::count(mediaValues.document(), UseCounter::PrefixedMaxDevicePixelRatioMediaFeature);
453 return devicePixelRatioMediaFeatureEval(value, MaxPrefix, mediaValues);
456 static bool minHeightMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
458 return heightMediaFeatureEval(value, MinPrefix, mediaValues);
461 static bool maxHeightMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
463 return heightMediaFeatureEval(value, MaxPrefix, mediaValues);
466 static bool minWidthMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
468 return widthMediaFeatureEval(value, MinPrefix, mediaValues);
471 static bool maxWidthMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
473 return widthMediaFeatureEval(value, MaxPrefix, mediaValues);
476 static bool minDeviceHeightMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
478 return deviceHeightMediaFeatureEval(value, MinPrefix, mediaValues);
481 static bool maxDeviceHeightMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
483 return deviceHeightMediaFeatureEval(value, MaxPrefix, mediaValues);
486 static bool minDeviceWidthMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
488 return deviceWidthMediaFeatureEval(value, MinPrefix, mediaValues);
491 static bool maxDeviceWidthMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
493 return deviceWidthMediaFeatureEval(value, MaxPrefix, mediaValues);
496 static bool minResolutionMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
498 return resolutionMediaFeatureEval(value, MinPrefix, mediaValues);
501 static bool maxResolutionMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
503 return resolutionMediaFeatureEval(value, MaxPrefix, mediaValues);
506 static bool transform3dMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues)
508 UseCounter::count(mediaValues.document(), UseCounter::PrefixedTransform3dMediaFeature);
510 bool returnValueIfNoParameter;
513 bool threeDEnabled = mediaValues.threeDEnabled();
515 returnValueIfNoParameter = threeDEnabled;
516 have3dRendering = threeDEnabled ? 1 : 0;
518 if (value.isValid()) {
520 return numberValue(value, number) && compareValue(have3dRendering, static_cast<int>(number), op);
522 return returnValueIfNoParameter;
525 static bool hoverMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
527 MediaValues::PointerDeviceType pointer = mediaValues.pointer();
529 // If we're on a port that hasn't explicitly opted into providing pointer device information
530 // (or otherwise can't be confident in the pointer hardware available), then behave exactly
531 // as if this feature feature isn't supported.
532 if (pointer == MediaValues::UnknownPointer)
536 if (value.isValid()) {
537 if (!numberValue(value, number))
541 return (pointer == MediaValues::NoPointer && !number)
542 || (pointer == MediaValues::TouchPointer && !number)
543 || (pointer == MediaValues::MousePointer && number == 1);
546 static bool pointerMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
548 MediaValues::PointerDeviceType pointer = mediaValues.pointer();
550 // If we're on a port that hasn't explicitly opted into providing pointer device information
551 // (or otherwise can't be confident in the pointer hardware available), then behave exactly
552 // as if this feature feature isn't supported.
553 if (pointer == MediaValues::UnknownPointer)
556 if (!value.isValid())
557 return pointer != MediaValues::NoPointer;
562 return (pointer == MediaValues::NoPointer && value.id == CSSValueNone)
563 || (pointer == MediaValues::TouchPointer && value.id == CSSValueCoarse)
564 || (pointer == MediaValues::MousePointer && value.id == CSSValueFine);
567 static bool scanMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues)
569 if (!mediaValues.scanMediaType())
572 if (!value.isValid())
578 // If a platform interface supplies progressive/interlace info for TVs in the
579 // future, it needs to be handled here. For now, assume a modern TV with
580 // progressive display.
581 return (value.id == CSSValueProgressive);
584 static void createFunctionMap()
587 gFunctionMap = new FunctionMap;
588 #define ADD_TO_FUNCTIONMAP(name) \
589 gFunctionMap->set(name##MediaFeature.impl(), name##MediaFeatureEval);
590 CSS_MEDIAQUERY_NAMES_FOR_EACH_MEDIAFEATURE(ADD_TO_FUNCTIONMAP);
591 #undef ADD_TO_FUNCTIONMAP
594 bool MediaQueryEvaluator::eval(const MediaQueryExp* expr) const
596 if (!m_mediaValues || !m_mediaValues->hasValues())
597 return m_expectedResult;
602 // Call the media feature evaluation function. Assume no prefix and let
603 // trampoline functions override the prefix if prefix is used.
604 EvalFunc func = gFunctionMap->get(expr->mediaFeature().impl());
606 return func(expr->expValue(), NoPrefix, *m_mediaValues);