[CSS Parser] Support unprefixed cross-fade
[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 "CSSGradientValue.h"
37 #include "CSSImageSetValue.h"
38 #include "CSSImageValue.h"
39 #include "CSSParserIdioms.h"
40 #include "CSSValuePool.h"
41 #include "Pair.h"
42 #include "StyleColor.h"
43
44 namespace WebCore {
45
46 namespace CSSPropertyParserHelpers {
47
48 bool consumeCommaIncludingWhitespace(CSSParserTokenRange& range)
49 {
50     CSSParserToken value = range.peek();
51     if (value.type() != CommaToken)
52         return false;
53     range.consumeIncludingWhitespace();
54     return true;
55 }
56
57 bool consumeSlashIncludingWhitespace(CSSParserTokenRange& range)
58 {
59     CSSParserToken value = range.peek();
60     if (value.type() != DelimiterToken || value.delimiter() != '/')
61         return false;
62     range.consumeIncludingWhitespace();
63     return true;
64 }
65
66 CSSParserTokenRange consumeFunction(CSSParserTokenRange& range)
67 {
68     ASSERT(range.peek().type() == FunctionToken);
69     CSSParserTokenRange contents = range.consumeBlock();
70     range.consumeWhitespace();
71     contents.consumeWhitespace();
72     return contents;
73 }
74
75 // FIXME: consider pulling in the parsing logic from CSSCalculationValue.cpp.
76 class CalcParser {
77
78 public:
79     explicit CalcParser(CSSParserTokenRange& range, ValueRange valueRange = ValueRangeAll)
80         : m_sourceRange(range)
81         , m_range(range)
82     {
83         const CSSParserToken& token = range.peek();
84         if (token.functionId() == CSSValueCalc || token.functionId() == CSSValueWebkitCalc)
85             m_calcValue = CSSCalcValue::create(consumeFunction(m_range), valueRange);
86     }
87
88     const CSSCalcValue* value() const { return m_calcValue.get(); }
89     RefPtr<CSSPrimitiveValue> consumeValue()
90     {
91         if (!m_calcValue)
92             return nullptr;
93         m_sourceRange = m_range;
94         return CSSValuePool::singleton().createValue(m_calcValue.release());
95     }
96     RefPtr<CSSPrimitiveValue> consumeNumber()
97     {
98         if (!m_calcValue)
99             return nullptr;
100         m_sourceRange = m_range;
101         CSSPrimitiveValue::UnitTypes unitType = m_calcValue->isInt() ? CSSPrimitiveValue::UnitTypes::CSS_PARSER_INTEGER : CSSPrimitiveValue::UnitTypes::CSS_NUMBER;
102         return CSSValuePool::singleton().createValue(m_calcValue->doubleValue(), unitType);
103     }
104
105     bool consumeNumberRaw(double& result)
106     {
107         if (!m_calcValue || m_calcValue->category() != CalcNumber)
108             return false;
109         m_sourceRange = m_range;
110         result = m_calcValue->doubleValue();
111         return true;
112     }
113
114 private:
115     CSSParserTokenRange& m_sourceRange;
116     CSSParserTokenRange m_range;
117     RefPtr<CSSCalcValue> m_calcValue;
118 };
119
120 RefPtr<CSSPrimitiveValue> consumeInteger(CSSParserTokenRange& range, double minimumValue)
121 {
122     const CSSParserToken& token = range.peek();
123     if (token.type() == NumberToken) {
124         if (token.numericValueType() == NumberValueType || token.numericValue() < minimumValue)
125             return nullptr;
126         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitTypes::CSS_NUMBER);
127     }
128     CalcParser calcParser(range);
129     if (const CSSCalcValue* calculation = calcParser.value()) {
130         if (calculation->category() != CalcNumber || !calculation->isInt())
131             return nullptr;
132         double value = calculation->doubleValue();
133         if (value < minimumValue)
134             return nullptr;
135         return calcParser.consumeNumber();
136     }
137     return nullptr;
138 }
139
140 RefPtr<CSSPrimitiveValue> consumePositiveInteger(CSSParserTokenRange& range)
141 {
142     return consumeInteger(range, 1);
143 }
144
145 bool consumeNumberRaw(CSSParserTokenRange& range, double& result)
146 {
147     if (range.peek().type() == NumberToken) {
148         result = range.consumeIncludingWhitespace().numericValue();
149         return true;
150     }
151     CalcParser calcParser(range, ValueRangeAll);
152     return calcParser.consumeNumberRaw(result);
153 }
154
155 // FIXME: Work out if this can just call consumeNumberRaw
156 RefPtr<CSSPrimitiveValue> consumeNumber(CSSParserTokenRange& range, ValueRange valueRange)
157 {
158     const CSSParserToken& token = range.peek();
159     if (token.type() == NumberToken) {
160         if (valueRange == ValueRangeNonNegative && token.numericValue() < 0)
161             return nullptr;
162         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
163     }
164     CalcParser calcParser(range, ValueRangeAll);
165     if (const CSSCalcValue* calculation = calcParser.value()) {
166         // FIXME: Calcs should not be subject to parse time range checks.
167         // spec: https://drafts.csswg.org/css-values-3/#calc-range
168         if (calculation->category() != CalcNumber || (valueRange == ValueRangeNonNegative && calculation->isNegative()))
169             return nullptr;
170         return calcParser.consumeNumber();
171     }
172     return nullptr;
173 }
174
175 inline bool shouldAcceptUnitlessValue(double value, CSSParserMode cssParserMode, UnitlessQuirk unitless)
176 {
177     // FIXME: Presentational HTML attributes shouldn't use the CSS parser for lengths
178     return value == 0
179         || isUnitLessValueParsingEnabledForMode(cssParserMode)
180         || (cssParserMode == HTMLQuirksMode && unitless == UnitlessQuirk::Allow);
181 }
182
183 RefPtr<CSSPrimitiveValue> consumeLength(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
184 {
185     const CSSParserToken& token = range.peek();
186     if (token.type() == DimensionToken) {
187         switch (token.unitType()) {
188         case CSSPrimitiveValue::UnitTypes::CSS_QUIRKY_EMS:
189             if (cssParserMode != UASheetMode)
190                 return nullptr;
191             FALLTHROUGH;
192         case CSSPrimitiveValue::UnitTypes::CSS_EMS:
193         case CSSPrimitiveValue::UnitTypes::CSS_REMS:
194         case CSSPrimitiveValue::UnitTypes::CSS_CHS:
195         case CSSPrimitiveValue::UnitTypes::CSS_EXS:
196         case CSSPrimitiveValue::UnitTypes::CSS_PX:
197         case CSSPrimitiveValue::UnitTypes::CSS_CM:
198         case CSSPrimitiveValue::UnitTypes::CSS_MM:
199         case CSSPrimitiveValue::UnitTypes::CSS_IN:
200         case CSSPrimitiveValue::UnitTypes::CSS_PT:
201         case CSSPrimitiveValue::UnitTypes::CSS_PC:
202         case CSSPrimitiveValue::UnitTypes::CSS_VW:
203         case CSSPrimitiveValue::UnitTypes::CSS_VH:
204         case CSSPrimitiveValue::UnitTypes::CSS_VMIN:
205         case CSSPrimitiveValue::UnitTypes::CSS_VMAX:
206             break;
207         default:
208             return nullptr;
209         }
210         if (valueRange == ValueRangeNonNegative && token.numericValue() < 0)
211             return nullptr;
212         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
213     }
214     if (token.type() == NumberToken) {
215         if (!shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless)
216             || (valueRange == ValueRangeNonNegative && token.numericValue() < 0))
217             return nullptr;
218         CSSPrimitiveValue::UnitTypes unitType = CSSPrimitiveValue::UnitTypes::CSS_PX;
219         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), unitType);
220     }
221     if (cssParserMode == SVGAttributeMode)
222         return nullptr;
223     CalcParser calcParser(range, valueRange);
224     if (calcParser.value() && calcParser.value()->category() == CalcLength)
225         return calcParser.consumeValue();
226     return nullptr;
227 }
228
229 RefPtr<CSSPrimitiveValue> consumePercent(CSSParserTokenRange& range, ValueRange valueRange)
230 {
231     const CSSParserToken& token = range.peek();
232     if (token.type() == PercentageToken) {
233         if (valueRange == ValueRangeNonNegative && token.numericValue() < 0)
234             return nullptr;
235         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitTypes::CSS_PERCENTAGE);
236     }
237     CalcParser calcParser(range, valueRange);
238     if (const CSSCalcValue* calculation = calcParser.value()) {
239         if (calculation->category() == CalcPercent)
240             return calcParser.consumeValue();
241     }
242     return nullptr;
243 }
244
245 static bool canConsumeCalcValue(CalculationCategory category, CSSParserMode cssParserMode)
246 {
247     if (category == CalcLength || category == CalcPercent || category == CalcPercentLength)
248         return true;
249
250     if (cssParserMode != SVGAttributeMode)
251         return false;
252
253     if (category == CalcNumber || category == CalcPercentNumber)
254         return true;
255
256     return false;
257 }
258
259 RefPtr<CSSPrimitiveValue> consumeLengthOrPercent(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
260 {
261     const CSSParserToken& token = range.peek();
262     if (token.type() == DimensionToken || token.type() == NumberToken)
263         return consumeLength(range, cssParserMode, valueRange, unitless);
264     if (token.type() == PercentageToken)
265         return consumePercent(range, valueRange);
266     CalcParser calcParser(range, valueRange);
267     if (const CSSCalcValue* calculation = calcParser.value()) {
268         if (canConsumeCalcValue(calculation->category(), cssParserMode))
269             return calcParser.consumeValue();
270     }
271     return nullptr;
272 }
273
274 RefPtr<CSSPrimitiveValue> consumeAngle(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
275 {
276     const CSSParserToken& token = range.peek();
277     if (token.type() == DimensionToken) {
278         switch (token.unitType()) {
279         case CSSPrimitiveValue::UnitTypes::CSS_DEG:
280         case CSSPrimitiveValue::UnitTypes::CSS_RAD:
281         case CSSPrimitiveValue::UnitTypes::CSS_GRAD:
282         case CSSPrimitiveValue::UnitTypes::CSS_TURN:
283             return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
284         default:
285             return nullptr;
286         }
287     }
288     if (token.type() == NumberToken && shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless)) {
289         range.consumeIncludingWhitespace();
290         return CSSValuePool::singleton().createValue(0, CSSPrimitiveValue::UnitTypes::CSS_DEG);
291     }
292
293     CalcParser calcParser(range, ValueRangeAll);
294     if (const CSSCalcValue* calculation = calcParser.value()) {
295         if (calculation->category() == CalcAngle)
296             return calcParser.consumeValue();
297     }
298     return nullptr;
299 }
300
301 RefPtr<CSSPrimitiveValue> consumeTime(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless)
302 {
303     const CSSParserToken& token = range.peek();
304     CSSPrimitiveValue::UnitTypes unit = token.unitType();
305     bool acceptUnitless = token.type() == NumberToken && shouldAcceptUnitlessValue(token.numericValue(), cssParserMode, unitless);
306     if (acceptUnitless)
307         unit = CSSPrimitiveValue::UnitTypes::CSS_MS;
308     if (token.type() == DimensionToken || acceptUnitless) {
309         if (valueRange == ValueRangeNonNegative && token.numericValue() < 0)
310             return nullptr;
311         if (unit == CSSPrimitiveValue::UnitTypes::CSS_MS || unit == CSSPrimitiveValue::UnitTypes::CSS_S)
312             return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
313         return nullptr;
314     }
315     CalcParser calcParser(range, valueRange);
316     if (const CSSCalcValue* calculation = calcParser.value()) {
317         if (calculation->category() == CalcTime)
318             return calcParser.consumeValue();
319     }
320     return nullptr;
321 }
322
323 RefPtr<CSSPrimitiveValue> consumeIdent(CSSParserTokenRange& range)
324 {
325     if (range.peek().type() != IdentToken)
326         return nullptr;
327     return CSSValuePool::singleton().createIdentifierValue(range.consumeIncludingWhitespace().id());
328 }
329
330 RefPtr<CSSPrimitiveValue> consumeIdentRange(CSSParserTokenRange& range, CSSValueID lower, CSSValueID upper)
331 {
332     if (range.peek().id() < lower || range.peek().id() > upper)
333         return nullptr;
334     return consumeIdent(range);
335 }
336
337 // FIXME-NEWPARSER: Eventually we'd like this to use CSSCustomIdentValue, but we need
338 // to do other plumbing work first (like changing Pair to CSSValuePair and make it not
339 // use only primitive values).
340 RefPtr<CSSPrimitiveValue> consumeCustomIdent(CSSParserTokenRange& range)
341 {
342     if (range.peek().type() != IdentToken || isCSSWideKeyword(range.peek().id()))
343         return nullptr;
344     return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().value().toString(), CSSPrimitiveValue::UnitTypes::CSS_STRING);
345 }
346
347 RefPtr<CSSPrimitiveValue> consumeString(CSSParserTokenRange& range)
348 {
349     if (range.peek().type() != StringToken)
350         return nullptr;
351     return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().value().toString(), CSSPrimitiveValue::UnitTypes::CSS_STRING);
352 }
353
354 StringView consumeUrlAsStringView(CSSParserTokenRange& range)
355 {
356     const CSSParserToken& token = range.peek();
357     if (token.type() == UrlToken) {
358         range.consumeIncludingWhitespace();
359         return token.value();
360     }
361     if (token.functionId() == CSSValueUrl) {
362         CSSParserTokenRange urlRange = range;
363         CSSParserTokenRange urlArgs = urlRange.consumeBlock();
364         const CSSParserToken& next = urlArgs.consumeIncludingWhitespace();
365         if (next.type() == BadStringToken || !urlArgs.atEnd())
366             return StringView();
367         ASSERT(next.type() == StringToken);
368         range = urlRange;
369         range.consumeWhitespace();
370         return next.value();
371     }
372
373     return StringView();
374 }
375
376 RefPtr<CSSPrimitiveValue> consumeUrl(CSSParserTokenRange& range)
377 {
378     StringView url = consumeUrlAsStringView(range);
379     if (url.isNull())
380         return nullptr;
381     return CSSValuePool::singleton().createValue(url.toString(), CSSPrimitiveValue::UnitTypes::CSS_URI);
382 }
383
384 static int clampRGBComponent(const CSSPrimitiveValue& value)
385 {
386     double result = value.doubleValue();
387     // FIXME: Multiply by 2.55 and round instead of floor.
388     if (value.isPercentage())
389         result *= 2.56;
390     return clampTo<int>(result, 0, 255);
391 }
392
393 static Color parseRGBParameters(CSSParserTokenRange& range, bool parseAlpha)
394 {
395     ASSERT(range.peek().functionId() == CSSValueRgb || range.peek().functionId() == CSSValueRgba);
396     Color result;
397     CSSParserTokenRange args = consumeFunction(range);
398     RefPtr<CSSPrimitiveValue> colorParameter = consumeInteger(args);
399     if (!colorParameter)
400         colorParameter = consumePercent(args, ValueRangeAll);
401     if (!colorParameter)
402         return Color();
403     const bool isPercent = colorParameter->isPercentage();
404     int colorArray[3];
405     colorArray[0] = clampRGBComponent(*colorParameter);
406     for (int i = 1; i < 3; i++) {
407         if (!consumeCommaIncludingWhitespace(args))
408             return Color();
409         colorParameter = isPercent ? consumePercent(args, ValueRangeAll) : consumeInteger(args);
410         if (!colorParameter)
411             return Color();
412         colorArray[i] = clampRGBComponent(*colorParameter);
413     }
414     if (parseAlpha) {
415         if (!consumeCommaIncludingWhitespace(args))
416             return Color();
417         double alpha;
418         if (!consumeNumberRaw(args, alpha))
419             return Color();
420         // Convert the floating pointer number of alpha to an integer in the range [0, 256),
421         // with an equal distribution across all 256 values.
422         int alphaComponent = static_cast<int>(clampTo<double>(alpha, 0.0, 1.0) * nextafter(256.0, 0.0));
423         result = Color(makeRGBA(colorArray[0], colorArray[1], colorArray[2], alphaComponent));
424     } else {
425         result = Color(makeRGB(colorArray[0], colorArray[1], colorArray[2]));
426     }
427
428     if (!args.atEnd())
429         return Color();
430
431     return result;
432 }
433
434 static Color parseHSLParameters(CSSParserTokenRange& range, bool parseAlpha)
435 {
436     ASSERT(range.peek().functionId() == CSSValueHsl || range.peek().functionId() == CSSValueHsla);
437     CSSParserTokenRange args = consumeFunction(range);
438     RefPtr<CSSPrimitiveValue> hslValue = consumeNumber(args, ValueRangeAll);
439     if (!hslValue)
440         return Color();
441     double colorArray[3];
442     colorArray[0] = (((hslValue->intValue() % 360) + 360) % 360) / 360.0;
443     for (int i = 1; i < 3; i++) {
444         if (!consumeCommaIncludingWhitespace(args))
445             return Color();
446         hslValue = consumePercent(args, ValueRangeAll);
447         if (!hslValue)
448             return Color();
449         double doubleValue = hslValue->doubleValue();
450         colorArray[i] = clampTo<double>(doubleValue, 0.0, 100.0) / 100.0; // Needs to be value between 0 and 1.0.
451     }
452     double alpha = 1.0;
453     if (parseAlpha) {
454         if (!consumeCommaIncludingWhitespace(args))
455             return Color();
456         if (!consumeNumberRaw(args, alpha))
457             return Color();
458         alpha = clampTo<double>(alpha, 0.0, 1.0);
459     }
460
461     if (!args.atEnd())
462         return Color();
463
464     return Color(makeRGBAFromHSLA(colorArray[0], colorArray[1], colorArray[2], alpha));
465 }
466
467 static Color parseColorFunctionParameters(CSSParserTokenRange& range)
468 {
469     ASSERT(range.peek().functionId() == CSSValueColor);
470     CSSParserTokenRange args = consumeFunction(range);
471
472     ColorSpace colorSpace;
473     switch (args.peek().id()) {
474     case CSSValueSrgb:
475         colorSpace = ColorSpaceSRGB;
476         break;
477     case CSSValueDisplayP3:
478         colorSpace = ColorSpaceDisplayP3;
479         break;
480     default:
481         return Color();
482     }
483     consumeIdent(args);
484
485     double colorChannels[4] = { 0, 0, 0, 1 };
486     for (int i = 0; i < 3; ++i) {
487         double value;
488         if (consumeNumberRaw(args, value))
489             colorChannels[i] = std::max(0.0, std::min(1.0, value));
490         else
491             break;
492     }
493
494     if (consumeSlashIncludingWhitespace(args)) {
495         auto alphaParameter = consumePercent(args, ValueRangeAll);
496         if (!alphaParameter)
497             alphaParameter = consumeNumber(args, ValueRangeAll);
498         if (!alphaParameter)
499             return Color();
500
501         colorChannels[3] = std::max(0.0, std::min(1.0, alphaParameter->isPercentage() ? (alphaParameter->doubleValue() / 100) : alphaParameter->doubleValue()));
502     }
503
504     // FIXME: Support the comma-separated list of fallback color values.
505
506     if (!args.atEnd())
507         return Color();
508     
509     return Color(colorChannels[0], colorChannels[1], colorChannels[2], colorChannels[3], colorSpace);
510 }
511
512 static Color parseHexColor(CSSParserTokenRange& range, bool acceptQuirkyColors)
513 {
514     RGBA32 result;
515     const CSSParserToken& token = range.peek();
516     if (token.type() == HashToken) {
517         if (!Color::parseHexColor(token.value(), result))
518             return Color();
519     } else if (acceptQuirkyColors) {
520         String color;
521         if (token.type() == NumberToken || token.type() == DimensionToken) {
522             if (token.numericValueType() != IntegerValueType
523                 || token.numericValue() < 0. || token.numericValue() >= 1000000.)
524                 return Color();
525             if (token.type() == NumberToken) // e.g. 112233
526                 color = String::format("%d", static_cast<int>(token.numericValue()));
527             else // e.g. 0001FF
528                 color = String::number(static_cast<int>(token.numericValue())) + token.value().toString();
529             while (color.length() < 6)
530                 color = "0" + color;
531         } else if (token.type() == IdentToken) { // e.g. FF0000
532             color = token.value().toString();
533         }
534         unsigned length = color.length();
535         if (length != 3 && length != 6)
536             return Color();
537         if (!Color::parseHexColor(color, result))
538             return Color();
539     } else {
540         return Color();
541     }
542     range.consumeIncludingWhitespace();
543     return Color(result);
544 }
545
546 static Color parseColorFunction(CSSParserTokenRange& range)
547 {
548     CSSParserTokenRange colorRange = range;
549     CSSValueID functionId = range.peek().functionId();
550     Color color;
551     switch (functionId) {
552     case CSSValueRgb:
553     case CSSValueRgba:
554         color = parseRGBParameters(colorRange, functionId == CSSValueRgba);
555         break;
556     case CSSValueHsl:
557     case CSSValueHsla:
558         color = parseHSLParameters(colorRange, functionId == CSSValueHsla);
559         break;
560     case CSSValueColor:
561         color = parseColorFunctionParameters(colorRange);
562         break;
563     default:
564         return Color();
565     }
566     range = colorRange;
567     return color;
568 }
569
570 RefPtr<CSSPrimitiveValue> consumeColor(CSSParserTokenRange& range, CSSParserMode cssParserMode, bool acceptQuirkyColors)
571 {
572     CSSValueID id = range.peek().id();
573     if (StyleColor::isColorKeyword(id)) {
574         if (!isValueAllowedInMode(id, cssParserMode))
575             return nullptr;
576         return consumeIdent(range);
577     }
578     Color color = parseHexColor(range, acceptQuirkyColors);
579     if (!color.isValid())
580         color = parseColorFunction(range);
581     if (!color.isValid())
582         return nullptr;
583     return CSSValuePool::singleton().createValue(color);
584 }
585
586 static RefPtr<CSSPrimitiveValue> consumePositionComponent(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
587 {
588     if (range.peek().type() == IdentToken)
589         return consumeIdent<CSSValueLeft, CSSValueTop, CSSValueBottom, CSSValueRight, CSSValueCenter>(range);
590     return consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, unitless);
591 }
592
593 static bool isHorizontalPositionKeywordOnly(const CSSPrimitiveValue& value)
594 {
595     return value.isValueID() && (value.valueID() == CSSValueLeft || value.valueID() == CSSValueRight);
596 }
597
598 static bool isVerticalPositionKeywordOnly(const CSSPrimitiveValue& value)
599 {
600     return value.isValueID() && (value.valueID() == CSSValueTop || value.valueID() == CSSValueBottom);
601 }
602
603 static void positionFromOneValue(CSSPrimitiveValue& value, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
604 {
605     bool valueAppliesToYAxisOnly = isVerticalPositionKeywordOnly(value);
606     resultX = &value;
607     resultY = CSSPrimitiveValue::createIdentifier(CSSValueCenter);
608     if (valueAppliesToYAxisOnly)
609         std::swap(resultX, resultY);
610 }
611
612 static bool positionFromTwoValues(CSSPrimitiveValue& value1, CSSPrimitiveValue& value2,
613     RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
614 {
615     bool mustOrderAsXY = isHorizontalPositionKeywordOnly(value1) || isVerticalPositionKeywordOnly(value2)
616         || !value1.isValueID() || !value2.isValueID();
617     bool mustOrderAsYX = isVerticalPositionKeywordOnly(value1) || isHorizontalPositionKeywordOnly(value2);
618     if (mustOrderAsXY && mustOrderAsYX)
619         return false;
620     resultX = &value1;
621     resultY = &value2;
622     if (mustOrderAsYX)
623         std::swap(resultX, resultY);
624     return true;
625 }
626
627     
628 template<typename... Args>
629 static Ref<CSSPrimitiveValue> createPrimitiveValuePair(Args&&... args)
630 {
631     return CSSValuePool::singleton().createValue(Pair::create(std::forward<Args>(args)...));
632 }
633
634 static bool positionFromThreeOrFourValues(CSSPrimitiveValue** values, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
635 {
636     CSSPrimitiveValue* center = nullptr;
637     for (int i = 0; values[i]; i++) {
638         CSSPrimitiveValue* currentValue = values[i];
639         if (!currentValue->isValueID())
640             return false;
641         CSSValueID id = currentValue->valueID();
642
643         if (id == CSSValueCenter) {
644             if (center)
645                 return false;
646             center = currentValue;
647             continue;
648         }
649
650         RefPtr<CSSPrimitiveValue> result;
651         if (values[i + 1] && !values[i + 1]->isValueID())
652             result = createPrimitiveValuePair(currentValue, values[++i]);
653         else
654             result = currentValue;
655
656         if (id == CSSValueLeft || id == CSSValueRight) {
657             if (resultX)
658                 return false;
659             resultX = result;
660         } else {
661             ASSERT(id == CSSValueTop || id == CSSValueBottom);
662             if (resultY)
663                 return false;
664             resultY = result;
665         }
666     }
667
668     if (center) {
669         ASSERT(resultX || resultY);
670         if (resultX && resultY)
671             return false;
672         if (!resultX)
673             resultX = center;
674         else
675             resultY = center;
676     }
677
678     ASSERT(resultX && resultY);
679     return true;
680 }
681
682 // FIXME: This may consume from the range upon failure. The background
683 // shorthand works around it, but we should just fix it here.
684 bool consumePosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
685 {
686     RefPtr<CSSPrimitiveValue> value1 = consumePositionComponent(range, cssParserMode, unitless);
687     if (!value1)
688         return false;
689
690     RefPtr<CSSPrimitiveValue> value2 = consumePositionComponent(range, cssParserMode, unitless);
691     if (!value2) {
692         positionFromOneValue(*value1, resultX, resultY);
693         return true;
694     }
695
696     RefPtr<CSSPrimitiveValue> value3 = consumePositionComponent(range, cssParserMode, unitless);
697     if (!value3)
698         return positionFromTwoValues(*value1, *value2, resultX, resultY);
699
700     RefPtr<CSSPrimitiveValue> value4 = consumePositionComponent(range, cssParserMode, unitless);
701     CSSPrimitiveValue* values[5];
702     values[0] = value1.get();
703     values[1] = value2.get();
704     values[2] = value3.get();
705     values[3] = value4.get();
706     values[4] = nullptr;
707     return positionFromThreeOrFourValues(values, resultX, resultY);
708 }
709
710 RefPtr<CSSPrimitiveValue> consumePosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
711 {
712     RefPtr<CSSPrimitiveValue> resultX;
713     RefPtr<CSSPrimitiveValue> resultY;
714     if (consumePosition(range, cssParserMode, unitless, resultX, resultY))
715         return createPrimitiveValuePair(resultX.releaseNonNull(), resultY.releaseNonNull());
716     return nullptr;
717 }
718
719 bool consumeOneOrTwoValuedPosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
720 {
721     RefPtr<CSSPrimitiveValue> value1 = consumePositionComponent(range, cssParserMode, unitless);
722     if (!value1)
723         return false;
724     RefPtr<CSSPrimitiveValue> value2 = consumePositionComponent(range, cssParserMode, unitless);
725     if (!value2) {
726         positionFromOneValue(*value1, resultX, resultY);
727         return true;
728     }
729     return positionFromTwoValues(*value1, *value2, resultX, resultY);
730 }
731
732 // This should go away once we drop support for -webkit-gradient
733 static RefPtr<CSSPrimitiveValue> consumeDeprecatedGradientPoint(CSSParserTokenRange& args, bool horizontal)
734 {
735     if (args.peek().type() == IdentToken) {
736         if ((horizontal && consumeIdent<CSSValueLeft>(args)) || (!horizontal && consumeIdent<CSSValueTop>(args)))
737             return CSSValuePool::singleton().createValue(0., CSSPrimitiveValue::UnitTypes::CSS_PERCENTAGE);
738         if ((horizontal && consumeIdent<CSSValueRight>(args)) || (!horizontal && consumeIdent<CSSValueBottom>(args)))
739             return CSSValuePool::singleton().createValue(100., CSSPrimitiveValue::UnitTypes::CSS_PERCENTAGE);
740         if (consumeIdent<CSSValueCenter>(args))
741             return CSSValuePool::singleton().createValue(50., CSSPrimitiveValue::UnitTypes::CSS_PERCENTAGE);
742         return nullptr;
743     }
744     RefPtr<CSSPrimitiveValue> result = consumePercent(args, ValueRangeAll);
745     if (!result)
746         result = consumeNumber(args, ValueRangeAll);
747     return result;
748 }
749
750 // Used to parse colors for -webkit-gradient(...).
751 static RefPtr<CSSPrimitiveValue> consumeDeprecatedGradientStopColor(CSSParserTokenRange& args, CSSParserMode cssParserMode)
752 {
753     if (args.peek().id() == CSSValueCurrentcolor)
754         return nullptr;
755     return consumeColor(args, cssParserMode);
756 }
757
758 static bool consumeDeprecatedGradientColorStop(CSSParserTokenRange& range, CSSGradientColorStop& stop, CSSParserMode cssParserMode)
759 {
760     CSSValueID id = range.peek().functionId();
761     if (id != CSSValueFrom && id != CSSValueTo && id != CSSValueColorStop)
762         return false;
763
764     CSSParserTokenRange args = consumeFunction(range);
765     double position;
766     if (id == CSSValueFrom || id == CSSValueTo) {
767         position = (id == CSSValueFrom) ? 0 : 1;
768     } else {
769         ASSERT(id == CSSValueColorStop);
770         const CSSParserToken& arg = args.consumeIncludingWhitespace();
771         if (arg.type() == PercentageToken)
772             position = arg.numericValue() / 100.0;
773         else if (arg.type() == NumberToken)
774             position = arg.numericValue();
775         else
776             return false;
777
778         if (!consumeCommaIncludingWhitespace(args))
779             return false;
780     }
781
782     stop.m_position = CSSValuePool::singleton().createValue(position, CSSPrimitiveValue::UnitTypes::CSS_NUMBER);
783     stop.m_color = consumeDeprecatedGradientStopColor(args, cssParserMode);
784     return stop.m_color && args.atEnd();
785 }
786
787 static RefPtr<CSSValue> consumeDeprecatedGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode)
788 {
789     RefPtr<CSSGradientValue> result;
790     CSSValueID id = args.consumeIncludingWhitespace().id();
791     bool isDeprecatedRadialGradient = (id == CSSValueRadial);
792     if (isDeprecatedRadialGradient)
793         result = CSSRadialGradientValue::create(NonRepeating, CSSDeprecatedRadialGradient);
794     else if (id == CSSValueLinear)
795         result = CSSLinearGradientValue::create(NonRepeating, CSSDeprecatedLinearGradient);
796     if (!result || !consumeCommaIncludingWhitespace(args))
797         return nullptr;
798
799     RefPtr<CSSPrimitiveValue> point = consumeDeprecatedGradientPoint(args, true);
800     if (!point)
801         return nullptr;
802     result->setFirstX(point.copyRef());
803     point = consumeDeprecatedGradientPoint(args, false);
804     if (!point)
805         return nullptr;
806     result->setFirstY(point.copyRef());
807
808     if (!consumeCommaIncludingWhitespace(args))
809         return nullptr;
810
811     // For radial gradients only, we now expect a numeric radius.
812     if (isDeprecatedRadialGradient) {
813         RefPtr<CSSPrimitiveValue> radius = consumeNumber(args, ValueRangeAll);
814         if (!radius || !consumeCommaIncludingWhitespace(args))
815             return nullptr;
816         downcast<CSSRadialGradientValue>(result.get())->setFirstRadius(radius.copyRef());
817     }
818
819     point = consumeDeprecatedGradientPoint(args, true);
820     if (!point)
821         return nullptr;
822     result->setSecondX(point.copyRef());
823     point = consumeDeprecatedGradientPoint(args, false);
824     if (!point)
825         return nullptr;
826     result->setSecondY(point.copyRef());
827
828     // For radial gradients only, we now expect the second radius.
829     if (isDeprecatedRadialGradient) {
830         if (!consumeCommaIncludingWhitespace(args))
831             return nullptr;
832         RefPtr<CSSPrimitiveValue> radius = consumeNumber(args, ValueRangeAll);
833         if (!radius)
834             return nullptr;
835         downcast<CSSRadialGradientValue>(result.get())->setSecondRadius(radius.copyRef());
836     }
837
838     CSSGradientColorStop stop;
839     while (consumeCommaIncludingWhitespace(args)) {
840         if (!consumeDeprecatedGradientColorStop(args, stop, cssParserMode))
841             return nullptr;
842         result->addStop(stop);
843     }
844
845     return result;
846 }
847
848 static bool consumeGradientColorStops(CSSParserTokenRange& range, CSSParserMode cssParserMode, CSSGradientValue* gradient)
849 {
850     bool supportsColorHints = gradient->gradientType() == CSSLinearGradient || gradient->gradientType() == CSSRadialGradient;
851
852     // The first color stop cannot be a color hint.
853     bool previousStopWasColorHint = true;
854     do {
855         CSSGradientColorStop stop;
856         stop.m_color = consumeColor(range, cssParserMode);
857         // Two hints in a row are not allowed.
858         if (!stop.m_color && (!supportsColorHints || previousStopWasColorHint))
859             return false;
860         
861         previousStopWasColorHint = !stop.m_color;
862         
863         // FIXME-NEWPARSER: This boolean could be removed. Null checking color would be sufficient.
864         stop.isMidpoint = !stop.m_color;
865
866         stop.m_position = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll);
867         if (!stop.m_color && !stop.m_position)
868             return false;
869         gradient->addStop(stop);
870     } while (consumeCommaIncludingWhitespace(range));
871
872     // The last color stop cannot be a color hint.
873     if (previousStopWasColorHint)
874         return false;
875
876     // Must have 2 or more stops to be valid.
877     return gradient->stopCount() >= 2;
878 }
879
880 static RefPtr<CSSValue> consumeDeprecatedRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating)
881 {
882     RefPtr<CSSRadialGradientValue> result = CSSRadialGradientValue::create(repeating, CSSPrefixedRadialGradient);
883     RefPtr<CSSPrimitiveValue> centerX;
884     RefPtr<CSSPrimitiveValue> centerY;
885     consumeOneOrTwoValuedPosition(args, cssParserMode, UnitlessQuirk::Forbid, centerX, centerY);
886     if ((centerX || centerY) && !consumeCommaIncludingWhitespace(args))
887         return nullptr;
888
889     result->setFirstX(centerX.copyRef());
890     result->setFirstY(centerY.copyRef());
891     result->setSecondX(centerX.copyRef());
892     result->setSecondY(centerY.copyRef());
893
894     RefPtr<CSSPrimitiveValue> shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args);
895     RefPtr<CSSPrimitiveValue> sizeKeyword = consumeIdent<CSSValueClosestSide, CSSValueClosestCorner, CSSValueFarthestSide, CSSValueFarthestCorner, CSSValueContain, CSSValueCover>(args);
896     if (!shape)
897         shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args);
898     result->setShape(shape.copyRef());
899     result->setSizingBehavior(sizeKeyword.copyRef());
900
901     // Or, two lengths or percentages
902     if (!shape && !sizeKeyword) {
903         RefPtr<CSSPrimitiveValue> horizontalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
904         RefPtr<CSSPrimitiveValue> verticalSize;
905         if (horizontalSize) {
906             verticalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
907             if (!verticalSize)
908                 return nullptr;
909             consumeCommaIncludingWhitespace(args);
910             result->setEndHorizontalSize(horizontalSize.copyRef());
911             result->setEndVerticalSize(verticalSize.copyRef());
912         }
913     } else {
914         consumeCommaIncludingWhitespace(args);
915     }
916     if (!consumeGradientColorStops(args, cssParserMode, result.get()))
917         return nullptr;
918
919     return result;
920 }
921
922 static RefPtr<CSSValue> consumeRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating)
923 {
924     RefPtr<CSSRadialGradientValue> result = CSSRadialGradientValue::create(repeating, CSSRadialGradient);
925
926     RefPtr<CSSPrimitiveValue> shape;
927     RefPtr<CSSPrimitiveValue> sizeKeyword;
928     RefPtr<CSSPrimitiveValue> horizontalSize;
929     RefPtr<CSSPrimitiveValue> verticalSize;
930
931     // First part of grammar, the size/shape clause:
932     // [ circle || <length> ] |
933     // [ ellipse || [ <length> | <percentage> ]{2} ] |
934     // [ [ circle | ellipse] || <size-keyword> ]
935     for (int i = 0; i < 3; ++i) {
936         if (args.peek().type() == IdentToken) {
937             CSSValueID id = args.peek().id();
938             if (id == CSSValueCircle || id == CSSValueEllipse) {
939                 if (shape)
940                     return nullptr;
941                 shape = consumeIdent(args);
942             } else if (id == CSSValueClosestSide || id == CSSValueClosestCorner || id == CSSValueFarthestSide || id == CSSValueFarthestCorner) {
943                 if (sizeKeyword)
944                     return nullptr;
945                 sizeKeyword = consumeIdent(args);
946             } else {
947                 break;
948             }
949         } else {
950             RefPtr<CSSPrimitiveValue> center = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
951             if (!center)
952                 break;
953             if (horizontalSize)
954                 return nullptr;
955             horizontalSize = center;
956             center = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
957             if (center) {
958                 verticalSize = center;
959                 ++i;
960             }
961         }
962     }
963
964     // You can specify size as a keyword or a length/percentage, not both.
965     if (sizeKeyword && horizontalSize)
966         return nullptr;
967     // Circles must have 0 or 1 lengths.
968     if (shape && shape->valueID() == CSSValueCircle && verticalSize)
969         return nullptr;
970     // Ellipses must have 0 or 2 length/percentages.
971     if (shape && shape->valueID() == CSSValueEllipse && horizontalSize && !verticalSize)
972         return nullptr;
973     // If there's only one size, it must be a length.
974     if (!verticalSize && horizontalSize && horizontalSize->isPercentage())
975         return nullptr;
976     if ((horizontalSize && horizontalSize->isCalculatedPercentageWithLength())
977         || (verticalSize && verticalSize->isCalculatedPercentageWithLength()))
978         return nullptr;
979
980     result->setShape(shape.copyRef());
981     result->setSizingBehavior(sizeKeyword.copyRef());
982     result->setEndHorizontalSize(horizontalSize.copyRef());
983     result->setEndVerticalSize(verticalSize.copyRef());
984
985     RefPtr<CSSPrimitiveValue> centerX;
986     RefPtr<CSSPrimitiveValue> centerY;
987     if (args.peek().id() == CSSValueAt) {
988         args.consumeIncludingWhitespace();
989         consumePosition(args, cssParserMode, UnitlessQuirk::Forbid, centerX, centerY);
990         if (!(centerX && centerY))
991             return nullptr;
992         
993         result->setFirstX(centerX.copyRef());
994         result->setFirstY(centerY.copyRef());
995         
996         // Right now, CSS radial gradients have the same start and end centers.
997         result->setSecondX(centerX.copyRef());
998         result->setSecondY(centerY.copyRef());
999     }
1000
1001     if ((shape || sizeKeyword || horizontalSize || centerX || centerY) && !consumeCommaIncludingWhitespace(args))
1002         return nullptr;
1003     if (!consumeGradientColorStops(args, cssParserMode, result.get()))
1004         return nullptr;
1005     return result;
1006 }
1007
1008 static RefPtr<CSSValue> consumeLinearGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating, CSSGradientType gradientType)
1009 {
1010     RefPtr<CSSLinearGradientValue> result = CSSLinearGradientValue::create(repeating, gradientType);
1011
1012     bool expectComma = true;
1013     RefPtr<CSSPrimitiveValue> angle = consumeAngle(args, cssParserMode, UnitlessQuirk::Forbid);
1014     if (angle)
1015         result->setAngle(angle.releaseNonNull());
1016     else if (gradientType == CSSPrefixedLinearGradient || consumeIdent<CSSValueTo>(args)) {
1017         RefPtr<CSSPrimitiveValue> endX = consumeIdent<CSSValueLeft, CSSValueRight>(args);
1018         RefPtr<CSSPrimitiveValue> endY = consumeIdent<CSSValueBottom, CSSValueTop>(args);
1019         if (!endX && !endY) {
1020             if (gradientType == CSSLinearGradient)
1021                 return nullptr;
1022             endY = CSSPrimitiveValue::createIdentifier(CSSValueTop);
1023             expectComma = false;
1024         } else if (!endX) {
1025             endX = consumeIdent<CSSValueLeft, CSSValueRight>(args);
1026         }
1027
1028         result->setFirstX(endX.copyRef());
1029         result->setFirstY(endY.copyRef());
1030     } else {
1031         expectComma = false;
1032     }
1033
1034     if (expectComma && !consumeCommaIncludingWhitespace(args))
1035         return nullptr;
1036     if (!consumeGradientColorStops(args, cssParserMode, result.get()))
1037         return nullptr;
1038     return result;
1039 }
1040
1041 RefPtr<CSSValue> consumeImageOrNone(CSSParserTokenRange& range, CSSParserContext context)
1042 {
1043     if (range.peek().id() == CSSValueNone)
1044         return consumeIdent(range);
1045     return consumeImage(range, context);
1046 }
1047
1048 static RefPtr<CSSValue> consumeCrossFade(CSSParserTokenRange& args, CSSParserContext context, bool prefixed)
1049 {
1050     RefPtr<CSSValue> fromImageValue = consumeImageOrNone(args, context);
1051     if (!fromImageValue || !consumeCommaIncludingWhitespace(args))
1052         return nullptr;
1053     RefPtr<CSSValue> toImageValue = consumeImageOrNone(args, context);
1054     if (!toImageValue || !consumeCommaIncludingWhitespace(args))
1055         return nullptr;
1056
1057     RefPtr<CSSPrimitiveValue> percentage;
1058     const CSSParserToken& percentageArg = args.consumeIncludingWhitespace();
1059     if (percentageArg.type() == PercentageToken)
1060         percentage = CSSValuePool::singleton().createValue(clampTo<double>(percentageArg.numericValue() / 100, 0, 1), CSSPrimitiveValue::UnitTypes::CSS_NUMBER);
1061     else if (percentageArg.type() == NumberToken)
1062         percentage = CSSValuePool::singleton().createValue(clampTo<double>(percentageArg.numericValue(), 0, 1), CSSPrimitiveValue::UnitTypes::CSS_NUMBER);
1063
1064     if (!percentage)
1065         return nullptr;
1066     return CSSCrossfadeValue::create(fromImageValue.releaseNonNull(), toImageValue.releaseNonNull(), percentage.releaseNonNull(), prefixed);
1067 }
1068
1069 static RefPtr<CSSValue> consumeWebkitCanvas(CSSParserTokenRange& args)
1070 {
1071     if (args.peek().type() != IdentToken)
1072         return nullptr;
1073     auto canvasName = args.consumeIncludingWhitespace().value().toString();
1074     if (!args.atEnd())
1075         return nullptr;
1076     return CSSCanvasValue::create(canvasName);
1077 }
1078     
1079 static RefPtr<CSSValue> consumeGeneratedImage(CSSParserTokenRange& range, CSSParserContext context)
1080 {
1081     CSSValueID id = range.peek().functionId();
1082     CSSParserTokenRange rangeCopy = range;
1083     CSSParserTokenRange args = consumeFunction(rangeCopy);
1084     RefPtr<CSSValue> result;
1085     if (id == CSSValueRadialGradient)
1086         result = consumeRadialGradient(args, context.mode, NonRepeating);
1087     else if (id == CSSValueRepeatingRadialGradient)
1088         result = consumeRadialGradient(args, context.mode, Repeating);
1089     else if (id == CSSValueWebkitLinearGradient)
1090         result = consumeLinearGradient(args, context.mode, NonRepeating, CSSPrefixedLinearGradient);
1091     else if (id == CSSValueWebkitRepeatingLinearGradient)
1092         result = consumeLinearGradient(args, context.mode, Repeating, CSSPrefixedLinearGradient);
1093     else if (id == CSSValueRepeatingLinearGradient)
1094         result = consumeLinearGradient(args, context.mode, Repeating, CSSLinearGradient);
1095     else if (id == CSSValueLinearGradient)
1096         result = consumeLinearGradient(args, context.mode, NonRepeating, CSSLinearGradient);
1097     else if (id == CSSValueWebkitGradient)
1098         result = consumeDeprecatedGradient(args, context.mode);
1099     else if (id == CSSValueWebkitRadialGradient)
1100         result = consumeDeprecatedRadialGradient(args, context.mode, NonRepeating);
1101     else if (id == CSSValueWebkitRepeatingRadialGradient)
1102         result = consumeDeprecatedRadialGradient(args, context.mode, Repeating);
1103     else if (id == CSSValueWebkitCrossFade || id == CSSValueCrossFade)
1104         result = consumeCrossFade(args, context, id == CSSValueWebkitCrossFade);
1105     else if (id == CSSValueWebkitCanvas)
1106         result = consumeWebkitCanvas(args);
1107
1108     if (!result || !args.atEnd())
1109         return nullptr;
1110     range = rangeCopy;
1111     return result;
1112 }
1113
1114 static RefPtr<CSSValue> consumeImageSet(CSSParserTokenRange& range, const CSSParserContext& context)
1115 {
1116     CSSParserTokenRange rangeCopy = range;
1117     CSSParserTokenRange args = consumeFunction(rangeCopy);
1118     RefPtr<CSSImageSetValue> imageSet = CSSImageSetValue::create();
1119     do {
1120         AtomicString urlValue = consumeUrlAsStringView(args).toAtomicString();
1121         if (urlValue.isNull())
1122             return nullptr;
1123
1124         RefPtr<CSSValue> image = CSSImageValue::create(completeURL(context, urlValue));
1125         imageSet->append(image.releaseNonNull());
1126
1127         const CSSParserToken& token = args.consumeIncludingWhitespace();
1128         if (token.type() != DimensionToken)
1129             return nullptr;
1130         if (token.value() != "x")
1131             return nullptr;
1132         ASSERT(token.unitType() == CSSPrimitiveValue::UnitTypes::CSS_UNKNOWN);
1133         double imageScaleFactor = token.numericValue();
1134         if (imageScaleFactor <= 0)
1135             return nullptr;
1136         imageSet->append(CSSValuePool::singleton().createValue(imageScaleFactor, CSSPrimitiveValue::UnitTypes::CSS_NUMBER));
1137     } while (consumeCommaIncludingWhitespace(args));
1138     if (!args.atEnd())
1139         return nullptr;
1140     range = rangeCopy;
1141     return imageSet;
1142 }
1143
1144 static bool isGeneratedImage(CSSValueID id)
1145 {
1146     return id == CSSValueLinearGradient || id == CSSValueRadialGradient
1147         || id == CSSValueRepeatingLinearGradient || id == CSSValueRepeatingRadialGradient
1148         || id == CSSValueWebkitLinearGradient || id == CSSValueWebkitRadialGradient
1149         || id == CSSValueWebkitRepeatingLinearGradient || id == CSSValueWebkitRepeatingRadialGradient
1150         || id == CSSValueWebkitGradient || id == CSSValueWebkitCrossFade || id == CSSValueWebkitCanvas
1151         || id == CSSValuePaint;
1152 }
1153
1154 RefPtr<CSSValue> consumeImage(CSSParserTokenRange& range, CSSParserContext context, ConsumeGeneratedImage generatedImage)
1155 {
1156     AtomicString uri = consumeUrlAsStringView(range).toAtomicString();
1157     if (!uri.isNull())
1158         return CSSImageValue::create(completeURL(context, uri));
1159     if (range.peek().type() == FunctionToken) {
1160         CSSValueID id = range.peek().functionId();
1161         if (id == CSSValueWebkitImageSet || id == CSSValueImageSet)
1162             return consumeImageSet(range, context);
1163         if (generatedImage == ConsumeGeneratedImage::Allow && isGeneratedImage(id))
1164             return consumeGeneratedImage(range, context);
1165     }
1166     return nullptr;
1167 }
1168
1169 } // namespace CSSPropertyParserHelpers
1170
1171 } // namespace WebCore