1 | /* |
2 | * Copyright (C) 2017 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
23 | * THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "CSSStyleDeclaration.h" |
28 | |
29 | #include "CSSPropertyNames.h" |
30 | #include "CSSPropertyParser.h" |
31 | #include "DeprecatedGlobalSettings.h" |
32 | #include "HashTools.h" |
33 | #include "RuntimeEnabledFeatures.h" |
34 | #include <wtf/IsoMallocInlines.h> |
35 | #include <wtf/Optional.h> |
36 | #include <wtf/Variant.h> |
37 | |
38 | namespace WebCore { |
39 | |
40 | WTF_MAKE_ISO_ALLOCATED_IMPL(CSSStyleDeclaration); |
41 | |
42 | namespace { |
43 | |
44 | enum class PropertyNamePrefix { |
45 | None, Epub, CSS, Pixel, Pos, WebKit, |
46 | #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES) |
47 | Apple, KHTML, |
48 | #endif |
49 | }; |
50 | |
51 | template<size_t prefixCStringLength> |
52 | static inline bool matchesCSSPropertyNamePrefix(const StringImpl& propertyName, const char (&prefix)[prefixCStringLength]) |
53 | { |
54 | size_t prefixLength = prefixCStringLength - 1; |
55 | |
56 | ASSERT(toASCIILower(propertyName[0]) == prefix[0]); |
57 | const size_t offset = 1; |
58 | |
59 | #ifndef NDEBUG |
60 | for (size_t i = 0; i < prefixLength; ++i) |
61 | ASSERT(isASCIILower(prefix[i])); |
62 | ASSERT(!prefix[prefixLength]); |
63 | ASSERT(propertyName.length()); |
64 | #endif |
65 | |
66 | // The prefix within the property name must be followed by a capital letter. |
67 | // Other characters in the prefix within the property name must be lowercase. |
68 | if (propertyName.length() < prefixLength + 1) |
69 | return false; |
70 | |
71 | for (size_t i = offset; i < prefixLength; ++i) { |
72 | if (propertyName[i] != prefix[i]) |
73 | return false; |
74 | } |
75 | |
76 | if (!isASCIIUpper(propertyName[prefixLength])) |
77 | return false; |
78 | |
79 | return true; |
80 | } |
81 | |
82 | static PropertyNamePrefix propertyNamePrefix(const StringImpl& propertyName) |
83 | { |
84 | ASSERT(propertyName.length()); |
85 | |
86 | // First character of the prefix within the property name may be upper or lowercase. |
87 | UChar firstChar = toASCIILower(propertyName[0]); |
88 | switch (firstChar) { |
89 | #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES) |
90 | case 'a': |
91 | if (RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled() && matchesCSSPropertyNamePrefix(propertyName, "apple" )) |
92 | return PropertyNamePrefix::Apple; |
93 | break; |
94 | #endif |
95 | case 'c': |
96 | if (matchesCSSPropertyNamePrefix(propertyName, "css" )) |
97 | return PropertyNamePrefix::CSS; |
98 | break; |
99 | #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES) |
100 | case 'k': |
101 | if (RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled() && matchesCSSPropertyNamePrefix(propertyName, "khtml" )) |
102 | return PropertyNamePrefix::KHTML; |
103 | break; |
104 | #endif |
105 | case 'e': |
106 | if (matchesCSSPropertyNamePrefix(propertyName, "epub" )) |
107 | return PropertyNamePrefix::Epub; |
108 | break; |
109 | case 'p': |
110 | if (matchesCSSPropertyNamePrefix(propertyName, "pos" )) |
111 | return PropertyNamePrefix::Pos; |
112 | if (matchesCSSPropertyNamePrefix(propertyName, "pixel" )) |
113 | return PropertyNamePrefix::Pixel; |
114 | break; |
115 | case 'w': |
116 | if (matchesCSSPropertyNamePrefix(propertyName, "webkit" )) |
117 | return PropertyNamePrefix::WebKit; |
118 | break; |
119 | default: |
120 | break; |
121 | } |
122 | return PropertyNamePrefix::None; |
123 | } |
124 | |
125 | static inline void writeWebKitPrefix(char*& buffer) |
126 | { |
127 | *buffer++ = '-'; |
128 | *buffer++ = 'w'; |
129 | *buffer++ = 'e'; |
130 | *buffer++ = 'b'; |
131 | *buffer++ = 'k'; |
132 | *buffer++ = 'i'; |
133 | *buffer++ = 't'; |
134 | *buffer++ = '-'; |
135 | } |
136 | |
137 | static inline void writeEpubPrefix(char*& buffer) |
138 | { |
139 | *buffer++ = '-'; |
140 | *buffer++ = 'e'; |
141 | *buffer++ = 'p'; |
142 | *buffer++ = 'u'; |
143 | *buffer++ = 'b'; |
144 | *buffer++ = '-'; |
145 | } |
146 | |
147 | struct CSSPropertyInfo { |
148 | CSSPropertyID propertyID; |
149 | bool hadPixelOrPosPrefix; |
150 | }; |
151 | |
152 | static CSSPropertyInfo parseJavaScriptCSSPropertyName(const AtomicString& propertyName) |
153 | { |
154 | using CSSPropertyInfoMap = HashMap<String, CSSPropertyInfo>; |
155 | static NeverDestroyed<CSSPropertyInfoMap> propertyInfoCache; |
156 | |
157 | CSSPropertyInfo propertyInfo = { CSSPropertyInvalid, false }; |
158 | |
159 | auto* propertyNameString = propertyName.impl(); |
160 | if (!propertyNameString) |
161 | return propertyInfo; |
162 | unsigned length = propertyNameString->length(); |
163 | if (!length) |
164 | return propertyInfo; |
165 | |
166 | propertyInfo = propertyInfoCache.get().get(propertyNameString); |
167 | if (propertyInfo.propertyID) |
168 | return propertyInfo; |
169 | |
170 | bool hadPixelOrPosPrefix = false; |
171 | |
172 | constexpr size_t bufferSize = maxCSSPropertyNameLength + 1; |
173 | char buffer[bufferSize]; |
174 | char* bufferPtr = buffer; |
175 | const char* name = bufferPtr; |
176 | |
177 | unsigned i = 0; |
178 | // Prefixes CSS, Pixel, Pos are ignored. |
179 | // Prefixes Apple, KHTML and Webkit are transposed to "-webkit-". |
180 | // The prefix "Epub" becomes "-epub-". |
181 | switch (propertyNamePrefix(*propertyNameString)) { |
182 | case PropertyNamePrefix::None: |
183 | if (isASCIIUpper((*propertyNameString)[0])) |
184 | return propertyInfo; |
185 | break; |
186 | case PropertyNamePrefix::CSS: |
187 | i += 3; |
188 | break; |
189 | case PropertyNamePrefix::Pixel: |
190 | i += 5; |
191 | hadPixelOrPosPrefix = true; |
192 | break; |
193 | case PropertyNamePrefix::Pos: |
194 | i += 3; |
195 | hadPixelOrPosPrefix = true; |
196 | break; |
197 | #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES) |
198 | case PropertyNamePrefix::Apple: |
199 | case PropertyNamePrefix::KHTML: |
200 | ASSERT(RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled()); |
201 | writeWebKitPrefix(bufferPtr); |
202 | i += 5; |
203 | break; |
204 | #endif |
205 | case PropertyNamePrefix::Epub: |
206 | writeEpubPrefix(bufferPtr); |
207 | i += 4; |
208 | break; |
209 | case PropertyNamePrefix::WebKit: |
210 | writeWebKitPrefix(bufferPtr); |
211 | i += 6; |
212 | break; |
213 | } |
214 | |
215 | *bufferPtr++ = toASCIILower((*propertyNameString)[i++]); |
216 | |
217 | char* bufferEnd = buffer + bufferSize; |
218 | char* stringEnd = bufferEnd - 1; |
219 | size_t bufferSizeLeft = stringEnd - bufferPtr; |
220 | size_t propertySizeLeft = length - i; |
221 | if (propertySizeLeft > bufferSizeLeft) |
222 | return propertyInfo; |
223 | |
224 | for (; i < length; ++i) { |
225 | UChar c = (*propertyNameString)[i]; |
226 | if (!c || !isASCII(c)) |
227 | return propertyInfo; // illegal character |
228 | if (isASCIIUpper(c)) { |
229 | size_t bufferSizeLeft = stringEnd - bufferPtr; |
230 | size_t propertySizeLeft = length - i + 1; |
231 | if (propertySizeLeft > bufferSizeLeft) |
232 | return propertyInfo; |
233 | *bufferPtr++ = '-'; |
234 | *bufferPtr++ = toASCIILowerUnchecked(c); |
235 | } else |
236 | *bufferPtr++ = c; |
237 | ASSERT_WITH_SECURITY_IMPLICATION(bufferPtr < bufferEnd); |
238 | } |
239 | ASSERT_WITH_SECURITY_IMPLICATION(bufferPtr < bufferEnd); |
240 | *bufferPtr = '\0'; |
241 | |
242 | unsigned outputLength = bufferPtr - buffer; |
243 | #if PLATFORM(IOS_FAMILY) |
244 | cssPropertyNameIOSAliasing(buffer, name, outputLength); |
245 | #endif |
246 | |
247 | auto* hashTableEntry = findProperty(name, outputLength); |
248 | if (auto propertyID = hashTableEntry ? hashTableEntry->id : 0) { |
249 | auto id = static_cast<CSSPropertyID>(propertyID); |
250 | if (isEnabledCSSProperty(id)) { |
251 | propertyInfo.hadPixelOrPosPrefix = hadPixelOrPosPrefix; |
252 | propertyInfo.propertyID = id; |
253 | propertyInfoCache.get().add(propertyNameString, propertyInfo); |
254 | } |
255 | } |
256 | return propertyInfo; |
257 | } |
258 | |
259 | } |
260 | |
261 | CSSPropertyID CSSStyleDeclaration::getCSSPropertyIDFromJavaScriptPropertyName(const AtomicString& propertyName) |
262 | { |
263 | return parseJavaScriptCSSPropertyName(propertyName).propertyID; |
264 | } |
265 | |
266 | Optional<Variant<String, double>> CSSStyleDeclaration::namedItem(const AtomicString& propertyName) |
267 | { |
268 | auto propertyInfo = parseJavaScriptCSSPropertyName(propertyName); |
269 | if (!propertyInfo.propertyID) |
270 | return WTF::nullopt; |
271 | |
272 | auto value = getPropertyCSSValueInternal(propertyInfo.propertyID); |
273 | if (!value) { |
274 | // If the property is a shorthand property (such as "padding"), it can only be accessed using getPropertyValue. |
275 | return Variant<String, double> { getPropertyValueInternal(propertyInfo.propertyID) }; |
276 | } |
277 | |
278 | if (propertyInfo.hadPixelOrPosPrefix && is<CSSPrimitiveValue>(*value)) { |
279 | // Call this version of the getter so that, e.g., pixelTop returns top as a number |
280 | // in pixel units and posTop should does the same _if_ this is a positioned element. |
281 | // FIXME: If not a positioned element, MSIE documentation says posTop should return 0; this rule is not implemented. |
282 | return Variant<String, double> { downcast<CSSPrimitiveValue>(*value).floatValue(CSSPrimitiveValue::CSS_PX) }; |
283 | } |
284 | |
285 | return Variant<String, double> { value->cssText() }; |
286 | } |
287 | |
288 | ExceptionOr<void> CSSStyleDeclaration::setNamedItem(const AtomicString& propertyName, String value, bool& propertySupported) |
289 | { |
290 | auto propertyInfo = parseJavaScriptCSSPropertyName(propertyName); |
291 | if (!propertyInfo.propertyID) { |
292 | propertySupported = false; |
293 | return { }; |
294 | } |
295 | |
296 | propertySupported = true; |
297 | |
298 | if (propertyInfo.hadPixelOrPosPrefix) |
299 | value.append("px" ); |
300 | |
301 | bool important = false; |
302 | if (DeprecatedGlobalSettings::shouldRespectPriorityInCSSAttributeSetters()) { |
303 | auto importantIndex = value.findIgnoringASCIICase("!important" ); |
304 | if (importantIndex && importantIndex != notFound) { |
305 | important = true; |
306 | value = value.left(importantIndex - 1); |
307 | } |
308 | } |
309 | |
310 | auto setPropertyInternalResult = setPropertyInternal(propertyInfo.propertyID, value, important); |
311 | if (setPropertyInternalResult.hasException()) |
312 | return setPropertyInternalResult.releaseException(); |
313 | |
314 | return { }; |
315 | } |
316 | |
317 | Vector<AtomicString> CSSStyleDeclaration::supportedPropertyNames() const |
318 | { |
319 | static unsigned numNames = 0; |
320 | static const AtomicString* const cssPropertyNames = [] { |
321 | String names[numCSSProperties]; |
322 | for (int i = 0; i < numCSSProperties; ++i) { |
323 | CSSPropertyID id = static_cast<CSSPropertyID>(firstCSSProperty + i); |
324 | if (isEnabledCSSProperty(id)) |
325 | names[numNames++] = getJSPropertyName(id); |
326 | } |
327 | std::sort(&names[0], &names[numNames], WTF::codePointCompareLessThan); |
328 | auto* identifiers = new AtomicString[numNames]; |
329 | for (unsigned i = 0; i < numNames; ++i) |
330 | identifiers[i] = names[i]; |
331 | return identifiers; |
332 | }(); |
333 | |
334 | Vector<AtomicString> result; |
335 | result.reserveInitialCapacity(numNames); |
336 | |
337 | for (unsigned i = 0; i < numNames; ++i) |
338 | result.uncheckedAppend(cssPropertyNames[i]); |
339 | |
340 | return result; |
341 | } |
342 | |
343 | String CSSStyleDeclaration::cssFloat() |
344 | { |
345 | return getPropertyValueInternal(CSSPropertyFloat); |
346 | } |
347 | |
348 | ExceptionOr<void> CSSStyleDeclaration::setCssFloat(const String& value) |
349 | { |
350 | auto result = setPropertyInternal(CSSPropertyFloat, value, false /* important */); |
351 | if (result.hasException()) |
352 | return result.releaseException(); |
353 | return { }; |
354 | } |
355 | |
356 | } |
357 | |