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
40namespace WebCore {
41
42template<typename CharacterType>
43CSSPrimitiveValue::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
200static 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
207CSSParserToken::CSSParserToken(CSSParserTokenType type, BlockType blockType)
208 : m_type(type)
209 , m_blockType(blockType)
210{
211}
212
213// Just a helper used for Delimiter tokens.
214CSSParserToken::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
222CSSParserToken::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
230CSSParserToken::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
241CSSParserToken::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
250CSSParserToken::CSSParserToken(HashTokenType type, StringView value)
251 : m_type(HashToken)
252 , m_blockType(NotBlock)
253 , m_hashTokenType(type)
254{
255 initValueFromStringView(value);
256}
257
258void 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
266void CSSParserToken::convertToPercentage()
267{
268 ASSERT(m_type == NumberToken);
269 m_type = PercentageToken;
270 m_unit = static_cast<unsigned>(CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
271}
272
273UChar CSSParserToken::delimiter() const
274{
275 ASSERT(m_type == DelimiterToken);
276 return m_delimiter;
277}
278
279NumericSign 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
287NumericValueType CSSParserToken::numericValueType() const
288{
289 ASSERT(m_type == NumberToken || m_type == PercentageToken || m_type == DimensionToken);
290 return static_cast<NumericValueType>(m_numericValueType);
291}
292
293double CSSParserToken::numericValue() const
294{
295 ASSERT(m_type == NumberToken || m_type == PercentageToken || m_type == DimensionToken);
296 return m_numericValue;
297}
298
299CSSPropertyID CSSParserToken::parseAsCSSPropertyID() const
300{
301 ASSERT(m_type == IdentToken);
302 return cssPropertyID(value());
303}
304
305CSSValueID 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
314CSSValueID 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
323bool 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
335CSSParserToken CSSParserToken::copyWithUpdatedString(const StringView& string) const
336{
337 CSSParserToken copy(*this);
338 copy.initValueFromStringView(string);
339 return copy;
340}
341
342bool 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
356bool 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
386void 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