Index: third_party/WebKit/Source/core/css/parser/CSSPropertyParserHelpers.cpp |
diff --git a/third_party/WebKit/Source/core/css/parser/CSSPropertyParserHelpers.cpp b/third_party/WebKit/Source/core/css/parser/CSSPropertyParserHelpers.cpp |
index 40202f650e10a4a060301a61258c614503d9657a..d383afc447c6a0771de7fe248e1134040189c2ab 100644 |
--- a/third_party/WebKit/Source/core/css/parser/CSSPropertyParserHelpers.cpp |
+++ b/third_party/WebKit/Source/core/css/parser/CSSPropertyParserHelpers.cpp |
@@ -6,8 +6,14 @@ |
#include "core/css/CSSCalculationValue.h" |
#include "core/css/CSSColorValue.h" |
+#include "core/css/CSSCrossfadeValue.h" |
+#include "core/css/CSSGradientValue.h" |
+#include "core/css/CSSImageSetValue.h" |
+#include "core/css/CSSImageValue.h" |
+#include "core/css/CSSPaintValue.h" |
#include "core/css/CSSStringValue.h" |
#include "core/css/CSSValuePair.h" |
+#include "core/frame/UseCounter.h" |
namespace blink { |
@@ -613,6 +619,457 @@ bool consumeOneOrTwoValuedPosition(CSSParserTokenRange& range, CSSParserMode css |
return positionFromTwoValues(value1, value2, resultX, resultY); |
} |
+// This should go away once we drop support for -webkit-gradient |
+static CSSPrimitiveValue* consumeDeprecatedGradientPoint(CSSParserTokenRange& args, bool horizontal) |
+{ |
+ if (args.peek().type() == IdentToken) { |
+ if ((horizontal && consumeIdent<CSSValueLeft>(args)) || (!horizontal && consumeIdent<CSSValueTop>(args))) |
+ return CSSPrimitiveValue::create(0., CSSPrimitiveValue::UnitType::Percentage); |
+ if ((horizontal && consumeIdent<CSSValueRight>(args)) || (!horizontal && consumeIdent<CSSValueBottom>(args))) |
+ return CSSPrimitiveValue::create(100., CSSPrimitiveValue::UnitType::Percentage); |
+ if (consumeIdent<CSSValueCenter>(args)) |
+ return CSSPrimitiveValue::create(50., CSSPrimitiveValue::UnitType::Percentage); |
+ return nullptr; |
+ } |
+ CSSPrimitiveValue* result = consumePercent(args, ValueRangeAll); |
+ if (!result) |
+ result = consumeNumber(args, ValueRangeAll); |
+ return result; |
+} |
+ |
+// Used to parse colors for -webkit-gradient(...). |
+static CSSValue* consumeDeprecatedGradientStopColor(CSSParserTokenRange& args, CSSParserMode cssParserMode) |
+{ |
+ if (args.peek().id() == CSSValueCurrentcolor) |
+ return nullptr; |
+ return consumeColor(args, cssParserMode); |
+} |
+ |
+static bool consumeDeprecatedGradientColorStop(CSSParserTokenRange& range, CSSGradientColorStop& stop, CSSParserMode cssParserMode) |
+{ |
+ CSSValueID id = range.peek().functionId(); |
+ if (id != CSSValueFrom && id != CSSValueTo && id != CSSValueColorStop) |
+ return false; |
+ |
+ CSSParserTokenRange args = consumeFunction(range); |
+ double position; |
+ if (id == CSSValueFrom || id == CSSValueTo) { |
+ position = (id == CSSValueFrom) ? 0 : 1; |
+ } else { |
+ DCHECK(id == CSSValueColorStop); |
+ const CSSParserToken& arg = args.consumeIncludingWhitespace(); |
+ if (arg.type() == PercentageToken) |
+ position = arg.numericValue() / 100.0; |
+ else if (arg.type() == NumberToken) |
+ position = arg.numericValue(); |
+ else |
+ return false; |
+ |
+ if (!consumeCommaIncludingWhitespace(args)) |
+ return false; |
+ } |
+ |
+ stop.m_position = CSSPrimitiveValue::create(position, CSSPrimitiveValue::UnitType::Number); |
+ stop.m_color = consumeDeprecatedGradientStopColor(args, cssParserMode); |
+ return stop.m_color && args.atEnd(); |
+} |
+ |
+static CSSValue* consumeDeprecatedGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode) |
+{ |
+ CSSGradientValue* result = nullptr; |
+ CSSValueID id = args.consumeIncludingWhitespace().id(); |
+ bool isDeprecatedRadialGradient = (id == CSSValueRadial); |
+ if (isDeprecatedRadialGradient) |
+ result = CSSRadialGradientValue::create(NonRepeating, CSSDeprecatedRadialGradient); |
+ else if (id == CSSValueLinear) |
+ result = CSSLinearGradientValue::create(NonRepeating, CSSDeprecatedLinearGradient); |
+ if (!result || !consumeCommaIncludingWhitespace(args)) |
+ return nullptr; |
+ |
+ CSSPrimitiveValue* point = consumeDeprecatedGradientPoint(args, true); |
+ if (!point) |
+ return nullptr; |
+ result->setFirstX(point); |
+ point = consumeDeprecatedGradientPoint(args, false); |
+ if (!point) |
+ return nullptr; |
+ result->setFirstY(point); |
+ |
+ if (!consumeCommaIncludingWhitespace(args)) |
+ return nullptr; |
+ |
+ // For radial gradients only, we now expect a numeric radius. |
+ if (isDeprecatedRadialGradient) { |
+ CSSPrimitiveValue* radius = consumeNumber(args, ValueRangeAll); |
+ if (!radius || !consumeCommaIncludingWhitespace(args)) |
+ return nullptr; |
+ toCSSRadialGradientValue(result)->setFirstRadius(radius); |
+ } |
+ |
+ point = consumeDeprecatedGradientPoint(args, true); |
+ if (!point) |
+ return nullptr; |
+ result->setSecondX(point); |
+ point = consumeDeprecatedGradientPoint(args, false); |
+ if (!point) |
+ return nullptr; |
+ result->setSecondY(point); |
+ |
+ // For radial gradients only, we now expect the second radius. |
+ if (isDeprecatedRadialGradient) { |
+ if (!consumeCommaIncludingWhitespace(args)) |
+ return nullptr; |
+ CSSPrimitiveValue* radius = consumeNumber(args, ValueRangeAll); |
+ if (!radius) |
+ return nullptr; |
+ toCSSRadialGradientValue(result)->setSecondRadius(radius); |
+ } |
+ |
+ CSSGradientColorStop stop; |
+ while (consumeCommaIncludingWhitespace(args)) { |
+ if (!consumeDeprecatedGradientColorStop(args, stop, cssParserMode)) |
+ return nullptr; |
+ result->addStop(stop); |
+ } |
+ |
+ return result; |
+} |
+ |
+static bool consumeGradientColorStops(CSSParserTokenRange& range, CSSParserMode cssParserMode, CSSGradientValue* gradient) |
+{ |
+ bool supportsColorHints = gradient->gradientType() == CSSLinearGradient || gradient->gradientType() == CSSRadialGradient; |
+ |
+ // The first color stop cannot be a color hint. |
+ bool previousStopWasColorHint = true; |
+ do { |
+ CSSGradientColorStop stop; |
+ stop.m_color = consumeColor(range, cssParserMode); |
+ // Two hints in a row are not allowed. |
+ if (!stop.m_color && (!supportsColorHints || previousStopWasColorHint)) |
+ return false; |
+ previousStopWasColorHint = !stop.m_color; |
+ stop.m_position = consumeLengthOrPercent(range, cssParserMode, ValueRangeAll); |
+ if (!stop.m_color && !stop.m_position) |
+ return false; |
+ gradient->addStop(stop); |
+ } while (consumeCommaIncludingWhitespace(range)); |
+ |
+ // The last color stop cannot be a color hint. |
+ if (previousStopWasColorHint) |
+ return false; |
+ |
+ // Must have 2 or more stops to be valid. |
+ return gradient->stopCount() >= 2; |
+} |
+ |
+static CSSValue* consumeDeprecatedRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating) |
+{ |
+ CSSRadialGradientValue* result = CSSRadialGradientValue::create(repeating, CSSPrefixedRadialGradient); |
+ CSSValue* centerX = nullptr; |
+ CSSValue* centerY = nullptr; |
+ consumeOneOrTwoValuedPosition(args, cssParserMode, UnitlessQuirk::Forbid, centerX, centerY); |
+ if ((centerX || centerY) && !consumeCommaIncludingWhitespace(args)) |
+ return nullptr; |
+ |
+ result->setFirstX(toCSSPrimitiveValue(centerX)); |
+ result->setSecondX(toCSSPrimitiveValue(centerX)); |
+ result->setFirstY(toCSSPrimitiveValue(centerY)); |
+ result->setSecondY(toCSSPrimitiveValue(centerY)); |
+ |
+ CSSPrimitiveValue* shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args); |
+ CSSPrimitiveValue* sizeKeyword = consumeIdent<CSSValueClosestSide, CSSValueClosestCorner, CSSValueFarthestSide, CSSValueFarthestCorner, CSSValueContain, CSSValueCover>(args); |
+ if (!shape) |
+ shape = consumeIdent<CSSValueCircle, CSSValueEllipse>(args); |
+ result->setShape(shape); |
+ result->setSizingBehavior(sizeKeyword); |
+ |
+ // Or, two lengths or percentages |
+ if (!shape && !sizeKeyword) { |
+ CSSPrimitiveValue* horizontalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll); |
+ CSSPrimitiveValue* verticalSize = nullptr; |
+ if (horizontalSize) { |
+ verticalSize = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll); |
+ if (!verticalSize) |
+ return nullptr; |
+ consumeCommaIncludingWhitespace(args); |
+ result->setEndHorizontalSize(horizontalSize); |
+ result->setEndVerticalSize(verticalSize); |
+ } |
+ } else { |
+ consumeCommaIncludingWhitespace(args); |
+ } |
+ if (!consumeGradientColorStops(args, cssParserMode, result)) |
+ return nullptr; |
+ |
+ return result; |
+} |
+ |
+static CSSValue* consumeRadialGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating) |
+{ |
+ CSSRadialGradientValue* result = CSSRadialGradientValue::create(repeating, CSSRadialGradient); |
+ |
+ CSSPrimitiveValue* shape = nullptr; |
+ CSSPrimitiveValue* sizeKeyword = nullptr; |
+ CSSPrimitiveValue* horizontalSize = nullptr; |
+ CSSPrimitiveValue* verticalSize = nullptr; |
+ |
+ // First part of grammar, the size/shape clause: |
+ // [ circle || <length> ] | |
+ // [ ellipse || [ <length> | <percentage> ]{2} ] | |
+ // [ [ circle | ellipse] || <size-keyword> ] |
+ for (int i = 0; i < 3; ++i) { |
+ if (args.peek().type() == IdentToken) { |
+ CSSValueID id = args.peek().id(); |
+ if (id == CSSValueCircle || id == CSSValueEllipse) { |
+ if (shape) |
+ return nullptr; |
+ shape = consumeIdent(args); |
+ } else if (id == CSSValueClosestSide || id == CSSValueClosestCorner || id == CSSValueFarthestSide || id == CSSValueFarthestCorner) { |
+ if (sizeKeyword) |
+ return nullptr; |
+ sizeKeyword = consumeIdent(args); |
+ } else { |
+ break; |
+ } |
+ } else { |
+ CSSPrimitiveValue* center = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll); |
+ if (!center) |
+ break; |
+ if (horizontalSize) |
+ return nullptr; |
+ horizontalSize = center; |
+ center = consumeLengthOrPercent(args, cssParserMode, ValueRangeAll); |
+ if (center) { |
+ verticalSize = center; |
+ ++i; |
+ } |
+ } |
+ } |
+ |
+ // You can specify size as a keyword or a length/percentage, not both. |
+ if (sizeKeyword && horizontalSize) |
+ return nullptr; |
+ // Circles must have 0 or 1 lengths. |
+ if (shape && shape->getValueID() == CSSValueCircle && verticalSize) |
+ return nullptr; |
+ // Ellipses must have 0 or 2 length/percentages. |
+ if (shape && shape->getValueID() == CSSValueEllipse && horizontalSize && !verticalSize) |
+ return nullptr; |
+ // If there's only one size, it must be a length. |
+ if (!verticalSize && horizontalSize && horizontalSize->isPercentage()) |
+ return nullptr; |
+ if ((horizontalSize && horizontalSize->isCalculatedPercentageWithLength()) |
+ || (verticalSize && verticalSize->isCalculatedPercentageWithLength())) |
+ return nullptr; |
+ |
+ result->setShape(shape); |
+ result->setSizingBehavior(sizeKeyword); |
+ result->setEndHorizontalSize(horizontalSize); |
+ result->setEndVerticalSize(verticalSize); |
+ |
+ CSSValue* centerX = nullptr; |
+ CSSValue* centerY = nullptr; |
+ if (args.peek().id() == CSSValueAt) { |
+ args.consumeIncludingWhitespace(); |
+ consumePosition(args, cssParserMode, UnitlessQuirk::Forbid, centerX, centerY); |
+ if (!(centerX && centerY)) |
+ return nullptr; |
+ result->setFirstX(centerX); |
+ result->setFirstY(centerY); |
+ // Right now, CSS radial gradients have the same start and end centers. |
+ result->setSecondX(centerX); |
+ result->setSecondY(centerY); |
+ } |
+ |
+ if ((shape || sizeKeyword || horizontalSize || centerX || centerY) && !consumeCommaIncludingWhitespace(args)) |
+ return nullptr; |
+ if (!consumeGradientColorStops(args, cssParserMode, result)) |
+ return nullptr; |
+ return result; |
+} |
+ |
+static CSSValue* consumeLinearGradient(CSSParserTokenRange& args, CSSParserMode cssParserMode, CSSGradientRepeat repeating, CSSGradientType gradientType) |
+{ |
+ CSSLinearGradientValue* result = CSSLinearGradientValue::create(repeating, gradientType); |
+ |
+ bool expectComma = true; |
+ CSSPrimitiveValue* angle = consumeAngle(args); |
+ if (angle) { |
+ result->setAngle(angle); |
+ } else if (gradientType == CSSPrefixedLinearGradient || consumeIdent<CSSValueTo>(args)) { |
+ CSSPrimitiveValue* endX = consumeIdent<CSSValueLeft, CSSValueRight>(args); |
+ CSSPrimitiveValue* endY = consumeIdent<CSSValueBottom, CSSValueTop>(args); |
+ if (!endX && !endY) { |
+ if (gradientType == CSSLinearGradient) |
+ return nullptr; |
+ endY = CSSPrimitiveValue::createIdentifier(CSSValueTop); |
+ expectComma = false; |
+ } else if (!endX) { |
+ endX = consumeIdent<CSSValueLeft, CSSValueRight>(args); |
+ } |
+ |
+ result->setFirstX(endX); |
+ result->setFirstY(endY); |
+ } else { |
+ expectComma = false; |
+ } |
+ |
+ if (expectComma && !consumeCommaIncludingWhitespace(args)) |
+ return nullptr; |
+ if (!consumeGradientColorStops(args, cssParserMode, result)) |
+ return nullptr; |
+ return result; |
+} |
+ |
+CSSValue* consumeImageOrNone(CSSParserTokenRange& range, CSSParserContext context) |
+{ |
+ if (range.peek().id() == CSSValueNone) |
+ return consumeIdent(range); |
+ return consumeImage(range, context); |
+} |
+ |
+static CSSValue* consumeCrossFade(CSSParserTokenRange& args, CSSParserContext context) |
+{ |
+ CSSValue* fromImageValue = consumeImageOrNone(args, context); |
+ if (!fromImageValue || !consumeCommaIncludingWhitespace(args)) |
+ return nullptr; |
+ CSSValue* toImageValue = consumeImageOrNone(args, context); |
+ if (!toImageValue || !consumeCommaIncludingWhitespace(args)) |
+ return nullptr; |
+ |
+ CSSPrimitiveValue* percentage = nullptr; |
+ const CSSParserToken& percentageArg = args.consumeIncludingWhitespace(); |
+ if (percentageArg.type() == PercentageToken) |
+ percentage = CSSPrimitiveValue::create(clampTo<double>(percentageArg.numericValue() / 100, 0, 1), CSSPrimitiveValue::UnitType::Number); |
+ else if (percentageArg.type() == NumberToken) |
+ percentage = CSSPrimitiveValue::create(clampTo<double>(percentageArg.numericValue(), 0, 1), CSSPrimitiveValue::UnitType::Number); |
+ |
+ if (!percentage) |
+ return nullptr; |
+ return CSSCrossfadeValue::create(fromImageValue, toImageValue, percentage); |
+} |
+ |
+static CSSValue* consumePaint(CSSParserTokenRange& args, CSSParserContext context) |
+{ |
+ DCHECK(RuntimeEnabledFeatures::cssPaintAPIEnabled()); |
+ |
+ CSSCustomIdentValue* name = consumeCustomIdent(args); |
+ if (!name) |
+ return nullptr; |
+ |
+ return CSSPaintValue::create(name); |
+} |
+ |
+static CSSValue* consumeGeneratedImage(CSSParserTokenRange& range, CSSParserContext context) |
+{ |
+ CSSValueID id = range.peek().functionId(); |
+ CSSParserTokenRange rangeCopy = range; |
+ CSSParserTokenRange args = consumeFunction(rangeCopy); |
+ CSSValue* result = nullptr; |
+ if (id == CSSValueRadialGradient) { |
+ result = consumeRadialGradient(args, context.mode(), NonRepeating); |
+ } else if (id == CSSValueRepeatingRadialGradient) { |
+ result = consumeRadialGradient(args, context.mode(), Repeating); |
+ } else if (id == CSSValueWebkitLinearGradient) { |
+ // FIXME: This should send a deprecation message. |
+ if (context.useCounter()) |
+ context.useCounter()->count(UseCounter::DeprecatedWebKitLinearGradient); |
+ result = consumeLinearGradient(args, context.mode(), NonRepeating, CSSPrefixedLinearGradient); |
+ } else if (id == CSSValueWebkitRepeatingLinearGradient) { |
+ // FIXME: This should send a deprecation message. |
+ if (context.useCounter()) |
+ context.useCounter()->count(UseCounter::DeprecatedWebKitRepeatingLinearGradient); |
+ result = consumeLinearGradient(args, context.mode(), Repeating, CSSPrefixedLinearGradient); |
+ } else if (id == CSSValueRepeatingLinearGradient) { |
+ result = consumeLinearGradient(args, context.mode(), Repeating, CSSLinearGradient); |
+ } else if (id == CSSValueLinearGradient) { |
+ result = consumeLinearGradient(args, context.mode(), NonRepeating, CSSLinearGradient); |
+ } else if (id == CSSValueWebkitGradient) { |
+ // FIXME: This should send a deprecation message. |
+ if (context.useCounter()) |
+ context.useCounter()->count(UseCounter::DeprecatedWebKitGradient); |
+ result = consumeDeprecatedGradient(args, context.mode()); |
+ } else if (id == CSSValueWebkitRadialGradient) { |
+ // FIXME: This should send a deprecation message. |
+ if (context.useCounter()) |
+ context.useCounter()->count(UseCounter::DeprecatedWebKitRadialGradient); |
+ result = consumeDeprecatedRadialGradient(args, context.mode(), NonRepeating); |
+ } else if (id == CSSValueWebkitRepeatingRadialGradient) { |
+ if (context.useCounter()) |
+ context.useCounter()->count(UseCounter::DeprecatedWebKitRepeatingRadialGradient); |
+ result = consumeDeprecatedRadialGradient(args, context.mode(), Repeating); |
+ } else if (id == CSSValueWebkitCrossFade) { |
+ result = consumeCrossFade(args, context); |
+ } else if (id == CSSValuePaint) { |
+ result = RuntimeEnabledFeatures::cssPaintAPIEnabled() ? consumePaint(args, context) : nullptr; |
+ } |
+ if (!result || !args.atEnd()) |
+ return nullptr; |
+ range = rangeCopy; |
+ return result; |
+} |
+ |
+static CSSValue* createCSSImageValueWithReferrer(const AtomicString& rawValue, const CSSParserContext& context) |
+{ |
+ CSSValue* imageValue = CSSImageValue::create(rawValue, context.completeURL(rawValue)); |
+ toCSSImageValue(imageValue)->setReferrer(context.referrer()); |
+ return imageValue; |
+} |
+ |
+static CSSValue* consumeImageSet(CSSParserTokenRange& range, const CSSParserContext& context) |
+{ |
+ CSSParserTokenRange rangeCopy = range; |
+ CSSParserTokenRange args = consumeFunction(rangeCopy); |
+ CSSImageSetValue* imageSet = CSSImageSetValue::create(); |
+ do { |
+ AtomicString urlValue = consumeUrl(args).toAtomicString(); |
+ if (urlValue.isNull()) |
+ return nullptr; |
+ |
+ CSSValue* image = createCSSImageValueWithReferrer(urlValue, context); |
+ imageSet->append(*image); |
+ |
+ const CSSParserToken& token = args.consumeIncludingWhitespace(); |
+ if (token.type() != DimensionToken) |
+ return nullptr; |
+ if (token.value() != "x") |
+ return nullptr; |
+ DCHECK(token.unitType() == CSSPrimitiveValue::UnitType::Unknown); |
+ double imageScaleFactor = token.numericValue(); |
+ if (imageScaleFactor <= 0) |
+ return nullptr; |
+ imageSet->append(*CSSPrimitiveValue::create(imageScaleFactor, CSSPrimitiveValue::UnitType::Number)); |
+ } while (consumeCommaIncludingWhitespace(args)); |
+ if (!args.atEnd()) |
+ return nullptr; |
+ range = rangeCopy; |
+ return imageSet; |
+} |
+ |
+static bool isGeneratedImage(CSSValueID id) |
+{ |
+ return id == CSSValueLinearGradient || id == CSSValueRadialGradient |
+ || id == CSSValueRepeatingLinearGradient || id == CSSValueRepeatingRadialGradient |
+ || id == CSSValueWebkitLinearGradient || id == CSSValueWebkitRadialGradient |
+ || id == CSSValueWebkitRepeatingLinearGradient || id == CSSValueWebkitRepeatingRadialGradient |
+ || id == CSSValueWebkitGradient || id == CSSValueWebkitCrossFade || id == CSSValuePaint; |
+} |
+ |
+CSSValue* consumeImage(CSSParserTokenRange& range, CSSParserContext context, ConsumeGeneratedImage generatedImage) |
+{ |
+ AtomicString uri = consumeUrl(range).toAtomicString(); |
+ if (!uri.isNull()) |
+ return createCSSImageValueWithReferrer(uri, context); |
+ if (range.peek().type() == FunctionToken) { |
+ CSSValueID id = range.peek().functionId(); |
+ if (id == CSSValueWebkitImageSet) |
+ return consumeImageSet(range, context); |
+ if (generatedImage == ConsumeGeneratedImage::Allow && isGeneratedImage(id)) |
+ return consumeGeneratedImage(range, context); |
+ } |
+ return nullptr; |
+} |
+ |
} // namespace CSSPropertyParserHelpers |
} // namespace blink |