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