REGRESSION(r244635): [GTK] Wrong background color used in non-dark mode
[WebKit-https.git] / Source / WebCore / css / CSSStyleDeclaration.cpp
1 /*
2  * Copyright (C) 2017 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. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "CSSStyleDeclaration.h"
28
29 #include "CSSPropertyNames.h"
30 #include "CSSPropertyParser.h"
31 #include "DeprecatedGlobalSettings.h"
32 #include "HashTools.h"
33 #include "RuntimeEnabledFeatures.h"
34 #include <wtf/IsoMallocInlines.h>
35 #include <wtf/Optional.h>
36 #include <wtf/Variant.h>
37
38 namespace WebCore {
39
40 WTF_MAKE_ISO_ALLOCATED_IMPL(CSSStyleDeclaration);
41
42 namespace {
43
44 enum class PropertyNamePrefix {
45     None, Epub, CSS, Pixel, Pos, WebKit,
46 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
47     Apple, KHTML,
48 #endif
49 };
50
51 template<size_t prefixCStringLength>
52 static inline bool matchesCSSPropertyNamePrefix(const StringImpl& propertyName, const char (&prefix)[prefixCStringLength])
53 {
54     size_t prefixLength = prefixCStringLength - 1;
55
56     ASSERT(toASCIILower(propertyName[0]) == prefix[0]);
57     const size_t offset = 1;
58
59 #ifndef NDEBUG
60     for (size_t i = 0; i < prefixLength; ++i)
61         ASSERT(isASCIILower(prefix[i]));
62     ASSERT(!prefix[prefixLength]);
63     ASSERT(propertyName.length());
64 #endif
65
66     // The prefix within the property name must be followed by a capital letter.
67     // Other characters in the prefix within the property name must be lowercase.
68     if (propertyName.length() < prefixLength + 1)
69         return false;
70
71     for (size_t i = offset; i < prefixLength; ++i) {
72         if (propertyName[i] != prefix[i])
73             return false;
74     }
75
76     if (!isASCIIUpper(propertyName[prefixLength]))
77         return false;
78
79     return true;
80 }
81
82 static PropertyNamePrefix propertyNamePrefix(const StringImpl& propertyName)
83 {
84     ASSERT(propertyName.length());
85
86     // First character of the prefix within the property name may be upper or lowercase.
87     UChar firstChar = toASCIILower(propertyName[0]);
88     switch (firstChar) {
89 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
90     case 'a':
91         if (RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled() && matchesCSSPropertyNamePrefix(propertyName, "apple"))
92             return PropertyNamePrefix::Apple;
93         break;
94 #endif
95     case 'c':
96         if (matchesCSSPropertyNamePrefix(propertyName, "css"))
97             return PropertyNamePrefix::CSS;
98         break;
99 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
100     case 'k':
101         if (RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled() && matchesCSSPropertyNamePrefix(propertyName, "khtml"))
102             return PropertyNamePrefix::KHTML;
103         break;
104 #endif
105     case 'e':
106         if (matchesCSSPropertyNamePrefix(propertyName, "epub"))
107             return PropertyNamePrefix::Epub;
108         break;
109     case 'p':
110         if (matchesCSSPropertyNamePrefix(propertyName, "pos"))
111             return PropertyNamePrefix::Pos;
112         if (matchesCSSPropertyNamePrefix(propertyName, "pixel"))
113             return PropertyNamePrefix::Pixel;
114         break;
115     case 'w':
116         if (matchesCSSPropertyNamePrefix(propertyName, "webkit"))
117             return PropertyNamePrefix::WebKit;
118         break;
119     default:
120         break;
121     }
122     return PropertyNamePrefix::None;
123 }
124
125 static inline void writeWebKitPrefix(char*& buffer)
126 {
127     *buffer++ = '-';
128     *buffer++ = 'w';
129     *buffer++ = 'e';
130     *buffer++ = 'b';
131     *buffer++ = 'k';
132     *buffer++ = 'i';
133     *buffer++ = 't';
134     *buffer++ = '-';
135 }
136
137 static inline void writeEpubPrefix(char*& buffer)
138 {
139     *buffer++ = '-';
140     *buffer++ = 'e';
141     *buffer++ = 'p';
142     *buffer++ = 'u';
143     *buffer++ = 'b';
144     *buffer++ = '-';
145 }
146
147 struct CSSPropertyInfo {
148     CSSPropertyID propertyID;
149     bool hadPixelOrPosPrefix;
150 };
151
152 static CSSPropertyInfo parseJavaScriptCSSPropertyName(const AtomicString& propertyName)
153 {
154     using CSSPropertyInfoMap = HashMap<String, CSSPropertyInfo>;
155     static NeverDestroyed<CSSPropertyInfoMap> propertyInfoCache;
156
157     CSSPropertyInfo propertyInfo = { CSSPropertyInvalid, false };
158
159     auto* propertyNameString = propertyName.impl();
160     if (!propertyNameString)
161         return propertyInfo;
162     unsigned length = propertyNameString->length();
163     if (!length)
164         return propertyInfo;
165
166     propertyInfo = propertyInfoCache.get().get(propertyNameString);
167     if (propertyInfo.propertyID)
168         return propertyInfo;
169
170     bool hadPixelOrPosPrefix = false;
171
172     constexpr size_t bufferSize = maxCSSPropertyNameLength + 1;
173     char buffer[bufferSize];
174     char* bufferPtr = buffer;
175     const char* name = bufferPtr;
176
177     unsigned i = 0;
178     // Prefixes CSS, Pixel, Pos are ignored.
179     // Prefixes Apple, KHTML and Webkit are transposed to "-webkit-".
180     // The prefix "Epub" becomes "-epub-".
181     switch (propertyNamePrefix(*propertyNameString)) {
182     case PropertyNamePrefix::None:
183         if (isASCIIUpper((*propertyNameString)[0]))
184             return propertyInfo;
185         break;
186     case PropertyNamePrefix::CSS:
187         i += 3;
188         break;
189     case PropertyNamePrefix::Pixel:
190         i += 5;
191         hadPixelOrPosPrefix = true;
192         break;
193     case PropertyNamePrefix::Pos:
194         i += 3;
195         hadPixelOrPosPrefix = true;
196         break;
197 #if ENABLE(LEGACY_CSS_VENDOR_PREFIXES)
198     case PropertyNamePrefix::Apple:
199     case PropertyNamePrefix::KHTML:
200         ASSERT(RuntimeEnabledFeatures::sharedFeatures().legacyCSSVendorPrefixesEnabled());
201         writeWebKitPrefix(bufferPtr);
202         i += 5;
203         break;
204 #endif
205     case PropertyNamePrefix::Epub:
206         writeEpubPrefix(bufferPtr);
207         i += 4;
208         break;
209     case PropertyNamePrefix::WebKit:
210         writeWebKitPrefix(bufferPtr);
211         i += 6;
212         break;
213     }
214
215     *bufferPtr++ = toASCIILower((*propertyNameString)[i++]);
216
217     char* bufferEnd = buffer + bufferSize;
218     char* stringEnd = bufferEnd - 1;
219     size_t bufferSizeLeft = stringEnd - bufferPtr;
220     size_t propertySizeLeft = length - i;
221     if (propertySizeLeft > bufferSizeLeft)
222         return propertyInfo;
223
224     for (; i < length; ++i) {
225         UChar c = (*propertyNameString)[i];
226         if (!c || !isASCII(c))
227             return propertyInfo; // illegal character
228         if (isASCIIUpper(c)) {
229             size_t bufferSizeLeft = stringEnd - bufferPtr;
230             size_t propertySizeLeft = length - i + 1;
231             if (propertySizeLeft > bufferSizeLeft)
232                 return propertyInfo;
233             *bufferPtr++ = '-';
234             *bufferPtr++ = toASCIILowerUnchecked(c);
235         } else
236             *bufferPtr++ = c;
237         ASSERT_WITH_SECURITY_IMPLICATION(bufferPtr < bufferEnd);
238     }
239     ASSERT_WITH_SECURITY_IMPLICATION(bufferPtr < bufferEnd);
240     *bufferPtr = '\0';
241
242     unsigned outputLength = bufferPtr - buffer;
243 #if PLATFORM(IOS_FAMILY)
244     cssPropertyNameIOSAliasing(buffer, name, outputLength);
245 #endif
246
247     auto* hashTableEntry = findProperty(name, outputLength);
248     if (auto propertyID = hashTableEntry ? hashTableEntry->id : 0) {
249         auto id = static_cast<CSSPropertyID>(propertyID);
250         if (isEnabledCSSProperty(id)) {
251             propertyInfo.hadPixelOrPosPrefix = hadPixelOrPosPrefix;
252             propertyInfo.propertyID = id;
253             propertyInfoCache.get().add(propertyNameString, propertyInfo);
254         }
255     }
256     return propertyInfo;
257 }
258
259 }
260
261 CSSPropertyID CSSStyleDeclaration::getCSSPropertyIDFromJavaScriptPropertyName(const AtomicString& propertyName)
262 {
263     return parseJavaScriptCSSPropertyName(propertyName).propertyID;
264 }
265
266 Optional<Variant<String, double>> CSSStyleDeclaration::namedItem(const AtomicString& propertyName)
267 {
268     auto propertyInfo = parseJavaScriptCSSPropertyName(propertyName);
269     if (!propertyInfo.propertyID)
270         return WTF::nullopt;
271
272     auto value = getPropertyCSSValueInternal(propertyInfo.propertyID);
273     if (!value) {
274         // If the property is a shorthand property (such as "padding"), it can only be accessed using getPropertyValue.
275         return Variant<String, double> { getPropertyValueInternal(propertyInfo.propertyID) };
276     }
277     
278     if (propertyInfo.hadPixelOrPosPrefix && is<CSSPrimitiveValue>(*value)) {
279         // Call this version of the getter so that, e.g., pixelTop returns top as a number
280         // in pixel units and posTop should does the same _if_ this is a positioned element.
281         // FIXME: If not a positioned element, MSIE documentation says posTop should return 0; this rule is not implemented.
282         return Variant<String, double> { downcast<CSSPrimitiveValue>(*value).floatValue(CSSPrimitiveValue::CSS_PX) };
283     }
284
285     return Variant<String, double> { value->cssText() };
286 }
287
288 ExceptionOr<void> CSSStyleDeclaration::setNamedItem(const AtomicString& propertyName, String value, bool& propertySupported)
289 {
290     auto propertyInfo = parseJavaScriptCSSPropertyName(propertyName);
291     if (!propertyInfo.propertyID) {
292         propertySupported = false;
293         return { };
294     }
295
296     propertySupported = true;
297
298     if (propertyInfo.hadPixelOrPosPrefix)
299         value.append("px");
300
301     bool important = false;
302     if (DeprecatedGlobalSettings::shouldRespectPriorityInCSSAttributeSetters()) {
303         auto importantIndex = value.findIgnoringASCIICase("!important");
304         if (importantIndex && importantIndex != notFound) {
305             important = true;
306             value = value.left(importantIndex - 1);
307         }
308     }
309
310     auto setPropertyInternalResult = setPropertyInternal(propertyInfo.propertyID, value, important);
311     if (setPropertyInternalResult.hasException())
312         return setPropertyInternalResult.releaseException();
313
314     return { };
315 }
316
317 Vector<AtomicString> CSSStyleDeclaration::supportedPropertyNames() const
318 {
319     static unsigned numNames = 0;
320     static const AtomicString* const cssPropertyNames = [] {
321         String names[numCSSProperties];
322         for (int i = 0; i < numCSSProperties; ++i) {
323             CSSPropertyID id = static_cast<CSSPropertyID>(firstCSSProperty + i);
324             if (isEnabledCSSProperty(id))
325                 names[numNames++] = getJSPropertyName(id);
326         }
327         std::sort(&names[0], &names[numNames], WTF::codePointCompareLessThan);
328         auto* identifiers = new AtomicString[numNames];
329         for (unsigned i = 0; i < numNames; ++i)
330             identifiers[i] = names[i];
331         return identifiers;
332     }();
333
334     Vector<AtomicString> result;
335     result.reserveInitialCapacity(numNames);
336
337     for (unsigned i = 0; i < numNames; ++i)
338         result.uncheckedAppend(cssPropertyNames[i]);
339
340     return result;
341 }
342
343 String CSSStyleDeclaration::cssFloat()
344 {
345     return getPropertyValueInternal(CSSPropertyFloat);
346 }
347
348 ExceptionOr<void> CSSStyleDeclaration::setCssFloat(const String& value)
349 {
350     auto result = setPropertyInternal(CSSPropertyFloat, value, false /* important */);
351     if (result.hasException())
352         return result.releaseException();
353     return { };
354 }
355
356 }