c14c197982234c315dd3cac87ff6b414cecb2a00
[WebKit-https.git] / Source / WebCore / bindings / js / JSCSSStyleDeclarationCustom.cpp
1 /*
2  * Copyright (C) 2007-2016 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 "CSSRule.h"
33 #include "CSSStyleDeclaration.h"
34 #include "CSSStyleSheet.h"
35 #include "CSSValue.h"
36 #include "HashTools.h"
37 #include "JSCSSStyleDeclaration.h"
38 #include "JSCSSValue.h"
39 #include "JSNode.h"
40 #include "JSStyleSheetCustom.h"
41 #include "RuntimeEnabledFeatures.h"
42 #include "Settings.h"
43 #include "StyleProperties.h"
44 #include "StyledElement.h"
45 #include <runtime/IdentifierInlines.h>
46 #include <runtime/StringPrototype.h>
47 #include <wtf/ASCIICType.h>
48 #include <wtf/text/AtomicString.h>
49 #include <wtf/text/StringConcatenate.h>
50
51 using namespace JSC;
52
53 namespace WebCore {
54
55 void* root(CSSStyleDeclaration* style)
56 {
57     ASSERT(style);
58     if (auto* parentRule = style->parentRule())
59         return root(parentRule);
60     if (auto* styleSheet = style->parentStyleSheet())
61         return root(styleSheet);
62     if (auto* parentElement = style->parentElement())
63         return root(parentElement);
64     return style;
65 }
66
67 void JSCSSStyleDeclaration::visitAdditionalChildren(SlotVisitor& visitor)
68 {
69     visitor.addOpaqueRoot(root(&wrapped()));
70 }
71
72 enum class PropertyNamePrefix {
73     None, Epub, CSS, Pixel, Pos, WebKit,
74 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
75     Apple, KHTML,
76 #endif
77 };
78
79 template<size_t prefixCStringLength>
80 static inline bool matchesCSSPropertyNamePrefix(const StringImpl& propertyName, const char (&prefix)[prefixCStringLength])
81 {
82     size_t prefixLength = prefixCStringLength - 1;
83
84     ASSERT(toASCIILower(propertyName[0]) == prefix[0]);
85     const size_t offset = 1;
86
87 #ifndef NDEBUG
88     for (size_t i = 0; i < prefixLength; ++i)
89         ASSERT(isASCIILower(prefix[i]));
90     ASSERT(!prefix[prefixLength]);
91     ASSERT(propertyName.length());
92 #endif
93
94     // The prefix within the property name must be followed by a capital letter.
95     // Other characters in the prefix within the property name must be lowercase.
96     if (propertyName.length() < prefixLength + 1)
97         return false;
98
99     for (size_t i = offset; i < prefixLength; ++i) {
100         if (propertyName[i] != prefix[i])
101             return false;
102     }
103
104     if (!isASCIIUpper(propertyName[prefixLength]))
105         return false;
106
107     return true;
108 }
109
110 static PropertyNamePrefix propertyNamePrefix(const StringImpl& propertyName)
111 {
112     ASSERT(propertyName.length());
113
114     // First character of the prefix within the property name may be upper or lowercase.
115     UChar firstChar = toASCIILower(propertyName[0]);
116     switch (firstChar) {
117 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
118     case 'a':
119         if (RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled() && matchesCSSPropertyNamePrefix(propertyName, "apple"))
120             return PropertyNamePrefix::Apple;
121         break;
122 #endif
123     case 'c':
124         if (matchesCSSPropertyNamePrefix(propertyName, "css"))
125             return PropertyNamePrefix::CSS;
126         break;
127 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
128     case 'k':
129         if (RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled() && matchesCSSPropertyNamePrefix(propertyName, "khtml"))
130             return PropertyNamePrefix::KHTML;
131         break;
132 #endif
133     case 'e':
134         if (matchesCSSPropertyNamePrefix(propertyName, "epub"))
135             return PropertyNamePrefix::Epub;
136         break;
137     case 'p':
138         if (matchesCSSPropertyNamePrefix(propertyName, "pos"))
139             return PropertyNamePrefix::Pos;
140         if (matchesCSSPropertyNamePrefix(propertyName, "pixel"))
141             return PropertyNamePrefix::Pixel;
142         break;
143     case 'w':
144         if (matchesCSSPropertyNamePrefix(propertyName, "webkit"))
145             return PropertyNamePrefix::WebKit;
146         break;
147     default:
148         break;
149     }
150     return PropertyNamePrefix::None;
151 }
152
153 static inline void writeWebKitPrefix(char*& buffer)
154 {
155     *buffer++ = '-';
156     *buffer++ = 'w';
157     *buffer++ = 'e';
158     *buffer++ = 'b';
159     *buffer++ = 'k';
160     *buffer++ = 'i';
161     *buffer++ = 't';
162     *buffer++ = '-';
163 }
164
165 static inline void writeEpubPrefix(char*& buffer)
166 {
167     *buffer++ = '-';
168     *buffer++ = 'e';
169     *buffer++ = 'p';
170     *buffer++ = 'u';
171     *buffer++ = 'b';
172     *buffer++ = '-';
173 }
174
175 struct CSSPropertyInfo {
176     CSSPropertyID propertyID;
177     bool hadPixelOrPosPrefix;
178 };
179
180 static CSSPropertyInfo parseJavaScriptCSSPropertyName(PropertyName propertyName)
181 {
182     CSSPropertyInfo propertyInfo = {CSSPropertyInvalid, false};
183     bool hadPixelOrPosPrefix = false;
184
185     StringImpl* propertyNameString = propertyName.publicName();
186     if (!propertyNameString)
187         return propertyInfo;
188     unsigned length = propertyNameString->length();
189     if (!length)
190         return propertyInfo;
191
192     String stringForCache = String(propertyNameString);
193     using CSSPropertyInfoMap = HashMap<String, CSSPropertyInfo>;
194     static NeverDestroyed<CSSPropertyInfoMap> propertyInfoCache;
195     propertyInfo = propertyInfoCache.get().get(stringForCache);
196     if (propertyInfo.propertyID)
197         return propertyInfo;
198
199     const size_t bufferSize = maxCSSPropertyNameLength + 1;
200     char buffer[bufferSize];
201     char* bufferPtr = buffer;
202     const char* name = bufferPtr;
203
204     unsigned i = 0;
205     // Prefixes CSS, Pixel, Pos are ignored.
206     // Prefixes Apple, KHTML and Webkit are transposed to "-webkit-".
207     // The prefix "Epub" becomes "-epub-".
208     switch (propertyNamePrefix(*propertyNameString)) {
209     case PropertyNamePrefix::None:
210         if (isASCIIUpper((*propertyNameString)[0]))
211             return propertyInfo;
212         break;
213     case PropertyNamePrefix::CSS:
214         i += 3;
215         break;
216     case PropertyNamePrefix::Pixel:
217         i += 5;
218         hadPixelOrPosPrefix = true;
219         break;
220     case PropertyNamePrefix::Pos:
221         i += 3;
222         hadPixelOrPosPrefix = true;
223         break;
224 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
225     case PropertyNamePrefix::Apple:
226     case PropertyNamePrefix::KHTML:
227         ASSERT(RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled());
228         writeWebKitPrefix(bufferPtr);
229         i += 5;
230         break;
231 #endif
232     case PropertyNamePrefix::Epub:
233         writeEpubPrefix(bufferPtr);
234         i += 4;
235         break;
236     case PropertyNamePrefix::WebKit:
237         writeWebKitPrefix(bufferPtr);
238         i += 6;
239         break;
240     }
241
242     *bufferPtr++ = toASCIILower((*propertyNameString)[i++]);
243
244     char* bufferEnd = buffer + bufferSize;
245     char* stringEnd = bufferEnd - 1;
246     size_t bufferSizeLeft = stringEnd - bufferPtr;
247     size_t propertySizeLeft = length - i;
248     if (propertySizeLeft > bufferSizeLeft)
249         return propertyInfo;
250
251     for (; i < length; ++i) {
252         UChar c = (*propertyNameString)[i];
253         if (!c || !isASCII(c))
254             return propertyInfo; // illegal character
255         if (isASCIIUpper(c)) {
256             size_t bufferSizeLeft = stringEnd - bufferPtr;
257             size_t propertySizeLeft = length - i + 1;
258             if (propertySizeLeft > bufferSizeLeft)
259                 return propertyInfo;
260             *bufferPtr++ = '-';
261             *bufferPtr++ = toASCIILowerUnchecked(c);
262         } else
263             *bufferPtr++ = c;
264         ASSERT_WITH_SECURITY_IMPLICATION(bufferPtr < bufferEnd);
265     }
266     ASSERT_WITH_SECURITY_IMPLICATION(bufferPtr < bufferEnd);
267     *bufferPtr = '\0';
268
269     unsigned outputLength = bufferPtr - buffer;
270 #if PLATFORM(IOS)
271     cssPropertyNameIOSAliasing(buffer, name, outputLength);
272 #endif
273
274     auto* hashTableEntry = findProperty(name, outputLength);
275     if (auto propertyID = hashTableEntry ? hashTableEntry->id : 0) {
276         propertyInfo.hadPixelOrPosPrefix = hadPixelOrPosPrefix;
277         propertyInfo.propertyID = static_cast<CSSPropertyID>(propertyID);
278         propertyInfoCache.get().add(stringForCache, propertyInfo);
279     }
280     return propertyInfo;
281 }
282
283 static inline JSValue stylePropertyGetter(ExecState& state, JSCSSStyleDeclaration& thisObject, CSSPropertyID propertyID, const RefPtr<CSSValue>& value)
284 {
285     if (value)
286         return toJS<IDLNullable<IDLDOMString>>(state, value->cssText());
287     // If the property is a shorthand property (such as "padding"), it can only be accessed using getPropertyValue.
288     return toJS<IDLDOMString>(state, thisObject.wrapped().getPropertyValueInternal(propertyID));
289 }
290
291 static inline JSValue stylePropertyGetter(ExecState& state, JSCSSStyleDeclaration& thisObject, CSSPropertyID propertyID)
292 {
293     return stylePropertyGetter(state, thisObject, propertyID, thisObject.wrapped().getPropertyCSSValueInternal(propertyID));
294 }
295
296 static inline JSValue stylePropertyGetterPixelOrPosPrefix(ExecState& state, JSCSSStyleDeclaration& thisObject, CSSPropertyID propertyID)
297 {
298     // Call this version of the getter so that, e.g., pixelTop returns top as a number
299     // in pixel units and posTop should does the same _if_ this is a positioned element.
300     // FIXME: If not a positioned element, MSIE documentation says posTop should return 0; this rule is not implemented.
301     auto value = thisObject.wrapped().getPropertyCSSValueInternal(propertyID);
302     if (is<CSSPrimitiveValue>(value.get()))
303         return jsNumber(downcast<CSSPrimitiveValue>(*value).floatValue(CSSPrimitiveValue::CSS_PX));
304     return stylePropertyGetter(state, thisObject, propertyID, value);
305 }
306
307 static inline JSValue stylePropertyGetter(ExecState& state, JSCSSStyleDeclaration& thisObject, const CSSPropertyInfo& propertyInfo)
308 {
309     if (propertyInfo.hadPixelOrPosPrefix)
310         return stylePropertyGetterPixelOrPosPrefix(state, thisObject, propertyInfo.propertyID);
311     return stylePropertyGetter(state, thisObject, propertyInfo.propertyID);
312 }
313
314 bool JSCSSStyleDeclaration::getOwnPropertySlotDelegate(ExecState* state, PropertyName propertyName, PropertySlot& slot)
315 {
316     auto propertyInfo = parseJavaScriptCSSPropertyName(propertyName);
317     if (!propertyInfo.propertyID)
318         return false;
319     slot.setValue(this, DontDelete, stylePropertyGetter(*state, *this, propertyInfo));
320     return true;
321 }
322
323 bool JSCSSStyleDeclaration::putDelegate(ExecState* state, PropertyName propertyName, JSValue value, PutPropertySlot&, bool& putResult)
324 {
325     auto propertyInfo = parseJavaScriptCSSPropertyName(propertyName);
326     if (!propertyInfo.propertyID)
327         return false;
328
329     auto propertyValue = convert<IDLDOMString>(*state, value, StringConversionConfiguration::TreatNullAsEmptyString);
330     if (propertyInfo.hadPixelOrPosPrefix)
331         propertyValue.append("px");
332
333     bool important = false;
334     if (Settings::shouldRespectPriorityInCSSAttributeSetters()) {
335         auto importantIndex = propertyValue.findIgnoringASCIICase("!important");
336         if (importantIndex && importantIndex != notFound) {
337             important = true;
338             propertyValue = propertyValue.left(importantIndex - 1);
339         }
340     }
341
342     auto setPropertyInternalResult = wrapped().setPropertyInternal(propertyInfo.propertyID, propertyValue, important);
343     if (setPropertyInternalResult.hasException()) {
344         propagateException(*state, setPropertyInternalResult.releaseException());
345         return true;
346     }
347     putResult = setPropertyInternalResult.releaseReturnValue();
348     return true;
349 }
350
351 JSValue JSCSSStyleDeclaration::getPropertyCSSValue(ExecState& state)
352 {
353     VM& vm = state.vm();
354     auto scope = DECLARE_THROW_SCOPE(vm);
355
356     if (UNLIKELY(state.argumentCount() < 1))
357         return throwException(&state, scope, createNotEnoughArgumentsError(&state));
358
359     auto propertyName = state.uncheckedArgument(0).toWTFString(&state);
360     RETURN_IF_EXCEPTION(scope, JSValue());
361
362     auto value = wrapped().getPropertyCSSValue(propertyName);
363     if (!value)
364         return jsNull();
365
366     globalObject()->world().m_cssValueRoots.add(value.get(), root(&wrapped())); // Balanced by JSCSSValueOwner::finalize().
367     return toJS(&state, globalObject(), *value);
368 }
369
370 void JSCSSStyleDeclaration::getOwnPropertyNames(JSObject* object, ExecState* state, PropertyNameArray& propertyNames, EnumerationMode mode)
371 {
372     static const Identifier* const cssPropertyNames = [state] {
373         String names[numCSSProperties];
374         for (int i = 0; i < numCSSProperties; ++i)
375             names[i] = getJSPropertyName(static_cast<CSSPropertyID>(firstCSSProperty + i));
376         std::sort(&names[0], &names[numCSSProperties], WTF::codePointCompareLessThan);
377         auto* identifiers = new Identifier[numCSSProperties];
378         for (int i = 0; i < numCSSProperties; ++i)
379             identifiers[i] = Identifier::fromString(state, names[i]);
380         return identifiers;
381     }();
382
383     unsigned length = jsCast<JSCSSStyleDeclaration*>(object)->wrapped().length();
384     for (unsigned i = 0; i < length; ++i)
385         propertyNames.add(Identifier::from(state, i));
386     for (int i = 0; i < numCSSProperties; ++i)
387         propertyNames.add(cssPropertyNames[i]);
388
389     Base::getOwnPropertyNames(object, state, propertyNames, mode);
390 }
391
392 } // namespace WebCore