1/*
2 * Copyright (C) 2006 Kimmo Kinnunen <kimmo.t.kinnunen@nokia.com>.
3 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 * Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "config.h"
29#include "MediaQueryExpression.h"
30
31#include "CSSAspectRatioValue.h"
32#include "CSSPrimitiveValue.h"
33#include "CSSPropertyParserHelpers.h"
34#include "MediaFeatureNames.h"
35#include "MediaQueryParserContext.h"
36#include "RuntimeEnabledFeatures.h"
37#include <wtf/text/TextStream.h>
38#include <wtf/text/StringBuilder.h>
39
40namespace WebCore {
41
42static inline bool featureWithValidIdent(const AtomicString& mediaFeature, const CSSPrimitiveValue& value, const MediaQueryParserContext& context)
43{
44 if (value.primitiveType() != CSSPrimitiveValue::UnitType::CSS_IDENT)
45 return false;
46
47 return mediaFeature == MediaFeatureNames::orientation
48 || mediaFeature == MediaFeatureNames::colorGamut
49 || mediaFeature == MediaFeatureNames::anyHover
50 || mediaFeature == MediaFeatureNames::anyPointer
51 || mediaFeature == MediaFeatureNames::hover
52 || mediaFeature == MediaFeatureNames::invertedColors
53 || mediaFeature == MediaFeatureNames::pointer
54#if ENABLE(APPLICATION_MANIFEST)
55 || mediaFeature == MediaFeatureNames::displayMode
56#endif
57#if ENABLE(DARK_MODE_CSS)
58 || (mediaFeature == MediaFeatureNames::prefersColorScheme && RuntimeEnabledFeatures::sharedFeatures().darkModeCSSEnabled())
59#endif
60 || mediaFeature == MediaFeatureNames::prefersReducedMotion
61 || (mediaFeature == MediaFeatureNames::prefersDarkInterface && (context.useSystemAppearance || isUASheetBehavior(context.mode)));
62}
63
64static inline bool featureWithValidDensity(const String& mediaFeature, const CSSPrimitiveValue& value)
65{
66 if (!value.isResolution() || value.doubleValue() <= 0)
67 return false;
68
69 return mediaFeature == MediaFeatureNames::resolution
70 || mediaFeature == MediaFeatureNames::minResolution
71 || mediaFeature == MediaFeatureNames::maxResolution;
72}
73
74static inline bool featureWithValidPositiveLength(const String& mediaFeature, const CSSPrimitiveValue& value)
75{
76 if (!(value.isLength() || (value.isNumber() && !value.doubleValue())) || value.doubleValue() < 0)
77 return false;
78
79 return mediaFeature == MediaFeatureNames::height
80 || mediaFeature == MediaFeatureNames::maxHeight
81 || mediaFeature == MediaFeatureNames::minHeight
82 || mediaFeature == MediaFeatureNames::width
83 || mediaFeature == MediaFeatureNames::maxWidth
84 || mediaFeature == MediaFeatureNames::minWidth
85 || mediaFeature == MediaFeatureNames::deviceHeight
86 || mediaFeature == MediaFeatureNames::maxDeviceHeight
87 || mediaFeature == MediaFeatureNames::minDeviceHeight
88 || mediaFeature == MediaFeatureNames::deviceWidth
89 || mediaFeature == MediaFeatureNames::minDeviceWidth
90 || mediaFeature == MediaFeatureNames::maxDeviceWidth;
91}
92
93static inline bool featureExpectingPositiveInteger(const String& mediaFeature)
94{
95 return mediaFeature == MediaFeatureNames::color
96 || mediaFeature == MediaFeatureNames::maxColor
97 || mediaFeature == MediaFeatureNames::minColor
98 || mediaFeature == MediaFeatureNames::colorIndex
99 || mediaFeature == MediaFeatureNames::maxColorIndex
100 || mediaFeature == MediaFeatureNames::minColorIndex
101 || mediaFeature == MediaFeatureNames::monochrome
102 || mediaFeature == MediaFeatureNames::maxMonochrome
103 || mediaFeature == MediaFeatureNames::minMonochrome;
104}
105
106static inline bool featureWithPositiveInteger(const String& mediaFeature, const CSSPrimitiveValue& value)
107{
108 if (!value.isNumber())
109 return false;
110 return featureExpectingPositiveInteger(mediaFeature);
111}
112
113static inline bool featureWithPositiveNumber(const String& mediaFeature, const CSSPrimitiveValue& value)
114{
115 if (!value.isNumber())
116 return false;
117
118 return mediaFeature == MediaFeatureNames::transform3d
119 || mediaFeature == MediaFeatureNames::devicePixelRatio
120 || mediaFeature == MediaFeatureNames::maxDevicePixelRatio
121 || mediaFeature == MediaFeatureNames::minDevicePixelRatio
122 || mediaFeature == MediaFeatureNames::transition
123 || mediaFeature == MediaFeatureNames::animation
124 || mediaFeature == MediaFeatureNames::transform2d;
125}
126
127static inline bool featureWithZeroOrOne(const String& mediaFeature, const CSSPrimitiveValue& value)
128{
129 if (!value.isNumber() || !(value.doubleValue() == 1 || !value.doubleValue()))
130 return false;
131
132 return mediaFeature == MediaFeatureNames::grid;
133}
134
135static inline bool isAspectRatioFeature(const AtomicString& mediaFeature)
136{
137 return mediaFeature == MediaFeatureNames::aspectRatio
138 || mediaFeature == MediaFeatureNames::deviceAspectRatio
139 || mediaFeature == MediaFeatureNames::minAspectRatio
140 || mediaFeature == MediaFeatureNames::maxAspectRatio
141 || mediaFeature == MediaFeatureNames::minDeviceAspectRatio
142 || mediaFeature == MediaFeatureNames::maxDeviceAspectRatio;
143}
144
145static inline bool isFeatureValidWithoutValue(const AtomicString& mediaFeature, const MediaQueryParserContext& context)
146{
147 // Media features that are prefixed by min/max cannot be used without a value.
148 return mediaFeature == MediaFeatureNames::anyHover
149 || mediaFeature == MediaFeatureNames::anyPointer
150 || mediaFeature == MediaFeatureNames::monochrome
151 || mediaFeature == MediaFeatureNames::color
152 || mediaFeature == MediaFeatureNames::colorIndex
153 || mediaFeature == MediaFeatureNames::grid
154 || mediaFeature == MediaFeatureNames::height
155 || mediaFeature == MediaFeatureNames::width
156 || mediaFeature == MediaFeatureNames::deviceHeight
157 || mediaFeature == MediaFeatureNames::deviceWidth
158 || mediaFeature == MediaFeatureNames::orientation
159 || mediaFeature == MediaFeatureNames::aspectRatio
160 || mediaFeature == MediaFeatureNames::deviceAspectRatio
161 || mediaFeature == MediaFeatureNames::hover
162 || mediaFeature == MediaFeatureNames::transform2d
163 || mediaFeature == MediaFeatureNames::transform3d
164 || mediaFeature == MediaFeatureNames::transition
165 || mediaFeature == MediaFeatureNames::animation
166 || mediaFeature == MediaFeatureNames::invertedColors
167 || mediaFeature == MediaFeatureNames::pointer
168 || mediaFeature == MediaFeatureNames::prefersReducedMotion
169 || (mediaFeature == MediaFeatureNames::prefersDarkInterface && (context.useSystemAppearance || isUASheetBehavior(context.mode)))
170#if ENABLE(DARK_MODE_CSS)
171 || (mediaFeature == MediaFeatureNames::prefersColorScheme && RuntimeEnabledFeatures::sharedFeatures().darkModeCSSEnabled())
172#endif
173 || mediaFeature == MediaFeatureNames::devicePixelRatio
174 || mediaFeature == MediaFeatureNames::resolution
175#if ENABLE(APPLICATION_MANIFEST)
176 || mediaFeature == MediaFeatureNames::displayMode
177#endif
178 || mediaFeature == MediaFeatureNames::videoPlayableInline;
179}
180
181inline RefPtr<CSSPrimitiveValue> consumeFirstValue(const String& mediaFeature, CSSParserTokenRange& range)
182{
183 if (auto value = CSSPropertyParserHelpers::consumeInteger(range, 0))
184 return value;
185
186 if (!featureExpectingPositiveInteger(mediaFeature) && !isAspectRatioFeature(mediaFeature)) {
187 if (auto value = CSSPropertyParserHelpers::consumeNumber(range, ValueRangeNonNegative))
188 return value;
189 }
190
191 if (auto value = CSSPropertyParserHelpers::consumeLength(range, HTMLStandardMode, ValueRangeNonNegative))
192 return value;
193
194 if (auto value = CSSPropertyParserHelpers::consumeResolution(range))
195 return value;
196
197 if (auto value = CSSPropertyParserHelpers::consumeIdent(range))
198 return value;
199
200 return nullptr;
201}
202
203MediaQueryExpression::MediaQueryExpression(const String& feature, CSSParserTokenRange& range, MediaQueryParserContext& context)
204 : m_mediaFeature(feature.convertToASCIILowercase())
205 , m_isValid(false)
206{
207 RefPtr<CSSPrimitiveValue> firstValue = consumeFirstValue(m_mediaFeature, range);
208 if (!firstValue) {
209 if (isFeatureValidWithoutValue(m_mediaFeature, context)) {
210 // Valid, creates a MediaQueryExp with an 'invalid' MediaQueryExpValue
211 m_isValid = true;
212 }
213 return;
214 }
215 // Create value for media query expression that must have 1 or more values.
216 if (isAspectRatioFeature(m_mediaFeature)) {
217 if (!firstValue->isNumber() || !firstValue->doubleValue())
218 return;
219 if (!CSSPropertyParserHelpers::consumeSlashIncludingWhitespace(range))
220 return;
221 RefPtr<CSSPrimitiveValue> denominatorValue = CSSPropertyParserHelpers::consumePositiveInteger(range);
222 if (!denominatorValue)
223 return;
224
225 unsigned numerator = clampTo<unsigned>(firstValue->doubleValue());
226 unsigned denominator = clampTo<unsigned>(denominatorValue->doubleValue());
227 m_value = CSSAspectRatioValue::create(numerator, denominator);
228 m_isValid = true;
229 return;
230 }
231 if (featureWithPositiveInteger(m_mediaFeature, *firstValue) || featureWithPositiveNumber(m_mediaFeature, *firstValue)
232 || featureWithZeroOrOne(m_mediaFeature, *firstValue) || featureWithValidDensity(m_mediaFeature, *firstValue)
233 || featureWithValidPositiveLength(m_mediaFeature, *firstValue) || featureWithValidIdent(m_mediaFeature, *firstValue, context)) {
234 m_value = firstValue;
235 m_isValid = true;
236 return;
237 }
238}
239
240String MediaQueryExpression::serialize() const
241{
242 if (!m_serializationCache.isNull())
243 return m_serializationCache;
244
245 StringBuilder result;
246 result.append('(');
247 result.append(m_mediaFeature.convertToASCIILowercase());
248 if (m_value) {
249 result.appendLiteral(": ");
250 result.append(m_value->cssText());
251 }
252 result.append(')');
253
254 m_serializationCache = result.toString();
255 return m_serializationCache;
256}
257
258TextStream& operator<<(TextStream& ts, const MediaQueryExpression& expression)
259{
260 ts << expression.serialize();
261 return ts;
262}
263
264
265} // namespace
266