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