Add a CSS fast path parser for hsl() and hsla() colors.
We already had one for rgb()/rgba(), so refactor a few things from it
and use them to build one for hsl()/hsla(). Speeds up MotionMark
multiply by 5–7%.
Change-Id: I5e70e9f8905184d7958e33a02b95c28c587f1208
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3417641
Reviewed-by: Anders Hartvoll Ruud <andruud@chromium.org>
Commit-Queue: Steinar H Gunderson <sesse@chromium.org>
Cr-Commit-Position: refs/heads/main@{#965281}
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_fast_paths.cc b/third_party/blink/renderer/core/css/parser/css_parser_fast_paths.cc
index 593482a..cc3292d 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser_fast_paths.cc
+++ b/third_party/blink/renderer/core/css/parser/css_parser_fast_paths.cc
@@ -178,7 +178,8 @@
length -= 4;
unit = CSSPrimitiveValue::UnitType::kTurns;
} else {
- // Only valid for zero (we'll check that in the caller).
+ // For rotate: Only valid for zero (we'll check that in the caller).
+ // For hsl(): To be treated as angles (also done in the caller).
unit = CSSPrimitiveValue::UnitType::kNumber;
}
@@ -240,13 +241,10 @@
}
}
-// Returns the number of characters which form a valid double
-// and are terminated by the given terminator character
+// Returns the number of initial characters which form a valid double.
template <typename CharacterType>
-static int CheckForValidDouble(const CharacterType* string,
- const CharacterType* end,
- const bool terminated_by_space,
- const char terminator) {
+static int FindLengthOfValidDouble(const CharacterType* string,
+ const CharacterType* end) {
int length = static_cast<int>(end - string);
if (length < 1)
return 0;
@@ -254,17 +252,12 @@
bool decimal_mark_seen = false;
int processed_length = 0;
- for (int i = 0; i < length; ++i) {
- if (string[i] == terminator ||
- (terminated_by_space && IsHTMLSpace<CharacterType>(string[i]))) {
- processed_length = i;
- break;
- }
+ for (int i = 0; i < length; ++i, ++processed_length) {
if (!IsASCIIDigit(string[i])) {
if (!decimal_mark_seen && string[i] == '.')
decimal_mark_seen = true;
else
- return 0;
+ break;
}
}
@@ -274,17 +267,32 @@
return processed_length;
}
-// Returns the number of characters consumed for parsing a valid double
-// terminated by the given terminator character
+// If also_accept_whitespace is true: Checks whether string[pos] is the given
+// character, _or_ an HTML space.
+// Otherwise: Checks whether string[pos] is the given character.
+// Returns false if pos is past the end of the string.
+template <typename CharacterType>
+static bool ContainsCharAtPos(const CharacterType* string,
+ const CharacterType* end,
+ int pos,
+ char ch,
+ bool also_accept_whitespace) {
+ DCHECK_GE(pos, 0);
+ if (pos >= static_cast<int>(end - string)) {
+ return false;
+ }
+ return string[pos] == ch ||
+ (also_accept_whitespace && IsHTMLSpace(string[pos]));
+}
+
+// Returns the number of characters consumed for parsing a valid double,
+// or 0 if the string did not start with a valid double.
template <typename CharacterType>
static int ParseDouble(const CharacterType* string,
const CharacterType* end,
- const char terminator,
- const bool terminated_by_space,
double& value) {
- int length =
- CheckForValidDouble(string, end, terminated_by_space, terminator);
- if (!length)
+ int length = FindLengthOfValidDouble(string, end);
+ if (length == 0)
return 0;
int position = 0;
@@ -316,67 +324,136 @@
return length;
}
+// Parse a float and clamp it upwards to max_value. Optimized for having
+// no decimal part.
template <typename CharacterType>
-static bool ParseColorNumberOrPercentage(const CharacterType*& string,
- const CharacterType* end,
- const char terminator,
- bool& should_whitespace_terminate,
- bool is_first_value,
- CSSPrimitiveValue::UnitType& expect,
- int& value) {
+static bool ParseFloatWithMaxValue(const CharacterType*& string,
+ const CharacterType* end,
+ int max_value,
+ double& value,
+ bool& negative) {
+ value = 0.0;
const CharacterType* current = string;
- double local_value = 0;
- bool negative = false;
while (current != end && IsHTMLSpace<CharacterType>(*current))
current++;
if (current != end && *current == '-') {
negative = true;
current++;
+ } else {
+ negative = false;
}
if (current == end || !IsASCIIDigit(*current))
return false;
while (current != end && IsASCIIDigit(*current)) {
- double new_value = local_value * 10 + *current++ - '0';
- if (new_value >= 255) {
- // Clamp values at 255.
- local_value = 255;
+ double new_value = value * 10 + *current++ - '0';
+ if (new_value >= max_value) {
+ // Clamp values at 255 or 100 (depending on the caller).
+ value = max_value;
while (current != end && IsASCIIDigit(*current))
++current;
break;
}
- local_value = new_value;
+ value = new_value;
}
if (current == end)
return false;
- if (expect == CSSPrimitiveValue::UnitType::kNumber && *current == '%')
- return false;
-
if (*current == '.') {
// We already parsed the integral part, try to parse
// the fraction part.
double fractional = 0;
- int num_characters_parsed =
- ParseDouble(current, end, '%', false, fractional);
- if (num_characters_parsed) {
- // Number is a percent.
- current += num_characters_parsed;
- if (*current != '%')
- return false;
- } else {
- // Number is a decimal.
- num_characters_parsed =
- ParseDouble(current, end, terminator, true, fractional);
- if (!num_characters_parsed)
- return false;
- current += num_characters_parsed;
+ int num_characters_parsed = ParseDouble(current, end, fractional);
+ if (num_characters_parsed == 0) {
+ return false;
}
- local_value += fractional;
+ current += num_characters_parsed;
+ value += fractional;
}
+ string = current;
+ return true;
+}
+
+namespace {
+
+enum TerminatorStatus {
+ // List elements are delimited with whitespace,
+ // e.g., rgb(10 20 30).
+ kMustWhitespaceTerminate,
+
+ // List elements are delimited with a given terminator,
+ // and any whitespace before it should be skipped over,
+ // e.g., rgb(10 , 20,30).
+ kMustCharacterTerminate,
+
+ // We are parsing the first element, so we could do either
+ // variant -- and when it's an in/out argument, we set it
+ // to one of the other values.
+ kCouldWhitespaceTerminate,
+};
+
+} // namespace
+
+template <typename CharacterType>
+static bool SkipToTerminator(const CharacterType*& string,
+ const CharacterType* end,
+ const char terminator,
+ TerminatorStatus& terminator_status) {
+ const CharacterType* current = string;
+
+ while (current != end && IsHTMLSpace<CharacterType>(*current))
+ current++;
+
+ switch (terminator_status) {
+ case kCouldWhitespaceTerminate:
+ if (current != end && *current == terminator) {
+ terminator_status = kMustCharacterTerminate;
+ ++current;
+ break;
+ }
+ terminator_status = kMustWhitespaceTerminate;
+ [[fallthrough]];
+ case kMustWhitespaceTerminate:
+ // We must have skipped over at least one space before finding
+ // something else (or the end).
+ if (current == string) {
+ return false;
+ }
+ break;
+ case kMustCharacterTerminate:
+ // We must have stopped at the given terminator character.
+ if (current == end || *current != terminator) {
+ return false;
+ }
+ ++current; // Skip over the terminator.
+ break;
+ }
+
+ string = current;
+ return true;
+}
+
+template <typename CharacterType>
+static bool ParseColorNumberOrPercentage(const CharacterType*& string,
+ const CharacterType* end,
+ const char terminator,
+ TerminatorStatus& terminator_status,
+ CSSPrimitiveValue::UnitType& expect,
+ int& value) {
+ const CharacterType* current = string;
+ double local_value;
+ bool negative = false;
+ if (!ParseFloatWithMaxValue<CharacterType>(current, end, 255, local_value,
+ negative))
+ return false;
+ if (current == end)
+ return false;
+
if (expect == CSSPrimitiveValue::UnitType::kPercentage && *current != '%')
return false;
+ if (expect == CSSPrimitiveValue::UnitType::kNumber && *current == '%')
+ return false;
if (*current == '%') {
expect = CSSPrimitiveValue::UnitType::kPercentage;
@@ -389,22 +466,8 @@
expect = CSSPrimitiveValue::UnitType::kNumber;
}
- while (current != end && IsHTMLSpace<CharacterType>(*current))
- current++;
-
- if (current == end || *current != terminator) {
- if (!should_whitespace_terminate ||
- !IsHTMLSpace<CharacterType>(*(current - 1))) {
- return false;
- }
- } else if (should_whitespace_terminate && is_first_value) {
- should_whitespace_terminate = false;
- } else if (should_whitespace_terminate) {
+ if (!SkipToTerminator(current, end, terminator, terminator_status))
return false;
- }
-
- if (!should_whitespace_terminate)
- current++;
// Clamp negative values at zero.
value = negative ? 0 : static_cast<int>(round(local_value));
@@ -412,6 +475,38 @@
return true;
}
+// Parses a percentage (including the % sign), clamps it and converts it to
+// 0.0..1.0.
+template <typename CharacterType>
+static bool ParsePercentage(const CharacterType*& string,
+ const CharacterType* end,
+ const char terminator,
+ TerminatorStatus& terminator_status,
+ double& value) {
+ const CharacterType* current = string;
+ bool negative = false;
+ if (!ParseFloatWithMaxValue<CharacterType>(current, end, 100, value,
+ negative)) {
+ return false;
+ }
+
+ if (*current != '%')
+ return false;
+
+ ++current;
+ if (negative) {
+ value = 0.0;
+ } else {
+ value = std::min(value * 0.01, 1.0);
+ }
+
+ if (!SkipToTerminator(current, end, terminator, terminator_status))
+ return false;
+
+ string = current;
+ return true;
+}
+
template <typename CharacterType>
static inline bool IsTenthAlpha(const CharacterType* string,
const wtf_size_t length) {
@@ -452,7 +547,9 @@
return false;
if (string[0] != '0' && string[0] != '1' && string[0] != '.') {
- if (CheckForValidDouble(string, end, false, terminator)) {
+ int length = FindLengthOfValidDouble(string, end);
+ if (length > 0 && ContainsCharAtPos(string, end, length, terminator,
+ /*also_accept_whitespace=*/false)) {
value = negative ? 0 : 255;
string = end;
return true;
@@ -477,8 +574,11 @@
}
double alpha = 0;
- if (!ParseDouble(string, end, terminator, false, alpha))
+ int dbl_length = ParseDouble(string, end, alpha);
+ if (dbl_length == 0 || !ContainsCharAtPos(string, end, dbl_length, terminator,
+ /*also_accept_whitespace=*/false)) {
return false;
+ }
value = negative ? 0 : static_cast<int>(round(std::min(alpha, 1.0) * 255.0));
string = end;
return true;
@@ -498,12 +598,23 @@
}
template <typename CharacterType>
+static inline bool MightBeHSLOrHSLA(const CharacterType* characters,
+ unsigned length) {
+ if (length < 5)
+ return false;
+ return IsASCIIAlphaCaselessEqual(characters[0], 'h') &&
+ IsASCIIAlphaCaselessEqual(characters[1], 's') &&
+ IsASCIIAlphaCaselessEqual(characters[2], 'l') &&
+ (characters[3] == '(' ||
+ (IsASCIIAlphaCaselessEqual(characters[3], 'a') &&
+ characters[4] == '('));
+}
+
+template <typename CharacterType>
static bool FastParseColorInternal(RGBA32& rgb,
const CharacterType* characters,
unsigned length,
bool quirks_mode) {
- CSSPrimitiveValue::UnitType expect = CSSPrimitiveValue::UnitType::kUnknown;
-
if (length >= 4 && characters[0] == '#')
return Color::ParseHexColor(characters + 1, length - 1, rgb);
@@ -512,7 +623,7 @@
return true;
}
- // rgb() and rgba() have the same syntax
+ // rgb() and rgba() have the same syntax.
if (MightBeRGBOrRGBA(characters, length)) {
int length_to_add = IsASCIIAlphaCaselessEqual(characters[3], 'a') ? 5 : 4;
const CharacterType* current = characters + length_to_add;
@@ -522,35 +633,35 @@
int blue;
int alpha;
bool should_have_alpha = false;
- bool should_whitespace_terminate = true;
- bool no_whitespace_check = false;
- if (!ParseColorNumberOrPercentage(current, end, ',',
- should_whitespace_terminate,
- true /* is_first_value */, expect, red))
+ TerminatorStatus terminator_status = kCouldWhitespaceTerminate;
+ CSSPrimitiveValue::UnitType expect = CSSPrimitiveValue::UnitType::kUnknown;
+ if (!ParseColorNumberOrPercentage(current, end, ',', terminator_status,
+ expect, red)) {
return false;
- if (!ParseColorNumberOrPercentage(
- current, end, ',', should_whitespace_terminate,
- false /* is_first_value */, expect, green))
+ }
+ if (!ParseColorNumberOrPercentage(current, end, ',', terminator_status,
+ expect, green)) {
return false;
+ }
+
+ TerminatorStatus no_whitespace_check = kMustCharacterTerminate;
if (!ParseColorNumberOrPercentage(current, end, ',', no_whitespace_check,
- false /* is_first_value */, expect,
- blue)) {
- // Might have slash as separator
+ expect, blue)) {
+ // Might have slash as separator.
if (ParseColorNumberOrPercentage(current, end, '/', no_whitespace_check,
- false /* is_first_value */, expect,
- blue)) {
- if (!should_whitespace_terminate)
+ expect, blue)) {
+ if (terminator_status != kMustWhitespaceTerminate)
return false;
should_have_alpha = true;
}
- // Might not have alpha
+ // Might not have alpha.
else if (!ParseColorNumberOrPercentage(
- current, end, ')', no_whitespace_check,
- false /* is_first_value */, expect, blue))
+ current, end, ')', no_whitespace_check, expect, blue)) {
return false;
+ }
} else {
- if (should_whitespace_terminate)
+ if (terminator_status != kMustCharacterTerminate)
return false;
should_have_alpha = true;
}
@@ -558,8 +669,6 @@
if (should_have_alpha) {
if (!ParseAlphaValue(current, end, ')', alpha))
return false;
- if (current != end)
- return false;
rgb = MakeRGBA(red, green, blue, alpha);
} else {
if (current != end)
@@ -569,6 +678,112 @@
return true;
}
+ // hsl() and hsla() also have the same syntax:
+ // https://www.w3.org/TR/css-color-4/#the-hsl-notation
+ // Also for legacy reasons, an hsla() function also exists, with an identical
+ // grammar and behavior to hsl().
+
+ if (MightBeHSLOrHSLA(characters, length)) {
+ int length_to_add = IsASCIIAlphaCaselessEqual(characters[3], 'a') ? 5 : 4;
+ const CharacterType* current = characters + length_to_add;
+ const CharacterType* end = characters + length;
+ bool should_have_alpha = false;
+
+ // Skip any whitespace before the hue.
+ while (current != end && IsHTMLSpace(*current))
+ current++;
+
+ // Find the end of the hue. This isn't optimal, but allows us to reuse
+ // ParseAngle() cleanly.
+ const CharacterType* hue_end = current;
+ while (hue_end != end && !IsHTMLSpace(*hue_end) && *hue_end != ',')
+ hue_end++;
+
+ CSSPrimitiveValue::UnitType hue_unit = CSSPrimitiveValue::UnitType::kNumber;
+ double hue;
+ if (!ParseSimpleAngle(current, static_cast<unsigned>(hue_end - current),
+ hue_unit, hue)) {
+ return false;
+ }
+
+ // We need to convert the hue to the 0..6 scale that MakeRGBAFromHSLA()
+ // expects.
+ switch (hue_unit) {
+ case CSSPrimitiveValue::UnitType::kNumber:
+ case CSSPrimitiveValue::UnitType::kDegrees:
+ // Unitless numbers are to be treated as degrees.
+ hue *= (6.0 / 360.0);
+ break;
+ case CSSPrimitiveValue::UnitType::kRadians:
+ hue = Rad2deg(hue) * (6.0 / 360.0);
+ break;
+ case CSSPrimitiveValue::UnitType::kGradians:
+ hue = Grad2deg(hue) * (6.0 / 360.0);
+ break;
+ case CSSPrimitiveValue::UnitType::kTurns:
+ hue *= 6.0;
+ break;
+ default:
+ NOTREACHED();
+ return false;
+ }
+
+ // Deal with wraparound so that we end up in 0..6,
+ // roughly analogous to the code in ParseHSLParameters().
+ // Taking these branches should be rare.
+ if (hue < 0.0) {
+ hue = fmod(hue, 6.0) + 6.0;
+ } else if (hue > 6.0) {
+ hue = fmod(hue, 6.0);
+ }
+
+ current = hue_end;
+
+ TerminatorStatus terminator_status = kCouldWhitespaceTerminate;
+ if (!SkipToTerminator(current, end, ',', terminator_status))
+ return false;
+
+ // Saturation and lightness must always be percentages.
+ double saturation;
+ if (!ParsePercentage(current, end, ',', terminator_status, saturation))
+ return false;
+
+ TerminatorStatus no_whitespace_check = kMustCharacterTerminate;
+
+ double lightness;
+ if (!ParsePercentage(current, end, ',', no_whitespace_check, lightness)) {
+ // Might have slash as separator.
+ if (ParsePercentage(current, end, '/', no_whitespace_check, lightness)) {
+ if (terminator_status != kMustWhitespaceTerminate)
+ return false;
+ should_have_alpha = true;
+ }
+ // Might not have alpha.
+ else if (!ParsePercentage(current, end, ')', no_whitespace_check,
+ lightness)) {
+ return false;
+ }
+ } else {
+ if (terminator_status != kMustCharacterTerminate)
+ return false;
+ should_have_alpha = true;
+ }
+
+ if (should_have_alpha) {
+ int alpha;
+ if (!ParseAlphaValue(current, end, ')', alpha))
+ return false;
+ if (current != end)
+ return false;
+ rgb = MakeRGBAFromHSLA(hue, saturation, lightness, alpha * (1.0 / 255.0));
+ } else {
+ if (current != end)
+ return false;
+ rgb = MakeRGBAFromHSLA(hue, saturation, lightness, 1.0);
+ }
+ return true;
+ }
+
return false;
}
@@ -590,7 +805,7 @@
bool quirks_mode = IsQuirksModeBehavior(parser_mode) &&
ColorPropertyAllowsQuirkyColor(property_id);
- // Fast path for hex colors and rgb()/rgba() colors
+ // Fast path for hex colors and rgb()/rgba()/hsl()/hsla() colors.
bool parse_result =
WTF::VisitCharacters(string, [&](const auto* chars, unsigned length) {
return FastParseColorInternal(color, chars, length, quirks_mode);
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_fast_paths_test.cc b/third_party/blink/renderer/core/css/parser/css_parser_fast_paths_test.cc
index 6530151..dde37e8 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser_fast_paths_test.cc
+++ b/third_party/blink/renderer/core/css/parser/css_parser_fast_paths_test.cc
@@ -175,7 +175,7 @@
TEST(CSSParserFastPathsTest, ParseColorWithLargeAlpha) {
CSSValue* value = CSSParserFastPaths::ParseColor("rgba(0,0,0,1893205797.13)",
kHTMLStandardMode);
- EXPECT_NE(nullptr, value);
+ ASSERT_NE(nullptr, value);
EXPECT_TRUE(value->IsColorValue());
EXPECT_EQ(Color::kBlack, To<cssvalue::CSSColor>(*value).Value());
}
@@ -183,27 +183,27 @@
TEST(CSSParserFastPathsTest, ParseColorWithNewSyntax) {
CSSValue* value =
CSSParserFastPaths::ParseColor("rgba(0 0 0)", kHTMLStandardMode);
- EXPECT_NE(nullptr, value);
+ ASSERT_NE(nullptr, value);
EXPECT_TRUE(value->IsColorValue());
EXPECT_EQ(Color::kBlack, To<cssvalue::CSSColor>(*value).Value());
value = CSSParserFastPaths::ParseColor("rgba(0 0 0 / 1)", kHTMLStandardMode);
- EXPECT_NE(nullptr, value);
+ ASSERT_NE(nullptr, value);
EXPECT_TRUE(value->IsColorValue());
EXPECT_EQ(Color::kBlack, To<cssvalue::CSSColor>(*value).Value());
value = CSSParserFastPaths::ParseColor("rgba(0, 0, 0, 1)", kHTMLStandardMode);
- EXPECT_NE(nullptr, value);
+ ASSERT_NE(nullptr, value);
EXPECT_TRUE(value->IsColorValue());
EXPECT_EQ(Color::kBlack, To<cssvalue::CSSColor>(*value).Value());
value = CSSParserFastPaths::ParseColor("RGBA(0 0 0 / 1)", kHTMLStandardMode);
- EXPECT_NE(nullptr, value);
+ ASSERT_NE(nullptr, value);
EXPECT_TRUE(value->IsColorValue());
EXPECT_EQ(Color::kBlack, To<cssvalue::CSSColor>(*value).Value());
value = CSSParserFastPaths::ParseColor("RGB(0 0 0 / 1)", kHTMLStandardMode);
- EXPECT_NE(nullptr, value);
+ ASSERT_NE(nullptr, value);
EXPECT_TRUE(value->IsColorValue());
EXPECT_EQ(Color::kBlack, To<cssvalue::CSSColor>(*value).Value());
@@ -224,29 +224,148 @@
TEST(CSSParserFastPathsTest, ParseColorWithDecimal) {
CSSValue* value = CSSParserFastPaths::ParseColor("rgba(0.0, 0.0, 0.0, 1.0)",
kHTMLStandardMode);
- EXPECT_NE(nullptr, value);
+ ASSERT_NE(nullptr, value);
EXPECT_TRUE(value->IsColorValue());
EXPECT_EQ(Color::kBlack, To<cssvalue::CSSColor>(*value).Value());
value =
CSSParserFastPaths::ParseColor("rgb(0.0, 0.0, 0.0)", kHTMLStandardMode);
- EXPECT_NE(nullptr, value);
+ ASSERT_NE(nullptr, value);
EXPECT_TRUE(value->IsColorValue());
EXPECT_EQ(Color::kBlack, To<cssvalue::CSSColor>(*value).Value());
value =
CSSParserFastPaths::ParseColor("rgb(0.0 , 0.0,0.0)", kHTMLStandardMode);
- EXPECT_NE(nullptr, value);
+ ASSERT_NE(nullptr, value);
EXPECT_TRUE(value->IsColorValue());
EXPECT_EQ(Color::kBlack, To<cssvalue::CSSColor>(*value).Value());
value = CSSParserFastPaths::ParseColor("rgb(254.5, 254.5, 254.5)",
kHTMLStandardMode);
- EXPECT_NE(nullptr, value);
+ ASSERT_NE(nullptr, value);
EXPECT_TRUE(value->IsColorValue());
EXPECT_EQ(Color::kWhite, To<cssvalue::CSSColor>(*value).Value());
}
+TEST(CSSParserFastPathsTest, ParseHSL) {
+ CSSValue* value =
+ CSSParserFastPaths::ParseColor("hsl(90deg, 50%, 25%)", kHTMLStandardMode);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->IsColorValue());
+ EXPECT_EQ("rgb(64, 96, 32)", value->CssText());
+
+ // Implicit “deg” angle.
+ value =
+ CSSParserFastPaths::ParseColor("hsl(180, 50%, 50%)", kHTMLStandardMode);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->IsColorValue());
+ EXPECT_EQ("rgb(64, 191, 191)", value->CssText());
+
+ // turn.
+ value = CSSParserFastPaths::ParseColor("hsl(0.25turn, 25%, 50%)",
+ kHTMLStandardMode);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->IsColorValue());
+ EXPECT_EQ("rgb(128, 159, 96)", value->CssText());
+
+ // rad.
+ value = CSSParserFastPaths::ParseColor("hsl(1.0rad, 50%, 50%)",
+ kHTMLStandardMode);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->IsColorValue());
+ EXPECT_EQ("rgb(191, 186, 64)", value->CssText());
+
+ // Wraparound.
+ value = CSSParserFastPaths::ParseColor("hsl(450deg, 50%, 50%)",
+ kHTMLStandardMode);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->IsColorValue());
+ EXPECT_EQ("rgb(128, 191, 64)", value->CssText());
+
+ // Lots of wraparound.
+ value = CSSParserFastPaths::ParseColor("hsl(4050deg, 50%, 50%)",
+ kHTMLStandardMode);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->IsColorValue());
+ EXPECT_EQ("rgb(128, 191, 64)", value->CssText());
+
+ // Negative wraparound.
+ value = CSSParserFastPaths::ParseColor("hsl(-270deg, 50%, 50%)",
+ kHTMLStandardMode);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->IsColorValue());
+ EXPECT_EQ("rgb(128, 191, 64)", value->CssText());
+
+ // Saturation clamping.
+ value = CSSParserFastPaths::ParseColor("hsl(45deg, 150%, 50%)",
+ kHTMLStandardMode);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->IsColorValue());
+ EXPECT_EQ("rgb(255, 191, 0)", value->CssText());
+
+ // Lightness clamping to negative.
+ value = CSSParserFastPaths::ParseColor("hsl(45deg, 150%, -1000%)",
+ kHTMLStandardMode);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->IsColorValue());
+ EXPECT_EQ("rgb(0, 0, 0)", value->CssText());
+
+ // Writing hsla() without alpha.
+ value = CSSParserFastPaths::ParseColor("hsla(45deg, 150%, 50%)",
+ kHTMLStandardMode);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->IsColorValue());
+ EXPECT_EQ("rgb(255, 191, 0)", value->CssText());
+}
+
+TEST(CSSParserFastPathsTest, ParseHSLWithAlpha) {
+ // With alpha, using hsl().
+ CSSValue* value = CSSParserFastPaths::ParseColor("hsl(30 , 1%,75%, 0.5)",
+ kHTMLStandardMode);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->IsColorValue());
+ EXPECT_EQ("rgba(192, 191, 191, 0.5)", value->CssText());
+
+ // With alpha, using hsla().
+ value = CSSParserFastPaths::ParseColor("hsla(30 , 1%,75%, 0.5)",
+ kHTMLStandardMode);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->IsColorValue());
+ EXPECT_EQ("rgba(192, 191, 191, 0.5)", value->CssText());
+
+ // With alpha, using space-separated syntax.
+ value = CSSParserFastPaths::ParseColor("hsla(30 1% 75% / 0.1)",
+ kHTMLStandardMode);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->IsColorValue());
+ EXPECT_EQ("rgba(192, 191, 191, 0.1)", value->CssText());
+
+ // Clamp alpha.
+ value = CSSParserFastPaths::ParseColor("hsla(30 1% 75% / 1.2)",
+ kHTMLStandardMode);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(value->IsColorValue());
+ EXPECT_EQ("rgb(192, 191, 191)", value->CssText());
+}
+
+TEST(CSSParserFastPathsTest, ParseHSLInvalid) {
+ // Invalid unit.
+ EXPECT_EQ(nullptr, CSSParserFastPaths::ParseColor("hsl(20dag, 50%, 20%)",
+ kHTMLStandardMode));
+
+ // Mix of new and old space syntax.
+ EXPECT_EQ(nullptr, CSSParserFastPaths::ParseColor("hsl(0.2, 50%, 20% 0.3)",
+ kHTMLStandardMode));
+ EXPECT_EQ(nullptr, CSSParserFastPaths::ParseColor("hsl(0.2, 50%, 20% / 0.3)",
+ kHTMLStandardMode));
+ EXPECT_EQ(nullptr, CSSParserFastPaths::ParseColor("hsl(0.2 50% 20%, 0.3)",
+ kHTMLStandardMode));
+
+ // Junk after percentage.
+ EXPECT_EQ(nullptr, CSSParserFastPaths::ParseColor(
+ "hsl(0.2, 50% foo, 20% 0.3)", kHTMLStandardMode));
+}
+
TEST(CSSParserFastPathsTest, IsValidKeywordPropertyAndValueOverflowClip) {
{
ScopedOverflowClipForTest overflow_clip_feature_enabler(false);