blob: 13630c397bee92cd0c5e08216c28c56bebdfa355 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/css/properties/CSSParsingUtils.h"
#include "core/CSSPropertyNames.h"
#include "core/CSSValueKeywords.h"
#include "core/StylePropertyShorthand.h"
#include "core/css/CSSBasicShapeValues.h"
#include "core/css/CSSBorderImage.h"
#include "core/css/CSSContentDistributionValue.h"
#include "core/css/CSSCustomIdentValue.h"
#include "core/css/CSSFontFamilyValue.h"
#include "core/css/CSSFontFeatureValue.h"
#include "core/css/CSSFontStyleRangeValue.h"
#include "core/css/CSSFunctionValue.h"
#include "core/css/CSSGridAutoRepeatValue.h"
#include "core/css/CSSGridLineNamesValue.h"
#include "core/css/CSSGridTemplateAreasValue.h"
#include "core/css/CSSIdentifierValue.h"
#include "core/css/CSSInitialValue.h"
#include "core/css/CSSPathValue.h"
#include "core/css/CSSPrimitiveValue.h"
#include "core/css/CSSRayValue.h"
#include "core/css/CSSShadowValue.h"
#include "core/css/CSSTimingFunctionValue.h"
#include "core/css/CSSURIValue.h"
#include "core/css/CSSValue.h"
#include "core/css/CSSValueList.h"
#include "core/css/CSSValuePair.h"
#include "core/css/parser/CSSParserContext.h"
#include "core/css/parser/CSSParserIdioms.h"
#include "core/css/parser/CSSParserLocalContext.h"
#include "core/css/parser/CSSParserToken.h"
#include "core/css/parser/CSSParserTokenRange.h"
#include "core/css/parser/CSSPropertyParserHelpers.h"
#include "core/css/properties/CSSProperty.h"
#include "core/frame/UseCounter.h"
#include "core/frame/WebFeature.h"
#include "core/svg/SVGParsingError.h"
#include "core/svg/SVGPathUtilities.h"
#include "platform/Length.h"
#include "platform/animation/TimingFunction.h"
#include "platform/runtime_enabled_features.h"
#include "platform/wtf/text/StringBuilder.h"
namespace blink {
using namespace cssvalue;
namespace CSSParsingUtils {
namespace {
bool IsLeftOrRightKeyword(CSSValueID id) {
return CSSPropertyParserHelpers::IdentMatches<CSSValueLeft, CSSValueRight>(
id);
}
bool IsAutoOrNormalOrStretch(CSSValueID id) {
return CSSPropertyParserHelpers::IdentMatches<CSSValueAuto, CSSValueNormal,
CSSValueStretch>(id);
}
bool IsContentDistributionKeyword(CSSValueID id) {
return CSSPropertyParserHelpers::IdentMatches<
CSSValueSpaceBetween, CSSValueSpaceAround, CSSValueSpaceEvenly,
CSSValueStretch>(id);
}
bool IsOverflowKeyword(CSSValueID id) {
return CSSPropertyParserHelpers::IdentMatches<CSSValueUnsafe, CSSValueSafe>(
id);
}
CSSIdentifierValue* ConsumeOverflowPositionKeyword(CSSParserTokenRange& range) {
return IsOverflowKeyword(range.Peek().Id())
? CSSPropertyParserHelpers::ConsumeIdent(range)
: nullptr;
}
bool IsBaselineKeyword(CSSValueID id) {
return CSSPropertyParserHelpers::IdentMatches<CSSValueFirst, CSSValueLast,
CSSValueBaseline>(id);
}
CSSValueID GetBaselineKeyword(CSSValue& value) {
if (!value.IsValuePair()) {
DCHECK(ToCSSIdentifierValue(value).GetValueID() == CSSValueBaseline);
return CSSValueBaseline;
}
DCHECK(ToCSSIdentifierValue(ToCSSValuePair(value).First()).GetValueID() ==
CSSValueLast);
DCHECK(ToCSSIdentifierValue(ToCSSValuePair(value).Second()).GetValueID() ==
CSSValueBaseline);
return CSSValueLastBaseline;
}
CSSValue* ConsumeBaselineKeyword(CSSParserTokenRange& range) {
CSSIdentifierValue* preference =
CSSPropertyParserHelpers::ConsumeIdent<CSSValueFirst, CSSValueLast>(
range);
CSSIdentifierValue* baseline =
CSSPropertyParserHelpers::ConsumeIdent<CSSValueBaseline>(range);
if (!baseline)
return nullptr;
if (preference && preference->GetValueID() == CSSValueLast) {
return CSSValuePair::Create(preference, baseline,
CSSValuePair::kDropIdenticalValues);
}
return baseline;
}
CSSValue* ConsumeSteps(CSSParserTokenRange& range) {
DCHECK_EQ(range.Peek().FunctionId(), CSSValueSteps);
CSSParserTokenRange range_copy = range;
CSSParserTokenRange args =
CSSPropertyParserHelpers::ConsumeFunction(range_copy);
CSSPrimitiveValue* steps =
CSSPropertyParserHelpers::ConsumePositiveInteger(args);
if (!steps)
return nullptr;
StepsTimingFunction::StepPosition position =
StepsTimingFunction::StepPosition::END;
if (CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(args)) {
switch (args.ConsumeIncludingWhitespace().Id()) {
case CSSValueMiddle:
if (!RuntimeEnabledFeatures::WebAnimationsAPIEnabled())
return nullptr;
position = StepsTimingFunction::StepPosition::MIDDLE;
break;
case CSSValueStart:
position = StepsTimingFunction::StepPosition::START;
break;
case CSSValueEnd:
position = StepsTimingFunction::StepPosition::END;
break;
default:
return nullptr;
}
}
if (!args.AtEnd())
return nullptr;
range = range_copy;
return CSSStepsTimingFunctionValue::Create(steps->GetIntValue(), position);
}
CSSValue* ConsumeFrames(CSSParserTokenRange& range) {
DCHECK_EQ(range.Peek().FunctionId(), CSSValueFrames);
CSSParserTokenRange range_copy = range;
CSSParserTokenRange args =
CSSPropertyParserHelpers::ConsumeFunction(range_copy);
CSSPrimitiveValue* frames =
CSSPropertyParserHelpers::ConsumePositiveInteger(args);
if (!frames)
return nullptr;
int frames_int = frames->GetIntValue();
if (frames_int <= 1)
return nullptr;
if (!args.AtEnd())
return nullptr;
range = range_copy;
return CSSFramesTimingFunctionValue::Create(frames_int);
}
CSSValue* ConsumeCubicBezier(CSSParserTokenRange& range) {
DCHECK_EQ(range.Peek().FunctionId(), CSSValueCubicBezier);
CSSParserTokenRange range_copy = range;
CSSParserTokenRange args =
CSSPropertyParserHelpers::ConsumeFunction(range_copy);
double x1, y1, x2, y2;
if (CSSPropertyParserHelpers::ConsumeNumberRaw(args, x1) && x1 >= 0 &&
x1 <= 1 &&
CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(args) &&
CSSPropertyParserHelpers::ConsumeNumberRaw(args, y1) &&
CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(args) &&
CSSPropertyParserHelpers::ConsumeNumberRaw(args, x2) && x2 >= 0 &&
x2 <= 1 &&
CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(args) &&
CSSPropertyParserHelpers::ConsumeNumberRaw(args, y2) && args.AtEnd()) {
range = range_copy;
return CSSCubicBezierTimingFunctionValue::Create(x1, y1, x2, y2);
}
return nullptr;
}
CSSIdentifierValue* ConsumeBorderImageRepeatKeyword(
CSSParserTokenRange& range) {
return CSSPropertyParserHelpers::ConsumeIdent<CSSValueStretch, CSSValueRepeat,
CSSValueSpace, CSSValueRound>(
range);
}
bool ConsumeCSSValueId(CSSParserTokenRange& range, CSSValueID& value) {
CSSIdentifierValue* keyword = CSSPropertyParserHelpers::ConsumeIdent(range);
if (!keyword || !range.AtEnd())
return false;
value = keyword->GetValueID();
return true;
}
CSSValue* ConsumeShapeRadius(CSSParserTokenRange& args,
CSSParserMode css_parser_mode) {
if (CSSPropertyParserHelpers::IdentMatches<CSSValueClosestSide,
CSSValueFarthestSide>(
args.Peek().Id()))
return CSSPropertyParserHelpers::ConsumeIdent(args);
return CSSPropertyParserHelpers::ConsumeLengthOrPercent(
args, css_parser_mode, kValueRangeNonNegative);
}
CSSBasicShapeCircleValue* ConsumeBasicShapeCircle(
CSSParserTokenRange& args,
const CSSParserContext& context) {
// spec: https://drafts.csswg.org/css-shapes/#supported-basic-shapes
// circle( [<shape-radius>]? [at <position>]? )
CSSBasicShapeCircleValue* shape = CSSBasicShapeCircleValue::Create();
if (CSSValue* radius = ConsumeShapeRadius(args, context.Mode()))
shape->SetRadius(radius);
if (CSSPropertyParserHelpers::ConsumeIdent<CSSValueAt>(args)) {
CSSValue* center_x = nullptr;
CSSValue* center_y = nullptr;
if (!ConsumePosition(
args, context, CSSPropertyParserHelpers::UnitlessQuirk::kForbid,
WebFeature::kThreeValuedPositionBasicShape, center_x, center_y))
return nullptr;
shape->SetCenterX(center_x);
shape->SetCenterY(center_y);
}
return shape;
}
CSSBasicShapeEllipseValue* ConsumeBasicShapeEllipse(
CSSParserTokenRange& args,
const CSSParserContext& context) {
// spec: https://drafts.csswg.org/css-shapes/#supported-basic-shapes
// ellipse( [<shape-radius>{2}]? [at <position>]? )
CSSBasicShapeEllipseValue* shape = CSSBasicShapeEllipseValue::Create();
WebFeature feature = WebFeature::kBasicShapeEllipseNoRadius;
if (CSSValue* radius_x = ConsumeShapeRadius(args, context.Mode())) {
shape->SetRadiusX(radius_x);
feature = WebFeature::kBasicShapeEllipseOneRadius;
if (CSSValue* radius_y = ConsumeShapeRadius(args, context.Mode())) {
shape->SetRadiusY(radius_y);
feature = WebFeature::kBasicShapeEllipseTwoRadius;
}
}
if (CSSPropertyParserHelpers::ConsumeIdent<CSSValueAt>(args)) {
CSSValue* center_x = nullptr;
CSSValue* center_y = nullptr;
if (!ConsumePosition(
args, context, CSSPropertyParserHelpers::UnitlessQuirk::kForbid,
WebFeature::kThreeValuedPositionBasicShape, center_x, center_y))
return nullptr;
shape->SetCenterX(center_x);
shape->SetCenterY(center_y);
}
context.Count(feature);
return shape;
}
CSSBasicShapePolygonValue* ConsumeBasicShapePolygon(
CSSParserTokenRange& args,
const CSSParserContext& context) {
CSSBasicShapePolygonValue* shape = CSSBasicShapePolygonValue::Create();
if (CSSPropertyParserHelpers::IdentMatches<CSSValueEvenodd, CSSValueNonzero>(
args.Peek().Id())) {
shape->SetWindRule(args.ConsumeIncludingWhitespace().Id() == CSSValueEvenodd
? RULE_EVENODD
: RULE_NONZERO);
if (!CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(args))
return nullptr;
}
do {
CSSPrimitiveValue* x_length =
CSSPropertyParserHelpers::ConsumeLengthOrPercent(args, context.Mode(),
kValueRangeAll);
if (!x_length)
return nullptr;
CSSPrimitiveValue* y_length =
CSSPropertyParserHelpers::ConsumeLengthOrPercent(args, context.Mode(),
kValueRangeAll);
if (!y_length)
return nullptr;
shape->AppendPoint(x_length, y_length);
} while (CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(args));
return shape;
}
CSSBasicShapeInsetValue* ConsumeBasicShapeInset(
CSSParserTokenRange& args,
const CSSParserContext& context) {
CSSBasicShapeInsetValue* shape = CSSBasicShapeInsetValue::Create();
CSSPrimitiveValue* top = CSSPropertyParserHelpers::ConsumeLengthOrPercent(
args, context.Mode(), kValueRangeAll);
if (!top)
return nullptr;
CSSPrimitiveValue* right = CSSPropertyParserHelpers::ConsumeLengthOrPercent(
args, context.Mode(), kValueRangeAll);
CSSPrimitiveValue* bottom = nullptr;
CSSPrimitiveValue* left = nullptr;
if (right) {
bottom = CSSPropertyParserHelpers::ConsumeLengthOrPercent(
args, context.Mode(), kValueRangeAll);
if (bottom) {
left = CSSPropertyParserHelpers::ConsumeLengthOrPercent(
args, context.Mode(), kValueRangeAll);
}
}
if (left)
shape->UpdateShapeSize4Values(top, right, bottom, left);
else if (bottom)
shape->UpdateShapeSize3Values(top, right, bottom);
else if (right)
shape->UpdateShapeSize2Values(top, right);
else
shape->UpdateShapeSize1Value(top);
if (CSSPropertyParserHelpers::ConsumeIdent<CSSValueRound>(args)) {
CSSValue* horizontal_radii[4] = {nullptr};
CSSValue* vertical_radii[4] = {nullptr};
if (!ConsumeRadii(horizontal_radii, vertical_radii, args, context.Mode(),
false))
return nullptr;
shape->SetTopLeftRadius(
CSSValuePair::Create(horizontal_radii[0], vertical_radii[0],
CSSValuePair::kDropIdenticalValues));
shape->SetTopRightRadius(
CSSValuePair::Create(horizontal_radii[1], vertical_radii[1],
CSSValuePair::kDropIdenticalValues));
shape->SetBottomRightRadius(
CSSValuePair::Create(horizontal_radii[2], vertical_radii[2],
CSSValuePair::kDropIdenticalValues));
shape->SetBottomLeftRadius(
CSSValuePair::Create(horizontal_radii[3], vertical_radii[3],
CSSValuePair::kDropIdenticalValues));
}
return shape;
}
bool ConsumeNumbers(CSSParserTokenRange& args,
CSSFunctionValue*& transform_value,
unsigned number_of_arguments) {
do {
CSSValue* parsed_value =
CSSPropertyParserHelpers::ConsumeNumber(args, kValueRangeAll);
if (!parsed_value)
return false;
transform_value->Append(*parsed_value);
if (--number_of_arguments &&
!CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(args)) {
return false;
}
} while (number_of_arguments);
return true;
}
bool ConsumePerspective(CSSParserTokenRange& args,
const CSSParserContext& context,
CSSFunctionValue*& transform_value,
bool use_legacy_parsing) {
CSSPrimitiveValue* parsed_value = CSSPropertyParserHelpers::ConsumeLength(
args, context.Mode(), kValueRangeNonNegative);
if (!parsed_value && use_legacy_parsing) {
double perspective;
if (!CSSPropertyParserHelpers::ConsumeNumberRaw(args, perspective) ||
perspective < 0) {
return false;
}
context.Count(WebFeature::kUnitlessPerspectiveInTransformProperty);
parsed_value = CSSPrimitiveValue::Create(
perspective, CSSPrimitiveValue::UnitType::kPixels);
}
if (!parsed_value)
return false;
transform_value->Append(*parsed_value);
return true;
}
bool ConsumeTranslate3d(CSSParserTokenRange& args,
CSSParserMode css_parser_mode,
CSSFunctionValue*& transform_value) {
unsigned number_of_arguments = 2;
CSSValue* parsed_value = nullptr;
do {
parsed_value = CSSPropertyParserHelpers::ConsumeLengthOrPercent(
args, css_parser_mode, kValueRangeAll);
if (!parsed_value)
return false;
transform_value->Append(*parsed_value);
if (!CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(args))
return false;
} while (--number_of_arguments);
parsed_value = CSSPropertyParserHelpers::ConsumeLength(args, css_parser_mode,
kValueRangeAll);
if (!parsed_value)
return false;
transform_value->Append(*parsed_value);
return true;
}
CSSValue* ConsumeTransformValue(CSSParserTokenRange& range,
const CSSParserContext& context,
bool use_legacy_parsing) {
CSSValueID function_id = range.Peek().FunctionId();
if (function_id == CSSValueInvalid)
return nullptr;
CSSParserTokenRange args = CSSPropertyParserHelpers::ConsumeFunction(range);
if (args.AtEnd())
return nullptr;
CSSFunctionValue* transform_value = CSSFunctionValue::Create(function_id);
CSSValue* parsed_value = nullptr;
switch (function_id) {
case CSSValueRotate:
case CSSValueRotateX:
case CSSValueRotateY:
case CSSValueRotateZ:
case CSSValueSkewX:
case CSSValueSkewY:
case CSSValueSkew:
parsed_value = CSSPropertyParserHelpers::ConsumeAngle(
args, &context, WebFeature::kUnitlessZeroAngleTransform);
if (!parsed_value)
return nullptr;
if (function_id == CSSValueSkew &&
CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(args)) {
transform_value->Append(*parsed_value);
parsed_value = CSSPropertyParserHelpers::ConsumeAngle(
args, &context, WebFeature::kUnitlessZeroAngleTransform);
if (!parsed_value)
return nullptr;
}
break;
case CSSValueScaleX:
case CSSValueScaleY:
case CSSValueScaleZ:
case CSSValueScale:
parsed_value =
CSSPropertyParserHelpers::ConsumeNumber(args, kValueRangeAll);
if (!parsed_value)
return nullptr;
if (function_id == CSSValueScale &&
CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(args)) {
transform_value->Append(*parsed_value);
parsed_value =
CSSPropertyParserHelpers::ConsumeNumber(args, kValueRangeAll);
if (!parsed_value)
return nullptr;
}
break;
case CSSValuePerspective:
if (!ConsumePerspective(args, context, transform_value,
use_legacy_parsing)) {
return nullptr;
}
break;
case CSSValueTranslateX:
case CSSValueTranslateY:
case CSSValueTranslate:
parsed_value = CSSPropertyParserHelpers::ConsumeLengthOrPercent(
args, context.Mode(), kValueRangeAll);
if (!parsed_value)
return nullptr;
if (function_id == CSSValueTranslate &&
CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(args)) {
transform_value->Append(*parsed_value);
parsed_value = CSSPropertyParserHelpers::ConsumeLengthOrPercent(
args, context.Mode(), kValueRangeAll);
if (!parsed_value)
return nullptr;
}
break;
case CSSValueTranslateZ:
parsed_value = CSSPropertyParserHelpers::ConsumeLength(
args, context.Mode(), kValueRangeAll);
break;
case CSSValueMatrix:
case CSSValueMatrix3d:
if (!ConsumeNumbers(args, transform_value,
(function_id == CSSValueMatrix3d) ? 16 : 6)) {
return nullptr;
}
break;
case CSSValueScale3d:
if (!ConsumeNumbers(args, transform_value, 3))
return nullptr;
break;
case CSSValueRotate3d:
if (!ConsumeNumbers(args, transform_value, 3) ||
!CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
parsed_value = CSSPropertyParserHelpers::ConsumeAngle(
args, &context, WebFeature::kUnitlessZeroAngleTransform);
if (!parsed_value)
return nullptr;
break;
case CSSValueTranslate3d:
if (!ConsumeTranslate3d(args, context.Mode(), transform_value))
return nullptr;
break;
default:
return nullptr;
}
if (parsed_value)
transform_value->Append(*parsed_value);
if (!args.AtEnd())
return nullptr;
return transform_value;
}
} // namespace
bool IsSelfPositionKeyword(CSSValueID id) {
return CSSPropertyParserHelpers::IdentMatches<
CSSValueStart, CSSValueEnd, CSSValueCenter, CSSValueSelfStart,
CSSValueSelfEnd, CSSValueFlexStart, CSSValueFlexEnd>(id);
}
bool IsSelfPositionOrLeftOrRightKeyword(CSSValueID id) {
return IsSelfPositionKeyword(id) || IsLeftOrRightKeyword(id);
}
bool IsContentPositionKeyword(CSSValueID id) {
return CSSPropertyParserHelpers::IdentMatches<
CSSValueStart, CSSValueEnd, CSSValueCenter, CSSValueFlexStart,
CSSValueFlexEnd>(id);
}
bool IsContentPositionOrLeftOrRightKeyword(CSSValueID id) {
return IsContentPositionKeyword(id) || IsLeftOrRightKeyword(id);
}
CSSValue* ConsumeSelfPositionOverflowPosition(
CSSParserTokenRange& range,
IsPositionKeyword is_position_keyword) {
DCHECK(is_position_keyword);
CSSValueID id = range.Peek().Id();
if (IsAutoOrNormalOrStretch(id))
return CSSPropertyParserHelpers::ConsumeIdent(range);
if (IsBaselineKeyword(id))
return ConsumeBaselineKeyword(range);
CSSIdentifierValue* overflow_position = ConsumeOverflowPositionKeyword(range);
if (!is_position_keyword(range.Peek().Id()))
return nullptr;
CSSIdentifierValue* self_position =
CSSPropertyParserHelpers::ConsumeIdent(range);
if (overflow_position) {
return CSSValuePair::Create(overflow_position, self_position,
CSSValuePair::kDropIdenticalValues);
}
return self_position;
}
CSSValue* ConsumeSimplifiedItemPosition(CSSParserTokenRange& range,
IsPositionKeyword is_position_keyword) {
DCHECK(is_position_keyword);
CSSValueID id = range.Peek().Id();
if (IsAutoOrNormalOrStretch(id) || is_position_keyword(id))
return CSSPropertyParserHelpers::ConsumeIdent(range);
if (IsBaselineKeyword(id))
return ConsumeBaselineKeyword(range);
return nullptr;
}
CSSValue* ConsumeContentDistributionOverflowPosition(
CSSParserTokenRange& range,
IsPositionKeyword is_position_keyword) {
DCHECK(is_position_keyword);
CSSValueID id = range.Peek().Id();
if (CSSPropertyParserHelpers::IdentMatches<CSSValueNormal>(id)) {
return CSSContentDistributionValue::Create(
CSSValueInvalid, range.ConsumeIncludingWhitespace().Id(),
CSSValueInvalid);
}
if (IsBaselineKeyword(id)) {
CSSValue* baseline = ConsumeBaselineKeyword(range);
if (!baseline)
return nullptr;
return CSSContentDistributionValue::Create(
CSSValueInvalid, GetBaselineKeyword(*baseline), CSSValueInvalid);
}
if (IsContentDistributionKeyword(id)) {
return CSSContentDistributionValue::Create(
range.ConsumeIncludingWhitespace().Id(), CSSValueInvalid,
CSSValueInvalid);
}
CSSValueID overflow = IsOverflowKeyword(id)
? range.ConsumeIncludingWhitespace().Id()
: CSSValueInvalid;
if (is_position_keyword(range.Peek().Id())) {
return CSSContentDistributionValue::Create(
CSSValueInvalid, range.ConsumeIncludingWhitespace().Id(), overflow);
}
return nullptr;
}
CSSValue* ConsumeSimplifiedContentPosition(
CSSParserTokenRange& range,
IsPositionKeyword is_position_keyword) {
DCHECK(is_position_keyword);
CSSValueID id = range.Peek().Id();
if (CSSPropertyParserHelpers::IdentMatches<CSSValueNormal>(id) ||
is_position_keyword(id)) {
return CSSContentDistributionValue::Create(
CSSValueInvalid, range.ConsumeIncludingWhitespace().Id(),
CSSValueInvalid);
}
if (IsBaselineKeyword(id)) {
CSSValue* baseline = ConsumeBaselineKeyword(range);
if (!baseline)
return nullptr;
return CSSContentDistributionValue::Create(
CSSValueInvalid, GetBaselineKeyword(*baseline), CSSValueInvalid);
}
if (IsContentDistributionKeyword(id)) {
return CSSContentDistributionValue::Create(
range.ConsumeIncludingWhitespace().Id(), CSSValueInvalid,
CSSValueInvalid);
}
return nullptr;
}
CSSValue* ConsumeAnimationIterationCount(CSSParserTokenRange& range) {
if (range.Peek().Id() == CSSValueInfinite)
return CSSPropertyParserHelpers::ConsumeIdent(range);
return CSSPropertyParserHelpers::ConsumeNumber(range, kValueRangeNonNegative);
}
CSSValue* ConsumeAnimationName(CSSParserTokenRange& range,
const CSSParserContext& context,
bool allow_quoted_name) {
if (range.Peek().Id() == CSSValueNone)
return CSSPropertyParserHelpers::ConsumeIdent(range);
if (allow_quoted_name && range.Peek().GetType() == kStringToken) {
// Legacy support for strings in prefixed animations.
context.Count(WebFeature::kQuotedAnimationName);
const CSSParserToken& token = range.ConsumeIncludingWhitespace();
if (EqualIgnoringASCIICase(token.Value(), "none"))
return CSSIdentifierValue::Create(CSSValueNone);
return CSSCustomIdentValue::Create(token.Value().ToAtomicString());
}
return CSSPropertyParserHelpers::ConsumeCustomIdent(range);
}
CSSValue* ConsumeAnimationTimingFunction(CSSParserTokenRange& range) {
CSSValueID id = range.Peek().Id();
if (id == CSSValueEase || id == CSSValueLinear || id == CSSValueEaseIn ||
id == CSSValueEaseOut || id == CSSValueEaseInOut ||
id == CSSValueStepStart || id == CSSValueStepEnd ||
id == CSSValueStepMiddle)
return CSSPropertyParserHelpers::ConsumeIdent(range);
CSSValueID function = range.Peek().FunctionId();
if (function == CSSValueSteps)
return ConsumeSteps(range);
if (RuntimeEnabledFeatures::FramesTimingFunctionEnabled() &&
function == CSSValueFrames) {
return ConsumeFrames(range);
}
if (function == CSSValueCubicBezier)
return ConsumeCubicBezier(range);
return nullptr;
}
bool ConsumeAnimationShorthand(
const StylePropertyShorthand& shorthand,
HeapVector<Member<CSSValueList>, kMaxNumAnimationLonghands>& longhands,
ConsumeAnimationItemValue consumeLonghandItem,
CSSParserTokenRange& range,
const CSSParserContext& context,
bool use_legacy_parsing) {
DCHECK(consumeLonghandItem);
const unsigned longhand_count = shorthand.length();
DCHECK_LE(longhand_count, kMaxNumAnimationLonghands);
for (size_t i = 0; i < longhand_count; ++i)
longhands[i] = CSSValueList::CreateCommaSeparated();
do {
bool parsed_longhand[kMaxNumAnimationLonghands] = {false};
do {
bool found_property = false;
for (size_t i = 0; i < longhand_count; ++i) {
if (parsed_longhand[i])
continue;
CSSValue* value =
consumeLonghandItem(shorthand.properties()[i]->PropertyID(), range,
context, use_legacy_parsing);
if (value) {
parsed_longhand[i] = true;
found_property = true;
longhands[i]->Append(*value);
break;
}
}
if (!found_property)
return false;
} while (!range.AtEnd() && range.Peek().GetType() != kCommaToken);
// TODO(timloh): This will make invalid longhands, see crbug.com/386459
for (size_t i = 0; i < longhand_count; ++i) {
if (!parsed_longhand[i])
longhands[i]->Append(*CSSInitialValue::Create());
parsed_longhand[i] = false;
}
} while (CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(range));
return true;
}
void AddBackgroundValue(CSSValue*& list, CSSValue* value) {
if (list) {
if (!list->IsBaseValueList()) {
CSSValue* first_value = list;
list = CSSValueList::CreateCommaSeparated();
ToCSSValueList(list)->Append(*first_value);
}
ToCSSValueList(list)->Append(*value);
} else {
// To conserve memory we don't actually wrap a single value in a list.
list = value;
}
}
CSSValue* ConsumeBackgroundAttachment(CSSParserTokenRange& range) {
return CSSPropertyParserHelpers::ConsumeIdent<CSSValueScroll, CSSValueFixed,
CSSValueLocal>(range);
}
CSSValue* ConsumeBackgroundBlendMode(CSSParserTokenRange& range) {
CSSValueID id = range.Peek().Id();
if (id == CSSValueNormal || id == CSSValueOverlay ||
(id >= CSSValueMultiply && id <= CSSValueLuminosity))
return CSSPropertyParserHelpers::ConsumeIdent(range);
return nullptr;
}
CSSValue* ConsumeBackgroundBox(CSSParserTokenRange& range) {
return CSSPropertyParserHelpers::ConsumeIdent<
CSSValueBorderBox, CSSValuePaddingBox, CSSValueContentBox>(range);
}
CSSValue* ConsumeBackgroundComposite(CSSParserTokenRange& range) {
return CSSPropertyParserHelpers::ConsumeIdentRange(range, CSSValueClear,
CSSValuePlusLighter);
}
CSSValue* ConsumeMaskSourceType(CSSParserTokenRange& range) {
DCHECK(RuntimeEnabledFeatures::CSSMaskSourceTypeEnabled());
return CSSPropertyParserHelpers::ConsumeIdent<CSSValueAuto, CSSValueAlpha,
CSSValueLuminance>(range);
}
CSSValue* ConsumeBackgroundSize(CSSParserTokenRange& range,
CSSParserMode css_parser_mode,
ParsingStyle parsing_style) {
if (CSSPropertyParserHelpers::IdentMatches<CSSValueContain, CSSValueCover>(
range.Peek().Id())) {
return CSSPropertyParserHelpers::ConsumeIdent(range);
}
CSSValue* horizontal =
CSSPropertyParserHelpers::ConsumeIdent<CSSValueAuto>(range);
if (!horizontal) {
horizontal = CSSPropertyParserHelpers::ConsumeLengthOrPercent(
range, css_parser_mode, kValueRangeAll,
CSSPropertyParserHelpers::UnitlessQuirk::kForbid);
}
CSSValue* vertical = nullptr;
if (!range.AtEnd()) {
if (range.Peek().Id() == CSSValueAuto) { // `auto' is the default
range.ConsumeIncludingWhitespace();
} else {
vertical = CSSPropertyParserHelpers::ConsumeLengthOrPercent(
range, css_parser_mode, kValueRangeAll,
CSSPropertyParserHelpers::UnitlessQuirk::kForbid);
}
} else if (parsing_style == ParsingStyle::kLegacy) {
// Legacy syntax: "-webkit-background-size: 10px" is equivalent to
// "background-size: 10px 10px".
vertical = horizontal;
}
if (!vertical)
return horizontal;
return CSSValuePair::Create(horizontal, vertical,
CSSValuePair::kKeepIdenticalValues);
}
bool ConsumeBackgroundPosition(CSSParserTokenRange& range,
const CSSParserContext& context,
CSSPropertyParserHelpers::UnitlessQuirk unitless,
CSSValue*& result_x,
CSSValue*& result_y) {
do {
CSSValue* position_x = nullptr;
CSSValue* position_y = nullptr;
if (!CSSPropertyParserHelpers::ConsumePosition(
range, context, unitless,
WebFeature::kThreeValuedPositionBackground, position_x, position_y))
return false;
AddBackgroundValue(result_x, position_x);
AddBackgroundValue(result_y, position_y);
} while (CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(range));
return true;
}
CSSValue* ConsumePrefixedBackgroundBox(CSSParserTokenRange& range,
AllowTextValue allow_text_value) {
// The values 'border', 'padding' and 'content' are deprecated and do not
// apply to the version of the property that has the -webkit- prefix removed.
if (CSSValue* value = CSSPropertyParserHelpers::ConsumeIdentRange(
range, CSSValueBorder, CSSValuePaddingBox))
return value;
if (allow_text_value == AllowTextValue::kAllow &&
range.Peek().Id() == CSSValueText)
return CSSPropertyParserHelpers::ConsumeIdent(range);
return nullptr;
}
CSSValue* ParseBackgroundBox(CSSParserTokenRange& range,
const CSSParserLocalContext& local_context) {
// This is legacy behavior that does not match spec, see crbug.com/604023
if (local_context.UseAliasParsing()) {
return CSSPropertyParserHelpers::ConsumeCommaSeparatedList(
ConsumePrefixedBackgroundBox, range, AllowTextValue::kAllow);
}
return CSSPropertyParserHelpers::ConsumeCommaSeparatedList(
ConsumeBackgroundBox, range);
}
CSSValue* ParseBackgroundOrMaskSize(
CSSParserTokenRange& range,
const CSSParserContext& context,
const CSSParserLocalContext& local_context) {
return CSSPropertyParserHelpers::ConsumeCommaSeparatedList(
ConsumeBackgroundSize, range, context.Mode(),
local_context.UseAliasParsing() ? ParsingStyle::kLegacy
: ParsingStyle::kNotLegacy);
}
namespace {
CSSValue* ConsumeBackgroundComponent(CSSPropertyID resolved_property,
CSSParserTokenRange& range,
const CSSParserContext& context) {
switch (resolved_property) {
case CSSPropertyBackgroundClip:
return ConsumeBackgroundBox(range);
case CSSPropertyBackgroundAttachment:
return ConsumeBackgroundAttachment(range);
case CSSPropertyBackgroundOrigin:
return ConsumeBackgroundBox(range);
case CSSPropertyBackgroundImage:
case CSSPropertyWebkitMaskImage:
return CSSPropertyParserHelpers::ConsumeImageOrNone(range, &context);
case CSSPropertyBackgroundPositionX:
case CSSPropertyWebkitMaskPositionX:
return ConsumePositionLonghand<CSSValueLeft, CSSValueRight>(
range, context.Mode());
case CSSPropertyBackgroundPositionY:
case CSSPropertyWebkitMaskPositionY:
return ConsumePositionLonghand<CSSValueTop, CSSValueBottom>(
range, context.Mode());
case CSSPropertyBackgroundSize:
case CSSPropertyWebkitMaskSize:
return ConsumeBackgroundSize(range, context.Mode(),
ParsingStyle::kNotLegacy);
case CSSPropertyBackgroundColor:
return CSSPropertyParserHelpers::ConsumeColor(range, context.Mode());
case CSSPropertyWebkitMaskClip:
return ConsumePrefixedBackgroundBox(range, AllowTextValue::kAllow);
case CSSPropertyWebkitMaskOrigin:
return ConsumePrefixedBackgroundBox(range, AllowTextValue::kForbid);
default:
break;
};
return nullptr;
}
} // namespace
// Note: this assumes y properties (e.g. background-position-y) follow the x
// properties in the shorthand array.
// TODO(jiameng): this is used by background and -webkit-mask, hence we
// need local_context as an input that contains shorthand id. We will consider
// remove local_context as an input after
// (i). StylePropertyShorthand is refactored and
// (ii). we split parsing logic of background and -webkit-mask into
// different property classes.
bool ParseBackgroundOrMask(bool important,
CSSParserTokenRange& range,
const CSSParserContext& context,
const CSSParserLocalContext& local_context,
HeapVector<CSSPropertyValue, 256>& properties) {
CSSPropertyID shorthand_id = local_context.CurrentShorthand();
DCHECK(shorthand_id == CSSPropertyBackground ||
shorthand_id == CSSPropertyWebkitMask);
const StylePropertyShorthand& shorthand =
shorthand_id == CSSPropertyBackground ? backgroundShorthand()
: webkitMaskShorthand();
const unsigned longhand_count = shorthand.length();
CSSValue* longhands[10] = {nullptr};
DCHECK_LE(longhand_count, 10u);
bool implicit = false;
do {
bool parsed_longhand[10] = {false};
CSSValue* origin_value = nullptr;
do {
bool found_property = false;
for (size_t i = 0; i < longhand_count; ++i) {
if (parsed_longhand[i])
continue;
CSSValue* value = nullptr;
CSSValue* value_y = nullptr;
const CSSProperty& property = *shorthand.properties()[i];
if (property.IDEquals(CSSPropertyBackgroundRepeatX) ||
property.IDEquals(CSSPropertyWebkitMaskRepeatX)) {
ConsumeRepeatStyleComponent(range, value, value_y, implicit);
} else if (property.IDEquals(CSSPropertyBackgroundPositionX) ||
property.IDEquals(CSSPropertyWebkitMaskPositionX)) {
if (!CSSPropertyParserHelpers::ConsumePosition(
range, context,
CSSPropertyParserHelpers::UnitlessQuirk::kForbid,
WebFeature::kThreeValuedPositionBackground, value, value_y))
continue;
} else if (property.IDEquals(CSSPropertyBackgroundSize) ||
property.IDEquals(CSSPropertyWebkitMaskSize)) {
if (!CSSPropertyParserHelpers::ConsumeSlashIncludingWhitespace(range))
continue;
value = ConsumeBackgroundSize(range, context.Mode(),
ParsingStyle::kNotLegacy);
if (!value ||
!parsed_longhand[i - 1]) // Position must have been
// parsed in the current layer.
{
return false;
}
} else if (property.IDEquals(CSSPropertyBackgroundPositionY) ||
property.IDEquals(CSSPropertyBackgroundRepeatY) ||
property.IDEquals(CSSPropertyWebkitMaskPositionY) ||
property.IDEquals(CSSPropertyWebkitMaskRepeatY)) {
continue;
} else {
value =
ConsumeBackgroundComponent(property.PropertyID(), range, context);
}
if (value) {
if (property.IDEquals(CSSPropertyBackgroundOrigin) ||
property.IDEquals(CSSPropertyWebkitMaskOrigin)) {
origin_value = value;
}
parsed_longhand[i] = true;
found_property = true;
AddBackgroundValue(longhands[i], value);
if (value_y) {
parsed_longhand[i + 1] = true;
AddBackgroundValue(longhands[i + 1], value_y);
}
}
}
if (!found_property)
return false;
} while (!range.AtEnd() && range.Peek().GetType() != kCommaToken);
// TODO(timloh): This will make invalid longhands, see crbug.com/386459
for (size_t i = 0; i < longhand_count; ++i) {
const CSSProperty& property = *shorthand.properties()[i];
if (property.IDEquals(CSSPropertyBackgroundColor) && !range.AtEnd()) {
if (parsed_longhand[i])
return false; // Colors are only allowed in the last layer.
continue;
}
if ((property.IDEquals(CSSPropertyBackgroundClip) ||
property.IDEquals(CSSPropertyWebkitMaskClip)) &&
!parsed_longhand[i] && origin_value) {
AddBackgroundValue(longhands[i], origin_value);
continue;
}
if (!parsed_longhand[i]) {
AddBackgroundValue(longhands[i], CSSInitialValue::Create());
}
}
} while (CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(range));
if (!range.AtEnd())
return false;
for (size_t i = 0; i < longhand_count; ++i) {
const CSSProperty& property = *shorthand.properties()[i];
if (property.IDEquals(CSSPropertyBackgroundSize) && longhands[i] &&
context.UseLegacyBackgroundSizeShorthandBehavior())
continue;
CSSPropertyParserHelpers::AddProperty(
property.PropertyID(), shorthand.id(), *longhands[i], important,
implicit ? CSSPropertyParserHelpers::IsImplicitProperty::kImplicit
: CSSPropertyParserHelpers::IsImplicitProperty::kNotImplicit,
properties);
}
return true;
}
bool ConsumeRepeatStyleComponent(CSSParserTokenRange& range,
CSSValue*& value1,
CSSValue*& value2,
bool& implicit) {
if (CSSPropertyParserHelpers::ConsumeIdent<CSSValueRepeatX>(range)) {
value1 = CSSIdentifierValue::Create(CSSValueRepeat);
value2 = CSSIdentifierValue::Create(CSSValueNoRepeat);
implicit = true;
return true;
}
if (CSSPropertyParserHelpers::ConsumeIdent<CSSValueRepeatY>(range)) {
value1 = CSSIdentifierValue::Create(CSSValueNoRepeat);
value2 = CSSIdentifierValue::Create(CSSValueRepeat);
implicit = true;
return true;
}
value1 = CSSPropertyParserHelpers::ConsumeIdent<
CSSValueRepeat, CSSValueNoRepeat, CSSValueRound, CSSValueSpace>(range);
if (!value1)
return false;
value2 = CSSPropertyParserHelpers::ConsumeIdent<
CSSValueRepeat, CSSValueNoRepeat, CSSValueRound, CSSValueSpace>(range);
if (!value2) {
value2 = value1;
implicit = true;
}
return true;
}
bool ConsumeRepeatStyle(CSSParserTokenRange& range,
CSSValue*& result_x,
CSSValue*& result_y,
bool& implicit) {
do {
CSSValue* repeat_x = nullptr;
CSSValue* repeat_y = nullptr;
if (!ConsumeRepeatStyleComponent(range, repeat_x, repeat_y, implicit))
return false;
AddBackgroundValue(result_x, repeat_x);
AddBackgroundValue(result_y, repeat_y);
} while (CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(range));
return true;
}
CSSValue* ConsumeWebkitBorderImage(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValue* source = nullptr;
CSSValue* slice = nullptr;
CSSValue* width = nullptr;
CSSValue* outset = nullptr;
CSSValue* repeat = nullptr;
if (ConsumeBorderImageComponents(range, context, source, slice, width, outset,
repeat, DefaultFill::kFill))
return CreateBorderImageValue(source, slice, width, outset, repeat);
return nullptr;
}
bool ConsumeBorderImageComponents(CSSParserTokenRange& range,
const CSSParserContext& context,
CSSValue*& source,
CSSValue*& slice,
CSSValue*& width,
CSSValue*& outset,
CSSValue*& repeat,
DefaultFill default_fill) {
do {
if (!source) {
source = CSSPropertyParserHelpers::ConsumeImageOrNone(range, &context);
if (source)
continue;
}
if (!repeat) {
repeat = ConsumeBorderImageRepeat(range);
if (repeat)
continue;
}
if (!slice) {
slice = ConsumeBorderImageSlice(range, default_fill);
if (slice) {
DCHECK(!width);
DCHECK(!outset);
if (CSSPropertyParserHelpers::ConsumeSlashIncludingWhitespace(range)) {
width = ConsumeBorderImageWidth(range);
if (CSSPropertyParserHelpers::ConsumeSlashIncludingWhitespace(
range)) {
outset = ConsumeBorderImageOutset(range);
if (!outset)
return false;
} else if (!width) {
return false;
}
}
} else {
return false;
}
} else {
return false;
}
} while (!range.AtEnd());
return true;
}
CSSValue* ConsumeBorderImageRepeat(CSSParserTokenRange& range) {
CSSIdentifierValue* horizontal = ConsumeBorderImageRepeatKeyword(range);
if (!horizontal)
return nullptr;
CSSIdentifierValue* vertical = ConsumeBorderImageRepeatKeyword(range);
if (!vertical)
vertical = horizontal;
return CSSValuePair::Create(horizontal, vertical,
CSSValuePair::kDropIdenticalValues);
}
CSSValue* ConsumeBorderImageSlice(CSSParserTokenRange& range,
DefaultFill default_fill) {
bool fill = CSSPropertyParserHelpers::ConsumeIdent<CSSValueFill>(range);
CSSValue* slices[4] = {nullptr};
for (size_t index = 0; index < 4; ++index) {
CSSPrimitiveValue* value =
CSSPropertyParserHelpers::ConsumePercent(range, kValueRangeNonNegative);
if (!value) {
value = CSSPropertyParserHelpers::ConsumeNumber(range,
kValueRangeNonNegative);
}
if (!value)
break;
slices[index] = value;
}
if (!slices[0])
return nullptr;
if (CSSPropertyParserHelpers::ConsumeIdent<CSSValueFill>(range)) {
if (fill)
return nullptr;
fill = true;
}
CSSPropertyParserHelpers::Complete4Sides(slices);
if (default_fill == DefaultFill::kFill)
fill = true;
return CSSBorderImageSliceValue::Create(
CSSQuadValue::Create(slices[0], slices[1], slices[2], slices[3],
CSSQuadValue::kSerializeAsQuad),
fill);
}
CSSValue* ConsumeBorderImageWidth(CSSParserTokenRange& range) {
CSSValue* widths[4] = {nullptr};
CSSValue* value = nullptr;
for (size_t index = 0; index < 4; ++index) {
value =
CSSPropertyParserHelpers::ConsumeNumber(range, kValueRangeNonNegative);
if (!value) {
value = CSSPropertyParserHelpers::ConsumeLengthOrPercent(
range, kHTMLStandardMode, kValueRangeNonNegative,
CSSPropertyParserHelpers::UnitlessQuirk::kForbid);
}
if (!value)
value = CSSPropertyParserHelpers::ConsumeIdent<CSSValueAuto>(range);
if (!value)
break;
widths[index] = value;
}
if (!widths[0])
return nullptr;
CSSPropertyParserHelpers::Complete4Sides(widths);
return CSSQuadValue::Create(widths[0], widths[1], widths[2], widths[3],
CSSQuadValue::kSerializeAsQuad);
}
CSSValue* ConsumeBorderImageOutset(CSSParserTokenRange& range) {
CSSValue* outsets[4] = {nullptr};
CSSValue* value = nullptr;
for (size_t index = 0; index < 4; ++index) {
value =
CSSPropertyParserHelpers::ConsumeNumber(range, kValueRangeNonNegative);
if (!value) {
value = CSSPropertyParserHelpers::ConsumeLength(range, kHTMLStandardMode,
kValueRangeNonNegative);
}
if (!value)
break;
outsets[index] = value;
}
if (!outsets[0])
return nullptr;
CSSPropertyParserHelpers::Complete4Sides(outsets);
return CSSQuadValue::Create(outsets[0], outsets[1], outsets[2], outsets[3],
CSSQuadValue::kSerializeAsQuad);
}
CSSValue* ParseBorderRadiusCorner(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValue* parsed_value1 = CSSPropertyParserHelpers::ConsumeLengthOrPercent(
range, context.Mode(), kValueRangeNonNegative);
if (!parsed_value1)
return nullptr;
CSSValue* parsed_value2 = CSSPropertyParserHelpers::ConsumeLengthOrPercent(
range, context.Mode(), kValueRangeNonNegative);
if (!parsed_value2)
parsed_value2 = parsed_value1;
return CSSValuePair::Create(parsed_value1, parsed_value2,
CSSValuePair::kDropIdenticalValues);
}
CSSValue* ParseBorderWidthSide(CSSParserTokenRange& range,
const CSSParserContext& context,
const CSSParserLocalContext& local_context) {
CSSPropertyID shorthand = local_context.CurrentShorthand();
bool allow_quirky_lengths =
IsQuirksModeBehavior(context.Mode()) &&
(shorthand == CSSPropertyInvalid || shorthand == CSSPropertyBorderWidth);
CSSPropertyParserHelpers::UnitlessQuirk unitless =
allow_quirky_lengths ? CSSPropertyParserHelpers::UnitlessQuirk::kAllow
: CSSPropertyParserHelpers::UnitlessQuirk::kForbid;
return ConsumeBorderWidth(range, context.Mode(), unitless);
}
CSSValue* ConsumeShadow(CSSParserTokenRange& range,
CSSParserMode css_parser_mode,
AllowInsetAndSpread inset_and_spread) {
if (range.Peek().Id() == CSSValueNone)
return CSSPropertyParserHelpers::ConsumeIdent(range);
return CSSPropertyParserHelpers::ConsumeCommaSeparatedList(
ParseSingleShadow, range, css_parser_mode, inset_and_spread);
}
CSSShadowValue* ParseSingleShadow(CSSParserTokenRange& range,
CSSParserMode css_parser_mode,
AllowInsetAndSpread inset_and_spread) {
CSSIdentifierValue* style = nullptr;
CSSValue* color = nullptr;
if (range.AtEnd())
return nullptr;
if (range.Peek().Id() == CSSValueInset) {
if (inset_and_spread != AllowInsetAndSpread::kAllow)
return nullptr;
style = CSSPropertyParserHelpers::ConsumeIdent(range);
}
color = CSSPropertyParserHelpers::ConsumeColor(range, css_parser_mode);
CSSPrimitiveValue* horizontal_offset =
CSSPropertyParserHelpers::ConsumeLength(range, css_parser_mode,
kValueRangeAll);
if (!horizontal_offset)
return nullptr;
CSSPrimitiveValue* vertical_offset = CSSPropertyParserHelpers::ConsumeLength(
range, css_parser_mode, kValueRangeAll);
if (!vertical_offset)
return nullptr;
CSSPrimitiveValue* blur_radius = CSSPropertyParserHelpers::ConsumeLength(
range, css_parser_mode, kValueRangeAll);
CSSPrimitiveValue* spread_distance = nullptr;
if (blur_radius) {
// Blur radius must be non-negative.
if (blur_radius->GetDoubleValue() < 0)
return nullptr;
if (inset_and_spread == AllowInsetAndSpread::kAllow) {
spread_distance = CSSPropertyParserHelpers::ConsumeLength(
range, css_parser_mode, kValueRangeAll);
}
}
if (!range.AtEnd()) {
if (!color)
color = CSSPropertyParserHelpers::ConsumeColor(range, css_parser_mode);
if (range.Peek().Id() == CSSValueInset) {
if (inset_and_spread != AllowInsetAndSpread::kAllow || style)
return nullptr;
style = CSSPropertyParserHelpers::ConsumeIdent(range);
}
}
return CSSShadowValue::Create(horizontal_offset, vertical_offset, blur_radius,
spread_distance, style, color);
}
CSSValue* ConsumeColumnCount(CSSParserTokenRange& range) {
if (range.Peek().Id() == CSSValueAuto)
return CSSPropertyParserHelpers::ConsumeIdent(range);
return CSSPropertyParserHelpers::ConsumePositiveInteger(range);
}
CSSValue* ConsumeColumnWidth(CSSParserTokenRange& range) {
if (range.Peek().Id() == CSSValueAuto)
return CSSPropertyParserHelpers::ConsumeIdent(range);
// Always parse lengths in strict mode here, since it would be ambiguous
// otherwise when used in the 'columns' shorthand property.
CSSPrimitiveValue* column_width = CSSPropertyParserHelpers::ConsumeLength(
range, kHTMLStandardMode, kValueRangeNonNegative);
if (!column_width ||
(!column_width->IsCalculated() && column_width->GetDoubleValue() == 0))
return nullptr;
return column_width;
}
bool ConsumeColumnWidthOrCount(CSSParserTokenRange& range,
CSSValue*& column_width,
CSSValue*& column_count) {
if (range.Peek().Id() == CSSValueAuto) {
CSSPropertyParserHelpers::ConsumeIdent(range);
return true;
}
if (!column_width) {
column_width = ConsumeColumnWidth(range);
if (column_width)
return true;
}
if (!column_count)
column_count = ConsumeColumnCount(range);
return column_count;
}
CSSValue* ConsumeGapLength(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueNormal)
return CSSPropertyParserHelpers::ConsumeIdent(range);
return CSSPropertyParserHelpers::ConsumeLengthOrPercent(
range, context.Mode(), kValueRangeNonNegative);
}
CSSValue* ConsumeCounter(CSSParserTokenRange& range, int default_value) {
if (range.Peek().Id() == CSSValueNone)
return CSSPropertyParserHelpers::ConsumeIdent(range);
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
do {
CSSCustomIdentValue* counter_name =
CSSPropertyParserHelpers::ConsumeCustomIdent(range);
if (!counter_name)
return nullptr;
int value = default_value;
if (CSSPrimitiveValue* counter_value =
CSSPropertyParserHelpers::ConsumeInteger(range))
value = clampTo<int>(counter_value->GetDoubleValue());
list->Append(*CSSValuePair::Create(
counter_name,
CSSPrimitiveValue::Create(value, CSSPrimitiveValue::UnitType::kInteger),
CSSValuePair::kDropIdenticalValues));
} while (!range.AtEnd());
return list;
}
CSSValue* ConsumeFontSize(CSSParserTokenRange& range,
CSSParserMode css_parser_mode,
CSSPropertyParserHelpers::UnitlessQuirk unitless) {
if (range.Peek().Id() >= CSSValueXxSmall &&
range.Peek().Id() <= CSSValueLarger)
return CSSPropertyParserHelpers::ConsumeIdent(range);
return CSSPropertyParserHelpers::ConsumeLengthOrPercent(
range, css_parser_mode, kValueRangeNonNegative, unitless);
}
CSSValue* ConsumeLineHeight(CSSParserTokenRange& range,
CSSParserMode css_parser_mode) {
if (range.Peek().Id() == CSSValueNormal)
return CSSPropertyParserHelpers::ConsumeIdent(range);
CSSPrimitiveValue* line_height =
CSSPropertyParserHelpers::ConsumeNumber(range, kValueRangeNonNegative);
if (line_height)
return line_height;
return CSSPropertyParserHelpers::ConsumeLengthOrPercent(
range, css_parser_mode, kValueRangeNonNegative);
}
CSSValueList* ConsumeFontFamily(CSSParserTokenRange& range) {
CSSValueList* list = CSSValueList::CreateCommaSeparated();
do {
CSSValue* parsed_value = ConsumeGenericFamily(range);
if (parsed_value) {
list->Append(*parsed_value);
} else {
parsed_value = ConsumeFamilyName(range);
if (parsed_value) {
list->Append(*parsed_value);
} else {
return nullptr;
}
}
} while (CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(range));
return list;
}
CSSValue* ConsumeGenericFamily(CSSParserTokenRange& range) {
return CSSPropertyParserHelpers::ConsumeIdentRange(range, CSSValueSerif,
CSSValueWebkitBody);
}
CSSValue* ConsumeFamilyName(CSSParserTokenRange& range) {
if (range.Peek().GetType() == kStringToken) {
return CSSFontFamilyValue::Create(
range.ConsumeIncludingWhitespace().Value().ToString());
}
if (range.Peek().GetType() != kIdentToken)
return nullptr;
String family_name = ConcatenateFamilyName(range);
if (family_name.IsNull())
return nullptr;
return CSSFontFamilyValue::Create(family_name);
}
String ConcatenateFamilyName(CSSParserTokenRange& range) {
StringBuilder builder;
bool added_space = false;
const CSSParserToken& first_token = range.Peek();
while (range.Peek().GetType() == kIdentToken) {
if (!builder.IsEmpty()) {
builder.Append(' ');
added_space = true;
}
builder.Append(range.ConsumeIncludingWhitespace().Value());
}
if (!added_space &&
(CSSPropertyParserHelpers::IsCSSWideKeyword(first_token.Value()) ||
EqualIgnoringASCIICase(first_token.Value(), "default"))) {
return String();
}
return builder.ToString();
}
CSSValueList* CombineToRangeListOrNull(const CSSPrimitiveValue* range_start,
const CSSPrimitiveValue* range_end) {
DCHECK(range_start);
DCHECK(range_end);
if (range_end->GetFloatValue() < range_start->GetFloatValue())
return nullptr;
CSSValueList* value_list = CSSValueList::CreateSpaceSeparated();
value_list->Append(*range_start);
value_list->Append(*range_end);
return value_list;
}
bool IsAngleWithinLimits(CSSPrimitiveValue* angle) {
constexpr float kMaxAngle = 90.0f;
return angle->GetFloatValue() >= -kMaxAngle &&
angle->GetFloatValue() <= kMaxAngle;
}
CSSValue* ConsumeFontStyle(CSSParserTokenRange& range,
const CSSParserMode& parser_mode) {
if (range.Peek().Id() == CSSValueNormal ||
range.Peek().Id() == CSSValueItalic)
return CSSPropertyParserHelpers::ConsumeIdent(range);
if (range.Peek().Id() != CSSValueOblique)
return nullptr;
CSSIdentifierValue* oblique_identifier =
CSSPropertyParserHelpers::ConsumeIdent<CSSValueOblique>(range);
CSSPrimitiveValue* start_angle =
CSSPropertyParserHelpers::ConsumeAngle(range, nullptr, WTF::nullopt);
if (!start_angle)
return oblique_identifier;
if (!IsAngleWithinLimits(start_angle))
return nullptr;
if (parser_mode != kCSSFontFaceRuleMode || range.AtEnd()) {
CSSValueList* value_list = CSSValueList::CreateSpaceSeparated();
value_list->Append(*start_angle);
return CSSFontStyleRangeValue::Create(*oblique_identifier, *value_list);
}
CSSPrimitiveValue* end_angle =
CSSPropertyParserHelpers::ConsumeAngle(range, nullptr, WTF::nullopt);
if (!end_angle || !IsAngleWithinLimits(end_angle))
return nullptr;
CSSValueList* range_list = CombineToRangeListOrNull(start_angle, end_angle);
if (!range_list)
return nullptr;
return CSSFontStyleRangeValue::Create(*oblique_identifier, *range_list);
}
CSSIdentifierValue* ConsumeFontStretchKeywordOnly(CSSParserTokenRange& range) {
const CSSParserToken& token = range.Peek();
if (token.Id() == CSSValueNormal || (token.Id() >= CSSValueUltraCondensed &&
token.Id() <= CSSValueUltraExpanded))
return CSSPropertyParserHelpers::ConsumeIdent(range);
return nullptr;
}
CSSValue* ConsumeFontStretch(CSSParserTokenRange& range,
const CSSParserMode& parser_mode) {
CSSIdentifierValue* parsed_keyword = ConsumeFontStretchKeywordOnly(range);
if (parsed_keyword)
return parsed_keyword;
CSSPrimitiveValue* start_percent =
CSSPropertyParserHelpers::ConsumePercent(range, kValueRangeNonNegative);
if (!start_percent || start_percent->GetFloatValue() <= 0)
return nullptr;
// In a non-font-face context, more than one percentage is not allowed.
if (parser_mode != kCSSFontFaceRuleMode || range.AtEnd())
return start_percent;
CSSPrimitiveValue* end_percent =
CSSPropertyParserHelpers::ConsumePercent(range, kValueRangeNonNegative);
if (!end_percent || end_percent->GetFloatValue() <= 0)
return nullptr;
return CombineToRangeListOrNull(start_percent, end_percent);
}
CSSValue* ConsumeFontWeight(CSSParserTokenRange& range,
const CSSParserMode& parser_mode) {
const CSSParserToken& token = range.Peek();
if (token.Id() >= CSSValueNormal && token.Id() <= CSSValueLighter)
return CSSPropertyParserHelpers::ConsumeIdent(range);
// Avoid consuming the first zero of font: 0/0; e.g. in the Acid3 test. In
// font:0/0; the first zero is the font size, the second is the line height.
// In font: 100 0/0; we should parse the first 100 as font-weight, the 0
// before the slash as font size. We need to peek and check the token in order
// to avoid parsing a 0 font size as a font-weight. If we call ConsumeNumber
// straight away without Peek, then the parsing cursor advances too far and we
// parsed font-size as font-weight incorrectly.
if (token.GetType() == kNumberToken &&
(token.NumericValue() < 1 || token.NumericValue() > 1000))
return nullptr;
CSSPrimitiveValue* start_weight =
CSSPropertyParserHelpers::ConsumeNumber(range, kValueRangeNonNegative);
if (!start_weight || start_weight->GetFloatValue() < 1 ||
start_weight->GetFloatValue() > 1000)
return nullptr;
// In a non-font-face context, more than one number is not allowed. Return
// what we have. If there is trailing garbage, the AtEnd() check in
// CSSPropertyParser::ParseValueStart will catch that.
if (parser_mode != kCSSFontFaceRuleMode || range.AtEnd())
return start_weight;
CSSPrimitiveValue* end_weight =
CSSPropertyParserHelpers::ConsumeNumber(range, kValueRangeNonNegative);
if (!end_weight || end_weight->GetFloatValue() < 1 ||
end_weight->GetFloatValue() > 1000)
return nullptr;
return CombineToRangeListOrNull(start_weight, end_weight);
}
CSSValue* ConsumeFontFeatureSettings(CSSParserTokenRange& range) {
if (range.Peek().Id() == CSSValueNormal)
return CSSPropertyParserHelpers::ConsumeIdent(range);
CSSValueList* settings = CSSValueList::CreateCommaSeparated();
do {
CSSFontFeatureValue* font_feature_value = ConsumeFontFeatureTag(range);
if (!font_feature_value)
return nullptr;
settings->Append(*font_feature_value);
} while (CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(range));
return settings;
}
CSSFontFeatureValue* ConsumeFontFeatureTag(CSSParserTokenRange& range) {
// Feature tag name consists of 4-letter characters.
const unsigned kTagNameLength = 4;
const CSSParserToken& token = range.ConsumeIncludingWhitespace();
// Feature tag name comes first
if (token.GetType() != kStringToken)
return nullptr;
if (token.Value().length() != kTagNameLength)
return nullptr;
AtomicString tag = token.Value().ToAtomicString();
for (unsigned i = 0; i < kTagNameLength; ++i) {
// Limits the range of characters to 0x20-0x7E, following the tag name rules
// defined in the OpenType specification.
UChar character = tag[i];
if (character < 0x20 || character > 0x7E)
return nullptr;
}
int tag_value = 1;
// Feature tag values could follow: <integer> | on | off
if (CSSPrimitiveValue* value =
CSSPropertyParserHelpers::ConsumeInteger(range, 0)) {
tag_value = clampTo<int>(value->GetDoubleValue());
} else if (range.Peek().Id() == CSSValueOn ||
range.Peek().Id() == CSSValueOff) {
tag_value = range.ConsumeIncludingWhitespace().Id() == CSSValueOn;
}
return CSSFontFeatureValue::Create(tag, tag_value);
}
CSSIdentifierValue* ConsumeFontVariantCSS21(CSSParserTokenRange& range) {
return CSSPropertyParserHelpers::ConsumeIdent<CSSValueNormal,
CSSValueSmallCaps>(range);
}
Vector<String> ParseGridTemplateAreasColumnNames(const String& grid_row_names) {
DCHECK(!grid_row_names.IsEmpty());
Vector<String> column_names;
// Using StringImpl to avoid checks and indirection in every call to
// String::operator[].
StringImpl& text = *grid_row_names.Impl();
StringBuilder area_name;
for (unsigned i = 0; i < text.length(); ++i) {
if (IsCSSSpace(text[i])) {
if (!area_name.IsEmpty()) {
column_names.push_back(area_name.ToString());
area_name.Clear();
}
continue;
}
if (text[i] == '.') {
if (area_name == ".")
continue;
if (!area_name.IsEmpty()) {
column_names.push_back(area_name.ToString());
area_name.Clear();
}
} else {
if (!IsNameCodePoint(text[i]))
return Vector<String>();
if (area_name == ".") {
column_names.push_back(area_name.ToString());
area_name.Clear();
}
}
area_name.Append(text[i]);
}
if (!area_name.IsEmpty())
column_names.push_back(area_name.ToString());
return column_names;
}
CSSValue* ConsumeGridBreadth(CSSParserTokenRange& range,
CSSParserMode css_parser_mode) {
const CSSParserToken& token = range.Peek();
if (CSSPropertyParserHelpers::IdentMatches<CSSValueMinContent,
CSSValueMaxContent, CSSValueAuto>(
token.Id()))
return CSSPropertyParserHelpers::ConsumeIdent(range);
if (token.GetType() == kDimensionToken &&
token.GetUnitType() == CSSPrimitiveValue::UnitType::kFraction) {
if (range.Peek().NumericValue() < 0)
return nullptr;
return CSSPrimitiveValue::Create(
range.ConsumeIncludingWhitespace().NumericValue(),
CSSPrimitiveValue::UnitType::kFraction);
}
return CSSPropertyParserHelpers::ConsumeLengthOrPercent(
range, css_parser_mode, kValueRangeNonNegative,
CSSPropertyParserHelpers::UnitlessQuirk::kAllow);
}
CSSValue* ConsumeFitContent(CSSParserTokenRange& range,
CSSParserMode css_parser_mode) {
CSSParserTokenRange range_copy = range;
CSSParserTokenRange args =
CSSPropertyParserHelpers::ConsumeFunction(range_copy);
CSSPrimitiveValue* length = CSSPropertyParserHelpers::ConsumeLengthOrPercent(
args, css_parser_mode, kValueRangeNonNegative,
CSSPropertyParserHelpers::UnitlessQuirk::kAllow);
if (!length || !args.AtEnd())
return nullptr;
range = range_copy;
CSSFunctionValue* result = CSSFunctionValue::Create(CSSValueFitContent);
result->Append(*length);
return result;
}
bool IsGridBreadthFixedSized(const CSSValue& value) {
if (value.IsIdentifierValue()) {
CSSValueID value_id = ToCSSIdentifierValue(value).GetValueID();
return !(value_id == CSSValueMinContent || value_id == CSSValueMaxContent ||
value_id == CSSValueAuto);
}
if (value.IsPrimitiveValue()) {
return !ToCSSPrimitiveValue(value).IsFlex();
}
NOTREACHED();
return true;
}
bool IsGridTrackFixedSized(const CSSValue& value) {
if (value.IsPrimitiveValue() || value.IsIdentifierValue())
return IsGridBreadthFixedSized(value);
DCHECK(value.IsFunctionValue());
auto& function = ToCSSFunctionValue(value);
if (function.FunctionType() == CSSValueFitContent)
return false;
const CSSValue& min_value = function.Item(0);
const CSSValue& max_value = function.Item(1);
return IsGridBreadthFixedSized(min_value) ||
IsGridBreadthFixedSized(max_value);
}
CSSValue* ConsumeGridTrackSize(CSSParserTokenRange& range,
CSSParserMode css_parser_mode) {
const CSSParserToken& token = range.Peek();
if (CSSPropertyParserHelpers::IdentMatches<CSSValueAuto>(token.Id()))
return CSSPropertyParserHelpers::ConsumeIdent(range);
if (token.FunctionId() == CSSValueMinmax) {
CSSParserTokenRange range_copy = range;
CSSParserTokenRange args =
CSSPropertyParserHelpers::ConsumeFunction(range_copy);
CSSValue* min_track_breadth = ConsumeGridBreadth(args, css_parser_mode);
if (!min_track_breadth ||
(min_track_breadth->IsPrimitiveValue() &&
ToCSSPrimitiveValue(min_track_breadth)->IsFlex()) ||
!CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(args))
return nullptr;
CSSValue* max_track_breadth = ConsumeGridBreadth(args, css_parser_mode);
if (!max_track_breadth || !args.AtEnd())
return nullptr;
range = range_copy;
CSSFunctionValue* result = CSSFunctionValue::Create(CSSValueMinmax);
result->Append(*min_track_breadth);
result->Append(*max_track_breadth);
return result;
}
if (token.FunctionId() == CSSValueFitContent)
return ConsumeFitContent(range, css_parser_mode);
return ConsumeGridBreadth(range, css_parser_mode);
}
CSSCustomIdentValue* ConsumeCustomIdentForGridLine(CSSParserTokenRange& range) {
if (range.Peek().Id() == CSSValueAuto || range.Peek().Id() == CSSValueSpan ||
range.Peek().Id() == CSSValueDefault)
return nullptr;
return CSSPropertyParserHelpers::ConsumeCustomIdent(range);
}
// Appends to the passed in CSSGridLineNamesValue if any, otherwise creates a
// new one.
CSSGridLineNamesValue* ConsumeGridLineNames(
CSSParserTokenRange& range,
CSSGridLineNamesValue* line_names = nullptr) {
CSSParserTokenRange range_copy = range;
if (range_copy.ConsumeIncludingWhitespace().GetType() != kLeftBracketToken)
return nullptr;
if (!line_names)
line_names = CSSGridLineNamesValue::Create();
while (CSSCustomIdentValue* line_name =
ConsumeCustomIdentForGridLine(range_copy))
line_names->Append(*line_name);
if (range_copy.ConsumeIncludingWhitespace().GetType() != kRightBracketToken)
return nullptr;
range = range_copy;
return line_names;
}
bool ConsumeGridTrackRepeatFunction(CSSParserTokenRange& range,
CSSParserMode css_parser_mode,
CSSValueList& list,
bool& is_auto_repeat,
bool& all_tracks_are_fixed_sized) {
CSSParserTokenRange args = CSSPropertyParserHelpers::ConsumeFunction(range);
// The number of repetitions for <auto-repeat> is not important at parsing
// level because it will be computed later, let's set it to 1.
size_t repetitions = 1;
is_auto_repeat =
CSSPropertyParserHelpers::IdentMatches<CSSValueAutoFill, CSSValueAutoFit>(
args.Peek().Id());
CSSValueList* repeated_values;
if (is_auto_repeat) {
repeated_values =
CSSGridAutoRepeatValue::Create(args.ConsumeIncludingWhitespace().Id());
} else {
// TODO(rob.buis): a consumeIntegerRaw would be more efficient here.
CSSPrimitiveValue* repetition =
CSSPropertyParserHelpers::ConsumePositiveInteger(args);
if (!repetition)
return false;
repetitions =
clampTo<size_t>(repetition->GetDoubleValue(), 0, kGridMaxTracks);
repeated_values = CSSValueList::CreateSpaceSeparated();
}
if (!CSSPropertyParserHelpers::ConsumeCommaIncludingWhitespace(args))
return false;
CSSGridLineNamesValue* line_names = ConsumeGridLineNames(args);
if (line_names)
repeated_values->Append(*line_names);
size_t number_of_tracks = 0;
while (!args.AtEnd()) {
CSSValue* track_size = ConsumeGridTrackSize(args, css_parser_mode);
if (!track_size)
return false;
if (all_tracks_are_fixed_sized)
all_tracks_are_fixed_sized = IsGridTrackFixedSized(*track_size);
repeated_values->Append(*track_size);
++number_of_tracks;
line_names = ConsumeGridLineNames(args);
if (line_names)
repeated_values->Append(*line_names);
}
// We should have found at least one <track-size> or else it is not a valid
// <track-list>.
if (!number_of_tracks)
return false;
if (is_auto_repeat) {
list.Append(*repeated_values);
} else {
// We clamp the repetitions to a multiple of the repeat() track list's size,
// while staying below the max grid size.
repetitions = std::min(repetitions, kGridMaxTracks / number_of_tracks);
for (size_t i = 0; i < repetitions; ++i) {
for (size_t j = 0; j < repeated_values->length(); ++j)
list.Append(repeated_values->Item(j));
}
}
return true;
}
bool ConsumeGridTemplateRowsAndAreasAndColumns(bool important,
CSSParserTokenRange& range,
const CSSParserContext& context,
CSSValue*& template_rows,
CSSValue*& template_columns,
CSSValue*& template_areas) {
DCHECK(!template_rows);
DCHECK(!template_columns);
DCHECK(!template_areas);
NamedGridAreaMap grid_area_map;
size_t row_count = 0;
size_t column_count = 0;
CSSValueList* template_rows_value_list = CSSValueList::CreateSpaceSeparated();
// Persists between loop iterations so we can use the same value for
// consecutive <line-names> values
CSSGridLineNamesValue* line_names = nullptr;
do {
// Handle leading <custom-ident>*.
bool has_previous_line_names = line_names;
line_names = ConsumeGridLineNames(range, line_names);
if (line_names && !has_previous_line_names)
template_rows_value_list->Append(*line_names);
// Handle a template-area's row.
if (range.Peek().GetType() != kStringToken ||
!ParseGridTemplateAreasRow(
range.ConsumeIncludingWhitespace().Value().ToString(),
grid_area_map, row_count, column_count))
return false;
++row_count;
// Handle template-rows's track-size.
CSSValue* value = ConsumeGridTrackSize(range, context.Mode());
if (!value)
value = CSSIdentifierValue::Create(CSSValueAuto);
template_rows_value_list->Append(*value);
// This will handle the trailing/leading <custom-ident>* in the grammar.
line_names = ConsumeGridLineNames(range);
if (line_names)
template_rows_value_list->Append(*line_names);
} while (!range.AtEnd() && !(range.Peek().GetType() == kDelimiterToken &&
range.Peek().Delimiter() == '/'));
if (!range.AtEnd()) {
if (!CSSPropertyParserHelpers::ConsumeSlashIncludingWhitespace(range))
return false;
template_columns = ConsumeGridTrackList(
range, context.Mode(), TrackListType::kGridTemplateNoRepeat);
if (!template_columns || !range.AtEnd())
return false;
} else {
template_columns = CSSIdentifierValue::Create(CSSValueNone);
}
template_rows = template_rows_value_list;
template_areas =
CSSGridTemplateAreasValue::Create(grid_area_map, row_count, column_count);
return true;
}
CSSValue* ConsumeGridLine(CSSParserTokenRange& range) {
if (range.Peek().Id() == CSSValueAuto)
return CSSPropertyParserHelpers::ConsumeIdent(range);
CSSIdentifierValue* span_value = nullptr;
CSSCustomIdentValue* grid_line_name = nullptr;
CSSPrimitiveValue* numeric_value =
CSSPropertyParserHelpers::ConsumeInteger(range);
if (numeric_value) {
grid_line_name = ConsumeCustomIdentForGridLine(range);
span_value = CSSPropertyParserHelpers::ConsumeIdent<CSSValueSpan>(range);
} else {
span_value = CSSPropertyParserHelpers::ConsumeIdent<CSSValueSpan>(range);
if (span_value) {
numeric_value = CSSPropertyParserHelpers::ConsumeInteger(range);
grid_line_name = ConsumeCustomIdentForGridLine(range);
if (!numeric_value)
numeric_value = CSSPropertyParserHelpers::ConsumeInteger(range);
} else {
grid_line_name = ConsumeCustomIdentForGridLine(range);
if (grid_line_name) {
numeric_value = CSSPropertyParserHelpers::ConsumeInteger(range);
span_value =
CSSPropertyParserHelpers::ConsumeIdent<CSSValueSpan>(range);
if (!span_value && !numeric_value)
return grid_line_name;
} else {
return nullptr;
}
}
}
if (span_value && !numeric_value && !grid_line_name)
return nullptr; // "span" keyword alone is invalid.
if (span_value && numeric_value && numeric_value->GetIntValue() < 0)
return nullptr; // Negative numbers are not allowed for span.
if (numeric_value && numeric_value->GetIntValue() == 0)
return nullptr; // An <integer> value of zero makes the declaration
// invalid.
if (numeric_value) {
numeric_value = CSSPrimitiveValue::Create(
clampTo(numeric_value->GetIntValue(), -kGridMaxTracks, kGridMaxTracks),
CSSPrimitiveValue::UnitType::kInteger);
}
CSSValueList* values = CSSValueList::CreateSpaceSeparated();
if (span_value)
values->Append(*span_value);
if (numeric_value)
values->Append(*numeric_value);
if (grid_line_name)
values->Append(*grid_line_name);
DCHECK(values->length());
return values;
}
CSSValue* ConsumeGridTrackList(CSSParserTokenRange& range,
CSSParserMode css_parser_mode,
TrackListType track_list_type) {
bool allow_grid_line_names = track_list_type != TrackListType::kGridAuto;
CSSValueList* values = CSSValueList::CreateSpaceSeparated();
CSSGridLineNamesValue* line_names = ConsumeGridLineNames(range);
if (line_names) {
if (!allow_grid_line_names)
return nullptr;
values->Append(*line_names);
}
bool allow_repeat = track_list_type == TrackListType::kGridTemplate;
bool seen_auto_repeat = false;
bool all_tracks_are_fixed_sized = true;
do {
bool is_auto_repeat;
if (range.Peek().FunctionId() == CSSValueRepeat) {
if (!allow_repeat)
return nullptr;
if (!ConsumeGridTrackRepeatFunction(range, css_parser_mode, *values,
is_auto_repeat,
all_tracks_are_fixed_sized))
return nullptr;
if (is_auto_repeat && seen_auto_repeat)
return nullptr;
seen_auto_repeat = seen_auto_repeat || is_auto_repeat;
} else if (CSSValue* value = ConsumeGridTrackSize(range, css_parser_mode)) {
if (all_tracks_are_fixed_sized)
all_tracks_are_fixed_sized = IsGridTrackFixedSized(*value);
values->Append(*value);
} else {
return nullptr;
}
if (seen_auto_repeat && !all_tracks_are_fixed_sized)
return nullptr;
line_names = ConsumeGridLineNames(range);
if (line_names) {
if (!allow_grid_line_names)
return nullptr;
values->Append(*line_names);
}
} while (!range.AtEnd() && range.Peek().GetType() != kDelimiterToken);
return values;
}
bool ParseGridTemplateAreasRow(const String& grid_row_names,
NamedGridAreaMap& grid_area_map,
const size_t row_count,
size_t& column_count) {
if (grid_row_names.IsEmpty() || grid_row_names.ContainsOnlyWhitespace())
return false;
Vector<String> column_names =
ParseGridTemplateAreasColumnNames(grid_row_names);
if (row_count == 0) {
column_count = column_names.size();
if (column_count == 0)
return false;
} else if (column_count != column_names.size()) {
// The declaration is invalid if all the rows don't have the number of
// columns.
return false;
}
for (size_t current_column = 0; current_column < column_count;
++current_column) {
const String& grid_area_name = column_names[current_column];
// Unamed areas are always valid (we consider them to be 1x1).
if (grid_area_name == ".")
continue;
size_t look_ahead_column = current_column + 1;
while (look_ahead_column < column_count &&
column_names[look_ahead_column] == grid_area_name)
look_ahead_column++;
NamedGridAreaMap::iterator grid_area_it =
grid_area_map.find(grid_area_name);
if (grid_area_it == grid_area_map.end()) {
grid_area_map.insert(grid_area_name,
GridArea(GridSpan::TranslatedDefiniteGridSpan(
row_count, row_count + 1),
GridSpan::TranslatedDefiniteGridSpan(
current_column, look_ahead_column)));
} else {
GridArea& grid_area = grid_area_it->value;
// The following checks test that the grid area is a single filled-in
// rectangle.
// 1. The new row is adjacent to the previously parsed row.
if (row_count != grid_area.rows.EndLine())
return false;
// 2. The new area starts at the same position as the previously parsed
// area.
if (current_column != grid_area.columns.StartLine())
return false;
// 3. The new area ends at the same position as the previously parsed
// area.
if (look_ahead_column != grid_area.columns.EndLine())
return false;
grid_area.rows = GridSpan::TranslatedDefiniteGridSpan(
grid_area.rows.StartLine(), grid_area.rows.EndLine() + 1);
}
current_column = look_ahead_column - 1;
}
return true;
}
CSSValue* ConsumeGridTemplatesRowsOrColumns(CSSParserTokenRange& range,
CSSParserMode css_parser_mode) {
if (range.Peek().Id() == CSSValueNone)
return CSSPropertyParserHelpers::ConsumeIdent(range);
return ConsumeGridTrackList(range, css_parser_mode,
TrackListType::kGridTemplate);
}
bool ConsumeGridItemPositionShorthand(bool important,
CSSParserTokenRange& range,
CSSValue*& start_value,
CSSValue*& end_value) {
// Input should be nullptrs.
DCHECK(!start_value);
DCHECK(!end_value);
start_value = ConsumeGridLine(range);
if (!start_value)
return false;
if (CSSPropertyParserHelpers::ConsumeSlashIncludingWhitespace(range)) {
end_value = ConsumeGridLine(range);
if (!end_value)
return false;
} else {
end_value = start_value->IsCustomIdentValue()
? start_value
: CSSIdentifierValue::Create(CSSValueAuto);
}
return range.AtEnd();
}
bool ConsumeGridTemplateShorthand(bool important,
CSSParserTokenRange& range,
const CSSParserContext& context,
CSSValue*& template_rows,
CSSValue*& template_columns,
CSSValue*& template_areas) {
DCHECK(!template_rows);
DCHECK(!template_columns);
DCHECK(!template_areas);
DCHECK_EQ(gridTemplateShorthand().length(), 3u);
CSSParserTokenRange range_copy = range;
template_rows = CSSPropertyParserHelpers::ConsumeIdent<CSSValueNone>(range);
// 1- 'none' case.
if (template_rows && range.AtEnd()) {
template_rows = CSSIdentifierValue::Create(CSSValueNone);
template_columns = CSSIdentifierValue::Create(CSSValueNone);
template_areas = CSSIdentifierValue::Create(CSSValueNone);
return true;
}
// 2- <grid-template-rows> / <grid-template-columns>
if (!template_rows) {
template_rows = ConsumeGridTrackList(range, context.Mode(),
TrackListType::kGridTemplate);
}
if (template_rows) {
if (!CSSPropertyParserHelpers::ConsumeSlashIncludingWhitespace(range))
return false;
template_columns = ConsumeGridTemplatesRowsOrColumns(range, context.Mode());
if (!template_columns || !range.AtEnd())
return false;
template_areas = CSSIdentifierValue::Create(CSSValueNone);
return true;
}
// 3- [ <line-names>? <string> <track-size>? <line-names>? ]+
// [ / <track-list> ]?
range = range_copy;
return ConsumeGridTemplateRowsAndAreasAndColumns(
important, range, context, template_rows, template_columns,
template_areas);
}
bool ConsumeFromPageBreakBetween(CSSParserTokenRange& range,
CSSValueID& value) {
if (!ConsumeCSSValueId(range, value)) {
return false;
}
if (value == CSSValueAlways) {
value = CSSValuePage;
return true;
}
return value == CSSValueAuto || value == CSSValueAvoid ||
value == CSSValueLeft || value == CSSValueRight;
}
bool ConsumeFromColumnBreakBetween(CSSParserTokenRange& range,
CSSValueID& value) {
if (!ConsumeCSSValueId(range, value)) {
return false;
}
if (value == CSSValueAlways) {
value = CSSValueColumn;
return true;
}
return value == CSSValueAuto || value == CSSValueAvoid;
}
bool ConsumeFromColumnOrPageBreakInside(CSSParserTokenRange& range,
CSSValueID& value) {
if (!ConsumeCSSValueId(range, value)) {
return false;
}
return value == CSSValueAuto || value == CSSValueAvoid;
}
bool ValidWidthOrHeightKeyword(CSSValueID id, const CSSParserContext& context) {
if (id == CSSValueWebkitMinContent || id == CSSValueWebkitMaxContent ||
id == CSSValueWebkitFillAvailable || id == CSSValueWebkitFitContent ||
id == CSSValueMinContent || id == CSSValueMaxContent ||
id == CSSValueFitContent) {
switch (id) {
case CSSValueWebkitMinContent:
context.Count(WebFeature::kCSSValuePrefixedMinContent);
break;
case CSSValueWebkitMaxContent:
context.Count(WebFeature::kCSSValuePrefixedMaxContent);
break;
case CSSValueWebkitFillAvailable:
context.Count(WebFeature::kCSSValuePrefixedFillAvailable);
break;
case CSSValueWebkitFitContent:
context.Count(WebFeature::kCSSValuePrefixedFitContent);
break;
default:
break;
}
return true;
}
return false;
}
CSSValue* ConsumePath(CSSParserTokenRange& range) {
// FIXME: Add support for <url>, <basic-shape>, <geometry-box>.
if (range.Peek().FunctionId() != CSSValuePath)
return nullptr;
CSSParserTokenRange function_range = range;
CSSParserTokenRange function_args =
CSSPropertyParserHelpers::ConsumeFunction(function_range);
if (function_args.Peek().GetType() != kStringToken)
return nullptr;
String path_string =
function_args.ConsumeIncludingWhitespace().Value().ToString();
std::unique_ptr<SVGPathByteStream> byte_stream = SVGPathByteStream::Create();
if (BuildByteStreamFromString(path_string, *byte_stream) !=
SVGParseStatus::kNoError ||
!function_args.AtEnd()) {
return nullptr;
}
range = function_range;
if (byte_stream->IsEmpty())
return CSSIdentifierValue::Create(CSSValueNone);
return CSSPathValue::Create(std::move(byte_stream));
}
CSSValue* ConsumeRay(CSSParserTokenRange& range,
const CSSParserContext& context) {
DCHECK_EQ(range.Peek().FunctionId(), CSSValueRay);
CSSParserTokenRange function_range = range;
CSSParserTokenRange function_args =
CSSPropertyParserHelpers::ConsumeFunction(function_range);
CSSPrimitiveValue* angle = nullptr;
CSSIdentifierValue* size = nullptr;
CSSIdentifierValue* contain = nullptr;
while (!function_args.AtEnd()) {
if (!angle) {
angle = CSSPropertyParserHelpers::ConsumeAngle(
function_args, &context, WTF::Optional<WebFeature>());
if (angle)
continue;
}
if (!size) {
size = CSSPropertyParserHelpers::ConsumeIdent<
CSSValueClosestSide, CSSValueClosestCorner, CSSValueFarthestSide,
CSSValueFarthestCorner, CSSValueSides>(function_args);
if (size)
continue;
}
if (RuntimeEnabledFeatures::CSSOffsetPathRayContainEnabled() && !contain) {
contain = CSSPropertyParserHelpers::ConsumeIdent<CSSValueContain>(
function_args);
if (contain)
continue;
}
return nullptr;
}
if (!angle || !size)
return nullptr;
range = function_range;
return CSSRayValue::Create(*angle, *size, contain);
}
CSSValue* ConsumeMaxWidthOrHeight(
CSSParserTokenRange& range,
const CSSParserContext& context,
CSSPropertyParserHelpers::UnitlessQuirk unitless) {
if (range.Peek().Id() == CSSValueNone ||
ValidWidthOrHeightKeyword(range.Peek().Id(), context))
return CSSPropertyParserHelpers::ConsumeIdent(range);
return CSSPropertyParserHelpers::ConsumeLengthOrPercent(
range, context.Mode(), kValueRangeNonNegative, unitless);
}
CSSValue* ConsumeWidthOrHeight(
CSSParserTokenRange& range,
const CSSParserContext& context,
CSSPropertyParserHelpers::UnitlessQuirk unitless) {
if (range.Peek().Id() == CSSValueAuto ||
ValidWidthOrHeightKeyword(range.Peek().Id(), context))
return CSSPropertyParserHelpers::ConsumeIdent(range);
return CSSPropertyParserHelpers::ConsumeLengthOrPercent(
range, context.Mode(), kValueRangeNonNegative, unitless);
}
CSSValue* ConsumeMarginOrOffset(
CSSParserTokenRange& range,
CSSParserMode css_parser_mode,
CSSPropertyParserHelpers::UnitlessQuirk unitless) {
if (range.Peek().Id() == CSSValueAuto)
return CSSPropertyParserHelpers::ConsumeIdent(range);
return CSSPropertyParserHelpers::ConsumeLengthOrPercent(
range, css_parser_mode, kValueRangeAll, unitless);
}
CSSValue* ConsumeOffsetPath(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValue* value = nullptr;
if (RuntimeEnabledFeatures::CSSOffsetPathRayEnabled() &&
range.Peek().FunctionId() == CSSValueRay)
value = ConsumeRay(range, context);
else
value = ConsumePathOrNone(range);
// Count when we receive a valid path other than 'none'.
if (value && !value->IsIdentifierValue())
context.Count(WebFeature::kCSSOffsetInEffect);
return value;
}
CSSValue* ConsumePathOrNone(CSSParserTokenRange& range) {
CSSValueID id = range.Peek().Id();
if (id == CSSValueNone)
return CSSPropertyParserHelpers::ConsumeIdent(range);
return ConsumePath(range);
}
CSSValue* ConsumeOffsetRotate(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValue* angle = CSSPropertyParserHelpers::ConsumeAngle(
range, &context, Optional<WebFeature>());
CSSValue* keyword =
CSSPropertyParserHelpers::ConsumeIdent<CSSValueAuto, CSSValueReverse>(
range);
if (!angle && !keyword)
return nullptr;
if (!angle) {
angle = CSSPropertyParserHelpers::ConsumeAngle(range, &context,
Optional<WebFeature>());
}
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
if (keyword)
list->Append(*keyword);
if (angle)
list->Append(*angle);
return list;
}
bool ConsumePlaceAlignment(CSSParserTokenRange& range,
ConsumePlaceAlignmentValue consume_alignment_value,
CSSValue*& align_value,
CSSValue*& justify_value) {
DCHECK(consume_alignment_value);
DCHECK(!align_value);
DCHECK(!justify_value);
bool is_baseline = IsBaselineKeyword(range.Peek().Id());
align_value = consume_alignment_value(range, IsSelfPositionKeyword);
if (!align_value)
return false;
// justify-content property does not allow the <baseline-position> values.
if (consume_alignment_value == ConsumeSimplifiedContentPosition) {
if (range.AtEnd() && is_baseline)
return false;
if (IsBaselineKeyword(range.Peek().Id()))
return false;
}
justify_value =
range.AtEnd()
? align_value
: consume_alignment_value(range, IsSelfPositionOrLeftOrRightKeyword);
return justify_value && range.AtEnd();
}
bool ConsumeRadii(CSSValue* horizontal_radii[4],
CSSValue* vertical_radii[4],
CSSParserTokenRange& range,
CSSParserMode css_parser_mode,
bool use_legacy_parsing) {
unsigned i = 0;
for (; i < 4 && !range.AtEnd() && range.Peek().GetType() != kDelimiterToken;
++i) {
horizontal_radii[i] = CSSPropertyParserHelpers::ConsumeLengthOrPercent(
range, css_parser_mode, kValueRangeNonNegative);
if (!horizontal_radii[i])
return false;
}
if (!horizontal_radii[0])
return false;
if (range.AtEnd()) {
// Legacy syntax: -webkit-border-radius: l1 l2; is equivalent to
// border-radius: l1 / l2;
if (use_legacy_parsing && i == 2) {
vertical_radii[0] = horizontal_radii[1];
horizontal_radii[1] = nullptr;
} else {
CSSPropertyParserHelpers::Complete4Sides(horizontal_radii);
for (unsigned i = 0; i < 4; ++i)
vertical_radii[i] = horizontal_radii[i];
return true;
}
} else {
if (!CSSPropertyParserHelpers::ConsumeSlashIncludingWhitespace(range))
return false;
for (i = 0; i < 4 && !range.AtEnd(); ++i) {
vertical_radii[i] = CSSPropertyParserHelpers::ConsumeLengthOrPercent(
range, css_parser_mode, kValueRangeNonNegative);
if (!vertical_radii[i])
return false;
}
if (!vertical_radii[0] || !range.AtEnd())
return false;
}
CSSPropertyParserHelpers::Complete4Sides(horizontal_radii);
CSSPropertyParserHelpers::Complete4Sides(vertical_radii);
return true;
}
CSSValue* ConsumeBasicShape(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValue* shape = nullptr;
if (range.Peek().GetType() != kFunctionToken)
return nullptr;
CSSValueID id = range.Peek().FunctionId();
CSSParserTokenRange range_copy = range;
CSSParserTokenRange args =
CSSPropertyParserHelpers::ConsumeFunction(range_copy);
if (id == CSSValueCircle)
shape = ConsumeBasicShapeCircle(args, context);
else if (id == CSSValueEllipse)
shape = ConsumeBasicShapeEllipse(args, context);
else if (id == CSSValuePolygon)
shape = ConsumeBasicShapePolygon(args, context);
else if (id == CSSValueInset)
shape = ConsumeBasicShapeInset(args, context);
if (!shape || !args.AtEnd())
return nullptr;
context.Count(WebFeature::kCSSBasicShape);
range = range_copy;
return shape;
}
CSSValue* ConsumeTextDecorationLine(CSSParserTokenRange& range) {
CSSValueID id = range.Peek().Id();
if (id == CSSValueNone)
return CSSPropertyParserHelpers::ConsumeIdent(range);
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
while (true) {
CSSIdentifierValue* ident =
CSSPropertyParserHelpers::ConsumeIdent<CSSValueBlink, CSSValueUnderline,
CSSValueOverline,
CSSValueLineThrough>(range);
if (!ident)
break;
if (list->HasValue(*ident))
return nullptr;
list->Append(*ident);
}
if (!list->length())
return nullptr;
return list;
}
CSSValue* ConsumeTransformList(CSSParserTokenRange& range,
const CSSParserContext& context,
const CSSParserLocalContext& local_context) {
if (range.Peek().Id() == CSSValueNone)
return CSSPropertyParserHelpers::ConsumeIdent(range);
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
do {
CSSValue* parsed_transform_value =
ConsumeTransformValue(range, context, local_context.UseAliasParsing());
if (!parsed_transform_value)
return nullptr;
list->Append(*parsed_transform_value);
} while (!range.AtEnd());
return list;
}
CSSValue* ConsumeTransitionProperty(CSSParserTokenRange& range) {
const CSSParserToken& token = range.Peek();
if (token.GetType() != kIdentToken)
return nullptr;
if (token.Id() == CSSValueNone)
return CSSPropertyParserHelpers::ConsumeIdent(range);
CSSPropertyID unresolved_property = token.ParseAsUnresolvedCSSPropertyID();
if (unresolved_property != CSSPropertyInvalid &&
unresolved_property != CSSPropertyVariable) {
#if DCHECK_IS_ON()
DCHECK(CSSProperty::Get(resolveCSSPropertyID(unresolved_property))
.IsEnabled());
#endif
range.ConsumeIncludingWhitespace();
return CSSCustomIdentValue::Create(unresolved_property);
}
return CSSPropertyParserHelpers::ConsumeCustomIdent(range);
}
bool IsValidPropertyList(const CSSValueList& value_list) {
if (value_list.length() < 2)
return true;
for (auto& value : value_list) {
if (value->IsIdentifierValue() &&
ToCSSIdentifierValue(*value).GetValueID() == CSSValueNone)
return false;
}
return true;
}
CSSValue* ConsumeBorderColorSide(CSSParserTokenRange& range,
const CSSParserContext& context,
const CSSParserLocalContext& local_context) {
CSSPropertyID shorthand = local_context.CurrentShorthand();
bool allow_quirky_colors =
IsQuirksModeBehavior(context.Mode()) &&
(shorthand == CSSPropertyInvalid || shorthand == CSSPropertyBorderColor);
return CSSPropertyParserHelpers::ConsumeColor(range, context.Mode(),
allow_quirky_colors);
}
CSSValue* ConsumeBorderWidth(CSSParserTokenRange& range,
CSSParserMode css_parser_mode,
CSSPropertyParserHelpers::UnitlessQuirk unitless) {
return CSSPropertyParserHelpers::ConsumeLineWidth(range, css_parser_mode,
unitless);
}
CSSValue* ParseSpacing(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueNormal)
return CSSPropertyParserHelpers::ConsumeIdent(range);
// TODO(timloh): allow <percentage>s in word-spacing.
return CSSPropertyParserHelpers::ConsumeLength(
range, context.Mode(), kValueRangeAll,
CSSPropertyParserHelpers::UnitlessQuirk::kAllow);
}
CSSValue* ParsePaintStroke(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueNone)
return CSSPropertyParserHelpers::ConsumeIdent(range);
CSSURIValue* url = CSSPropertyParserHelpers::ConsumeUrl(range, &context);
if (url) {
CSSValue* parsed_value = nullptr;
if (range.Peek().Id() == CSSValueNone) {
parsed_value = CSSPropertyParserHelpers::ConsumeIdent(range);
} else {
parsed_value =
CSSPropertyParserHelpers::ConsumeColor(range, context.Mode());
}
if (parsed_value) {
CSSValueList* values = CSSValueList::CreateSpaceSeparated();
values->Append(*url);
values->Append(*parsed_value);
return values;
}
return url;
}
return CSSPropertyParserHelpers::ConsumeColor(range, context.Mode());
}
} // namespace CSSParsingUtils
} // namespace blink