1// Copyright 2016 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 "CSSPropertyParserHelpers.h"
32
33#include "CSSCalculationValue.h"
34#include "CSSCanvasValue.h"
35#include "CSSCrossfadeValue.h"
36#include "CSSFilterImageValue.h"
37#include "CSSGradientValue.h"
38#include "CSSImageSetValue.h"
39#include "CSSImageValue.h"
40#include "CSSNamedImageValue.h"
41#include "CSSPaintImageValue.h"
42#include "CSSParserIdioms.h"
43#include "CSSValuePool.h"
44#include "Pair.h"
45#include "RuntimeEnabledFeatures.h"
46#include "StyleColor.h"
47#include <wtf/text/StringConcatenateNumbers.h>
48
49namespace WebCore {
50
51namespace CSSPropertyParserHelpers {
52
53bool consumeCommaIncludingWhitespace(CSSParserTokenRange& range)
54{
55 CSSParserToken value = range.peek();
56 if (value.type() != CommaToken)
57 return false;
58 range.consumeIncludingWhitespace();
59 return true;
60}
61
62bool consumeSlashIncludingWhitespace(CSSParserTokenRange& range)
63{
64 CSSParserToken value = range.peek();
65 if (value.type() != DelimiterToken || value.delimiter() != '/')
66 return false;
67 range.consumeIncludingWhitespace();
68 return true;
69}
70
71CSSParserTokenRange consumeFunction(CSSParserTokenRange& range)
72{
73 ASSERT(range.peek().type() == FunctionToken);
74 CSSParserTokenRange contents = range.consumeBlock();
75 range.consumeWhitespace();
76 contents.consumeWhitespace();
77 return contents;
78}
79
80// FIXME: consider pulling in the parsing logic from CSSCalculationValue.cpp.
81class CalcParser {
82public:
83 explicit CalcParser(CSSParserTokenRange& range, CalculationCategory destinationCategory, ValueRange valueRange = ValueRangeAll)
84 : m_sourceRange(range)
85 , m_range(range)
86 {
87 const CSSParserToken& token = range.peek();
88 auto functionId = token.functionId();
89 if (functionId == CSSValueCalc || functionId == CSSValueWebkitCalc || functionId == CSSValueMin || functionId == CSSValueMax)
90 m_calcValue = CSSCalcValue::create(functionId, consumeFunction(m_range), destinationCategory, valueRange);
91 }
92
93 const CSSCalcValue* value() const { return m_calcValue.get(); }
94 RefPtr<CSSPrimitiveValue> consumeValue()
95 {
96 if (!m_calcValue)
97 return nullptr;
98 m_sourceRange = m_range;
99 return CSSValuePool::singleton().createValue(WTFMove(m_calcValue));
100 }
101 RefPtr<CSSPrimitiveValue> consumeNumber()
102 {
103 if (!m_calcValue)
104 return nullptr;
105 m_sourceRange = m_range;
106 return CSSValuePool::singleton().createValue(m_calcValue->doubleValue(), CSSPrimitiveValue::UnitType::CSS_NUMBER);
107 }
108
109 bool consumeNumberRaw(double& result)
110 {
111 if (!m_calcValue || m_calcValue->category() != CalculationCategory::Number)
112 return false;
113 m_sourceRange = m_range;
114 result = m_calcValue->doubleValue();
115 return true;
116 }
117
118 bool consumePositiveIntegerRaw(int& result)
119 {
120 if (!m_calcValue || m_calcValue->category() != CalculationCategory::Number || !m_calcValue->isInt())
121 return false;
122 result = static_cast<int>(m_calcValue->doubleValue());
123 if (result < 1)
124 return false;
125 m_sourceRange = m_range;
126 return true;
127 }
128
129private:
130 CSSParserTokenRange& m_sourceRange;
131 CSSParserTokenRange m_range;
132 RefPtr<CSSCalcValue> m_calcValue;
133};
134
135RefPtr<CSSPrimitiveValue> consumeInteger(CSSParserTokenRange& range, double minimumValue)
136{
137 const CSSParserToken& token = range.peek();
138 if (token.type() == NumberToken) {
139 if (token.numericValueType() == NumberValueType || token.numericValue() < minimumValue)
140 return nullptr;
141 return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_NUMBER);
142 }
143
144 if (token.type() != FunctionToken)
145 return nullptr;
146
147 CalcParser calcParser(range, CalculationCategory::Number);
148 if (const CSSCalcValue* calculation = calcParser.value()) {
149 if (calculation->category() != CalculationCategory::Number || !calculation->isInt())
150 return nullptr;
151 double value = calculation->doubleValue();
152 if (value < minimumValue)
153 return nullptr;
154 return calcParser.consumeNumber();
155 }
156
157 return nullptr;
158}
159
160RefPtr<CSSPrimitiveValue> consumePositiveInteger(CSSParserTokenRange& range)
161{
162 return consumeInteger(range, 1);
163}
164
165bool consumePositiveIntegerRaw(CSSParserTokenRange& range, int& result)
166{
167 const CSSParserToken& token = range.peek();
168 if (token.type() == NumberToken) {
169 if (token.numericValueType() == NumberValueType || token.numericValue() < 1)
170 return false;
171 result = range.consumeIncludingWhitespace().numericValue();
172 return true;
173 }
174
175 if (token.type() != FunctionToken)
176 return false;
177
178 CalcParser calcParser(range, CalculationCategory::Number);
179 return calcParser.consumePositiveIntegerRaw(result);
180}
181
182bool consumeNumberRaw(CSSParserTokenRange& range, double& result)
183{
184 const CSSParserToken& token = range.peek();
185 if (token.type() == NumberToken) {
186 result = range.consumeIncludingWhitespace().numericValue();
187 return true;
188 }
189
190 if (token.type() != FunctionToken)
191 return false;
192
193 CalcParser calcParser(range, CalculationCategory::Number, ValueRangeAll);
194 return calcParser.consumeNumberRaw(result);
195}
196
197// FIXME: Work out if this can just call consumeNumberRaw
198RefPtr<CSSPrimitiveValue> consumeNumber(CSSParserTokenRange& range, ValueRange valueRange)
199{
200 const CSSParserToken& token = range.peek();
201 if (token.type() == NumberToken) {
202 if (valueRange == ValueRangeNonNegative && token.numericValue() < 0)
203 return nullptr;
204 return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
205 }
206
207 if (token.type() != FunctionToken)
208 return nullptr;
209
210 CalcParser calcParser(range, CalculationCategory::Number, ValueRangeAll);
211 if (const CSSCalcValue* calculation = calcParser.value()) {
212 // FIXME: Calcs should not be subject to parse time range checks.
213 // spec: https://drafts.csswg.org/css-values-3/#calc-range
214 if (calculation->category() != CalculationCategory::Number || (valueRange == ValueRangeNonNegative && calculation->isNegative()))
215 return nullptr;
216 return calcParser.consumeNumber();
217 }
218
219 return nullptr;
220}
221
222#if !ENABLE(VARIATION_FONTS)
223static inline bool divisibleBy100(double value)
224{
225 return static_cast<int>(value / 100) * 100 == value;
226}
227#endif
228
229RefPtr<CSSPrimitiveValue> consumeFontWeightNumber(CSSParserTokenRange& range)
230{
231 // Values less than or equal to 0 or greater than or equal to 1000 are parse errors.
232 auto& token = range.peek();
233 if (token.type() == NumberToken && token.numericValue() >= 1 && token.numericValue() <= 1000
234#if !ENABLE(VARIATION_FONTS)
235 && token.numericValueType() == IntegerValueType && divisibleBy100(token.numericValue())
236#endif
237 )
238 return consumeNumber(range, ValueRangeAll);
239
240 if (token.type() != FunctionToken)
241 return nullptr;
242
243 // "[For calc()], the used value resulting from an expression must be clamped to the range allowed in the target context."
244 CalcParser calcParser(range, CalculationCategory::Number, ValueRangeAll);
245 double result;
246 if (calcParser.consumeNumberRaw(result)
247#if !ENABLE(VARIATION_FONTS)
248 && result > 0 && result < 1000 && divisibleBy100(result)
249#endif
250 ) {
251 result = std::min(std::max(result, std::nextafter(0., 1.)), std::nextafter(1000., 0.));
252 return CSSValuePool::singleton().createValue(result, CSSPrimitiveValue::UnitType::CSS_NUMBER);
253 }
254
255 return nullptr;
256}
257
258inline bool shouldAcceptUnitlessValue(double value, CSSParserMode cssParserMode, UnitlessQuirk unitless)
259{
260 // FIXME: Presentational HTML attributes shouldn't use the CSS parser for lengths
261 return value == 0
262 || isUnitLessValueParsingEnabledForMode(cssParserMode)
263 || (cssParserMode == HTMLQuirksMode && unitless == UnitlessQuirk::Allow);
264}
265
266RefPtr<CSSPrimitiveValue> consumeLength(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
267{
268 const CSSParserToken& token = range.peek();
269 if (token.type() == DimensionToken) {
270 switch (token.unitType()) {
271 case CSSPrimitiveValue::UnitType::CSS_QUIRKY_EMS:
272 if (cssParserMode != UASheetMode)
273 return nullptr;
274 FALLTHROUGH;
275 case CSSPrimitiveValue::UnitType::CSS_EMS:
276 case CSSPrimitiveValue::UnitType::CSS_REMS:
277 case CSSPrimitiveValue::UnitType::CSS_CHS:
278 case CSSPrimitiveValue::UnitType::CSS_EXS:
279 case CSSPrimitiveValue::UnitType::CSS_PX:
280 case CSSPrimitiveValue::UnitType::CSS_CM:
281 case CSSPrimitiveValue::UnitType::CSS_MM:
282 case CSSPrimitiveValue::UnitType::CSS_IN:
283 case CSSPrimitiveValue::UnitType::CSS_PT:
284 case CSSPrimitiveValue::UnitType::CSS_PC:
285 case CSSPrimitiveValue::UnitType::CSS_VW:
286 case CSSPrimitiveValue::UnitType::CSS_VH:
287 case CSSPrimitiveValue::UnitType::CSS_VMIN:
288 case CSSPrimitiveValue::UnitType::CSS_VMAX:
289 break;
290 default:
291 return nullptr;
292 }
293 if ((valueRange == ValueRangeNonNegative && token.numericValue() < 0) || std::isinf(token.numericValue()))
294 return nullptr;
295 return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
296 }
297 if (token.type() == NumberToken) {
298 if (!shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless)
299 || (valueRange == ValueRangeNonNegative && token.numericValue() < 0))
300 return nullptr;
301 if (std::isinf(token.numericValue()))
302 return nullptr;
303 CSSPrimitiveValue::UnitType unitType = CSSPrimitiveValue::UnitType::CSS_PX;
304 return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), unitType);
305 }
306
307 if (token.type() != FunctionToken)
308 return nullptr;
309
310 CalcParser calcParser(range, CalculationCategory::Length, valueRange);
311 if (calcParser.value() && calcParser.value()->category() == CalculationCategory::Length)
312 return calcParser.consumeValue();
313
314 return nullptr;
315}
316
317RefPtr<CSSPrimitiveValue> consumePercent(CSSParserTokenRange& range, ValueRange valueRange)
318{
319 const CSSParserToken& token = range.peek();
320 if (token.type() == PercentageToken) {
321 if ((valueRange == ValueRangeNonNegative && token.numericValue() < 0) || std::isinf(token.numericValue()))
322 return nullptr;
323 return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
324 }
325
326 if (token.type() != FunctionToken)
327 return nullptr;
328
329 CalcParser calcParser(range, CalculationCategory::Percent, valueRange);
330 if (const CSSCalcValue* calculation = calcParser.value()) {
331 if (calculation->category() == CalculationCategory::Percent)
332 return calcParser.consumeValue();
333 }
334 return nullptr;
335}
336
337static bool canConsumeCalcValue(CalculationCategory category, CSSParserMode cssParserMode)
338{
339 if (category == CalculationCategory::Length || category == CalculationCategory::Percent || category == CalculationCategory::PercentLength)
340 return true;
341
342 if (cssParserMode != SVGAttributeMode)
343 return false;
344
345 if (category == CalculationCategory::Number || category == CalculationCategory::PercentNumber)
346 return true;
347
348 return false;
349}
350
351RefPtr<CSSPrimitiveValue> consumeLengthOrPercent(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
352{
353 const CSSParserToken& token = range.peek();
354 if (token.type() == DimensionToken || token.type() == NumberToken)
355 return consumeLength(range, cssParserMode, valueRange, unitless);
356 if (token.type() == PercentageToken)
357 return consumePercent(range, valueRange);
358
359 if (token.type() != FunctionToken)
360 return nullptr;
361
362 CalcParser calcParser(range, CalculationCategory::Length, valueRange);
363 if (const CSSCalcValue* calculation = calcParser.value()) {
364 if (canConsumeCalcValue(calculation->category(), cssParserMode))
365 return calcParser.consumeValue();
366 }
367 return nullptr;
368}
369
370RefPtr<CSSPrimitiveValue> consumeAngle(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
371{
372 const CSSParserToken& token = range.peek();
373 if (token.type() == DimensionToken) {
374 switch (token.unitType()) {
375 case CSSPrimitiveValue::UnitType::CSS_DEG:
376 case CSSPrimitiveValue::UnitType::CSS_RAD:
377 case CSSPrimitiveValue::UnitType::CSS_GRAD:
378 case CSSPrimitiveValue::UnitType::CSS_TURN:
379 return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
380 default:
381 return nullptr;
382 }
383 }
384
385 if (token.type() == NumberToken && shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless))
386 return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_DEG);
387
388 if (token.type() != FunctionToken)
389 return nullptr;
390
391 CalcParser calcParser(range, CalculationCategory::Angle, ValueRangeAll);
392 if (const CSSCalcValue* calculation = calcParser.value()) {
393 if (calculation->category() == CalculationCategory::Angle)
394 return calcParser.consumeValue();
395 }
396 return nullptr;
397}
398
399static RefPtr<CSSPrimitiveValue> consumeAngleOrPercent(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
400{
401 const CSSParserToken& token = range.peek();
402 if (token.type() == DimensionToken) {
403 switch (token.unitType()) {
404 case CSSPrimitiveValue::UnitType::CSS_DEG:
405 case CSSPrimitiveValue::UnitType::CSS_RAD:
406 case CSSPrimitiveValue::UnitType::CSS_GRAD:
407 case CSSPrimitiveValue::UnitType::CSS_TURN:
408 return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
409 default:
410 return nullptr;
411 }
412 }
413 if (token.type() == NumberToken && shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless))
414 return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_DEG);
415
416 if (token.type() == PercentageToken)
417 return consumePercent(range, valueRange);
418
419 if (token.type() != FunctionToken)
420 return nullptr;
421
422 CalcParser angleCalcParser(range, CalculationCategory::Angle, valueRange);
423 if (const CSSCalcValue* calculation = angleCalcParser.value()) {
424 if (calculation->category() == CalculationCategory::Angle)
425 return angleCalcParser.consumeValue();
426 }
427
428 CalcParser percentCalcParser(range, CalculationCategory::Percent, valueRange);
429 if (const CSSCalcValue* calculation = percentCalcParser.value()) {
430 if (calculation->category() == CalculationCategory::Percent)
431 return percentCalcParser.consumeValue();
432 }
433 return nullptr;
434}
435
436RefPtr<CSSPrimitiveValue> consumeTime(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
437{
438 const CSSParserToken& token = range.peek();
439 CSSPrimitiveValue::UnitType unit = token.unitType();
440 bool acceptUnitless = token.type() == NumberToken && shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless);
441 if (acceptUnitless)
442 unit = CSSPrimitiveValue::UnitType::CSS_MS;
443 if (token.type() == DimensionToken || acceptUnitless) {
444 if (valueRange == ValueRangeNonNegative && token.numericValue() < 0)
445 return nullptr;
446 if (unit == CSSPrimitiveValue::UnitType::CSS_MS || unit == CSSPrimitiveValue::UnitType::CSS_S)
447 return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), unit);
448 return nullptr;
449 }
450
451 if (token.type() != FunctionToken)
452 return nullptr;
453
454 CalcParser calcParser(range, CalculationCategory::Time, valueRange);
455 if (const CSSCalcValue* calculation = calcParser.value()) {
456 if (calculation->category() == CalculationCategory::Time)
457 return calcParser.consumeValue();
458 }
459 return nullptr;
460}
461
462RefPtr<CSSPrimitiveValue> consumeResolution(CSSParserTokenRange& range)
463{
464 const CSSParserToken& token = range.peek();
465 // Unlike the other types, calc() does not work with <resolution>.
466 if (token.type() != DimensionToken)
467 return nullptr;
468 CSSPrimitiveValue::UnitType unit = token.unitType();
469 if (unit == CSSPrimitiveValue::UnitType::CSS_DPPX || unit == CSSPrimitiveValue::UnitType::CSS_DPI || unit == CSSPrimitiveValue::UnitType::CSS_DPCM)
470 return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), unit);
471 return nullptr;
472}
473
474RefPtr<CSSPrimitiveValue> consumeIdent(CSSParserTokenRange& range)
475{
476 if (range.peek().type() != IdentToken)
477 return nullptr;
478 return CSSValuePool::singleton().createIdentifierValue(range.consumeIncludingWhitespace().id());
479}
480
481RefPtr<CSSPrimitiveValue> consumeIdentRange(CSSParserTokenRange& range, CSSValueID lower, CSSValueID upper)
482{
483 if (range.peek().id() < lower || range.peek().id() > upper)
484 return nullptr;
485 return consumeIdent(range);
486}
487
488// FIXME-NEWPARSER: Eventually we'd like this to use CSSCustomIdentValue, but we need
489// to do other plumbing work first (like changing Pair to CSSValuePair and make it not
490// use only primitive values).
491RefPtr<CSSPrimitiveValue> consumeCustomIdent(CSSParserTokenRange& range)
492{
493 if (range.peek().type() != IdentToken || isCSSWideKeyword(range.peek().id()))
494 return nullptr;
495 return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().value().toString(), CSSPrimitiveValue::UnitType::CSS_STRING);
496}
497
498RefPtr<CSSPrimitiveValue> consumeString(CSSParserTokenRange& range)
499{
500 if (range.peek().type() != StringToken)
501 return nullptr;
502 return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().value().toString(), CSSPrimitiveValue::UnitType::CSS_STRING);
503}
504
505StringView consumeUrlAsStringView(CSSParserTokenRange& range)
506{
507 const CSSParserToken& token = range.peek();
508 if (token.type() == UrlToken) {
509 range.consumeIncludingWhitespace();
510 return token.value();
511 }
512 if (token.functionId() == CSSValueUrl) {
513 CSSParserTokenRange urlRange = range;
514 CSSParserTokenRange urlArgs = urlRange.consumeBlock();
515 const CSSParserToken& next = urlArgs.consumeIncludingWhitespace();
516 if (next.type() == BadStringToken || !urlArgs.atEnd())
517 return StringView();
518 ASSERT(next.type() == StringToken);
519 range = urlRange;
520 range.consumeWhitespace();
521 return next.value();
522 }
523
524 return StringView();
525}
526
527RefPtr<CSSPrimitiveValue> consumeUrl(CSSParserTokenRange& range)
528{
529 StringView url = consumeUrlAsStringView(range);
530 if (url.isNull())
531 return nullptr;
532 return CSSValuePool::singleton().createValue(url.toString(), CSSPrimitiveValue::UnitType::CSS_URI);
533}
534
535static int clampRGBComponent(const CSSPrimitiveValue& value)
536{
537 double result = value.doubleValue();
538 if (value.isPercentage())
539 result = result / 100.0 * 255.0;
540
541 return clampTo<int>(round(result), 0, 255);
542}
543
544static Color parseRGBParameters(CSSParserTokenRange& range)
545{
546 ASSERT(range.peek().functionId() == CSSValueRgb || range.peek().functionId() == CSSValueRgba);
547 Color result;
548 CSSParserTokenRange args = consumeFunction(range);
549 RefPtr<CSSPrimitiveValue> colorParameter = consumeNumber(args, ValueRangeAll);
550 if (!colorParameter)
551 colorParameter = consumePercent(args, ValueRangeAll);
552 if (!colorParameter)
553 return Color();
554
555 const bool isPercent = colorParameter->isPercentage();
556
557 enum class ColorSyntax {
558 Commas,
559 WhitespaceSlash,
560 };
561
562 ColorSyntax syntax = ColorSyntax::Commas;
563 auto consumeSeparator = [&] {
564 if (syntax == ColorSyntax::Commas)
565 return consumeCommaIncludingWhitespace(args);
566
567 return true;
568 };
569
570 int colorArray[3];
571 colorArray[0] = clampRGBComponent(*colorParameter);
572 for (int i = 1; i < 3; i++) {
573 if (i == 1)
574 syntax = consumeCommaIncludingWhitespace(args) ? ColorSyntax::Commas : ColorSyntax::WhitespaceSlash;
575 else if (!consumeSeparator())
576 return Color();
577
578 colorParameter = isPercent ? consumePercent(args, ValueRangeAll) : consumeNumber(args, ValueRangeAll);
579 if (!colorParameter)
580 return Color();
581 colorArray[i] = clampRGBComponent(*colorParameter);
582 }
583
584 // Historically, alpha was only parsed for rgba(), but css-color-4 specifies that rgba() is a simple alias for rgb().
585 auto consumeAlphaSeparator = [&] {
586 if (syntax == ColorSyntax::Commas)
587 return consumeCommaIncludingWhitespace(args);
588
589 return consumeSlashIncludingWhitespace(args);
590 };
591
592 int alphaComponent = 255;
593 if (consumeAlphaSeparator()) {
594 double alpha;
595 if (!consumeNumberRaw(args, alpha)) {
596 auto alphaPercent = consumePercent(args, ValueRangeAll);
597 if (!alphaPercent)
598 return Color();
599 alpha = alphaPercent->doubleValue() / 100.0;
600 }
601
602 // Convert the floating pointer number of alpha to an integer in the range [0, 256),
603 // with an equal distribution across all 256 values.
604 alphaComponent = static_cast<int>(clampTo<double>(alpha, 0.0, 1.0) * nextafter(256.0, 0.0));
605 };
606
607 result = Color(makeRGBA(colorArray[0], colorArray[1], colorArray[2], alphaComponent));
608
609 if (!args.atEnd())
610 return Color();
611
612 return result;
613}
614
615static Color parseHSLParameters(CSSParserTokenRange& range, CSSParserMode cssParserMode)
616{
617 ASSERT(range.peek().functionId() == CSSValueHsl || range.peek().functionId() == CSSValueHsla);
618 CSSParserTokenRange args = consumeFunction(range);
619 auto hslValue = consumeAngle(args, cssParserMode, UnitlessQuirk::Forbid);
620 double angleInDegrees;
621 if (!hslValue) {
622 hslValue = consumeNumber(args, ValueRangeAll);
623 if (!hslValue)
624 return Color();
625 angleInDegrees = hslValue->doubleValue();
626 } else
627 angleInDegrees = hslValue->computeDegrees();
628 double colorArray[3];
629 // The hue needs to be in the range [0.0, 6.0) for calcHue()
630 colorArray[0] = fmod(fmod(angleInDegrees, 360.0) + 360.0, 360.0) / 60.0;
631 bool requiresCommas = false;
632 for (int i = 1; i < 3; i++) {
633 if (consumeCommaIncludingWhitespace(args)) {
634 if (i != 1 && !requiresCommas)
635 return Color();
636 requiresCommas = true;
637 } else if (requiresCommas || args.atEnd() || (&args.peek() - 1)->type() != WhitespaceToken)
638 return Color();
639 hslValue = consumePercent(args, ValueRangeAll);
640 if (!hslValue)
641 return Color();
642 double doubleValue = hslValue->doubleValue();
643 colorArray[i] = clampTo<double>(doubleValue, 0.0, 100.0) / 100.0; // Needs to be value between 0 and 1.0.
644 }
645
646 double alpha = 1.0;
647 bool commaConsumed = consumeCommaIncludingWhitespace(args);
648 bool slashConsumed = consumeSlashIncludingWhitespace(args);
649 if ((commaConsumed && !requiresCommas) || (slashConsumed && requiresCommas))
650 return Color();
651 if (commaConsumed || slashConsumed) {
652 if (!consumeNumberRaw(args, alpha)) {
653 auto alphaPercent = consumePercent(args, ValueRangeAll);
654 if (!alphaPercent)
655 return Color();
656 alpha = alphaPercent->doubleValue() / 100.0f;
657 }
658 alpha = clampTo<double>(alpha, 0.0, 1.0);
659 }
660
661 if (!args.atEnd())
662 return Color();
663
664 return Color(makeRGBAFromHSLA(colorArray[0], colorArray[1], colorArray[2], alpha));
665}
666
667static Color parseColorFunctionParameters(CSSParserTokenRange& range)
668{
669 ASSERT(range.peek().functionId() == CSSValueColor);
670 CSSParserTokenRange args = consumeFunction(range);
671
672 ColorSpace colorSpace;
673 switch (args.peek().id()) {
674 case CSSValueSRGB:
675 colorSpace = ColorSpaceSRGB;
676 break;
677 case CSSValueDisplayP3:
678 colorSpace = ColorSpaceDisplayP3;
679 break;
680 default:
681 return Color();
682 }
683 consumeIdent(args);
684
685 double colorChannels[4] = { 0, 0, 0, 1 };
686 for (int i = 0; i < 3; ++i) {
687 double value;
688 if (consumeNumberRaw(args, value))
689 colorChannels[i] = std::max(0.0, std::min(1.0, value));
690 else
691 break;
692 }
693
694 if (consumeSlashIncludingWhitespace(args)) {
695 auto alphaParameter = consumePercent(args, ValueRangeAll);
696 if (!alphaParameter)
697 alphaParameter = consumeNumber(args, ValueRangeAll);
698 if (!alphaParameter)
699 return Color();
700
701 colorChannels[3] = std::max(0.0, std::min(1.0, alphaParameter->isPercentage() ? (alphaParameter->doubleValue() / 100) : alphaParameter->doubleValue()));
702 }
703
704 // FIXME: Support the comma-separated list of fallback color values.
705
706 if (!args.atEnd())
707 return Color();
708
709 return Color(colorChannels[0], colorChannels[1], colorChannels[2], colorChannels[3], colorSpace);
710}
711
712static Color parseHexColor(CSSParserTokenRange& range, bool acceptQuirkyColors)
713{
714 RGBA32 result;
715 const CSSParserToken& token = range.peek();
716 if (token.type() == HashToken) {
717 if (!Color::parseHexColor(token.value(), result))
718 return Color();
719 } else if (acceptQuirkyColors) {
720 String color;
721 if (token.type() == NumberToken || token.type() == DimensionToken) {
722 if (token.numericValueType() != IntegerValueType
723 || token.numericValue() < 0. || token.numericValue() >= 1000000.)
724 return Color();
725 if (token.type() == NumberToken) // e.g. 112233
726 color = String::number(static_cast<int>(token.numericValue()));
727 else // e.g. 0001FF
728 color = makeString(static_cast<int>(token.numericValue()), token.value().toString());
729 while (color.length() < 6)
730 color = "0" + color;
731 } else if (token.type() == IdentToken) { // e.g. FF0000
732 color = token.value().toString();
733 }
734 unsigned length = color.length();
735 if (length != 3 && length != 6)
736 return Color();
737 if (!Color::parseHexColor(color, result))
738 return Color();
739 } else {
740 return Color();
741 }
742 range.consumeIncludingWhitespace();
743 return Color(result);
744}
745
746static Color parseColorFunction(CSSParserTokenRange& range, CSSParserMode cssParserMode)
747{
748 CSSParserTokenRange colorRange = range;
749 CSSValueID functionId = range.peek().functionId();
750 Color color;
751 switch (functionId) {
752 case CSSValueRgb:
753 case CSSValueRgba:
754 color = parseRGBParameters(colorRange);
755 break;
756 case CSSValueHsl:
757 case CSSValueHsla:
758 color = parseHSLParameters(colorRange, cssParserMode);
759 break;
760 case CSSValueColor:
761 color = parseColorFunctionParameters(colorRange);
762 break;
763 default:
764 return Color();
765 }
766 if (color.isValid())
767 range = colorRange;
768 return color;
769}
770
771RefPtr<CSSPrimitiveValue> consumeColor(CSSParserTokenRange& range, CSSParserMode cssParserMode, bool acceptQuirkyColors)
772{
773 CSSValueID id = range.peek().id();
774 if (StyleColor::isColorKeyword(id)) {
775 if (!isValueAllowedInMode(id, cssParserMode))
776 return nullptr;
777 return consumeIdent(range);
778 }
779 Color color = parseHexColor(range, acceptQuirkyColors);
780 if (!color.isValid())
781 color = parseColorFunction(range, cssParserMode);
782 if (!color.isValid())
783 return nullptr;
784 return CSSValuePool::singleton().createValue(color);
785}
786
787static RefPtr<CSSPrimitiveValue> consumePositionComponent(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
788{
789 if (range.peek().type() == IdentToken)
790 return consumeIdent<CSSValueLeft, CSSValueTop, CSSValueBottom, CSSValueRight, CSSValueCenter>(range);
791 return consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, unitless);
792}
793
794static bool isHorizontalPositionKeywordOnly(const CSSPrimitiveValue& value)
795{
796 return value.isValueID() && (value.valueID() == CSSValueLeft || value.valueID() == CSSValueRight);
797}
798
799static bool isVerticalPositionKeywordOnly(const CSSPrimitiveValue& value)
800{
801 return value.isValueID() && (value.valueID() == CSSValueTop || value.valueID() == CSSValueBottom);
802}
803
804static void positionFromOneValue(CSSPrimitiveValue& value, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
805{
806 bool valueAppliesToYAxisOnly = isVerticalPositionKeywordOnly(value);
807 resultX = &value;
808 resultY = CSSPrimitiveValue::createIdentifier(CSSValueCenter);
809 if (valueAppliesToYAxisOnly)
810 std::swap(resultX, resultY);
811}
812
813static bool positionFromTwoValues(CSSPrimitiveValue& value1, CSSPrimitiveValue& value2,
814 RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
815{
816 bool mustOrderAsXY = isHorizontalPositionKeywordOnly(value1) || isVerticalPositionKeywordOnly(value2)
817 || !value1.isValueID() || !value2.isValueID();
818 bool mustOrderAsYX = isVerticalPositionKeywordOnly(value1) || isHorizontalPositionKeywordOnly(value2);
819 if (mustOrderAsXY && mustOrderAsYX)
820 return false;
821 resultX = &value1;
822 resultY = &value2;
823 if (mustOrderAsYX)
824 std::swap(resultX, resultY);
825 return true;
826}
827
828namespace CSSPropertyParserHelpersInternal {
829template<typename... Args>
830static Ref<CSSPrimitiveValue> createPrimitiveValuePair(Args&&... args)
831{
832 return CSSValuePool::singleton().createValue(Pair::create(std::forward<Args>(args)...));
833}
834}
835
836static bool positionFromThreeOrFourValues(CSSPrimitiveValue** values, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
837{
838 CSSPrimitiveValue* center = nullptr;
839 for (int i = 0; values[i]; i++) {
840 CSSPrimitiveValue* currentValue = values[i];
841 if (!currentValue->isValueID())
842 return false;
843 CSSValueID id = currentValue->valueID();
844
845 if (id == CSSValueCenter) {
846 if (center)
847 return false;
848 center = currentValue;
849 continue;
850 }
851
852 RefPtr<CSSPrimitiveValue> result;
853 if (values[i + 1] && !values[i + 1]->isValueID())
854 result = CSSPropertyParserHelpersInternal::createPrimitiveValuePair(currentValue, values[++i]);
855 else
856 result = currentValue;
857
858 if (id == CSSValueLeft || id == CSSValueRight) {
859 if (resultX)
860 return false;
861 resultX = result;
862 } else {
863 ASSERT(id == CSSValueTop || id == CSSValueBottom);
864 if (resultY)
865 return false;
866 resultY = result;
867 }
868 }
869
870 if (center) {
871 ASSERT(resultX || resultY);
872 if (resultX && resultY)
873 return false;
874 if (!resultX)
875 resultX = center;
876 else
877 resultY = center;
878 }
879
880 ASSERT(resultX && resultY);
881 return true;
882}
883
884// FIXME: This may consume from the range upon failure. The background
885// shorthand works around it, but we should just fix it here.
886bool consumePosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
887{
888 RefPtr<CSSPrimitiveValue> value1 = consumePositionComponent(range, cssParserMode, unitless);
889 if (!value1)
890 return false;
891
892 RefPtr<CSSPrimitiveValue> value2 = consumePositionComponent(range, cssParserMode, unitless);
893 if (!value2) {
894 positionFromOneValue(*value1, resultX, resultY);
895 return true;
896 }
897
898 RefPtr<CSSPrimitiveValue> value3 = consumePositionComponent(range, cssParserMode, unitless);
899 if (!value3)
900 return positionFromTwoValues(*value1, *value2, resultX, resultY);
901
902 RefPtr<CSSPrimitiveValue> value4 = consumePositionComponent(range, cssParserMode, unitless);
903 CSSPrimitiveValue* values[5];
904 values[0] = value1.get();
905 values[1] = value2.get();
906 values[2] = value3.get();
907 values[3] = value4.get();
908 values[4] = nullptr;
909 return positionFromThreeOrFourValues(values, resultX, resultY);
910}
911
912RefPtr<CSSPrimitiveValue> consumePosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
913{
914 RefPtr<CSSPrimitiveValue> resultX;
915 RefPtr<CSSPrimitiveValue> resultY;
916 if (consumePosition(range, cssParserMode, unitless, resultX, resultY))
917 return CSSPropertyParserHelpersInternal::createPrimitiveValuePair(resultX.releaseNonNull(), resultY.releaseNonNull());
918 return nullptr;
919}
920
921bool consumeOneOrTwoValuedPosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
922{
923 RefPtr<CSSPrimitiveValue> value1 = consumePositionComponent(range, cssParserMode, unitless);
924 if (!value1)
925 return false;
926 RefPtr<CSSPrimitiveValue> value2 = consumePositionComponent(range, cssParserMode, unitless);
927 if (!value2) {
928 positionFromOneValue(*value1, resultX, resultY);
929 return true;
930 }
931 return positionFromTwoValues(*value1, *value2, resultX, resultY);
932}
933
934// This should go away once we drop support for -webkit-gradient
935static RefPtr<CSSPrimitiveValue> consumeDeprecatedGradientPoint(CSSParserTokenRange& args, bool horizontal)
936{
937 if (args.peek().type() == IdentToken) {
938 if ((horizontal && consumeIdent<CSSValueLeft>(args)) || (!horizontal && consumeIdent<CSSValueTop>(args)))
939 return CSSValuePool::singleton().createValue(0., CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
940 if ((horizontal && consumeIdent<CSSValueRight>(args)) || (!horizontal && consumeIdent<CSSValueBottom>(args)))
941 return CSSValuePool::singleton().createValue(100., CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
942 if (consumeIdent<CSSValueCenter>(args))
943 return CSSValuePool::singleton().createValue(50., CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
944 return nullptr;
945 }
946 RefPtr<CSSPrimitiveValue> result = consumePercent(args, ValueRangeAll);
947 if (!result)
948 result = consumeNumber(args, ValueRangeAll);
949 return result;
950}
951
952// Used to parse colors for -webkit-gradient(...).
953static RefPtr<CSSPrimitiveValue> consumeDeprecatedGradientStopColor(CSSParserTokenRange& args, CSSParserMode cssParserMode)
954{
955 if (args.peek().id() == CSSValueCurrentcolor)
956 return nullptr;
957 return consumeColor(args, cssParserMode);
958}
959
960static bool consumeDeprecatedGradientColorStop(CSSParserTokenRange& range, CSSGradientColorStop& stop, CSSParserMode cssParserMode)
961{
962 CSSValueID id = range.peek().functionId();
963 if (id != CSSValueFrom && id != CSSValueTo && id != CSSValueColorStop)
964 return false;
965
966 CSSParserTokenRange args = consumeFunction(range);
967 double position;
968 if (id == CSSValueFrom || id == CSSValueTo) {
969 position = (id == CSSValueFrom) ? 0 : 1;
970 } else {
971 ASSERT(id == CSSValueColorStop);
972 if (auto percentValue = consumePercent(args, ValueRangeAll))
973 position = percentValue->doubleValue() / 100.0;
974 else if (!consumeNumberRaw(args, position))
975 return false;
976
977 if (!consumeCommaIncludingWhitespace(args))
978 return false;
979 }
980
981 stop.m_position = CSSValuePool::singleton().createValue(position, CSSPrimitiveValue::UnitType::CSS_NUMBER);
982 stop.m_color = consumeDeprecatedGradientStopColor(args, cssParserMode);
983 return stop.m_color && args.atEnd();
984}
985
986static RefPtr<CSSValue> consumeDeprecatedGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode)
987{
988 RefPtr<CSSGradientValue> result;
989 CSSValueID id = args.consumeIncludingWhitespace().id();
990 bool isDeprecatedRadialGradient = (id == CSSValueRadial);
991 if (isDeprecatedRadialGradient)
992 result = CSSRadialGradientValue::create(NonRepeating, CSSDeprecatedRadialGradient);
993 else if (id == CSSValueLinear)
994 result = CSSLinearGradientValue::create(NonRepeating, CSSDeprecatedLinearGradient);
995 if (!result || !consumeCommaIncludingWhitespace(args))
996 return nullptr;
997
998 RefPtr<CSSPrimitiveValue> point = consumeDeprecatedGradientPoint(args, true);
999 if (!point)
1000 return nullptr;
1001 result->setFirstX(point.copyRef());
1002 point = consumeDeprecatedGradientPoint(args, false);
1003 if (!point)
1004 return nullptr;
1005 result->setFirstY(point.copyRef());
1006
1007 if (!consumeCommaIncludingWhitespace(args))
1008 return nullptr;
1009
1010 // For radial gradients only, we now expect a numeric radius.
1011 if (isDeprecatedRadialGradient) {
1012 RefPtr<CSSPrimitiveValue> radius = consumeNumber(args, ValueRangeAll);
1013 if (!radius || !consumeCommaIncludingWhitespace(args))
1014 return nullptr;
1015 downcast<CSSRadialGradientValue>(result.get())->setFirstRadius(radius.copyRef());
1016 }
1017
1018 point = consumeDeprecatedGradientPoint(args, true);
1019 if (!point)
1020 return nullptr;
1021 result->setSecondX(point.copyRef());
1022 point = consumeDeprecatedGradientPoint(args, false);
1023 if (!point)
1024 return nullptr;
1025 result->setSecondY(point.copyRef());
1026
1027 // For radial gradients only, we now expect the second radius.
1028 if (isDeprecatedRadialGradient) {
1029 if (!consumeCommaIncludingWhitespace(args))
1030 return nullptr;
1031 RefPtr<CSSPrimitiveValue> radius = consumeNumber(args, ValueRangeAll);
1032 if (!radius)
1033 return nullptr;
1034 downcast<CSSRadialGradientValue>(result.get())->setSecondRadius(radius.copyRef());
1035 }
1036
1037 CSSGradientColorStop stop;
1038 while (consumeCommaIncludingWhitespace(args)) {
1039 if (!consumeDeprecatedGradientColorStop(args, stop, cssParserMode))
1040 return nullptr;
1041 result->addStop(stop);
1042 }
1043
1044 result->doneAddingStops();
1045 return result;
1046}
1047
1048static bool consumeGradientColorStops(CSSParserTokenRange& range, CSSParserMode cssParserMode, CSSGradientValue& gradient)
1049{
1050 bool supportsColorHints = gradient.gradientType() == CSSLinearGradient || gradient.gradientType() == CSSRadialGradient || gradient.gradientType() == CSSConicGradient;
1051
1052 bool isConicGradient = gradient.gradientType() == CSSConicGradient;
1053
1054 // The first color stop cannot be a color hint.
1055 bool previousStopWasColorHint = true;
1056 do {
1057 CSSGradientColorStop stop;
1058 stop.m_color = consumeColor(range, cssParserMode);
1059 // Two hints in a row are not allowed.
1060 if (!stop.m_color && (!supportsColorHints || previousStopWasColorHint))
1061 return false;
1062
1063 previousStopWasColorHint = !stop.m_color;
1064
1065 // FIXME-NEWPARSER: This boolean could be removed. Null checking color would be sufficient.
1066 stop.isMidpoint = !stop.m_color;
1067
1068 if (isConicGradient)
1069 stop.m_position = consumeAngleOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Forbid);
1070 else
1071 stop.m_position = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll);
1072
1073 if (!stop.m_color && !stop.m_position)
1074 return false;
1075 gradient.addStop(stop);
1076
1077 // See if there is a second color hint, which is optional.
1078 CSSGradientColorStop secondStop;
1079 if (isConicGradient)
1080 secondStop.m_position = consumeAngleOrPercent(range, cssParserMode, ValueRangeAll, UnitlessQuirk::Forbid);
1081 else
1082 secondStop.m_position = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll);
1083
1084 if (secondStop.m_position) {
1085 secondStop.m_color = stop.m_color;
1086 gradient.addStop(secondStop);
1087 }
1088
1089 } while (consumeCommaIncludingWhitespace(range));
1090
1091 gradient.doneAddingStops();
1092
1093 // The last color stop cannot be a color hint.
1094 if (previousStopWasColorHint)
1095 return false;
1096
1097 // Must have 2 or more stops to be valid.
1098 return gradient.stopCount() >= 2;
1099}
1100
1101static RefPtr<CSSValue> consumeDeprecatedRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating)
1102{
1103 RefPtr<CSSRadialGradientValue> result = CSSRadialGradientValue::create(repeating, CSSPrefixedRadialGradient);
1104 RefPtr<CSSPrimitiveValue> centerX;
1105 RefPtr<CSSPrimitiveValue> centerY;
1106 consumeOneOrTwoValuedPosition(args, cssParserMode, UnitlessQuirk::Forbid, centerX, centerY);
1107 if ((centerX || centerY) && !consumeCommaIncludingWhitespace(args))
1108 return nullptr;
1109
1110 result->setFirstX(centerX.copyRef());
1111 result->setFirstY(centerY.copyRef());
1112 result->setSecondX(centerX.copyRef());
1113 result->setSecondY(centerY.copyRef());
1114
1115 RefPtr<CSSPrimitiveValue> shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args);
1116 RefPtr<CSSPrimitiveValue> sizeKeyword = consumeIdent<CSSValueClosestSide, CSSValueClosestCorner, CSSValueFarthestSide, CSSValueFarthestCorner, CSSValueContain, CSSValueCover>(args);
1117 if (!shape)
1118 shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args);
1119 result->setShape(shape.copyRef());
1120 result->setSizingBehavior(sizeKeyword.copyRef());
1121
1122 // Or, two lengths or percentages
1123 if (!shape && !sizeKeyword) {
1124 RefPtr<CSSPrimitiveValue> horizontalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
1125 RefPtr<CSSPrimitiveValue> verticalSize;
1126 if (horizontalSize) {
1127 verticalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
1128 if (!verticalSize)
1129 return nullptr;
1130 consumeCommaIncludingWhitespace(args);
1131 result->setEndHorizontalSize(horizontalSize.copyRef());
1132 result->setEndVerticalSize(verticalSize.copyRef());
1133 }
1134 } else {
1135 consumeCommaIncludingWhitespace(args);
1136 }
1137 if (!consumeGradientColorStops(args, cssParserMode, *result))
1138 return nullptr;
1139
1140 return result;
1141}
1142
1143static RefPtr<CSSValue> consumeRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating)
1144{
1145 RefPtr<CSSRadialGradientValue> result = CSSRadialGradientValue::create(repeating, CSSRadialGradient);
1146
1147 RefPtr<CSSPrimitiveValue> shape;
1148 RefPtr<CSSPrimitiveValue> sizeKeyword;
1149 RefPtr<CSSPrimitiveValue> horizontalSize;
1150 RefPtr<CSSPrimitiveValue> verticalSize;
1151
1152 // First part of grammar, the size/shape clause:
1153 // [ circle || <length> ] |
1154 // [ ellipse || [ <length> | <percentage> ]{2} ] |
1155 // [ [ circle | ellipse] || <size-keyword> ]
1156 for (int i = 0; i < 3; ++i) {
1157 if (args.peek().type() == IdentToken) {
1158 CSSValueID id = args.peek().id();
1159 if (id == CSSValueCircle || id == CSSValueEllipse) {
1160 if (shape)
1161 return nullptr;
1162 shape = consumeIdent(args);
1163 } else if (id == CSSValueClosestSide || id == CSSValueClosestCorner || id == CSSValueFarthestSide || id == CSSValueFarthestCorner) {
1164 if (sizeKeyword)
1165 return nullptr;
1166 sizeKeyword = consumeIdent(args);
1167 } else {
1168 break;
1169 }
1170 } else {
1171 RefPtr<CSSPrimitiveValue> center = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
1172 if (!center)
1173 break;
1174 if (horizontalSize)
1175 return nullptr;
1176 horizontalSize = center;
1177 center = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
1178 if (center) {
1179 verticalSize = center;
1180 ++i;
1181 }
1182 }
1183 }
1184
1185 // You can specify size as a keyword or a length/percentage, not both.
1186 if (sizeKeyword && horizontalSize)
1187 return nullptr;
1188 // Circles must have 0 or 1 lengths.
1189 if (shape && shape->valueID() == CSSValueCircle && verticalSize)
1190 return nullptr;
1191 // Ellipses must have 0 or 2 length/percentages.
1192 if (shape && shape->valueID() == CSSValueEllipse && horizontalSize && !verticalSize)
1193 return nullptr;
1194 // If there's only one size, it must be a length.
1195 if (!verticalSize && horizontalSize && horizontalSize->isPercentage())
1196 return nullptr;
1197 if ((horizontalSize && horizontalSize->isCalculatedPercentageWithLength())
1198 || (verticalSize && verticalSize->isCalculatedPercentageWithLength()))
1199 return nullptr;
1200
1201 result->setShape(shape.copyRef());
1202 result->setSizingBehavior(sizeKeyword.copyRef());
1203 result->setEndHorizontalSize(horizontalSize.copyRef());
1204 result->setEndVerticalSize(verticalSize.copyRef());
1205
1206 RefPtr<CSSPrimitiveValue> centerX;
1207 RefPtr<CSSPrimitiveValue> centerY;
1208 if (args.peek().id() == CSSValueAt) {
1209 args.consumeIncludingWhitespace();
1210 consumePosition(args, cssParserMode, UnitlessQuirk::Forbid, centerX, centerY);
1211 if (!(centerX && centerY))
1212 return nullptr;
1213
1214 result->setFirstX(centerX.copyRef());
1215 result->setFirstY(centerY.copyRef());
1216
1217 // Right now, CSS radial gradients have the same start and end centers.
1218 result->setSecondX(centerX.copyRef());
1219 result->setSecondY(centerY.copyRef());
1220 }
1221
1222 if ((shape || sizeKeyword || horizontalSize || centerX || centerY) && !consumeCommaIncludingWhitespace(args))
1223 return nullptr;
1224 if (!consumeGradientColorStops(args, cssParserMode, *result))
1225 return nullptr;
1226 return result;
1227}
1228
1229static RefPtr<CSSValue> consumeLinearGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating, CSSGradientType gradientType)
1230{
1231 RefPtr<CSSLinearGradientValue> result = CSSLinearGradientValue::create(repeating, gradientType);
1232
1233 bool expectComma = true;
1234 RefPtr<CSSPrimitiveValue> angle = consumeAngle(args, cssParserMode, UnitlessQuirk::Forbid);
1235 if (angle)
1236 result->setAngle(angle.releaseNonNull());
1237 else if (gradientType == CSSPrefixedLinearGradient || consumeIdent<CSSValueTo>(args)) {
1238 RefPtr<CSSPrimitiveValue> endX = consumeIdent<CSSValueLeft, CSSValueRight>(args);
1239 RefPtr<CSSPrimitiveValue> endY = consumeIdent<CSSValueBottom, CSSValueTop>(args);
1240 if (!endX && !endY) {
1241 if (gradientType == CSSLinearGradient)
1242 return nullptr;
1243 endY = CSSPrimitiveValue::createIdentifier(CSSValueTop);
1244 expectComma = false;
1245 } else if (!endX) {
1246 endX = consumeIdent<CSSValueLeft, CSSValueRight>(args);
1247 }
1248
1249 result->setFirstX(endX.copyRef());
1250 result->setFirstY(endY.copyRef());
1251 } else {
1252 expectComma = false;
1253 }
1254
1255 if (expectComma && !consumeCommaIncludingWhitespace(args))
1256 return nullptr;
1257 if (!consumeGradientColorStops(args, cssParserMode, *result))
1258 return nullptr;
1259 return result;
1260}
1261
1262static RefPtr<CSSValue> consumeConicGradient(CSSParserTokenRange& args, CSSParserContext context, CSSGradientRepeat repeating)
1263{
1264#if ENABLE(CSS_CONIC_GRADIENTS)
1265 RefPtr<CSSConicGradientValue> result = CSSConicGradientValue::create(repeating);
1266
1267 bool expectComma = false;
1268 if (args.peek().type() == IdentToken) {
1269 if (consumeIdent<CSSValueFrom>(args)) {
1270 auto angle = consumeAngle(args, context.mode, UnitlessQuirk::Forbid);
1271 if (!angle)
1272 return nullptr;
1273 result->setAngle(angle.releaseNonNull());
1274 expectComma = true;
1275 }
1276
1277 if (consumeIdent<CSSValueAt>(args)) {
1278 RefPtr<CSSPrimitiveValue> centerX;
1279 RefPtr<CSSPrimitiveValue> centerY;
1280 consumePosition(args, context.mode, UnitlessQuirk::Forbid, centerX, centerY);
1281 if (!(centerX && centerY))
1282 return nullptr;
1283
1284 result->setFirstX(centerX.copyRef());
1285 result->setFirstY(centerY.copyRef());
1286
1287 // Right now, conic gradients have the same start and end centers.
1288 result->setSecondX(centerX.copyRef());
1289 result->setSecondY(centerY.copyRef());
1290
1291 expectComma = true;
1292 }
1293 }
1294
1295 if (expectComma && !consumeCommaIncludingWhitespace(args))
1296 return nullptr;
1297 if (!consumeGradientColorStops(args, context.mode, *result))
1298 return nullptr;
1299 return result;
1300#else
1301 UNUSED_PARAM(args);
1302 UNUSED_PARAM(context);
1303 UNUSED_PARAM(repeating);
1304 return nullptr;
1305#endif
1306}
1307
1308RefPtr<CSSValue> consumeImageOrNone(CSSParserTokenRange& range, CSSParserContext context)
1309{
1310 if (range.peek().id() == CSSValueNone)
1311 return consumeIdent(range);
1312 return consumeImage(range, context);
1313}
1314
1315static RefPtr<CSSValue> consumeCrossFade(CSSParserTokenRange& args, CSSParserContext context, bool prefixed)
1316{
1317 RefPtr<CSSValue> fromImageValue = consumeImageOrNone(args, context);
1318 if (!fromImageValue || !consumeCommaIncludingWhitespace(args))
1319 return nullptr;
1320 RefPtr<CSSValue> toImageValue = consumeImageOrNone(args, context);
1321 if (!toImageValue || !consumeCommaIncludingWhitespace(args))
1322 return nullptr;
1323
1324 RefPtr<CSSPrimitiveValue> percentage;
1325 if (auto percentValue = consumePercent(args, ValueRangeAll))
1326 percentage = CSSValuePool::singleton().createValue(clampTo<double>(percentValue->doubleValue() / 100.0, 0, 1), CSSPrimitiveValue::UnitType::CSS_NUMBER);
1327 else if (auto numberValue = consumeNumber(args, ValueRangeAll))
1328 percentage = CSSValuePool::singleton().createValue(clampTo<double>(numberValue->doubleValue(), 0, 1), CSSPrimitiveValue::UnitType::CSS_NUMBER);
1329
1330 if (!percentage)
1331 return nullptr;
1332 return CSSCrossfadeValue::create(fromImageValue.releaseNonNull(), toImageValue.releaseNonNull(), percentage.releaseNonNull(), prefixed);
1333}
1334
1335static RefPtr<CSSValue> consumeWebkitCanvas(CSSParserTokenRange& args)
1336{
1337 if (args.peek().type() != IdentToken)
1338 return nullptr;
1339 auto canvasName = args.consumeIncludingWhitespace().value().toString();
1340 if (!args.atEnd())
1341 return nullptr;
1342 return CSSCanvasValue::create(canvasName);
1343}
1344
1345static RefPtr<CSSValue> consumeWebkitNamedImage(CSSParserTokenRange& args)
1346{
1347 if (args.peek().type() != IdentToken)
1348 return nullptr;
1349 auto imageName = args.consumeIncludingWhitespace().value().toString();
1350 if (!args.atEnd())
1351 return nullptr;
1352 return CSSNamedImageValue::create(imageName);
1353}
1354
1355static RefPtr<CSSValue> consumeFilterImage(CSSParserTokenRange& args, const CSSParserContext& context)
1356{
1357 auto imageValue = consumeImageOrNone(args, context);
1358 if (!imageValue || !consumeCommaIncludingWhitespace(args))
1359 return nullptr;
1360
1361 auto filterValue = consumeFilter(args, context, AllowedFilterFunctions::PixelFilters);
1362
1363 if (!filterValue)
1364 return nullptr;
1365
1366 if (!args.atEnd())
1367 return nullptr;
1368
1369 return CSSFilterImageValue::create(imageValue.releaseNonNull(), filterValue.releaseNonNull());
1370}
1371
1372#if ENABLE(CSS_PAINTING_API)
1373static RefPtr<CSSValue> consumeCustomPaint(CSSParserTokenRange& args)
1374{
1375 if (!RuntimeEnabledFeatures::sharedFeatures().cssPaintingAPIEnabled())
1376 return nullptr;
1377 if (args.peek().type() != IdentToken)
1378 return nullptr;
1379 auto name = args.consumeIncludingWhitespace().value().toString();
1380
1381 if (!args.atEnd() && args.peek() != CommaToken)
1382 return nullptr;
1383 if (!args.atEnd())
1384 args.consume();
1385
1386 auto argumentList = CSSVariableData::create(args);
1387
1388 while (!args.atEnd())
1389 args.consume();
1390
1391 return CSSPaintImageValue::create(name, WTFMove(argumentList));
1392}
1393#endif
1394
1395static RefPtr<CSSValue> consumeGeneratedImage(CSSParserTokenRange& range, CSSParserContext context)
1396{
1397 CSSValueID id = range.peek().functionId();
1398 CSSParserTokenRange rangeCopy = range;
1399 CSSParserTokenRange args = consumeFunction(rangeCopy);
1400 RefPtr<CSSValue> result;
1401 if (id == CSSValueRadialGradient)
1402 result = consumeRadialGradient(args, context.mode, NonRepeating);
1403 else if (id == CSSValueRepeatingRadialGradient)
1404 result = consumeRadialGradient(args, context.mode, Repeating);
1405 else if (id == CSSValueWebkitLinearGradient)
1406 result = consumeLinearGradient(args, context.mode, NonRepeating, CSSPrefixedLinearGradient);
1407 else if (id == CSSValueWebkitRepeatingLinearGradient)
1408 result = consumeLinearGradient(args, context.mode, Repeating, CSSPrefixedLinearGradient);
1409 else if (id == CSSValueRepeatingLinearGradient)
1410 result = consumeLinearGradient(args, context.mode, Repeating, CSSLinearGradient);
1411 else if (id == CSSValueLinearGradient)
1412 result = consumeLinearGradient(args, context.mode, NonRepeating, CSSLinearGradient);
1413 else if (id == CSSValueWebkitGradient)
1414 result = consumeDeprecatedGradient(args, context.mode);
1415 else if (id == CSSValueWebkitRadialGradient)
1416 result = consumeDeprecatedRadialGradient(args, context.mode, NonRepeating);
1417 else if (id == CSSValueWebkitRepeatingRadialGradient)
1418 result = consumeDeprecatedRadialGradient(args, context.mode, Repeating);
1419 else if (id == CSSValueConicGradient)
1420 result = consumeConicGradient(args, context, NonRepeating);
1421 else if (id == CSSValueRepeatingConicGradient)
1422 result = consumeConicGradient(args, context, Repeating);
1423 else if (id == CSSValueWebkitCrossFade || id == CSSValueCrossFade)
1424 result = consumeCrossFade(args, context, id == CSSValueWebkitCrossFade);
1425 else if (id == CSSValueWebkitCanvas)
1426 result = consumeWebkitCanvas(args);
1427 else if (id == CSSValueWebkitNamedImage)
1428 result = consumeWebkitNamedImage(args);
1429 else if (id == CSSValueWebkitFilter || id == CSSValueFilter)
1430 result = consumeFilterImage(args, context);
1431#if ENABLE(CSS_PAINTING_API)
1432 else if (id == CSSValuePaint)
1433 result = consumeCustomPaint(args);
1434#endif
1435 if (!result || !args.atEnd())
1436 return nullptr;
1437 range = rangeCopy;
1438 return result;
1439}
1440
1441static RefPtr<CSSValue> consumeImageSet(CSSParserTokenRange& range, const CSSParserContext& context)
1442{
1443 CSSParserTokenRange rangeCopy = range;
1444 CSSParserTokenRange args = consumeFunction(rangeCopy);
1445 RefPtr<CSSImageSetValue> imageSet = CSSImageSetValue::create(context.isContentOpaque ? LoadedFromOpaqueSource::Yes : LoadedFromOpaqueSource::No);
1446 do {
1447 AtomicString urlValue = consumeUrlAsStringView(args).toAtomicString();
1448 if (urlValue.isNull())
1449 return nullptr;
1450
1451 RefPtr<CSSValue> image = CSSImageValue::create(completeURL(context, urlValue), context.isContentOpaque ? LoadedFromOpaqueSource::Yes : LoadedFromOpaqueSource::No);
1452 imageSet->append(image.releaseNonNull());
1453
1454 const CSSParserToken& token = args.consumeIncludingWhitespace();
1455 if (token.type() != DimensionToken)
1456 return nullptr;
1457 if (token.value() != "x")
1458 return nullptr;
1459 ASSERT(token.unitType() == CSSPrimitiveValue::UnitType::CSS_UNKNOWN);
1460 double imageScaleFactor = token.numericValue();
1461 if (imageScaleFactor <= 0)
1462 return nullptr;
1463 imageSet->append(CSSValuePool::singleton().createValue(imageScaleFactor, CSSPrimitiveValue::UnitType::CSS_NUMBER));
1464 } while (consumeCommaIncludingWhitespace(args));
1465 if (!args.atEnd())
1466 return nullptr;
1467 range = rangeCopy;
1468 return imageSet;
1469}
1470
1471static bool isGeneratedImage(CSSValueID id)
1472{
1473 return id == CSSValueLinearGradient
1474 || id == CSSValueRadialGradient
1475 || id == CSSValueConicGradient
1476 || id == CSSValueRepeatingLinearGradient
1477 || id == CSSValueRepeatingRadialGradient
1478 || id == CSSValueRepeatingConicGradient
1479 || id == CSSValueWebkitLinearGradient
1480 || id == CSSValueWebkitRadialGradient
1481 || id == CSSValueWebkitRepeatingLinearGradient
1482 || id == CSSValueWebkitRepeatingRadialGradient
1483 || id == CSSValueWebkitGradient
1484 || id == CSSValueWebkitCrossFade
1485 || id == CSSValueWebkitCanvas
1486 || id == CSSValueCrossFade
1487 || id == CSSValueWebkitNamedImage
1488 || id == CSSValueWebkitFilter
1489#if ENABLE(CSS_PAINTING_API)
1490 || id == CSSValuePaint
1491#endif
1492 || id == CSSValueFilter;
1493}
1494
1495static bool isPixelFilterFunction(CSSValueID filterFunction)
1496{
1497 switch (filterFunction) {
1498 case CSSValueBlur:
1499 case CSSValueBrightness:
1500 case CSSValueContrast:
1501 case CSSValueDropShadow:
1502 case CSSValueGrayscale:
1503 case CSSValueHueRotate:
1504 case CSSValueInvert:
1505 case CSSValueOpacity:
1506 case CSSValueSaturate:
1507 case CSSValueSepia:
1508 return true;
1509 default:
1510 return false;
1511 }
1512}
1513
1514static bool isColorFilterFunction(CSSValueID filterFunction)
1515{
1516 switch (filterFunction) {
1517 case CSSValueBrightness:
1518 case CSSValueContrast:
1519 case CSSValueGrayscale:
1520 case CSSValueHueRotate:
1521 case CSSValueInvert:
1522 case CSSValueOpacity:
1523 case CSSValueSaturate:
1524 case CSSValueSepia:
1525 case CSSValueAppleInvertLightness:
1526 return true;
1527 default:
1528 return false;
1529 }
1530}
1531
1532static bool allowsValuesGreaterThanOne(CSSValueID filterFunction)
1533{
1534 switch (filterFunction) {
1535 case CSSValueBrightness:
1536 case CSSValueContrast:
1537 case CSSValueSaturate:
1538 return true;
1539 default:
1540 return false;
1541 }
1542}
1543
1544static RefPtr<CSSFunctionValue> consumeFilterFunction(CSSParserTokenRange& range, const CSSParserContext& context, AllowedFilterFunctions allowedFunctions)
1545{
1546 CSSValueID filterType = range.peek().functionId();
1547 switch (allowedFunctions) {
1548 case AllowedFilterFunctions::PixelFilters:
1549 if (!isPixelFilterFunction(filterType))
1550 return nullptr;
1551 break;
1552 case AllowedFilterFunctions::ColorFilters:
1553 if (!isColorFilterFunction(filterType))
1554 return nullptr;
1555 break;
1556 }
1557
1558 CSSParserTokenRange args = consumeFunction(range);
1559 RefPtr<CSSFunctionValue> filterValue = CSSFunctionValue::create(filterType);
1560
1561 if (filterType == CSSValueAppleInvertLightness) {
1562 if (!args.atEnd())
1563 return nullptr;
1564 return filterValue;
1565 }
1566
1567 RefPtr<CSSValue> parsedValue;
1568
1569 if (filterType == CSSValueDropShadow)
1570 parsedValue = consumeSingleShadow(args, context.mode, false, false);
1571 else {
1572 if (args.atEnd())
1573 return filterValue;
1574
1575 if (filterType == CSSValueHueRotate)
1576 parsedValue = consumeAngle(args, context.mode, UnitlessQuirk::Forbid);
1577 else if (filterType == CSSValueBlur)
1578 parsedValue = consumeLength(args, HTMLStandardMode, ValueRangeNonNegative);
1579 else {
1580 parsedValue = consumePercent(args, ValueRangeNonNegative);
1581 if (!parsedValue)
1582 parsedValue = consumeNumber(args, ValueRangeNonNegative);
1583 if (parsedValue && !allowsValuesGreaterThanOne(filterType)) {
1584 bool isPercentage = downcast<CSSPrimitiveValue>(*parsedValue).isPercentage();
1585 double maxAllowed = isPercentage ? 100.0 : 1.0;
1586 if (downcast<CSSPrimitiveValue>(*parsedValue).doubleValue() > maxAllowed)
1587 parsedValue = CSSPrimitiveValue::create(maxAllowed, isPercentage ? CSSPrimitiveValue::UnitType::CSS_PERCENTAGE : CSSPrimitiveValue::UnitType::CSS_NUMBER);
1588 }
1589 }
1590 }
1591 if (!parsedValue || !args.atEnd())
1592 return nullptr;
1593 filterValue->append(parsedValue.releaseNonNull());
1594 return filterValue;
1595}
1596
1597RefPtr<CSSValue> consumeFilter(CSSParserTokenRange& range, const CSSParserContext& context, AllowedFilterFunctions allowedFunctions)
1598{
1599 if (range.peek().id() == CSSValueNone)
1600 return consumeIdent(range);
1601
1602 bool referenceFiltersAllowed = allowedFunctions == AllowedFilterFunctions::PixelFilters;
1603 auto list = CSSValueList::createSpaceSeparated();
1604 do {
1605 RefPtr<CSSValue> filterValue = referenceFiltersAllowed ? consumeUrl(range) : nullptr;
1606 if (!filterValue) {
1607 filterValue = consumeFilterFunction(range, context, allowedFunctions);
1608 if (!filterValue)
1609 return nullptr;
1610 }
1611 list->append(filterValue.releaseNonNull());
1612 } while (!range.atEnd());
1613
1614 return list.ptr();
1615}
1616
1617RefPtr<CSSShadowValue> consumeSingleShadow(CSSParserTokenRange& range, CSSParserMode cssParserMode, bool allowInset, bool allowSpread)
1618{
1619 RefPtr<CSSPrimitiveValue> style;
1620 RefPtr<CSSPrimitiveValue> color;
1621
1622 if (range.atEnd())
1623 return nullptr;
1624 if (range.peek().id() == CSSValueInset) {
1625 if (!allowInset)
1626 return nullptr;
1627 style = consumeIdent(range);
1628 }
1629 color = consumeColor(range, cssParserMode);
1630
1631 auto horizontalOffset = consumeLength(range, cssParserMode, ValueRangeAll);
1632 if (!horizontalOffset)
1633 return nullptr;
1634
1635 auto verticalOffset = consumeLength(range, cssParserMode, ValueRangeAll);
1636 if (!verticalOffset)
1637 return nullptr;
1638
1639 auto blurRadius = consumeLength(range, cssParserMode, ValueRangeAll);
1640 RefPtr<CSSPrimitiveValue> spreadDistance;
1641 if (blurRadius) {
1642 // Blur radius must be non-negative.
1643 if (blurRadius->doubleValue() < 0)
1644 return nullptr;
1645 if (allowSpread)
1646 spreadDistance = consumeLength(range, cssParserMode, ValueRangeAll);
1647 }
1648
1649 if (!range.atEnd()) {
1650 if (!color)
1651 color = consumeColor(range, cssParserMode);
1652 if (range.peek().id() == CSSValueInset) {
1653 if (!allowInset || style)
1654 return nullptr;
1655 style = consumeIdent(range);
1656 }
1657 }
1658
1659 return CSSShadowValue::create(WTFMove(horizontalOffset), WTFMove(verticalOffset), WTFMove(blurRadius), WTFMove(spreadDistance), WTFMove(style), WTFMove(color));
1660}
1661
1662RefPtr<CSSValue> consumeImage(CSSParserTokenRange& range, CSSParserContext context, ConsumeGeneratedImage generatedImage)
1663{
1664 AtomicString uri = consumeUrlAsStringView(range).toAtomicString();
1665 if (!uri.isNull())
1666 return CSSImageValue::create(completeURL(context, uri), context.isContentOpaque ? LoadedFromOpaqueSource::Yes : LoadedFromOpaqueSource::No);
1667
1668 if (range.peek().type() == FunctionToken) {
1669 CSSValueID id = range.peek().functionId();
1670 if (id == CSSValueWebkitImageSet || id == CSSValueImageSet)
1671 return consumeImageSet(range, context);
1672 if (generatedImage == ConsumeGeneratedImage::Allow && isGeneratedImage(id))
1673 return consumeGeneratedImage(range, context);
1674 }
1675 return nullptr;
1676}
1677
1678} // namespace CSSPropertyParserHelpers
1679
1680} // namespace WebCore
1681