21f1ef43a2b04b33aa3046f2a1c3f88298b0db58
[WebKit-https.git] / Source / WebCore / bindings / js / JSCSSStyleDeclarationCustom.cpp
1 /*
2  * Copyright (C) 2007, 2008, 2009 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 COMPUTER, 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 COMPUTER, 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 "PlatformString.h"
37 #include "Settings.h"
38 #include "StylePropertySet.h"
39 #include <runtime/StringPrototype.h>
40 #include <wtf/ASCIICType.h>
41 #include <wtf/text/AtomicString.h>
42 #include <wtf/text/StringBuilder.h>
43 #include <wtf/text/StringConcatenate.h>
44 #include <wtf/text/WTFString.h>
45
46 using namespace JSC;
47 using namespace WTF;
48 using namespace std;
49
50 namespace WebCore {
51
52 void JSCSSStyleDeclaration::visitChildren(JSCell* cell, SlotVisitor& visitor)
53 {
54     JSCSSStyleDeclaration* thisObject = jsCast<JSCSSStyleDeclaration*>(cell);
55     ASSERT_GC_OBJECT_INHERITS(thisObject, &s_info);
56     COMPILE_ASSERT(StructureFlags & OverridesVisitChildren, OverridesVisitChildrenWithoutSettingFlag);
57     ASSERT(thisObject->structure()->typeInfo().overridesVisitChildren());
58     Base::visitChildren(thisObject, visitor);
59     visitor.addOpaqueRoot(root(thisObject->impl()));
60 }
61
62 class CSSPropertyInfo {
63 public:
64     CSSPropertyID propertyID;
65     bool hadPixelOrPosPrefix;
66 };
67
68 enum PropertyNamePrefix
69 {
70     PropertyNamePrefixNone,
71     PropertyNamePrefixCSS,
72     PropertyNamePrefixPixel,
73     PropertyNamePrefixPos,
74 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
75     PropertyNamePrefixApple,
76 #endif
77     PropertyNamePrefixEpub,
78 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
79     PropertyNamePrefixKHTML,
80 #endif
81     PropertyNamePrefixWebKit
82 };
83
84 template<size_t prefixCStringLength>
85 static inline bool matchesCSSPropertyNamePrefix(const StringImpl& propertyName, const char (&prefix)[prefixCStringLength])
86 {
87     size_t prefixLength = prefixCStringLength - 1;
88
89     ASSERT(toASCIILower(propertyName[0]) == prefix[0]);
90     const size_t offset = 1;
91
92 #ifndef NDEBUG
93     for (size_t i = 0; i < prefixLength; ++i)
94         ASSERT(isASCIILower(prefix[i]));
95     ASSERT(!prefix[prefixLength]);
96     ASSERT(propertyName.length());
97 #endif
98
99     // The prefix within the property name must be followed by a capital letter.
100     // Other characters in the prefix within the property name must be lowercase.
101     if (propertyName.length() < (prefixLength + 1))
102         return false;
103
104     for (size_t i = offset; i < prefixLength; ++i) {
105         if (propertyName[i] != prefix[i])
106             return false;
107     }
108
109     if (!isASCIIUpper(propertyName[prefixLength]))
110         return false;
111     return true;
112 }
113
114 static PropertyNamePrefix getCSSPropertyNamePrefix(const StringImpl& propertyName)
115 {
116     ASSERT(propertyName.length());
117
118     // First character of the prefix within the property name may be upper or lowercase.
119     UChar firstChar = toASCIILower(propertyName[0]);
120     switch (firstChar) {
121 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
122     case 'a':
123         if (matchesCSSPropertyNamePrefix(propertyName, "apple"))
124             return PropertyNamePrefixApple;
125         break;
126 #endif
127     case 'c':
128         if (matchesCSSPropertyNamePrefix(propertyName, "css"))
129             return PropertyNamePrefixCSS;
130         break;
131 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
132     case 'k':
133         if (matchesCSSPropertyNamePrefix(propertyName, "khtml"))
134             return PropertyNamePrefixKHTML;
135         break;
136 #endif
137     case 'e':
138         if (matchesCSSPropertyNamePrefix(propertyName, "epub"))
139             return PropertyNamePrefixEpub;
140         break;
141     case 'p':
142         if (matchesCSSPropertyNamePrefix(propertyName, "pos"))
143             return PropertyNamePrefixPos;
144         if (matchesCSSPropertyNamePrefix(propertyName, "pixel"))
145             return PropertyNamePrefixPixel;
146         break;
147     case 'w':
148         if (matchesCSSPropertyNamePrefix(propertyName, "webkit"))
149             return PropertyNamePrefixWebKit;
150         break;
151     default:
152         break;
153     }
154     return PropertyNamePrefixNone;
155 }
156
157 static inline void writeWebKitPrefix(char*& buffer)
158 {
159     *buffer++ = '-';
160     *buffer++ = 'w';
161     *buffer++ = 'e';
162     *buffer++ = 'b';
163     *buffer++ = 'k';
164     *buffer++ = 'i';
165     *buffer++ = 't';
166     *buffer++ = '-';
167 }
168
169 static inline void writeEpubPrefix(char*& buffer)
170 {
171     *buffer++ = '-';
172     *buffer++ = 'e';
173     *buffer++ = 'p';
174     *buffer++ = 'u';
175     *buffer++ = 'b';
176     *buffer++ = '-';
177 }
178
179 static CSSPropertyInfo cssPropertyIDForJSCSSPropertyName(PropertyName propertyName)
180 {
181     CSSPropertyInfo propertyInfo = {CSSPropertyInvalid, false};
182     bool hadPixelOrPosPrefix = false;
183
184     StringImpl* propertyNameString = propertyName.impl();
185     unsigned length = propertyNameString->length();
186     if (!length)
187         return propertyInfo;
188
189     String stringForCache = String(propertyNameString);
190     typedef HashMap<String, CSSPropertyInfo> CSSPropertyInfoMap;
191     DEFINE_STATIC_LOCAL(CSSPropertyInfoMap, propertyInfoCache, ());
192     propertyInfo = propertyInfoCache.get(stringForCache);
193     if (propertyInfo.propertyID)
194         return propertyInfo;
195
196     const size_t bufferSize = maxCSSPropertyNameLength + 1;
197     char buffer[bufferSize];
198     char* bufferPtr = buffer;
199     const char* name = bufferPtr;
200
201     unsigned i = 0;
202     // Prefixes CSS, Pixel, Pos are ignored.
203     // Prefixes Apple, KHTML and Webkit are transposed to "-webkit-".
204     // The prefix "Epub" becomes "-epub-".
205     switch (getCSSPropertyNamePrefix(*propertyNameString)) {
206     case PropertyNamePrefixNone:
207         if (isASCIIUpper((*propertyNameString)[0]))
208             return propertyInfo;
209         break;
210     case PropertyNamePrefixCSS:
211         i += 3;
212         break;
213     case PropertyNamePrefixPixel:
214         i += 5;
215         hadPixelOrPosPrefix = true;
216         break;
217     case PropertyNamePrefixPos:
218         i += 3;
219         hadPixelOrPosPrefix = true;
220         break;
221 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
222     case PropertyNamePrefixApple:
223     case PropertyNamePrefixKHTML:
224         writeWebKitPrefix(bufferPtr);
225         i += 5;
226         break;
227 #endif
228     case PropertyNamePrefixEpub:
229         writeEpubPrefix(bufferPtr);
230         i += 4;
231         break;
232     case PropertyNamePrefixWebKit:
233         writeWebKitPrefix(bufferPtr);
234         i += 6;
235         break;
236     }
237
238     *bufferPtr++ = toASCIILower((*propertyNameString)[i++]);
239
240     char* bufferEnd = buffer + bufferSize;
241     char* stringEnd = bufferEnd - 1;
242     size_t bufferSizeLeft = stringEnd - bufferPtr;
243     size_t propertySizeLeft = length - i;
244     if (propertySizeLeft > bufferSizeLeft)
245         return propertyInfo;
246
247     for (; i < length; ++i) {
248         UChar c = (*propertyNameString)[i];
249         if (!c || c >= 0x7F)
250             return propertyInfo; // illegal character
251         if (isASCIIUpper(c)) {
252             size_t bufferSizeLeft = stringEnd - bufferPtr;
253             size_t propertySizeLeft = length - i + 1;
254             if (propertySizeLeft > bufferSizeLeft)
255                 return propertyInfo;
256             *bufferPtr++ = '-';
257             *bufferPtr++ = toASCIILower(c);
258         } else
259             *bufferPtr++ = c;
260         ASSERT(bufferPtr < bufferEnd);
261     }
262     ASSERT(bufferPtr < bufferEnd);
263     *bufferPtr = '\0';
264
265     unsigned outputLength = bufferPtr - buffer;
266 #if PLATFORM(IOS)
267     cssPropertyNameIOSAliasing(buffer, name, outputLength);
268 #endif
269
270     const Property* hashTableEntry = findProperty(name, outputLength);
271     int propertyID = hashTableEntry ? hashTableEntry->id : 0;
272     if (propertyID) {
273         propertyInfo.hadPixelOrPosPrefix = hadPixelOrPosPrefix;
274         propertyInfo.propertyID = static_cast<CSSPropertyID>(propertyID);
275         propertyInfoCache.add(stringForCache, propertyInfo);
276     }
277     return propertyInfo;
278 }
279
280 static inline JSValue getPropertyValueFallback(ExecState* exec, JSCSSStyleDeclaration* thisObj, unsigned index)
281 {
282     // If the property is a shorthand property (such as "padding"),
283     // it can only be accessed using getPropertyValue.
284     return jsString(exec, thisObj->impl()->getPropertyValueInternal(static_cast<CSSPropertyID>(index)));
285 }
286
287 static inline JSValue cssPropertyGetterPixelOrPosPrefix(ExecState* exec, JSCSSStyleDeclaration* thisObj, unsigned propertyID)
288 {
289     // Set up pixelOrPos boolean to handle the fact that
290     // pixelTop returns "CSS Top" as number value in unit pixels
291     // posTop returns "CSS top" as number value in unit pixels _if_ its a
292     // positioned element. if it is not a positioned element, return 0
293     // from MSIE documentation FIXME: IMPLEMENT THAT (Dirk)
294     RefPtr<CSSValue> v = thisObj->impl()->getPropertyCSSValueInternal(static_cast<CSSPropertyID>(propertyID));
295     if (v) {
296         if (v->isPrimitiveValue())
297             return jsNumber(static_pointer_cast<CSSPrimitiveValue>(v)->getFloatValue(CSSPrimitiveValue::CSS_PX));
298         return jsStringOrNull(exec, v->cssText());
299     }
300
301     return getPropertyValueFallback(exec, thisObj, propertyID);
302 }
303
304 static JSValue cssPropertyGetterPixelOrPosPrefixCallback(ExecState* exec, JSValue slotBase, unsigned propertyID)
305 {
306     return cssPropertyGetterPixelOrPosPrefix(exec, jsCast<JSCSSStyleDeclaration*>(asObject(slotBase)), propertyID);
307 }
308
309 static inline JSValue cssPropertyGetter(ExecState* exec, JSCSSStyleDeclaration* thisObj, unsigned propertyID)
310 {
311     RefPtr<CSSValue> v = thisObj->impl()->getPropertyCSSValueInternal(static_cast<CSSPropertyID>(propertyID));
312     if (v)
313         return jsStringOrNull(exec, v->cssText());
314
315     return getPropertyValueFallback(exec, thisObj, propertyID);
316 }
317
318 static JSValue cssPropertyGetterCallback(ExecState* exec, JSValue slotBase, unsigned propertyID)
319 {
320     return cssPropertyGetter(exec, jsCast<JSCSSStyleDeclaration*>(asObject(slotBase)), propertyID);
321 }
322
323 bool JSCSSStyleDeclaration::getOwnPropertySlotDelegate(ExecState*, PropertyName propertyIdentifier, PropertySlot& slot)
324 {
325     CSSPropertyInfo propertyInfo = cssPropertyIDForJSCSSPropertyName(propertyIdentifier);
326     if (!propertyInfo.propertyID)
327         return false;
328
329     if (propertyInfo.hadPixelOrPosPrefix)
330         slot.setCustomIndex(this, static_cast<unsigned>(propertyInfo.propertyID), cssPropertyGetterPixelOrPosPrefixCallback);
331     else
332         slot.setCustomIndex(this, static_cast<unsigned>(propertyInfo.propertyID), cssPropertyGetterCallback);
333     return true;
334 }
335
336 bool JSCSSStyleDeclaration::getOwnPropertyDescriptorDelegate(JSC::ExecState* exec, JSC::PropertyName propertyIdentifier, JSC::PropertyDescriptor& descriptor)
337 {
338     CSSPropertyInfo propertyInfo = cssPropertyIDForJSCSSPropertyName(propertyIdentifier);
339     if (!propertyInfo.propertyID)
340         return false;
341
342     JSValue value;
343     if (propertyInfo.hadPixelOrPosPrefix)
344         value = cssPropertyGetterPixelOrPosPrefix(exec, this, propertyInfo.propertyID);
345     else
346         value = cssPropertyGetter(exec, this, propertyInfo.propertyID);
347     descriptor.setDescriptor(value, ReadOnly | DontDelete | DontEnum);
348     return true;
349 }
350
351 bool JSCSSStyleDeclaration::putDelegate(ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot&)
352 {
353     CSSPropertyInfo propertyInfo = cssPropertyIDForJSCSSPropertyName(propertyName);
354     if (!propertyInfo.propertyID)
355         return false;
356
357     String propValue = valueToStringWithNullCheck(exec, value);
358     if (propertyInfo.hadPixelOrPosPrefix)
359         propValue += "px";
360
361     bool important = false;
362     if (Settings::shouldRespectPriorityInCSSAttributeSetters()) {
363         size_t importantIndex = propValue.find("!important", 0, false);
364         if (importantIndex != notFound) {
365             important = true;
366             propValue = propValue.left(importantIndex - 1);
367         }
368     }
369
370     ExceptionCode ec = 0;
371     impl()->setPropertyInternal(static_cast<CSSPropertyID>(propertyInfo.propertyID), propValue, important, ec);
372     setDOMException(exec, ec);
373     return true;
374 }
375
376 JSValue JSCSSStyleDeclaration::getPropertyCSSValue(ExecState* exec)
377 {
378     const String& propertyName(ustringToString(exec->argument(0).toString(exec)->value(exec)));
379     if (exec->hadException())
380         return jsUndefined();
381
382     RefPtr<CSSValue> cssValue = impl()->getPropertyCSSValue(propertyName);
383     if (!cssValue)
384         return jsNull();
385
386     currentWorld(exec)->m_cssValueRoots.add(cssValue.get(), root(impl())); // Balanced by JSCSSValueOwner::finalize().
387     return toJS(exec, globalObject(), WTF::getPtr(cssValue));
388 }
389
390 void JSCSSStyleDeclaration::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
391 {
392     JSCSSStyleDeclaration* thisObject = jsCast<JSCSSStyleDeclaration*>(object);
393     ASSERT_GC_OBJECT_INHERITS(thisObject, &s_info);
394
395     unsigned length = thisObject->impl()->length();
396     for (unsigned i = 0; i < length; ++i)
397         propertyNames.add(Identifier::from(exec, i));
398
399     static Identifier* propertyIdentifiers = 0;
400     if (!propertyIdentifiers) {
401         Vector<String, numCSSProperties> jsPropertyNames;
402         for (int id = firstCSSProperty; id < firstCSSProperty + numCSSProperties; ++id)
403             jsPropertyNames.append(getJSPropertyName(static_cast<CSSPropertyID>(id)));
404         sort(jsPropertyNames.begin(), jsPropertyNames.end(), WTF::codePointCompareLessThan);
405
406         propertyIdentifiers = new Identifier[numCSSProperties];
407         for (int i = 0; i < numCSSProperties; ++i)
408             propertyIdentifiers[i] = Identifier(exec, jsPropertyNames[i].impl());
409     }
410
411     for (int i = 0; i < numCSSProperties; ++i)
412         propertyNames.add(propertyIdentifiers[i]);
413
414     Base::getOwnPropertyNames(thisObject, exec, propertyNames, mode);
415 }
416
417 } // namespace WebCore