/* * CSS Media Query Evaluator * * Copyright (C) 2006 Kimmo Kinnunen . * Copyright (C) 2013 Apple Inc. All rights reserved. * Copyright (C) 2013 Intel Corporation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "sky/engine/config.h" #include "sky/engine/core/css/MediaQueryEvaluator.h" #include "gen/sky/core/CSSValueKeywords.h" #include "gen/sky/core/MediaFeatureNames.h" #include "gen/sky/core/MediaFeatures.h" #include "gen/sky/core/MediaTypeNames.h" #include "gen/sky/platform/RuntimeEnabledFeatures.h" #include "sky/engine/core/css/CSSAspectRatioValue.h" #include "sky/engine/core/css/CSSHelper.h" #include "sky/engine/core/css/CSSPrimitiveValue.h" #include "sky/engine/core/css/CSSToLengthConversionData.h" #include "sky/engine/core/css/MediaList.h" #include "sky/engine/core/css/MediaQuery.h" #include "sky/engine/core/css/MediaValuesDynamic.h" #include "sky/engine/core/css/PointerProperties.h" #include "sky/engine/core/css/resolver/MediaQueryResult.h" #include "sky/engine/core/dom/NodeRenderStyle.h" #include "sky/engine/core/frame/FrameHost.h" #include "sky/engine/core/frame/FrameView.h" #include "sky/engine/core/frame/LocalFrame.h" #include "sky/engine/core/frame/Settings.h" #include "sky/engine/core/frame/UseCounter.h" #include "sky/engine/core/rendering/RenderView.h" #include "sky/engine/core/rendering/style/RenderStyle.h" #include "sky/engine/platform/PlatformScreen.h" #include "sky/engine/platform/geometry/FloatRect.h" #include "sky/engine/wtf/HashMap.h" namespace blink { using namespace MediaFeatureNames; enum MediaFeaturePrefix { MinPrefix, MaxPrefix, NoPrefix }; typedef bool (*EvalFunc)(const MediaQueryExpValue&, MediaFeaturePrefix, const MediaValues&); typedef HashMap FunctionMap; static FunctionMap* gFunctionMap; MediaQueryEvaluator::MediaQueryEvaluator(bool mediaFeatureResult) : m_expectedResult(mediaFeatureResult) { } MediaQueryEvaluator::MediaQueryEvaluator(const char* acceptedMediaType, bool mediaFeatureResult) : m_mediaType(acceptedMediaType) , m_expectedResult(mediaFeatureResult) { } MediaQueryEvaluator::MediaQueryEvaluator(LocalFrame* frame) : m_expectedResult(false) // Doesn't matter when we have m_frame and m_style. , m_mediaValues(MediaValues::createDynamicIfFrameExists(frame)) { } MediaQueryEvaluator::MediaQueryEvaluator(const MediaValues& mediaValues) : m_expectedResult(false) // Doesn't matter when we have mediaValues. , m_mediaValues(mediaValues.copy()) { } MediaQueryEvaluator::~MediaQueryEvaluator() { } const String MediaQueryEvaluator::mediaType() const { // If a static mediaType was given by the constructor, we use it here. if (!m_mediaType.isEmpty()) return m_mediaType; // Otherwise, we get one from mediaValues (which may be dynamic or cached). if (m_mediaValues) return m_mediaValues->mediaType(); return nullAtom; } bool MediaQueryEvaluator::mediaTypeMatch(const String& mediaTypeToMatch) const { return mediaTypeToMatch.isEmpty() || equalIgnoringCase(mediaTypeToMatch, MediaTypeNames::all) || equalIgnoringCase(mediaTypeToMatch, mediaType()); } static bool applyRestrictor(MediaQuery::Restrictor r, bool value) { return r == MediaQuery::Not ? !value : value; } bool MediaQueryEvaluator::eval(const MediaQuery* query, MediaQueryResultList* viewportDependentMediaQueryResults) const { if (!mediaTypeMatch(query->mediaType())) return applyRestrictor(query->restrictor(), false); const ExpressionHeapVector& expressions = query->expressions(); // Iterate through expressions, stop if any of them eval to false (AND semantics). size_t i = 0; for (; i < expressions.size(); ++i) { bool exprResult = eval(expressions.at(i).get()); if (viewportDependentMediaQueryResults && expressions.at(i)->isViewportDependent()) viewportDependentMediaQueryResults->append(adoptRef(new MediaQueryResult(*expressions.at(i), exprResult))); if (!exprResult) break; } // Assume true if we are at the end of the list, otherwise assume false. return applyRestrictor(query->restrictor(), expressions.size() == i); } bool MediaQueryEvaluator::eval(const MediaQuerySet* querySet, MediaQueryResultList* viewportDependentMediaQueryResults) const { if (!querySet) return true; const Vector >& queries = querySet->queryVector(); if (!queries.size()) return true; // Empty query list evaluates to true. // Iterate over queries, stop if any of them eval to true (OR semantics). bool result = false; for (size_t i = 0; i < queries.size() && !result; ++i) result = eval(queries[i].get(), viewportDependentMediaQueryResults); return result; } template bool compareValue(T a, T b, MediaFeaturePrefix op) { switch (op) { case MinPrefix: return a >= b; case MaxPrefix: return a <= b; case NoPrefix: return a == b; } return false; } static bool compareAspectRatioValue(const MediaQueryExpValue& value, int width, int height, MediaFeaturePrefix op) { if (value.isRatio) return compareValue(width * static_cast(value.denominator), height * static_cast(value.numerator), op); return false; } static bool numberValue(const MediaQueryExpValue& value, float& result) { if (value.isValue && value.unit == CSSPrimitiveValue::CSS_NUMBER) { result = value.value; return true; } return false; } static bool colorMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues) { float number; int bitsPerComponent = mediaValues.colorBitsPerComponent(); if (value.isValid()) return numberValue(value, number) && compareValue(bitsPerComponent, static_cast(number), op); return bitsPerComponent != 0; } static bool colorIndexMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues&) { // FIXME: We currently assume that we do not support indexed displays, as it is unknown // how to retrieve the information if the display mode is indexed. This matches Firefox. if (!value.isValid()) return false; // Acording to spec, if the device does not use a color lookup table, the value is zero. float number; return numberValue(value, number) && compareValue(0, static_cast(number), op); } static bool monochromeMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues) { if (!mediaValues.monochromeBitsPerComponent()) { if (value.isValid()) { float number; return numberValue(value, number) && compareValue(0, static_cast(number), op); } return false; } return colorMediaFeatureEval(value, op, mediaValues); } static bool orientationMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { int width = mediaValues.viewportWidth(); int height = mediaValues.viewportHeight(); if (value.isID) { if (width > height) // Square viewport is portrait. return CSSValueLandscape == value.id; return CSSValuePortrait == value.id; } // Expression (orientation) evaluates to true if width and height >= 0. return height >= 0 && width >= 0; } static bool aspectRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues) { if (value.isValid()) return compareAspectRatioValue(value, mediaValues.viewportWidth(), mediaValues.viewportHeight(), op); // ({,min-,max-}aspect-ratio) // assume if we have a device, its aspect ratio is non-zero. return true; } static bool deviceAspectRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues) { if (value.isValid()) return compareAspectRatioValue(value, mediaValues.deviceWidth(), mediaValues.deviceHeight(), op); // ({,min-,max-}device-aspect-ratio) // assume if we have a device, its aspect ratio is non-zero. return true; } static bool evalResolution(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues) { // According to MQ4, only 'screen', 'print' and 'speech' may match. // FIXME: What should speech match? https://www.w3.org/Style/CSS/Tracker/issues/348 float actualResolution = 0; // This checks the actual media type applied to the document, and we know // this method only got called if this media type matches the one defined // in the query. Thus, if if the document's media type is "print", the // media type of the query will either be "print" or "all". if (equalIgnoringCase(mediaValues.mediaType(), MediaTypeNames::screen)) { actualResolution = clampTo(mediaValues.devicePixelRatio()); } else if (equalIgnoringCase(mediaValues.mediaType(), MediaTypeNames::print)) { // The resolution of images while printing should not depend on the DPI // of the screen. Until we support proper ways of querying this info // we use 300px which is considered minimum for current printers. actualResolution = 300 / cssPixelsPerInch; } if (!value.isValid()) return !!actualResolution; if (!value.isValue) return false; if (value.unit == CSSPrimitiveValue::CSS_NUMBER) return compareValue(actualResolution, clampTo(value.value), op); if (!CSSPrimitiveValue::isResolution(value.unit)) return false; double canonicalFactor = CSSPrimitiveValue::conversionToCanonicalUnitsScaleFactor(value.unit); double dppxFactor = CSSPrimitiveValue::conversionToCanonicalUnitsScaleFactor(CSSPrimitiveValue::CSS_DPPX); float valueInDppx = clampTo(value.value * (canonicalFactor / dppxFactor)); if (CSSPrimitiveValue::isDotsPerCentimeter(value.unit)) { // To match DPCM to DPPX values, we limit to 2 decimal points. // The http://dev.w3.org/csswg/css3-values/#absolute-lengths recommends // "that the pixel unit refer to the whole number of device pixels that best // approximates the reference pixel". With that in mind, allowing 2 decimal // point precision seems appropriate. return compareValue( floorf(0.5 + 100 * actualResolution) / 100, floorf(0.5 + 100 * valueInDppx) / 100, op); } return compareValue(actualResolution, valueInDppx, op); } static bool devicePixelRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues) { UseCounter::count(mediaValues.document(), UseCounter::PrefixedDevicePixelRatioMediaFeature); return (!value.isValid() || value.unit == CSSPrimitiveValue::CSS_NUMBER) && evalResolution(value, op, mediaValues); } static bool resolutionMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& MediaValues) { return (!value.isValid() || CSSPrimitiveValue::isResolution(value.unit)) && evalResolution(value, op, MediaValues); } static bool computeLength(const MediaQueryExpValue& value, const MediaValues& mediaValues, int& result) { if (!value.isValue) return false; if (value.unit == CSSPrimitiveValue::CSS_NUMBER) { result = clampTo(value.value); return !mediaValues.strictMode() || !result; } if (CSSPrimitiveValue::isLength(value.unit)) return mediaValues.computeLength(value.value, value.unit, result); return false; } static bool deviceHeightMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues) { if (value.isValid()) { int length; return computeLength(value, mediaValues, length) && compareValue(static_cast(mediaValues.deviceHeight()), length, op); } // ({,min-,max-}device-height) // assume if we have a device, assume non-zero return true; } static bool deviceWidthMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues) { if (value.isValid()) { int length; return computeLength(value, mediaValues, length) && compareValue(static_cast(mediaValues.deviceWidth()), length, op); } // ({,min-,max-}device-width) // assume if we have a device, assume non-zero return true; } static bool heightMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues) { int height = mediaValues.viewportHeight(); if (value.isValid()) { int length; return computeLength(value, mediaValues, length) && compareValue(height, length, op); } return height; } static bool widthMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues) { int width = mediaValues.viewportWidth(); if (value.isValid()) { int length; return computeLength(value, mediaValues, length) && compareValue(width, length, op); } return width; } // Rest of the functions are trampolines which set the prefix according to the media feature expression used. static bool minColorMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return colorMediaFeatureEval(value, MinPrefix, mediaValues); } static bool maxColorMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return colorMediaFeatureEval(value, MaxPrefix, mediaValues); } static bool minColorIndexMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return colorIndexMediaFeatureEval(value, MinPrefix, mediaValues); } static bool maxColorIndexMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return colorIndexMediaFeatureEval(value, MaxPrefix, mediaValues); } static bool minMonochromeMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return monochromeMediaFeatureEval(value, MinPrefix, mediaValues); } static bool maxMonochromeMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return monochromeMediaFeatureEval(value, MaxPrefix, mediaValues); } static bool minAspectRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return aspectRatioMediaFeatureEval(value, MinPrefix, mediaValues); } static bool maxAspectRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return aspectRatioMediaFeatureEval(value, MaxPrefix, mediaValues); } static bool minDeviceAspectRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return deviceAspectRatioMediaFeatureEval(value, MinPrefix, mediaValues); } static bool maxDeviceAspectRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return deviceAspectRatioMediaFeatureEval(value, MaxPrefix, mediaValues); } static bool minDevicePixelRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { UseCounter::count(mediaValues.document(), UseCounter::PrefixedMinDevicePixelRatioMediaFeature); return devicePixelRatioMediaFeatureEval(value, MinPrefix, mediaValues); } static bool maxDevicePixelRatioMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { UseCounter::count(mediaValues.document(), UseCounter::PrefixedMaxDevicePixelRatioMediaFeature); return devicePixelRatioMediaFeatureEval(value, MaxPrefix, mediaValues); } static bool minHeightMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return heightMediaFeatureEval(value, MinPrefix, mediaValues); } static bool maxHeightMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return heightMediaFeatureEval(value, MaxPrefix, mediaValues); } static bool minWidthMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return widthMediaFeatureEval(value, MinPrefix, mediaValues); } static bool maxWidthMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return widthMediaFeatureEval(value, MaxPrefix, mediaValues); } static bool minDeviceHeightMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return deviceHeightMediaFeatureEval(value, MinPrefix, mediaValues); } static bool maxDeviceHeightMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return deviceHeightMediaFeatureEval(value, MaxPrefix, mediaValues); } static bool minDeviceWidthMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return deviceWidthMediaFeatureEval(value, MinPrefix, mediaValues); } static bool maxDeviceWidthMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return deviceWidthMediaFeatureEval(value, MaxPrefix, mediaValues); } static bool minResolutionMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return resolutionMediaFeatureEval(value, MinPrefix, mediaValues); } static bool maxResolutionMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { return resolutionMediaFeatureEval(value, MaxPrefix, mediaValues); } static bool transform3dMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix op, const MediaValues& mediaValues) { UseCounter::count(mediaValues.document(), UseCounter::PrefixedTransform3dMediaFeature); bool returnValueIfNoParameter; int have3dRendering; bool threeDEnabled = mediaValues.threeDEnabled(); returnValueIfNoParameter = threeDEnabled; have3dRendering = threeDEnabled ? 1 : 0; if (value.isValid()) { float number; return numberValue(value, number) && compareValue(have3dRendering, static_cast(number), op); } return returnValueIfNoParameter; } static bool hoverMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { HoverType hover = mediaValues.primaryHoverType(); if (RuntimeEnabledFeatures::hoverMediaQueryKeywordsEnabled()) { if (!value.isValid()) return hover != HoverTypeNone; if (!value.isID) return false; return (hover == HoverTypeNone && value.id == CSSValueNone) || (hover == HoverTypeOnDemand && value.id == CSSValueOnDemand) || (hover == HoverTypeHover && value.id == CSSValueHover); } else { float number = 1; if (value.isValid()) { if (!numberValue(value, number)) return false; } return (hover == HoverTypeNone && !number) || (hover == HoverTypeOnDemand && !number) || (hover == HoverTypeHover && number == 1); } } static bool anyHoverMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { if (!RuntimeEnabledFeatures::anyPointerMediaQueriesEnabled()) return false; int availableHoverTypes = mediaValues.availableHoverTypes(); if (!value.isValid()) return availableHoverTypes & ~HoverTypeNone; if (!value.isID) return false; switch (value.id) { case CSSValueNone: return availableHoverTypes & HoverTypeNone; case CSSValueOnDemand: return availableHoverTypes & HoverTypeOnDemand; case CSSValueHover: return availableHoverTypes & HoverTypeHover; default: ASSERT_NOT_REACHED(); return false; } } static bool pointerMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { PointerType pointer = mediaValues.primaryPointerType(); if (!value.isValid()) return pointer != PointerTypeNone; if (!value.isID) return false; return (pointer == PointerTypeNone && value.id == CSSValueNone) || (pointer == PointerTypeCoarse && value.id == CSSValueCoarse) || (pointer == PointerTypeFine && value.id == CSSValueFine); } static bool anyPointerMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { if (!RuntimeEnabledFeatures::anyPointerMediaQueriesEnabled()) return false; int availablePointers = mediaValues.availablePointerTypes(); if (!value.isValid()) return availablePointers & ~PointerTypeNone; if (!value.isID) return false; switch (value.id) { case CSSValueCoarse: return availablePointers & PointerTypeCoarse; case CSSValueFine: return availablePointers & PointerTypeFine; case CSSValueNone: return availablePointers & PointerTypeNone; default: ASSERT_NOT_REACHED(); return false; } } static bool scanMediaFeatureEval(const MediaQueryExpValue& value, MediaFeaturePrefix, const MediaValues& mediaValues) { // Scan only applies to 'tv' media. if (!equalIgnoringCase(mediaValues.mediaType(), MediaTypeNames::tv)) return false; if (!value.isValid()) return true; if (!value.isID) return false; // If a platform interface supplies progressive/interlace info for TVs in the // future, it needs to be handled here. For now, assume a modern TV with // progressive display. return (value.id == CSSValueProgressive); } static void createFunctionMap() { // Create the table. gFunctionMap = new FunctionMap; #define ADD_TO_FUNCTIONMAP(name) \ gFunctionMap->set(name##MediaFeature.impl(), name##MediaFeatureEval); CSS_MEDIAQUERY_NAMES_FOR_EACH_MEDIAFEATURE(ADD_TO_FUNCTIONMAP); #undef ADD_TO_FUNCTIONMAP } bool MediaQueryEvaluator::eval(const MediaQueryExp* expr) const { if (!m_mediaValues || !m_mediaValues->hasValues()) return m_expectedResult; if (!gFunctionMap) createFunctionMap(); // Call the media feature evaluation function. Assume no prefix and let // trampoline functions override the prefix if prefix is used. EvalFunc func = gFunctionMap->get(expr->mediaFeature().impl()); if (func) return func(expr->expValue(), NoPrefix, *m_mediaValues); return false; } } // namespace