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