1/*
2 * Copyright (C) 2007-2011 Google 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 are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32#include "V8CSSStyleDeclaration.h"
33
34#include "CSSPropertyNames.h"
35#include "bindings/v8/ExceptionState.h"
36#include "bindings/v8/V8Binding.h"
37#include "core/css/CSSParser.h"
38#include "core/css/CSSPrimitiveValue.h"
39#include "core/css/CSSStyleDeclaration.h"
40#include "core/css/CSSValue.h"
41#include "core/css/RuntimeCSSEnabled.h"
42#include "core/events/EventTarget.h"
43#include "wtf/ASCIICType.h"
44#include "wtf/PassRefPtr.h"
45#include "wtf/RefPtr.h"
46#include "wtf/StdLibExtras.h"
47#include "wtf/Vector.h"
48#include "wtf/text/StringBuilder.h"
49#include "wtf/text/StringConcatenate.h"
50
51using namespace WTF;
52using namespace std;
53
54namespace WebCore {
55
56// Check for a CSS prefix.
57// Passed prefix is all lowercase.
58// First character of the prefix within the property name may be upper or lowercase.
59// Other characters in the prefix within the property name must be lowercase.
60// The prefix within the property name must be followed by a capital letter.
61static bool hasCSSPropertyNamePrefix(const String& propertyName, const char* prefix)
62{
63#ifndef NDEBUG
64    ASSERT(*prefix);
65    for (const char* p = prefix; *p; ++p)
66        ASSERT(isASCIILower(*p));
67    ASSERT(propertyName.length());
68#endif
69
70    if (toASCIILower(propertyName[0]) != prefix[0])
71        return false;
72
73    unsigned length = propertyName.length();
74    for (unsigned i = 1; i < length; ++i) {
75        if (!prefix[i])
76            return isASCIIUpper(propertyName[i]);
77        if (propertyName[i] != prefix[i])
78            return false;
79    }
80    return false;
81}
82
83struct CSSPropertyInfo {
84    unsigned propID: 30; // CSSPropertyID
85    unsigned nameWithDash: 1;
86    unsigned nameWithCssPrefix: 1;
87};
88
89static inline void countCssPropertyInfoUsage(const CSSPropertyInfo& propInfo)
90{
91    if (propInfo.nameWithDash)
92        UseCounter::count(activeDOMWindow(), UseCounter::CSSStyleDeclarationPropertyName);
93    if (propInfo.propID == CSSPropertyFloat && !propInfo.nameWithCssPrefix)
94        UseCounter::count(activeDOMWindow(), UseCounter::CSSStyleDeclarationFloatPropertyName);
95}
96
97// When getting properties on CSSStyleDeclarations, the name used from
98// Javascript and the actual name of the property are not the same, so
99// we have to do the following translation. The translation turns upper
100// case characters into lower case characters and inserts dashes to
101// separate words.
102//
103// Example: 'backgroundPositionY' -> 'background-position-y'
104//
105// Also, certain prefixes such as 'css-' are stripped.
106static CSSPropertyInfo* cssPropertyInfo(v8::Handle<v8::String> v8PropertyName)
107{
108    String propertyName = toCoreString(v8PropertyName);
109    typedef HashMap<String, CSSPropertyInfo*> CSSPropertyInfoMap;
110    DEFINE_STATIC_LOCAL(CSSPropertyInfoMap, map, ());
111    CSSPropertyInfo* propInfo = map.get(propertyName);
112    if (!propInfo) {
113        unsigned length = propertyName.length();
114        if (!length)
115            return 0;
116
117        StringBuilder builder;
118        builder.reserveCapacity(length);
119
120        unsigned i = 0;
121        bool hasSeenDash = false;
122        bool hasSeenCssPrefix = false;
123
124        if (hasCSSPropertyNamePrefix(propertyName, "css")) {
125            hasSeenCssPrefix = true;
126            i += 3;
127        } else if (hasCSSPropertyNamePrefix(propertyName, "webkit")) {
128            builder.append('-');
129        } else if (isASCIIUpper(propertyName[0])) {
130            return 0;
131        }
132
133        builder.append(toASCIILower(propertyName[i++]));
134
135        for (; i < length; ++i) {
136            UChar c = propertyName[i];
137            if (!isASCIIUpper(c)) {
138                if (c == '-')
139                    hasSeenDash = true;
140                builder.append(c);
141            }
142            else {
143                builder.append('-');
144                builder.append(toASCIILower(c));
145            }
146        }
147
148        String propName = builder.toString();
149        CSSPropertyID propertyID = cssPropertyID(propName);
150        if (propertyID && RuntimeCSSEnabled::isCSSPropertyEnabled(propertyID)) {
151            propInfo = new CSSPropertyInfo();
152            propInfo->propID = propertyID;
153            propInfo->nameWithDash = hasSeenDash;
154            propInfo->nameWithCssPrefix = hasSeenCssPrefix;
155            map.add(propertyName, propInfo);
156        }
157    }
158    return propInfo;
159}
160
161void V8CSSStyleDeclaration::namedPropertyEnumeratorCustom(const v8::PropertyCallbackInfo<v8::Array>& info)
162{
163    typedef Vector<String, numCSSProperties - 1> PreAllocatedPropertyVector;
164    DEFINE_STATIC_LOCAL(PreAllocatedPropertyVector, propertyNames, ());
165    static unsigned propertyNamesLength = 0;
166
167    if (propertyNames.isEmpty()) {
168        for (int id = firstCSSProperty; id <= lastCSSProperty; ++id) {
169            CSSPropertyID propertyId = static_cast<CSSPropertyID>(id);
170            if (RuntimeCSSEnabled::isCSSPropertyEnabled(propertyId))
171                propertyNames.append(getJSPropertyName(propertyId));
172        }
173        sort(propertyNames.begin(), propertyNames.end(), codePointCompareLessThan);
174        propertyNamesLength = propertyNames.size();
175    }
176
177    v8::Handle<v8::Array> properties = v8::Array::New(info.GetIsolate(), propertyNamesLength);
178    for (unsigned i = 0; i < propertyNamesLength; ++i) {
179        String key = propertyNames.at(i);
180        ASSERT(!key.isNull());
181        properties->Set(v8::Integer::New(i, info.GetIsolate()), v8String(info.GetIsolate(), key));
182    }
183
184    v8SetReturnValue(info, properties);
185}
186
187void V8CSSStyleDeclaration::namedPropertyQueryCustom(v8::Local<v8::String> v8Name, const v8::PropertyCallbackInfo<v8::Integer>& info)
188{
189    // NOTE: cssPropertyInfo lookups incur several mallocs.
190    // Successful lookups have the same cost the first time, but are cached.
191    if (CSSPropertyInfo* propInfo = cssPropertyInfo(v8Name)) {
192        countCssPropertyInfoUsage(*propInfo);
193        v8SetReturnValueInt(info, 0);
194        return;
195    }
196}
197
198void V8CSSStyleDeclaration::namedPropertyGetterCustom(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
199{
200    // First look for API defined attributes on the style declaration object.
201    if (info.Holder()->HasRealNamedCallbackProperty(name))
202        return;
203
204    // Search the style declaration.
205    CSSPropertyInfo* propInfo = cssPropertyInfo(name);
206
207    // Do not handle non-property names.
208    if (!propInfo)
209        return;
210
211    countCssPropertyInfoUsage(*propInfo);
212    CSSStyleDeclaration* imp = V8CSSStyleDeclaration::toNative(info.Holder());
213    RefPtr<CSSValue> cssValue = imp->getPropertyCSSValueInternal(static_cast<CSSPropertyID>(propInfo->propID));
214    if (cssValue) {
215        v8SetReturnValueStringOrNull(info, cssValue->cssText(), info.GetIsolate());
216        return;
217    }
218
219    String result = imp->getPropertyValueInternal(static_cast<CSSPropertyID>(propInfo->propID));
220    if (result.isNull())
221        result = ""; // convert null to empty string.
222
223    v8SetReturnValueString(info, result, info.GetIsolate());
224}
225
226void V8CSSStyleDeclaration::namedPropertySetterCustom(v8::Local<v8::String> name, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<v8::Value>& info)
227{
228    CSSStyleDeclaration* imp = V8CSSStyleDeclaration::toNative(info.Holder());
229    CSSPropertyInfo* propInfo = cssPropertyInfo(name);
230    if (!propInfo)
231        return;
232
233    countCssPropertyInfoUsage(*propInfo);
234    V8TRYCATCH_FOR_V8STRINGRESOURCE_VOID(V8StringResource<WithNullCheck>, propertyValue, value);
235    ExceptionState exceptionState(ExceptionState::SetterContext, getPropertyName(static_cast<CSSPropertyID>(propInfo->propID)), "CSSStyleDeclaration", info.Holder(), info.GetIsolate());
236    imp->setPropertyInternal(static_cast<CSSPropertyID>(propInfo->propID), propertyValue, false, exceptionState);
237
238    if (exceptionState.throwIfNeeded())
239        return;
240
241    v8SetReturnValue(info, value);
242}
243
244} // namespace WebCore
245