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 "MediaQueryExp.h"
32
33#include "CSSAspectRatioValue.h"
34#include "CSSParser.h"
35#include "CSSPrimitiveValue.h"
36#include "CSSValueList.h"
37#include <wtf/text/StringBuilder.h>
38
39namespace WebCore {
40
41static inline bool featureWithCSSValueID(const AtomicString& mediaFeature, const CSSParserValue* value)
42{
43    if (!value->id)
44        return false;
45
46    return mediaFeature == MediaFeatureNames::orientationMediaFeature
47#if ENABLE(VIEW_MODE_CSS_MEDIA)
48        || mediaFeature == MediaFeatureNames::view_modeMediaFeature
49#endif // ENABLE(VIEW_MODE_CSS_MEDIA)
50        || mediaFeature == MediaFeatureNames::pointerMediaFeature;
51}
52
53static inline bool featureWithValidPositiveLenghtOrNumber(const AtomicString& mediaFeature, const CSSParserValue* value)
54{
55    if (!(((value->unit >= CSSPrimitiveValue::CSS_EMS && value->unit <= CSSPrimitiveValue::CSS_PC) || value->unit == CSSPrimitiveValue::CSS_REMS) || value->unit == CSSPrimitiveValue::CSS_NUMBER) || value->fValue < 0)
56        return false;
57
58    return mediaFeature == MediaFeatureNames::heightMediaFeature
59        || mediaFeature == MediaFeatureNames::max_heightMediaFeature
60        || mediaFeature == MediaFeatureNames::min_heightMediaFeature
61        || mediaFeature == MediaFeatureNames::widthMediaFeature
62        || mediaFeature == MediaFeatureNames::max_widthMediaFeature
63        || mediaFeature == MediaFeatureNames::min_widthMediaFeature
64        || mediaFeature == MediaFeatureNames::device_heightMediaFeature
65        || mediaFeature == MediaFeatureNames::max_device_heightMediaFeature
66        || mediaFeature == MediaFeatureNames::min_device_heightMediaFeature
67        || mediaFeature == MediaFeatureNames::device_widthMediaFeature
68        || mediaFeature == MediaFeatureNames::max_device_widthMediaFeature
69        || mediaFeature == MediaFeatureNames::min_device_widthMediaFeature;
70}
71
72static inline bool featureWithValidDensity(const AtomicString& mediaFeature, const CSSParserValue* value)
73{
74    if ((value->unit != CSSPrimitiveValue::CSS_DPPX && value->unit != CSSPrimitiveValue::CSS_DPI && value->unit != CSSPrimitiveValue::CSS_DPCM) || value->fValue <= 0)
75        return false;
76
77    return mediaFeature == MediaFeatureNames::resolutionMediaFeature
78        || mediaFeature == MediaFeatureNames::max_resolutionMediaFeature
79        || mediaFeature == MediaFeatureNames::min_resolutionMediaFeature;
80}
81
82static inline bool featureWithPositiveInteger(const AtomicString& mediaFeature, const CSSParserValue* value)
83{
84    if (!value->isInt || value->fValue < 0)
85        return false;
86
87    return mediaFeature == MediaFeatureNames::colorMediaFeature
88        || mediaFeature == MediaFeatureNames::max_colorMediaFeature
89        || mediaFeature == MediaFeatureNames::min_colorMediaFeature
90        || mediaFeature == MediaFeatureNames::color_indexMediaFeature
91        || mediaFeature == MediaFeatureNames::max_color_indexMediaFeature
92        || mediaFeature == MediaFeatureNames::min_color_indexMediaFeature
93        || mediaFeature == MediaFeatureNames::min_monochromeMediaFeature
94        || mediaFeature == MediaFeatureNames::max_monochromeMediaFeature;
95}
96
97static inline bool featureWithPositiveNumber(const AtomicString& mediaFeature, const CSSParserValue* value)
98{
99    if (value->unit != CSSPrimitiveValue::CSS_NUMBER || value->fValue < 0)
100        return false;
101
102    return mediaFeature == MediaFeatureNames::transform_2dMediaFeature
103        || mediaFeature == MediaFeatureNames::transform_3dMediaFeature
104        || mediaFeature == MediaFeatureNames::transitionMediaFeature
105        || mediaFeature == MediaFeatureNames::animationMediaFeature
106        || mediaFeature == MediaFeatureNames::device_pixel_ratioMediaFeature
107        || mediaFeature == MediaFeatureNames::max_device_pixel_ratioMediaFeature
108        || mediaFeature == MediaFeatureNames::min_device_pixel_ratioMediaFeature;
109}
110
111static inline bool featureWithZeroOrOne(const AtomicString& mediaFeature, const CSSParserValue* value)
112{
113    if (!value->isInt || !(value->fValue == 1 || !value->fValue))
114        return false;
115
116    return mediaFeature == MediaFeatureNames::gridMediaFeature
117        || mediaFeature == MediaFeatureNames::hoverMediaFeature;
118}
119
120static inline bool featureWithAspectRatio(const AtomicString& mediaFeature)
121{
122    return mediaFeature == MediaFeatureNames::aspect_ratioMediaFeature
123        || mediaFeature == MediaFeatureNames::device_aspect_ratioMediaFeature
124        || mediaFeature == MediaFeatureNames::min_aspect_ratioMediaFeature
125        || mediaFeature == MediaFeatureNames::max_aspect_ratioMediaFeature
126        || mediaFeature == MediaFeatureNames::min_device_aspect_ratioMediaFeature
127        || mediaFeature == MediaFeatureNames::max_device_aspect_ratioMediaFeature;
128}
129
130static inline bool featureWithoutValue(const AtomicString& mediaFeature)
131{
132    // Media features that are prefixed by min/max cannot be used without a value.
133    return mediaFeature == MediaFeatureNames::monochromeMediaFeature
134        || mediaFeature == MediaFeatureNames::colorMediaFeature
135        || mediaFeature == MediaFeatureNames::color_indexMediaFeature
136        || mediaFeature == MediaFeatureNames::gridMediaFeature
137        || mediaFeature == MediaFeatureNames::heightMediaFeature
138        || mediaFeature == MediaFeatureNames::widthMediaFeature
139        || mediaFeature == MediaFeatureNames::device_heightMediaFeature
140        || mediaFeature == MediaFeatureNames::device_widthMediaFeature
141        || mediaFeature == MediaFeatureNames::orientationMediaFeature
142        || mediaFeature == MediaFeatureNames::aspect_ratioMediaFeature
143        || mediaFeature == MediaFeatureNames::device_aspect_ratioMediaFeature
144        || mediaFeature == MediaFeatureNames::hoverMediaFeature
145        || mediaFeature == MediaFeatureNames::transform_2dMediaFeature
146        || mediaFeature == MediaFeatureNames::transform_3dMediaFeature
147        || mediaFeature == MediaFeatureNames::transitionMediaFeature
148        || mediaFeature == MediaFeatureNames::animationMediaFeature
149#if ENABLE(VIEW_MODE_CSS_MEDIA)
150        || mediaFeature == MediaFeatureNames::view_modeMediaFeature
151#endif // ENABLE(VIEW_MODE_CSS_MEDIA)
152        || mediaFeature == MediaFeatureNames::pointerMediaFeature
153        || mediaFeature == MediaFeatureNames::device_pixel_ratioMediaFeature
154        || mediaFeature == MediaFeatureNames::resolutionMediaFeature;
155}
156
157inline MediaQueryExp::MediaQueryExp(const AtomicString& mediaFeature, CSSParserValueList* valueList)
158    : m_mediaFeature(mediaFeature)
159    , m_value(0)
160    , m_isValid(false)
161{
162    // Initialize media query expression that must have 1 or more values.
163    if (valueList) {
164        if (valueList->size() == 1) {
165            CSSParserValue* value = valueList->current();
166
167            // Media features that use CSSValueIDs.
168            if (featureWithCSSValueID(mediaFeature, value))
169                m_value = CSSPrimitiveValue::createIdentifier(value->id);
170
171            // Media features that must have non-negative <density>, ie. dppx, dpi or dpcm.
172            else if (featureWithValidDensity(mediaFeature, value))
173                m_value = CSSPrimitiveValue::create(value->fValue, (CSSPrimitiveValue::UnitTypes) value->unit);
174
175            // Media features that must have non-negative <lenght> or number value.
176            else if (featureWithValidPositiveLenghtOrNumber(mediaFeature, value))
177                m_value = CSSPrimitiveValue::create(value->fValue, (CSSPrimitiveValue::UnitTypes) value->unit);
178
179            // Media features that must have non-negative integer value.
180            else if (featureWithPositiveInteger(mediaFeature, value))
181                m_value = CSSPrimitiveValue::create(value->fValue, CSSPrimitiveValue::CSS_NUMBER);
182
183            // Media features that must have non-negative number value.
184            else if (featureWithPositiveNumber(mediaFeature, value))
185                m_value = CSSPrimitiveValue::create(value->fValue, CSSPrimitiveValue::CSS_NUMBER);
186
187            // Media features that must have (0|1) value.
188            else if (featureWithZeroOrOne(mediaFeature, value))
189                m_value = CSSPrimitiveValue::create(value->fValue, CSSPrimitiveValue::CSS_NUMBER);
190
191            m_isValid = m_value;
192        } else if (valueList->size() == 3 && featureWithAspectRatio(mediaFeature)) {
193            // Create list of values.
194            // Currently accepts only <integer>/<integer>.
195            // Applicable to device-aspect-ratio and aspec-ratio.
196            bool isValid = true;
197            float numeratorValue = 0;
198            float denominatorValue = 0;
199
200            // The aspect-ratio must be <integer> (whitespace)? / (whitespace)? <integer>.
201            for (unsigned i = 0; i < 3; ++i, valueList->next()) {
202                const CSSParserValue* value = valueList->current();
203                if (i != 1 && value->unit == CSSPrimitiveValue::CSS_NUMBER && value->fValue > 0 && value->isInt) {
204                    if (!i)
205                        numeratorValue = value->fValue;
206                    else
207                        denominatorValue = value->fValue;
208                } else if (i == 1 && value->unit == CSSParserValue::Operator && value->iValue == '/')
209                    continue;
210                else {
211                    isValid = false;
212                    break;
213                }
214            }
215
216            if (isValid)
217                m_value = CSSAspectRatioValue::create(numeratorValue, denominatorValue);
218
219            m_isValid = m_value;
220        }
221    } else if (featureWithoutValue(mediaFeature))
222        m_isValid = true;
223}
224
225PassOwnPtr<MediaQueryExp> MediaQueryExp::create(const AtomicString& mediaFeature, CSSParserValueList* values)
226{
227    return adoptPtr(new MediaQueryExp(mediaFeature, values));
228}
229
230MediaQueryExp::~MediaQueryExp()
231{
232}
233
234String MediaQueryExp::serialize() const
235{
236    if (!m_serializationCache.isNull())
237        return m_serializationCache;
238
239    StringBuilder result;
240    result.append('(');
241    result.append(m_mediaFeature.lower());
242    if (m_value) {
243        result.appendLiteral(": ");
244        result.append(m_value->cssText());
245    }
246    result.append(')');
247
248    const_cast<MediaQueryExp*>(this)->m_serializationCache = result.toString();
249    return m_serializationCache;
250}
251
252} // namespace
253