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 "Document.h"
|
---|
33 | #include "HashTools.h"
|
---|
34 | #include "Settings.h"
|
---|
35 | #include "StyledElement.h"
|
---|
36 | #include <variant>
|
---|
37 | #include <wtf/IsoMallocInlines.h>
|
---|
38 | #include <wtf/text/StringParsingBuffer.h>
|
---|
39 |
|
---|
40 | namespace WebCore {
|
---|
41 |
|
---|
42 | WTF_MAKE_ISO_ALLOCATED_IMPL(CSSStyleDeclaration);
|
---|
43 |
|
---|
44 | namespace {
|
---|
45 |
|
---|
46 | enum class PropertyNamePrefix { None, Epub, WebKit };
|
---|
47 |
|
---|
48 | template<size_t prefixCStringLength>
|
---|
49 | static inline bool matchesCSSPropertyNamePrefix(const StringImpl& propertyName, const char (&prefix)[prefixCStringLength])
|
---|
50 | {
|
---|
51 | size_t prefixLength = prefixCStringLength - 1;
|
---|
52 |
|
---|
53 | ASSERT(toASCIILower(propertyName[0]) == prefix[0]);
|
---|
54 | const size_t offset = 1;
|
---|
55 |
|
---|
56 | #ifndef NDEBUG
|
---|
57 | for (size_t i = 0; i < prefixLength; ++i)
|
---|
58 | ASSERT(isASCIILower(prefix[i]));
|
---|
59 | ASSERT(!prefix[prefixLength]);
|
---|
60 | ASSERT(propertyName.length());
|
---|
61 | #endif
|
---|
62 |
|
---|
63 | // The prefix within the property name must be followed by a capital letter.
|
---|
64 | // Other characters in the prefix within the property name must be lowercase.
|
---|
65 | if (propertyName.length() < prefixLength + 1)
|
---|
66 | return false;
|
---|
67 |
|
---|
68 | for (size_t i = offset; i < prefixLength; ++i) {
|
---|
69 | if (propertyName[i] != prefix[i])
|
---|
70 | return false;
|
---|
71 | }
|
---|
72 |
|
---|
73 | if (!isASCIIUpper(propertyName[prefixLength]))
|
---|
74 | return false;
|
---|
75 |
|
---|
76 | return true;
|
---|
77 | }
|
---|
78 |
|
---|
79 | static PropertyNamePrefix propertyNamePrefix(const StringImpl& propertyName)
|
---|
80 | {
|
---|
81 | ASSERT(propertyName.length());
|
---|
82 |
|
---|
83 | // First character of the prefix within the property name may be upper or lowercase.
|
---|
84 | UChar firstChar = toASCIILower(propertyName[0]);
|
---|
85 | switch (firstChar) {
|
---|
86 | case 'e':
|
---|
87 | if (matchesCSSPropertyNamePrefix(propertyName, "epub"))
|
---|
88 | return PropertyNamePrefix::Epub;
|
---|
89 | break;
|
---|
90 | case 'w':
|
---|
91 | if (matchesCSSPropertyNamePrefix(propertyName, "webkit"))
|
---|
92 | return PropertyNamePrefix::WebKit;
|
---|
93 | break;
|
---|
94 | default:
|
---|
95 | break;
|
---|
96 | }
|
---|
97 | return PropertyNamePrefix::None;
|
---|
98 | }
|
---|
99 |
|
---|
100 | static inline void writeWebKitPrefix(char*& buffer)
|
---|
101 | {
|
---|
102 | *buffer++ = '-';
|
---|
103 | *buffer++ = 'w';
|
---|
104 | *buffer++ = 'e';
|
---|
105 | *buffer++ = 'b';
|
---|
106 | *buffer++ = 'k';
|
---|
107 | *buffer++ = 'i';
|
---|
108 | *buffer++ = 't';
|
---|
109 | *buffer++ = '-';
|
---|
110 | }
|
---|
111 |
|
---|
112 | static inline void writeEpubPrefix(char*& buffer)
|
---|
113 | {
|
---|
114 | *buffer++ = '-';
|
---|
115 | *buffer++ = 'e';
|
---|
116 | *buffer++ = 'p';
|
---|
117 | *buffer++ = 'u';
|
---|
118 | *buffer++ = 'b';
|
---|
119 | *buffer++ = '-';
|
---|
120 | }
|
---|
121 |
|
---|
122 | static CSSPropertyID parseJavaScriptCSSPropertyName(const AtomString& propertyName)
|
---|
123 | {
|
---|
124 | using CSSPropertyIDMap = HashMap<String, CSSPropertyID>;
|
---|
125 | static NeverDestroyed<CSSPropertyIDMap> propertyIDCache;
|
---|
126 |
|
---|
127 | auto* propertyNameString = propertyName.impl();
|
---|
128 | if (!propertyNameString)
|
---|
129 | return CSSPropertyInvalid;
|
---|
130 |
|
---|
131 | unsigned length = propertyNameString->length();
|
---|
132 | if (!length)
|
---|
133 | return CSSPropertyInvalid;
|
---|
134 |
|
---|
135 | if (auto id = propertyIDCache.get().get(propertyNameString))
|
---|
136 | return id;
|
---|
137 |
|
---|
138 | constexpr size_t bufferSize = maxCSSPropertyNameLength + 1;
|
---|
139 | char buffer[bufferSize];
|
---|
140 | char* bufferPtr = buffer;
|
---|
141 | const char* name = bufferPtr;
|
---|
142 |
|
---|
143 | unsigned i = 0;
|
---|
144 | switch (propertyNamePrefix(*propertyNameString)) {
|
---|
145 | case PropertyNamePrefix::None:
|
---|
146 | if (isASCIIUpper((*propertyNameString)[0]))
|
---|
147 | return CSSPropertyInvalid;
|
---|
148 | break;
|
---|
149 | case PropertyNamePrefix::Epub:
|
---|
150 | writeEpubPrefix(bufferPtr);
|
---|
151 | i += 4;
|
---|
152 | break;
|
---|
153 | case PropertyNamePrefix::WebKit:
|
---|
154 | writeWebKitPrefix(bufferPtr);
|
---|
155 | i += 6;
|
---|
156 | break;
|
---|
157 | }
|
---|
158 |
|
---|
159 | *bufferPtr++ = toASCIILower((*propertyNameString)[i++]);
|
---|
160 |
|
---|
161 | char* bufferEnd = buffer + bufferSize;
|
---|
162 | char* stringEnd = bufferEnd - 1;
|
---|
163 | size_t bufferSizeLeft = stringEnd - bufferPtr;
|
---|
164 | size_t propertySizeLeft = length - i;
|
---|
165 | if (propertySizeLeft > bufferSizeLeft)
|
---|
166 | return CSSPropertyInvalid;
|
---|
167 |
|
---|
168 | for (; i < length; ++i) {
|
---|
169 | UChar c = (*propertyNameString)[i];
|
---|
170 | if (!c || !isASCII(c))
|
---|
171 | return CSSPropertyInvalid; // illegal character
|
---|
172 | if (isASCIIUpper(c)) {
|
---|
173 | size_t bufferSizeLeft = stringEnd - bufferPtr;
|
---|
174 | size_t propertySizeLeft = length - i + 1;
|
---|
175 | if (propertySizeLeft > bufferSizeLeft)
|
---|
176 | return CSSPropertyInvalid;
|
---|
177 | *bufferPtr++ = '-';
|
---|
178 | *bufferPtr++ = toASCIILowerUnchecked(c);
|
---|
179 | } else
|
---|
180 | *bufferPtr++ = c;
|
---|
181 | ASSERT_WITH_SECURITY_IMPLICATION(bufferPtr < bufferEnd);
|
---|
182 | }
|
---|
183 | ASSERT_WITH_SECURITY_IMPLICATION(bufferPtr < bufferEnd);
|
---|
184 | *bufferPtr = '\0';
|
---|
185 |
|
---|
186 | unsigned outputLength = bufferPtr - buffer;
|
---|
187 | auto hashTableEntry = findProperty(name, outputLength);
|
---|
188 | if (!hashTableEntry)
|
---|
189 | return CSSPropertyInvalid;
|
---|
190 |
|
---|
191 | auto id = static_cast<CSSPropertyID>(hashTableEntry->id);
|
---|
192 | if (!id)
|
---|
193 | return CSSPropertyInvalid;
|
---|
194 |
|
---|
195 | propertyIDCache.get().add(propertyNameString, id);
|
---|
196 | return id;
|
---|
197 | }
|
---|
198 |
|
---|
199 | static CSSPropertyID propertyIDFromJavaScriptCSSPropertyName(const AtomString& propertyName, const Settings* settings)
|
---|
200 | {
|
---|
201 | auto id = parseJavaScriptCSSPropertyName(propertyName);
|
---|
202 | if (!isEnabledCSSProperty(id) || !isCSSPropertyEnabledBySettings(id, settings))
|
---|
203 | return CSSPropertyInvalid;
|
---|
204 | return id;
|
---|
205 | }
|
---|
206 |
|
---|
207 | }
|
---|
208 |
|
---|
209 | CSSPropertyID CSSStyleDeclaration::getCSSPropertyIDFromJavaScriptPropertyName(const AtomString& propertyName)
|
---|
210 | {
|
---|
211 | // FIXME: This is going to return incorrect results for css properties disabled by Settings.
|
---|
212 | return propertyIDFromJavaScriptCSSPropertyName(propertyName, nullptr);
|
---|
213 | }
|
---|
214 |
|
---|
215 | const Settings* CSSStyleDeclaration::settings() const
|
---|
216 | {
|
---|
217 | return parentElement() ? &parentElement()->document().settings() : nullptr;
|
---|
218 | }
|
---|
219 |
|
---|
220 | enum class CSSPropertyLookupMode { ConvertUsingDashPrefix, ConvertUsingNoDashPrefix, NoConversion };
|
---|
221 |
|
---|
222 | template<CSSPropertyLookupMode mode> static CSSPropertyID lookupCSSPropertyFromIDLAttribute(const AtomString& attribute)
|
---|
223 | {
|
---|
224 | static NeverDestroyed<HashMap<AtomString, CSSPropertyID>> cache;
|
---|
225 |
|
---|
226 | if (auto id = cache.get().get(attribute))
|
---|
227 | return id;
|
---|
228 |
|
---|
229 | char outputBuffer[maxCSSPropertyNameLength + 1];
|
---|
230 | char* outputBufferCurrent = outputBuffer;
|
---|
231 | const char* outputBufferStart = outputBufferCurrent;
|
---|
232 |
|
---|
233 | if constexpr (mode == CSSPropertyLookupMode::ConvertUsingDashPrefix || mode == CSSPropertyLookupMode::ConvertUsingNoDashPrefix) {
|
---|
234 | // Conversion is implementing the "IDL attribute to CSS property algorithm"
|
---|
235 | // from https://drafts.csswg.org/cssom/#idl-attribute-to-css-property.
|
---|
236 |
|
---|
237 | if constexpr (mode == CSSPropertyLookupMode::ConvertUsingDashPrefix)
|
---|
238 | *outputBufferCurrent++ = '-';
|
---|
239 |
|
---|
240 | readCharactersForParsing(attribute, [&](auto buffer) {
|
---|
241 | while (buffer.hasCharactersRemaining()) {
|
---|
242 | auto c = *buffer++;
|
---|
243 | ASSERT_WITH_MESSAGE(isASCII(c), "Invalid property name: %s", attribute.string().utf8().data());
|
---|
244 | if (isASCIIUpper(c)) {
|
---|
245 | *outputBufferCurrent++ = '-';
|
---|
246 | *outputBufferCurrent++ = toASCIILowerUnchecked(c);
|
---|
247 | } else
|
---|
248 | *outputBufferCurrent++ = c;
|
---|
249 | }
|
---|
250 | });
|
---|
251 | *outputBufferCurrent = '\0';
|
---|
252 | } else {
|
---|
253 | readCharactersForParsing(attribute, [&](auto buffer) {
|
---|
254 | while (buffer.hasCharactersRemaining()) {
|
---|
255 | auto c = *buffer++;
|
---|
256 | ASSERT_WITH_MESSAGE(c == '-' || isASCIILower(c), "Invalid property name: %s", attribute.string().utf8().data());
|
---|
257 | *outputBufferCurrent++ = c;
|
---|
258 | }
|
---|
259 | });
|
---|
260 | *outputBufferCurrent = '\0';
|
---|
261 | }
|
---|
262 |
|
---|
263 | auto hashTableEntry = findProperty(outputBufferStart, outputBufferCurrent - outputBuffer);
|
---|
264 | ASSERT_WITH_MESSAGE(hashTableEntry, "Invalid property name: %s", attribute.string().utf8().data());
|
---|
265 |
|
---|
266 | auto id = static_cast<CSSPropertyID>(hashTableEntry->id);
|
---|
267 | cache.get().add(attribute, id);
|
---|
268 | return id;
|
---|
269 | }
|
---|
270 |
|
---|
271 | String CSSStyleDeclaration::propertyValueForCamelCasedIDLAttribute(const AtomString& attribute)
|
---|
272 | {
|
---|
273 | auto propertyID = lookupCSSPropertyFromIDLAttribute<CSSPropertyLookupMode::ConvertUsingNoDashPrefix>(attribute);
|
---|
274 | ASSERT_WITH_MESSAGE(propertyID != CSSPropertyInvalid, "Invalid attribute: %s", attribute.string().utf8().data());
|
---|
275 | return getPropertyValueInternal(propertyID);
|
---|
276 | }
|
---|
277 |
|
---|
278 | ExceptionOr<void> CSSStyleDeclaration::setPropertyValueForCamelCasedIDLAttribute(const AtomString& attribute, const String& value)
|
---|
279 | {
|
---|
280 | auto propertyID = lookupCSSPropertyFromIDLAttribute<CSSPropertyLookupMode::ConvertUsingNoDashPrefix>(attribute);
|
---|
281 | ASSERT_WITH_MESSAGE(propertyID != CSSPropertyInvalid, "Invalid attribute: %s", attribute.string().utf8().data());
|
---|
282 | return setPropertyInternal(propertyID, value, false);
|
---|
283 | }
|
---|
284 |
|
---|
285 | String CSSStyleDeclaration::propertyValueForWebKitCasedIDLAttribute(const AtomString& attribute)
|
---|
286 | {
|
---|
287 | auto propertyID = lookupCSSPropertyFromIDLAttribute<CSSPropertyLookupMode::ConvertUsingDashPrefix>(attribute);
|
---|
288 | ASSERT_WITH_MESSAGE(propertyID != CSSPropertyInvalid, "Invalid attribute: %s", attribute.string().utf8().data());
|
---|
289 | return getPropertyValueInternal(propertyID);
|
---|
290 | }
|
---|
291 |
|
---|
292 | ExceptionOr<void> CSSStyleDeclaration::setPropertyValueForWebKitCasedIDLAttribute(const AtomString& attribute, const String& value)
|
---|
293 | {
|
---|
294 | auto propertyID = lookupCSSPropertyFromIDLAttribute<CSSPropertyLookupMode::ConvertUsingDashPrefix>(attribute);
|
---|
295 | ASSERT_WITH_MESSAGE(propertyID != CSSPropertyInvalid, "Invalid attribute: %s", attribute.string().utf8().data());
|
---|
296 | return setPropertyInternal(propertyID, value, false);
|
---|
297 | }
|
---|
298 |
|
---|
299 | String CSSStyleDeclaration::propertyValueForDashedIDLAttribute(const AtomString& attribute)
|
---|
300 | {
|
---|
301 | auto propertyID = lookupCSSPropertyFromIDLAttribute<CSSPropertyLookupMode::NoConversion>(attribute);
|
---|
302 | ASSERT_WITH_MESSAGE(propertyID != CSSPropertyInvalid, "Invalid attribute: %s", attribute.string().utf8().data());
|
---|
303 | return getPropertyValueInternal(propertyID);
|
---|
304 | }
|
---|
305 |
|
---|
306 | ExceptionOr<void> CSSStyleDeclaration::setPropertyValueForDashedIDLAttribute(const AtomString& attribute, const String& value)
|
---|
307 | {
|
---|
308 | auto propertyID = lookupCSSPropertyFromIDLAttribute<CSSPropertyLookupMode::NoConversion>(attribute);
|
---|
309 | ASSERT_WITH_MESSAGE(propertyID != CSSPropertyInvalid, "Invalid attribute: %s", attribute.string().utf8().data());
|
---|
310 | return setPropertyInternal(propertyID, value, false);
|
---|
311 | }
|
---|
312 |
|
---|
313 | String CSSStyleDeclaration::propertyValueForEpubCasedIDLAttribute(const AtomString& attribute)
|
---|
314 | {
|
---|
315 | auto propertyID = lookupCSSPropertyFromIDLAttribute<CSSPropertyLookupMode::ConvertUsingDashPrefix>(attribute);
|
---|
316 | ASSERT_WITH_MESSAGE(propertyID != CSSPropertyInvalid, "Invalid attribute: %s", attribute.string().utf8().data());
|
---|
317 | return getPropertyValueInternal(propertyID);
|
---|
318 | }
|
---|
319 |
|
---|
320 | ExceptionOr<void> CSSStyleDeclaration::setPropertyValueForEpubCasedIDLAttribute(const AtomString& attribute, const String& value)
|
---|
321 | {
|
---|
322 | auto propertyID = lookupCSSPropertyFromIDLAttribute<CSSPropertyLookupMode::ConvertUsingDashPrefix>(attribute);
|
---|
323 | ASSERT_WITH_MESSAGE(propertyID != CSSPropertyInvalid, "Invalid attribute: %s", attribute.string().utf8().data());
|
---|
324 | return setPropertyInternal(propertyID, value, false);
|
---|
325 | }
|
---|
326 |
|
---|
327 | String CSSStyleDeclaration::cssFloat()
|
---|
328 | {
|
---|
329 | return getPropertyValueInternal(CSSPropertyFloat);
|
---|
330 | }
|
---|
331 |
|
---|
332 | ExceptionOr<void> CSSStyleDeclaration::setCssFloat(const String& value)
|
---|
333 | {
|
---|
334 | return setPropertyInternal(CSSPropertyFloat, value, false /* important */);
|
---|
335 | }
|
---|
336 |
|
---|
337 | }
|
---|