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