| 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
|
|
|