e43a6ae5997f59242a21d24614f7683dc151691c
[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 isAspectRatioFeature(const AtomicString& mediaFeature)
126 {
127     return mediaFeature == MediaFeatureNames::aspectRatio
128         || mediaFeature == MediaFeatureNames::deviceAspectRatio
129         || mediaFeature == MediaFeatureNames::minAspectRatio
130         || mediaFeature == MediaFeatureNames::maxAspectRatio
131         || mediaFeature == MediaFeatureNames::minDeviceAspectRatio
132         || mediaFeature == MediaFeatureNames::maxDeviceAspectRatio;
133 }
134
135 static inline bool isFeatureValidWithoutValue(const AtomicString& mediaFeature)
136 {
137     // Media features that are prefixed by min/max cannot be used without a value.
138     return mediaFeature == MediaFeatureNames::anyHover
139         || mediaFeature == MediaFeatureNames::anyPointer
140         || mediaFeature == MediaFeatureNames::monochrome
141         || mediaFeature == MediaFeatureNames::color
142         || mediaFeature == MediaFeatureNames::colorIndex
143         || mediaFeature == MediaFeatureNames::grid
144         || mediaFeature == MediaFeatureNames::height
145         || mediaFeature == MediaFeatureNames::width
146         || mediaFeature == MediaFeatureNames::deviceHeight
147         || mediaFeature == MediaFeatureNames::deviceWidth
148         || mediaFeature == MediaFeatureNames::orientation
149         || mediaFeature == MediaFeatureNames::aspectRatio
150         || mediaFeature == MediaFeatureNames::deviceAspectRatio
151         || mediaFeature == MediaFeatureNames::hover
152         || mediaFeature == MediaFeatureNames::transform2d
153         || mediaFeature == MediaFeatureNames::transform3d
154         || mediaFeature == MediaFeatureNames::transition
155         || mediaFeature == MediaFeatureNames::animation
156         || mediaFeature == MediaFeatureNames::invertedColors
157 #if ENABLE(VIEW_MODE_CSS_MEDIA)
158         || mediaFeature == MediaFeatureNames::viewMode
159 #endif
160         || mediaFeature == MediaFeatureNames::pointer
161         || mediaFeature == MediaFeatureNames::prefersReducedMotion
162         || mediaFeature == MediaFeatureNames::devicePixelRatio
163         || mediaFeature == MediaFeatureNames::resolution
164         || mediaFeature == MediaFeatureNames::videoPlayableInline;
165 }
166
167 MediaQueryExpression::MediaQueryExpression(const String& feature, const Vector<CSSParserToken, 4>& tokenList)
168     : m_mediaFeature(feature.convertToASCIILowercase())
169     , m_isValid(false)
170 {
171     // Create value for media query expression that must have 1 or more values.
172     if (!tokenList.size() && isFeatureValidWithoutValue(m_mediaFeature)) {
173         // Valid, creates a MediaQueryExp with an 'invalid' MediaQueryExpValue
174         m_isValid = true;
175     } else if (tokenList.size() == 1) {
176         CSSParserToken token = tokenList.first();
177         if (token.type() == IdentToken) {
178             CSSValueID ident = token.id();
179             if (!featureWithValidIdent(m_mediaFeature))
180                 return;
181             m_value = CSSPrimitiveValue::createIdentifier(ident);
182             m_isValid = true;
183         } else if (token.type() == NumberToken || token.type() == PercentageToken || token.type() == DimensionToken) {
184             // Check for numeric token types since it is only safe for these types to call numericValue.
185             if (featureWithValidDensity(m_mediaFeature, token)
186                 || featureWithValidPositiveLength(m_mediaFeature, token)) {
187                 // Media features that must have non-negative <density>, ie. dppx, dpi or dpcm,
188                 // or Media features that must have non-negative <length> or number value.
189                 m_value = CSSPrimitiveValue::create(token.numericValue(), (CSSPrimitiveValue::UnitTypes) token.unitType());
190                 m_isValid = true;
191             } else if (featureWithPositiveInteger(m_mediaFeature, token)
192                 || featureWithPositiveNumber(m_mediaFeature, token)
193                 || featureWithZeroOrOne(m_mediaFeature, token)) {
194                 // Media features that must have non-negative integer value,
195                 // or media features that must have non-negative number value,
196                 // or media features that must have (0|1) value.
197                 m_value = CSSPrimitiveValue::create(token.numericValue(), CSSPrimitiveValue::UnitTypes::CSS_NUMBER);
198                 m_isValid = true;
199             }
200         }
201     } else if (tokenList.size() == 3 && isAspectRatioFeature(m_mediaFeature)) {
202         // FIXME: <ratio> is supposed to allow whitespace around the '/'
203         // Applicable to device-aspect-ratio and aspect-ratio.
204         const CSSParserToken& numerator = tokenList[0];
205         const CSSParserToken& delimiter = tokenList[1];
206         const CSSParserToken& denominator = tokenList[2];
207         if (delimiter.type() != DelimiterToken || delimiter.delimiter() != '/')
208             return;
209         if (numerator.type() != NumberToken || numerator.numericValue() <= 0 || numerator.numericValueType() != IntegerValueType)
210             return;
211         if (denominator.type() != NumberToken || denominator.numericValue() <= 0 || denominator.numericValueType() != IntegerValueType)
212             return;
213         
214         m_value = CSSAspectRatioValue::create(numerator.numericValue(), denominator.numericValue());
215         m_isValid = true;
216     }
217 }
218
219 String MediaQueryExpression::serialize() const
220 {
221     if (!m_serializationCache.isNull())
222         return m_serializationCache;
223
224     StringBuilder result;
225     result.append('(');
226     result.append(m_mediaFeature.convertToASCIILowercase());
227     if (m_value) {
228         result.appendLiteral(": ");
229         result.append(m_value->cssText());
230     }
231     result.append(')');
232
233     m_serializationCache = result.toString();
234     return m_serializationCache;
235 }
236
237 } // namespace