289 lines
13 KiB
C++
289 lines
13 KiB
C++
/*
|
|
* 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.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<CSSPrimitiveValue> 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<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->isNumber() || !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
|