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