1 /*
2 * CSS Media Query
3 *
4 * Copyright (C) 2006 Kimmo Kinnunen <kimmo.t.kinnunen@nokia.com>.
5 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
6 * Copyright (C) 2013 Apple Inc. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
25 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "config.h"
31 #include "core/css/MediaQueryExp.h"
32
33 #include "core/css/CSSAspectRatioValue.h"
34 #include "core/css/CSSPrimitiveValue.h"
35 #include "core/css/parser/CSSParserValues.h"
36 #include "core/html/parser/HTMLParserIdioms.h"
37 #include "platform/Decimal.h"
38 #include "platform/RuntimeEnabledFeatures.h"
39 #include "wtf/text/StringBuffer.h"
40 #include "wtf/text/StringBuilder.h"
41
42 namespace blink {
43
44 using namespace MediaFeatureNames;
45
featureWithCSSValueID(const String & mediaFeature,const CSSParserValue * value)46 static inline bool featureWithCSSValueID(const String& mediaFeature, const CSSParserValue* value)
47 {
48 if (!value->id)
49 return false;
50
51 return mediaFeature == orientationMediaFeature
52 || mediaFeature == pointerMediaFeature
53 || mediaFeature == anyPointerMediaFeature
54 || (mediaFeature == hoverMediaFeature && RuntimeEnabledFeatures::hoverMediaQueryKeywordsEnabled())
55 || mediaFeature == anyHoverMediaFeature
56 || mediaFeature == scanMediaFeature;
57 }
58
featureWithValidIdent(const String & mediaFeature,CSSValueID ident)59 static inline bool featureWithValidIdent(const String& mediaFeature, CSSValueID ident)
60 {
61 if (mediaFeature == orientationMediaFeature)
62 return ident == CSSValuePortrait || ident == CSSValueLandscape;
63
64 if (mediaFeature == pointerMediaFeature || mediaFeature == anyPointerMediaFeature)
65 return ident == CSSValueNone || ident == CSSValueCoarse || ident == CSSValueFine;
66
67 if ((mediaFeature == hoverMediaFeature && RuntimeEnabledFeatures::hoverMediaQueryKeywordsEnabled())
68 || mediaFeature == anyHoverMediaFeature)
69 return ident == CSSValueNone || ident == CSSValueOnDemand || ident == CSSValueHover;
70
71 if (mediaFeature == scanMediaFeature)
72 return ident == CSSValueInterlace || ident == CSSValueProgressive;
73
74 ASSERT_NOT_REACHED();
75 return false;
76 }
77
positiveLengthUnit(const int unit)78 static bool positiveLengthUnit(const int unit)
79 {
80 switch (unit) {
81 case CSSPrimitiveValue::CSS_EMS:
82 case CSSPrimitiveValue::CSS_EXS:
83 case CSSPrimitiveValue::CSS_PX:
84 case CSSPrimitiveValue::CSS_CM:
85 case CSSPrimitiveValue::CSS_MM:
86 case CSSPrimitiveValue::CSS_IN:
87 case CSSPrimitiveValue::CSS_PT:
88 case CSSPrimitiveValue::CSS_PC:
89 case CSSPrimitiveValue::CSS_REMS:
90 case CSSPrimitiveValue::CSS_CHS:
91 return true;
92 }
93 return false;
94 }
95
featureWithValidPositiveLength(const String & mediaFeature,const CSSParserValue * value)96 static inline bool featureWithValidPositiveLength(const String& mediaFeature, const CSSParserValue* value)
97 {
98 if (!(positiveLengthUnit(value->unit) || (value->unit == CSSPrimitiveValue::CSS_NUMBER && value->fValue == 0)) || value->fValue < 0)
99 return false;
100
101
102 return mediaFeature == heightMediaFeature
103 || mediaFeature == maxHeightMediaFeature
104 || mediaFeature == minHeightMediaFeature
105 || mediaFeature == widthMediaFeature
106 || mediaFeature == maxWidthMediaFeature
107 || mediaFeature == minWidthMediaFeature
108 || mediaFeature == deviceHeightMediaFeature
109 || mediaFeature == maxDeviceHeightMediaFeature
110 || mediaFeature == minDeviceHeightMediaFeature
111 || mediaFeature == deviceWidthMediaFeature
112 || mediaFeature == minDeviceWidthMediaFeature
113 || mediaFeature == maxDeviceWidthMediaFeature;
114 }
115
featureWithValidDensity(const String & mediaFeature,const CSSParserValue * value)116 static inline bool featureWithValidDensity(const String& mediaFeature, const CSSParserValue* value)
117 {
118 if ((value->unit != CSSPrimitiveValue::CSS_DPPX && value->unit != CSSPrimitiveValue::CSS_DPI && value->unit != CSSPrimitiveValue::CSS_DPCM) || value->fValue <= 0)
119 return false;
120
121 return mediaFeature == resolutionMediaFeature
122 || mediaFeature == minResolutionMediaFeature
123 || mediaFeature == maxResolutionMediaFeature;
124 }
125
featureWithPositiveInteger(const String & mediaFeature,const CSSParserValue * value)126 static inline bool featureWithPositiveInteger(const String& mediaFeature, const CSSParserValue* value)
127 {
128 if (!value->isInt || value->fValue < 0)
129 return false;
130
131 return mediaFeature == colorMediaFeature
132 || mediaFeature == maxColorMediaFeature
133 || mediaFeature == minColorMediaFeature
134 || mediaFeature == colorIndexMediaFeature
135 || mediaFeature == maxColorIndexMediaFeature
136 || mediaFeature == minColorIndexMediaFeature
137 || mediaFeature == monochromeMediaFeature
138 || mediaFeature == maxMonochromeMediaFeature
139 || mediaFeature == minMonochromeMediaFeature;
140 }
141
featureWithPositiveNumber(const String & mediaFeature,const CSSParserValue * value)142 static inline bool featureWithPositiveNumber(const String& mediaFeature, const CSSParserValue* value)
143 {
144 if (value->unit != CSSPrimitiveValue::CSS_NUMBER || value->fValue < 0)
145 return false;
146
147 return mediaFeature == transform3dMediaFeature
148 || mediaFeature == devicePixelRatioMediaFeature
149 || mediaFeature == maxDevicePixelRatioMediaFeature
150 || mediaFeature == minDevicePixelRatioMediaFeature;
151 }
152
featureWithZeroOrOne(const String & mediaFeature,const CSSParserValue * value)153 static inline bool featureWithZeroOrOne(const String& mediaFeature, const CSSParserValue* value)
154 {
155 if (!value->isInt || !(value->fValue == 1 || !value->fValue))
156 return false;
157
158 return mediaFeature == gridMediaFeature
159 || (mediaFeature == hoverMediaFeature && !RuntimeEnabledFeatures::hoverMediaQueryKeywordsEnabled());
160 }
161
featureWithAspectRatio(const String & mediaFeature)162 static inline bool featureWithAspectRatio(const String& mediaFeature)
163 {
164 return mediaFeature == aspectRatioMediaFeature
165 || mediaFeature == deviceAspectRatioMediaFeature
166 || mediaFeature == minAspectRatioMediaFeature
167 || mediaFeature == maxAspectRatioMediaFeature
168 || mediaFeature == minDeviceAspectRatioMediaFeature
169 || mediaFeature == maxDeviceAspectRatioMediaFeature;
170 }
171
featureWithoutValue(const String & mediaFeature)172 static inline bool featureWithoutValue(const String& mediaFeature)
173 {
174 // Media features that are prefixed by min/max cannot be used without a value.
175 return mediaFeature == monochromeMediaFeature
176 || mediaFeature == colorMediaFeature
177 || mediaFeature == colorIndexMediaFeature
178 || mediaFeature == gridMediaFeature
179 || mediaFeature == heightMediaFeature
180 || mediaFeature == widthMediaFeature
181 || mediaFeature == deviceHeightMediaFeature
182 || mediaFeature == deviceWidthMediaFeature
183 || mediaFeature == orientationMediaFeature
184 || mediaFeature == aspectRatioMediaFeature
185 || mediaFeature == deviceAspectRatioMediaFeature
186 || mediaFeature == hoverMediaFeature
187 || mediaFeature == anyHoverMediaFeature
188 || mediaFeature == transform3dMediaFeature
189 || mediaFeature == pointerMediaFeature
190 || mediaFeature == anyPointerMediaFeature
191 || mediaFeature == devicePixelRatioMediaFeature
192 || mediaFeature == resolutionMediaFeature
193 || mediaFeature == scanMediaFeature;
194 }
195
isViewportDependent() const196 bool MediaQueryExp::isViewportDependent() const
197 {
198 return m_mediaFeature == widthMediaFeature
199 || m_mediaFeature == heightMediaFeature
200 || m_mediaFeature == minWidthMediaFeature
201 || m_mediaFeature == minHeightMediaFeature
202 || m_mediaFeature == maxWidthMediaFeature
203 || m_mediaFeature == maxHeightMediaFeature
204 || m_mediaFeature == orientationMediaFeature
205 || m_mediaFeature == aspectRatioMediaFeature
206 || m_mediaFeature == minAspectRatioMediaFeature
207 || m_mediaFeature == devicePixelRatioMediaFeature
208 || m_mediaFeature == resolutionMediaFeature
209 || m_mediaFeature == maxAspectRatioMediaFeature;
210 }
211
MediaQueryExp(const MediaQueryExp & other)212 MediaQueryExp::MediaQueryExp(const MediaQueryExp& other)
213 : m_mediaFeature(other.mediaFeature())
214 , m_expValue(other.expValue())
215 {
216 }
217
MediaQueryExp(const String & mediaFeature,const MediaQueryExpValue & expValue)218 MediaQueryExp::MediaQueryExp(const String& mediaFeature, const MediaQueryExpValue& expValue)
219 : m_mediaFeature(mediaFeature)
220 , m_expValue(expValue)
221 {
222 }
223
createIfValid(const String & mediaFeature,CSSParserValueList * valueList)224 PassOwnPtrWillBeRawPtr<MediaQueryExp> MediaQueryExp::createIfValid(const String& mediaFeature, CSSParserValueList* valueList)
225 {
226 ASSERT(!mediaFeature.isNull());
227
228 MediaQueryExpValue expValue;
229 bool isValid = false;
230 String lowerMediaFeature = attemptStaticStringCreation(mediaFeature.lower());
231
232 // Create value for media query expression that must have 1 or more values.
233 if (valueList && valueList->size() > 0) {
234 if (valueList->size() == 1) {
235 CSSParserValue* value = valueList->current();
236 ASSERT(value);
237
238 if (featureWithCSSValueID(lowerMediaFeature, value) && featureWithValidIdent(lowerMediaFeature, value->id)) {
239 // Media features that use CSSValueIDs.
240 expValue.id = value->id;
241 expValue.unit = CSSPrimitiveValue::CSS_VALUE_ID;
242 expValue.isID = true;
243 } else if (featureWithValidDensity(lowerMediaFeature, value)
244 || featureWithValidPositiveLength(lowerMediaFeature, value)) {
245 // Media features that must have non-negative <density>, ie. dppx, dpi or dpcm,
246 // or Media features that must have non-negative <length> or number value.
247 expValue.value = value->fValue;
248 expValue.unit = (CSSPrimitiveValue::UnitType)value->unit;
249 expValue.isValue = true;
250 } else if (featureWithPositiveInteger(lowerMediaFeature, value)
251 || featureWithPositiveNumber(lowerMediaFeature, value)
252 || featureWithZeroOrOne(lowerMediaFeature, value)) {
253 // Media features that must have non-negative integer value,
254 // or media features that must have non-negative number value,
255 // or media features that must have (0|1) value.
256 expValue.value = value->fValue;
257 expValue.unit = CSSPrimitiveValue::CSS_NUMBER;
258 expValue.isValue = true;
259 }
260
261 isValid = (expValue.isID || expValue.isValue);
262
263 } else if (valueList->size() == 3 && featureWithAspectRatio(lowerMediaFeature)) {
264 // Create list of values.
265 // Currently accepts only <integer>/<integer>.
266 // Applicable to device-aspect-ratio and aspec-ratio.
267 isValid = true;
268 float numeratorValue = 0;
269 float denominatorValue = 0;
270 // The aspect-ratio must be <integer> (whitespace)? / (whitespace)? <integer>.
271 for (unsigned i = 0; i < 3; ++i, valueList->next()) {
272 const CSSParserValue* value = valueList->current();
273 if (i != 1 && value->unit == CSSPrimitiveValue::CSS_NUMBER && value->fValue > 0 && value->isInt) {
274 if (!i)
275 numeratorValue = value->fValue;
276 else
277 denominatorValue = value->fValue;
278 } else if (i == 1 && value->unit == CSSParserValue::Operator && value->iValue == '/') {
279 continue;
280 } else {
281 isValid = false;
282 break;
283 }
284 }
285
286 if (isValid) {
287 expValue.numerator = (unsigned)numeratorValue;
288 expValue.denominator = (unsigned)denominatorValue;
289 expValue.isRatio = true;
290 }
291 }
292 } else if (featureWithoutValue(lowerMediaFeature)) {
293 isValid = true;
294 }
295
296 if (!isValid)
297 return nullptr;
298
299 return adoptPtrWillBeNoop(new MediaQueryExp(lowerMediaFeature, expValue));
300 }
301
~MediaQueryExp()302 MediaQueryExp::~MediaQueryExp()
303 {
304 }
305
operator ==(const MediaQueryExp & other) const306 bool MediaQueryExp::operator==(const MediaQueryExp& other) const
307 {
308 return (other.m_mediaFeature == m_mediaFeature)
309 && ((!other.m_expValue.isValid() && !m_expValue.isValid())
310 || (other.m_expValue.isValid() && m_expValue.isValid() && other.m_expValue.equals(m_expValue)));
311 }
312
serialize() const313 String MediaQueryExp::serialize() const
314 {
315 StringBuilder result;
316 result.append('(');
317 result.append(m_mediaFeature.lower());
318 if (m_expValue.isValid()) {
319 result.appendLiteral(": ");
320 result.append(m_expValue.cssText());
321 }
322 result.append(')');
323
324 return result.toString();
325 }
326
printNumber(double number)327 static inline String printNumber(double number)
328 {
329 return Decimal::fromDouble(number).toString();
330 }
331
cssText() const332 String MediaQueryExpValue::cssText() const
333 {
334 StringBuilder output;
335 if (isValue) {
336 output.append(printNumber(value));
337 output.append(CSSPrimitiveValue::unitTypeToString(unit));
338 } else if (isRatio) {
339 output.append(printNumber(numerator));
340 output.append('/');
341 output.append(printNumber(denominator));
342 } else if (isID) {
343 output.append(getValueName(id));
344 }
345
346 return output.toString();
347 }
348
349 } // namespace
350