[CSS Parser] Enable basic parser testing.
[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_NUMBER);
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 CSSValuePool::singleton().createIdentifierValue(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 // FIXME-NEWPARSER: Eventually we'd like this to use CSSCustomIdentValue, but we need
337 // to do other plumbing work first (like changing Pair to CSSValuePair and make it not
338 // use only primitive values).
339 RefPtr<CSSPrimitiveValue> consumeCustomIdent(CSSParserTokenRange& range)
340 {
341     if (range.peek().type() != IdentToken || isCSSWideKeyword(range.peek().id()))
342         return nullptr;
343     return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().value().toString(), CSSPrimitiveValue::UnitTypes::CSS_STRING);
344 }
345
346 RefPtr<CSSPrimitiveValue> consumeString(CSSParserTokenRange& range)
347 {
348     if (range.peek().type() != StringToken)
349         return nullptr;
350     return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().value().toString(), CSSPrimitiveValue::UnitTypes::CSS_STRING);
351 }
352
353 StringView consumeUrlAsStringView(CSSParserTokenRange& range)
354 {
355     const CSSParserToken& token = range.peek();
356     if (token.type() == UrlToken) {
357         range.consumeIncludingWhitespace();
358         return token.value();
359     }
360     if (token.functionId() == CSSValueUrl) {
361         CSSParserTokenRange urlRange = range;
362         CSSParserTokenRange urlArgs = urlRange.consumeBlock();
363         const CSSParserToken& next = urlArgs.consumeIncludingWhitespace();
364         if (next.type() == BadStringToken || !urlArgs.atEnd())
365             return StringView();
366         ASSERT(next.type() == StringToken);
367         range = urlRange;
368         range.consumeWhitespace();
369         return next.value();
370     }
371
372     return StringView();
373 }
374
375 RefPtr<CSSPrimitiveValue> consumeUrl(CSSParserTokenRange& range)
376 {
377     StringView url = consumeUrlAsStringView(range);
378     if (url.isNull())
379         return nullptr;
380     return CSSPrimitiveValue::create(url.toString(), CSSPrimitiveValue::UnitTypes::CSS_URI);
381 }
382
383 static int clampRGBComponent(const CSSPrimitiveValue& value)
384 {
385     double result = value.doubleValue();
386     // FIXME: Multiply by 2.55 and round instead of floor.
387     if (value.isPercentage())
388         result *= 2.56;
389     return clampTo<int>(result, 0, 255);
390 }
391
392 static bool parseRGBParameters(CSSParserTokenRange& range, RGBA32& result, bool parseAlpha)
393 {
394     ASSERT(range.peek().functionId() == CSSValueRgb || range.peek().functionId() == CSSValueRgba);
395     CSSParserTokenRange args = consumeFunction(range);
396     RefPtr<CSSPrimitiveValue> colorParameter = consumeInteger(args);
397     if (!colorParameter)
398         colorParameter = consumePercent(args, ValueRangeAll);
399     if (!colorParameter)
400         return false;
401     const bool isPercent = colorParameter->isPercentage();
402     int colorArray[3];
403     colorArray[0] = clampRGBComponent(*colorParameter);
404     for (int i = 1; i < 3; i++) {
405         if (!consumeCommaIncludingWhitespace(args))
406             return false;
407         colorParameter = isPercent ? consumePercent(args, ValueRangeAll) : consumeInteger(args);
408         if (!colorParameter)
409             return false;
410         colorArray[i] = clampRGBComponent(*colorParameter);
411     }
412     if (parseAlpha) {
413         if (!consumeCommaIncludingWhitespace(args))
414             return false;
415         double alpha;
416         if (!consumeNumberRaw(args, alpha))
417             return false;
418         // Convert the floating pointer number of alpha to an integer in the range [0, 256),
419         // with an equal distribution across all 256 values.
420         int alphaComponent = static_cast<int>(clampTo<double>(alpha, 0.0, 1.0) * nextafter(256.0, 0.0));
421         result = makeRGBA(colorArray[0], colorArray[1], colorArray[2], alphaComponent);
422     } else {
423         result = makeRGB(colorArray[0], colorArray[1], colorArray[2]);
424     }
425     return args.atEnd();
426 }
427
428 static bool parseHSLParameters(CSSParserTokenRange& range, RGBA32& result, bool parseAlpha)
429 {
430     ASSERT(range.peek().functionId() == CSSValueHsl || range.peek().functionId() == CSSValueHsla);
431     CSSParserTokenRange args = consumeFunction(range);
432     RefPtr<CSSPrimitiveValue> hslValue = consumeNumber(args, ValueRangeAll);
433     if (!hslValue)
434         return false;
435     double colorArray[3];
436     colorArray[0] = (((hslValue->intValue() % 360) + 360) % 360) / 360.0;
437     for (int i = 1; i < 3; i++) {
438         if (!consumeCommaIncludingWhitespace(args))
439             return false;
440         hslValue = consumePercent(args, ValueRangeAll);
441         if (!hslValue)
442             return false;
443         double doubleValue = hslValue->doubleValue();
444         colorArray[i] = clampTo<double>(doubleValue, 0.0, 100.0) / 100.0; // Needs to be value between 0 and 1.0.
445     }
446     double alpha = 1.0;
447     if (parseAlpha) {
448         if (!consumeCommaIncludingWhitespace(args))
449             return false;
450         if (!consumeNumberRaw(args, alpha))
451             return false;
452         alpha = clampTo<double>(alpha, 0.0, 1.0);
453     }
454     result = makeRGBAFromHSLA(colorArray[0], colorArray[1], colorArray[2], alpha);
455     return args.atEnd();
456 }
457
458 static bool parseHexColor(CSSParserTokenRange& range, RGBA32& result, bool acceptQuirkyColors)
459 {
460     const CSSParserToken& token = range.peek();
461     if (token.type() == HashToken) {
462         if (!Color::parseHexColor(token.value(), result))
463             return false;
464     } else if (acceptQuirkyColors) {
465         String color;
466         if (token.type() == NumberToken || token.type() == DimensionToken) {
467             if (token.numericValueType() != IntegerValueType
468                 || token.numericValue() < 0. || token.numericValue() >= 1000000.)
469                 return false;
470             if (token.type() == NumberToken) // e.g. 112233
471                 color = String::format("%d", static_cast<int>(token.numericValue()));
472             else // e.g. 0001FF
473                 color = String::number(static_cast<int>(token.numericValue())) + token.value().toString();
474             while (color.length() < 6)
475                 color = "0" + color;
476         } else if (token.type() == IdentToken) { // e.g. FF0000
477             color = token.value().toString();
478         }
479         unsigned length = color.length();
480         if (length != 3 && length != 6)
481             return false;
482         if (!Color::parseHexColor(color, result))
483             return false;
484     } else {
485         return false;
486     }
487     range.consumeIncludingWhitespace();
488     return true;
489 }
490
491 static bool parseColorFunction(CSSParserTokenRange& range, RGBA32& result)
492 {
493     CSSValueID functionId = range.peek().functionId();
494     if (functionId < CSSValueRgb || functionId > CSSValueHsla)
495         return false;
496     CSSParserTokenRange colorRange = range;
497     if ((functionId <= CSSValueRgba && !parseRGBParameters(colorRange, result, functionId == CSSValueRgba))
498         || (functionId >= CSSValueHsl && !parseHSLParameters(colorRange, result, functionId == CSSValueHsla)))
499         return false;
500     range = colorRange;
501     return true;
502 }
503
504 RefPtr<CSSPrimitiveValue> consumeColor(CSSParserTokenRange& range, CSSParserMode cssParserMode, bool acceptQuirkyColors)
505 {
506     CSSValueID id = range.peek().id();
507     if (StyleColor::isColorKeyword(id)) {
508         if (!isValueAllowedInMode(id, cssParserMode))
509             return nullptr;
510         return consumeIdent(range);
511     }
512     RGBA32 color = Color::transparent;
513     if (!parseHexColor(range, color, acceptQuirkyColors) && !parseColorFunction(range, color))
514         return nullptr;
515     return CSSPrimitiveValue::create(Color(color));
516 }
517
518 static RefPtr<CSSPrimitiveValue> consumePositionComponent(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
519 {
520     if (range.peek().type() == IdentToken)
521         return consumeIdent<CSSValueLeft, CSSValueTop, CSSValueBottom, CSSValueRight, CSSValueCenter>(range);
522     return consumeLengthOrPercent(range, cssParserMode, ValueRangeAll, unitless);
523 }
524
525 static bool isHorizontalPositionKeywordOnly(const CSSPrimitiveValue& value)
526 {
527     return value.isValueID() && (value.valueID() == CSSValueLeft || value.valueID() == CSSValueRight);
528 }
529
530 static bool isVerticalPositionKeywordOnly(const CSSPrimitiveValue& value)
531 {
532     return value.isValueID() && (value.valueID() == CSSValueTop || value.valueID() == CSSValueBottom);
533 }
534
535 static void positionFromOneValue(CSSPrimitiveValue& value, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
536 {
537     bool valueAppliesToYAxisOnly = isVerticalPositionKeywordOnly(value);
538     resultX = &value;
539     resultY = CSSPrimitiveValue::createIdentifier(CSSValueCenter);
540     if (valueAppliesToYAxisOnly)
541         std::swap(resultX, resultY);
542 }
543
544 static bool positionFromTwoValues(CSSPrimitiveValue& value1, CSSPrimitiveValue& value2,
545     RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
546 {
547     bool mustOrderAsXY = isHorizontalPositionKeywordOnly(value1) || isVerticalPositionKeywordOnly(value2)
548         || !value1.isValueID() || !value2.isValueID();
549     bool mustOrderAsYX = isVerticalPositionKeywordOnly(value1) || isHorizontalPositionKeywordOnly(value2);
550     if (mustOrderAsXY && mustOrderAsYX)
551         return false;
552     resultX = &value1;
553     resultY = &value2;
554     if (mustOrderAsYX)
555         std::swap(resultX, resultY);
556     return true;
557 }
558
559     
560 template<typename... Args>
561 static Ref<CSSPrimitiveValue> createPrimitiveValuePair(Args&&... args)
562 {
563     return CSSValuePool::singleton().createValue(Pair::create(std::forward<Args>(args)...));
564 }
565
566 static bool positionFromThreeOrFourValues(CSSPrimitiveValue** values, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
567 {
568     CSSPrimitiveValue* center = nullptr;
569     for (int i = 0; values[i]; i++) {
570         CSSPrimitiveValue* currentValue = values[i];
571         if (!currentValue->isValueID())
572             return false;
573         CSSValueID id = currentValue->valueID();
574
575         if (id == CSSValueCenter) {
576             if (center)
577                 return false;
578             center = currentValue;
579             continue;
580         }
581
582         RefPtr<CSSPrimitiveValue> result;
583         if (values[i + 1] && !values[i + 1]->isValueID())
584             result = createPrimitiveValuePair(currentValue, values[++i]);
585         else
586             result = currentValue;
587
588         if (id == CSSValueLeft || id == CSSValueRight) {
589             if (resultX)
590                 return false;
591             resultX = result;
592         } else {
593             ASSERT(id == CSSValueTop || id == CSSValueBottom);
594             if (resultY)
595                 return false;
596             resultY = result;
597         }
598     }
599
600     if (center) {
601         ASSERT(resultX || resultY);
602         if (resultX && resultY)
603             return false;
604         if (!resultX)
605             resultX = center;
606         else
607             resultY = center;
608     }
609
610     ASSERT(resultX && resultY);
611     return true;
612 }
613
614 // FIXME: This may consume from the range upon failure. The background
615 // shorthand works around it, but we should just fix it here.
616 bool consumePosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
617 {
618     RefPtr<CSSPrimitiveValue> value1 = consumePositionComponent(range, cssParserMode, unitless);
619     if (!value1)
620         return false;
621
622     RefPtr<CSSPrimitiveValue> value2 = consumePositionComponent(range, cssParserMode, unitless);
623     if (!value2) {
624         positionFromOneValue(*value1, resultX, resultY);
625         return true;
626     }
627
628     RefPtr<CSSPrimitiveValue> value3 = consumePositionComponent(range, cssParserMode, unitless);
629     if (!value3)
630         return positionFromTwoValues(*value1, *value2, resultX, resultY);
631
632     RefPtr<CSSPrimitiveValue> value4 = consumePositionComponent(range, cssParserMode, unitless);
633     CSSPrimitiveValue* values[5];
634     values[0] = value1.get();
635     values[1] = value2.get();
636     values[2] = value3.get();
637     values[3] = value4.get();
638     values[4] = nullptr;
639     return positionFromThreeOrFourValues(values, resultX, resultY);
640 }
641
642 RefPtr<CSSPrimitiveValue> consumePosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless)
643 {
644     RefPtr<CSSPrimitiveValue> resultX;
645     RefPtr<CSSPrimitiveValue> resultY;
646     if (consumePosition(range, cssParserMode, unitless, resultX, resultY))
647         return createPrimitiveValuePair(resultX.releaseNonNull(), resultY.releaseNonNull());
648     return nullptr;
649 }
650
651 bool consumeOneOrTwoValuedPosition(CSSParserTokenRange& range, CSSParserMode cssParserMode, UnitlessQuirk unitless, RefPtr<CSSPrimitiveValue>& resultX, RefPtr<CSSPrimitiveValue>& resultY)
652 {
653     RefPtr<CSSPrimitiveValue> value1 = consumePositionComponent(range, cssParserMode, unitless);
654     if (!value1)
655         return false;
656     RefPtr<CSSPrimitiveValue> value2 = consumePositionComponent(range, cssParserMode, unitless);
657     if (!value2) {
658         positionFromOneValue(*value1, resultX, resultY);
659         return true;
660     }
661     return positionFromTwoValues(*value1, *value2, resultX, resultY);
662 }
663
664 // This should go away once we drop support for -webkit-gradient
665 static RefPtr<CSSPrimitiveValue> consumeDeprecatedGradientPoint(CSSParserTokenRange& args, bool horizontal)
666 {
667     if (args.peek().type() == IdentToken) {
668         if ((horizontal && consumeIdent<CSSValueLeft>(args)) || (!horizontal && consumeIdent<CSSValueTop>(args)))
669             return CSSPrimitiveValue::create(0., CSSPrimitiveValue::UnitTypes::CSS_PERCENTAGE);
670         if ((horizontal && consumeIdent<CSSValueRight>(args)) || (!horizontal && consumeIdent<CSSValueBottom>(args)))
671             return CSSPrimitiveValue::create(100., CSSPrimitiveValue::UnitTypes::CSS_PERCENTAGE);
672         if (consumeIdent<CSSValueCenter>(args))
673             return CSSPrimitiveValue::create(50., CSSPrimitiveValue::UnitTypes::CSS_PERCENTAGE);
674         return nullptr;
675     }
676     RefPtr<CSSPrimitiveValue> result = consumePercent(args, ValueRangeAll);
677     if (!result)
678         result = consumeNumber(args, ValueRangeAll);
679     return result;
680 }
681
682 // Used to parse colors for -webkit-gradient(...).
683 static RefPtr<CSSPrimitiveValue> consumeDeprecatedGradientStopColor(CSSParserTokenRange& args, CSSParserMode cssParserMode)
684 {
685     if (args.peek().id() == CSSValueCurrentcolor)
686         return nullptr;
687     return consumeColor(args, cssParserMode);
688 }
689
690 static bool consumeDeprecatedGradientColorStop(CSSParserTokenRange& range, CSSGradientColorStop& stop, CSSParserMode cssParserMode)
691 {
692     CSSValueID id = range.peek().functionId();
693     if (id != CSSValueFrom && id != CSSValueTo && id != CSSValueColorStop)
694         return false;
695
696     CSSParserTokenRange args = consumeFunction(range);
697     double position;
698     if (id == CSSValueFrom || id == CSSValueTo) {
699         position = (id == CSSValueFrom) ? 0 : 1;
700     } else {
701         ASSERT(id == CSSValueColorStop);
702         const CSSParserToken& arg = args.consumeIncludingWhitespace();
703         if (arg.type() == PercentageToken)
704             position = arg.numericValue() / 100.0;
705         else if (arg.type() == NumberToken)
706             position = arg.numericValue();
707         else
708             return false;
709
710         if (!consumeCommaIncludingWhitespace(args))
711             return false;
712     }
713
714     stop.m_position = CSSPrimitiveValue::create(position, CSSPrimitiveValue::UnitTypes::CSS_NUMBER);
715     stop.m_color = consumeDeprecatedGradientStopColor(args, cssParserMode);
716     return stop.m_color && args.atEnd();
717 }
718
719 static RefPtr<CSSValue> consumeDeprecatedGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode)
720 {
721     RefPtr<CSSGradientValue> result;
722     CSSValueID id = args.consumeIncludingWhitespace().id();
723     bool isDeprecatedRadialGradient = (id == CSSValueRadial);
724     if (isDeprecatedRadialGradient)
725         result = CSSRadialGradientValue::create(NonRepeating, CSSDeprecatedRadialGradient);
726     else if (id == CSSValueLinear)
727         result = CSSLinearGradientValue::create(NonRepeating, CSSDeprecatedLinearGradient);
728     if (!result || !consumeCommaIncludingWhitespace(args))
729         return nullptr;
730
731     RefPtr<CSSPrimitiveValue> point = consumeDeprecatedGradientPoint(args, true);
732     if (!point)
733         return nullptr;
734     result->setFirstX(WTFMove(point));
735     point = consumeDeprecatedGradientPoint(args, false);
736     if (!point)
737         return nullptr;
738     result->setFirstY(WTFMove(point));
739
740     if (!consumeCommaIncludingWhitespace(args))
741         return nullptr;
742
743     // For radial gradients only, we now expect a numeric radius.
744     if (isDeprecatedRadialGradient) {
745         RefPtr<CSSPrimitiveValue> radius = consumeNumber(args, ValueRangeAll);
746         if (!radius || !consumeCommaIncludingWhitespace(args))
747             return nullptr;
748         downcast<CSSRadialGradientValue>(result.get())->setFirstRadius(WTFMove(radius));
749     }
750
751     point = consumeDeprecatedGradientPoint(args, true);
752     if (!point)
753         return nullptr;
754     result->setSecondX(WTFMove(point));
755     point = consumeDeprecatedGradientPoint(args, false);
756     if (!point)
757         return nullptr;
758     result->setSecondY(WTFMove(point));
759
760     // For radial gradients only, we now expect the second radius.
761     if (isDeprecatedRadialGradient) {
762         if (!consumeCommaIncludingWhitespace(args))
763             return nullptr;
764         RefPtr<CSSPrimitiveValue> radius = consumeNumber(args, ValueRangeAll);
765         if (!radius)
766             return nullptr;
767         downcast<CSSRadialGradientValue>(result.get())->setSecondRadius(WTFMove(radius));
768     }
769
770     CSSGradientColorStop stop;
771     while (consumeCommaIncludingWhitespace(args)) {
772         if (!consumeDeprecatedGradientColorStop(args, stop, cssParserMode))
773             return nullptr;
774         result->addStop(stop);
775     }
776
777     return result;
778 }
779
780 static bool consumeGradientColorStops(CSSParserTokenRange& range, CSSParserMode cssParserMode, CSSGradientValue* gradient)
781 {
782     bool supportsColorHints = gradient->gradientType() == CSSLinearGradient || gradient->gradientType() == CSSRadialGradient;
783
784     // The first color stop cannot be a color hint.
785     bool previousStopWasColorHint = true;
786     do {
787         CSSGradientColorStop stop;
788         stop.m_color = consumeColor(range, cssParserMode);
789         // Two hints in a row are not allowed.
790         if (!stop.m_color && (!supportsColorHints || previousStopWasColorHint))
791             return false;
792         previousStopWasColorHint = !stop.m_color;
793         stop.m_position = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll);
794         if (!stop.m_color && !stop.m_position)
795             return false;
796         gradient->addStop(stop);
797     } while (consumeCommaIncludingWhitespace(range));
798
799     // The last color stop cannot be a color hint.
800     if (previousStopWasColorHint)
801         return false;
802
803     // Must have 2 or more stops to be valid.
804     return gradient->stopCount() >= 2;
805 }
806
807 static RefPtr<CSSValue> consumeDeprecatedRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating)
808 {
809     RefPtr<CSSRadialGradientValue> result = CSSRadialGradientValue::create(repeating, CSSPrefixedRadialGradient);
810     RefPtr<CSSPrimitiveValue> centerX;
811     RefPtr<CSSPrimitiveValue> centerY;
812     consumeOneOrTwoValuedPosition(args, cssParserMode, UnitlessQuirk::Forbid, centerX, centerY);
813     if ((centerX || centerY) && !consumeCommaIncludingWhitespace(args))
814         return nullptr;
815
816     result->setFirstX(WTFMove(centerX));
817     result->setFirstY(WTFMove(centerY));
818     result->setSecondX(WTFMove(centerX));
819     result->setSecondY(WTFMove(centerY));
820
821     RefPtr<CSSPrimitiveValue> shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args);
822     RefPtr<CSSPrimitiveValue> sizeKeyword = consumeIdent<CSSValueClosestSide, CSSValueClosestCorner, CSSValueFarthestSide, CSSValueFarthestCorner, CSSValueContain, CSSValueCover>(args);
823     if (!shape)
824         shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args);
825     result->setShape(WTFMove(shape));
826     result->setSizingBehavior(WTFMove(sizeKeyword));
827
828     // Or, two lengths or percentages
829     if (!shape && !sizeKeyword) {
830         RefPtr<CSSPrimitiveValue> horizontalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
831         RefPtr<CSSPrimitiveValue> verticalSize;
832         if (horizontalSize) {
833             verticalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
834             if (!verticalSize)
835                 return nullptr;
836             consumeCommaIncludingWhitespace(args);
837             result->setEndHorizontalSize(WTFMove(horizontalSize));
838             result->setEndVerticalSize(WTFMove(verticalSize));
839         }
840     } else {
841         consumeCommaIncludingWhitespace(args);
842     }
843     if (!consumeGradientColorStops(args, cssParserMode, result.get()))
844         return nullptr;
845
846     return result;
847 }
848
849 static RefPtr<CSSValue> consumeRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating)
850 {
851     RefPtr<CSSRadialGradientValue> result = CSSRadialGradientValue::create(repeating, CSSRadialGradient);
852
853     RefPtr<CSSPrimitiveValue> shape;
854     RefPtr<CSSPrimitiveValue> sizeKeyword;
855     RefPtr<CSSPrimitiveValue> horizontalSize;
856     RefPtr<CSSPrimitiveValue> verticalSize;
857
858     // First part of grammar, the size/shape clause:
859     // [ circle || <length> ] |
860     // [ ellipse || [ <length> | <percentage> ]{2} ] |
861     // [ [ circle | ellipse] || <size-keyword> ]
862     for (int i = 0; i < 3; ++i) {
863         if (args.peek().type() == IdentToken) {
864             CSSValueID id = args.peek().id();
865             if (id == CSSValueCircle || id == CSSValueEllipse) {
866                 if (shape)
867                     return nullptr;
868                 shape = consumeIdent(args);
869             } else if (id == CSSValueClosestSide || id == CSSValueClosestCorner || id == CSSValueFarthestSide || id == CSSValueFarthestCorner) {
870                 if (sizeKeyword)
871                     return nullptr;
872                 sizeKeyword = consumeIdent(args);
873             } else {
874                 break;
875             }
876         } else {
877             RefPtr<CSSPrimitiveValue> center = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
878             if (!center)
879                 break;
880             if (horizontalSize)
881                 return nullptr;
882             horizontalSize = center;
883             center = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll);
884             if (center) {
885                 verticalSize = center;
886                 ++i;
887             }
888         }
889     }
890
891     // You can specify size as a keyword or a length/percentage, not both.
892     if (sizeKeyword && horizontalSize)
893         return nullptr;
894     // Circles must have 0 or 1 lengths.
895     if (shape && shape->valueID() == CSSValueCircle && verticalSize)
896         return nullptr;
897     // Ellipses must have 0 or 2 length/percentages.
898     if (shape && shape->valueID() == CSSValueEllipse && horizontalSize && !verticalSize)
899         return nullptr;
900     // If there's only one size, it must be a length.
901     if (!verticalSize && horizontalSize && horizontalSize->isPercentage())
902         return nullptr;
903     if ((horizontalSize && horizontalSize->isCalculatedPercentageWithLength())
904         || (verticalSize && verticalSize->isCalculatedPercentageWithLength()))
905         return nullptr;
906
907     result->setShape(WTFMove(shape));
908     result->setSizingBehavior(WTFMove(sizeKeyword));
909     result->setEndHorizontalSize(WTFMove(horizontalSize));
910     result->setEndVerticalSize(WTFMove(verticalSize));
911
912     RefPtr<CSSPrimitiveValue> centerX;
913     RefPtr<CSSPrimitiveValue> centerY;
914     if (args.peek().id() == CSSValueAt) {
915         args.consumeIncludingWhitespace();
916         consumePosition(args, cssParserMode, UnitlessQuirk::Forbid, centerX, centerY);
917         if (!(centerX && centerY))
918             return nullptr;
919         
920         result->setFirstX(WTFMove(centerX));
921         result->setFirstY(WTFMove(centerY));
922         
923         // Right now, CSS radial gradients have the same start and end centers.
924         result->setSecondX(WTFMove(centerX));
925         result->setSecondY(WTFMove(centerY));
926     }
927
928     if ((shape || sizeKeyword || horizontalSize || centerX || centerY) && !consumeCommaIncludingWhitespace(args))
929         return nullptr;
930     if (!consumeGradientColorStops(args, cssParserMode, result.get()))
931         return nullptr;
932     return result;
933 }
934
935 static RefPtr<CSSValue> consumeLinearGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating, CSSGradientType gradientType)
936 {
937     RefPtr<CSSLinearGradientValue> result = CSSLinearGradientValue::create(repeating, gradientType);
938
939     bool expectComma = true;
940     RefPtr<CSSPrimitiveValue> angle = consumeAngle(args, cssParserMode, UnitlessQuirk::Forbid);
941     if (angle)
942         result->setAngle(angle.releaseNonNull());
943     else if (gradientType == CSSPrefixedLinearGradient || consumeIdent<CSSValueTo>(args)) {
944         RefPtr<CSSPrimitiveValue> endX = consumeIdent<CSSValueLeft, CSSValueRight>(args);
945         RefPtr<CSSPrimitiveValue> endY = consumeIdent<CSSValueBottom, CSSValueTop>(args);
946         if (!endX && !endY) {
947             if (gradientType == CSSLinearGradient)
948                 return nullptr;
949             endY = CSSPrimitiveValue::createIdentifier(CSSValueTop);
950             expectComma = false;
951         } else if (!endX) {
952             endX = consumeIdent<CSSValueLeft, CSSValueRight>(args);
953         }
954
955         result->setFirstX(WTFMove(endX));
956         result->setFirstY(WTFMove(endY));
957     } else {
958         expectComma = false;
959     }
960
961     if (expectComma && !consumeCommaIncludingWhitespace(args))
962         return nullptr;
963     if (!consumeGradientColorStops(args, cssParserMode, result.get()))
964         return nullptr;
965     return result;
966 }
967
968 RefPtr<CSSValue> consumeImageOrNone(CSSParserTokenRange& range, CSSParserContext context)
969 {
970     if (range.peek().id() == CSSValueNone)
971         return consumeIdent(range);
972     return consumeImage(range, context);
973 }
974
975 static RefPtr<CSSValue> consumeCrossFade(CSSParserTokenRange& args, CSSParserContext context)
976 {
977     RefPtr<CSSValue> fromImageValue = consumeImageOrNone(args, context);
978     if (!fromImageValue || !consumeCommaIncludingWhitespace(args))
979         return nullptr;
980     RefPtr<CSSValue> toImageValue = consumeImageOrNone(args, context);
981     if (!toImageValue || !consumeCommaIncludingWhitespace(args))
982         return nullptr;
983
984     RefPtr<CSSPrimitiveValue> percentage;
985     const CSSParserToken& percentageArg = args.consumeIncludingWhitespace();
986     if (percentageArg.type() == PercentageToken)
987         percentage = CSSPrimitiveValue::create(clampTo<double>(percentageArg.numericValue() / 100, 0, 1), CSSPrimitiveValue::UnitTypes::CSS_NUMBER);
988     else if (percentageArg.type() == NumberToken)
989         percentage = CSSPrimitiveValue::create(clampTo<double>(percentageArg.numericValue(), 0, 1), CSSPrimitiveValue::UnitTypes::CSS_NUMBER);
990
991     if (!percentage)
992         return nullptr;
993     return CSSCrossfadeValue::create(fromImageValue.releaseNonNull(), toImageValue.releaseNonNull(), percentage.releaseNonNull());
994 }
995
996 static RefPtr<CSSValue> consumeGeneratedImage(CSSParserTokenRange& range, CSSParserContext context)
997 {
998     CSSValueID id = range.peek().functionId();
999     CSSParserTokenRange rangeCopy = range;
1000     CSSParserTokenRange args = consumeFunction(rangeCopy);
1001     RefPtr<CSSValue> result;
1002     if (id == CSSValueRadialGradient)
1003         result = consumeRadialGradient(args, context.mode, NonRepeating);
1004     else if (id == CSSValueRepeatingRadialGradient)
1005         result = consumeRadialGradient(args, context.mode, Repeating);
1006     else if (id == CSSValueWebkitLinearGradient)
1007         result = consumeLinearGradient(args, context.mode, NonRepeating, CSSPrefixedLinearGradient);
1008     else if (id == CSSValueWebkitRepeatingLinearGradient)
1009         result = consumeLinearGradient(args, context.mode, Repeating, CSSPrefixedLinearGradient);
1010     else if (id == CSSValueRepeatingLinearGradient)
1011         result = consumeLinearGradient(args, context.mode, Repeating, CSSLinearGradient);
1012     else if (id == CSSValueLinearGradient)
1013         result = consumeLinearGradient(args, context.mode, NonRepeating, CSSLinearGradient);
1014     else if (id == CSSValueWebkitGradient)
1015         result = consumeDeprecatedGradient(args, context.mode);
1016     else if (id == CSSValueWebkitRadialGradient)
1017         result = consumeDeprecatedRadialGradient(args, context.mode, NonRepeating);
1018     else if (id == CSSValueWebkitRepeatingRadialGradient)
1019         result = consumeDeprecatedRadialGradient(args, context.mode, Repeating);
1020     else if (id == CSSValueWebkitCrossFade)
1021         result = consumeCrossFade(args, context);
1022
1023     if (!result || !args.atEnd())
1024         return nullptr;
1025     range = rangeCopy;
1026     return result;
1027 }
1028
1029 static RefPtr<CSSValue> consumeImageSet(CSSParserTokenRange& range, const CSSParserContext& context)
1030 {
1031     CSSParserTokenRange rangeCopy = range;
1032     CSSParserTokenRange args = consumeFunction(rangeCopy);
1033     RefPtr<CSSImageSetValue> imageSet = CSSImageSetValue::create();
1034     do {
1035         AtomicString urlValue = consumeUrlAsStringView(args).toAtomicString();
1036         if (urlValue.isNull())
1037             return nullptr;
1038
1039         RefPtr<CSSValue> image = CSSImageValue::create(completeURL(context, urlValue));
1040         imageSet->append(image.releaseNonNull());
1041
1042         const CSSParserToken& token = args.consumeIncludingWhitespace();
1043         if (token.type() != DimensionToken)
1044             return nullptr;
1045         if (token.value() != "x")
1046             return nullptr;
1047         ASSERT(token.unitType() == CSSPrimitiveValue::UnitTypes::CSS_UNKNOWN);
1048         double imageScaleFactor = token.numericValue();
1049         if (imageScaleFactor <= 0)
1050             return nullptr;
1051         imageSet->append(CSSPrimitiveValue::create(imageScaleFactor, CSSPrimitiveValue::UnitTypes::CSS_NUMBER));
1052     } while (consumeCommaIncludingWhitespace(args));
1053     if (!args.atEnd())
1054         return nullptr;
1055     range = rangeCopy;
1056     return imageSet;
1057 }
1058
1059 static bool isGeneratedImage(CSSValueID id)
1060 {
1061     return id == CSSValueLinearGradient || id == CSSValueRadialGradient
1062         || id == CSSValueRepeatingLinearGradient || id == CSSValueRepeatingRadialGradient
1063         || id == CSSValueWebkitLinearGradient || id == CSSValueWebkitRadialGradient
1064         || id == CSSValueWebkitRepeatingLinearGradient || id == CSSValueWebkitRepeatingRadialGradient
1065         || id == CSSValueWebkitGradient || id == CSSValueWebkitCrossFade || id == CSSValuePaint;
1066 }
1067
1068 RefPtr<CSSValue> consumeImage(CSSParserTokenRange& range, CSSParserContext context, ConsumeGeneratedImage generatedImage)
1069 {
1070     AtomicString uri = consumeUrlAsStringView(range).toAtomicString();
1071     if (!uri.isNull())
1072         return CSSImageValue::create(completeURL(context, uri));
1073     if (range.peek().type() == FunctionToken) {
1074         CSSValueID id = range.peek().functionId();
1075         if (id == CSSValueWebkitImageSet)
1076             return consumeImageSet(range, context);
1077         if (generatedImage == ConsumeGeneratedImage::Allow && isGeneratedImage(id))
1078             return consumeGeneratedImage(range, context);
1079     }
1080     return nullptr;
1081 }
1082
1083 } // namespace CSSPropertyParserHelpers
1084
1085 } // namespace WebCore