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 AtomicString& 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 AtomicString& 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 AtomicString& 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 |
266 | |