/* * Copyright (C) 2006 Kimmo Kinnunen . * 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 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.isNumber() && !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.isNumber()) return false; return featureExpectingPositiveInteger(mediaFeature); } static inline bool featureWithPositiveNumber(const String& mediaFeature, const CSSPrimitiveValue& value) { if (!value.isNumber()) 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.isNumber() || !(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 consumeFirstValue(const String& mediaFeature, CSSParserTokenRange& range) { if (auto value = CSSPropertyParserHelpers::consumeInteger(range, 0)) 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 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->isNumber() || !firstValue->doubleValue()) return; if (!CSSPropertyParserHelpers::consumeSlashIncludingWhitespace(range)) return; auto denominatorValue = CSSPropertyParserHelpers::consumePositiveIntegerRaw(range); if (!denominatorValue) return; unsigned numerator = clampTo(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