blob: 5cc8589bfff6442244dbd7e0b8ee2cf13e8940e2 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/css/properties/css_parsing_utils.h"
#include <memory>
#include <utility>
#include "third_party/blink/renderer/core/css/counter_style_map.h"
#include "third_party/blink/renderer/core/css/css_axis_value.h"
#include "third_party/blink/renderer/core/css/css_basic_shape_values.h"
#include "third_party/blink/renderer/core/css/css_border_image.h"
#include "third_party/blink/renderer/core/css/css_bracketed_value_list.h"
#include "third_party/blink/renderer/core/css/css_color.h"
#include "third_party/blink/renderer/core/css/css_color_mix_value.h"
#include "third_party/blink/renderer/core/css/css_content_distribution_value.h"
#include "third_party/blink/renderer/core/css/css_crossfade_value.h"
#include "third_party/blink/renderer/core/css/css_custom_ident_value.h"
#include "third_party/blink/renderer/core/css/css_font_family_value.h"
#include "third_party/blink/renderer/core/css/css_font_feature_value.h"
#include "third_party/blink/renderer/core/css/css_font_style_range_value.h"
#include "third_party/blink/renderer/core/css/css_function_value.h"
#include "third_party/blink/renderer/core/css/css_gradient_value.h"
#include "third_party/blink/renderer/core/css/css_grid_auto_repeat_value.h"
#include "third_party/blink/renderer/core/css/css_grid_integer_repeat_value.h"
#include "third_party/blink/renderer/core/css/css_grid_template_areas_value.h"
#include "third_party/blink/renderer/core/css/css_identifier_value.h"
#include "third_party/blink/renderer/core/css/css_image_set_option_value.h"
#include "third_party/blink/renderer/core/css/css_image_set_type_value.h"
#include "third_party/blink/renderer/core/css/css_image_set_value.h"
#include "third_party/blink/renderer/core/css/css_image_value.h"
#include "third_party/blink/renderer/core/css/css_inherited_value.h"
#include "third_party/blink/renderer/core/css/css_initial_value.h"
#include "third_party/blink/renderer/core/css/css_light_dark_value_pair.h"
#include "third_party/blink/renderer/core/css/css_math_expression_node.h"
#include "third_party/blink/renderer/core/css/css_math_function_value.h"
#include "third_party/blink/renderer/core/css/css_numeric_literal_value.h"
#include "third_party/blink/renderer/core/css/css_paint_value.h"
#include "third_party/blink/renderer/core/css/css_path_value.h"
#include "third_party/blink/renderer/core/css/css_primitive_value.h"
#include "third_party/blink/renderer/core/css/css_property_names.h"
#include "third_party/blink/renderer/core/css/css_property_value.h"
#include "third_party/blink/renderer/core/css/css_ratio_value.h"
#include "third_party/blink/renderer/core/css/css_ray_value.h"
#include "third_party/blink/renderer/core/css/css_revert_layer_value.h"
#include "third_party/blink/renderer/core/css/css_revert_value.h"
#include "third_party/blink/renderer/core/css/css_scroll_value.h"
#include "third_party/blink/renderer/core/css/css_shadow_value.h"
#include "third_party/blink/renderer/core/css/css_string_value.h"
#include "third_party/blink/renderer/core/css/css_timing_function_value.h"
#include "third_party/blink/renderer/core/css/css_unset_value.h"
#include "third_party/blink/renderer/core/css/css_uri_value.h"
#include "third_party/blink/renderer/core/css/css_value.h"
#include "third_party/blink/renderer/core/css/css_value_list.h"
#include "third_party/blink/renderer/core/css/css_value_pair.h"
#include "third_party/blink/renderer/core/css/css_variable_data.h"
#include "third_party/blink/renderer/core/css/css_view_value.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_fast_paths.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_idioms.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_local_context.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_mode.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_token.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_token_range.h"
#include "third_party/blink/renderer/core/css/parser/css_variable_parser.h"
#include "third_party/blink/renderer/core/css/properties/css_parsing_utils.h"
#include "third_party/blink/renderer/core/css/properties/css_property.h"
#include "third_party/blink/renderer/core/css/properties/longhand.h"
#include "third_party/blink/renderer/core/css/style_color.h"
#include "third_party/blink/renderer/core/css_value_keywords.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/web_feature.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/style_property_shorthand.h"
#include "third_party/blink/renderer/core/svg/svg_parsing_error.h"
#include "third_party/blink/renderer/core/svg/svg_path_utilities.h"
#include "third_party/blink/renderer/platform/animation/timing_function.h"
#include "third_party/blink/renderer/platform/fonts/font_selection_types.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "ui/gfx/animation/keyframe/timing_function.h"
#include "ui/gfx/color_utils.h"
namespace blink {
using cssvalue::CSSBracketedValueList;
using cssvalue::CSSFontFeatureValue;
namespace css_parsing_utils {
namespace {
const char kTwoDashes[] = "--";
bool IsLeftOrRightKeyword(CSSValueID id) {
return IdentMatches<CSSValueID::kLeft, CSSValueID::kRight>(id);
}
bool IsAuto(CSSValueID id) {
return IdentMatches<CSSValueID::kAuto>(id);
}
bool IsNormalOrStretch(CSSValueID id) {
return IdentMatches<CSSValueID::kNormal, CSSValueID::kStretch>(id);
}
bool IsContentDistributionKeyword(CSSValueID id) {
return IdentMatches<CSSValueID::kSpaceBetween, CSSValueID::kSpaceAround,
CSSValueID::kSpaceEvenly, CSSValueID::kStretch>(id);
}
bool IsOverflowKeyword(CSSValueID id) {
return IdentMatches<CSSValueID::kUnsafe, CSSValueID::kSafe>(id);
}
bool IsIdent(const CSSValue& value, CSSValueID id) {
const auto* ident = DynamicTo<CSSIdentifierValue>(value);
return ident && ident->GetValueID() == id;
}
CSSIdentifierValue* ConsumeOverflowPositionKeyword(CSSParserTokenRange& range) {
return IsOverflowKeyword(range.Peek().Id()) ? ConsumeIdent(range) : nullptr;
}
CSSValueID GetBaselineKeyword(CSSValue& value) {
auto* value_pair = DynamicTo<CSSValuePair>(value);
if (!value_pair) {
DCHECK(To<CSSIdentifierValue>(value).GetValueID() == CSSValueID::kBaseline);
return CSSValueID::kBaseline;
}
DCHECK(To<CSSIdentifierValue>(value_pair->First()).GetValueID() ==
CSSValueID::kLast);
DCHECK(To<CSSIdentifierValue>(value_pair->Second()).GetValueID() ==
CSSValueID::kBaseline);
return CSSValueID::kLastBaseline;
}
CSSValue* ConsumeFirstBaseline(CSSParserTokenRange& range) {
ConsumeIdent<CSSValueID::kFirst>(range);
return ConsumeIdent<CSSValueID::kBaseline>(range);
}
CSSValue* ConsumeBaseline(CSSParserTokenRange& range) {
CSSIdentifierValue* preference =
ConsumeIdent<CSSValueID::kFirst, CSSValueID::kLast>(range);
CSSIdentifierValue* baseline = ConsumeIdent<CSSValueID::kBaseline>(range);
if (!baseline) {
return nullptr;
}
if (preference && preference->GetValueID() == CSSValueID::kLast) {
return MakeGarbageCollected<CSSValuePair>(
preference, baseline, CSSValuePair::kDropIdenticalValues);
}
return baseline;
}
absl::optional<cssvalue::CSSLinearStop> ConsumeLinearStop(
CSSParserTokenRange& range,
const CSSParserContext& context) {
absl::optional<double> number;
absl::optional<double> length_a;
absl::optional<double> length_b;
while (!range.AtEnd()) {
if (range.Peek().GetType() == kCommaToken) {
break;
}
CSSPrimitiveValue* value =
ConsumeNumber(range, context, CSSPrimitiveValue::ValueRange::kAll);
if (!number.has_value() && value && value->IsNumber()) {
number = value->GetDoubleValue();
continue;
}
value = ConsumePercent(range, context, CSSPrimitiveValue::ValueRange::kAll);
if (!length_a.has_value() && value && value->IsPercentage()) {
length_a = value->GetDoubleValue();
value =
ConsumePercent(range, context, CSSPrimitiveValue::ValueRange::kAll);
if (value && value->IsPercentage()) {
length_b = value->GetDoubleValue();
}
continue;
}
return {};
}
if (!number.has_value()) {
return {};
}
return {{number.value(), length_a, length_b}};
}
CSSValue* ConsumeLinear(CSSParserTokenRange& range,
const CSSParserContext& context) {
// https://w3c.github.io/csswg-drafts/css-easing/#linear-easing-function-parsing
DCHECK_EQ(range.Peek().FunctionId(), CSSValueID::kLinear);
CSSParserTokenRange range_copy = range;
CSSParserTokenRange args = ConsumeFunction(range_copy);
Vector<cssvalue::CSSLinearStop> stop_list{};
absl::optional<cssvalue::CSSLinearStop> linear_stop;
do {
linear_stop = ConsumeLinearStop(args, context);
if (!linear_stop.has_value()) {
return nullptr;
}
stop_list.emplace_back(linear_stop.value());
} while (ConsumeCommaIncludingWhitespace(args));
if (!args.AtEnd()) {
return nullptr;
}
// 1. Let function be a new linear easing function.
// 2. Let largestInput be negative infinity.
// 3. If there are less than two items in stopList, then return failure.
if (stop_list.size() < 2) {
return nullptr;
}
// 4. For each stop in stopList:
double largest_input = std::numeric_limits<double>::lowest();
Vector<gfx::LinearEasingPoint> points{};
for (wtf_size_t i = 0; i < stop_list.size(); ++i) {
const auto& stop = stop_list[i];
// 4.1. Let point be a new linear easing point with its output set
// to stop’s <number> as a number.
gfx::LinearEasingPoint point{std::numeric_limits<double>::quiet_NaN(),
stop.number};
// 4.2. Append point to function’s points.
points.emplace_back(point);
// 4.3. If stop has a <linear-stop-length>, then:
if (stop.length_a.has_value()) {
// 4.3.1. Set point’s input to whichever is greater:
// stop’s <linear-stop-length>'s first <percentage> as a number,
// or largestInput.
points.back().input = std::max(largest_input, stop.length_a.value());
// 4.3.2. Set largestInput to point’s input.
largest_input = points.back().input;
// 4.3.3. If stop’s <linear-stop-length> has a second <percentage>, then:
if (stop.length_b.has_value()) {
// 4.3.3.1. Let extraPoint be a new linear easing point with its output
// set to stop’s <number> as a number.
gfx::LinearEasingPoint extra_point{
// 4.3.3.3. Set extraPoint’s input to whichever is greater:
// stop’s <linear-stop-length>'s second <percentage>
// as a number, or largestInput.
std::max(largest_input, stop.length_b.value()), stop.number};
// 4.3.3.2. Append extraPoint to function’s points.
points.emplace_back(extra_point);
// 4.3.3.4. Set largestInput to extraPoint’s input.
largest_input = extra_point.input;
}
// 4.4. Otherwise, if stop is the first item in stopList, then:
} else if (i == 0) {
// 4.4.1. Set point’s input to 0.
points.back().input = 0;
// 4.4.2. Set largestInput to 0.
largest_input = 0;
// 4.5. Otherwise, if stop is the last item in stopList,
// then set point’s input to whichever is greater: 1 or largestInput.
} else if (i == stop_list.size() - 1) {
points.back().input = std::max(100., largest_input);
}
}
// 5. For runs of items in function’s points that have a null input, assign a
// number to the input by linearly interpolating between the closest previous
// and next points that have a non-null input.
wtf_size_t upper_index = 0;
for (wtf_size_t i = 1; i < points.size(); ++i) {
if (std::isnan(points[i].input)) {
if (i > upper_index) {
const auto* it = std::find_if(
std::next(points.begin(), i + 1), points.end(),
[](const auto& point) { return !std::isnan(point.input); });
upper_index = static_cast<wtf_size_t>(it - points.begin());
}
points[i].input = points[i - 1].input +
(points[upper_index].input - points[i - 1].input) /
(upper_index - (i - 1));
}
}
range = range_copy;
// 6. Return function.
return MakeGarbageCollected<cssvalue::CSSLinearTimingFunctionValue>(
std::move(points));
}
CSSValue* ConsumeSteps(CSSParserTokenRange& range,
const CSSParserContext& context) {
DCHECK_EQ(range.Peek().FunctionId(), CSSValueID::kSteps);
CSSParserTokenRange range_copy = range;
CSSParserTokenRange args = ConsumeFunction(range_copy);
CSSPrimitiveValue* steps = ConsumePositiveInteger(args, context);
if (!steps) {
return nullptr;
}
StepsTimingFunction::StepPosition position =
StepsTimingFunction::StepPosition::END;
if (ConsumeCommaIncludingWhitespace(args)) {
switch (args.ConsumeIncludingWhitespace().Id()) {
case CSSValueID::kStart:
position = StepsTimingFunction::StepPosition::START;
break;
case CSSValueID::kEnd:
position = StepsTimingFunction::StepPosition::END;
break;
case CSSValueID::kJumpBoth:
position = StepsTimingFunction::StepPosition::JUMP_BOTH;
break;
case CSSValueID::kJumpEnd:
position = StepsTimingFunction::StepPosition::JUMP_END;
break;
case CSSValueID::kJumpNone:
position = StepsTimingFunction::StepPosition::JUMP_NONE;
break;
case CSSValueID::kJumpStart:
position = StepsTimingFunction::StepPosition::JUMP_START;
break;
default:
return nullptr;
}
}
if (!args.AtEnd()) {
return nullptr;
}
// Steps(n, jump-none) requires n >= 2.
if (position == StepsTimingFunction::StepPosition::JUMP_NONE &&
steps->GetIntValue() < 2) {
return nullptr;
}
range = range_copy;
return MakeGarbageCollected<cssvalue::CSSStepsTimingFunctionValue>(
steps->GetIntValue(), position);
}
CSSValue* ConsumeCubicBezier(CSSParserTokenRange& range,
const CSSParserContext& context) {
DCHECK_EQ(range.Peek().FunctionId(), CSSValueID::kCubicBezier);
CSSParserTokenRange range_copy = range;
CSSParserTokenRange args = ConsumeFunction(range_copy);
double x1, y1, x2, y2;
if (ConsumeNumberRaw(args, context, x1) && x1 >= 0 && x1 <= 1 &&
ConsumeCommaIncludingWhitespace(args) &&
ConsumeNumberRaw(args, context, y1) &&
ConsumeCommaIncludingWhitespace(args) &&
ConsumeNumberRaw(args, context, x2) && x2 >= 0 && x2 <= 1 &&
ConsumeCommaIncludingWhitespace(args) &&
ConsumeNumberRaw(args, context, y2) && args.AtEnd()) {
range = range_copy;
return MakeGarbageCollected<cssvalue::CSSCubicBezierTimingFunctionValue>(
x1, y1, x2, y2);
}
return nullptr;
}
CSSIdentifierValue* ConsumeBorderImageRepeatKeyword(
CSSParserTokenRange& range) {
return ConsumeIdent<CSSValueID::kStretch, CSSValueID::kRepeat,
CSSValueID::kSpace, CSSValueID::kRound>(range);
}
bool ConsumeCSSValueId(CSSParserTokenRange& range, CSSValueID& value) {
CSSIdentifierValue* keyword = ConsumeIdent(range);
if (!keyword || !range.AtEnd()) {
return false;
}
value = keyword->GetValueID();
return true;
}
CSSValue* ConsumeShapeRadius(CSSParserTokenRange& args,
const CSSParserContext& context) {
if (IdentMatches<CSSValueID::kClosestSide, CSSValueID::kFarthestSide>(
args.Peek().Id())) {
return ConsumeIdent(args);
}
return ConsumeLengthOrPercent(args, context,
CSSPrimitiveValue::ValueRange::kNonNegative);
}
cssvalue::CSSBasicShapeCircleValue* ConsumeBasicShapeCircle(
CSSParserTokenRange& args,
const CSSParserContext& context) {
// spec: https://drafts.csswg.org/css-shapes/#supported-basic-shapes
// circle( [<shape-radius>]? [at <position>]? )
auto* shape = MakeGarbageCollected<cssvalue::CSSBasicShapeCircleValue>();
if (CSSValue* radius = ConsumeShapeRadius(args, context)) {
shape->SetRadius(radius);
}
if (ConsumeIdent<CSSValueID::kAt>(args)) {
CSSValue* center_x = nullptr;
CSSValue* center_y = nullptr;
if (!ConsumePosition(args, context, UnitlessQuirk::kForbid,
absl::optional<WebFeature>(), center_x, center_y)) {
return nullptr;
}
shape->SetCenterX(center_x);
shape->SetCenterY(center_y);
}
return shape;
}
cssvalue::CSSBasicShapeEllipseValue* ConsumeBasicShapeEllipse(
CSSParserTokenRange& args,
const CSSParserContext& context) {
// spec: https://drafts.csswg.org/css-shapes/#supported-basic-shapes
// ellipse( [<shape-radius>{2}]? [at <position>]? )
auto* shape = MakeGarbageCollected<cssvalue::CSSBasicShapeEllipseValue>();
WebFeature feature = WebFeature::kBasicShapeEllipseNoRadius;
if (CSSValue* radius_x = ConsumeShapeRadius(args, context)) {
CSSValue* radius_y = ConsumeShapeRadius(args, context);
if (!radius_y) {
return nullptr;
}
shape->SetRadiusX(radius_x);
shape->SetRadiusY(radius_y);
feature = WebFeature::kBasicShapeEllipseTwoRadius;
}
if (ConsumeIdent<CSSValueID::kAt>(args)) {
CSSValue* center_x = nullptr;
CSSValue* center_y = nullptr;
if (!ConsumePosition(args, context, UnitlessQuirk::kForbid,
absl::optional<WebFeature>(), center_x, center_y)) {
return nullptr;
}
shape->SetCenterX(center_x);
shape->SetCenterY(center_y);
}
context.Count(feature);
return shape;
}
cssvalue::CSSBasicShapePolygonValue* ConsumeBasicShapePolygon(
CSSParserTokenRange& args,
const CSSParserContext& context) {
auto* shape = MakeGarbageCollected<cssvalue::CSSBasicShapePolygonValue>();
if (IdentMatches<CSSValueID::kEvenodd, CSSValueID::kNonzero>(
args.Peek().Id())) {
shape->SetWindRule(args.ConsumeIncludingWhitespace().Id() ==
CSSValueID::kEvenodd
? RULE_EVENODD
: RULE_NONZERO);
if (!ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
}
do {
CSSPrimitiveValue* x_length = ConsumeLengthOrPercent(
args, context, CSSPrimitiveValue::ValueRange::kAll);
if (!x_length) {
return nullptr;
}
CSSPrimitiveValue* y_length = ConsumeLengthOrPercent(
args, context, CSSPrimitiveValue::ValueRange::kAll);
if (!y_length) {
return nullptr;
}
shape->AppendPoint(x_length, y_length);
} while (ConsumeCommaIncludingWhitespace(args));
return shape;
}
template <typename T>
bool ConsumeBorderRadiusCommon(CSSParserTokenRange& args,
const CSSParserContext& context,
T* shape) {
if (ConsumeIdent<CSSValueID::kRound>(args)) {
CSSValue* horizontal_radii[4] = {nullptr};
CSSValue* vertical_radii[4] = {nullptr};
if (!ConsumeRadii(horizontal_radii, vertical_radii, args, context, false)) {
return false;
}
shape->SetTopLeftRadius(MakeGarbageCollected<CSSValuePair>(
horizontal_radii[0], vertical_radii[0],
CSSValuePair::kDropIdenticalValues));
shape->SetTopRightRadius(MakeGarbageCollected<CSSValuePair>(
horizontal_radii[1], vertical_radii[1],
CSSValuePair::kDropIdenticalValues));
shape->SetBottomRightRadius(MakeGarbageCollected<CSSValuePair>(
horizontal_radii[2], vertical_radii[2],
CSSValuePair::kDropIdenticalValues));
shape->SetBottomLeftRadius(MakeGarbageCollected<CSSValuePair>(
horizontal_radii[3], vertical_radii[3],
CSSValuePair::kDropIdenticalValues));
}
return true;
}
cssvalue::CSSBasicShapeInsetValue* ConsumeBasicShapeInset(
CSSParserTokenRange& args,
const CSSParserContext& context) {
auto* shape = MakeGarbageCollected<cssvalue::CSSBasicShapeInsetValue>();
CSSPrimitiveValue* top = ConsumeLengthOrPercent(
args, context, CSSPrimitiveValue::ValueRange::kAll);
if (!top) {
return nullptr;
}
CSSPrimitiveValue* right = ConsumeLengthOrPercent(
args, context, CSSPrimitiveValue::ValueRange::kAll);
CSSPrimitiveValue* bottom = nullptr;
CSSPrimitiveValue* left = nullptr;
if (right) {
bottom = ConsumeLengthOrPercent(args, context,
CSSPrimitiveValue::ValueRange::kAll);
if (bottom) {
left = ConsumeLengthOrPercent(args, context,
CSSPrimitiveValue::ValueRange::kAll);
}
}
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 (!ConsumeBorderRadiusCommon(args, context, shape)) {
return nullptr;
}
return shape;
}
cssvalue::CSSBasicShapeRectValue* ConsumeBasicShapeRect(
CSSParserTokenRange& args,
const CSSParserContext& context) {
CSSValue* lengths[4];
for (auto*& length : lengths) {
length = ConsumeLengthOrPercent(args, context,
CSSPrimitiveValue::ValueRange::kAll);
if (length) {
continue;
}
if (args.Peek().Id() == CSSValueID::kAuto) {
length = css_parsing_utils::ConsumeIdent(args);
}
if (!length) {
return nullptr;
}
}
auto* shape = MakeGarbageCollected<cssvalue::CSSBasicShapeRectValue>(
lengths[0], lengths[1], lengths[2], lengths[3]);
if (!ConsumeBorderRadiusCommon(args, context, shape)) {
return nullptr;
}
return shape;
}
cssvalue::CSSBasicShapeXYWHValue* ConsumeBasicShapeXYWH(
CSSParserTokenRange& args,
const CSSParserContext& context) {
CSSPrimitiveValue* lengths[4];
for (size_t i = 0; i < 4; i++) {
// The last 2 values are width/height which must be positive.
auto value_range = i > 1 ? CSSPrimitiveValue::ValueRange::kNonNegative
: CSSPrimitiveValue::ValueRange::kAll;
lengths[i] = ConsumeLengthOrPercent(args, context, value_range);
if (!lengths[i]) {
return nullptr;
}
}
auto* shape = MakeGarbageCollected<cssvalue::CSSBasicShapeXYWHValue>(
lengths[0], lengths[1], lengths[2], lengths[3]);
if (!ConsumeBorderRadiusCommon(args, context, shape)) {
return nullptr;
}
return shape;
}
bool ConsumeNumbers(CSSParserTokenRange& args,
const CSSParserContext& context,
CSSFunctionValue*& transform_value,
unsigned number_of_arguments) {
do {
CSSValue* parsed_value =
ConsumeNumber(args, context, CSSPrimitiveValue::ValueRange::kAll);
if (!parsed_value) {
return false;
}
transform_value->Append(*parsed_value);
if (--number_of_arguments && !ConsumeCommaIncludingWhitespace(args)) {
return false;
}
} while (number_of_arguments);
return true;
}
bool ConsumeNumbersOrPercents(CSSParserTokenRange& args,
const CSSParserContext& context,
CSSFunctionValue*& transform_value,
unsigned number_of_arguments) {
do {
CSSValue* parsed_value = ConsumeNumberOrPercent(
args, context, CSSPrimitiveValue::ValueRange::kAll);
if (!parsed_value) {
return false;
}
transform_value->Append(*parsed_value);
if (--number_of_arguments && !ConsumeCommaIncludingWhitespace(args)) {
return false;
}
} while (number_of_arguments);
return true;
}
bool ConsumePerspective(CSSParserTokenRange& args,
const CSSParserContext& context,
CSSFunctionValue*& transform_value,
bool use_legacy_parsing) {
CSSValue* parsed_value =
ConsumeLength(args, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (!parsed_value) {
parsed_value = ConsumeIdent<CSSValueID::kNone>(args);
}
if (!parsed_value && use_legacy_parsing) {
double perspective;
if (!ConsumeNumberRaw(args, context, perspective) || perspective < 0) {
return false;
}
context.Count(WebFeature::kUnitlessPerspectiveInTransformProperty);
parsed_value = CSSNumericLiteralValue::Create(
perspective, CSSPrimitiveValue::UnitType::kPixels);
}
if (!parsed_value) {
return false;
}
transform_value->Append(*parsed_value);
return true;
}
bool ConsumeTranslate3d(CSSParserTokenRange& args,
const CSSParserContext& context,
CSSFunctionValue*& transform_value) {
unsigned number_of_arguments = 2;
CSSValue* parsed_value = nullptr;
do {
parsed_value = ConsumeLengthOrPercent(args, context,
CSSPrimitiveValue::ValueRange::kAll);
if (!parsed_value) {
return false;
}
transform_value->Append(*parsed_value);
if (!ConsumeCommaIncludingWhitespace(args)) {
return false;
}
} while (--number_of_arguments);
parsed_value =
ConsumeLength(args, context, CSSPrimitiveValue::ValueRange::kAll);
if (!parsed_value) {
return false;
}
transform_value->Append(*parsed_value);
return true;
}
// Add CSSVariableData to variableData vector.
bool AddCSSPaintArgument(
const Vector<CSSParserToken>& tokens,
Vector<scoped_refptr<CSSVariableData>>* const variable_data) {
CSSParserTokenRange token_range(tokens);
if (CSSVariableParser::ContainsValidVariableReferences(token_range)) {
return false;
}
if (!token_range.AtEnd()) {
// CSSParserTokenRange doesn't store precise location information about
// where each token started or ended, so we don't have the actual original
// string. However, for CSS paint arguments, it's not a huge issue
// if we get normalized whitespace etc., so we work around it by creating
// a fake “original text” by serializing the tokens back.
String text = token_range.Serialize();
scoped_refptr<CSSVariableData> unparsed_css_variable_data =
CSSVariableData::Create({token_range, text}, false, false);
if (unparsed_css_variable_data.get()) {
variable_data->push_back(std::move(unparsed_css_variable_data));
return true;
}
}
return false;
}
// Consume input arguments, if encounter function, will return the function
// block as a Vector of CSSParserToken, otherwise, will just return a Vector of
// a single CSSParserToken.
Vector<CSSParserToken> ConsumeFunctionArgsOrNot(CSSParserTokenRange& args) {
Vector<CSSParserToken> argument_tokens;
if (args.Peek().GetBlockType() == CSSParserToken::kBlockStart) {
// A block of some type (maybe a function, maybe (), [], or {}).
// Push the block start.
//
// For functions, we don't have any upfront knowledge about the input
// argument types here, we should just leave the token as it is and
// resolve it later in the variable parsing phase.
argument_tokens.push_back(args.Peek());
CSSParserTokenType closing_type =
CSSParserToken::ClosingTokenType(args.Peek().GetType());
CSSParserTokenRange contents = args.ConsumeBlock();
while (!contents.AtEnd()) {
argument_tokens.push_back(contents.Consume());
}
argument_tokens.push_back(
CSSParserToken(closing_type, CSSParserToken::kBlockEnd));
} else {
argument_tokens.push_back(args.ConsumeIncludingWhitespace());
}
return argument_tokens;
}
CSSFunctionValue* ConsumeFilterFunction(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValueID filter_type = range.Peek().FunctionId();
if (filter_type < CSSValueID::kInvert ||
filter_type > CSSValueID::kDropShadow) {
return nullptr;
}
CSSParserTokenRange args = ConsumeFunction(range);
CSSFunctionValue* filter_value =
MakeGarbageCollected<CSSFunctionValue>(filter_type);
CSSValue* parsed_value = nullptr;
if (filter_type == CSSValueID::kDropShadow) {
parsed_value =
ParseSingleShadow(args, context, AllowInsetAndSpread::kForbid);
} else {
if (args.AtEnd()) {
context.Count(WebFeature::kCSSFilterFunctionNoArguments);
return filter_value;
}
if (filter_type == CSSValueID::kBrightness) {
// FIXME (crbug.com/397061): Support calc expressions like calc(10% + 0.5)
parsed_value =
ConsumePercent(args, context, CSSPrimitiveValue::ValueRange::kAll);
if (!parsed_value) {
parsed_value = ConsumeNumber(
args, context, CSSPrimitiveValue::ValueRange::kNonNegative);
}
} else if (filter_type == CSSValueID::kHueRotate) {
parsed_value =
ConsumeAngle(args, context, WebFeature::kUnitlessZeroAngleFilter);
} else if (filter_type == CSSValueID::kBlur) {
CSSParserContext::ParserModeOverridingScope scope(context,
kHTMLStandardMode);
parsed_value = ConsumeLength(args, context,
CSSPrimitiveValue::ValueRange::kNonNegative);
} else {
// FIXME (crbug.com/397061): Support calc expressions like calc(10% + 0.5)
parsed_value = ConsumePercent(
args, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (!parsed_value) {
parsed_value = ConsumeNumber(
args, context, CSSPrimitiveValue::ValueRange::kNonNegative);
}
if (parsed_value && filter_type != CSSValueID::kSaturate &&
filter_type != CSSValueID::kContrast) {
bool is_percentage =
To<CSSPrimitiveValue>(parsed_value)->IsPercentage();
double max_allowed = is_percentage ? 100.0 : 1.0;
if (To<CSSPrimitiveValue>(parsed_value)->GetDoubleValue() >
max_allowed) {
parsed_value = CSSNumericLiteralValue::Create(
max_allowed, is_percentage
? CSSPrimitiveValue::UnitType::kPercentage
: CSSPrimitiveValue::UnitType::kNumber);
}
}
}
}
if (!parsed_value || !args.AtEnd()) {
return nullptr;
}
filter_value->Append(*parsed_value);
return filter_value;
}
template <typename Func, typename... Args>
CSSLightDarkValuePair* ConsumeInternalLightDark(Func consume_value,
CSSParserTokenRange& range,
const CSSParserContext& context,
Args&&... args) {
if (range.Peek().FunctionId() != CSSValueID::kInternalLightDark) {
return nullptr;
}
if (!isValueAllowedInMode(CSSValueID::kInternalLightDark, context.Mode())) {
return nullptr;
}
CSSParserTokenRange range_copy = range;
CSSParserTokenRange arg_range = ConsumeFunction(range_copy);
CSSValue* light_value =
consume_value(arg_range, context, std::forward<Args>(args)...);
if (!light_value || !ConsumeCommaIncludingWhitespace(arg_range)) {
return nullptr;
}
CSSValue* dark_value =
consume_value(arg_range, context, std::forward<Args>(args)...);
if (!dark_value || !arg_range.AtEnd()) {
return nullptr;
}
range = range_copy;
return MakeGarbageCollected<CSSLightDarkValuePair>(light_value, dark_value);
}
// https://drafts.csswg.org/css-syntax/#typedef-any-value
bool IsTokenAllowedForAnyValue(const CSSParserToken& token) {
switch (token.GetType()) {
case kBadStringToken:
case kEOFToken:
case kBadUrlToken:
return false;
case kRightParenthesisToken:
case kRightBracketToken:
case kRightBraceToken:
return token.GetBlockType() == CSSParserToken::kBlockEnd;
default:
return true;
}
}
bool IsGeneratedImage(const CSSValueID id) {
switch (id) {
case CSSValueID::kLinearGradient:
case CSSValueID::kRadialGradient:
case CSSValueID::kConicGradient:
case CSSValueID::kRepeatingLinearGradient:
case CSSValueID::kRepeatingRadialGradient:
case CSSValueID::kRepeatingConicGradient:
case CSSValueID::kWebkitLinearGradient:
case CSSValueID::kWebkitRadialGradient:
case CSSValueID::kWebkitRepeatingLinearGradient:
case CSSValueID::kWebkitRepeatingRadialGradient:
case CSSValueID::kWebkitGradient:
case CSSValueID::kWebkitCrossFade:
case CSSValueID::kPaint:
return true;
default:
return false;
}
}
bool IsImageSet(const CSSValueID id) {
if (id == CSSValueID::kWebkitImageSet) {
return true;
}
if (id == CSSValueID::kImageSet) {
return RuntimeEnabledFeatures::CSSImageSetEnabled();
}
return false;
}
} // namespace
void Complete4Sides(CSSValue* side[4]) {
if (side[3]) {
return;
}
if (!side[2]) {
if (!side[1]) {
side[1] = side[0];
}
side[2] = side[0];
}
side[3] = side[1];
}
bool ConsumeCommaIncludingWhitespace(CSSParserTokenRange& range) {
CSSParserToken value = range.Peek();
if (value.GetType() != kCommaToken) {
return false;
}
range.ConsumeIncludingWhitespace();
return true;
}
bool ConsumeSlashIncludingWhitespace(CSSParserTokenRange& range) {
CSSParserToken value = range.Peek();
if (value.GetType() != kDelimiterToken || value.Delimiter() != '/') {
return false;
}
range.ConsumeIncludingWhitespace();
return true;
}
CSSParserTokenRange ConsumeFunction(CSSParserTokenRange& range) {
DCHECK_EQ(range.Peek().GetType(), kFunctionToken);
CSSParserTokenRange contents = range.ConsumeBlock();
range.ConsumeWhitespace();
contents.ConsumeWhitespace();
return contents;
}
bool ConsumeAnyValue(CSSParserTokenRange& range) {
bool result = IsTokenAllowedForAnyValue(range.Peek());
unsigned nesting_level = 0;
while (nesting_level || result) {
const CSSParserToken& token = range.Consume();
if (token.GetBlockType() == CSSParserToken::kBlockStart) {
nesting_level++;
} else if (token.GetBlockType() == CSSParserToken::kBlockEnd) {
nesting_level--;
}
if (range.AtEnd()) {
return result;
}
result = result && IsTokenAllowedForAnyValue(range.Peek());
}
return result;
}
// TODO(rwlbuis): consider pulling in the parsing logic from
// css_math_expression_node.cc.
class MathFunctionParser {
STACK_ALLOCATED();
public:
MathFunctionParser(
CSSParserTokenRange& range,
const CSSParserContext& context,
CSSPrimitiveValue::ValueRange value_range,
CSSAnchorQueryTypes allowed_anchor_queries = kCSSAnchorQueryTypesNone)
: source_range_(range), range_(range) {
const CSSParserToken& token = range.Peek();
if (token.GetType() == kFunctionToken) {
calc_value_ = CSSMathFunctionValue::Create(
CSSMathExpressionNode::ParseMathFunction(
token.FunctionId(), ConsumeFunction(range_), context,
allowed_anchor_queries),
value_range);
}
if (calc_value_ && calc_value_->HasComparisons()) {
context.Count(WebFeature::kCSSComparisonFunctions);
}
}
const CSSMathFunctionValue* Value() const { return calc_value_; }
CSSMathFunctionValue* ConsumeValue() {
if (!calc_value_) {
return nullptr;
}
source_range_ = range_;
CSSMathFunctionValue* result = calc_value_;
calc_value_ = nullptr;
return result;
}
bool ConsumeNumberRaw(double& result) {
if (!calc_value_ || calc_value_->Category() != kCalcNumber) {
return false;
}
source_range_ = range_;
result = calc_value_->GetDoubleValue();
return true;
}
private:
CSSParserTokenRange& source_range_;
CSSParserTokenRange range_;
CSSMathFunctionValue* calc_value_ = nullptr;
};
CSSPrimitiveValue* ConsumeInteger(CSSParserTokenRange& range,
const CSSParserContext& context,
double minimum_value) {
const CSSParserToken& token = range.Peek();
if (token.GetType() == kNumberToken) {
if (token.GetNumericValueType() == kNumberValueType ||
token.NumericValue() < minimum_value) {
return nullptr;
}
return CSSNumericLiteralValue::Create(
range.ConsumeIncludingWhitespace().NumericValue(),
CSSPrimitiveValue::UnitType::kInteger);
}
DCHECK(minimum_value == -std::numeric_limits<double>::max() ||
minimum_value == 0 || minimum_value == 1);
CSSPrimitiveValue::ValueRange value_range =
CSSPrimitiveValue::ValueRange::kInteger;
if (minimum_value == 0) {
value_range = CSSPrimitiveValue::ValueRange::kNonNegativeInteger;
} else if (minimum_value == 1) {
value_range = CSSPrimitiveValue::ValueRange::kPositiveInteger;
}
MathFunctionParser math_parser(range, context, value_range);
if (const CSSMathFunctionValue* math_value = math_parser.Value()) {
if (math_value->Category() != kCalcNumber) {
return nullptr;
}
return math_parser.ConsumeValue();
}
return nullptr;
}
// This implements the behavior defined in [1], where calc() expressions
// are valid when <integer> is expected, even if the calc()-expression does
// not result in an integral value.
//
// TODO(andruud): Eventually this behavior should just be part of
// ConsumeInteger, and this function can be removed. For now, having a separate
// function with this behavior allows us to implement [1] gradually.
//
// [1] https://drafts.csswg.org/css-values-4/#calc-type-checking
CSSPrimitiveValue* ConsumeIntegerOrNumberCalc(
CSSParserTokenRange& range,
const CSSParserContext& context,
CSSPrimitiveValue::ValueRange value_range) {
CSSParserTokenRange int_range(range);
double minimum_value = -std::numeric_limits<double>::max();
switch (value_range) {
case CSSPrimitiveValue::ValueRange::kAll:
NOTREACHED() << "unexpected value range for integer parsing";
[[fallthrough]];
case CSSPrimitiveValue::ValueRange::kInteger:
minimum_value = -std::numeric_limits<double>::max();
break;
case CSSPrimitiveValue::ValueRange::kNonNegative:
NOTREACHED() << "unexpected value range for integer parsing";
[[fallthrough]];
case CSSPrimitiveValue::ValueRange::kNonNegativeInteger:
minimum_value = 0.0;
break;
case CSSPrimitiveValue::ValueRange::kPositiveInteger:
minimum_value = 1.0;
break;
}
if (CSSPrimitiveValue* value =
ConsumeInteger(int_range, context, minimum_value)) {
range = int_range;
return value;
}
MathFunctionParser math_parser(range, context, value_range);
if (const CSSMathFunctionValue* calculation = math_parser.Value()) {
if (calculation->Category() != kCalcNumber) {
return nullptr;
}
return math_parser.ConsumeValue();
}
return nullptr;
}
CSSPrimitiveValue* ConsumePositiveInteger(CSSParserTokenRange& range,
const CSSParserContext& context) {
return ConsumeInteger(range, context, 1);
}
bool ConsumeNumberRaw(CSSParserTokenRange& range,
const CSSParserContext& context,
double& result) {
if (range.Peek().GetType() == kNumberToken) {
result = range.ConsumeIncludingWhitespace().NumericValue();
return true;
}
MathFunctionParser math_parser(range, context,
CSSPrimitiveValue::ValueRange::kAll);
return math_parser.ConsumeNumberRaw(result);
}
// TODO(timloh): Work out if this can just call consumeNumberRaw
CSSPrimitiveValue* ConsumeNumber(CSSParserTokenRange& range,
const CSSParserContext& context,
CSSPrimitiveValue::ValueRange value_range) {
const CSSParserToken& token = range.Peek();
if (token.GetType() == kNumberToken) {
if (value_range == CSSPrimitiveValue::ValueRange::kNonNegative &&
token.NumericValue() < 0) {
return nullptr;
}
return CSSNumericLiteralValue::Create(
range.ConsumeIncludingWhitespace().NumericValue(), token.GetUnitType());
}
MathFunctionParser math_parser(range, context, value_range);
if (const CSSMathFunctionValue* calculation = math_parser.Value()) {
if (calculation->Category() != kCalcNumber) {
return nullptr;
}
return math_parser.ConsumeValue();
}
return nullptr;
}
inline bool ShouldAcceptUnitlessLength(double value,
CSSParserMode css_parser_mode,
UnitlessQuirk unitless) {
return value == 0 || css_parser_mode == kSVGAttributeMode ||
(css_parser_mode == kHTMLQuirksMode &&
unitless == UnitlessQuirk::kAllow);
}
CSSPrimitiveValue* ConsumeLength(CSSParserTokenRange& range,
const CSSParserContext& context,
CSSPrimitiveValue::ValueRange value_range,
UnitlessQuirk unitless) {
const CSSParserToken& token = range.Peek();
if (token.GetType() == kDimensionToken) {
switch (token.GetUnitType()) {
case CSSPrimitiveValue::UnitType::kQuirkyEms:
if (context.Mode() != kUASheetMode) {
return nullptr;
}
[[fallthrough]];
case CSSPrimitiveValue::UnitType::kEms:
case CSSPrimitiveValue::UnitType::kRems:
case CSSPrimitiveValue::UnitType::kChs:
case CSSPrimitiveValue::UnitType::kExs:
case CSSPrimitiveValue::UnitType::kPixels:
case CSSPrimitiveValue::UnitType::kCentimeters:
case CSSPrimitiveValue::UnitType::kMillimeters:
case CSSPrimitiveValue::UnitType::kQuarterMillimeters:
case CSSPrimitiveValue::UnitType::kInches:
case CSSPrimitiveValue::UnitType::kPoints:
case CSSPrimitiveValue::UnitType::kPicas:
case CSSPrimitiveValue::UnitType::kUserUnits:
case CSSPrimitiveValue::UnitType::kViewportWidth:
case CSSPrimitiveValue::UnitType::kViewportHeight:
case CSSPrimitiveValue::UnitType::kViewportMin:
case CSSPrimitiveValue::UnitType::kViewportMax:
case CSSPrimitiveValue::UnitType::kIcs:
case CSSPrimitiveValue::UnitType::kLhs:
case CSSPrimitiveValue::UnitType::kRexs:
case CSSPrimitiveValue::UnitType::kRchs:
case CSSPrimitiveValue::UnitType::kRics:
case CSSPrimitiveValue::UnitType::kRlhs:
break;
case CSSPrimitiveValue::UnitType::kViewportInlineSize:
case CSSPrimitiveValue::UnitType::kViewportBlockSize:
case CSSPrimitiveValue::UnitType::kSmallViewportWidth:
case CSSPrimitiveValue::UnitType::kSmallViewportHeight:
case CSSPrimitiveValue::UnitType::kSmallViewportInlineSize:
case CSSPrimitiveValue::UnitType::kSmallViewportBlockSize:
case CSSPrimitiveValue::UnitType::kSmallViewportMin:
case CSSPrimitiveValue::UnitType::kSmallViewportMax:
case CSSPrimitiveValue::UnitType::kLargeViewportWidth:
case CSSPrimitiveValue::UnitType::kLargeViewportHeight:
case CSSPrimitiveValue::UnitType::kLargeViewportInlineSize:
case CSSPrimitiveValue::UnitType::kLargeViewportBlockSize:
case CSSPrimitiveValue::UnitType::kLargeViewportMin:
case CSSPrimitiveValue::UnitType::kLargeViewportMax:
case CSSPrimitiveValue::UnitType::kDynamicViewportWidth:
case CSSPrimitiveValue::UnitType::kDynamicViewportHeight:
case CSSPrimitiveValue::UnitType::kDynamicViewportInlineSize:
case CSSPrimitiveValue::UnitType::kDynamicViewportBlockSize:
case CSSPrimitiveValue::UnitType::kDynamicViewportMin:
case CSSPrimitiveValue::UnitType::kDynamicViewportMax:
if (!RuntimeEnabledFeatures::CSSViewportUnits4Enabled()) {
return nullptr;
}
break;
case CSSPrimitiveValue::UnitType::kContainerWidth:
case CSSPrimitiveValue::UnitType::kContainerHeight:
case CSSPrimitiveValue::UnitType::kContainerInlineSize:
case CSSPrimitiveValue::UnitType::kContainerBlockSize:
case CSSPrimitiveValue::UnitType::kContainerMin:
case CSSPrimitiveValue::UnitType::kContainerMax:
break;
default:
return nullptr;
}
if (value_range == CSSPrimitiveValue::ValueRange::kNonNegative &&
token.NumericValue() < 0) {
return nullptr;
}
return CSSNumericLiteralValue::Create(
range.ConsumeIncludingWhitespace().NumericValue(), token.GetUnitType());
}
if (token.GetType() == kNumberToken) {
if (!ShouldAcceptUnitlessLength(token.NumericValue(), context.Mode(),
unitless) ||
(value_range == CSSPrimitiveValue::ValueRange::kNonNegative &&
token.NumericValue() < 0)) {
return nullptr;
}
CSSPrimitiveValue::UnitType unit_type =
CSSPrimitiveValue::UnitType::kPixels;
if (context.Mode() == kSVGAttributeMode) {
unit_type = CSSPrimitiveValue::UnitType::kUserUnits;
}
return CSSNumericLiteralValue::Create(
range.ConsumeIncludingWhitespace().NumericValue(), unit_type);
}
if (context.Mode() == kSVGAttributeMode) {
return nullptr;
}
MathFunctionParser math_parser(range, context, value_range);
if (math_parser.Value() && math_parser.Value()->Category() == kCalcLength) {
return math_parser.ConsumeValue();
}
return nullptr;
}
CSSPrimitiveValue* ConsumePercent(CSSParserTokenRange& range,
const CSSParserContext& context,
CSSPrimitiveValue::ValueRange value_range) {
const CSSParserToken& token = range.Peek();
if (token.GetType() == kPercentageToken) {
if (value_range == CSSPrimitiveValue::ValueRange::kNonNegative &&
token.NumericValue() < 0) {
return nullptr;
}
return CSSNumericLiteralValue::Create(
range.ConsumeIncludingWhitespace().NumericValue(),
CSSPrimitiveValue::UnitType::kPercentage);
}
MathFunctionParser math_parser(range, context, value_range);
if (const CSSMathFunctionValue* calculation = math_parser.Value()) {
if (calculation->Category() == kCalcPercent) {
return math_parser.ConsumeValue();
}
}
return nullptr;
}
CSSPrimitiveValue* ConsumeNumberOrPercent(
CSSParserTokenRange& range,
const CSSParserContext& context,
CSSPrimitiveValue::ValueRange value_range) {
if (CSSPrimitiveValue* value = ConsumeNumber(range, context, value_range)) {
return value;
}
if (CSSPrimitiveValue* value = ConsumePercent(range, context, value_range)) {
return CSSNumericLiteralValue::Create(value->GetDoubleValue() / 100.0,
CSSPrimitiveValue::UnitType::kNumber);
}
return nullptr;
}
CSSPrimitiveValue* ConsumeAlphaValue(CSSParserTokenRange& range,
const CSSParserContext& context) {
return ConsumeNumberOrPercent(range, context,
CSSPrimitiveValue::ValueRange::kAll);
}
bool CanConsumeCalcValue(CalculationCategory category,
CSSParserMode css_parser_mode) {
return category == kCalcLength || category == kCalcPercent ||
category == kCalcPercentLength ||
(css_parser_mode == kSVGAttributeMode && category == kCalcNumber);
}
CSSPrimitiveValue* ConsumeLengthOrPercent(
CSSParserTokenRange& range,
const CSSParserContext& context,
CSSPrimitiveValue::ValueRange value_range,
UnitlessQuirk unitless,
CSSAnchorQueryTypes allowed_anchor_queries) {
const CSSParserToken& token = range.Peek();
if (token.GetType() == kDimensionToken || token.GetType() == kNumberToken) {
return ConsumeLength(range, context, value_range, unitless);
}
if (token.GetType() == kPercentageToken) {
return ConsumePercent(range, context, value_range);
}
MathFunctionParser math_parser(range, context, value_range,
allowed_anchor_queries);
if (const CSSMathFunctionValue* calculation = math_parser.Value()) {
if (CanConsumeCalcValue(calculation->Category(), context.Mode())) {
return math_parser.ConsumeValue();
}
}
return nullptr;
}
namespace {
bool IsNonZeroUserUnitsValue(const CSSPrimitiveValue* value) {
if (!value) {
return false;
}
if (const auto* numeric_literal = DynamicTo<CSSNumericLiteralValue>(value)) {
return numeric_literal->GetType() ==
CSSPrimitiveValue::UnitType::kUserUnits &&
value->GetDoubleValue() != 0;
}
const auto& math_value = To<CSSMathFunctionValue>(*value);
return math_value.Category() == kCalcNumber && math_value.DoubleValue() != 0;
}
} // namespace
CSSPrimitiveValue* ConsumeSVGGeometryPropertyLength(
CSSParserTokenRange& range,
const CSSParserContext& context,
CSSPrimitiveValue::ValueRange value_range) {
CSSParserContext::ParserModeOverridingScope scope(context, kSVGAttributeMode);
CSSPrimitiveValue* value = ConsumeLengthOrPercent(range, context, value_range,
UnitlessQuirk::kForbid);
if (IsNonZeroUserUnitsValue(value)) {
context.Count(WebFeature::kSVGGeometryPropertyHasNonZeroUnitlessValue);
}
return value;
}
CSSPrimitiveValue* ConsumeGradientLengthOrPercent(
CSSParserTokenRange& range,
const CSSParserContext& context,
CSSPrimitiveValue::ValueRange value_range,
UnitlessQuirk unitless) {
return ConsumeLengthOrPercent(range, context, value_range, unitless);
}
static CSSPrimitiveValue* ConsumeNumericLiteralAngle(
CSSParserTokenRange& range,
const CSSParserContext& context,
absl::optional<WebFeature> unitless_zero_feature) {
const CSSParserToken& token = range.Peek();
if (token.GetType() == kDimensionToken) {
switch (token.GetUnitType()) {
case CSSPrimitiveValue::UnitType::kDegrees:
case CSSPrimitiveValue::UnitType::kRadians:
case CSSPrimitiveValue::UnitType::kGradians:
case CSSPrimitiveValue::UnitType::kTurns:
return CSSNumericLiteralValue::Create(
range.ConsumeIncludingWhitespace().NumericValue(),
token.GetUnitType());
default:
return nullptr;
}
}
if (token.GetType() == kNumberToken && token.NumericValue() == 0 &&
unitless_zero_feature) {
range.ConsumeIncludingWhitespace();
context.Count(*unitless_zero_feature);
return CSSNumericLiteralValue::Create(
0, CSSPrimitiveValue::UnitType::kDegrees);
}
return nullptr;
}
static CSSPrimitiveValue* ConsumeMathFunctionAngle(
CSSParserTokenRange& range,
const CSSParserContext& context,
double minimum_value,
double maximum_value) {
MathFunctionParser math_parser(range, context,
CSSPrimitiveValue::ValueRange::kAll);
if (const CSSMathFunctionValue* calculation = math_parser.Value()) {
if (calculation->Category() != kCalcAngle) {
return nullptr;
}
}
if (CSSMathFunctionValue* result = math_parser.ConsumeValue()) {
if (result->ComputeDegrees() < minimum_value) {
return CSSNumericLiteralValue::Create(
minimum_value, CSSPrimitiveValue::UnitType::kDegrees);
}
if (result->ComputeDegrees() > maximum_value) {
return CSSNumericLiteralValue::Create(
maximum_value, CSSPrimitiveValue::UnitType::kDegrees);
}
return result;
}
return nullptr;
}
static CSSPrimitiveValue* ConsumeMathFunctionAngle(
CSSParserTokenRange& range,
const CSSParserContext& context) {
MathFunctionParser math_parser(range, context,
CSSPrimitiveValue::ValueRange::kAll);
if (const CSSMathFunctionValue* calculation = math_parser.Value()) {
if (calculation->Category() != kCalcAngle) {
return nullptr;
}
}
return math_parser.ConsumeValue();
}
CSSPrimitiveValue* ConsumeAngle(
CSSParserTokenRange& range,
const CSSParserContext& context,
absl::optional<WebFeature> unitless_zero_feature,
double minimum_value,
double maximum_value) {
if (auto* result =
ConsumeNumericLiteralAngle(range, context, unitless_zero_feature)) {
return result;
}
return ConsumeMathFunctionAngle(range, context, minimum_value, maximum_value);
}
CSSPrimitiveValue* ConsumeAngle(
CSSParserTokenRange& range,
const CSSParserContext& context,
absl::optional<WebFeature> unitless_zero_feature) {
if (auto* result =
ConsumeNumericLiteralAngle(range, context, unitless_zero_feature)) {
return result;
}
return ConsumeMathFunctionAngle(range, context);
}
// ConsumeHue takes an angle as input (as angle in radians or in degrees, or as
// plain number in degrees) and returns a plain number in degrees.
CSSPrimitiveValue* ConsumeHue(
CSSParserTokenRange& range,
const CSSParserContext& context,
absl::optional<WebFeature> unitless_zero_feature) {
CSSPrimitiveValue* value = ConsumeAngle(range, context, absl::nullopt);
double angle_value;
if (!value) {
value = ConsumeNumber(range, context, CSSPrimitiveValue::ValueRange::kAll);
if (!value) {
return nullptr;
}
angle_value = value->GetDoubleValue();
} else {
angle_value = value->ComputeDegrees();
}
return CSSNumericLiteralValue::Create(
fmod(fmod(angle_value, 360.0) + 360.0, 360.0),
CSSPrimitiveValue::UnitType::kNumber);
}
CSSPrimitiveValue* ConsumeTime(CSSParserTokenRange& range,
const CSSParserContext& context,
CSSPrimitiveValue::ValueRange value_range) {
const CSSParserToken& token = range.Peek();
if (token.GetType() == kDimensionToken) {
if (value_range == CSSPrimitiveValue::ValueRange::kNonNegative &&
token.NumericValue() < 0) {
return nullptr;
}
CSSPrimitiveValue::UnitType unit = token.GetUnitType();
if (unit == CSSPrimitiveValue::UnitType::kMilliseconds ||
unit == CSSPrimitiveValue::UnitType::kSeconds) {
return CSSNumericLiteralValue::Create(
range.ConsumeIncludingWhitespace().NumericValue(),
token.GetUnitType());
}
return nullptr;
}
MathFunctionParser math_parser(range, context, value_range);
if (const CSSMathFunctionValue* calculation = math_parser.Value()) {
if (calculation->Category() == kCalcTime) {
return math_parser.ConsumeValue();
}
}
return nullptr;
}
CSSPrimitiveValue* ConsumeResolution(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (const CSSParserToken& token = range.Peek();
token.GetType() == kDimensionToken) {
CSSPrimitiveValue::UnitType unit = token.GetUnitType();
if (!CSSPrimitiveValue::IsResolution(unit)) {
return nullptr;
}
return CSSNumericLiteralValue::Create(
range.ConsumeIncludingWhitespace().NumericValue(), unit);
}
MathFunctionParser math_parser(range, context,
CSSPrimitiveValue::ValueRange::kAll);
const CSSMathFunctionValue* math_value = math_parser.Value();
if (math_value && math_value->IsResolution()) {
return math_parser.ConsumeValue();
}
return nullptr;
}
// https://drafts.csswg.org/css-values-4/#ratio-value
//
// <ratio> = <number [0,+inf]> [ / <number [0,+inf]> ]?
CSSValue* ConsumeRatio(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSPrimitiveValue* first = ConsumeNumber(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (!first) {
return nullptr;
}
CSSPrimitiveValue* second = nullptr;
if (css_parsing_utils::ConsumeSlashIncludingWhitespace(range)) {
second = ConsumeNumber(range, context,
CSSPrimitiveValue::ValueRange::kNonNegative);
if (!second) {
return nullptr;
}
} else {
second = CSSNumericLiteralValue::Create(
1, CSSPrimitiveValue::UnitType::kInteger);
}
return MakeGarbageCollected<cssvalue::CSSRatioValue>(*first, *second);
}
CSSIdentifierValue* ConsumeIdent(CSSParserTokenRange& range) {
if (range.Peek().GetType() != kIdentToken) {
return nullptr;
}
return CSSIdentifierValue::Create(range.ConsumeIncludingWhitespace().Id());
}
CSSIdentifierValue* ConsumeIdentRange(CSSParserTokenRange& range,
CSSValueID lower,
CSSValueID upper) {
if (range.Peek().Id() < lower || range.Peek().Id() > upper) {
return nullptr;
}
return ConsumeIdent(range);
}
CSSCustomIdentValue* ConsumeCustomIdent(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().GetType() != kIdentToken ||
IsCSSWideKeyword(range.Peek().Id()) ||
range.Peek().Id() == CSSValueID::kDefault) {
return nullptr;
}
return MakeGarbageCollected<CSSCustomIdentValue>(
range.ConsumeIncludingWhitespace().Value().ToAtomicString());
}
CSSCustomIdentValue* ConsumeDashedIdent(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().GetType() != kIdentToken) {
return nullptr;
}
if (!range.Peek().Value().ToString().StartsWith(kTwoDashes)) {
return nullptr;
}
return ConsumeCustomIdent(range, context);
}
CSSStringValue* ConsumeString(CSSParserTokenRange& range) {
if (range.Peek().GetType() != kStringToken) {
return nullptr;
}
return MakeGarbageCollected<CSSStringValue>(
range.ConsumeIncludingWhitespace().Value().ToString());
}
StringView ConsumeStringAsStringView(CSSParserTokenRange& range) {
if (range.Peek().GetType() != CSSParserTokenType::kStringToken) {
return StringView();
}
return range.ConsumeIncludingWhitespace().Value();
}
namespace {
StringView ApplyFetchRestrictions(StringView url,
const CSSParserContext& context) {
// Invalidate the URL if only data URLs are allowed and the protocol is not
// data.
if (!url.IsNull() &&
context.ResourceFetchRestriction() ==
ResourceFetchRestriction::kOnlyDataUrls &&
!ProtocolIs(url.ToString(), "data")) {
// The StringView must be instantiated with an empty string otherwise the
// URL will incorrectly be identified as null. The resource should behave as
// if it failed to load.
return StringView("");
}
return url;
}
} // namespace
StringView ConsumeUrlAsStringView(CSSParserTokenRange& range,
const CSSParserContext& context) {
StringView url;
const CSSParserToken& token = range.Peek();
if (token.GetType() == kUrlToken) {
range.ConsumeIncludingWhitespace();
url = token.Value();
} else if (token.FunctionId() == CSSValueID::kUrl) {
CSSParserTokenRange url_range = range;
CSSParserTokenRange url_args = url_range.ConsumeBlock();
const CSSParserToken& next = url_args.ConsumeIncludingWhitespace();
if (next.GetType() == kBadStringToken || !url_args.AtEnd()) {
return StringView();
}
DCHECK_EQ(next.GetType(), kStringToken);
range = url_range;
range.ConsumeWhitespace();
url = next.Value();
}
return ApplyFetchRestrictions(url, context);
}
cssvalue::CSSURIValue* ConsumeUrl(CSSParserTokenRange& range,
const CSSParserContext& context) {
StringView url = ConsumeUrlAsStringView(range, context);
if (url.IsNull()) {
return nullptr;
}
AtomicString url_string = url.ToAtomicString();
return MakeGarbageCollected<cssvalue::CSSURIValue>(
url_string, context.CompleteURL(url_string));
}
static int ClampRGBComponent(const CSSPrimitiveValue& value) {
double result = value.GetDoubleValue();
if (value.IsPercentage()) {
// 2.55 cannot be precisely represented as a double
result = (result / 100.0) * 255.0;
}
return ClampTo<int>(round(result), 0, 255);
}
static bool ParseRGBParameters(CSSParserTokenRange& range,
const CSSParserContext& context,
Color& result) {
DCHECK(range.Peek().FunctionId() == CSSValueID::kRgb ||
range.Peek().FunctionId() == CSSValueID::kRgba);
CSSParserTokenRange args = ConsumeFunction(range);
CSSPrimitiveValue* value;
absl::optional<int> color_array[3];
bool requires_commas = false;
bool requires_percent = false;
bool requires_bare_numbers = false;
bool has_none = false;
for (absl::optional<int>& color : color_array) {
// Commas have to be consistent
if (ConsumeCommaIncludingWhitespace(args)) {
requires_commas = true;
} else if (requires_commas || args.AtEnd()) {
return false;
}
// Cannot mix percentages and bare numbers
value = ConsumePercent(args, context, CSSPrimitiveValue::ValueRange::kAll);
if (value) {
if (requires_bare_numbers) {
return false;
}
requires_percent = true;
} else {
value = ConsumeNumber(args, context, CSSPrimitiveValue::ValueRange::kAll);
if (value) {
if (requires_percent) {
return false;
}
requires_bare_numbers = true;
}
}
if (value) {
color = ClampRGBComponent(*value);
} else {
if (!ConsumeIdent<CSSValueID::kNone>(args)) {
return false;
}
has_none = true;
}
}
bool comma_consumed = ConsumeCommaIncludingWhitespace(args);
bool slash_consumed = ConsumeSlashIncludingWhitespace(args);
if ((comma_consumed && !requires_commas) ||
(slash_consumed && requires_commas)) {
return false;
}
if (comma_consumed || slash_consumed) {
absl::optional<double> alpha;
if (double alpha_double; ConsumeNumberRaw(args, context, alpha_double)) {
alpha = alpha_double;
} else {
CSSPrimitiveValue* alpha_percent =
ConsumePercent(args, context, CSSPrimitiveValue::ValueRange::kAll);
if (!alpha_percent) {
if (!ConsumeIdent<CSSValueID::kNone>(args)) {
return false;
}
has_none = true;
} else {
alpha = alpha_percent->GetDoubleValue() / 100.0;
}
}
// W3 standard stipulates a 2.55 alpha value multiplication factor.
absl::optional<int> alpha_component;
if (alpha) {
alpha_component = static_cast<int>(
lround(ClampTo<double>(alpha.value(), 0.0, 1.0) * 255.0));
}
result = Color::FromRGBALegacy(color_array[0], color_array[1],
color_array[2], alpha_component);
} else {
result = Color::FromRGBALegacy(color_array[0], color_array[1],
color_array[2], 255);
}
if (has_none && requires_commas) {
return false;
}
return args.AtEnd();
}
static bool ParseHSLParameters(CSSParserTokenRange& range,
const CSSParserContext& context,
Color& result) {
DCHECK(range.Peek().FunctionId() == CSSValueID::kHsl ||
range.Peek().FunctionId() == CSSValueID::kHsla);
CSSParserTokenRange args = ConsumeFunction(range);
absl::optional<float> color_array[3];
CSSPrimitiveValue* value = ConsumeHue(args, context, absl::nullopt);
bool has_none = false;
if (value) {
// HSL expects a hue in the range [0.0, 6.0]
// https://www.w3.org/TR/css-color-4/#typedef-hue
color_array[0] = value->GetDoubleValue() / 60.0f;
} else {
if (!ConsumeIdent<CSSValueID::kNone>(args)) {
return false;
}
has_none = true;
}
bool requires_commas = false;
for (int i = 1; i < 3; i++) {
if (ConsumeCommaIncludingWhitespace(args)) {
if (i != 1 && !requires_commas) {
return false;
}
requires_commas = true;
} else if (requires_commas || args.AtEnd()) {
return false;
}
value = ConsumePercent(args, context, CSSPrimitiveValue::ValueRange::kAll);
if (value) {
double double_value = value->GetDoubleValue();
color_array[i] = ClampTo<float>(double_value, 0.0f, 100.0f) /
100.0f; // Needs to be value between 0 and 1.0.
} else {
if (!ConsumeIdent<CSSValueID::kNone>(args)) {
return false;
}
has_none = true;
}
}
absl::optional<float> alpha;
bool comma_consumed = ConsumeCommaIncludingWhitespace(args);
bool slash_consumed = ConsumeSlashIncludingWhitespace(args);
if ((comma_consumed && !requires_commas) ||
(slash_consumed && requires_commas)) {
return false;
}
if (comma_consumed || slash_consumed) {
double alpha_param = 1.0;
if (ConsumeNumberRaw(args, context, alpha_param)) {
alpha = ClampTo<float>(alpha_param, 0.0f, 1.0f);
} else {
CSSPrimitiveValue* alpha_percent =
ConsumePercent(args, context, CSSPrimitiveValue::ValueRange::kAll);
if (alpha_percent) {
alpha = alpha_percent->GetDoubleValue() / 100.0f;
} else {
if (!ConsumeIdent<CSSValueID::kNone>(args)) {
return false;
}
has_none = true;
}
}
} else {
// It was the hsl form so it should consider alpha to be 1.0f.
alpha = 1.0f;
}
if (requires_commas && has_none) {
return false;
}
result =
Color::FromHSLA(color_array[0], color_array[1], color_array[2], alpha);
return args.AtEnd();
}
// If there is no alpha value, set it to 1.0. "none" is a different value.
// This is the behavior required by most CSSColor4 colors like lab, lch and the
// color() function.
static absl::optional<double> ConsumeAlphaWithLeadingSlash(
CSSParserTokenRange& range,
const CSSParserContext& context) {
absl::optional<double> result;
double raw_alpha = 1.0;
bool alpha_is_none = false;
if (ConsumeSlashIncludingWhitespace(range)) {
alpha_is_none = ConsumeIdent<CSSValueID::kNone>(range);
if (!alpha_is_none) {
if (!ConsumeNumberRaw(range, context, raw_alpha)) {
CSSPrimitiveValue* alpha_percent =
ConsumePercent(range, context, CSSPrimitiveValue::ValueRange::kAll);
if (!alpha_percent) {
return false;
}
raw_alpha = alpha_percent->GetDoubleValue() / 100.0;
}
}
}
if (!alpha_is_none) {
result = ClampTo<double>(raw_alpha, 0.0, 1.0);
}
return result;
}
static bool ParseHWBParameters(CSSParserTokenRange& range,
const CSSParserContext& context,
Color& result) {
DCHECK(range.Peek().FunctionId() == CSSValueID::kHwb);
CSSParserTokenRange args = ConsumeFunction(range);
CSSPrimitiveValue* value;
absl::optional<float> hue;
if (!ConsumeIdent<CSSValueID::kNone>(args)) {
value = ConsumeHue(args, context, absl::nullopt);
if (!value) {
return false;
}
// HWB expects a hue in the range [0.0, 6.0]
// https://www.w3.org/TR/css-color-4/#typedef-hue
hue = value->GetDoubleValue() / 60.0f;
}
// Consume two percentage values.
absl::optional<float> percentages[2];
for (auto& percentage : percentages) {
if (ConsumeIdent<CSSValueID::kNone>(args)) {
continue;
}
value = ConsumePercent(args, context, CSSPrimitiveValue::ValueRange::kAll);
if (!value) {
return false;
}
double double_value = value->GetDoubleValue();
percentage = ClampTo<float>(double_value, 0.0f, 100.0f) /
100.0f; // Needs to be a value between 0 and 1.0.
}
absl::optional<float> float_alpha;
absl::optional<double> optional_alpha =
ConsumeAlphaWithLeadingSlash(args, context);
// Per spec, "none" for hwb = 0.0
if (optional_alpha.has_value()) {
float_alpha = optional_alpha.value();
}
result = Color::FromHWBA(hue, percentages[0], percentages[1], float_alpha);
return args.AtEnd();
}
static bool ParseLABOrOKLABParameters(CSSParserTokenRange& range,
const CSSParserContext& context,
Color& result) {
CSSValueID function_id = range.Peek().FunctionId();
DCHECK(function_id == CSSValueID::kLab || function_id == CSSValueID::kOklab);
context.Count(WebFeature::kCSSColorLabOklab);
CSSParserTokenRange args = ConsumeFunction(range);
// Consume lightness, either a percentage or a number or "none"
absl::optional<double> lightness;
if (!ConsumeIdent<CSSValueID::kNone>(args)) {
if (CSSPrimitiveValue* value_percent =
ConsumePercent(args, context, CSSPrimitiveValue::ValueRange::kAll);
value_percent) {
lightness =
std::min(100.0, std::max(0.0, value_percent->GetDoubleValue()));
} else if (CSSPrimitiveValue* value = ConsumeNumber(
args, context, CSSPrimitiveValue::ValueRange::kAll);
value) {
lightness =
std::min(100.0, std::max(0.0, value->GetDoubleValue()) *
(function_id == CSSValueID::kLab ? 1.0 : 100.0));
} else {
return false;
}
}
absl::optional<double> ab[2];
for (absl::optional<double>& i : ab) {
if (ConsumeIdent<CSSValueID::kNone>(args)) {
continue;
}
if (CSSPrimitiveValue* value =
ConsumeNumber(args, context, CSSPrimitiveValue::ValueRange::kAll);
value) {
i = value->GetDoubleValue();
} else {
return false;
}
}
absl::optional<double> alpha = ConsumeAlphaWithLeadingSlash(args, context);
Color::ColorSpace color_space = (function_id == CSSValueID::kLab)
? Color::ColorSpace::kLab
: Color::ColorSpace::kOklab;
result = Color::FromColorSpace(color_space, lightness, ab[0], ab[1], alpha);
return args.AtEnd();
}
static bool ParseLCHOrOKLCHParameters(CSSParserTokenRange& range,
const CSSParserContext& context,
Color& result) {
CSSValueID function_id = range.Peek().FunctionId();
DCHECK(function_id == CSSValueID::kLch || function_id == CSSValueID::kOklch);
context.Count(WebFeature::kCSSColorLchOklch);
CSSParserTokenRange args = ConsumeFunction(range);
// Consume lightness, either a percentage or a number
absl::optional<double> lightness;
if (!ConsumeIdent<CSSValueID::kNone>(args)) {
if (CSSPrimitiveValue* value_percent =
ConsumePercent(args, context, CSSPrimitiveValue::ValueRange::kAll);
value_percent) {
lightness =
std::min(100.0, std::max(0.0, value_percent->GetDoubleValue()));
} else if (CSSPrimitiveValue* value = ConsumeNumber(
args, context, CSSPrimitiveValue::ValueRange::kAll);
value) {
lightness =
std::min(100.0, std::max(0.0, value->GetDoubleValue()) *
(function_id == CSSValueID::kLch ? 1.0 : 100.0));
} else {
return false;
}
}
absl::optional<double> chroma;
if (!ConsumeIdent<CSSValueID::kNone>(args)) {
if (CSSPrimitiveValue* value =
ConsumeNumber(args, context, CSSPrimitiveValue::ValueRange::kAll);
value) {
chroma = std::max(0.0, value->GetDoubleValue());
} else {
return false;
}
}
absl::optional<double> hue;
if (!ConsumeIdent<CSSValueID::kNone>(args)) {
if (CSSPrimitiveValue* value = ConsumeHue(args, context, absl::nullopt);
value) {
hue = std::max(0.0, value->GetDoubleValue());
} else {
return false;
}
}
absl::optional<double> alpha = ConsumeAlphaWithLeadingSlash(args, context);
Color::ColorSpace color_space = (function_id == CSSValueID::kLch)
? Color::ColorSpace::kLch
: Color::ColorSpace::kOklch;
result = Color::FromColorSpace(color_space, lightness, chroma, hue, alpha);
return args.AtEnd();
}
static bool ConsumeColorInterpolationSpace(
CSSParserTokenRange& args,
Color::ColorSpace& color_space,
Color::HueInterpolationMethod& hue_interpolation) {
if (!RuntimeEnabledFeatures::CSSColor4Enabled()) {
return false;
}
if (!ConsumeIdent<CSSValueID::kIn>(args)) {
return false;
}
absl::optional<Color::ColorSpace> read_color_space;
if (ConsumeIdent<CSSValueID::kXyz>(args)) {
read_color_space = Color::ColorSpace::kXYZD65;
} else if (ConsumeIdent<CSSValueID::kXyzD50>(args)) {
read_color_space = Color::ColorSpace::kXYZD50;
} else if (ConsumeIdent<CSSValueID::kXyzD65>(args)) {
read_color_space = Color::ColorSpace::kXYZD65;
} else if (ConsumeIdent<CSSValueID::kSRGBLinear>(args)) {
read_color_space = Color::ColorSpace::kSRGBLinear;
} else if (ConsumeIdent<CSSValueID::kLab>(args)) {
read_color_space = Color::ColorSpace::kLab;
} else if (ConsumeIdent<CSSValueID::kOklab>(args)) {
read_color_space = Color::ColorSpace::kOklab;
} else if (ConsumeIdent<CSSValueID::kLch>(args)) {
read_color_space = Color::ColorSpace::kLch;
} else if (ConsumeIdent<CSSValueID::kOklch>(args)) {
read_color_space = Color::ColorSpace::kOklch;
} else if (ConsumeIdent<CSSValueID::kSRGB>(args)) {
read_color_space = Color::ColorSpace::kSRGB;
} else if (ConsumeIdent<CSSValueID::kHsl>(args)) {
read_color_space = Color::ColorSpace::kHSL;
} else if (ConsumeIdent<CSSValueID::kHwb>(args)) {
read_color_space = Color::ColorSpace::kHWB;
}
if (read_color_space) {
color_space = read_color_space.value();
absl::optional<Color::HueInterpolationMethod> read_hue;
if (color_space == Color::ColorSpace::kHSL ||
color_space == Color::ColorSpace::kHWB ||
color_space == Color::ColorSpace::kLch ||
color_space == Color::ColorSpace::kOklch) {
if (ConsumeIdent<CSSValueID::kShorter>(args)) {
read_hue = Color::HueInterpolationMethod::kShorter;
} else if (ConsumeIdent<CSSValueID::kLonger>(args)) {
read_hue = Color::HueInterpolationMethod::kLonger;
} else if (ConsumeIdent<CSSValueID::kDecreasing>(args)) {
read_hue = Color::HueInterpolationMethod::kDecreasing;
} else if (ConsumeIdent<CSSValueID::kIncreasing>(args)) {
read_hue = Color::HueInterpolationMethod::kIncreasing;
}
if (read_hue) {
if (!ConsumeIdent<CSSValueID::kHue>(args)) {
return false;
}
hue_interpolation = read_hue.value();
} else {
// Shorter is the default method for hue interpolation.
hue_interpolation = Color::HueInterpolationMethod::kShorter;
}
}
return true;
}
return false;
}
// https://www.w3.org/TR/css-color-5/#color-mix
static CSSValue* ConsumeColorMixFunction(CSSParserTokenRange& range,
const CSSParserContext& context) {
DCHECK(range.Peek().FunctionId() == CSSValueID::kColorMix);
context.Count(WebFeature::kCSSColorMixFunction);
if (!RuntimeEnabledFeatures::CSSColor4Enabled()) {
return nullptr;
}
CSSParserTokenRange range_copy = range;
CSSParserTokenRange args = ConsumeFunction(range_copy);
// First argument is the colorspace
Color::ColorSpace color_space;
Color::HueInterpolationMethod hue_interpolation_method =
Color::HueInterpolationMethod::kShorter;
if (!ConsumeColorInterpolationSpace(args, color_space,
hue_interpolation_method)) {
return nullptr;
}
if (!ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
CSSValue* color1 =
ConsumeColor(args, context, false /* Accept quirky colors */);
CSSPrimitiveValue* p1 =
ConsumePercent(args, context, CSSPrimitiveValue::ValueRange::kAll);
// Color can come after the percentage
if (!color1) {
color1 = ConsumeColor(args, context, false /* Accept quirky colors */);
if (!color1) {
return nullptr;
}
}
// Reject negative values and values > 100%, but not calc() values.
if (p1 && p1->IsNumericLiteralValue() &&
(p1->GetDoubleValue() < 0.0 || p1->GetDoubleValue() > 100.0)) {
return nullptr;
}
if (!ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
CSSValue* color2 =
ConsumeColor(args, context, false /* Accept quirky colors */);
CSSPrimitiveValue* p2 =
ConsumePercent(args, context, CSSPrimitiveValue::ValueRange::kAll);
// Color can come after the percentage
if (!color2) {
color2 = ConsumeColor(args, context, false /* Accept quirky colors */);
if (!color2) {
return nullptr;
}
}
// Reject negative values and values > 100%, but not calc() values.
if (p2 && p2->IsNumericLiteralValue() &&
(p2->GetDoubleValue() < 0.0 || p2->GetDoubleValue() > 100.0)) {
return nullptr;
}
// If both values are literally zero (and not calc()) reject at parse time
if (p1 && p2 && p1->IsNumericLiteralValue() && p1->GetDoubleValue() == 0.0f &&
p2->IsNumericLiteralValue() && p2->GetDoubleValue() == 0.0) {
return nullptr;
}
if (!args.AtEnd()) {
return nullptr;
}
range = range_copy;
cssvalue::CSSColorMixValue* result =
MakeGarbageCollected<cssvalue::CSSColorMixValue>(
color1, color2, p1, p2, color_space, hue_interpolation_method);
return result;
}
// https://www.w3.org/TR/css-color-4/#funcdef-color
static bool ParseColorFunctionParameters(CSSParserTokenRange& range,
const CSSParserContext& context,
Color& result) {
DCHECK(range.Peek().FunctionId() == CSSValueID::kColor);
context.Count(WebFeature::kCSSColorColorSpecifiedSpace);
CSSParserTokenRange args = ConsumeFunction(range);
// First argument is the colorspace
CSSValueID colorspace_id_ = args.ConsumeIncludingWhitespace().Id();
Color::ColorSpace colorspace;
switch (colorspace_id_) {
case CSSValueID::kSRGB:
colorspace = Color::ColorSpace::kSRGB;
break;
case CSSValueID::kRec2020:
colorspace = Color::ColorSpace::kRec2020;
break;
case CSSValueID::kSRGBLinear:
colorspace = Color::ColorSpace::kSRGBLinear;
break;
case CSSValueID::kDisplayP3:
colorspace = Color::ColorSpace::kDisplayP3;
break;
case CSSValueID::kA98Rgb:
colorspace = Color::ColorSpace::kA98RGB;
break;
case CSSValueID::kProphotoRgb:
colorspace = Color::ColorSpace::kProPhotoRGB;
break;
case CSSValueID::kXyzD50:
colorspace = Color::ColorSpace::kXYZD50;
break;
case CSSValueID::kXyz:
case CSSValueID::kXyzD65:
colorspace = Color::ColorSpace::kXYZD65;
break;
default:
return false;
}
absl::optional<double> params[3];
bool has_commas = false;
bool has_none = false;
for (absl::optional<double>& param : params) {
if (ConsumeCommaIncludingWhitespace(args)) {
has_commas = true;
}
if (ConsumeIdent<CSSValueID::kNone>(args)) {
has_none = true;
continue;
}
CSSPrimitiveValue* value =
ConsumeNumber(args, context, CSSPrimitiveValue::ValueRange::kAll);
if (value) {
param = value->GetDoubleValue();
continue;
}
value = ConsumePercent(args, context, CSSPrimitiveValue::ValueRange::kAll);
if (value) {
param = value->GetDoubleValue() / 100.0;
continue;
}
// Missing components should not parse.
return false;
}
if (has_commas && has_none) {
return false;
}
absl::optional<double> alpha = ConsumeAlphaWithLeadingSlash(args, context);
result =
Color::FromColorSpace(colorspace, params[0], params[1], params[2], alpha);
return args.AtEnd();
}
static bool ParseHexColor(CSSParserTokenRange& range,
Color& result,
bool accept_quirky_colors) {
const CSSParserToken& token = range.Peek();
if (token.GetType() == kHashToken) {
if (!Color::ParseHexColor(token.Value(), result)) {
return false;
}
} else if (accept_quirky_colors) {
String color;
if (token.GetType() == kNumberToken || token.GetType() == kDimensionToken) {
if (token.GetNumericValueType() != kIntegerValueType ||
token.NumericValue() < 0. || token.NumericValue() >= 1000000.) {
return false;
}
if (token.GetType() == kNumberToken) { // e.g. 112233
color = String::Format("%d", static_cast<int>(token.NumericValue()));
} else { // e.g. 0001FF
color = String::Number(static_cast<int>(token.NumericValue())) +
token.Value().ToString();
}
while (color.length() < 6) {
color = "0" + color;
}
} else if (token.GetType() == kIdentToken) { // e.g. FF0000
color = token.Value().ToString();
}
unsigned length = color.length();
if (length != 3 && length != 6) {
return false;
}
if (!Color::ParseHexColor(color, result)) {
return false;
}
} else {
return false;
}
range.ConsumeIncludingWhitespace();
return true;
}
static bool ParseFunctionalSyntaxColor(CSSParserTokenRange& range,
const CSSParserContext& context,
Color& result) {
CSSParserTokenRange color_range = range;
switch (range.Peek().FunctionId()) {
case CSSValueID::kRgb:
case CSSValueID::kRgba:
if (!ParseRGBParameters(color_range, context, result)) {
return false;
}
break;
case CSSValueID::kHsl:
case CSSValueID::kHsla:
if (!ParseHSLParameters(color_range, context, result)) {
return false;
}
break;
case CSSValueID::kHwb:
if (!ParseHWBParameters(color_range, context, result)) {
return false;
}
break;
case CSSValueID::kLab:
case CSSValueID::kOklab:
if (!RuntimeEnabledFeatures::CSSColor4Enabled() ||
!ParseLABOrOKLABParameters(color_range, context, result)) {
return false;
}
break;
case CSSValueID::kLch:
case CSSValueID::kOklch:
if (!RuntimeEnabledFeatures::CSSColor4Enabled() ||
!ParseLCHOrOKLCHParameters(color_range, context, result)) {
return false;
}
break;
case CSSValueID::kColor:
if (!RuntimeEnabledFeatures::CSSColor4Enabled() ||
!ParseColorFunctionParameters(color_range, context, result)) {
return false;
}
break;
default:
return false;
}
range = color_range;
return true;
}
namespace {
// TODO(crbug.com/1111385): Remove this when we move color-contrast()
// representation to ComputedStyle. This method does not handle currentColor
// correctly.
Color ResolveColor(CSSValue* value) {
if (auto* color = DynamicTo<cssvalue::CSSColor>(value)) {
return color->Value();
}
if (auto* color = DynamicTo<CSSIdentifierValue>(value)) {
CSSValueID color_id = color->GetValueID();
DCHECK(StyleColor::IsColorKeyword(color_id));
return StyleColor::ColorFromKeyword(color_id,
mojom::blink::ColorScheme::kLight);
}
NOTREACHED();
return Color();
}
} // namespace
CSSValue* ConsumeColorContrast(CSSParserTokenRange& range,
const CSSParserContext& context,
bool accept_quirky_colors) {
DCHECK_EQ(range.Peek().FunctionId(), CSSValueID::kColorContrast);
CSSParserTokenRange range_copy = range;
CSSParserTokenRange args = ConsumeFunction(range_copy);
CSSValue* background_color =
ConsumeColor(args, context, accept_quirky_colors);
if (!background_color) {
return nullptr;
}
if (!ConsumeIdent<CSSValueID::kVs>(args)) {
return nullptr;
}
VectorOf<CSSValue> colors_to_compare_against;
do {
CSSValue* color = ConsumeColor(args, context, accept_quirky_colors);
if (!color) {
return nullptr;
}
colors_to_compare_against.push_back(color);
} while (ConsumeCommaIncludingWhitespace(args));
if (colors_to_compare_against.size() < 2) {
return nullptr;
}
absl::optional<double> target_contrast;
if (ConsumeIdent<CSSValueID::kTo>(args)) {
double target_contrast_temp;
if (ConsumeIdent<CSSValueID::kAA>(args)) {
target_contrast = 4.5;
} else if (ConsumeIdent<CSSValueID::kAALarge>(args)) {
target_contrast = 3;
} else if (ConsumeIdent<CSSValueID::kAAA>(args)) {
target_contrast = 7;
} else if (ConsumeIdent<CSSValueID::kAAALarge>(args)) {
target_contrast = 4.5;
} else if (ConsumeNumberRaw(args, context, target_contrast_temp)) {
target_contrast = target_contrast_temp;
} else {
return nullptr;
}
}
// Bail out if there is any trailing stuff after we parse everything
if (!args.AtEnd()) {
return nullptr;
}
// TODO(crbug.com/1111385): Represent |background_color| and
// |colors_to_compare_against| in ComputedStyle and evaluate with currentColor
// and other variables at used-value time instead of doing it at parse time
// below.
SkColor4f resolved_background_color =
ResolveColor(background_color).toSkColor4f();
int highest_contrast_index = -1;
float highest_contrast_ratio = 0;
for (unsigned i = 0; i < colors_to_compare_against.size(); i++) {
float contrast_ratio = color_utils::GetContrastRatio(
resolved_background_color,
ResolveColor(colors_to_compare_against[i]).toSkColor4f());
if (target_contrast.has_value()) {
if (contrast_ratio >= target_contrast.value()) {
highest_contrast_ratio = contrast_ratio;
highest_contrast_index = i;
break;
}
} else if (contrast_ratio > highest_contrast_ratio) {
highest_contrast_ratio = contrast_ratio;
highest_contrast_index = i;
}
}
range = range_copy;
if (highest_contrast_index < 0) {
// If an explicit target contrast was set and no provided colors have enough
// contrast, then return white or black depending on which has the most
// contrast.
return color_utils::GetContrastRatio(resolved_background_color,
SkColors::kWhite) >
color_utils::GetContrastRatio(resolved_background_color,
SkColors::kBlack)
? MakeGarbageCollected<cssvalue::CSSColor>(Color::kWhite)
: MakeGarbageCollected<cssvalue::CSSColor>(Color::kBlack);
}
return MakeGarbageCollected<cssvalue::CSSColor>(
ResolveColor(colors_to_compare_against[highest_contrast_index]));
}
CSSValue* ConsumeColor(CSSParserTokenRange& range,
const CSSParserContext& context,
bool accept_quirky_colors,
AllowedColorKeywords allowed_keywords) {
if (RuntimeEnabledFeatures::CSSColorContrastEnabled() &&
range.Peek().FunctionId() == CSSValueID::kColorContrast) {
return ConsumeColorContrast(range, context, accept_quirky_colors);
}
if (RuntimeEnabledFeatures::CSSColor4Enabled() &&
range.Peek().FunctionId() == CSSValueID::kColorMix) {
CSSValue* color = ConsumeColorMixFunction(range, context);
return color;
}
CSSValueID id = range.Peek().Id();
if (StyleColor::IsColorKeyword(id)) {
if (!isValueAllowedInMode(id, context.Mode())) {
return nullptr;
}
if (allowed_keywords != AllowedColorKeywords::kAllowSystemColor &&
(StyleColor::IsSystemColorIncludingDeprecated(id) ||
StyleColor::IsSystemColor(id))) {
return nullptr;
}
CSSIdentifierValue* color = ConsumeIdent(range);
return color;
}
Color color = Color::kTransparent;
if (!ParseHexColor(range, color, accept_quirky_colors) &&
!ParseFunctionalSyntaxColor(range, context, color)) {
return ConsumeInternalLightDark(ConsumeColor, range, context,
accept_quirky_colors, allowed_keywords);
}
return cssvalue::CSSColor::Create(color);
}
CSSValue* ConsumeLineWidth(CSSParserTokenRange& range,
const CSSParserContext& context,
UnitlessQuirk unitless) {
CSSValueID id = range.Peek().Id();
if (id == CSSValueID::kThin || id == CSSValueID::kMedium ||
id == CSSValueID::kThick) {
return ConsumeIdent(range);
}
return ConsumeLength(range, context,
CSSPrimitiveValue::ValueRange::kNonNegative, unitless);
}
static CSSValue* ConsumePositionComponent(CSSParserTokenRange& range,
const CSSParserContext& context,
UnitlessQuirk unitless,
bool& horizontal_edge,
bool& vertical_edge) {
if (range.Peek().GetType() != kIdentToken) {
return ConsumeLengthOrPercent(
range, context, CSSPrimitiveValue::ValueRange::kAll, unitless);
}
CSSValueID id = range.Peek().Id();
if (id == CSSValueID::kLeft || id == CSSValueID::kRight) {
if (horizontal_edge) {
return nullptr;
}
horizontal_edge = true;
} else if (id == CSSValueID::kTop || id == CSSValueID::kBottom) {
if (vertical_edge) {
return nullptr;
}
vertical_edge = true;
} else if (id != CSSValueID::kCenter) {
return nullptr;
}
return ConsumeIdent(range);
}
static bool IsHorizontalPositionKeywordOnly(const CSSValue& value) {
auto* identifier_value = DynamicTo<CSSIdentifierValue>(value);
if (!identifier_value) {
return false;
}
CSSValueID value_id = identifier_value->GetValueID();
return value_id == CSSValueID::kLeft || value_id == CSSValueID::kRight;
}
static bool IsVerticalPositionKeywordOnly(const CSSValue& value) {
auto* identifier_value = DynamicTo<CSSIdentifierValue>(value);
if (!identifier_value) {
return false;
}
CSSValueID value_id = identifier_value->GetValueID();
return value_id == CSSValueID::kTop || value_id == CSSValueID::kBottom;
}
static void PositionFromOneValue(CSSValue* value,
CSSValue*& result_x,
CSSValue*& result_y) {
bool value_applies_to_y_axis_only = IsVerticalPositionKeywordOnly(*value);
result_x = value;
result_y = CSSIdentifierValue::Create(CSSValueID::kCenter);
if (value_applies_to_y_axis_only) {
std::swap(result_x, result_y);
}
}
static void PositionFromTwoValues(CSSValue* value1,
CSSValue* value2,
CSSValue*& result_x,
CSSValue*& result_y) {
bool must_order_as_xy = IsHorizontalPositionKeywordOnly(*value1) ||
IsVerticalPositionKeywordOnly(*value2) ||
!value1->IsIdentifierValue() ||
!value2->IsIdentifierValue();
bool must_order_as_yx = IsVerticalPositionKeywordOnly(*value1) ||
IsHorizontalPositionKeywordOnly(*value2);
DCHECK(!must_order_as_xy || !must_order_as_yx);
result_x = value1;
result_y = value2;
if (must_order_as_yx) {
std::swap(result_x, result_y);
}
}
static void PositionFromThreeOrFourValues(CSSValue** values,
CSSValue*& result_x,
CSSValue*& result_y) {
CSSIdentifierValue* center = nullptr;
for (int i = 0; values[i]; i++) {
auto* current_value = To<CSSIdentifierValue>(values[i]);
CSSValueID id = current_value->GetValueID();
if (id == CSSValueID::kCenter) {
DCHECK(!center);
center = current_value;
continue;
}
CSSValue* result = nullptr;
if (values[i + 1] && !values[i + 1]->IsIdentifierValue()) {
result = MakeGarbageCollected<CSSValuePair>(
current_value, values[++i], CSSValuePair::kKeepIdenticalValues);
} else {
result = current_value;
}
if (id == CSSValueID::kLeft || id == CSSValueID::kRight) {
DCHECK(!result_x);
result_x = result;
} else {
DCHECK(id == CSSValueID::kTop || id == CSSValueID::kBottom);
DCHECK(!result_y);
result_y = result;
}
}
if (center) {
DCHECK(!!result_x != !!result_y);
if (!result_x) {
result_x = center;
} else {
result_y = center;
}
}
DCHECK(result_x && result_y);
}
bool ConsumePosition(CSSParserTokenRange& range,
const CSSParserContext& context,
UnitlessQuirk unitless,
absl::optional<WebFeature> three_value_position,
CSSValue*& result_x,
CSSValue*& result_y) {
bool horizontal_edge = false;
bool vertical_edge = false;
CSSValue* value1 = ConsumePositionComponent(range, context, unitless,
horizontal_edge, vertical_edge);
if (!value1) {
return false;
}
if (!value1->IsIdentifierValue()) {
horizontal_edge = true;
}
CSSParserTokenRange range_after_first_consume = range;
CSSValue* value2 = ConsumePositionComponent(range, context, unitless,
horizontal_edge, vertical_edge);
if (!value2) {
PositionFromOneValue(value1, result_x, result_y);
return true;
}
CSSParserTokenRange range_after_second_consume = range;
CSSValue* value3 = nullptr;
auto* identifier_value1 = DynamicTo<CSSIdentifierValue>(value1);
auto* identifier_value2 = DynamicTo<CSSIdentifierValue>(value2);
// TODO(crbug.com/940442): Fix the strange comparison of a
// CSSIdentifierValue instance against a specific "range peek" type check.
if (identifier_value1 &&
!!identifier_value2 != (range.Peek().GetType() == kIdentToken) &&
(identifier_value2
? identifier_value2->GetValueID()
: identifier_value1->GetValueID()) != CSSValueID::kCenter) {
value3 = ConsumePositionComponent(range, context, unitless, horizontal_edge,
vertical_edge);
}
if (!value3) {
if (vertical_edge && !value2->IsIdentifierValue()) {
range = range_after_first_consume;
PositionFromOneValue(value1, result_x, result_y);
return true;
}
PositionFromTwoValues(value1, value2, result_x, result_y);
return true;
}
CSSValue* value4 = nullptr;
auto* identifier_value3 = DynamicTo<CSSIdentifierValue>(value3);
if (identifier_value3 &&
identifier_value3->GetValueID() != CSSValueID::kCenter &&
range.Peek().GetType() != kIdentToken) {
value4 = ConsumePositionComponent(range, context, unitless, horizontal_edge,
vertical_edge);
}
if (!value4) {
if (!three_value_position) {
// [top | bottom] <length-percentage> is not permitted
if (vertical_edge && !value2->IsIdentifierValue()) {
range = range_after_first_consume;
PositionFromOneValue(value1, result_x, result_y);
return true;
}
range = range_after_second_consume;
PositionFromTwoValues(value1, value2, result_x, result_y);
return true;
}
DCHECK_EQ(*three_value_position,
WebFeature::kThreeValuedPositionBackground);
context.Count(*three_value_position);
}
CSSValue* values[5];
values[0] = value1;
values[1] = value2;
values[2] = value3;
values[3] = value4;
values[4] = nullptr;
PositionFromThreeOrFourValues(values, result_x, result_y);
return true;
}
CSSValuePair* ConsumePosition(CSSParserTokenRange& range,
const CSSParserContext& context,
UnitlessQuirk unitless,
absl::optional<WebFeature> three_value_position) {
CSSValue* result_x = nullptr;
CSSValue* result_y = nullptr;
if (ConsumePosition(range, context, unitless, three_value_position, result_x,
result_y)) {
return MakeGarbageCollected<CSSValuePair>(
result_x, result_y, CSSValuePair::kKeepIdenticalValues);
}
return nullptr;
}
bool ConsumeOneOrTwoValuedPosition(CSSParserTokenRange& range,
const CSSParserContext& context,
UnitlessQuirk unitless,
CSSValue*& result_x,
CSSValue*& result_y) {
bool horizontal_edge = false;
bool vertical_edge = false;
CSSValue* value1 = ConsumePositionComponent(range, context, unitless,
horizontal_edge, vertical_edge);
if (!value1) {
return false;
}
if (!value1->IsIdentifierValue()) {
horizontal_edge = true;
}
if (vertical_edge &&
ConsumeLengthOrPercent(range, context,
CSSPrimitiveValue::ValueRange::kAll, unitless)) {
// <length-percentage> is not permitted after top | bottom.
return false;
}
CSSValue* value2 = ConsumePositionComponent(range, context, unitless,
horizontal_edge, vertical_edge);
if (!value2) {
PositionFromOneValue(value1, result_x, result_y);
return true;
}
PositionFromTwoValues(value1, value2, result_x, result_y);
return true;
}
bool ConsumeBorderShorthand(CSSParserTokenRange& range,
const CSSParserContext& context,
const CSSValue*& result_width,
const CSSValue*& result_style,
const CSSValue*& result_color) {
while (!result_width || !result_style || !result_color) {
if (!result_width) {
result_width = ConsumeLineWidth(range, context, UnitlessQuirk::kForbid);
if (result_width) {
continue;
}
}
if (!result_style) {
result_style = ParseLonghand(CSSPropertyID::kBorderLeftStyle,
CSSPropertyID::kBorder, context, range);
if (result_style) {
continue;
}
}
if (!result_color) {
result_color = ConsumeColor(range, context);
if (result_color) {
continue;
}
}
break;
}
if (!result_width && !result_style && !result_color) {
return false;
}
if (!result_width) {
result_width = CSSInitialValue::Create();
}
if (!result_style) {
result_style = CSSInitialValue::Create();
}
if (!result_color) {
result_color = CSSInitialValue::Create();
}
return true;
}
// This should go away once we drop support for -webkit-gradient
static CSSPrimitiveValue* ConsumeDeprecatedGradientPoint(
CSSParserTokenRange& args,
const CSSParserContext& context,
bool horizontal) {
if (args.Peek().GetType() == kIdentToken) {
if ((horizontal && ConsumeIdent<CSSValueID::kLeft>(args)) ||
(!horizontal && ConsumeIdent<CSSValueID::kTop>(args))) {
return CSSNumericLiteralValue::Create(
0., CSSPrimitiveValue::UnitType::kPercentage);
}
if ((horizontal && ConsumeIdent<CSSValueID::kRight>(args)) ||
(!horizontal && ConsumeIdent<CSSValueID::kBottom>(args))) {
return CSSNumericLiteralValue::Create(
100., CSSPrimitiveValue::UnitType::kPercentage);
}
if (ConsumeIdent<CSSValueID::kCenter>(args)) {
return CSSNumericLiteralValue::Create(
50., CSSPrimitiveValue::UnitType::kPercentage);
}
return nullptr;
}
CSSPrimitiveValue* result =
ConsumePercent(args, context, CSSPrimitiveValue::ValueRange::kAll);
if (!result) {
result = ConsumeNumber(args, context, CSSPrimitiveValue::ValueRange::kAll);
}
return result;
}
// Used to parse colors for -webkit-gradient(...).
static CSSValue* ConsumeDeprecatedGradientStopColor(
CSSParserTokenRange& args,
const CSSParserContext& context) {
if (args.Peek().Id() == CSSValueID::kCurrentcolor) {
return nullptr;
}
return ConsumeColor(args, context);
}
static bool ConsumeDeprecatedGradientColorStop(
CSSParserTokenRange& range,
cssvalue::CSSGradientColorStop& stop,
const CSSParserContext& context) {
CSSValueID id = range.Peek().FunctionId();
if (id != CSSValueID::kFrom && id != CSSValueID::kTo &&
id != CSSValueID::kColorStop) {
return false;
}
CSSParserTokenRange args = ConsumeFunction(range);
double position;
if (id == CSSValueID::kFrom || id == CSSValueID::kTo) {
position = (id == CSSValueID::kFrom) ? 0 : 1;
} else {
DCHECK(id == CSSValueID::kColorStop);
if (CSSPrimitiveValue* percent_value = ConsumePercent(
args, context, CSSPrimitiveValue::ValueRange::kAll)) {
position = percent_value->GetDoubleValue() / 100.0;
} else if (!ConsumeNumberRaw(args, context, position)) {
return false;
}
if (!ConsumeCommaIncludingWhitespace(args)) {
return false;
}
}
stop.offset_ = CSSNumericLiteralValue::Create(
position, CSSPrimitiveValue::UnitType::kNumber);
stop.color_ = ConsumeDeprecatedGradientStopColor(args, context);
return stop.color_ && args.AtEnd();
}
static CSSValue* ConsumeDeprecatedGradient(CSSParserTokenRange& args,
const CSSParserContext& context) {
CSSValueID id = args.ConsumeIncludingWhitespace().Id();
if (id != CSSValueID::kRadial && id != CSSValueID::kLinear) {
return nullptr;
}
if (!ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
const CSSPrimitiveValue* first_x =
ConsumeDeprecatedGradientPoint(args, context, true);
if (!first_x) {
return nullptr;
}
const CSSPrimitiveValue* first_y =
ConsumeDeprecatedGradientPoint(args, context, false);
if (!first_y) {
return nullptr;
}
if (!ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
// For radial gradients only, we now expect a numeric radius.
const CSSPrimitiveValue* first_radius = nullptr;
if (id == CSSValueID::kRadial) {
first_radius = ConsumeNumber(args, context,
CSSPrimitiveValue::ValueRange::kNonNegative);
if (!first_radius || !ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
}
const CSSPrimitiveValue* second_x =
ConsumeDeprecatedGradientPoint(args, context, true);
if (!second_x) {
return nullptr;
}
const CSSPrimitiveValue* second_y =
ConsumeDeprecatedGradientPoint(args, context, false);
if (!second_y) {
return nullptr;
}
// For radial gradients only, we now expect the second radius.
const CSSPrimitiveValue* second_radius = nullptr;
if (id == CSSValueID::kRadial) {
if (!ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
second_radius = ConsumeNumber(args, context,
CSSPrimitiveValue::ValueRange::kNonNegative);
if (!second_radius) {
return nullptr;
}
}
cssvalue::CSSGradientValue* result;
if (id == CSSValueID::kRadial) {
result = MakeGarbageCollected<cssvalue::CSSRadialGradientValue>(
first_x, first_y, first_radius, second_x, second_y, second_radius,
cssvalue::kNonRepeating, cssvalue::kCSSDeprecatedRadialGradient);
} else {
result = MakeGarbageCollected<cssvalue::CSSLinearGradientValue>(
first_x, first_y, second_x, second_y, nullptr, cssvalue::kNonRepeating,
cssvalue::kCSSDeprecatedLinearGradient);
}
cssvalue::CSSGradientColorStop stop;
while (ConsumeCommaIncludingWhitespace(args)) {
if (!ConsumeDeprecatedGradientColorStop(args, stop, context)) {
return nullptr;
}
result->AddStop(stop);
}
return result;
}
static CSSPrimitiveValue* ConsumeGradientAngleOrPercent(
CSSParserTokenRange& range,
const CSSParserContext& context,
CSSPrimitiveValue::ValueRange value_range,
UnitlessQuirk) {
const CSSParserToken& token = range.Peek();
if (token.GetType() == kDimensionToken || token.GetType() == kNumberToken) {
return ConsumeAngle(range, context, WebFeature::kUnitlessZeroAngleGradient);
}
if (token.GetType() == kPercentageToken) {
return ConsumePercent(range, context, value_range);
}
MathFunctionParser math_parser(range, context, value_range);
if (const CSSMathFunctionValue* calculation = math_parser.Value()) {
CalculationCategory category = calculation->Category();
// TODO(fs): Add and support kCalcPercentAngle?
if (category == kCalcAngle || category == kCalcPercent) {
return math_parser.ConsumeValue();
}
}
return nullptr;
}
using PositionFunctor = CSSPrimitiveValue* (*)(CSSParserTokenRange&,
const CSSParserContext&,
CSSPrimitiveValue::ValueRange,
UnitlessQuirk);
static bool ConsumeGradientColorStops(CSSParserTokenRange& range,
const CSSParserContext& context,
cssvalue::CSSGradientValue* gradient,
PositionFunctor consume_position_func) {
bool supports_color_hints =
gradient->GradientType() == cssvalue::kCSSLinearGradient ||
gradient->GradientType() == cssvalue::kCSSRadialGradient ||
gradient->GradientType() == cssvalue::kCSSConicGradient;
// The first color stop cannot be a color hint.
bool previous_stop_was_color_hint = true;
do {
cssvalue::CSSGradientColorStop stop;
stop.color_ = ConsumeColor(range, context);
// Two hints in a row are not allowed.
if (!stop.color_ &&
(!supports_color_hints || previous_stop_was_color_hint)) {
return false;
}
previous_stop_was_color_hint = !stop.color_;
stop.offset_ = consume_position_func(range, context,
CSSPrimitiveValue::ValueRange::kAll,
UnitlessQuirk::kForbid);
if (!stop.color_ && !stop.offset_) {
return false;
}
gradient->AddStop(stop);
if (!stop.color_ || !stop.offset_) {
continue;
}
// Optional second position.
stop.offset_ = consume_position_func(range, context,
CSSPrimitiveValue::ValueRange::kAll,
UnitlessQuirk::kForbid);
if (stop.offset_) {
gradient->AddStop(stop);
}
} while (ConsumeCommaIncludingWhitespace(range));
// The last color stop cannot be a color hint.
if (previous_stop_was_color_hint) {
return false;
}
// Must have 2 or more stops to be valid.
return gradient->StopCount() >= 2;
}
static CSSValue* ConsumeDeprecatedRadialGradient(
CSSParserTokenRange& args,
const CSSParserContext& context,
cssvalue::CSSGradientRepeat repeating) {
CSSValue* center_x = nullptr;
CSSValue* center_y = nullptr;
ConsumeOneOrTwoValuedPosition(args, context, UnitlessQuirk::kForbid, center_x,
center_y);
if ((center_x || center_y) && !ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
const CSSIdentifierValue* shape =
ConsumeIdent<CSSValueID::kCircle, CSSValueID::kEllipse>(args);
const CSSIdentifierValue* size_keyword =
ConsumeIdent<CSSValueID::kClosestSide, CSSValueID::kClosestCorner,
CSSValueID::kFarthestSide, CSSValueID::kFarthestCorner,
CSSValueID::kContain, CSSValueID::kCover>(args);
if (!shape) {
shape = ConsumeIdent<CSSValueID::kCircle, CSSValueID::kEllipse>(args);
}
// Or, two lengths or percentages
const CSSPrimitiveValue* horizontal_size = nullptr;
const CSSPrimitiveValue* vertical_size = nullptr;
if (!shape && !size_keyword) {
horizontal_size = ConsumeLengthOrPercent(
args, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (horizontal_size) {
vertical_size = ConsumeLengthOrPercent(
args, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (!vertical_size) {
return nullptr;
}
ConsumeCommaIncludingWhitespace(args);
}
} else {
ConsumeCommaIncludingWhitespace(args);
}
cssvalue::CSSGradientValue* result =
MakeGarbageCollected<cssvalue::CSSRadialGradientValue>(
center_x, center_y, shape, size_keyword, horizontal_size,
vertical_size, repeating, cssvalue::kCSSPrefixedRadialGradient);
return ConsumeGradientColorStops(args, context, result,
ConsumeGradientLengthOrPercent)
? result
: nullptr;
}
static CSSValue* ConsumeRadialGradient(CSSParserTokenRange& args,
const CSSParserContext& context,
cssvalue::CSSGradientRepeat repeating) {
const CSSIdentifierValue* shape = nullptr;
const CSSIdentifierValue* size_keyword = nullptr;
const CSSPrimitiveValue* horizontal_size = nullptr;
const CSSPrimitiveValue* vertical_size = nullptr;
// First part of grammar, the size/shape/color space clause:
// [ in <color-space>? &&
// [[ circle || <length> ] |
// [ ellipse || [ <length> | <percentage> ]{2} ] |
// [ [ circle | ellipse] || <size-keyword> ]] ]
Color::ColorSpace color_space;
Color::HueInterpolationMethod hue_interpolation_method =
Color::HueInterpolationMethod::kShorter;
bool has_color_space = ConsumeColorInterpolationSpace(
args, color_space, hue_interpolation_method);
for (int i = 0; i < 3; ++i) {
if (args.Peek().GetType() == kIdentToken) {
CSSValueID id = args.Peek().Id();
if (id == CSSValueID::kCircle || id == CSSValueID::kEllipse) {
if (shape) {
return nullptr;
}
shape = ConsumeIdent(args);
} else if (id == CSSValueID::kClosestSide ||
id == CSSValueID::kClosestCorner ||
id == CSSValueID::kFarthestSide ||
id == CSSValueID::kFarthestCorner) {
if (size_keyword) {
return nullptr;
}
size_keyword = ConsumeIdent(args);
} else {
break;
}
} else {
CSSPrimitiveValue* center = ConsumeLengthOrPercent(
args, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (!center) {
break;
}
if (horizontal_size) {
return nullptr;
}
horizontal_size = center;
center = ConsumeLengthOrPercent(
args, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (center) {
vertical_size = center;
++i;
}
}
}
// You can specify size as a keyword or a length/percentage, not both.
if (size_keyword && horizontal_size) {
return nullptr;
}
// Circles must have 0 or 1 lengths.
if (shape && shape->GetValueID() == CSSValueID::kCircle && vertical_size) {
return nullptr;
}
// Ellipses must have 0 or 2 length/percentages.
if (shape && shape->GetValueID() == CSSValueID::kEllipse && horizontal_size &&
!vertical_size) {
return nullptr;
}
// If there's only one size, it must be a length.
if (!vertical_size && horizontal_size && horizontal_size->IsPercentage()) {
return nullptr;
}
if ((horizontal_size &&
horizontal_size->IsCalculatedPercentageWithLength()) ||
(vertical_size && vertical_size->IsCalculatedPercentageWithLength())) {
return nullptr;
}
CSSValue* center_x = nullptr;
CSSValue* center_y = nullptr;
if (args.Peek().Id() == CSSValueID::kAt) {
args.ConsumeIncludingWhitespace();
ConsumePosition(args, context, UnitlessQuirk::kForbid,
absl::optional<WebFeature>(), center_x, center_y);
if (!(center_x && center_y)) {
return nullptr;
}
// Right now, CSS radial gradients have the same start and end centers.
}
if (!has_color_space) {
has_color_space = ConsumeColorInterpolationSpace(args, color_space,
hue_interpolation_method);
}
if ((shape || size_keyword || horizontal_size || center_x || center_y ||
has_color_space) &&
!ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
cssvalue::CSSGradientValue* result =
MakeGarbageCollected<cssvalue::CSSRadialGradientValue>(
center_x, center_y, shape, size_keyword, horizontal_size,
vertical_size, repeating, cssvalue::kCSSRadialGradient);
if (has_color_space) {
result->SetColorInterpolationSpace(color_space, hue_interpolation_method);
context.Count(WebFeature::kCSSColorGradientColorSpace);
}
return ConsumeGradientColorStops(args, context, result,
ConsumeGradientLengthOrPercent)
? result
: nullptr;
}
static CSSValue* ConsumeLinearGradient(
CSSParserTokenRange& args,
const CSSParserContext& context,
cssvalue::CSSGradientRepeat repeating,
cssvalue::CSSGradientType gradient_type) {
// First part of grammar, the size/shape/color space clause:
// [ in <color-space>? || [ <angle> | to <side-or-corner> ]?]
bool expect_comma = true;
Color::ColorSpace color_space;
Color::HueInterpolationMethod hue_interpolation_method =
Color::HueInterpolationMethod::kShorter;
bool has_color_space = ConsumeColorInterpolationSpace(
args, color_space, hue_interpolation_method);
const CSSPrimitiveValue* angle =
ConsumeAngle(args, context, WebFeature::kUnitlessZeroAngleGradient);
const CSSIdentifierValue* end_x = nullptr;
const CSSIdentifierValue* end_y = nullptr;
if (!angle) {
// <side-or-corner> parsing
if (gradient_type == cssvalue::kCSSPrefixedLinearGradient ||
ConsumeIdent<CSSValueID::kTo>(args)) {
end_x = ConsumeIdent<CSSValueID::kLeft, CSSValueID::kRight>(args);
end_y = ConsumeIdent<CSSValueID::kBottom, CSSValueID::kTop>(args);
if (!end_x && !end_y) {
if (gradient_type == cssvalue::kCSSLinearGradient) {
return nullptr;
}
end_y = CSSIdentifierValue::Create(CSSValueID::kTop);
expect_comma = false;
} else if (!end_x) {
end_x = ConsumeIdent<CSSValueID::kLeft, CSSValueID::kRight>(args);
}
} else {
// No <angle> or <side-to-corner>
expect_comma = false;
}
}
// It's possible that the <color-space> comes after the [ <angle> |
// <side-or-corner> ]
if (!has_color_space) {
has_color_space = ConsumeColorInterpolationSpace(args, color_space,
hue_interpolation_method);
}
if (has_color_space) {
expect_comma = true;
}
if (expect_comma && !ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
cssvalue::CSSGradientValue* result =
MakeGarbageCollected<cssvalue::CSSLinearGradientValue>(
end_x, end_y, nullptr, nullptr, angle, repeating, gradient_type);
if (has_color_space) {
result->SetColorInterpolationSpace(color_space, hue_interpolation_method);
context.Count(WebFeature::kCSSColorGradientColorSpace);
}
return ConsumeGradientColorStops(args, context, result,
ConsumeGradientLengthOrPercent)
? result
: nullptr;
}
static CSSValue* ConsumeConicGradient(CSSParserTokenRange& args,
const CSSParserContext& context,
cssvalue::CSSGradientRepeat repeating) {
Color::ColorSpace color_space;
Color::HueInterpolationMethod hue_interpolation_method =
Color::HueInterpolationMethod::kShorter;
bool has_color_space = ConsumeColorInterpolationSpace(
args, color_space, hue_interpolation_method);
const CSSPrimitiveValue* from_angle = nullptr;
if (ConsumeIdent<CSSValueID::kFrom>(args)) {
if (!(from_angle = ConsumeAngle(args, context,
WebFeature::kUnitlessZeroAngleGradient))) {
return nullptr;
}
}
CSSValue* center_x = nullptr;
CSSValue* center_y = nullptr;
if (ConsumeIdent<CSSValueID::kAt>(args)) {
if (!ConsumePosition(args, context, UnitlessQuirk::kForbid,
absl::optional<WebFeature>(), center_x, center_y)) {
return nullptr;
}
}
if (!has_color_space) {
has_color_space = ConsumeColorInterpolationSpace(args, color_space,
hue_interpolation_method);
}
// Comma separator required when fromAngle, position or color_space is
// present.
if ((from_angle || center_x || center_y || has_color_space) &&
!ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
auto* result = MakeGarbageCollected<cssvalue::CSSConicGradientValue>(
center_x, center_y, from_angle, repeating);
if (has_color_space) {
result->SetColorInterpolationSpace(color_space, hue_interpolation_method);
context.Count(WebFeature::kCSSColorGradientColorSpace);
}
return ConsumeGradientColorStops(args, context, result,
ConsumeGradientAngleOrPercent)
? result
: nullptr;
}
CSSValue* ConsumeImageOrNone(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kNone) {
return ConsumeIdent(range);
}
return ConsumeImage(range, context);
}
CSSValue* ConsumeAxis(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValueID axis_id = range.Peek().Id();
if (axis_id == CSSValueID::kX || axis_id == CSSValueID::kY ||
axis_id == CSSValueID::kZ) {
ConsumeIdent(range);
return MakeGarbageCollected<cssvalue::CSSAxisValue>(axis_id);
}
CSSValue* x_dimension =
ConsumeNumber(range, context, CSSPrimitiveValue::ValueRange::kAll);
CSSValue* y_dimension =
ConsumeNumber(range, context, CSSPrimitiveValue::ValueRange::kAll);
CSSValue* z_dimension =
ConsumeNumber(range, context, CSSPrimitiveValue::ValueRange::kAll);
if (!x_dimension || !y_dimension || !z_dimension) {
return nullptr;
}
double x = To<CSSPrimitiveValue>(x_dimension)->GetDoubleValue();
double y = To<CSSPrimitiveValue>(y_dimension)->GetDoubleValue();
double z = To<CSSPrimitiveValue>(z_dimension)->GetDoubleValue();
return MakeGarbageCollected<cssvalue::CSSAxisValue>(x, y, z);
}
CSSValue* ConsumeIntrinsicSizeLonghandOld(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (css_parsing_utils::IdentMatches<CSSValueID::kAuto>(range.Peek().Id())) {
return css_parsing_utils::ConsumeIdent(range);
}
return css_parsing_utils::ConsumeLength(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative);
}
CSSValue* ConsumeIntrinsicSizeLonghandNew(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (css_parsing_utils::IdentMatches<CSSValueID::kNone>(range.Peek().Id())) {
return css_parsing_utils::ConsumeIdent(range);
}
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
if (css_parsing_utils::IdentMatches<CSSValueID::kAuto>(range.Peek().Id())) {
list->Append(*css_parsing_utils::ConsumeIdent(range));
}
CSSValue* length = css_parsing_utils::ConsumeLength(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (!length) {
return nullptr;
}
list->Append(*length);
return list;
}
CSSValue* ConsumeIntrinsicSizeLonghand(CSSParserTokenRange& range,
const CSSParserContext& context) {
return ConsumeIntrinsicSizeLonghandNew(range, context);
}
static CSSValue* ConsumeCrossFade(CSSParserTokenRange& args,
const CSSParserContext& context) {
CSSValue* from_image_value = ConsumeImageOrNone(args, context);
if (!from_image_value || !ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
CSSValue* to_image_value = ConsumeImageOrNone(args, context);
if (!to_image_value || !ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
CSSPrimitiveValue* percentage = nullptr;
if (CSSPrimitiveValue* percent_value =
ConsumePercent(args, context, CSSPrimitiveValue::ValueRange::kAll)) {
percentage = CSSNumericLiteralValue::Create(
ClampTo<double>(percent_value->GetDoubleValue() / 100.0, 0, 1),
CSSPrimitiveValue::UnitType::kNumber);
} else if (CSSPrimitiveValue* number_value = ConsumeNumber(
args, context, CSSPrimitiveValue::ValueRange::kAll)) {
percentage = CSSNumericLiteralValue::Create(
ClampTo<double>(number_value->GetDoubleValue(), 0, 1),
CSSPrimitiveValue::UnitType::kNumber);
}
if (!percentage) {
return nullptr;
}
return MakeGarbageCollected<cssvalue::CSSCrossfadeValue>(
from_image_value, to_image_value, percentage);
}
static CSSValue* ConsumePaint(CSSParserTokenRange& args,
const CSSParserContext& context) {
CSSCustomIdentValue* name = ConsumeCustomIdent(args, context);
if (!name) {
return nullptr;
}
if (args.AtEnd()) {
return MakeGarbageCollected<CSSPaintValue>(name);
}
if (!RuntimeEnabledFeatures::CSSPaintAPIArgumentsEnabled()) {
// Arguments not enabled, but exists. Invalid.
return nullptr;
}
// Begin parse paint arguments.
if (!ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
// Consume arguments.
// TODO(renjieliu): We may want to optimize the implementation by resolve
// variables early if paint function is registered.
Vector<CSSParserToken> argument_tokens;
Vector<scoped_refptr<CSSVariableData>> variable_data;
while (!args.AtEnd()) {
if (args.Peek().GetType() != kCommaToken) {
argument_tokens.AppendVector(ConsumeFunctionArgsOrNot(args));
} else {
if (!AddCSSPaintArgument(argument_tokens, &variable_data)) {
return nullptr;
}
argument_tokens.clear();
if (!ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
}
}
if (!AddCSSPaintArgument(argument_tokens, &variable_data)) {
return nullptr;
}
return MakeGarbageCollected<CSSPaintValue>(name, variable_data);
}
static CSSValue* ConsumeGeneratedImage(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValueID id = range.Peek().FunctionId();
if (!IsGeneratedImage(id)) {
return nullptr;
}
CSSParserTokenRange range_copy = range;
CSSParserTokenRange args = ConsumeFunction(range_copy);
CSSValue* result = nullptr;
if (id == CSSValueID::kRadialGradient) {
result = ConsumeRadialGradient(args, context, cssvalue::kNonRepeating);
} else if (id == CSSValueID::kRepeatingRadialGradient) {
result = ConsumeRadialGradient(args, context, cssvalue::kRepeating);
} else if (id == CSSValueID::kWebkitLinearGradient) {
context.Count(WebFeature::kDeprecatedWebKitLinearGradient);
result = ConsumeLinearGradient(args, context, cssvalue::kNonRepeating,
cssvalue::kCSSPrefixedLinearGradient);
} else if (id == CSSValueID::kWebkitRepeatingLinearGradient) {
context.Count(WebFeature::kDeprecatedWebKitRepeatingLinearGradient);
result = ConsumeLinearGradient(args, context, cssvalue::kRepeating,
cssvalue::kCSSPrefixedLinearGradient);
} else if (id == CSSValueID::kRepeatingLinearGradient) {
result = ConsumeLinearGradient(args, context, cssvalue::kRepeating,
cssvalue::kCSSLinearGradient);
} else if (id == CSSValueID::kLinearGradient) {
result = ConsumeLinearGradient(args, context, cssvalue::kNonRepeating,
cssvalue::kCSSLinearGradient);
} else if (id == CSSValueID::kWebkitGradient) {
context.Count(WebFeature::kDeprecatedWebKitGradient);
result = ConsumeDeprecatedGradient(args, context);
} else if (id == CSSValueID::kWebkitRadialGradient) {
context.Count(WebFeature::kDeprecatedWebKitRadialGradient);
result =
ConsumeDeprecatedRadialGradient(args, context, cssvalue::kNonRepeating);
} else if (id == CSSValueID::kWebkitRepeatingRadialGradient) {
context.Count(WebFeature::kDeprecatedWebKitRepeatingRadialGradient);
result =
ConsumeDeprecatedRadialGradient(args, context, cssvalue::kRepeating);
} else if (id == CSSValueID::kConicGradient) {
result = ConsumeConicGradient(args, context, cssvalue::kNonRepeating);
} else if (id == CSSValueID::kRepeatingConicGradient) {
result = ConsumeConicGradient(args, context, cssvalue::kRepeating);
} else if (id == CSSValueID::kWebkitCrossFade) {
result = ConsumeCrossFade(args, context);
} else if (id == CSSValueID::kPaint) {
result = context.IsSecureContext() ? ConsumePaint(args, context) : nullptr;
}
if (!result || !args.AtEnd()) {
return nullptr;
}
WebFeature feature;
if (id == CSSValueID::kWebkitCrossFade) {
feature = WebFeature::kWebkitCrossFade;
} else if (id == CSSValueID::kPaint) {
feature = WebFeature::kCSSPaintFunction;
} else {
feature = WebFeature::kCSSGradient;
}
context.Count(feature);
range = range_copy;
return result;
}
static CSSImageValue* CreateCSSImageValueWithReferrer(
const StringView& uri,
const CSSParserContext& context) {
AtomicString raw_value = uri.ToAtomicString();
auto* image_value = MakeGarbageCollected<CSSImageValue>(
raw_value, context.CompleteURL(raw_value), context.GetReferrer(),
context.IsOriginClean() ? OriginClean::kTrue : OriginClean::kFalse,
context.IsAdRelated());
if (context.Mode() == kUASheetMode) {
image_value->SetInitiator(fetch_initiator_type_names::kUacss);
}
return image_value;
}
static CSSImageSetTypeValue* ConsumeImageSetType(CSSParserTokenRange& range) {
if (!RuntimeEnabledFeatures::CSSImageSetEnabled() ||
range.Peek().FunctionId() != CSSValueID::kType) {
return nullptr;
}
CSSParserTokenRange range_copy = range;
CSSParserTokenRange args = ConsumeFunction(range_copy);
auto type = ConsumeStringAsStringView(args);
if (type.IsNull() || !args.AtEnd()) {
return nullptr;
}
range = range_copy;
return MakeGarbageCollected<CSSImageSetTypeValue>(type.ToString());
}
static CSSImageSetOptionValue* ConsumeImageSetOption(
CSSParserTokenRange& range,
const CSSParserContext& context,
ConsumeGeneratedImagePolicy generated_image_policy) {
const ConsumeStringUrlImagePolicy string_url_image_policy =
RuntimeEnabledFeatures::CSSImageSetEnabled()
? ConsumeStringUrlImagePolicy::kAllow
: ConsumeStringUrlImagePolicy::kForbid;
const CSSValue* image = ConsumeImage(range, context, generated_image_policy,
string_url_image_policy,
ConsumeImageSetImagePolicy::kForbid);
if (!image) {
return nullptr;
}
// Type could appear before or after resolution
CSSImageSetTypeValue* type = ConsumeImageSetType(range);
if (!RuntimeEnabledFeatures::CSSImageSetEnabled() &&
range.Peek().GetType() != kDimensionToken &&
range.Peek().GetUnitType() != CSSPrimitiveValue::UnitType::kX) {
return nullptr;
}
CSSPrimitiveValue* resolution = ConsumeResolution(range, context);
if (!type) {
type = ConsumeImageSetType(range);
}
return MakeGarbageCollected<CSSImageSetOptionValue>(image, resolution, type);
}
static CSSValue* ConsumeImageSet(
CSSParserTokenRange& range,
const CSSParserContext& context,
ConsumeGeneratedImagePolicy generated_image_policy =
ConsumeGeneratedImagePolicy::kAllow) {
CSSParserTokenRange range_copy = range;
CSSParserTokenRange args = ConsumeFunction(range_copy);
auto* image_set = MakeGarbageCollected<CSSImageSetValue>();
do {
auto* image_set_option =
ConsumeImageSetOption(args, context, generated_image_policy);
if (!image_set_option) {
return nullptr;
}
image_set->Append(*image_set_option);
} while (ConsumeCommaIncludingWhitespace(args));
if (!args.AtEnd()) {
return nullptr;
}
switch (range.Peek().FunctionId()) {
case CSSValueID::kWebkitImageSet:
context.Count(WebFeature::kWebkitImageSet);
break;
case CSSValueID::kImageSet:
context.Count(WebFeature::kImageSet);
break;
default:
NOTREACHED();
break;
}
range = range_copy;
return image_set;
}
CSSValue* ConsumeImage(
CSSParserTokenRange& range,
const CSSParserContext& context,
const ConsumeGeneratedImagePolicy generated_image_policy,
const ConsumeStringUrlImagePolicy string_url_image_policy,
const ConsumeImageSetImagePolicy image_set_image_policy) {
StringView uri = ConsumeUrlAsStringView(range, context);
if (!uri.IsNull()) {
return CreateCSSImageValueWithReferrer(uri, context);
}
if (string_url_image_policy == ConsumeStringUrlImagePolicy::kAllow) {
StringView uri_string = ConsumeStringAsStringView(range);
if (!uri_string.IsNull()) {
uri_string = ApplyFetchRestrictions(uri_string, context);
return CreateCSSImageValueWithReferrer(uri_string, context);
}
}
if (range.Peek().GetType() == kFunctionToken) {
CSSValueID id = range.Peek().FunctionId();
if (image_set_image_policy == ConsumeImageSetImagePolicy::kAllow &&
IsImageSet(id)) {
return ConsumeImageSet(range, context, generated_image_policy);
}
if (generated_image_policy == ConsumeGeneratedImagePolicy::kAllow &&
IsGeneratedImage(id)) {
return ConsumeGeneratedImage(range, context);
}
return ConsumeInternalLightDark(ConsumeImageOrNone, range, context);
}
return nullptr;
}
// https://drafts.csswg.org/css-shapes-1/#typedef-shape-box
CSSIdentifierValue* ConsumeShapeBox(CSSParserTokenRange& range) {
return ConsumeIdent<CSSValueID::kContentBox, CSSValueID::kPaddingBox,
CSSValueID::kBorderBox, CSSValueID::kMarginBox>(range);
}
// https://drafts.csswg.org/css-box-4/#typedef-visual-box
CSSIdentifierValue* ConsumeVisualBox(CSSParserTokenRange& range) {
return ConsumeIdent<CSSValueID::kContentBox, CSSValueID::kPaddingBox,
CSSValueID::kBorderBox>(range);
}
// https://drafts.csswg.org/css-box-4/#typedef-coord-box
CSSIdentifierValue* ConsumeCoordBox(CSSParserTokenRange& range) {
return ConsumeIdent<CSSValueID::kContentBox, CSSValueID::kPaddingBox,
CSSValueID::kBorderBox, CSSValueID::kFillBox,
CSSValueID::kStrokeBox, CSSValueID::kViewBox>(range);
}
void AddProperty(CSSPropertyID resolved_property,
CSSPropertyID current_shorthand,
const CSSValue& value,
bool important,
IsImplicitProperty implicit,
HeapVector<CSSPropertyValue, 64>& properties) {
DCHECK(!IsPropertyAlias(resolved_property));
DCHECK(implicit == IsImplicitProperty::kNotImplicit ||
implicit == IsImplicitProperty::kImplicit);
int shorthand_index = 0;
bool set_from_shorthand = false;
if (IsValidCSSPropertyID(current_shorthand)) {
Vector<StylePropertyShorthand, 4> shorthands;
getMatchingShorthandsForLonghand(resolved_property, &shorthands);
set_from_shorthand = true;
if (shorthands.size() > 1) {
shorthand_index =
indexOfShorthandForLonghand(current_shorthand, shorthands);
}
}
properties.push_back(CSSPropertyValue(
CSSPropertyName(resolved_property), value, important, set_from_shorthand,
shorthand_index, implicit == IsImplicitProperty::kImplicit));
}
CSSValue* ConsumeTransformValue(CSSParserTokenRange& range,
const CSSParserContext& context) {
bool use_legacy_parsing = false;
return ConsumeTransformValue(range, context, use_legacy_parsing);
}
CSSValue* ConsumeTransformList(CSSParserTokenRange& range,
const CSSParserContext& context) {
return ConsumeTransformList(range, context, CSSParserLocalContext());
}
CSSValue* ConsumeFilterFunctionList(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kNone) {
return ConsumeIdent(range);
}
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
do {
CSSValue* filter_value = ConsumeUrl(range, context);
if (!filter_value) {
filter_value = ConsumeFilterFunction(range, context);
if (!filter_value) {
return nullptr;
}
}
list->Append(*filter_value);
} while (!range.AtEnd());
return list;
}
void CountKeywordOnlyPropertyUsage(CSSPropertyID property,
const CSSParserContext& context,
CSSValueID value_id) {
if (!context.IsUseCounterRecordingEnabled()) {
return;
}
switch (property) {
case CSSPropertyID::kAppearance:
// TODO(crbug.com/924486): Remove CSS value slider-horizontal,
// slider-vertical and the associated warnings.
if ((value_id == CSSValueID::kSliderHorizontal ||
value_id == CSSValueID::kSliderVertical) ||
(!RuntimeEnabledFeatures::RemoveNonStandardAppearanceValueEnabled() &&
(value_id == CSSValueID::kInnerSpinButton ||
value_id == CSSValueID::kMediaSlider ||
value_id == CSSValueID::kMediaSliderthumb ||
value_id == CSSValueID::kMediaVolumeSlider ||
value_id == CSSValueID::kMediaVolumeSliderthumb ||
value_id == CSSValueID::kSliderthumbHorizontal ||
value_id == CSSValueID::kSliderthumbVertical ||
value_id == CSSValueID::kSearchfieldCancelButton))) {
if (const auto* document = context.GetDocument()) {
document->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther,
mojom::blink::ConsoleMessageLevel::kWarning,
String("The keyword '") + getValueName(value_id) +
"' specified to an 'appearance' property is not "
"standardized. It will be removed in the future."));
}
context.Count(WebFeature::kCSSValueAppearanceNonStandard);
}
[[fallthrough]];
// This function distinguishes 'appearance' and '-webkit-appearance'
// though other property aliases are handles as their aliased properties.
// See Appearance::ParseSingleValue().
case CSSPropertyID::kAliasWebkitAppearance: {
WebFeature feature;
if (value_id == CSSValueID::kNone) {
feature = WebFeature::kCSSValueAppearanceNone;
} else {
feature = WebFeature::kCSSValueAppearanceNotNone;
if (value_id == CSSValueID::kButton) {
feature = WebFeature::kCSSValueAppearanceButton;
} else if (value_id == CSSValueID::kCheckbox) {
feature = WebFeature::kCSSValueAppearanceCheckbox;
} else if (value_id == CSSValueID::kInnerSpinButton) {
feature = WebFeature::kCSSValueAppearanceInnerSpinButton;
} else if (value_id == CSSValueID::kMediaSlider) {
feature = WebFeature::kCSSValueAppearanceMediaSlider;
} else if (value_id == CSSValueID::kMediaSliderthumb) {
feature = WebFeature::kCSSValueAppearanceMediaSliderthumb;
} else if (value_id == CSSValueID::kMediaVolumeSlider) {
feature = WebFeature::kCSSValueAppearanceMediaVolumeSlider;
} else if (value_id == CSSValueID::kMediaVolumeSliderthumb) {
feature = WebFeature::kCSSValueAppearanceMediaVolumeSliderthumb;
} else if (value_id == CSSValueID::kMenulist) {
feature = WebFeature::kCSSValueAppearanceMenulist;
} else if (value_id == CSSValueID::kMenulistButton) {
feature = WebFeature::kCSSValueAppearanceMenulistButton;
} else if (value_id == CSSValueID::kMeter) {
feature = WebFeature::kCSSValueAppearanceMeter;
} else if (value_id == CSSValueID::kListbox) {
feature = WebFeature::kCSSValueAppearanceListbox;
} else if (value_id == CSSValueID::kProgressBar) {
feature = WebFeature::kCSSValueAppearanceProgressBar;
} else if (value_id == CSSValueID::kPushButton) {
feature = WebFeature::kCSSValueAppearancePushButton;
} else if (value_id == CSSValueID::kRadio) {
feature = WebFeature::kCSSValueAppearanceRadio;
} else if (value_id == CSSValueID::kSearchfieldCancelButton) {
feature = WebFeature::kCSSValueAppearanceSearchCancel;
} else if (value_id == CSSValueID::kSquareButton) {
feature = WebFeature::kCSSValueAppearanceSquareButton;
} else if (value_id == CSSValueID::kSearchfield) {
feature = WebFeature::kCSSValueAppearanceSearchField;
} else if (value_id == CSSValueID::kSliderHorizontal) {
feature = WebFeature::kCSSValueAppearanceSliderHorizontal;
} else if (value_id == CSSValueID::kSliderVertical) {
feature = WebFeature::kCSSValueAppearanceSliderVertical;
} else if (value_id == CSSValueID::kSliderthumbHorizontal) {
feature = WebFeature::kCSSValueAppearanceSliderthumbHorizontal;
} else if (value_id == CSSValueID::kSliderthumbVertical) {
feature = WebFeature::kCSSValueAppearanceSliderthumbVertical;
} else if (value_id == CSSValueID::kTextarea) {
feature = WebFeature::kCSSValueAppearanceTextarea;
} else if (value_id == CSSValueID::kTextfield) {
feature = WebFeature::kCSSValueAppearanceTextField;
} else {
feature = WebFeature::kCSSValueAppearanceOthers;
}
}
context.Count(feature);
break;
}
case CSSPropertyID::kWebkitUserModify: {
switch (value_id) {
case CSSValueID::kReadOnly:
context.Count(WebFeature::kCSSValueUserModifyReadOnly);
break;
case CSSValueID::kReadWrite:
context.Count(WebFeature::kCSSValueUserModifyReadWrite);
break;
case CSSValueID::kReadWritePlaintextOnly:
context.Count(WebFeature::kCSSValueUserModifyReadWritePlaintextOnly);
break;
default:
NOTREACHED();
}
break;
}
case CSSPropertyID::kDisplay:
if (value_id == CSSValueID::kContents) {
context.Count(WebFeature::kCSSValueDisplayContents);
}
break;
case CSSPropertyID::kOverflowX:
case CSSPropertyID::kOverflowY:
if (value_id == CSSValueID::kOverlay) {
context.Count(WebFeature::kCSSValueOverflowOverlay);
}
break;
default:
break;
}
}
const CSSValue* ParseLonghand(CSSPropertyID unresolved_property,
CSSPropertyID current_shorthand,
const CSSParserContext& context,
CSSParserTokenRange& range) {
CSSPropertyID property_id = ResolveCSSPropertyID(unresolved_property);
DCHECK(!CSSProperty::Get(property_id).IsShorthand());
if (CSSParserFastPaths::IsHandledByKeywordFastPath(property_id)) {
if (CSSParserFastPaths::IsValidKeywordPropertyAndValue(
property_id, range.Peek().Id(), context.Mode())) {
CountKeywordOnlyPropertyUsage(property_id, context, range.Peek().Id());
return ConsumeIdent(range);
}
return nullptr;
}
const auto local_context =
CSSParserLocalContext()
.WithAliasParsing(IsPropertyAlias(unresolved_property))
.WithCurrentShorthand(current_shorthand);
const CSSValue* result = To<Longhand>(CSSProperty::Get(property_id))
.ParseSingleValue(range, context, local_context);
return result;
}
bool ConsumeShorthandVia2Longhands(
const StylePropertyShorthand& shorthand,
bool important,
const CSSParserContext& context,
CSSParserTokenRange& range,
HeapVector<CSSPropertyValue, 64>& properties) {
DCHECK_EQ(shorthand.length(), 2u);
const CSSProperty** longhands = shorthand.properties();
const CSSValue* start =
ParseLonghand(longhands[0]->PropertyID(), shorthand.id(), context, range);
if (!start) {
return false;
}
const CSSValue* end =
ParseLonghand(longhands[1]->PropertyID(), shorthand.id(), context, range);
if (shorthand.id() == CSSPropertyID::kOverflow && start && end) {
context.Count(WebFeature::kTwoValuedOverflow);
}
if (!end) {
end = start;
}
AddProperty(longhands[0]->PropertyID(), shorthand.id(), *start, important,
IsImplicitProperty::kNotImplicit, properties);
AddProperty(longhands[1]->PropertyID(), shorthand.id(), *end, important,
IsImplicitProperty::kNotImplicit, properties);
return range.AtEnd();
}
bool ConsumeShorthandVia4Longhands(
const StylePropertyShorthand& shorthand,
bool important,
const CSSParserContext& context,
CSSParserTokenRange& range,
HeapVector<CSSPropertyValue, 64>& properties) {
DCHECK_EQ(shorthand.length(), 4u);
const CSSProperty** longhands = shorthand.properties();
const CSSValue* top =
ParseLonghand(longhands[0]->PropertyID(), shorthand.id(), context, range);
if (!top) {
return false;
}
const CSSValue* right =
ParseLonghand(longhands[1]->PropertyID(), shorthand.id(), context, range);
const CSSValue* bottom = nullptr;
const CSSValue* left = nullptr;
if (right) {
bottom = ParseLonghand(longhands[2]->PropertyID(), shorthand.id(), context,
range);
if (bottom) {
left = ParseLonghand(longhands[3]->PropertyID(), shorthand.id(), context,
range);
}
}
if (!right) {
right = top;
}
if (!bottom) {
bottom = top;
}
if (!left) {
left = right;
}
AddProperty(longhands[0]->PropertyID(), shorthand.id(), *top, important,
IsImplicitProperty::kNotImplicit, properties);
AddProperty(longhands[1]->PropertyID(), shorthand.id(), *right, important,
IsImplicitProperty::kNotImplicit, properties);
AddProperty(longhands[2]->PropertyID(), shorthand.id(), *bottom, important,
IsImplicitProperty::kNotImplicit, properties);
AddProperty(longhands[3]->PropertyID(), shorthand.id(), *left, important,
IsImplicitProperty::kNotImplicit, properties);
return range.AtEnd();
}
bool ConsumeShorthandGreedilyViaLonghands(
const StylePropertyShorthand& shorthand,
bool important,
const CSSParserContext& context,
CSSParserTokenRange& range,
HeapVector<CSSPropertyValue, 64>& properties,
bool use_initial_value_function) {
// Existing shorthands have at most 6 longhands.
DCHECK_LE(shorthand.length(), 6u);
const CSSValue* longhands[6] = {nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr};
const CSSProperty** shorthand_properties = shorthand.properties();
do {
bool found_longhand = false;
for (size_t i = 0; !found_longhand && i < shorthand.length(); ++i) {
if (longhands[i]) {
continue;
}
longhands[i] = ParseLonghand(shorthand_properties[i]->PropertyID(),
shorthand.id(), context, range);
if (longhands[i]) {
found_longhand = true;
}
}
if (!found_longhand) {
return false;
}
} while (!range.AtEnd());
for (size_t i = 0; i < shorthand.length(); ++i) {
if (longhands[i]) {
AddProperty(shorthand_properties[i]->PropertyID(), shorthand.id(),
*longhands[i], important, IsImplicitProperty::kNotImplicit,
properties);
} else {
const CSSValue* value =
use_initial_value_function
? To<Longhand>(shorthand_properties[i])->InitialValue()
: CSSInitialValue::Create();
AddProperty(shorthand_properties[i]->PropertyID(), shorthand.id(), *value,
important, IsImplicitProperty::kNotImplicit, properties);
}
}
return true;
}
void AddExpandedPropertyForValue(CSSPropertyID property,
const CSSValue& value,
bool important,
HeapVector<CSSPropertyValue, 64>& properties) {
const StylePropertyShorthand& shorthand = shorthandForProperty(property);
unsigned shorthand_length = shorthand.length();
DCHECK(shorthand_length);
const CSSProperty** longhands = shorthand.properties();
for (unsigned i = 0; i < shorthand_length; ++i) {
AddProperty(longhands[i]->PropertyID(), property, value, important,
IsImplicitProperty::kNotImplicit, properties);
}
}
bool IsBaselineKeyword(CSSValueID id) {
return IdentMatches<CSSValueID::kFirst, CSSValueID::kLast,
CSSValueID::kBaseline>(id);
}
bool IsSelfPositionKeyword(CSSValueID id) {
return IdentMatches<CSSValueID::kStart, CSSValueID::kEnd, CSSValueID::kCenter,
CSSValueID::kSelfStart, CSSValueID::kSelfEnd,
CSSValueID::kFlexStart, CSSValueID::kFlexEnd>(id);
}
bool IsSelfPositionOrLeftOrRightKeyword(CSSValueID id) {
return IsSelfPositionKeyword(id) || IsLeftOrRightKeyword(id);
}
bool IsContentPositionKeyword(CSSValueID id) {
return IdentMatches<CSSValueID::kStart, CSSValueID::kEnd, CSSValueID::kCenter,
CSSValueID::kFlexStart, CSSValueID::kFlexEnd>(id);
}
bool IsContentPositionOrLeftOrRightKeyword(CSSValueID id) {
return IsContentPositionKeyword(id) || IsLeftOrRightKeyword(id);
}
// https://drafts.csswg.org/css-values-4/#css-wide-keywords
bool IsCSSWideKeyword(CSSValueID id) {
return id == CSSValueID::kInherit || id == CSSValueID::kInitial ||
id == CSSValueID::kUnset || id == CSSValueID::kRevert ||
id == CSSValueID::kRevertLayer;
// This function should match the overload after it.
}
// https://drafts.csswg.org/css-values-4/#css-wide-keywords
bool IsCSSWideKeyword(StringView keyword) {
return EqualIgnoringASCIICase(keyword, "initial") ||
EqualIgnoringASCIICase(keyword, "inherit") ||
EqualIgnoringASCIICase(keyword, "unset") ||
EqualIgnoringASCIICase(keyword, "revert") ||
EqualIgnoringASCIICase(keyword, "revert-layer");
// This function should match the overload before it.
}
// https://drafts.csswg.org/css-cascade/#default
bool IsRevertKeyword(StringView keyword) {
return EqualIgnoringASCIICase(keyword, "revert");
}
// https://drafts.csswg.org/css-values-4/#identifier-value
bool IsDefaultKeyword(StringView keyword) {
return EqualIgnoringASCIICase(keyword, "default");
}
// https://drafts.csswg.org/css-syntax/#typedef-hash-token
bool IsHashIdentifier(const CSSParserToken& token) {
return token.GetType() == kHashToken &&
token.GetHashTokenType() == kHashTokenId;
}
bool IsDashedIdent(const CSSParserToken& token) {
if (token.GetType() != kIdentToken) {
return false;
}
DCHECK(!IsCSSWideKeyword(token.Value()));
return token.Value().ToString().StartsWith(kTwoDashes);
}
CSSValue* ConsumeCSSWideKeyword(CSSParserTokenRange& range) {
if (!IsCSSWideKeyword(range.Peek().Id())) {
return nullptr;
}
switch (range.ConsumeIncludingWhitespace().Id()) {
case CSSValueID::kInitial:
return CSSInitialValue::Create();
case CSSValueID::kInherit:
return CSSInheritedValue::Create();
case CSSValueID::kUnset:
return cssvalue::CSSUnsetValue::Create();
case CSSValueID::kRevert:
return cssvalue::CSSRevertValue::Create();
case CSSValueID::kRevertLayer:
return cssvalue::CSSRevertLayerValue::Create();
default:
NOTREACHED();
return nullptr;
}
}
bool IsTimelineName(const CSSParserToken& token) {
if (token.GetType() == kStringToken) {
return true;
}
return token.GetType() == kIdentToken &&
IsCustomIdent<CSSValueID::kNone>(token.Id());
}
CSSValue* ConsumeSelfPositionOverflowPosition(
CSSParserTokenRange& range,
IsPositionKeyword is_position_keyword) {
DCHECK(is_position_keyword);
CSSValueID id = range.Peek().Id();
if (IsAuto(id) || IsNormalOrStretch(id)) {
return ConsumeIdent(range);
}
if (CSSValue* baseline = ConsumeBaseline(range)) {
return baseline;
}
CSSIdentifierValue* overflow_position = ConsumeOverflowPositionKeyword(range);
if (!is_position_keyword(range.Peek().Id())) {
return nullptr;
}
CSSIdentifierValue* self_position = ConsumeIdent(range);
if (overflow_position) {
return MakeGarbageCollected<CSSValuePair>(
overflow_position, self_position, CSSValuePair::kDropIdenticalValues);
}
return self_position;
}
CSSValue* ConsumeContentDistributionOverflowPosition(
CSSParserTokenRange& range,
IsPositionKeyword is_position_keyword) {
DCHECK(is_position_keyword);
CSSValueID id = range.Peek().Id();
if (IdentMatches<CSSValueID::kNormal>(id)) {
return MakeGarbageCollected<cssvalue::CSSContentDistributionValue>(
CSSValueID::kInvalid, range.ConsumeIncludingWhitespace().Id(),
CSSValueID::kInvalid);
}
if (CSSValue* baseline = ConsumeFirstBaseline(range)) {
return MakeGarbageCollected<cssvalue::CSSContentDistributionValue>(
CSSValueID::kInvalid, GetBaselineKeyword(*baseline),
CSSValueID::kInvalid);
}
if (IsContentDistributionKeyword(id)) {
return MakeGarbageCollected<cssvalue::CSSContentDistributionValue>(
range.ConsumeIncludingWhitespace().Id(), CSSValueID::kInvalid,
CSSValueID::kInvalid);
}
CSSValueID overflow = IsOverflowKeyword(id)
? range.ConsumeIncludingWhitespace().Id()
: CSSValueID::kInvalid;
if (is_position_keyword(range.Peek().Id())) {
return MakeGarbageCollected<cssvalue::CSSContentDistributionValue>(
CSSValueID::kInvalid, range.ConsumeIncludingWhitespace().Id(),
overflow);
}
return nullptr;
}
CSSValue* ConsumeAnimationIterationCount(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kInfinite) {
return ConsumeIdent(range);
}
return ConsumeNumber(range, context,
CSSPrimitiveValue::ValueRange::kNonNegative);
}
CSSValue* ConsumeAnimationName(CSSParserTokenRange& range,
const CSSParserContext& context,
bool allow_quoted_name) {
if (range.Peek().Id() == CSSValueID::kNone) {
return 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(CSSValueID::kNone);
}
return MakeGarbageCollected<CSSCustomIdentValue>(
token.Value().ToAtomicString());
}
return ConsumeCustomIdent(range, context);
}
CSSValue* ConsumeScrollFunction(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().FunctionId() != CSSValueID::kScroll) {
return nullptr;
}
CSSParserTokenRange block = range.ConsumeBlock();
CSSValue* scroller = nullptr;
CSSIdentifierValue* axis = nullptr;
while (!scroller || !axis) {
if (block.AtEnd()) {
break;
}
if (!scroller) {
if ((scroller = ConsumeIdent<CSSValueID::kRoot, CSSValueID::kNearest,
CSSValueID::kSelf>(block))) {
continue;
}
}
if (!axis) {
if ((axis = ConsumeIdent<CSSValueID::kBlock, CSSValueID::kInline,
CSSValueID::kVertical, CSSValueID::kHorizontal>(
block))) {
continue;
}
}
return nullptr;
}
if (!block.AtEnd()) {
return nullptr;
}
// Nullify default values.
// https://drafts.csswg.org/scroll-animations-1/#valdef-scroll-nearest
if (scroller && IsIdent(*scroller, CSSValueID::kNearest)) {
scroller = nullptr;
}
// https://drafts.csswg.org/scroll-animations-1/#valdef-scroll-block
if (axis && IsIdent(*axis, CSSValueID::kBlock)) {
axis = nullptr;
}
return MakeGarbageCollected<cssvalue::CSSScrollValue>(scroller, axis);
}
CSSValue* ConsumeViewFunction(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().FunctionId() != CSSValueID::kView) {
return nullptr;
}
CSSParserTokenRange block = range.ConsumeBlock();
CSSIdentifierValue* axis = nullptr;
CSSValue* inset = nullptr;
while (!axis || !inset) {
if (block.AtEnd()) {
break;
}
if (!axis) {
if ((axis = ConsumeIdent<CSSValueID::kBlock, CSSValueID::kInline,
CSSValueID::kVertical, CSSValueID::kHorizontal>(
block))) {
continue;
}
}
if (!inset) {
if ((inset = ConsumeSingleTimelineInset(block, context))) {
continue;
}
}
return nullptr;
}
if (!block.AtEnd()) {
return nullptr;
}
// Nullify default values.
// https://drafts.csswg.org/scroll-animations-1/#valdef-scroll-block
if (axis && IsIdent(*axis, CSSValueID::kBlock)) {
axis = nullptr;
}
if (inset) {
auto* inset_pair = DynamicTo<CSSValuePair>(inset);
if (IsIdent(inset_pair->First(), CSSValueID::kAuto) &&
IsIdent(inset_pair->Second(), CSSValueID::kAuto)) {
inset = nullptr;
}
}
return MakeGarbageCollected<cssvalue::CSSViewValue>(axis, inset);
}
CSSValue* ConsumeAnimationTimeline(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (auto* value = ConsumeIdent<CSSValueID::kNone, CSSValueID::kAuto>(range)) {
return value;
}
if (auto* value = ConsumeCustomIdent(range, context)) {
return value;
}
if (auto* value = ConsumeViewFunction(range, context)) {
return value;
}
return ConsumeScrollFunction(range, context);
}
CSSValue* ConsumeAnimationTimingFunction(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValueID id = range.Peek().Id();
if (id == CSSValueID::kEase || id == CSSValueID::kLinear ||
id == CSSValueID::kEaseIn || id == CSSValueID::kEaseOut ||
id == CSSValueID::kEaseInOut || id == CSSValueID::kStepStart ||
id == CSSValueID::kStepEnd) {
return ConsumeIdent(range);
}
CSSValueID function = range.Peek().FunctionId();
if (function == CSSValueID::kLinear &&
RuntimeEnabledFeatures::CSSLinearTimingFunctionEnabled()) {
return ConsumeLinear(range, context);
}
if (function == CSSValueID::kSteps) {
return ConsumeSteps(range, context);
}
if (function == CSSValueID::kCubicBezier) {
return ConsumeCubicBezier(range, context);
}
return nullptr;
}
CSSValue* ConsumeAnimationDuration(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (RuntimeEnabledFeatures::ScrollTimelineEnabled()) {
if (CSSValue* ident = ConsumeIdent<CSSValueID::kAuto>(range)) {
return ident;
}
}
return ConsumeTime(range, context,
CSSPrimitiveValue::ValueRange::kNonNegative);
}
CSSValue* ConsumeTimelineRangeName(CSSParserTokenRange& range) {
return ConsumeIdent<CSSValueID::kContain, CSSValueID::kCover,
CSSValueID::kEntry, CSSValueID::kEntryCrossing,
CSSValueID::kExit, CSSValueID::kExitCrossing>(range);
}
CSSValue* ConsumeTimelineRangeNameAndPercent(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
CSSValue* range_name = ConsumeTimelineRangeName(range);
if (!range_name) {
return nullptr;
}
list->Append(*range_name);
CSSValue* percentage =
ConsumePercent(range, context, CSSPrimitiveValue::ValueRange::kAll);
if (!percentage) {
return nullptr;
}
list->Append(*percentage);
return list;
}
CSSValue* ConsumeAnimationDelay(CSSParserTokenRange& range,
const CSSParserContext& context) {
DCHECK(RuntimeEnabledFeatures::CSSAnimationDelayStartEndEnabled());
return ConsumeTime(range, context, CSSPrimitiveValue::ValueRange::kAll);
}
CSSValue* ConsumeAnimationRange(CSSParserTokenRange& range,
const CSSParserContext& context,
double default_offset_percent) {
DCHECK(RuntimeEnabledFeatures::ScrollTimelineEnabled());
if (CSSValue* ident = ConsumeIdent<CSSValueID::kNormal>(range)) {
return ident;
}
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
CSSValue* range_name = ConsumeTimelineRangeName(range);
if (range_name) {
list->Append(*range_name);
}
CSSPrimitiveValue* percentage = ConsumeLengthOrPercent(
range, context, CSSPrimitiveValue::ValueRange::kAll);
if (percentage &&
!(range_name && percentage->IsPercentage() &&
percentage->GetValue<double>() == default_offset_percent)) {
list->Append(*percentage);
} else if (!range_name) {
return nullptr;
}
return list;
}
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 (unsigned i = 0; i < longhand_count; ++i) {
longhands[i] = CSSValueList::CreateCommaSeparated();
}
do {
bool parsed_longhand[kMaxNumAnimationLonghands] = {false};
do {
bool found_property = false;
for (unsigned 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);
for (unsigned i = 0; i < longhand_count; ++i) {
if (!parsed_longhand[i]) {
longhands[i]->Append(
*To<Longhand>(shorthand.properties()[i])->InitialValue());
}
parsed_longhand[i] = false;
}
} while (ConsumeCommaIncludingWhitespace(range));
return true;
}
CSSValue* ConsumeSingleTimelineAttachment(CSSParserTokenRange& range) {
return ConsumeIdent<CSSValueID::kLocal, CSSValueID::kDefer,
CSSValueID::kAncestor>(range);
}
CSSValue* ConsumeSingleTimelineAxis(CSSParserTokenRange& range) {
return ConsumeIdent<CSSValueID::kBlock, CSSValueID::kInline,
CSSValueID::kVertical, CSSValueID::kHorizontal>(range);
}
CSSValue* ConsumeSingleTimelineName(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (CSSValue* value = ConsumeIdent<CSSValueID::kNone>(range)) {
return value;
}
return ConsumeCustomIdent(range, context);
}
namespace {
CSSValue* ConsumeSingleTimelineInsetSide(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (CSSValue* ident = ConsumeIdent<CSSValueID::kAuto>(range)) {
return ident;
}
return ConsumeLengthOrPercent(range, context,
CSSPrimitiveValue::ValueRange::kAll);
}
} // namespace
CSSValue* ConsumeSingleTimelineInset(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValue* start = ConsumeSingleTimelineInsetSide(range, context);
if (!start) {
return nullptr;
}
CSSValue* end = ConsumeSingleTimelineInsetSide(range, context);
if (!end) {
end = start;
}
return MakeGarbageCollected<CSSValuePair>(start, end,
CSSValuePair::kDropIdenticalValues);
}
void AddBackgroundValue(CSSValue*& list, CSSValue* value) {
if (list) {
if (!list->IsBaseValueList()) {
CSSValue* first_value = list;
list = CSSValueList::CreateCommaSeparated();
To<CSSValueList>(list)->Append(*first_value);
}
To<CSSValueList>(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 ConsumeIdent<CSSValueID::kScroll, CSSValueID::kFixed,
CSSValueID::kLocal>(range);
}
CSSValue* ConsumeBackgroundBlendMode(CSSParserTokenRange& range) {
CSSValueID id = range.Peek().Id();
if (id == CSSValueID::kNormal || id == CSSValueID::kOverlay ||
(id >= CSSValueID::kMultiply && id <= CSSValueID::kLuminosity)) {
return ConsumeIdent(range);
}
return nullptr;
}
CSSValue* ConsumeBackgroundBox(CSSParserTokenRange& range) {
return ConsumeIdent<CSSValueID::kBorderBox, CSSValueID::kPaddingBox,
CSSValueID::kContentBox>(range);
}
CSSValue* ConsumeBackgroundComposite(CSSParserTokenRange& range) {
return ConsumeIdentRange(range, CSSValueID::kClear, CSSValueID::kPlusLighter);
}
CSSPrimitiveValue* ConsumeLengthOrPercentCountNegative(
CSSParserTokenRange& range,
const CSSParserContext& context,
absl::optional<WebFeature> negative_size) {
CSSPrimitiveValue* result = ConsumeLengthOrPercent(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative,
UnitlessQuirk::kForbid);
if (!result && negative_size) {
context.Count(*negative_size);
}
return result;
}
CSSValue* ConsumeBackgroundSize(CSSParserTokenRange& range,
const CSSParserContext& context,
absl::optional<WebFeature> negative_size,
ParsingStyle parsing_style) {
if (IdentMatches<CSSValueID::kContain, CSSValueID::kCover>(
range.Peek().Id())) {
return ConsumeIdent(range);
}
CSSValue* horizontal = ConsumeIdent<CSSValueID::kAuto>(range);
if (!horizontal) {
horizontal =
ConsumeLengthOrPercentCountNegative(range, context, negative_size);
}
if (!horizontal) {
return nullptr;
}
CSSValue* vertical = nullptr;
if (!range.AtEnd()) {
if (range.Peek().Id() == CSSValueID::kAuto) { // `auto' is the default
range.ConsumeIncludingWhitespace();
} else {
vertical =
ConsumeLengthOrPercentCountNegative(range, context, negative_size);
}
} 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 MakeGarbageCollected<CSSValuePair>(horizontal, vertical,
CSSValuePair::kKeepIdenticalValues);
}
static void SetAllowsNegativePercentageReference(CSSValue* value) {
if (auto* math_value = DynamicTo<CSSMathFunctionValue>(value)) {
math_value->SetAllowsNegativePercentageReference();
}
}
bool ConsumeBackgroundPosition(CSSParserTokenRange& range,
const CSSParserContext& context,
UnitlessQuirk unitless,
CSSValue*& result_x,
CSSValue*& result_y) {
do {
CSSValue* position_x = nullptr;
CSSValue* position_y = nullptr;
if (!ConsumePosition(range, context, unitless,
WebFeature::kThreeValuedPositionBackground, position_x,
position_y)) {
return false;
}
// TODO(crbug.com/825895): So far, 'background-position' is the only
// property that allows resolving a percentage against a negative value. If
// we have more of such properties, we should instead pass an additional
// argument to ask the parser to set this flag.
SetAllowsNegativePercentageReference(position_x);
SetAllowsNegativePercentageReference(position_y);
AddBackgroundValue(result_x, position_x);
AddBackgroundValue(result_y, position_y);
} while (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 = ConsumeIdentRange(range, CSSValueID::kBorder,
CSSValueID::kPaddingBox)) {
return value;
}
if (allow_text_value == AllowTextValue::kAllow &&
range.Peek().Id() == CSSValueID::kText) {
return ConsumeIdent(range);
}
return nullptr;
}
CSSValue* ParseBackgroundBox(CSSParserTokenRange& range,
const CSSParserLocalContext& local_context,
AllowTextValue alias_allow_text_value) {
// This is legacy behavior that does not match spec, see crbug.com/604023
if (local_context.UseAliasParsing()) {
return ConsumeCommaSeparatedList(ConsumePrefixedBackgroundBox, range,
alias_allow_text_value);
}
return ConsumeCommaSeparatedList(ConsumeBackgroundBox, range);
}
CSSValue* ParseBackgroundOrMaskSize(CSSParserTokenRange& range,
const CSSParserContext& context,
const CSSParserLocalContext& local_context,
absl::optional<WebFeature> negative_size) {
return ConsumeCommaSeparatedList(
ConsumeBackgroundSize, range, context, negative_size,
local_context.UseAliasParsing() ? ParsingStyle::kLegacy
: ParsingStyle::kNotLegacy);
}
namespace {
CSSValue* ConsumeBackgroundComponent(CSSPropertyID resolved_property,
CSSParserTokenRange& range,
const CSSParserContext& context) {
switch (resolved_property) {
case CSSPropertyID::kBackgroundClip:
return ConsumeBackgroundBox(range);
case CSSPropertyID::kBackgroundAttachment:
return ConsumeBackgroundAttachment(range);
case CSSPropertyID::kBackgroundOrigin:
return ConsumeBackgroundBox(range);
case CSSPropertyID::kBackgroundImage:
case CSSPropertyID::kWebkitMaskImage:
return ConsumeImageOrNone(range, context);
case CSSPropertyID::kBackgroundPositionX:
case CSSPropertyID::kWebkitMaskPositionX:
return ConsumePositionLonghand<CSSValueID::kLeft, CSSValueID::kRight>(
range, context);
case CSSPropertyID::kBackgroundPositionY:
case CSSPropertyID::kWebkitMaskPositionY:
return ConsumePositionLonghand<CSSValueID::kTop, CSSValueID::kBottom>(
range, context);
case CSSPropertyID::kBackgroundSize:
return ConsumeBackgroundSize(range, context,
WebFeature::kNegativeBackgroundSize,
ParsingStyle::kNotLegacy);
case CSSPropertyID::kWebkitMaskSize:
return ConsumeBackgroundSize(range, context,
WebFeature::kNegativeMaskSize,
ParsingStyle::kNotLegacy);
case CSSPropertyID::kBackgroundColor:
return ConsumeColor(range, context);
case CSSPropertyID::kWebkitMaskClip:
return ConsumePrefixedBackgroundBox(range, AllowTextValue::kAllow);
case CSSPropertyID::kWebkitMaskOrigin:
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, 64>& properties) {
CSSPropertyID shorthand_id = local_context.CurrentShorthand();
DCHECK(shorthand_id == CSSPropertyID::kBackground ||
shorthand_id == CSSPropertyID::kWebkitMask);
const StylePropertyShorthand& shorthand =
shorthand_id == CSSPropertyID::kBackground ? 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;
bool bg_position_parsed_in_current_layer = false;
for (unsigned 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(CSSPropertyID::kBackgroundRepeatX) ||
property.IDEquals(CSSPropertyID::kWebkitMaskRepeatX)) {
ConsumeRepeatStyleComponent(range, value, value_y, implicit);
} else if (property.IDEquals(CSSPropertyID::kBackgroundPositionX) ||
property.IDEquals(CSSPropertyID::kWebkitMaskPositionX)) {
if (!ConsumePosition(range, context, UnitlessQuirk::kForbid,
WebFeature::kThreeValuedPositionBackground,
value, value_y)) {
continue;
}
if (value) {
bg_position_parsed_in_current_layer = true;
}
} else if (property.IDEquals(CSSPropertyID::kBackgroundSize) ||
property.IDEquals(CSSPropertyID::kWebkitMaskSize)) {
if (!ConsumeSlashIncludingWhitespace(range)) {
continue;
}
value = ConsumeBackgroundSize(
range, context,
property.IDEquals(CSSPropertyID::kBackgroundSize)
? WebFeature::kNegativeBackgroundSize
: WebFeature::kNegativeMaskSize,
ParsingStyle::kNotLegacy);
if (!value || !bg_position_parsed_in_current_layer) {
return false;
}
} else if (property.IDEquals(CSSPropertyID::kBackgroundPositionY) ||
property.IDEquals(CSSPropertyID::kBackgroundRepeatY) ||
property.IDEquals(CSSPropertyID::kWebkitMaskPositionY) ||
property.IDEquals(CSSPropertyID::kWebkitMaskRepeatY)) {
continue;
} else {
value =
ConsumeBackgroundComponent(property.PropertyID(), range, context);
}
if (value) {
if (property.IDEquals(CSSPropertyID::kBackgroundOrigin) ||
property.IDEquals(CSSPropertyID::kWebkitMaskOrigin)) {
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 (unsigned i = 0; i < longhand_count; ++i) {
const CSSProperty& property = *shorthand.properties()[i];
if (property.IDEquals(CSSPropertyID::kBackgroundColor) &&
!range.AtEnd()) {
if (parsed_longhand[i]) {
return false; // Colors are only allowed in the last layer.
}
continue;
}
if ((property.IDEquals(CSSPropertyID::kBackgroundClip) ||
property.IDEquals(CSSPropertyID::kWebkitMaskClip)) &&
!parsed_longhand[i] && origin_value) {
AddBackgroundValue(longhands[i], origin_value);
continue;
}
if (!parsed_longhand[i]) {
AddBackgroundValue(longhands[i], CSSInitialValue::Create());
}
}
} while (ConsumeCommaIncludingWhitespace(range));
if (!range.AtEnd()) {
return false;
}
for (unsigned i = 0; i < longhand_count; ++i) {
const CSSProperty& property = *shorthand.properties()[i];
if (property.IDEquals(CSSPropertyID::kBackgroundSize) && longhands[i] &&
context.UseLegacyBackgroundSizeShorthandBehavior()) {
continue;
}
AddProperty(property.PropertyID(), shorthand.id(), *longhands[i], important,
implicit ? IsImplicitProperty::kImplicit
: IsImplicitProperty::kNotImplicit,
properties);
}
return true;
}
bool ConsumeRepeatStyleComponent(CSSParserTokenRange& range,
CSSValue*& value1,
CSSValue*& value2,
bool& implicit) {
if (ConsumeIdent<CSSValueID::kRepeatX>(range)) {
value1 = CSSIdentifierValue::Create(CSSValueID::kRepeat);
value2 = CSSIdentifierValue::Create(CSSValueID::kNoRepeat);
implicit = true;
return true;
}
if (ConsumeIdent<CSSValueID::kRepeatY>(range)) {
value1 = CSSIdentifierValue::Create(CSSValueID::kNoRepeat);
value2 = CSSIdentifierValue::Create(CSSValueID::kRepeat);
implicit = true;
return true;
}
value1 = ConsumeIdent<CSSValueID::kRepeat, CSSValueID::kNoRepeat,
CSSValueID::kRound, CSSValueID::kSpace>(range);
if (!value1) {
return false;
}
value2 = ConsumeIdent<CSSValueID::kRepeat, CSSValueID::kNoRepeat,
CSSValueID::kRound, CSSValueID::kSpace>(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 (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 = ConsumeImageOrNone(range, context);
if (source) {
continue;
}
}
if (!repeat) {
repeat = ConsumeBorderImageRepeat(range);
if (repeat) {
continue;
}
}
if (!slice) {
slice = ConsumeBorderImageSlice(range, context, default_fill);
if (slice) {
DCHECK(!width);
DCHECK(!outset);
if (ConsumeSlashIncludingWhitespace(range)) {
width = ConsumeBorderImageWidth(range, context);
if (ConsumeSlashIncludingWhitespace(range)) {
outset = ConsumeBorderImageOutset(range, context);
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 MakeGarbageCollected<CSSValuePair>(horizontal, vertical,
CSSValuePair::kDropIdenticalValues);
}
CSSValue* ConsumeBorderImageSlice(CSSParserTokenRange& range,
const CSSParserContext& context,
DefaultFill default_fill) {
bool fill = ConsumeIdent<CSSValueID::kFill>(range);
CSSValue* slices[4] = {nullptr};
for (size_t index = 0; index < 4; ++index) {
CSSPrimitiveValue* value = ConsumePercent(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (!value) {
value = ConsumeNumber(range, context,
CSSPrimitiveValue::ValueRange::kNonNegative);
}
if (!value) {
break;
}
slices[index] = value;
}
if (!slices[0]) {
return nullptr;
}
if (ConsumeIdent<CSSValueID::kFill>(range)) {
if (fill) {
return nullptr;
}
fill = true;
}
Complete4Sides(slices);
if (default_fill == DefaultFill::kFill) {
fill = true;
}
return MakeGarbageCollected<cssvalue::CSSBorderImageSliceValue>(
MakeGarbageCollected<CSSQuadValue>(slices[0], slices[1], slices[2],
slices[3],
CSSQuadValue::kSerializeAsQuad),
fill);
}
CSSValue* ConsumeBorderImageWidth(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValue* widths[4] = {nullptr};
CSSValue* value = nullptr;
for (size_t index = 0; index < 4; ++index) {
value = ConsumeNumber(range, context,
CSSPrimitiveValue::ValueRange::kNonNegative);
if (!value) {
CSSParserContext::ParserModeOverridingScope scope(context,
kHTMLStandardMode);
value = ConsumeLengthOrPercent(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative,
UnitlessQuirk::kForbid);
}
if (!value) {
value = ConsumeIdent<CSSValueID::kAuto>(range);
}
if (!value) {
break;
}
widths[index] = value;
}
if (!widths[0]) {
return nullptr;
}
Complete4Sides(widths);
return MakeGarbageCollected<CSSQuadValue>(widths[0], widths[1], widths[2],
widths[3],
CSSQuadValue::kSerializeAsQuad);
}
CSSValue* ConsumeBorderImageOutset(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValue* outsets[4] = {nullptr};
CSSValue* value = nullptr;
for (size_t index = 0; index < 4; ++index) {
value = ConsumeNumber(range, context,
CSSPrimitiveValue::ValueRange::kNonNegative);
if (!value) {
CSSParserContext::ParserModeOverridingScope scope(context,
kHTMLStandardMode);
value = ConsumeLength(range, context,
CSSPrimitiveValue::ValueRange::kNonNegative);
}
if (!value) {
break;
}
outsets[index] = value;
}
if (!outsets[0]) {
return nullptr;
}
Complete4Sides(outsets);
return MakeGarbageCollected<CSSQuadValue>(outsets[0], outsets[1], outsets[2],
outsets[3],
CSSQuadValue::kSerializeAsQuad);
}
CSSValue* ParseBorderRadiusCorner(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValue* parsed_value1 = ConsumeLengthOrPercent(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (!parsed_value1) {
return nullptr;
}
CSSValue* parsed_value2 = ConsumeLengthOrPercent(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (!parsed_value2) {
parsed_value2 = parsed_value1;
}
return MakeGarbageCollected<CSSValuePair>(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 == CSSPropertyID::kInvalid ||
shorthand == CSSPropertyID::kBorderWidth);
UnitlessQuirk unitless =
allow_quirky_lengths ? UnitlessQuirk::kAllow : UnitlessQuirk::kForbid;
return ConsumeBorderWidth(range, context, unitless);
}
CSSValue* ConsumeShadow(CSSParserTokenRange& range,
const CSSParserContext& context,
AllowInsetAndSpread inset_and_spread) {
if (range.Peek().Id() == CSSValueID::kNone) {
return ConsumeIdent(range);
}
return ConsumeCommaSeparatedList(ParseSingleShadow, range, context,
inset_and_spread);
}
CSSShadowValue* ParseSingleShadow(CSSParserTokenRange& range,
const CSSParserContext& context,
AllowInsetAndSpread inset_and_spread) {
CSSIdentifierValue* style = nullptr;
CSSValue* color = nullptr;
if (range.AtEnd()) {
return nullptr;
}
color = ConsumeColor(range, context);
if (range.Peek().Id() == CSSValueID::kInset) {
if (inset_and_spread != AllowInsetAndSpread::kAllow) {
return nullptr;
}
style = ConsumeIdent(range);
if (!color) {
color = ConsumeColor(range, context);
}
}
CSSPrimitiveValue* horizontal_offset =
ConsumeLength(range, context, CSSPrimitiveValue::ValueRange::kAll);
if (!horizontal_offset) {
return nullptr;
}
CSSPrimitiveValue* vertical_offset =
ConsumeLength(range, context, CSSPrimitiveValue::ValueRange::kAll);
if (!vertical_offset) {
return nullptr;
}
CSSPrimitiveValue* blur_radius = ConsumeLength(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative);
CSSPrimitiveValue* spread_distance = nullptr;
if (blur_radius) {
if (inset_and_spread == AllowInsetAndSpread::kAllow) {
spread_distance =
ConsumeLength(range, context, CSSPrimitiveValue::ValueRange::kAll);
}
}
if (!range.AtEnd()) {
if (!color) {
color = ConsumeColor(range, context);
}
if (range.Peek().Id() == CSSValueID::kInset) {
if (inset_and_spread != AllowInsetAndSpread::kAllow || style) {
return nullptr;
}
style = ConsumeIdent(range);
if (!color) {
color = ConsumeColor(range, context);
}
}
}
return MakeGarbageCollected<CSSShadowValue>(horizontal_offset,
vertical_offset, blur_radius,
spread_distance, style, color);
}
CSSValue* ConsumeColumnCount(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kAuto) {
return ConsumeIdent(range);
}
return ConsumePositiveInteger(range, context);
}
CSSValue* ConsumeColumnWidth(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kAuto) {
return ConsumeIdent(range);
}
// Always parse lengths in strict mode here, since it would be ambiguous
// otherwise when used in the 'columns' shorthand property.
CSSParserContext::ParserModeOverridingScope scope(context, kHTMLStandardMode);
CSSPrimitiveValue* column_width = ConsumeLength(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (!column_width) {
return nullptr;
}
return column_width;
}
bool ConsumeColumnWidthOrCount(CSSParserTokenRange& range,
const CSSParserContext& context,
CSSValue*& column_width,
CSSValue*& column_count) {
if (range.Peek().Id() == CSSValueID::kAuto) {
ConsumeIdent(range);
return true;
}
if (!column_width) {
column_width = ConsumeColumnWidth(range, context);
if (column_width) {
return true;
}
}
if (!column_count) {
column_count = ConsumeColumnCount(range, context);
}
return column_count;
}
CSSValue* ConsumeGapLength(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kNormal) {
return ConsumeIdent(range);
}
return ConsumeLengthOrPercent(range, context,
CSSPrimitiveValue::ValueRange::kNonNegative);
}
CSSValue* ConsumeCounter(CSSParserTokenRange& range,
const CSSParserContext& context,
int default_value) {
if (range.Peek().Id() == CSSValueID::kNone) {
return ConsumeIdent(range);
}
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
do {
CSSCustomIdentValue* counter_name = ConsumeCustomIdent(range, context);
if (!counter_name) {
return nullptr;
}
int value = default_value;
if (CSSPrimitiveValue* counter_value = ConsumeInteger(range, context)) {
value = ClampTo<int>(counter_value->GetDoubleValue());
}
list->Append(*MakeGarbageCollected<CSSValuePair>(
counter_name,
CSSNumericLiteralValue::Create(value,
CSSPrimitiveValue::UnitType::kInteger),
CSSValuePair::kDropIdenticalValues));
} while (!range.AtEnd());
return list;
}
CSSValue* ConsumeMathDepth(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kAutoAdd) {
return ConsumeIdent(range);
}
if (CSSPrimitiveValue* integer_value = ConsumeInteger(range, context)) {
return integer_value;
}
CSSValueID function_id = range.Peek().FunctionId();
if (function_id == CSSValueID::kAdd) {
CSSParserTokenRange add_args = ConsumeFunction(range);
CSSValue* value = ConsumeInteger(add_args, context);
if (value && add_args.AtEnd()) {
auto* add_value = MakeGarbageCollected<CSSFunctionValue>(function_id);
add_value->Append(*value);
return add_value;
}
}
return nullptr;
}
CSSValue* ConsumeFontSize(CSSParserTokenRange& range,
const CSSParserContext& context,
UnitlessQuirk unitless) {
if (range.Peek().Id() == CSSValueID::kWebkitXxxLarge) {
context.Count(WebFeature::kFontSizeWebkitXxxLarge);
}
if ((range.Peek().Id() >= CSSValueID::kXxSmall &&
range.Peek().Id() <= CSSValueID::kWebkitXxxLarge) ||
(RuntimeEnabledFeatures::CSSMathDepthEnabled() &&
range.Peek().Id() == CSSValueID::kMath)) {
return ConsumeIdent(range);
}
return ConsumeLengthOrPercent(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative, unitless);
}
CSSValue* ConsumeLineHeight(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kNormal) {
return ConsumeIdent(range);
}
CSSPrimitiveValue* line_height = ConsumeNumber(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (line_height) {
return line_height;
}
return ConsumeLengthOrPercent(range, context,
CSSPrimitiveValue::ValueRange::kNonNegative);
}
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 (ConsumeCommaIncludingWhitespace(range));
return list;
}
CSSValue* ConsumeGenericFamily(CSSParserTokenRange& range) {
if (RuntimeEnabledFeatures::CSSFontFamilyMathEnabled() &&
range.Peek().Id() == CSSValueID::kMath) {
return ConsumeIdent(range);
}
return ConsumeIdentRange(range, CSSValueID::kSerif, CSSValueID::kWebkitBody);
}
CSSValue* ConsumeFamilyName(CSSParserTokenRange& range) {
if (range.Peek().GetType() == kStringToken) {
return CSSFontFamilyValue::Create(
range.ConsumeIncludingWhitespace().Value().ToAtomicString());
}
if (range.Peek().GetType() != kIdentToken) {
return nullptr;
}
String family_name = ConcatenateFamilyName(range);
if (family_name.IsNull()) {
return nullptr;
}
return CSSFontFamilyValue::Create(AtomicString(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.empty()) {
builder.Append(' ');
added_space = true;
}
builder.Append(range.ConsumeIncludingWhitespace().Value());
}
if (!added_space && (IsCSSWideKeyword(first_token.Value()) ||
IsDefaultKeyword(first_token.Value()))) {
return String();
}
return builder.ReleaseString();
}
CSSValueList* CombineToRangeList(const CSSPrimitiveValue* range_start,
const CSSPrimitiveValue* range_end) {
DCHECK(range_start);
DCHECK(range_end);
// Reversed ranges are valid, let them pass through here and swap them in
// FontFace to keep serialisation of the value as specified.
// https://drafts.csswg.org/css-fonts/#font-prop-desc
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 CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kNormal ||
range.Peek().Id() == CSSValueID::kItalic) {
return ConsumeIdent(range);
}
if (RuntimeEnabledFeatures::CSSFontFaceAutoVariableRangeEnabled() &&
range.Peek().Id() == CSSValueID::kAuto &&
context.Mode() == kCSSFontFaceRuleMode) {
return ConsumeIdent(range);
}
if (range.Peek().Id() != CSSValueID::kOblique) {
return nullptr;
}
CSSIdentifierValue* oblique_identifier =
ConsumeIdent<CSSValueID::kOblique>(range);
CSSPrimitiveValue* start_angle = ConsumeAngle(
range, context, absl::nullopt, MinObliqueValue(), MaxObliqueValue());
if (!start_angle) {
return oblique_identifier;
}
if (!IsAngleWithinLimits(start_angle)) {
return nullptr;
}
if (context.Mode() != kCSSFontFaceRuleMode || range.AtEnd()) {
CSSValueList* value_list = CSSValueList::CreateSpaceSeparated();
value_list->Append(*start_angle);
return MakeGarbageCollected<cssvalue::CSSFontStyleRangeValue>(
*oblique_identifier, *value_list);
}
CSSPrimitiveValue* end_angle = ConsumeAngle(
range, context, absl::nullopt, MinObliqueValue(), MaxObliqueValue());
if (!end_angle || !IsAngleWithinLimits(end_angle)) {
return nullptr;
}
CSSValueList* range_list = CombineToRangeList(start_angle, end_angle);
if (!range_list) {
return nullptr;
}
return MakeGarbageCollected<cssvalue::CSSFontStyleRangeValue>(
*oblique_identifier, *range_list);
}
CSSIdentifierValue* ConsumeFontStretchKeywordOnly(
CSSParserTokenRange& range,
const CSSParserContext& context) {
const CSSParserToken& token = range.Peek();
if (token.Id() == CSSValueID::kNormal ||
(token.Id() >= CSSValueID::kUltraCondensed &&
token.Id() <= CSSValueID::kUltraExpanded)) {
return ConsumeIdent(range);
}
if (RuntimeEnabledFeatures::CSSFontFaceAutoVariableRangeEnabled() &&
token.Id() == CSSValueID::kAuto &&
context.Mode() == kCSSFontFaceRuleMode) {
return ConsumeIdent(range);
}
return nullptr;
}
CSSValue* ConsumeFontStretch(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSIdentifierValue* parsed_keyword =
ConsumeFontStretchKeywordOnly(range, context);
if (parsed_keyword) {
return parsed_keyword;
}
CSSPrimitiveValue* start_percent = ConsumePercent(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (!start_percent) {
return nullptr;
}
// In a non-font-face context, more than one percentage is not allowed.
if (context.Mode() != kCSSFontFaceRuleMode || range.AtEnd()) {
return start_percent;
}
CSSPrimitiveValue* end_percent = ConsumePercent(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (!end_percent) {
return nullptr;
}
return CombineToRangeList(start_percent, end_percent);
}
CSSValue* ConsumeFontWeight(CSSParserTokenRange& range,
const CSSParserContext& context) {
const CSSParserToken& token = range.Peek();
if (context.Mode() != kCSSFontFaceRuleMode) {
if (token.Id() >= CSSValueID::kNormal &&
token.Id() <= CSSValueID::kLighter) {
return ConsumeIdent(range);
}
} else {
if ((token.Id() == CSSValueID::kNormal ||
token.Id() == CSSValueID::kBold) ||
(RuntimeEnabledFeatures::CSSFontFaceAutoVariableRangeEnabled() &&
token.Id() == CSSValueID::kAuto)) {
return 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 = ConsumeNumber(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative);
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 (context.Mode() != kCSSFontFaceRuleMode || range.AtEnd()) {
return start_weight;
}
CSSPrimitiveValue* end_weight = ConsumeNumber(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (!end_weight || end_weight->GetFloatValue() < 1 ||
end_weight->GetFloatValue() > 1000) {
return nullptr;
}
return CombineToRangeList(start_weight, end_weight);
}
CSSValue* ConsumeFontFeatureSettings(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kNormal) {
return ConsumeIdent(range);
}
CSSValueList* settings = CSSValueList::CreateCommaSeparated();
do {
CSSFontFeatureValue* font_feature_value =
ConsumeFontFeatureTag(range, context);
if (!font_feature_value) {
return nullptr;
}
settings->Append(*font_feature_value);
} while (ConsumeCommaIncludingWhitespace(range));
return settings;
}
CSSFontFeatureValue* ConsumeFontFeatureTag(CSSParserTokenRange& range,
const CSSParserContext& context) {
// 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 = ConsumeInteger(range, context, 0)) {
tag_value = ClampTo<int>(value->GetDoubleValue());
} else if (range.Peek().Id() == CSSValueID::kOn ||
range.Peek().Id() == CSSValueID::kOff) {
tag_value = range.ConsumeIncludingWhitespace().Id() == CSSValueID::kOn;
}
return MakeGarbageCollected<CSSFontFeatureValue>(tag, tag_value);
}
CSSIdentifierValue* ConsumeFontVariantCSS21(CSSParserTokenRange& range) {
return ConsumeIdent<CSSValueID::kNormal, CSSValueID::kSmallCaps>(range);
}
CSSIdentifierValue* ConsumeFontTechIdent(CSSParserTokenRange& range) {
return ConsumeIdent<CSSValueID::kFeaturesOpentype, CSSValueID::kFeaturesAat,
CSSValueID::kFeaturesGraphite, CSSValueID::kColorCOLRv0,
CSSValueID::kColorCOLRv1, CSSValueID::kColorSVG,
CSSValueID::kColorSbix, CSSValueID::kColorCBDT,
CSSValueID::kVariations, CSSValueID::kPalettes,
CSSValueID::kIncremental>(range);
}
CSSIdentifierValue* ConsumeFontFormatIdent(CSSParserTokenRange& range) {
return ConsumeIdent<CSSValueID::kCollection, CSSValueID::kEmbeddedOpentype,
CSSValueID::kOpentype, CSSValueID::kTruetype,
CSSValueID::kSVG, CSSValueID::kWoff, CSSValueID::kWoff2>(
range);
}
CSSValueID FontFormatToId(String font_format) {
CSSValueID converted_id = CssValueKeywordID(font_format);
if (converted_id == CSSValueID::kCollection ||
converted_id == CSSValueID::kEmbeddedOpentype ||
converted_id == CSSValueID::kOpentype ||
converted_id == CSSValueID::kTruetype ||
converted_id == CSSValueID::kSVG || converted_id == CSSValueID::kWoff ||
converted_id == CSSValueID::kWoff2) {
return converted_id;
}
return CSSValueID::kInvalid;
}
bool IsSupportedKeywordTech(CSSValueID keyword) {
switch (keyword) {
case CSSValueID::kFeaturesOpentype:
case CSSValueID::kFeaturesAat:
case CSSValueID::kColorCOLRv0:
case CSSValueID::kColorCOLRv1:
case CSSValueID::kColorSbix:
case CSSValueID::kColorCBDT:
case CSSValueID::kVariations:
case CSSValueID::kPalettes:
return true;
case CSSValueID::kFeaturesGraphite:
case CSSValueID::kColorSVG:
case CSSValueID::kIncremental:
return false;
default:
return false;
}
NOTREACHED();
return false;
}
bool IsSupportedKeywordFormat(CSSValueID keyword) {
switch (keyword) {
case CSSValueID::kCollection:
case CSSValueID::kOpentype:
case CSSValueID::kTruetype:
case CSSValueID::kWoff:
case CSSValueID::kWoff2:
return true;
case CSSValueID::kEmbeddedOpentype:
case CSSValueID::kSVG:
return false;
default:
return false;
}
}
Vector<String> ParseGridTemplateAreasColumnNames(const String& grid_row_names) {
DCHECK(!grid_row_names.empty());
// Using StringImpl to avoid checks and indirection in every call to
// String::operator[].
StringImpl& text = *grid_row_names.Impl();
StringBuilder area_name;
Vector<String> column_names;
for (unsigned i = 0; i < text.length(); ++i) {
if (IsCSSSpace(text[i])) {
if (!area_name.empty()) {
column_names.push_back(area_name.ReleaseString());
}
continue;
}
if (text[i] == '.') {
if (area_name == ".") {
continue;
}
if (!area_name.empty()) {
column_names.push_back(area_name.ReleaseString());
}
} else {
if (!IsNameCodePoint(text[i])) {
return Vector<String>();
}
if (area_name == ".") {
column_names.push_back(area_name.ReleaseString());
}
}
area_name.Append(text[i]);
}
if (!area_name.empty()) {
column_names.push_back(area_name.ReleaseString());
}
return column_names;
}
CSSValue* ConsumeGridBreadth(CSSParserTokenRange& range,
const CSSParserContext& context) {
const CSSParserToken& token = range.Peek();
if (IdentMatches<CSSValueID::kAuto, CSSValueID::kMinContent,
CSSValueID::kMaxContent>(token.Id())) {
return ConsumeIdent(range);
}
if (token.GetType() == kDimensionToken &&
token.GetUnitType() == CSSPrimitiveValue::UnitType::kFraction) {
if (token.NumericValue() < 0) {
return nullptr;
}
return CSSNumericLiteralValue::Create(
range.ConsumeIncludingWhitespace().NumericValue(),
CSSPrimitiveValue::UnitType::kFraction);
}
return ConsumeLengthOrPercent(range, context,
CSSPrimitiveValue::ValueRange::kNonNegative,
UnitlessQuirk::kForbid);
}
CSSValue* ConsumeFitContent(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSParserTokenRange range_copy = range;
CSSParserTokenRange args = ConsumeFunction(range_copy);
CSSPrimitiveValue* length = ConsumeLengthOrPercent(
args, context, CSSPrimitiveValue::ValueRange::kNonNegative,
UnitlessQuirk::kAllow);
if (!length || !args.AtEnd()) {
return nullptr;
}
range = range_copy;
auto* result =
MakeGarbageCollected<CSSFunctionValue>(CSSValueID::kFitContent);
result->Append(*length);
return result;
}
bool IsGridBreadthFixedSized(const CSSValue& value) {
if (auto* identifier_value = DynamicTo<CSSIdentifierValue>(value)) {
CSSValueID value_id = identifier_value->GetValueID();
return value_id != CSSValueID::kAuto &&
value_id != CSSValueID::kMinContent &&
value_id != CSSValueID::kMaxContent;
}
if (auto* primitive_value = DynamicTo<CSSPrimitiveValue>(value)) {
return !primitive_value->IsFlex();
}
NOTREACHED();
return true;
}
bool IsGridTrackFixedSized(const CSSValue& value) {
if (value.IsPrimitiveValue() || value.IsIdentifierValue()) {
return IsGridBreadthFixedSized(value);
}
auto& function = To<CSSFunctionValue>(value);
if (function.FunctionType() == CSSValueID::kFitContent) {
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,
const CSSParserContext& context) {
const auto& token_id = range.Peek().FunctionId();
if (token_id == CSSValueID::kMinmax) {
CSSParserTokenRange range_copy = range;
CSSParserTokenRange args = ConsumeFunction(range_copy);
CSSValue* min_track_breadth = ConsumeGridBreadth(args, context);
auto* min_track_breadth_primitive_value =
DynamicTo<CSSPrimitiveValue>(min_track_breadth);
if (!min_track_breadth ||
(min_track_breadth_primitive_value &&
min_track_breadth_primitive_value->IsFlex()) ||
!ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
CSSValue* max_track_breadth = ConsumeGridBreadth(args, context);
if (!max_track_breadth || !args.AtEnd()) {
return nullptr;
}
range = range_copy;
auto* result = MakeGarbageCollected<CSSFunctionValue>(CSSValueID::kMinmax);
result->Append(*min_track_breadth);
result->Append(*max_track_breadth);
return result;
}
return (token_id == CSSValueID::kFitContent)
? ConsumeFitContent(range, context)
: ConsumeGridBreadth(range, context);
}
CSSCustomIdentValue* ConsumeCustomIdentForGridLine(
CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kAuto ||
range.Peek().Id() == CSSValueID::kSpan) {
return nullptr;
}
return ConsumeCustomIdent(range, context);
}
// Appends to the passed in CSSBracketedValueList if any, otherwise creates a
// new one. Returns nullptr if an empty list is consumed.
CSSBracketedValueList* ConsumeGridLineNames(
CSSParserTokenRange& range,
const CSSParserContext& context,
bool is_subgrid_track_list,
CSSBracketedValueList* line_names = nullptr) {
CSSParserTokenRange range_copy = range;
if (range_copy.ConsumeIncludingWhitespace().GetType() != kLeftBracketToken) {
return nullptr;
}
if (!line_names) {
line_names = MakeGarbageCollected<CSSBracketedValueList>();
}
while (CSSCustomIdentValue* line_name =
ConsumeCustomIdentForGridLine(range_copy, context)) {
line_names->Append(*line_name);
}
if (range_copy.ConsumeIncludingWhitespace().GetType() != kRightBracketToken) {
return nullptr;
}
range = range_copy;
if (!is_subgrid_track_list && line_names->length() == 0U) {
return nullptr;
}
return line_names;
}
bool AppendLineNames(CSSParserTokenRange& range,
const CSSParserContext& context,
bool is_subgrid_track_list,
CSSValueList* values) {
if (CSSBracketedValueList* line_names =
ConsumeGridLineNames(range, context, is_subgrid_track_list)) {
values->Append(*line_names);
return true;
}
return false;
}
bool ConsumeGridTrackRepeatFunction(CSSParserTokenRange& range,
const CSSParserContext& context,
bool is_subgrid_track_list,
CSSValueList& list,
bool& is_auto_repeat,
bool& all_tracks_are_fixed_sized) {
CSSParserTokenRange args = ConsumeFunction(range);
is_auto_repeat = IdentMatches<CSSValueID::kAutoFill, CSSValueID::kAutoFit>(
args.Peek().Id());
CSSValueList* repeated_values;
// 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.
wtf_size_t repetitions = 1;
if (is_auto_repeat) {
repeated_values = MakeGarbageCollected<cssvalue::CSSGridAutoRepeatValue>(
args.ConsumeIncludingWhitespace().Id());
} else {
// TODO(rob.buis): a consumeIntegerRaw would be more efficient here.
CSSPrimitiveValue* repetition = ConsumePositiveInteger(args, context);
if (!repetition) {
return false;
}
repetitions =
ClampTo<wtf_size_t>(repetition->GetDoubleValue(), 0, kGridMaxTracks);
repeated_values = CSSValueList::CreateSpaceSeparated();
}
if (!ConsumeCommaIncludingWhitespace(args)) {
return false;
}
wtf_size_t number_of_line_name_sets =
AppendLineNames(args, context, is_subgrid_track_list, repeated_values);
wtf_size_t number_of_tracks = 0;
while (!args.AtEnd()) {
if (is_subgrid_track_list) {
if (!number_of_line_name_sets ||
!AppendLineNames(args, context, is_subgrid_track_list,
repeated_values)) {
return false;
}
++number_of_line_name_sets;
} else {
CSSValue* track_size = ConsumeGridTrackSize(args, context);
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;
AppendLineNames(args, context, is_subgrid_track_list, repeated_values);
}
}
// We should have found at least one <track-size> or else it is not a valid
// <track-list>. If it's a subgrid <line-name-list>, then we should have found
// at least one named grid line.
if ((is_subgrid_track_list && !number_of_line_name_sets) ||
(!is_subgrid_track_list && !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 / (is_subgrid_track_list
? number_of_line_name_sets
: number_of_tracks));
auto* integer_repeated_values =
MakeGarbageCollected<cssvalue::CSSGridIntegerRepeatValue>(repetitions);
for (wtf_size_t i = 0; i < repeated_values->length(); ++i) {
integer_repeated_values->Append(repeated_values->Item(i));
}
list.Append(*integer_repeated_values);
}
return true;
}
bool ConsumeGridTemplateRowsAndAreasAndColumns(
bool important,
CSSParserTokenRange& range,
const CSSParserContext& context,
const CSSValue*& template_rows,
const CSSValue*& template_columns,
const CSSValue*& template_areas) {
DCHECK(!template_rows);
DCHECK(!template_columns);
DCHECK(!template_areas);
NamedGridAreaMap grid_area_map;
wtf_size_t row_count = 0;
wtf_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
CSSBracketedValueList* line_names = nullptr;
do {
// Handle leading <custom-ident>*.
bool has_previous_line_names = line_names;
line_names = ConsumeGridLineNames(
range, context, /* is_subgrid_track_list */ false, 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);
if (!value) {
value = CSSIdentifierValue::Create(CSSValueID::kAuto);
}
template_rows_value_list->Append(*value);
// This will handle the trailing/leading <custom-ident>* in the grammar.
line_names =
ConsumeGridLineNames(range, context, /* is_subgrid_track_list */ false);
if (line_names) {
template_rows_value_list->Append(*line_names);
}
} while (!range.AtEnd() && !(range.Peek().GetType() == kDelimiterToken &&
range.Peek().Delimiter() == '/'));
if (!range.AtEnd()) {
if (!ConsumeSlashIncludingWhitespace(range)) {
return false;
}
template_columns = ConsumeGridTrackList(
range, context, TrackListType::kGridTemplateNoRepeat);
if (!template_columns || !range.AtEnd()) {
return false;
}
} else {
template_columns = CSSIdentifierValue::Create(CSSValueID::kNone);
}
template_rows = template_rows_value_list;
template_areas = MakeGarbageCollected<cssvalue::CSSGridTemplateAreasValue>(
grid_area_map, row_count, column_count);
return true;
}
CSSValue* ConsumeGridLine(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kAuto) {
return ConsumeIdent(range);
}
CSSIdentifierValue* span_value = nullptr;
CSSCustomIdentValue* grid_line_name = nullptr;
CSSPrimitiveValue* numeric_value = ConsumeInteger(range, context);
if (numeric_value) {
grid_line_name = ConsumeCustomIdentForGridLine(range, context);
span_value = ConsumeIdent<CSSValueID::kSpan>(range);
} else {
span_value = ConsumeIdent<CSSValueID::kSpan>(range);
if (span_value) {
numeric_value = ConsumeInteger(range, context);
grid_line_name = ConsumeCustomIdentForGridLine(range, context);
if (!numeric_value) {
numeric_value = ConsumeInteger(range, context);
}
} else {
grid_line_name = ConsumeCustomIdentForGridLine(range, context);
if (grid_line_name) {
numeric_value = ConsumeInteger(range, context);
span_value = ConsumeIdent<CSSValueID::kSpan>(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 = CSSNumericLiteralValue::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,
const CSSParserContext& context,
TrackListType track_list_type) {
bool allow_grid_line_names = track_list_type != TrackListType::kGridAuto;
if (!allow_grid_line_names && range.Peek().GetType() == kLeftBracketToken) {
return nullptr;
}
bool is_subgrid_track_list =
RuntimeEnabledFeatures::LayoutNGSubgridEnabled() &&
track_list_type == TrackListType::kGridTemplateSubgrid;
CSSValueList* values = CSSValueList::CreateSpaceSeparated();
if (is_subgrid_track_list) {
if (IdentMatches<CSSValueID::kSubgrid>(range.Peek().Id())) {
values->Append(*ConsumeIdent(range));
} else {
return nullptr;
}
}
AppendLineNames(range, context, is_subgrid_track_list, values);
bool allow_repeat =
is_subgrid_track_list || track_list_type == TrackListType::kGridTemplate;
bool seen_auto_repeat = false;
bool all_tracks_are_fixed_sized = true;
auto IsRangeAtEnd = [](CSSParserTokenRange& range) -> bool {
return range.AtEnd() || range.Peek().GetType() == kDelimiterToken;
};
do {
bool is_auto_repeat;
if (range.Peek().FunctionId() == CSSValueID::kRepeat) {
if (!allow_repeat) {
return nullptr;
}
if (!ConsumeGridTrackRepeatFunction(range, context, is_subgrid_track_list,
*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, context)) {
// If we find a <track-size> in a subgrid track list, then it isn't a
// valid <line-name-list>.
if (is_subgrid_track_list) {
return nullptr;
}
if (all_tracks_are_fixed_sized) {
all_tracks_are_fixed_sized = IsGridTrackFixedSized(*value);
}
values->Append(*value);
} else if (!is_subgrid_track_list) {
return nullptr;
}
if (seen_auto_repeat && !all_tracks_are_fixed_sized) {
return nullptr;
}
if (!allow_grid_line_names && range.Peek().GetType() == kLeftBracketToken) {
return nullptr;
}
bool did_append_line_names =
AppendLineNames(range, context, is_subgrid_track_list, values);
if (is_subgrid_track_list && !did_append_line_names &&
range.Peek().FunctionId() != CSSValueID::kRepeat) {
return IsRangeAtEnd(range) ? values : nullptr;
}
} while (!IsRangeAtEnd(range));
return values;
}
bool ParseGridTemplateAreasRow(const String& grid_row_names,
NamedGridAreaMap& grid_area_map,
const wtf_size_t row_count,
wtf_size_t& column_count) {
if (grid_row_names.ContainsOnlyWhitespaceOrEmpty()) {
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 (wtf_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;
}
wtf_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,
const CSSParserContext& context) {
switch (range.Peek().Id()) {
case CSSValueID::kNone:
return ConsumeIdent(range);
case CSSValueID::kSubgrid:
return ConsumeGridTrackList(range, context,
TrackListType::kGridTemplateSubgrid);
default:
return ConsumeGridTrackList(range, context, TrackListType::kGridTemplate);
}
}
bool ConsumeGridItemPositionShorthand(bool important,
CSSParserTokenRange& range,
const CSSParserContext& context,
CSSValue*& start_value,
CSSValue*& end_value) {
// Input should be nullptrs.
DCHECK(!start_value);
DCHECK(!end_value);
start_value = ConsumeGridLine(range, context);
if (!start_value) {
return false;
}
if (ConsumeSlashIncludingWhitespace(range)) {
end_value = ConsumeGridLine(range, context);
if (!end_value) {
return false;
}
} else {
end_value = start_value->IsCustomIdentValue()
? start_value
: CSSIdentifierValue::Create(CSSValueID::kAuto);
}
return range.AtEnd();
}
bool ConsumeGridTemplateShorthand(bool important,
CSSParserTokenRange& range,
const CSSParserContext& context,
const CSSValue*& template_rows,
const CSSValue*& template_columns,
const CSSValue*& template_areas) {
DCHECK(!template_rows);
DCHECK(!template_columns);
DCHECK(!template_areas);
DCHECK_EQ(gridTemplateShorthand().length(), 3u);
CSSParserTokenRange range_copy = range;
template_rows = ConsumeIdent<CSSValueID::kNone>(range);
// 1- 'none' case.
if (template_rows && range.AtEnd()) {
template_rows = CSSIdentifierValue::Create(CSSValueID::kNone);
template_columns = CSSIdentifierValue::Create(CSSValueID::kNone);
template_areas = CSSIdentifierValue::Create(CSSValueID::kNone);
return true;
}
// 2- <grid-template-rows> / <grid-template-columns>
if (!template_rows) {
template_rows = ConsumeGridTemplatesRowsOrColumns(range, context);
}
if (template_rows) {
if (!ConsumeSlashIncludingWhitespace(range)) {
return false;
}
template_columns = ConsumeGridTemplatesRowsOrColumns(range, context);
if (!template_columns || !range.AtEnd()) {
return false;
}
template_areas = CSSIdentifierValue::Create(CSSValueID::kNone);
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);
}
CSSValue* ConsumeHyphenateLimitChars(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValueList* const list = CSSValueList::CreateSpaceSeparated();
while (!range.AtEnd() && list->length() < 3) {
if (const CSSPrimitiveValue* value = ConsumeIntegerOrNumberCalc(
range, context, CSSPrimitiveValue::ValueRange::kPositiveInteger)) {
list->Append(*value);
continue;
}
if (const CSSIdentifierValue* ident =
ConsumeIdent<CSSValueID::kAuto>(range)) {
list->Append(*ident);
continue;
}
return nullptr;
}
if (list->length()) {
return list;
}
return nullptr;
}
bool ConsumeFromPageBreakBetween(CSSParserTokenRange& range,
CSSValueID& value) {
if (!ConsumeCSSValueId(range, value)) {
return false;
}
if (value == CSSValueID::kAlways) {
value = CSSValueID::kPage;
return true;
}
return value == CSSValueID::kAuto || value == CSSValueID::kAvoid ||
value == CSSValueID::kLeft || value == CSSValueID::kRight;
}
bool ConsumeFromColumnBreakBetween(CSSParserTokenRange& range,
CSSValueID& value) {
if (!ConsumeCSSValueId(range, value)) {
return false;
}
if (value == CSSValueID::kAlways) {
value = CSSValueID::kColumn;
return true;
}
return value == CSSValueID::kAuto || value == CSSValueID::kAvoid;
}
bool ConsumeFromColumnOrPageBreakInside(CSSParserTokenRange& range,
CSSValueID& value) {
if (!ConsumeCSSValueId(range, value)) {
return false;
}
return value == CSSValueID::kAuto || value == CSSValueID::kAvoid;
}
bool ValidWidthOrHeightKeyword(CSSValueID id, const CSSParserContext& context) {
if (id == CSSValueID::kWebkitMinContent ||
id == CSSValueID::kWebkitMaxContent ||
id == CSSValueID::kWebkitFillAvailable ||
id == CSSValueID::kWebkitFitContent || id == CSSValueID::kMinContent ||
id == CSSValueID::kMaxContent || id == CSSValueID::kFitContent) {
switch (id) {
case CSSValueID::kWebkitMinContent:
context.Count(WebFeature::kCSSValuePrefixedMinContent);
break;
case CSSValueID::kWebkitMaxContent:
context.Count(WebFeature::kCSSValuePrefixedMaxContent);
break;
case CSSValueID::kWebkitFillAvailable:
context.Count(WebFeature::kCSSValuePrefixedFillAvailable);
break;
case CSSValueID::kWebkitFitContent:
context.Count(WebFeature::kCSSValuePrefixedFitContent);
break;
default:
break;
}
return true;
}
return false;
}
std::unique_ptr<SVGPathByteStream> ConsumePathStringArg(
CSSParserTokenRange& args) {
if (args.Peek().GetType() != kStringToken) {
return nullptr;
}
StringView path_string = args.ConsumeIncludingWhitespace().Value();
std::unique_ptr<SVGPathByteStream> byte_stream =
std::make_unique<SVGPathByteStream>();
if (BuildByteStreamFromString(path_string, *byte_stream) !=
SVGParseStatus::kNoError) {
return nullptr;
}
return byte_stream;
}
cssvalue::CSSPathValue* ConsumeBasicShapePath(CSSParserTokenRange& args) {
auto wind_rule = RULE_NONZERO;
if (IdentMatches<CSSValueID::kEvenodd, CSSValueID::kNonzero>(
args.Peek().Id())) {
wind_rule = args.ConsumeIncludingWhitespace().Id() == CSSValueID::kEvenodd
? RULE_EVENODD
: RULE_NONZERO;
if (!ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
}
auto byte_stream = ConsumePathStringArg(args);
if (!byte_stream || !args.AtEnd()) {
return nullptr;
}
return MakeGarbageCollected<cssvalue::CSSPathValue>(std::move(byte_stream),
wind_rule);
}
CSSValue* ConsumePathFunction(CSSParserTokenRange& range) {
// FIXME: Add support for <url>, <basic-shape>, <geometry-box>.
if (range.Peek().FunctionId() != CSSValueID::kPath) {
return nullptr;
}
CSSParserTokenRange function_range = range;
CSSParserTokenRange function_args = ConsumeFunction(function_range);
auto byte_stream = ConsumePathStringArg(function_args);
if (!byte_stream || !function_args.AtEnd()) {
return nullptr;
}
range = function_range;
if (byte_stream->IsEmpty()) {
return CSSIdentifierValue::Create(CSSValueID::kNone);
}
return MakeGarbageCollected<cssvalue::CSSPathValue>(std::move(byte_stream));
}
CSSValue* ConsumeRay(CSSParserTokenRange& range,
const CSSParserContext& context) {
DCHECK_EQ(range.Peek().FunctionId(), CSSValueID::kRay);
CSSParserTokenRange function_range = range;
CSSParserTokenRange function_args = ConsumeFunction(function_range);
CSSPrimitiveValue* angle = nullptr;
CSSIdentifierValue* size = nullptr;
CSSIdentifierValue* contain = nullptr;
while (!function_args.AtEnd()) {
if (!angle) {
angle =
ConsumeAngle(function_args, context, absl::optional<WebFeature>());
if (angle) {
continue;
}
}
if (!size) {
size =
ConsumeIdent<CSSValueID::kClosestSide, CSSValueID::kClosestCorner,
CSSValueID::kFarthestSide, CSSValueID::kFarthestCorner,
CSSValueID::kSides>(function_args);
if (size) {
continue;
}
}
if (RuntimeEnabledFeatures::CSSOffsetPathRayContainEnabled() && !contain) {
contain = ConsumeIdent<CSSValueID::kContain>(function_args);
if (contain) {
continue;
}
}
return nullptr;
}
if (!angle) {
return nullptr;
}
if (!size) {
size = CSSIdentifierValue::Create(CSSValueID::kClosestSide);
}
range = function_range;
return MakeGarbageCollected<cssvalue::CSSRayValue>(*angle, *size, contain);
}
CSSValue* ConsumeMaxWidthOrHeight(CSSParserTokenRange& range,
const CSSParserContext& context,
UnitlessQuirk unitless) {
if (range.Peek().Id() == CSSValueID::kNone ||
ValidWidthOrHeightKeyword(range.Peek().Id(), context)) {
return ConsumeIdent(range);
}
return ConsumeLengthOrPercent(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative, unitless,
static_cast<CSSAnchorQueryTypes>(CSSAnchorQueryType::kAnchorSize));
}
CSSValue* ConsumeWidthOrHeight(CSSParserTokenRange& range,
const CSSParserContext& context,
UnitlessQuirk unitless) {
if (range.Peek().Id() == CSSValueID::kAuto ||
ValidWidthOrHeightKeyword(range.Peek().Id(), context)) {
return ConsumeIdent(range);
}
return ConsumeLengthOrPercent(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative, unitless,
static_cast<CSSAnchorQueryTypes>(CSSAnchorQueryType::kAnchorSize));
}
CSSValue* ConsumeMarginOrOffset(CSSParserTokenRange& range,
const CSSParserContext& context,
UnitlessQuirk unitless,
CSSAnchorQueryTypes allowed_anchor_queries) {
if (range.Peek().Id() == CSSValueID::kAuto) {
return ConsumeIdent(range);
}
return ConsumeLengthOrPercent(range, context,
CSSPrimitiveValue::ValueRange::kAll, unitless,
allowed_anchor_queries);
}
CSSValue* ConsumeScrollPadding(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kAuto) {
return ConsumeIdent(range);
}
CSSParserContext::ParserModeOverridingScope scope(context, kHTMLStandardMode);
return ConsumeLengthOrPercent(range, context,
CSSPrimitiveValue::ValueRange::kNonNegative,
UnitlessQuirk::kForbid);
}
namespace {
// https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes
bool IsBasicShapeSupportedByOffsetPath(const CSSValueID& id) {
switch (id) {
case CSSValueID::kCircle:
case CSSValueID::kEllipse:
return RuntimeEnabledFeatures::
CSSOffsetPathBasicShapesCircleAndEllipseEnabled();
case CSSValueID::kInset:
case CSSValueID::kXywh:
case CSSValueID::kRect:
case CSSValueID::kPolygon:
return RuntimeEnabledFeatures::
CSSOffsetPathBasicShapesRectanglesAndPolygonEnabled();
default:
return false;
}
}
} // namespace
CSSValue* ConsumeScrollStart(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (CSSIdentifierValue* ident =
ConsumeIdent<CSSValueID::kAuto, CSSValueID::kStart,
CSSValueID::kCenter, CSSValueID::kEnd, CSSValueID::kTop,
CSSValueID::kBottom, CSSValueID::kLeft,
CSSValueID::kRight>(range)) {
return ident;
}
return ConsumeLengthOrPercent(range, context,
CSSPrimitiveValue::ValueRange::kNonNegative);
}
CSSValue* ConsumeOffsetPath(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValue* offset_path = nullptr;
CSSValue* coord_box = nullptr;
if (CSSValue* none = ConsumeIdent<CSSValueID::kNone>(range)) {
return none;
}
if (RuntimeEnabledFeatures::CSSOffsetPathCoordBoxEnabled()) {
coord_box = ConsumeCoordBox(range);
}
const CSSValueID function_id = range.Peek().FunctionId();
if (RuntimeEnabledFeatures::CSSOffsetPathRayEnabled() &&
function_id == CSSValueID::kRay) {
offset_path = ConsumeRay(range, context);
}
if (!offset_path && IsBasicShapeSupportedByOffsetPath(function_id)) {
offset_path = ConsumeBasicShape(range, context, AllowPathValue::kAllow,
AllowBasicShapeRectValue::kAllow,
AllowBasicShapeXYWHValue::kAllow);
}
if (!offset_path && RuntimeEnabledFeatures::CSSOffsetPathUrlEnabled()) {
offset_path = ConsumeUrl(range, context);
}
if (!offset_path) {
offset_path = ConsumePathFunction(range);
}
if (!coord_box && RuntimeEnabledFeatures::CSSOffsetPathCoordBoxEnabled()) {
coord_box = ConsumeCoordBox(range);
}
if (!offset_path && !coord_box) {
return nullptr;
}
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
if (offset_path) {
list->Append(*offset_path);
}
if (coord_box) {
list->Append(*coord_box);
}
// Count when we receive a valid path other than 'none'.
context.Count(WebFeature::kCSSOffsetInEffect);
return list;
}
CSSValue* ConsumePathOrNone(CSSParserTokenRange& range) {
CSSValueID id = range.Peek().Id();
if (id == CSSValueID::kNone) {
return ConsumeIdent(range);
}
return ConsumePathFunction(range);
}
CSSValue* ConsumeOffsetRotate(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValue* angle = ConsumeAngle(range, context, absl::optional<WebFeature>());
CSSValue* keyword =
ConsumeIdent<CSSValueID::kAuto, CSSValueID::kReverse>(range);
if (!angle && !keyword) {
return nullptr;
}
if (!angle) {
angle = ConsumeAngle(range, context, absl::optional<WebFeature>());
}
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
if (keyword) {
list->Append(*keyword);
}
if (angle) {
list->Append(*angle);
}
return list;
}
CSSValue* ConsumeInitialLetter(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (auto* normal = ConsumeIdent<CSSValueID::kNormal>(range)) {
return CSSIdentifierValue::Create(CSSValueID::kNormal);
}
CSSValueList* const list = CSSValueList::CreateSpaceSeparated();
// ["drop" | "raise"] number[1,Inf]
if (auto* sink_type =
ConsumeIdent<CSSValueID::kDrop, CSSValueID::kRaise>(range)) {
if (auto* size = ConsumeNumber(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative)) {
if (size->GetFloatValue() < 1) {
return nullptr;
}
list->Append(*size);
list->Append(*sink_type);
return list;
}
return nullptr;
}
// number[1, Inf]
// number[1, Inf] ["drop" | "raise"]
// number[1, Inf] integer[1, Inf]
if (auto* size = ConsumeNumber(range, context,
CSSPrimitiveValue::ValueRange::kNonNegative)) {
if (size->GetFloatValue() < 1) {
return nullptr;
}
list->Append(*size);
if (range.AtEnd()) {
return list;
}
if (auto* sink_type =
ConsumeIdent<CSSValueID::kDrop, CSSValueID::kRaise>(range)) {
list->Append(*sink_type);
return list;
}
if (auto* sink = ConsumeIntegerOrNumberCalc(
range, context, CSSPrimitiveValue::ValueRange::kPositiveInteger)) {
list->Append(*sink);
return list;
}
}
return nullptr;
}
bool ConsumeRadii(CSSValue* horizontal_radii[4],
CSSValue* vertical_radii[4],
CSSParserTokenRange& range,
const CSSParserContext& context,
bool use_legacy_parsing) {
unsigned horizontal_value_count = 0;
for (; horizontal_value_count < 4 && !range.AtEnd() &&
range.Peek().GetType() != kDelimiterToken;
++horizontal_value_count) {
horizontal_radii[horizontal_value_count] = ConsumeLengthOrPercent(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (!horizontal_radii[horizontal_value_count]) {
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 && horizontal_value_count == 2) {
vertical_radii[0] = horizontal_radii[1];
horizontal_radii[1] = nullptr;
} else {
Complete4Sides(horizontal_radii);
for (unsigned i = 0; i < 4; ++i) {
vertical_radii[i] = horizontal_radii[i];
}
return true;
}
} else {
if (!ConsumeSlashIncludingWhitespace(range)) {
return false;
}
for (unsigned i = 0; i < 4 && !range.AtEnd(); ++i) {
vertical_radii[i] = ConsumeLengthOrPercent(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (!vertical_radii[i]) {
return false;
}
}
if (!vertical_radii[0] || !range.AtEnd()) {
return false;
}
}
Complete4Sides(horizontal_radii);
Complete4Sides(vertical_radii);
return true;
}
CSSValue* ConsumeBasicShape(CSSParserTokenRange& range,
const CSSParserContext& context,
AllowPathValue allow_path,
AllowBasicShapeRectValue allow_rect,
AllowBasicShapeXYWHValue allow_xywh) {
CSSValue* shape = nullptr;
if (range.Peek().GetType() != kFunctionToken) {
return nullptr;
}
CSSValueID id = range.Peek().FunctionId();
CSSParserTokenRange range_copy = range;
CSSParserTokenRange args = ConsumeFunction(range_copy);
if (id == CSSValueID::kCircle) {
shape = ConsumeBasicShapeCircle(args, context);
} else if (id == CSSValueID::kEllipse) {
shape = ConsumeBasicShapeEllipse(args, context);
} else if (id == CSSValueID::kPolygon) {
shape = ConsumeBasicShapePolygon(args, context);
} else if (id == CSSValueID::kInset) {
shape = ConsumeBasicShapeInset(args, context);
} else if (id == CSSValueID::kPath && allow_path == AllowPathValue::kAllow) {
shape = ConsumeBasicShapePath(args);
} else if (id == CSSValueID::kRect &&
allow_rect == AllowBasicShapeRectValue::kAllow) {
shape = ConsumeBasicShapeRect(args, context);
} else if (id == CSSValueID::kXywh &&
allow_xywh == AllowBasicShapeXYWHValue::kAllow) {
shape = ConsumeBasicShapeXYWH(args, context);
}
if (!shape || !args.AtEnd()) {
return nullptr;
}
context.Count(WebFeature::kCSSBasicShape);
range = range_copy;
return shape;
}
// none | [ underline || overline || line-through || blink ] | spelling-error |
// grammar-error
CSSValue* ConsumeTextDecorationLine(CSSParserTokenRange& range) {
CSSValueID id = range.Peek().Id();
if (id == CSSValueID::kNone) {
return ConsumeIdent(range);
}
if (RuntimeEnabledFeatures::CSSSpellingGrammarErrorsEnabled() &&
(id == CSSValueID::kSpellingError || id == CSSValueID::kGrammarError)) {
// Note that StyleBuilderConverter::ConvertFlags() requires that values
// other than 'none' appear in a CSSValueList.
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
list->Append(*ConsumeIdent(range));
return list;
}
CSSIdentifierValue* underline = nullptr;
CSSIdentifierValue* overline = nullptr;
CSSIdentifierValue* line_through = nullptr;
CSSIdentifierValue* blink = nullptr;
while (true) {
id = range.Peek().Id();
if (id == CSSValueID::kUnderline && !underline) {
underline = ConsumeIdent(range);
} else if (id == CSSValueID::kOverline && !overline) {
overline = ConsumeIdent(range);
} else if (id == CSSValueID::kLineThrough && !line_through) {
line_through = ConsumeIdent(range);
} else if (id == CSSValueID::kBlink && !blink) {
blink = ConsumeIdent(range);
} else {
break;
}
}
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
if (underline) {
list->Append(*underline);
}
if (overline) {
list->Append(*overline);
}
if (line_through) {
list->Append(*line_through);
}
if (blink) {
list->Append(*blink);
}
if (!list->length()) {
return nullptr;
}
return list;
}
CSSValue* ConsumeToggleGroup(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kNone) {
return nullptr;
}
CSSCustomIdentValue* toggle_name = ConsumeCustomIdent(range, context);
if (!toggle_name) {
return nullptr;
}
CSSIdentifierValue* self_value = ConsumeIdent<CSSValueID::kSelf>(range);
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
list->Append(*toggle_name);
if (self_value) {
list->Append(*self_value);
}
return list;
}
// <toggle-value> = <integer [0,∞]> | <custom-ident>
static CSSValue* ConsumeToggleValue(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (CSSPrimitiveValue* integer_value = ConsumeIntegerOrNumberCalc(
range, context, CSSPrimitiveValue::ValueRange::kNonNegativeInteger)) {
return integer_value;
}
return ConsumeCustomIdent(range, context);
}
CSSValue* ConsumeToggleSpecifier(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kNone) {
return nullptr;
}
CSSCustomIdentValue* toggle_name = ConsumeCustomIdent(range, context);
if (!toggle_name) {
return nullptr;
}
// Create the list now so that we can append the states to it when we
// find them; save the other values for the end.
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
list->Append(*toggle_name);
bool found_states = false;
CSSIdentifierValue* overflow_value = nullptr;
CSSIdentifierValue* group_value = nullptr;
CSSIdentifierValue* self_value = nullptr;
while (!range.AtEnd()) {
if (!overflow_value) {
overflow_value = ConsumeIdent<CSSValueID::kCycle, CSSValueID::kCycleOn,
CSSValueID::kSticky>(range);
if (overflow_value) {
continue;
}
}
if (!group_value) {
group_value = ConsumeIdent<CSSValueID::kGroup>(range);
if (group_value) {
continue;
}
}
if (!self_value) {
self_value = ConsumeIdent<CSSValueID::kSelf>(range);
if (self_value) {
continue;
}
}
if (!found_states) {
// <toggle-states> [at <toggle-value>]?
// where:
// <toggle-states> = <integer [1,∞]> | '[' <custom-ident>* ']'
// <toggle-value> = <integer [0,∞]> | <custom-ident>
if (CSSPrimitiveValue* maximum_state_value = ConsumeIntegerOrNumberCalc(
range, context,
CSSPrimitiveValue::ValueRange::kPositiveInteger)) {
found_states = true;
list->Append(*maximum_state_value);
} else if (range.Peek().GetType() == kLeftBracketToken) {
CSSParserTokenRange block = range.ConsumeBlock();
block.ConsumeWhitespace();
range.ConsumeWhitespace();
auto* state_list = MakeGarbageCollected<CSSBracketedValueList>();
HashSet<AtomicString> states_found;
while (true) {
CSSCustomIdentValue* state_name = ConsumeCustomIdent(block, context);
if (!state_name) {
break;
}
// If <toggle-states> is a bracketed list, and there are any
// repeated <custom-ident>s among its items, the property is
// invalid.
if (!states_found.insert(state_name->Value()).is_new_entry) {
return nullptr;
}
state_list->Append(*state_name);
}
if (state_list->length() < 2u || !block.AtEnd()) {
return nullptr;
}
list->Append(*state_list);
found_states = true;
}
if (found_states) {
if (CSSValue* at_value = ConsumeIdent<CSSValueID::kAt>(range)) {
list->Append(*at_value);
if (CSSValue* toggle_value = ConsumeToggleValue(range, context)) {
list->Append(*toggle_value);
} else {
return nullptr;
}
}
continue;
}
}
break;
}
if (overflow_value) {
list->Append(*overflow_value);
}
if (group_value) {
list->Append(*group_value);
}
if (self_value) {
list->Append(*self_value);
}
return list;
}
CSSValue* ConsumeToggleTrigger(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kNone) {
return nullptr;
}
CSSCustomIdentValue* toggle_name = ConsumeCustomIdent(range, context);
if (!toggle_name) {
return nullptr;
}
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
list->Append(*toggle_name);
CSSIdentifierValue* mode_value =
ConsumeIdent<CSSValueID::kPrev, CSSValueID::kNext, CSSValueID::kSet>(
range);
if (!mode_value) {
return list;
}
list->Append(*mode_value);
if (mode_value->GetValueID() != CSSValueID::kSet) {
// [prev | next] <integer [1,∞]>?
DCHECK(mode_value->GetValueID() == CSSValueID::kPrev ||
mode_value->GetValueID() == CSSValueID::kNext);
CSSPrimitiveValue* increment_value = ConsumeIntegerOrNumberCalc(
range, context, CSSPrimitiveValue::ValueRange::kPositiveInteger);
if (increment_value) {
list->Append(*increment_value);
}
} else {
// set <toggle-value>
DCHECK_EQ(mode_value->GetValueID(), CSSValueID::kSet);
if (CSSValue* target_value = ConsumeToggleValue(range, context)) {
list->Append(*target_value);
} else {
return nullptr;
}
}
return list;
}
CSSValue* ConsumeTransformValue(CSSParserTokenRange& range,
const CSSParserContext& context,
bool use_legacy_parsing) {
CSSValueID function_id = range.Peek().FunctionId();
if (!IsValidCSSValueID(function_id)) {
return nullptr;
}
CSSParserTokenRange args = ConsumeFunction(range);
if (args.AtEnd()) {
return nullptr;
}
auto* transform_value = MakeGarbageCollected<CSSFunctionValue>(function_id);
CSSValue* parsed_value = nullptr;
switch (function_id) {
case CSSValueID::kRotate:
case CSSValueID::kRotateX:
case CSSValueID::kRotateY:
case CSSValueID::kRotateZ:
case CSSValueID::kSkewX:
case CSSValueID::kSkewY:
case CSSValueID::kSkew:
parsed_value =
ConsumeAngle(args, context, WebFeature::kUnitlessZeroAngleTransform);
if (!parsed_value) {
return nullptr;
}
if (function_id == CSSValueID::kSkew &&
ConsumeCommaIncludingWhitespace(args)) {
transform_value->Append(*parsed_value);
parsed_value = ConsumeAngle(args, context,
WebFeature::kUnitlessZeroAngleTransform);
if (!parsed_value) {
return nullptr;
}
}
break;
case CSSValueID::kScaleX:
case CSSValueID::kScaleY:
case CSSValueID::kScaleZ:
case CSSValueID::kScale:
parsed_value = ConsumeNumberOrPercent(
args, context, CSSPrimitiveValue::ValueRange::kAll);
if (!parsed_value) {
return nullptr;
}
if (function_id == CSSValueID::kScale &&
ConsumeCommaIncludingWhitespace(args)) {
transform_value->Append(*parsed_value);
parsed_value = ConsumeNumberOrPercent(
args, context, CSSPrimitiveValue::ValueRange::kAll);
if (!parsed_value) {
return nullptr;
}
}
break;
case CSSValueID::kPerspective:
if (!ConsumePerspective(args, context, transform_value,
use_legacy_parsing)) {
return nullptr;
}
break;
case CSSValueID::kTranslateX:
case CSSValueID::kTranslateY:
case CSSValueID::kTranslate:
parsed_value = ConsumeLengthOrPercent(
args, context, CSSPrimitiveValue::ValueRange::kAll);
if (!parsed_value) {
return nullptr;
}
if (function_id == CSSValueID::kTranslate &&
ConsumeCommaIncludingWhitespace(args)) {
transform_value->Append(*parsed_value);
parsed_value = ConsumeLengthOrPercent(
args, context, CSSPrimitiveValue::ValueRange::kAll);
if (!parsed_value) {
return nullptr;
}
}
break;
case CSSValueID::kTranslateZ:
parsed_value =
ConsumeLength(args, context, CSSPrimitiveValue::ValueRange::kAll);
break;
case CSSValueID::kMatrix:
case CSSValueID::kMatrix3d:
if (!ConsumeNumbers(args, context, transform_value,
(function_id == CSSValueID::kMatrix3d) ? 16 : 6)) {
return nullptr;
}
break;
case CSSValueID::kScale3d:
if (!ConsumeNumbersOrPercents(args, context, transform_value, 3)) {
return nullptr;
}
break;
case CSSValueID::kRotate3d:
if (!ConsumeNumbers(args, context, transform_value, 3) ||
!ConsumeCommaIncludingWhitespace(args)) {
return nullptr;
}
parsed_value =
ConsumeAngle(args, context, WebFeature::kUnitlessZeroAngleTransform);
if (!parsed_value) {
return nullptr;
}
break;
case CSSValueID::kTranslate3d:
if (!ConsumeTranslate3d(args, context, transform_value)) {
return nullptr;
}
break;
default:
return nullptr;
}
if (parsed_value) {
transform_value->Append(*parsed_value);
}
if (!args.AtEnd()) {
return nullptr;
}
return transform_value;
}
CSSValue* ConsumeTransformList(CSSParserTokenRange& range,
const CSSParserContext& context,
const CSSParserLocalContext& local_context) {
if (range.Peek().Id() == CSSValueID::kNone) {
return 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 CSSParserContext& context) {
const CSSParserToken& token = range.Peek();
if (token.GetType() != kIdentToken) {
return nullptr;
}
if (token.Id() == CSSValueID::kNone) {
return ConsumeIdent(range);
}
const auto* execution_context = context.GetExecutionContext();
CSSPropertyID unresolved_property =
token.ParseAsUnresolvedCSSPropertyID(execution_context);
if (unresolved_property != CSSPropertyID::kInvalid &&
unresolved_property != CSSPropertyID::kVariable) {
#if DCHECK_IS_ON()
DCHECK(CSSProperty::Get(ResolveCSSPropertyID(unresolved_property))
.IsWebExposed(execution_context));
#endif
range.ConsumeIncludingWhitespace();
return MakeGarbageCollected<CSSCustomIdentValue>(unresolved_property);
}
return ConsumeCustomIdent(range, context);
}
bool IsValidPropertyList(const CSSValueList& value_list) {
if (value_list.length() < 2) {
return true;
}
for (auto& value : value_list) {
auto* identifier_value = DynamicTo<CSSIdentifierValue>(value.Get());
if (identifier_value &&
identifier_value->GetValueID() == CSSValueID::kNone) {
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 == CSSPropertyID::kInvalid ||
shorthand == CSSPropertyID::kBorderColor);
return ConsumeColor(range, context, allow_quirky_colors);
}
CSSValue* ConsumeBorderWidth(CSSParserTokenRange& range,
const CSSParserContext& context,
UnitlessQuirk unitless) {
return ConsumeLineWidth(range, context, unitless);
}
CSSValue* ParseSpacing(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kNormal) {
return ConsumeIdent(range);
}
// TODO(timloh): allow <percentage>s in word-spacing.
return ConsumeLength(range, context, CSSPrimitiveValue::ValueRange::kAll,
UnitlessQuirk::kAllow);
}
CSSValue* ConsumeSingleContainerName(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().GetType() != kIdentToken) {
return nullptr;
}
if (range.Peek().Id() == CSSValueID::kNone) {
return nullptr;
}
if (EqualIgnoringASCIICase(range.Peek().Value(), "not")) {
return nullptr;
}
if (EqualIgnoringASCIICase(range.Peek().Value(), "and")) {
return nullptr;
}
if (EqualIgnoringASCIICase(range.Peek().Value(), "or")) {
return nullptr;
}
return ConsumeCustomIdent(range, context);
}
CSSValue* ConsumeContainerName(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (CSSValue* value = ConsumeIdent<CSSValueID::kNone>(range)) {
return value;
}
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
while (CSSValue* value = ConsumeSingleContainerName(range, context)) {
list->Append(*value);
}
return list->length() ? list : nullptr;
}
CSSValue* ConsumeContainerType(CSSParserTokenRange& range) {
if (CSSValue* value = ConsumeIdent<CSSValueID::kNormal>(range)) {
return value;
}
if (CSSValue* value =
ConsumeIdent<CSSValueID::kSize, CSSValueID::kInlineSize>(range)) {
// Note that StyleBuilderConverter::ConvertFlags requires that values
// other than the ZeroValue appear in a CSSValueList, hence we return a list
// with one item here. Also note that the full grammar will require multiple
// list items in the future, if we add support for non-size container types.
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
list->Append(*value);
return list;
}
return nullptr;
}
CSSValue* ConsumeSVGPaint(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kNone) {
return ConsumeIdent(range);
}
cssvalue::CSSURIValue* url = ConsumeUrl(range, context);
if (url) {
CSSValue* parsed_value = nullptr;
if (range.Peek().Id() == CSSValueID::kNone) {
parsed_value = ConsumeIdent(range);
} else {
parsed_value = ConsumeColor(range, context);
}
if (parsed_value) {
CSSValueList* values = CSSValueList::CreateSpaceSeparated();
values->Append(*url);
values->Append(*parsed_value);
return values;
}
return url;
}
return ConsumeColor(range, context);
}
UnitlessQuirk UnitlessUnlessShorthand(
const CSSParserLocalContext& local_context) {
return local_context.CurrentShorthand() == CSSPropertyID::kInvalid
? UnitlessQuirk::kAllow
: UnitlessQuirk::kForbid;
}
bool ShouldLowerCaseCounterStyleNameOnParse(const AtomicString& name,
const CSSParserContext& context) {
if (context.Mode() == kUASheetMode) {
// Names in UA sheet should be already in lower case.
DCHECK_EQ(name, name.LowerASCII());
return false;
}
return CounterStyleMap::GetUACounterStyleMap()->FindCounterStyleAcrossScopes(
name.LowerASCII());
}
CSSCustomIdentValue* ConsumeCounterStyleName(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSParserTokenRange original_range = range;
// <counter-style-name> is a <custom-ident> that is not an ASCII
// case-insensitive match for "none".
const CSSParserToken& name_token = range.ConsumeIncludingWhitespace();
if (name_token.GetType() != kIdentToken ||
!css_parsing_utils::IsCustomIdent<CSSValueID::kNone>(name_token.Id())) {
range = original_range;
return nullptr;
}
AtomicString name(name_token.Value().ToString());
if (ShouldLowerCaseCounterStyleNameOnParse(name, context)) {
name = name.LowerASCII();
}
return MakeGarbageCollected<CSSCustomIdentValue>(name);
}
AtomicString ConsumeCounterStyleNameInPrelude(CSSParserTokenRange& prelude,
const CSSParserContext& context) {
const CSSParserToken& name_token = prelude.ConsumeIncludingWhitespace();
if (!prelude.AtEnd()) {
return g_null_atom;
}
if (name_token.GetType() != kIdentToken ||
!IsCustomIdent<CSSValueID::kNone>(name_token.Id())) {
return g_null_atom;
}
if (context.Mode() != kUASheetMode) {
if (name_token.Id() == CSSValueID::kDecimal ||
name_token.Id() == CSSValueID::kDisc ||
name_token.Id() == CSSValueID::kCircle ||
name_token.Id() == CSSValueID::kSquare ||
name_token.Id() == CSSValueID::kDisclosureOpen ||
name_token.Id() == CSSValueID::kDisclosureClosed) {
return g_null_atom;
}
}
AtomicString name(name_token.Value().ToString());
if (ShouldLowerCaseCounterStyleNameOnParse(name, context)) {
name = name.LowerASCII();
}
return name;
}
CSSValue* ConsumeFontSizeAdjust(CSSParserTokenRange& range,
const CSSParserContext& context) {
if (range.Peek().Id() == CSSValueID::kNone) {
return css_parsing_utils::ConsumeIdent(range);
}
CSSIdentifierValue* font_metric =
ConsumeIdent<CSSValueID::kExHeight, CSSValueID::kCapHeight,
CSSValueID::kChWidth, CSSValueID::kIcWidth>(range);
CSSPrimitiveValue* value = css_parsing_utils::ConsumeNumber(
range, context, CSSPrimitiveValue::ValueRange::kNonNegative);
if (!value || !font_metric ||
font_metric->GetValueID() == CSSValueID::kExHeight) {
return value;
}
return MakeGarbageCollected<CSSValuePair>(font_metric, value,
CSSValuePair::kKeepIdenticalValues);
}
} // namespace css_parsing_utils
} // namespace blink