787b662fcc9422a68977c540b210640dee8a2f73
[WebKit-https.git] / Source / WebCore / css / MediaQueryExp.cpp
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 "MediaQueryExp.h"
30
31 #include "CSSAspectRatioValue.h"
32 #include "CSSParser.h"
33 #include "CSSParserIdioms.h"
34 #include "CSSParserToken.h"
35 #include "CSSPrimitiveValue.h"
36 #include "CSSValueList.h"
37 #include "MediaFeatureNames.h"
38 #include <wtf/text/StringBuilder.h>
39
40 namespace WebCore {
41
42 static inline bool featureWithValidIdent(const AtomicString& mediaFeature)
43 {
44     return mediaFeature == MediaFeatureNames::orientation
45 #if ENABLE(VIEW_MODE_CSS_MEDIA)
46     || mediaFeature == MediaFeatureNames::viewMode
47 #endif
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     || mediaFeature == MediaFeatureNames::prefersReducedMotion;
55 }
56
57 static inline bool featureWithValidDensity(const String& mediaFeature, const CSSParserToken& token)
58 {
59     if (!CSSPrimitiveValue::isResolution(static_cast<CSSPrimitiveValue::UnitTypes>(token.unitType())) || token.numericValue() <= 0)
60         return false;
61     
62     return mediaFeature == MediaFeatureNames::resolution
63     || mediaFeature == MediaFeatureNames::minResolution
64     || mediaFeature == MediaFeatureNames::maxResolution;
65 }
66
67 static inline bool featureWithValidPositiveLength(const String& mediaFeature, const CSSParserToken& token)
68 {
69     if (!(CSSPrimitiveValue::isLength(token.unitType()) || (token.type() == NumberToken && !token.numericValue())) || token.numericValue() < 0)
70         return false;
71     
72     
73     return mediaFeature == MediaFeatureNames::height
74     || mediaFeature == MediaFeatureNames::maxHeight
75     || mediaFeature == MediaFeatureNames::minHeight
76     || mediaFeature == MediaFeatureNames::width
77     || mediaFeature == MediaFeatureNames::maxWidth
78     || mediaFeature == MediaFeatureNames::minWidth
79     || mediaFeature == MediaFeatureNames::deviceHeight
80     || mediaFeature == MediaFeatureNames::maxDeviceHeight
81     || mediaFeature == MediaFeatureNames::minDeviceHeight
82     || mediaFeature == MediaFeatureNames::deviceWidth
83     || mediaFeature == MediaFeatureNames::minDeviceWidth
84     || mediaFeature == MediaFeatureNames::maxDeviceWidth;
85 }
86
87 static inline bool featureWithPositiveInteger(const String& mediaFeature, const CSSParserToken& token)
88 {
89     if (token.numericValueType() != IntegerValueType || token.numericValue() < 0)
90         return false;
91     
92     return mediaFeature == MediaFeatureNames::color
93     || mediaFeature == MediaFeatureNames:: maxColor
94     || mediaFeature == MediaFeatureNames:: minColor
95     || mediaFeature == MediaFeatureNames::colorIndex
96     || mediaFeature == MediaFeatureNames::maxColorIndex
97     || mediaFeature == MediaFeatureNames::minColorIndex
98     || mediaFeature == MediaFeatureNames::monochrome
99     || mediaFeature == MediaFeatureNames::maxMonochrome
100     || mediaFeature == MediaFeatureNames::minMonochrome;
101 }
102
103 static inline bool featureWithPositiveNumber(const String& mediaFeature, const CSSParserToken& token)
104 {
105     if (token.type() != NumberToken || token.numericValue() < 0)
106         return false;
107     
108     return mediaFeature == MediaFeatureNames::transform3d
109     || mediaFeature == MediaFeatureNames::devicePixelRatio
110     || mediaFeature == MediaFeatureNames::maxDevicePixelRatio
111     || mediaFeature == MediaFeatureNames::minDevicePixelRatio
112     || mediaFeature == MediaFeatureNames::transition
113     || mediaFeature == MediaFeatureNames::animation
114     || mediaFeature == MediaFeatureNames::transform2d;
115 }
116
117 static inline bool featureWithZeroOrOne(const String& mediaFeature, const CSSParserToken& token)
118 {
119     if (token.numericValueType() != IntegerValueType || !(token.numericValue() == 1 || !token.numericValue()))
120         return false;
121     
122     return mediaFeature == MediaFeatureNames::grid;
123 }
124
125 static inline bool isFeatureValidWithIdentifier(const AtomicString& mediaFeature, CSSValueID id)
126 {
127     if (!id)
128         return false;
129
130     return featureWithValidIdent(mediaFeature);
131 }
132
133 static inline bool isFeatureValidWithNonNegativeLengthOrNumber(const AtomicString& mediaFeature, const CSSParserValue& value)
134 {
135     if (!(CSSPrimitiveValue::isLength(static_cast<CSSPrimitiveValue::UnitTypes>(value.unit)) || value.unit == CSSPrimitiveValue::CSS_NUMBER) || value.fValue < 0)
136         return false;
137
138     return mediaFeature == MediaFeatureNames::height
139         || mediaFeature == MediaFeatureNames::maxHeight
140         || mediaFeature == MediaFeatureNames::minHeight
141         || mediaFeature == MediaFeatureNames::width
142         || mediaFeature == MediaFeatureNames::maxWidth
143         || mediaFeature == MediaFeatureNames::minWidth
144         || mediaFeature == MediaFeatureNames::deviceHeight
145         || mediaFeature == MediaFeatureNames::maxDeviceHeight
146         || mediaFeature == MediaFeatureNames::minDeviceHeight
147         || mediaFeature == MediaFeatureNames::deviceWidth
148         || mediaFeature == MediaFeatureNames::maxDeviceWidth
149         || mediaFeature == MediaFeatureNames::minDeviceWidth;
150 }
151
152 static inline bool isFeatureValidWithDensity(const AtomicString& mediaFeature, const CSSParserValue& value)
153 {
154     if (!CSSPrimitiveValue::isResolution(static_cast<CSSPrimitiveValue::UnitTypes>(value.unit)) || value.fValue <= 0)
155         return false;
156
157     return mediaFeature == MediaFeatureNames::resolution
158         || mediaFeature == MediaFeatureNames::maxResolution
159         || mediaFeature == MediaFeatureNames::minResolution;
160 }
161
162 static inline bool isFeatureValidWithNonNegativeInteger(const AtomicString& mediaFeature, const CSSParserValue& value)
163 {
164     if (!value.isInt || value.fValue < 0)
165         return false;
166
167     return mediaFeature == MediaFeatureNames::color
168         || mediaFeature == MediaFeatureNames::maxColor
169         || mediaFeature == MediaFeatureNames::minColor
170         || mediaFeature == MediaFeatureNames::colorIndex
171         || mediaFeature == MediaFeatureNames::maxColorIndex
172         || mediaFeature == MediaFeatureNames::minColorIndex
173         || mediaFeature == MediaFeatureNames::minMonochrome
174         || mediaFeature == MediaFeatureNames::maxMonochrome;
175 }
176
177 static inline bool isFeatureValidWithNonNegativeNumber(const AtomicString& mediaFeature, const CSSParserValue& value)
178 {
179     if (value.unit != CSSPrimitiveValue::CSS_NUMBER || value.fValue < 0)
180         return false;
181
182     return mediaFeature == MediaFeatureNames::transform2d
183         || mediaFeature == MediaFeatureNames::transform3d
184         || mediaFeature == MediaFeatureNames::transition
185         || mediaFeature == MediaFeatureNames::animation
186         || mediaFeature == MediaFeatureNames::devicePixelRatio
187         || mediaFeature == MediaFeatureNames::maxDevicePixelRatio
188         || mediaFeature == MediaFeatureNames::minDevicePixelRatio;
189 }
190
191 static inline bool isFeatureValidWithZeroOrOne(const AtomicString& mediaFeature, const CSSParserValue& value)
192 {
193     if (!value.isInt || !(value.fValue == 1 || !value.fValue))
194         return false;
195
196     return mediaFeature == MediaFeatureNames::grid;
197 }
198
199 static inline bool isAspectRatioFeature(const AtomicString& mediaFeature)
200 {
201     return mediaFeature == MediaFeatureNames::aspectRatio
202         || mediaFeature == MediaFeatureNames::deviceAspectRatio
203         || mediaFeature == MediaFeatureNames::minAspectRatio
204         || mediaFeature == MediaFeatureNames::maxAspectRatio
205         || mediaFeature == MediaFeatureNames::minDeviceAspectRatio
206         || mediaFeature == MediaFeatureNames::maxDeviceAspectRatio;
207 }
208
209 static inline bool isFeatureValidWithoutValue(const AtomicString& mediaFeature)
210 {
211     // Media features that are prefixed by min/max cannot be used without a value.
212     return mediaFeature == MediaFeatureNames::anyHover
213         || mediaFeature == MediaFeatureNames::anyPointer
214         || mediaFeature == MediaFeatureNames::monochrome
215         || mediaFeature == MediaFeatureNames::color
216         || mediaFeature == MediaFeatureNames::colorIndex
217         || mediaFeature == MediaFeatureNames::grid
218         || mediaFeature == MediaFeatureNames::height
219         || mediaFeature == MediaFeatureNames::width
220         || mediaFeature == MediaFeatureNames::deviceHeight
221         || mediaFeature == MediaFeatureNames::deviceWidth
222         || mediaFeature == MediaFeatureNames::orientation
223         || mediaFeature == MediaFeatureNames::aspectRatio
224         || mediaFeature == MediaFeatureNames::deviceAspectRatio
225         || mediaFeature == MediaFeatureNames::hover
226         || mediaFeature == MediaFeatureNames::transform2d
227         || mediaFeature == MediaFeatureNames::transform3d
228         || mediaFeature == MediaFeatureNames::transition
229         || mediaFeature == MediaFeatureNames::animation
230         || mediaFeature == MediaFeatureNames::invertedColors
231 #if ENABLE(VIEW_MODE_CSS_MEDIA)
232         || mediaFeature == MediaFeatureNames::viewMode
233 #endif
234         || mediaFeature == MediaFeatureNames::pointer
235         || mediaFeature == MediaFeatureNames::prefersReducedMotion
236         || mediaFeature == MediaFeatureNames::devicePixelRatio
237         || mediaFeature == MediaFeatureNames::resolution
238         || mediaFeature == MediaFeatureNames::videoPlayableInline;
239 }
240
241 static inline bool isFeatureValidWithNumberWithUnit(const AtomicString& mediaFeature, const CSSParserValue& value)
242 {
243     return isFeatureValidWithDensity(mediaFeature, value) || isFeatureValidWithNonNegativeLengthOrNumber(mediaFeature, value);
244 }
245
246 static inline bool isFeatureValidWithNumber(const AtomicString& mediaFeature, const CSSParserValue& value)
247 {
248     return isFeatureValidWithNonNegativeInteger(mediaFeature, value) || isFeatureValidWithNonNegativeNumber(mediaFeature, value) || isFeatureValidWithZeroOrOne(mediaFeature, value);
249 }
250
251 static inline bool isSlash(CSSParserValue& value)
252 {
253     return value.unit == CSSParserValue::Operator && value.iValue == '/';
254 }
255
256 static inline bool isPositiveIntegerValue(CSSParserValue& value)
257 {
258     return value.unit == CSSPrimitiveValue::CSS_NUMBER && value.fValue > 0 && value.isInt;
259 }
260
261 MediaQueryExpression::MediaQueryExpression(const AtomicString& mediaFeature, CSSParserValueList* valueList)
262     : m_mediaFeature(mediaFeature)
263 {
264     if (!valueList) {
265         if (isFeatureValidWithoutValue(mediaFeature))
266             m_isValid = true;
267     } else if (valueList->size() == 1) {
268         auto& value = *valueList->valueAt(0);
269         if (isFeatureValidWithIdentifier(mediaFeature, value.id)) {
270             m_value = CSSPrimitiveValue::createIdentifier(value.id);
271             m_isValid = true;
272         } else if (isFeatureValidWithNumberWithUnit(mediaFeature, value) || isFeatureValidWithNonNegativeLengthOrNumber(mediaFeature, value)) {
273             m_value = CSSPrimitiveValue::create(value.fValue, (CSSPrimitiveValue::UnitTypes) value.unit);
274             m_isValid = true;
275         } else if (isFeatureValidWithNumber(mediaFeature, value)) {
276             // FIXME: Can we merge this with the case above?
277             m_value = CSSPrimitiveValue::create(value.fValue, CSSPrimitiveValue::CSS_NUMBER);
278             m_isValid = true;
279         }
280     } else if (valueList->size() == 3 && isAspectRatioFeature(mediaFeature)) {
281         auto& numerator = *valueList->valueAt(0);
282         auto& slash = *valueList->valueAt(1);
283         auto& denominator = *valueList->valueAt(2);
284         if (isPositiveIntegerValue(numerator) && isSlash(slash) && isPositiveIntegerValue(denominator)) {
285             m_value = CSSAspectRatioValue::create(numerator.fValue, denominator.fValue);
286             m_isValid = true;
287         }
288     }
289 }
290
291 MediaQueryExpression::MediaQueryExpression(const String& feature, const Vector<CSSParserToken, 4>& tokenList)
292     : m_mediaFeature(feature)
293     , m_isValid(false)
294 {
295     // Create value for media query expression that must have 1 or more values.
296     if (!tokenList.size() && isFeatureValidWithoutValue(m_mediaFeature)) {
297         // Valid, creates a MediaQueryExp with an 'invalid' MediaQueryExpValue
298         m_isValid = true;
299     } else if (tokenList.size() == 1) {
300         CSSParserToken token = tokenList.first();
301         if (token.type() == IdentToken) {
302             CSSValueID ident = token.id();
303             if (!featureWithValidIdent(m_mediaFeature))
304                 return;
305             m_value = CSSPrimitiveValue::createIdentifier(ident);
306             m_isValid = true;
307         } else if (token.type() == NumberToken || token.type() == PercentageToken || token.type() == DimensionToken) {
308             // Check for numeric token types since it is only safe for these types to call numericValue.
309             if (featureWithValidDensity(m_mediaFeature, token)
310                 || featureWithValidPositiveLength(m_mediaFeature, token)) {
311                 // Media features that must have non-negative <density>, ie. dppx, dpi or dpcm,
312                 // or Media features that must have non-negative <length> or number value.
313                 m_value = CSSPrimitiveValue::create(token.numericValue(), (CSSPrimitiveValue::UnitTypes) token.unitType());
314                 m_isValid = true;
315             } else if (featureWithPositiveInteger(m_mediaFeature, token)
316                 || featureWithPositiveNumber(m_mediaFeature, token)
317                 || featureWithZeroOrOne(m_mediaFeature, token)) {
318                 // Media features that must have non-negative integer value,
319                 // or media features that must have non-negative number value,
320                 // or media features that must have (0|1) value.
321                 m_value = CSSPrimitiveValue::create(token.numericValue(), CSSPrimitiveValue::UnitTypes::CSS_NUMBER);
322                 m_isValid = true;
323             }
324         }
325     } else if (tokenList.size() == 3 && isAspectRatioFeature(m_mediaFeature)) {
326         // FIXME: <ratio> is supposed to allow whitespace around the '/'
327         // Applicable to device-aspect-ratio and aspect-ratio.
328         const CSSParserToken& numerator = tokenList[0];
329         const CSSParserToken& delimiter = tokenList[1];
330         const CSSParserToken& denominator = tokenList[2];
331         if (delimiter.type() != DelimiterToken || delimiter.delimiter() != '/')
332             return;
333         if (numerator.type() != NumberToken || numerator.numericValue() <= 0 || numerator.numericValueType() != IntegerValueType)
334             return;
335         if (denominator.type() != NumberToken || denominator.numericValue() <= 0 || denominator.numericValueType() != IntegerValueType)
336             return;
337         
338         m_value = CSSAspectRatioValue::create(numerator.numericValue(), denominator.numericValue());
339         m_isValid = true;
340     }
341 }
342
343 String MediaQueryExpression::serialize() const
344 {
345     if (!m_serializationCache.isNull())
346         return m_serializationCache;
347
348     StringBuilder result;
349     result.append('(');
350     result.append(m_mediaFeature.convertToASCIILowercase());
351     if (m_value) {
352         result.appendLiteral(": ");
353         result.append(m_value->cssText());
354     }
355     result.append(')');
356
357     m_serializationCache = result.toString();
358     return m_serializationCache;
359 }
360
361 } // namespace