1 | // Copyright 2014 The Chromium Authors. All rights reserved. |
2 | // Copyright (C) 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 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 | #include "config.h" |
31 | #include "CSSParserToken.h" |
32 | |
33 | #include "CSSMarkup.h" |
34 | #include "CSSPrimitiveValue.h" |
35 | #include "CSSPropertyParser.h" |
36 | #include <limits.h> |
37 | #include <wtf/HexNumber.h> |
38 | #include <wtf/text/StringBuilder.h> |
39 | |
40 | namespace WebCore { |
41 | |
42 | template<typename CharacterType> |
43 | CSSPrimitiveValue::UnitType cssPrimitiveValueUnitFromTrie(const CharacterType* data, unsigned length) |
44 | { |
45 | ASSERT(data); |
46 | ASSERT(length); |
47 | switch (length) { |
48 | case 1: |
49 | switch (toASCIILower(data[0])) { |
50 | case 's': |
51 | return CSSPrimitiveValue::UnitType::CSS_S; |
52 | } |
53 | break; |
54 | case 2: |
55 | switch (toASCIILower(data[0])) { |
56 | case 'c': |
57 | switch (toASCIILower(data[1])) { |
58 | case 'h': |
59 | return CSSPrimitiveValue::UnitType::CSS_CHS; |
60 | case 'm': |
61 | return CSSPrimitiveValue::UnitType::CSS_CM; |
62 | } |
63 | break; |
64 | case 'e': |
65 | switch (toASCIILower(data[1])) { |
66 | case 'm': |
67 | return CSSPrimitiveValue::UnitType::CSS_EMS; |
68 | case 'x': |
69 | return CSSPrimitiveValue::UnitType::CSS_EXS; |
70 | } |
71 | break; |
72 | case 'f': |
73 | if (toASCIILower(data[1]) == 'r') |
74 | return CSSPrimitiveValue::UnitType::CSS_FR; |
75 | break; |
76 | case 'h': |
77 | if (toASCIILower(data[1]) == 'z') |
78 | return CSSPrimitiveValue::UnitType::CSS_HZ; |
79 | break; |
80 | case 'i': |
81 | if (toASCIILower(data[1]) == 'n') |
82 | return CSSPrimitiveValue::UnitType::CSS_IN; |
83 | break; |
84 | case 'm': |
85 | switch (toASCIILower(data[1])) { |
86 | case 'm': |
87 | return CSSPrimitiveValue::UnitType::CSS_MM; |
88 | case 's': |
89 | return CSSPrimitiveValue::UnitType::CSS_MS; |
90 | } |
91 | break; |
92 | case 'p': |
93 | switch (toASCIILower(data[1])) { |
94 | case 'c': |
95 | return CSSPrimitiveValue::UnitType::CSS_PC; |
96 | case 't': |
97 | return CSSPrimitiveValue::UnitType::CSS_PT; |
98 | case 'x': |
99 | return CSSPrimitiveValue::UnitType::CSS_PX; |
100 | } |
101 | break; |
102 | case 'v': |
103 | switch (toASCIILower(data[1])) { |
104 | case 'h': |
105 | return CSSPrimitiveValue::UnitType::CSS_VH; |
106 | case 'w': |
107 | return CSSPrimitiveValue::UnitType::CSS_VW; |
108 | } |
109 | break; |
110 | } |
111 | break; |
112 | case 3: |
113 | switch (toASCIILower(data[0])) { |
114 | case 'd': |
115 | switch (toASCIILower(data[1])) { |
116 | case 'e': |
117 | if (toASCIILower(data[2]) == 'g') |
118 | return CSSPrimitiveValue::UnitType::CSS_DEG; |
119 | break; |
120 | case 'p': |
121 | if (toASCIILower(data[2]) == 'i') |
122 | return CSSPrimitiveValue::UnitType::CSS_DPI; |
123 | break; |
124 | } |
125 | break; |
126 | case 'k': |
127 | if (toASCIILower(data[1]) == 'h' && toASCIILower(data[2]) == 'z') |
128 | return CSSPrimitiveValue::UnitType::CSS_KHZ; |
129 | break; |
130 | case 'r': |
131 | switch (toASCIILower(data[1])) { |
132 | case 'a': |
133 | if (toASCIILower(data[2]) == 'd') |
134 | return CSSPrimitiveValue::UnitType::CSS_RAD; |
135 | break; |
136 | case 'e': |
137 | if (toASCIILower(data[2]) == 'm') |
138 | return CSSPrimitiveValue::UnitType::CSS_REMS; |
139 | break; |
140 | } |
141 | break; |
142 | } |
143 | break; |
144 | case 4: |
145 | switch (toASCIILower(data[0])) { |
146 | case 'd': |
147 | switch (toASCIILower(data[1])) { |
148 | case 'p': |
149 | switch (toASCIILower(data[2])) { |
150 | case 'c': |
151 | if (toASCIILower(data[3]) == 'm') |
152 | return CSSPrimitiveValue::UnitType::CSS_DPCM; |
153 | break; |
154 | case 'p': |
155 | if (toASCIILower(data[3]) == 'x') |
156 | return CSSPrimitiveValue::UnitType::CSS_DPPX; |
157 | break; |
158 | } |
159 | break; |
160 | } |
161 | break; |
162 | case 'g': |
163 | if (toASCIILower(data[1]) == 'r' && toASCIILower(data[2]) == 'a' && toASCIILower(data[3]) == 'd') |
164 | return CSSPrimitiveValue::UnitType::CSS_GRAD; |
165 | break; |
166 | case 't': |
167 | if (toASCIILower(data[1]) == 'u' && toASCIILower(data[2]) == 'r' && toASCIILower(data[3]) == 'n') |
168 | return CSSPrimitiveValue::UnitType::CSS_TURN; |
169 | break; |
170 | case 'v': |
171 | switch (toASCIILower(data[1])) { |
172 | case 'm': |
173 | switch (toASCIILower(data[2])) { |
174 | case 'a': |
175 | if (toASCIILower(data[3]) == 'x') |
176 | return CSSPrimitiveValue::UnitType::CSS_VMAX; |
177 | break; |
178 | case 'i': |
179 | if (toASCIILower(data[3]) == 'n') |
180 | return CSSPrimitiveValue::UnitType::CSS_VMIN; |
181 | break; |
182 | } |
183 | break; |
184 | } |
185 | break; |
186 | } |
187 | break; |
188 | case 5: |
189 | switch (toASCIILower(data[0])) { |
190 | case '_': |
191 | if (toASCIILower(data[1]) == '_' && toASCIILower(data[2]) == 'q' && toASCIILower(data[3]) == 'e' && toASCIILower(data[4]) == 'm') |
192 | return CSSPrimitiveValue::UnitType::CSS_QUIRKY_EMS; |
193 | break; |
194 | } |
195 | break; |
196 | } |
197 | return CSSPrimitiveValue::UnitType::CSS_UNKNOWN; |
198 | } |
199 | |
200 | static CSSPrimitiveValue::UnitType stringToUnitType(StringView stringView) |
201 | { |
202 | if (stringView.is8Bit()) |
203 | return cssPrimitiveValueUnitFromTrie(stringView.characters8(), stringView.length()); |
204 | return cssPrimitiveValueUnitFromTrie(stringView.characters16(), stringView.length()); |
205 | } |
206 | |
207 | CSSParserToken::CSSParserToken(CSSParserTokenType type, BlockType blockType) |
208 | : m_type(type) |
209 | , m_blockType(blockType) |
210 | { |
211 | } |
212 | |
213 | // Just a helper used for Delimiter tokens. |
214 | CSSParserToken::CSSParserToken(CSSParserTokenType type, UChar c) |
215 | : m_type(type) |
216 | , m_blockType(NotBlock) |
217 | , m_delimiter(c) |
218 | { |
219 | ASSERT(m_type == DelimiterToken); |
220 | } |
221 | |
222 | CSSParserToken::CSSParserToken(CSSParserTokenType type, StringView value, BlockType blockType) |
223 | : m_type(type) |
224 | , m_blockType(blockType) |
225 | { |
226 | initValueFromStringView(value); |
227 | m_id = -1; |
228 | } |
229 | |
230 | CSSParserToken::CSSParserToken(CSSParserTokenType type, double numericValue, NumericValueType numericValueType, NumericSign sign) |
231 | : m_type(type) |
232 | , m_blockType(NotBlock) |
233 | , m_numericValueType(numericValueType) |
234 | , m_numericSign(sign) |
235 | , m_unit(static_cast<unsigned>(CSSPrimitiveValue::UnitType::CSS_NUMBER)) |
236 | { |
237 | ASSERT(type == NumberToken); |
238 | m_numericValue = numericValue; |
239 | } |
240 | |
241 | CSSParserToken::CSSParserToken(CSSParserTokenType type, UChar32 start, UChar32 end) |
242 | : m_type(UnicodeRangeToken) |
243 | , m_blockType(NotBlock) |
244 | { |
245 | ASSERT_UNUSED(type, type == UnicodeRangeToken); |
246 | m_unicodeRange.start = start; |
247 | m_unicodeRange.end = end; |
248 | } |
249 | |
250 | CSSParserToken::CSSParserToken(HashTokenType type, StringView value) |
251 | : m_type(HashToken) |
252 | , m_blockType(NotBlock) |
253 | , m_hashTokenType(type) |
254 | { |
255 | initValueFromStringView(value); |
256 | } |
257 | |
258 | void CSSParserToken::convertToDimensionWithUnit(StringView unit) |
259 | { |
260 | ASSERT(m_type == NumberToken); |
261 | m_type = DimensionToken; |
262 | initValueFromStringView(unit); |
263 | m_unit = static_cast<unsigned>(stringToUnitType(unit)); |
264 | } |
265 | |
266 | void CSSParserToken::convertToPercentage() |
267 | { |
268 | ASSERT(m_type == NumberToken); |
269 | m_type = PercentageToken; |
270 | m_unit = static_cast<unsigned>(CSSPrimitiveValue::UnitType::CSS_PERCENTAGE); |
271 | } |
272 | |
273 | UChar CSSParserToken::delimiter() const |
274 | { |
275 | ASSERT(m_type == DelimiterToken); |
276 | return m_delimiter; |
277 | } |
278 | |
279 | NumericSign CSSParserToken::numericSign() const |
280 | { |
281 | // This is valid for DimensionToken and PercentageToken, but only used |
282 | // in <an+b> parsing on NumberTokens. |
283 | ASSERT(m_type == NumberToken); |
284 | return static_cast<NumericSign>(m_numericSign); |
285 | } |
286 | |
287 | NumericValueType CSSParserToken::numericValueType() const |
288 | { |
289 | ASSERT(m_type == NumberToken || m_type == PercentageToken || m_type == DimensionToken); |
290 | return static_cast<NumericValueType>(m_numericValueType); |
291 | } |
292 | |
293 | double CSSParserToken::numericValue() const |
294 | { |
295 | ASSERT(m_type == NumberToken || m_type == PercentageToken || m_type == DimensionToken); |
296 | return m_numericValue; |
297 | } |
298 | |
299 | CSSPropertyID CSSParserToken::parseAsCSSPropertyID() const |
300 | { |
301 | ASSERT(m_type == IdentToken); |
302 | return cssPropertyID(value()); |
303 | } |
304 | |
305 | CSSValueID CSSParserToken::id() const |
306 | { |
307 | if (m_type != IdentToken) |
308 | return CSSValueInvalid; |
309 | if (m_id < 0) |
310 | m_id = cssValueKeywordID(value()); |
311 | return static_cast<CSSValueID>(m_id); |
312 | } |
313 | |
314 | CSSValueID CSSParserToken::functionId() const |
315 | { |
316 | if (m_type != FunctionToken) |
317 | return CSSValueInvalid; |
318 | if (m_id < 0) |
319 | m_id = cssValueKeywordID(value()); |
320 | return static_cast<CSSValueID>(m_id); |
321 | } |
322 | |
323 | bool CSSParserToken::hasStringBacking() const |
324 | { |
325 | CSSParserTokenType tokenType = type(); |
326 | return tokenType == IdentToken |
327 | || tokenType == FunctionToken |
328 | || tokenType == AtKeywordToken |
329 | || tokenType == HashToken |
330 | || tokenType == UrlToken |
331 | || tokenType == DimensionToken |
332 | || tokenType == StringToken; |
333 | } |
334 | |
335 | CSSParserToken CSSParserToken::copyWithUpdatedString(const StringView& string) const |
336 | { |
337 | CSSParserToken copy(*this); |
338 | copy.initValueFromStringView(string); |
339 | return copy; |
340 | } |
341 | |
342 | bool CSSParserToken::valueDataCharRawEqual(const CSSParserToken& other) const |
343 | { |
344 | if (m_valueLength != other.m_valueLength) |
345 | return false; |
346 | |
347 | if (m_valueDataCharRaw == other.m_valueDataCharRaw && m_valueIs8Bit == other.m_valueIs8Bit) |
348 | return true; |
349 | |
350 | if (m_valueIs8Bit) |
351 | return other.m_valueIs8Bit ? equal(static_cast<const LChar*>(m_valueDataCharRaw), static_cast<const LChar*>(other.m_valueDataCharRaw), m_valueLength) : equal(static_cast<const LChar*>(m_valueDataCharRaw), static_cast<const UChar*>(other.m_valueDataCharRaw), m_valueLength); |
352 | |
353 | return other.m_valueIs8Bit ? equal(static_cast<const UChar*>(m_valueDataCharRaw), static_cast<const LChar*>(other.m_valueDataCharRaw), m_valueLength) : equal(static_cast<const UChar*>(m_valueDataCharRaw), static_cast<const UChar*>(other.m_valueDataCharRaw), m_valueLength); |
354 | } |
355 | |
356 | bool CSSParserToken::operator==(const CSSParserToken& other) const |
357 | { |
358 | if (m_type != other.m_type) |
359 | return false; |
360 | switch (m_type) { |
361 | case DelimiterToken: |
362 | return delimiter() == other.delimiter(); |
363 | case HashToken: |
364 | if (m_hashTokenType != other.m_hashTokenType) |
365 | return false; |
366 | FALLTHROUGH; |
367 | case IdentToken: |
368 | case FunctionToken: |
369 | case StringToken: |
370 | case UrlToken: |
371 | return valueDataCharRawEqual(other); |
372 | case DimensionToken: |
373 | if (!valueDataCharRawEqual(other)) |
374 | return false; |
375 | FALLTHROUGH; |
376 | case NumberToken: |
377 | case PercentageToken: |
378 | return m_numericSign == other.m_numericSign && m_numericValue == other.m_numericValue && m_numericValueType == other.m_numericValueType; |
379 | case UnicodeRangeToken: |
380 | return m_unicodeRange.start == other.m_unicodeRange.start && m_unicodeRange.end == other.m_unicodeRange.end; |
381 | default: |
382 | return true; |
383 | } |
384 | } |
385 | |
386 | void CSSParserToken::serialize(StringBuilder& builder) const |
387 | { |
388 | // This is currently only used for @supports CSSOM. To keep our implementation |
389 | // simple we handle some of the edge cases incorrectly (see comments below). |
390 | switch (type()) { |
391 | case IdentToken: |
392 | serializeIdentifier(value().toString(), builder); |
393 | break; |
394 | case FunctionToken: |
395 | serializeIdentifier(value().toString(), builder); |
396 | builder.append('('); |
397 | break; |
398 | case AtKeywordToken: |
399 | builder.append('@'); |
400 | serializeIdentifier(value().toString(), builder); |
401 | break; |
402 | case HashToken: |
403 | builder.append('#'); |
404 | serializeIdentifier(value().toString(), builder, (getHashTokenType() == HashTokenUnrestricted)); |
405 | break; |
406 | case UrlToken: |
407 | builder.appendLiteral("url(" ); |
408 | serializeIdentifier(value().toString(), builder); |
409 | builder.append(')'); |
410 | break; |
411 | case DelimiterToken: |
412 | if (delimiter() == '\\') { |
413 | builder.appendLiteral("\\\n" ); |
414 | break; |
415 | } |
416 | builder.append(delimiter()); |
417 | break; |
418 | case NumberToken: |
419 | // These won't properly preserve the NumericValueType flag |
420 | if (m_numericSign == PlusSign) |
421 | builder.append('+'); |
422 | builder.appendFixedPrecisionNumber(numericValue()); |
423 | break; |
424 | case PercentageToken: |
425 | builder.appendFixedPrecisionNumber(numericValue()); |
426 | builder.append('%'); |
427 | break; |
428 | case DimensionToken: |
429 | // This will incorrectly serialize e.g. 4e3e2 as 4000e2 |
430 | builder.appendFixedPrecisionNumber(numericValue()); |
431 | serializeIdentifier(value().toString(), builder); |
432 | break; |
433 | case UnicodeRangeToken: |
434 | builder.appendLiteral("U+" ); |
435 | appendUnsignedAsHex(unicodeRangeStart(), builder); |
436 | builder.append('-'); |
437 | appendUnsignedAsHex(unicodeRangeEnd(), builder); |
438 | break; |
439 | case StringToken: |
440 | serializeString(value().toString(), builder); |
441 | break; |
442 | |
443 | case IncludeMatchToken: |
444 | builder.appendLiteral("~=" ); |
445 | break; |
446 | case DashMatchToken: |
447 | builder.appendLiteral("|=" ); |
448 | break; |
449 | case PrefixMatchToken: |
450 | builder.appendLiteral("^=" ); |
451 | break; |
452 | case SuffixMatchToken: |
453 | builder.appendLiteral("$=" ); |
454 | break; |
455 | case SubstringMatchToken: |
456 | builder.appendLiteral("*=" ); |
457 | break; |
458 | case ColumnToken: |
459 | builder.appendLiteral("||" ); |
460 | break; |
461 | case CDOToken: |
462 | builder.appendLiteral("<!--" ); |
463 | break; |
464 | case CDCToken: |
465 | builder.appendLiteral("-->" ); |
466 | break; |
467 | case BadStringToken: |
468 | builder.appendLiteral("'\n" ); |
469 | break; |
470 | case BadUrlToken: |
471 | builder.appendLiteral("url(()" ); |
472 | break; |
473 | case WhitespaceToken: |
474 | builder.append(' '); |
475 | break; |
476 | case ColonToken: |
477 | builder.append(':'); |
478 | break; |
479 | case SemicolonToken: |
480 | builder.append(';'); |
481 | break; |
482 | case CommaToken: |
483 | builder.append(','); |
484 | break; |
485 | case LeftParenthesisToken: |
486 | builder.append('('); |
487 | break; |
488 | case RightParenthesisToken: |
489 | builder.append(')'); |
490 | break; |
491 | case LeftBracketToken: |
492 | builder.append('['); |
493 | break; |
494 | case RightBracketToken: |
495 | builder.append(']'); |
496 | break; |
497 | case LeftBraceToken: |
498 | builder.append('{'); |
499 | break; |
500 | case RightBraceToken: |
501 | builder.append('}'); |
502 | break; |
503 | |
504 | case EOFToken: |
505 | case CommentToken: |
506 | ASSERT_NOT_REACHED(); |
507 | break; |
508 | } |
509 | } |
510 | |
511 | } // namespace WebCore |
512 | |