[Web Animations] Complete support for keyframe animations
[WebKit-https.git] / Source / WebCore / css / CSSStyleDeclaration.cpp
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
35 namespace WebCore {
36
37 namespace {
38
39 enum class PropertyNamePrefix {
40     None, Epub, CSS, Pixel, Pos, WebKit,
41 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
42     Apple, KHTML,
43 #endif
44 };
45
46 template<size_t prefixCStringLength>
47 static inline bool matchesCSSPropertyNamePrefix(const StringImpl& propertyName, const char (&prefix)[prefixCStringLength])
48 {
49     size_t prefixLength = prefixCStringLength - 1;
50
51     ASSERT(toASCIILower(propertyName[0]) == prefix[0]);
52     const size_t offset = 1;
53
54 #ifndef NDEBUG
55     for (size_t i = 0; i < prefixLength; ++i)
56         ASSERT(isASCIILower(prefix[i]));
57     ASSERT(!prefix[prefixLength]);
58     ASSERT(propertyName.length());
59 #endif
60
61     // The prefix within the property name must be followed by a capital letter.
62     // Other characters in the prefix within the property name must be lowercase.
63     if (propertyName.length() < prefixLength + 1)
64         return false;
65
66     for (size_t i = offset; i < prefixLength; ++i) {
67         if (propertyName[i] != prefix[i])
68             return false;
69     }
70
71     if (!isASCIIUpper(propertyName[prefixLength]))
72         return false;
73
74     return true;
75 }
76
77 static PropertyNamePrefix propertyNamePrefix(const StringImpl& propertyName)
78 {
79     ASSERT(propertyName.length());
80
81     // First character of the prefix within the property name may be upper or lowercase.
82     UChar firstChar = toASCIILower(propertyName[0]);
83     switch (firstChar) {
84 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
85     case 'a':
86         if (RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled() && matchesCSSPropertyNamePrefix(propertyName, "apple"))
87             return PropertyNamePrefix::Apple;
88         break;
89 #endif
90     case 'c':
91         if (matchesCSSPropertyNamePrefix(propertyName, "css"))
92             return PropertyNamePrefix::CSS;
93         break;
94 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
95     case 'k':
96         if (RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled() && matchesCSSPropertyNamePrefix(propertyName, "khtml"))
97             return PropertyNamePrefix::KHTML;
98         break;
99 #endif
100     case 'e':
101         if (matchesCSSPropertyNamePrefix(propertyName, "epub"))
102             return PropertyNamePrefix::Epub;
103         break;
104     case 'p':
105         if (matchesCSSPropertyNamePrefix(propertyName, "pos"))
106             return PropertyNamePrefix::Pos;
107         if (matchesCSSPropertyNamePrefix(propertyName, "pixel"))
108             return PropertyNamePrefix::Pixel;
109         break;
110     case 'w':
111         if (matchesCSSPropertyNamePrefix(propertyName, "webkit"))
112             return PropertyNamePrefix::WebKit;
113         break;
114     default:
115         break;
116     }
117     return PropertyNamePrefix::None;
118 }
119
120 static inline void writeWebKitPrefix(char*& buffer)
121 {
122     *buffer++ = '-';
123     *buffer++ = 'w';
124     *buffer++ = 'e';
125     *buffer++ = 'b';
126     *buffer++ = 'k';
127     *buffer++ = 'i';
128     *buffer++ = 't';
129     *buffer++ = '-';
130 }
131
132 static inline void writeEpubPrefix(char*& buffer)
133 {
134     *buffer++ = '-';
135     *buffer++ = 'e';
136     *buffer++ = 'p';
137     *buffer++ = 'u';
138     *buffer++ = 'b';
139     *buffer++ = '-';
140 }
141
142 struct CSSPropertyInfo {
143     CSSPropertyID propertyID;
144     bool hadPixelOrPosPrefix;
145 };
146
147 static CSSPropertyInfo parseJavaScriptCSSPropertyName(const AtomicString& propertyName)
148 {
149     using CSSPropertyInfoMap = HashMap<String, CSSPropertyInfo>;
150     static NeverDestroyed<CSSPropertyInfoMap> propertyInfoCache;
151
152     CSSPropertyInfo propertyInfo = { CSSPropertyInvalid, false };
153
154     auto* propertyNameString = propertyName.impl();
155     if (!propertyNameString)
156         return propertyInfo;
157     unsigned length = propertyNameString->length();
158     if (!length)
159         return propertyInfo;
160
161     propertyInfo = propertyInfoCache.get().get(propertyNameString);
162     if (propertyInfo.propertyID)
163         return propertyInfo;
164
165     bool hadPixelOrPosPrefix = false;
166
167     constexpr size_t bufferSize = maxCSSPropertyNameLength + 1;
168     char buffer[bufferSize];
169     char* bufferPtr = buffer;
170     const char* name = bufferPtr;
171
172     unsigned i = 0;
173     // Prefixes CSS, Pixel, Pos are ignored.
174     // Prefixes Apple, KHTML and Webkit are transposed to "-webkit-".
175     // The prefix "Epub" becomes "-epub-".
176     switch (propertyNamePrefix(*propertyNameString)) {
177     case PropertyNamePrefix::None:
178         if (isASCIIUpper((*propertyNameString)[0]))
179             return propertyInfo;
180         break;
181     case PropertyNamePrefix::CSS:
182         i += 3;
183         break;
184     case PropertyNamePrefix::Pixel:
185         i += 5;
186         hadPixelOrPosPrefix = true;
187         break;
188     case PropertyNamePrefix::Pos:
189         i += 3;
190         hadPixelOrPosPrefix = true;
191         break;
192 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
193     case PropertyNamePrefix::Apple:
194     case PropertyNamePrefix::KHTML:
195         ASSERT(RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled());
196         writeWebKitPrefix(bufferPtr);
197         i += 5;
198         break;
199 #endif
200     case PropertyNamePrefix::Epub:
201         writeEpubPrefix(bufferPtr);
202         i += 4;
203         break;
204     case PropertyNamePrefix::WebKit:
205         writeWebKitPrefix(bufferPtr);
206         i += 6;
207         break;
208     }
209
210     *bufferPtr++ = toASCIILower((*propertyNameString)[i++]);
211
212     char* bufferEnd = buffer + bufferSize;
213     char* stringEnd = bufferEnd - 1;
214     size_t bufferSizeLeft = stringEnd - bufferPtr;
215     size_t propertySizeLeft = length - i;
216     if (propertySizeLeft > bufferSizeLeft)
217         return propertyInfo;
218
219     for (; i < length; ++i) {
220         UChar c = (*propertyNameString)[i];
221         if (!c || !isASCII(c))
222             return propertyInfo; // illegal character
223         if (isASCIIUpper(c)) {
224             size_t bufferSizeLeft = stringEnd - bufferPtr;
225             size_t propertySizeLeft = length - i + 1;
226             if (propertySizeLeft > bufferSizeLeft)
227                 return propertyInfo;
228             *bufferPtr++ = '-';
229             *bufferPtr++ = toASCIILowerUnchecked(c);
230         } else
231             *bufferPtr++ = c;
232         ASSERT_WITH_SECURITY_IMPLICATION(bufferPtr < bufferEnd);
233     }
234     ASSERT_WITH_SECURITY_IMPLICATION(bufferPtr < bufferEnd);
235     *bufferPtr = '\0';
236
237     unsigned outputLength = bufferPtr - buffer;
238 #if PLATFORM(IOS)
239     cssPropertyNameIOSAliasing(buffer, name, outputLength);
240 #endif
241
242     auto* hashTableEntry = findProperty(name, outputLength);
243     if (auto propertyID = hashTableEntry ? hashTableEntry->id : 0) {
244         propertyInfo.hadPixelOrPosPrefix = hadPixelOrPosPrefix;
245         propertyInfo.propertyID = static_cast<CSSPropertyID>(propertyID);
246         propertyInfoCache.get().add(propertyNameString, propertyInfo);
247     }
248     return propertyInfo;
249 }
250
251 }
252
253 CSSPropertyID CSSStyleDeclaration::getCSSPropertyIDFromJavaScriptPropertyName(const AtomicString& propertyName)
254 {
255     return parseJavaScriptCSSPropertyName(propertyName).propertyID;
256 }
257
258 std::optional<Variant<String, double>> CSSStyleDeclaration::namedItem(const AtomicString& propertyName)
259 {
260     auto propertyInfo = parseJavaScriptCSSPropertyName(propertyName);
261     if (!propertyInfo.propertyID)
262         return std::nullopt;
263
264     auto value = getPropertyCSSValueInternal(propertyInfo.propertyID);
265     if (!value) {
266         // If the property is a shorthand property (such as "padding"), it can only be accessed using getPropertyValue.
267         return Variant<String, double> { getPropertyValueInternal(propertyInfo.propertyID) };
268     }
269     
270     if (propertyInfo.hadPixelOrPosPrefix && is<CSSPrimitiveValue>(*value)) {
271         // Call this version of the getter so that, e.g., pixelTop returns top as a number
272         // in pixel units and posTop should does the same _if_ this is a positioned element.
273         // FIXME: If not a positioned element, MSIE documentation says posTop should return 0; this rule is not implemented.
274         return Variant<String, double> { downcast<CSSPrimitiveValue>(*value).floatValue(CSSPrimitiveValue::CSS_PX) };
275     }
276
277     return Variant<String, double> { value->cssText() };
278 }
279
280 ExceptionOr<void> CSSStyleDeclaration::setNamedItem(const AtomicString& propertyName, String value, bool& propertySupported)
281 {
282     auto propertyInfo = parseJavaScriptCSSPropertyName(propertyName);
283     if (!propertyInfo.propertyID) {
284         propertySupported = false;
285         return { };
286     }
287
288     propertySupported = true;
289
290     if (propertyInfo.hadPixelOrPosPrefix)
291         value.append("px");
292
293     bool important = false;
294     if (DeprecatedGlobalSettings::shouldRespectPriorityInCSSAttributeSetters()) {
295         auto importantIndex = value.findIgnoringASCIICase("!important");
296         if (importantIndex && importantIndex != notFound) {
297             important = true;
298             value = value.left(importantIndex - 1);
299         }
300     }
301
302     auto setPropertyInternalResult = setPropertyInternal(propertyInfo.propertyID, value, important);
303     if (setPropertyInternalResult.hasException())
304         return setPropertyInternalResult.releaseException();
305
306     return { };
307 }
308
309 Vector<AtomicString> CSSStyleDeclaration::supportedPropertyNames() const
310 {
311     static const AtomicString* const cssPropertyNames = [] {
312         String names[numCSSProperties];
313         for (int i = 0; i < numCSSProperties; ++i)
314             names[i] = getJSPropertyName(static_cast<CSSPropertyID>(firstCSSProperty + i));
315         std::sort(&names[0], &names[numCSSProperties], WTF::codePointCompareLessThan);
316         auto* identifiers = new AtomicString[numCSSProperties];
317         for (int i = 0; i < numCSSProperties; ++i)
318             identifiers[i] = names[i];
319         return identifiers;
320     }();
321
322     Vector<AtomicString> result;
323     result.reserveInitialCapacity(numCSSProperties);
324
325     for (unsigned i = 0; i < numCSSProperties; ++i)
326         result.uncheckedAppend(cssPropertyNames[i]);
327
328     return result;
329 }
330
331 String CSSStyleDeclaration::cssFloat()
332 {
333     return getPropertyValueInternal(CSSPropertyFloat);
334 }
335
336 ExceptionOr<void> CSSStyleDeclaration::setCssFloat(const String& value)
337 {
338     auto result = setPropertyInternal(CSSPropertyFloat, value, false /* important */);
339     if (result.hasException())
340         return result.releaseException();
341     return { };
342 }
343
344 }