| /* |
| * Copyright (C) 2006 Kimmo Kinnunen <kimmo.t.kinnunen@nokia.com>. |
| * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). |
| * Copyright (C) 2013, 2015 Apple Inc. 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 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 "config.h" |
| #include "MediaQueryExpression.h" |
| |
| #include "CSSAspectRatioValue.h" |
| #include "CSSPrimitiveValue.h" |
| #include "CSSPropertyParserHelpers.h" |
| #include "MediaFeatureNames.h" |
| #include "MediaQueryParserContext.h" |
| #include <wtf/text/TextStream.h> |
| |
| namespace WebCore { |
| |
| static inline bool isValidValueForIdentMediaFeature(const AtomString& feature, const CSSPrimitiveValue& value) |
| { |
| auto valueID = value.valueID(); |
| |
| if (feature == MediaFeatureNames::orientation) |
| return valueID == CSSValuePortrait || valueID == CSSValueLandscape; |
| if (feature == MediaFeatureNames::colorGamut) |
| return valueID == CSSValueSRGB || valueID == CSSValueP3 || valueID == CSSValueRec2020; |
| if (feature == MediaFeatureNames::anyHover || feature == MediaFeatureNames::hover) |
| return valueID == CSSValueHover || valueID == CSSValueNone; |
| if (feature == MediaFeatureNames::anyPointer || feature == MediaFeatureNames::pointer) |
| return valueID == CSSValueFine || valueID == CSSValueCoarse || valueID == CSSValueNone; |
| if (feature == MediaFeatureNames::invertedColors) |
| return valueID == CSSValueInverted || valueID == CSSValueNone; |
| #if ENABLE(APPLICATION_MANIFEST) |
| if (feature == MediaFeatureNames::displayMode) |
| return valueID == CSSValueFullscreen || valueID == CSSValueStandalone || valueID == CSSValueMinimalUi || valueID == CSSValueBrowser; |
| #endif |
| #if ENABLE(DARK_MODE_CSS) |
| if (feature == MediaFeatureNames::prefersColorScheme) |
| return valueID == CSSValueLight || valueID == CSSValueDark; |
| #endif |
| if (feature == MediaFeatureNames::prefersContrast) |
| return valueID == CSSValueNoPreference || valueID == CSSValueMore || valueID == CSSValueLess; |
| if (feature == MediaFeatureNames::prefersReducedMotion) |
| return valueID == CSSValueNoPreference || valueID == CSSValueReduce; |
| if (feature == MediaFeatureNames::prefersDarkInterface) |
| return valueID == CSSValuePrefers || valueID == CSSValueNoPreference; |
| if (feature == MediaFeatureNames::dynamicRange) |
| return valueID == CSSValueHigh || valueID == CSSValueStandard; |
| |
| return false; |
| } |
| |
| static inline bool featureWithValidIdent(const AtomString& mediaFeature, const CSSPrimitiveValue& value, const MediaQueryParserContext& context) |
| { |
| if (value.primitiveType() != CSSUnitType::CSS_IDENT || !isValidValueForIdentMediaFeature(mediaFeature, value)) |
| return false; |
| |
| return mediaFeature == MediaFeatureNames::orientation |
| || mediaFeature == MediaFeatureNames::colorGamut |
| || mediaFeature == MediaFeatureNames::anyHover |
| || mediaFeature == MediaFeatureNames::anyPointer |
| || mediaFeature == MediaFeatureNames::hover |
| || mediaFeature == MediaFeatureNames::invertedColors |
| || mediaFeature == MediaFeatureNames::pointer |
| #if ENABLE(APPLICATION_MANIFEST) |
| || mediaFeature == MediaFeatureNames::displayMode |
| #endif |
| #if ENABLE(DARK_MODE_CSS) |
| || mediaFeature == MediaFeatureNames::prefersColorScheme |
| #endif |
| || mediaFeature == MediaFeatureNames::prefersContrast |
| || mediaFeature == MediaFeatureNames::prefersReducedMotion |
| || (mediaFeature == MediaFeatureNames::prefersDarkInterface && (context.useSystemAppearance || isUASheetBehavior(context.mode))) |
| || mediaFeature == MediaFeatureNames::dynamicRange; |
| } |
| |
| static inline bool featureWithValidDensity(const String& mediaFeature, const CSSPrimitiveValue& value) |
| { |
| if (!value.isResolution() || value.doubleValue() <= 0) |
| return false; |
| |
| return mediaFeature == MediaFeatureNames::resolution |
| || mediaFeature == MediaFeatureNames::minResolution |
| || mediaFeature == MediaFeatureNames::maxResolution; |
| } |
| |
| static inline bool featureWithValidPositiveLength(const String& mediaFeature, const CSSPrimitiveValue& value) |
| { |
| if (!(value.isLength() || (value.isNumberOrInteger() && !value.doubleValue())) || value.doubleValue() < 0) |
| return false; |
| |
| return mediaFeature == MediaFeatureNames::height |
| || mediaFeature == MediaFeatureNames::maxHeight |
| || mediaFeature == MediaFeatureNames::minHeight |
| || mediaFeature == MediaFeatureNames::width |
| || mediaFeature == MediaFeatureNames::maxWidth |
| || mediaFeature == MediaFeatureNames::minWidth |
| || mediaFeature == MediaFeatureNames::deviceHeight |
| || mediaFeature == MediaFeatureNames::maxDeviceHeight |
| || mediaFeature == MediaFeatureNames::minDeviceHeight |
| || mediaFeature == MediaFeatureNames::deviceWidth |
| || mediaFeature == MediaFeatureNames::minDeviceWidth |
| || mediaFeature == MediaFeatureNames::maxDeviceWidth; |
| } |
| |
| static inline bool featureExpectingPositiveInteger(const String& mediaFeature) |
| { |
| return mediaFeature == MediaFeatureNames::color |
| || mediaFeature == MediaFeatureNames::maxColor |
| || mediaFeature == MediaFeatureNames::minColor |
| || mediaFeature == MediaFeatureNames::colorIndex |
| || mediaFeature == MediaFeatureNames::maxColorIndex |
| || mediaFeature == MediaFeatureNames::minColorIndex |
| || mediaFeature == MediaFeatureNames::monochrome |
| || mediaFeature == MediaFeatureNames::maxMonochrome |
| || mediaFeature == MediaFeatureNames::minMonochrome; |
| } |
| |
| static inline bool featureWithPositiveInteger(const String& mediaFeature, const CSSPrimitiveValue& value) |
| { |
| if (!value.isInteger()) |
| return false; |
| return featureExpectingPositiveInteger(mediaFeature); |
| } |
| |
| static inline bool featureWithPositiveNumber(const String& mediaFeature, const CSSPrimitiveValue& value) |
| { |
| if (!value.isNumberOrInteger()) |
| return false; |
| |
| return mediaFeature == MediaFeatureNames::transform3d |
| || mediaFeature == MediaFeatureNames::devicePixelRatio |
| || mediaFeature == MediaFeatureNames::maxDevicePixelRatio |
| || mediaFeature == MediaFeatureNames::minDevicePixelRatio |
| || mediaFeature == MediaFeatureNames::transition |
| || mediaFeature == MediaFeatureNames::animation |
| || mediaFeature == MediaFeatureNames::transform2d; |
| } |
| |
| static inline bool featureWithZeroOrOne(const String& mediaFeature, const CSSPrimitiveValue& value) |
| { |
| if (!value.isNumberOrInteger() || !(value.doubleValue() == 1 || !value.doubleValue())) |
| return false; |
| |
| return mediaFeature == MediaFeatureNames::grid; |
| } |
| |
| static inline bool isAspectRatioFeature(const AtomString& mediaFeature) |
| { |
| return mediaFeature == MediaFeatureNames::aspectRatio |
| || mediaFeature == MediaFeatureNames::deviceAspectRatio |
| || mediaFeature == MediaFeatureNames::minAspectRatio |
| || mediaFeature == MediaFeatureNames::maxAspectRatio |
| || mediaFeature == MediaFeatureNames::minDeviceAspectRatio |
| || mediaFeature == MediaFeatureNames::maxDeviceAspectRatio; |
| } |
| |
| static inline bool isFeatureValidWithoutValue(const AtomString& mediaFeature, const MediaQueryParserContext& context) |
| { |
| // Media features that are prefixed by min/max cannot be used without a value. |
| return mediaFeature == MediaFeatureNames::anyHover |
| || mediaFeature == MediaFeatureNames::anyPointer |
| || mediaFeature == MediaFeatureNames::monochrome |
| || mediaFeature == MediaFeatureNames::color |
| || mediaFeature == MediaFeatureNames::colorIndex |
| || mediaFeature == MediaFeatureNames::grid |
| || mediaFeature == MediaFeatureNames::height |
| || mediaFeature == MediaFeatureNames::width |
| || mediaFeature == MediaFeatureNames::deviceHeight |
| || mediaFeature == MediaFeatureNames::deviceWidth |
| || mediaFeature == MediaFeatureNames::orientation |
| || mediaFeature == MediaFeatureNames::aspectRatio |
| || mediaFeature == MediaFeatureNames::deviceAspectRatio |
| || mediaFeature == MediaFeatureNames::hover |
| || mediaFeature == MediaFeatureNames::transform2d |
| || mediaFeature == MediaFeatureNames::transform3d |
| || mediaFeature == MediaFeatureNames::transition |
| || mediaFeature == MediaFeatureNames::animation |
| || mediaFeature == MediaFeatureNames::invertedColors |
| || mediaFeature == MediaFeatureNames::pointer |
| || mediaFeature == MediaFeatureNames::prefersContrast |
| || mediaFeature == MediaFeatureNames::prefersReducedMotion |
| || (mediaFeature == MediaFeatureNames::prefersDarkInterface && (context.useSystemAppearance || isUASheetBehavior(context.mode))) |
| #if ENABLE(DARK_MODE_CSS) |
| || (mediaFeature == MediaFeatureNames::prefersColorScheme) |
| #endif |
| || mediaFeature == MediaFeatureNames::devicePixelRatio |
| || mediaFeature == MediaFeatureNames::resolution |
| #if ENABLE(APPLICATION_MANIFEST) |
| || mediaFeature == MediaFeatureNames::displayMode |
| #endif |
| || mediaFeature == MediaFeatureNames::videoPlayableInline; |
| } |
| |
| inline RefPtr<CSSPrimitiveValue> consumeFirstValue(const String& mediaFeature, CSSParserTokenRange& range) |
| { |
| if (auto value = CSSPropertyParserHelpers::consumeIntegerZeroAndGreater(range)) |
| return value; |
| |
| if (!featureExpectingPositiveInteger(mediaFeature) && !isAspectRatioFeature(mediaFeature)) { |
| if (auto value = CSSPropertyParserHelpers::consumeNumber(range, ValueRange::NonNegative)) |
| return value; |
| } |
| |
| if (auto value = CSSPropertyParserHelpers::consumeLength(range, HTMLStandardMode, ValueRange::NonNegative)) |
| return value; |
| |
| if (auto value = CSSPropertyParserHelpers::consumeResolution(range)) |
| return value; |
| |
| if (auto value = CSSPropertyParserHelpers::consumeIdent(range)) |
| return value; |
| |
| return nullptr; |
| } |
| |
| MediaQueryExpression::MediaQueryExpression(const String& feature, CSSParserTokenRange& range, MediaQueryParserContext& context) |
| : m_mediaFeature(feature.convertToASCIILowercase()) |
| , m_isValid(false) |
| { |
| RefPtr<CSSPrimitiveValue> firstValue = consumeFirstValue(m_mediaFeature, range); |
| if (!firstValue) { |
| if (isFeatureValidWithoutValue(m_mediaFeature, context)) { |
| // Valid, creates a MediaQueryExp with an 'invalid' MediaQueryExpValue |
| m_isValid = true; |
| } |
| return; |
| } |
| // Create value for media query expression that must have 1 or more values. |
| if (isAspectRatioFeature(m_mediaFeature)) { |
| if (!firstValue->isNumberOrInteger() || !firstValue->doubleValue()) |
| return; |
| if (!CSSPropertyParserHelpers::consumeSlashIncludingWhitespace(range)) |
| return; |
| auto denominatorValue = CSSPropertyParserHelpers::consumePositiveIntegerRaw(range); |
| if (!denominatorValue) |
| return; |
| |
| unsigned numerator = clampTo<unsigned>(firstValue->doubleValue()); |
| m_value = CSSAspectRatioValue::create(numerator, *denominatorValue); |
| m_isValid = true; |
| return; |
| } |
| if (featureWithPositiveInteger(m_mediaFeature, *firstValue) || featureWithPositiveNumber(m_mediaFeature, *firstValue) |
| || featureWithZeroOrOne(m_mediaFeature, *firstValue) || featureWithValidDensity(m_mediaFeature, *firstValue) |
| || featureWithValidPositiveLength(m_mediaFeature, *firstValue) || featureWithValidIdent(m_mediaFeature, *firstValue, context)) { |
| m_value = firstValue; |
| m_isValid = true; |
| return; |
| } |
| } |
| |
| String MediaQueryExpression::serialize() const |
| { |
| if (m_serializationCache.isNull()) |
| m_serializationCache = makeString('(', m_mediaFeature.convertToASCIILowercase(), m_value ? ": " : "", m_value ? m_value->cssText() : "", ')'); |
| return m_serializationCache; |
| } |
| |
| TextStream& operator<<(TextStream& ts, const MediaQueryExpression& expression) |
| { |
| ts << expression.serialize(); |
| return ts; |
| } |
| |
| |
| } // namespace |