Use NeverDestroyed instead of DEPRECATED_DEFINE_STATIC_LOCAL
[WebKit-https.git] / Source / WebCore / bindings / js / JSCSSStyleDeclarationCustom.cpp
1 /*
2  * Copyright (C) 2007, 2008, 2009, 2013 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "JSCSSStyleDeclarationCustom.h"
28
29 #include "CSSParser.h"
30 #include "CSSPrimitiveValue.h"
31 #include "CSSPropertyNames.h"
32 #include "CSSValue.h"
33 #include "HashTools.h"
34 #include "JSCSSValue.h"
35 #include "JSNode.h"
36 #include "RuntimeEnabledFeatures.h"
37 #include "Settings.h"
38 #include "StyleProperties.h"
39 #include "StyledElement.h"
40 #include <runtime/IdentifierInlines.h>
41 #include <runtime/StringPrototype.h>
42 #include <wtf/ASCIICType.h>
43 #include <wtf/text/AtomicString.h>
44 #include <wtf/text/StringBuilder.h>
45 #include <wtf/text/StringConcatenate.h>
46 #include <wtf/text/WTFString.h>
47
48 using namespace JSC;
49
50 namespace WebCore {
51
52 void JSCSSStyleDeclaration::visitAdditionalChildren(SlotVisitor& visitor)
53 {
54     visitor.addOpaqueRoot(root(&wrapped()));
55 }
56
57 class CSSPropertyInfo {
58 public:
59     CSSPropertyID propertyID;
60     bool hadPixelOrPosPrefix;
61 };
62
63 enum PropertyNamePrefix {
64     PropertyNamePrefixNone,
65     PropertyNamePrefixCSS,
66     PropertyNamePrefixPixel,
67     PropertyNamePrefixPos,
68 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
69     PropertyNamePrefixApple,
70 #endif
71     PropertyNamePrefixEpub,
72 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
73     PropertyNamePrefixKHTML,
74 #endif
75     PropertyNamePrefixWebKit
76 };
77
78 template<size_t prefixCStringLength>
79 static inline bool matchesCSSPropertyNamePrefix(const StringImpl& propertyName, const char (&prefix)[prefixCStringLength])
80 {
81     size_t prefixLength = prefixCStringLength - 1;
82
83     ASSERT(toASCIILower(propertyName[0]) == prefix[0]);
84     const size_t offset = 1;
85
86 #ifndef NDEBUG
87     for (size_t i = 0; i < prefixLength; ++i)
88         ASSERT(isASCIILower(prefix[i]));
89     ASSERT(!prefix[prefixLength]);
90     ASSERT(propertyName.length());
91 #endif
92
93     // The prefix within the property name must be followed by a capital letter.
94     // Other characters in the prefix within the property name must be lowercase.
95     if (propertyName.length() < (prefixLength + 1))
96         return false;
97
98     for (size_t i = offset; i < prefixLength; ++i) {
99         if (propertyName[i] != prefix[i])
100             return false;
101     }
102
103     if (!isASCIIUpper(propertyName[prefixLength]))
104         return false;
105     return true;
106 }
107
108 static PropertyNamePrefix getCSSPropertyNamePrefix(const StringImpl& propertyName)
109 {
110     ASSERT(propertyName.length());
111
112     // First character of the prefix within the property name may be upper or lowercase.
113     UChar firstChar = toASCIILower(propertyName[0]);
114     switch (firstChar) {
115 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
116     case 'a':
117         if (RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled() && matchesCSSPropertyNamePrefix(propertyName, "apple"))
118             return PropertyNamePrefixApple;
119         break;
120 #endif
121     case 'c':
122         if (matchesCSSPropertyNamePrefix(propertyName, "css"))
123             return PropertyNamePrefixCSS;
124         break;
125 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
126     case 'k':
127         if (RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled() && matchesCSSPropertyNamePrefix(propertyName, "khtml"))
128             return PropertyNamePrefixKHTML;
129         break;
130 #endif
131     case 'e':
132         if (matchesCSSPropertyNamePrefix(propertyName, "epub"))
133             return PropertyNamePrefixEpub;
134         break;
135     case 'p':
136         if (matchesCSSPropertyNamePrefix(propertyName, "pos"))
137             return PropertyNamePrefixPos;
138         if (matchesCSSPropertyNamePrefix(propertyName, "pixel"))
139             return PropertyNamePrefixPixel;
140         break;
141     case 'w':
142         if (matchesCSSPropertyNamePrefix(propertyName, "webkit"))
143             return PropertyNamePrefixWebKit;
144         break;
145     default:
146         break;
147     }
148     return PropertyNamePrefixNone;
149 }
150
151 static inline void writeWebKitPrefix(char*& buffer)
152 {
153     *buffer++ = '-';
154     *buffer++ = 'w';
155     *buffer++ = 'e';
156     *buffer++ = 'b';
157     *buffer++ = 'k';
158     *buffer++ = 'i';
159     *buffer++ = 't';
160     *buffer++ = '-';
161 }
162
163 static inline void writeEpubPrefix(char*& buffer)
164 {
165     *buffer++ = '-';
166     *buffer++ = 'e';
167     *buffer++ = 'p';
168     *buffer++ = 'u';
169     *buffer++ = 'b';
170     *buffer++ = '-';
171 }
172
173 static CSSPropertyInfo cssPropertyIDForJSCSSPropertyName(PropertyName propertyName)
174 {
175     CSSPropertyInfo propertyInfo = {CSSPropertyInvalid, false};
176     bool hadPixelOrPosPrefix = false;
177
178     StringImpl* propertyNameString = propertyName.publicName();
179     if (!propertyNameString)
180         return propertyInfo;
181     unsigned length = propertyNameString->length();
182     if (!length)
183         return propertyInfo;
184
185     String stringForCache = String(propertyNameString);
186     typedef HashMap<String, CSSPropertyInfo> CSSPropertyInfoMap;
187     static NeverDestroyed<CSSPropertyInfoMap> propertyInfoCache;
188     propertyInfo = propertyInfoCache.get().get(stringForCache);
189     if (propertyInfo.propertyID)
190         return propertyInfo;
191
192     const size_t bufferSize = maxCSSPropertyNameLength + 1;
193     char buffer[bufferSize];
194     char* bufferPtr = buffer;
195     const char* name = bufferPtr;
196
197     unsigned i = 0;
198     // Prefixes CSS, Pixel, Pos are ignored.
199     // Prefixes Apple, KHTML and Webkit are transposed to "-webkit-".
200     // The prefix "Epub" becomes "-epub-".
201     switch (getCSSPropertyNamePrefix(*propertyNameString)) {
202     case PropertyNamePrefixNone:
203         if (isASCIIUpper((*propertyNameString)[0]))
204             return propertyInfo;
205         break;
206     case PropertyNamePrefixCSS:
207         i += 3;
208         break;
209     case PropertyNamePrefixPixel:
210         i += 5;
211         hadPixelOrPosPrefix = true;
212         break;
213     case PropertyNamePrefixPos:
214         i += 3;
215         hadPixelOrPosPrefix = true;
216         break;
217 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
218     case PropertyNamePrefixApple:
219     case PropertyNamePrefixKHTML:
220         ASSERT(RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled());
221         writeWebKitPrefix(bufferPtr);
222         i += 5;
223         break;
224 #endif
225     case PropertyNamePrefixEpub:
226         writeEpubPrefix(bufferPtr);
227         i += 4;
228         break;
229     case PropertyNamePrefixWebKit:
230         writeWebKitPrefix(bufferPtr);
231         i += 6;
232         break;
233     }
234
235     *bufferPtr++ = toASCIILower((*propertyNameString)[i++]);
236
237     char* bufferEnd = buffer + bufferSize;
238     char* stringEnd = bufferEnd - 1;
239     size_t bufferSizeLeft = stringEnd - bufferPtr;
240     size_t propertySizeLeft = length - i;
241     if (propertySizeLeft > bufferSizeLeft)
242         return propertyInfo;
243
244     for (; i < length; ++i) {
245         UChar c = (*propertyNameString)[i];
246         if (!c || c >= 0x7F)
247             return propertyInfo; // illegal character
248         if (isASCIIUpper(c)) {
249             size_t bufferSizeLeft = stringEnd - bufferPtr;
250             size_t propertySizeLeft = length - i + 1;
251             if (propertySizeLeft > bufferSizeLeft)
252                 return propertyInfo;
253             *bufferPtr++ = '-';
254             *bufferPtr++ = toASCIILower(c);
255         } else
256             *bufferPtr++ = c;
257         ASSERT_WITH_SECURITY_IMPLICATION(bufferPtr < bufferEnd);
258     }
259     ASSERT_WITH_SECURITY_IMPLICATION(bufferPtr < bufferEnd);
260     *bufferPtr = '\0';
261
262     unsigned outputLength = bufferPtr - buffer;
263 #if PLATFORM(IOS)
264     cssPropertyNameIOSAliasing(buffer, name, outputLength);
265 #endif
266
267     const Property* hashTableEntry = findProperty(name, outputLength);
268     int propertyID = hashTableEntry ? hashTableEntry->id : 0;
269     if (propertyID) {
270         propertyInfo.hadPixelOrPosPrefix = hadPixelOrPosPrefix;
271         propertyInfo.propertyID = static_cast<CSSPropertyID>(propertyID);
272         propertyInfoCache.get().add(stringForCache, propertyInfo);
273     }
274     return propertyInfo;
275 }
276
277 static inline JSValue getPropertyValueFallback(ExecState* exec, JSCSSStyleDeclaration* thisObj, unsigned index)
278 {
279     // If the property is a shorthand property (such as "padding"),
280     // it can only be accessed using getPropertyValue.
281     return jsStringWithCache(exec, thisObj->wrapped().getPropertyValueInternal(static_cast<CSSPropertyID>(index)));
282 }
283
284 static inline JSValue cssPropertyGetterPixelOrPosPrefix(ExecState* exec, JSCSSStyleDeclaration* thisObj, unsigned propertyID)
285 {
286     // Set up pixelOrPos boolean to handle the fact that
287     // pixelTop returns "CSS Top" as number value in unit pixels
288     // posTop returns "CSS top" as number value in unit pixels _if_ its a
289     // positioned element. if it is not a positioned element, return 0
290     // from MSIE documentation FIXME: IMPLEMENT THAT (Dirk)
291     RefPtr<CSSValue> v = thisObj->wrapped().getPropertyCSSValueInternal(static_cast<CSSPropertyID>(propertyID));
292     if (v) {
293         if (v->isPrimitiveValue())
294             return jsNumber(static_pointer_cast<CSSPrimitiveValue>(v)->getFloatValue(CSSPrimitiveValue::CSS_PX));
295         return jsStringOrNull(exec, v->cssText());
296     }
297
298     return getPropertyValueFallback(exec, thisObj, propertyID);
299 }
300
301 static inline JSValue cssPropertyGetter(ExecState* exec, JSCSSStyleDeclaration* thisObj, unsigned propertyID)
302 {
303     RefPtr<CSSValue> v = thisObj->wrapped().getPropertyCSSValueInternal(static_cast<CSSPropertyID>(propertyID));
304     if (v)
305         return jsStringOrNull(exec, v->cssText());
306
307     return getPropertyValueFallback(exec, thisObj, propertyID);
308 }
309
310 bool JSCSSStyleDeclaration::getOwnPropertySlotDelegate(ExecState* exec, PropertyName propertyIdentifier, PropertySlot& slot)
311 {
312     CSSPropertyInfo propertyInfo = cssPropertyIDForJSCSSPropertyName(propertyIdentifier);
313     if (!propertyInfo.propertyID)
314         return false;
315
316     if (propertyInfo.hadPixelOrPosPrefix)
317         slot.setValue(this, DontDelete, cssPropertyGetterPixelOrPosPrefix(exec, this, propertyInfo.propertyID));
318     else
319         slot.setValue(this, DontDelete, cssPropertyGetter(exec, this, propertyInfo.propertyID));
320     return true;
321 }
322
323 bool JSCSSStyleDeclaration::putDelegate(ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot&)
324 {
325     CSSPropertyInfo propertyInfo = cssPropertyIDForJSCSSPropertyName(propertyName);
326     if (!propertyInfo.propertyID)
327         return false;
328
329     String propValue = valueToStringWithNullCheck(exec, value);
330     if (propertyInfo.hadPixelOrPosPrefix)
331         propValue.append("px");
332
333     bool important = false;
334     if (Settings::shouldRespectPriorityInCSSAttributeSetters()) {
335         size_t importantIndex = propValue.find("!important", 0, false);
336         if (importantIndex != notFound) {
337             important = true;
338             propValue = propValue.left(importantIndex - 1);
339         }
340     }
341
342     ExceptionCode ec = 0;
343     CSSPropertyID propertyID = static_cast<CSSPropertyID>(propertyInfo.propertyID);
344     wrapped().setPropertyInternal(propertyID, propValue, important, ec);
345     setDOMException(exec, ec);
346
347     return true;
348 }
349
350 JSValue JSCSSStyleDeclaration::getPropertyCSSValue(ExecState& state)
351 {
352     const String& propertyName = state.argument(0).toString(&state)->value(&state);
353     if (state.hadException())
354         return jsUndefined();
355
356     RefPtr<CSSValue> cssValue = wrapped().getPropertyCSSValue(propertyName);
357     if (!cssValue)
358         return jsNull();
359
360     globalObject()->world().m_cssValueRoots.add(cssValue.get(), root(&wrapped())); // Balanced by JSCSSValueOwner::finalize().
361     return toJS(&state, globalObject(), WTF::getPtr(cssValue));
362 }
363
364 void JSCSSStyleDeclaration::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
365 {
366     JSCSSStyleDeclaration* thisObject = jsCast<JSCSSStyleDeclaration*>(object);
367     ASSERT_GC_OBJECT_INHERITS(thisObject, info());
368
369     unsigned length = thisObject->wrapped().length();
370     for (unsigned i = 0; i < length; ++i)
371         propertyNames.add(Identifier::from(exec, i));
372
373     static Identifier* propertyIdentifiers = 0;
374     if (!propertyIdentifiers) {
375         Vector<String, numCSSProperties> jsPropertyNames;
376         for (int id = firstCSSProperty; id < firstCSSProperty + numCSSProperties; ++id)
377             jsPropertyNames.append(getJSPropertyName(static_cast<CSSPropertyID>(id)));
378         std::sort(jsPropertyNames.begin(), jsPropertyNames.end(), WTF::codePointCompareLessThan);
379
380         propertyIdentifiers = new Identifier[numCSSProperties];
381         for (int i = 0; i < numCSSProperties; ++i)
382             propertyIdentifiers[i] = Identifier::fromString(exec, jsPropertyNames[i]);
383     }
384
385     for (int i = 0; i < numCSSProperties; ++i)
386         propertyNames.add(propertyIdentifiers[i]);
387
388     Base::getOwnPropertyNames(thisObject, exec, propertyNames, mode);
389 }
390
391 } // namespace WebCore