4373e8b3f172d8a025990657b2425d58993e10d3
[WebKit-https.git] / Source / WebCore / css / MediaQueryExpression.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 "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
40 namespace WebCore {
41
42 static inline bool featureWithValidIdent(const AtomString& 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
64 static 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
74 static 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
93 static 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
106 static inline bool featureWithPositiveInteger(const String& mediaFeature, const CSSPrimitiveValue& value)
107 {
108     if (!value.isNumber())
109         return false;
110     return featureExpectingPositiveInteger(mediaFeature);
111 }
112
113 static 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
127 static 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
135 static inline bool isAspectRatioFeature(const AtomString& 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
145 static inline bool isFeatureValidWithoutValue(const AtomString& 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
181 inline 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
203 MediaQueryExpression::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
240 String 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
258 TextStream& operator<<(TextStream& ts, const MediaQueryExpression& expression)
259 {
260     ts << expression.serialize();
261     return ts;
262 }
263
264
265 } // namespace