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
38namespace WebCore {
39
40WTF_MAKE_ISO_ALLOCATED_IMPL(CSSStyleDeclaration);
41
42namespace {
43
44enum class PropertyNamePrefix {
45 None, Epub, CSS, Pixel, Pos, WebKit,
46#if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
47 Apple, KHTML,
48#endif
49};
50
51template<size_t prefixCStringLength>
52static 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
82static 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
125static 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
137static inline void writeEpubPrefix(char*& buffer)
138{
139 *buffer++ = '-';
140 *buffer++ = 'e';
141 *buffer++ = 'p';
142 *buffer++ = 'u';
143 *buffer++ = 'b';
144 *buffer++ = '-';
145}
146
147struct CSSPropertyInfo {
148 CSSPropertyID propertyID;
149 bool hadPixelOrPosPrefix;
150};
151
152static 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
261CSSPropertyID CSSStyleDeclaration::getCSSPropertyIDFromJavaScriptPropertyName(const AtomicString& propertyName)
262{
263 return parseJavaScriptCSSPropertyName(propertyName).propertyID;
264}
265
266Optional<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
288ExceptionOr<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
317Vector<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
343String CSSStyleDeclaration::cssFloat()
344{
345 return getPropertyValueInternal(CSSPropertyFloat);
346}
347
348ExceptionOr<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