LibWeb/CSS: Parse custom-idents more consistently

These have a few rules that we didn't follow in most cases:
- CSS-wide keywords are not allowed. (inherit, initial, etc)
- `default` is not allowed.
- The above and any other disallowed identifiers must be tested
  case-insensitively.

This introduces a `parse_custom_ident_value()` method, which takes a
list of disallowed identifier names, and handles the above rules.
This commit is contained in:
Sam Atkins 2024-07-26 16:03:01 +01:00 committed by Andreas Kling
parent d3f089dc26
commit 6ae2b8c3d9
Notes: github-actions[bot] 2024-07-27 12:46:26 +00:00
4 changed files with 80 additions and 20 deletions

View file

@ -0,0 +1,16 @@
Before testing: none
badger: badger
none: none
BANANA: BANANA
NONE: none
InHeRiT: none
revert: none
initial: none
unset: none
george: george
REVERT: none
NaCl: NaCl
default: INVALID
string: string
32: INVALID
done: done

View file

@ -0,0 +1,14 @@
<script src="../include.js"></script>
<div id="foo"></div>
<script>
test(() => {
const foo = document.getElementById("foo");
println(`Before testing: ${getComputedStyle(foo).getPropertyValue("animation-name")}`);
const cases = [ 'badger', 'none', 'BANANA', 'NONE', 'InHeRiT', 'revert', 'initial', 'unset', 'george', 'REVERT', 'NaCl', 'default', 'string', '32', 'done' ];
for (const name of cases) {
foo.style.setProperty('animation-name', 'INVALID');
foo.style.setProperty('animation-name', name);
println(`${name}: ${getComputedStyle(foo).getPropertyValue("animation-name")}`);
}
});
</script>

View file

@ -1562,6 +1562,37 @@ RefPtr<StyleValue> Parser::parse_builtin_value(ComponentValue const& component_v
return nullptr;
}
// https://www.w3.org/TR/css-values-4/#custom-idents
RefPtr<CustomIdentStyleValue> Parser::parse_custom_ident_value(TokenStream<ComponentValue>& tokens, std::initializer_list<StringView> blacklist)
{
auto transaction = tokens.begin_transaction();
tokens.skip_whitespace();
auto token = tokens.next_token();
if (!token.is(Token::Type::Ident))
return nullptr;
auto custom_ident = token.token().ident();
// The CSS-wide keywords are not valid <custom-ident>s.
if (is_css_wide_keyword(custom_ident))
return nullptr;
// The default keyword is reserved and is also not a valid <custom-ident>.
if (custom_ident.equals_ignoring_ascii_case("default"sv))
return nullptr;
// Specifications using <custom-ident> must specify clearly what other keywords are excluded from <custom-ident>,
// if any—for example by saying that any pre-defined keywords in that propertys value definition are excluded.
// Excluded keywords are excluded in all ASCII case permutations.
for (auto& value : blacklist) {
if (custom_ident.equals_ignoring_ascii_case(value))
return nullptr;
}
transaction.commit();
return CustomIdentStyleValue::create(custom_ident);
}
RefPtr<CalculatedStyleValue> Parser::parse_calculated_value(ComponentValue const& component_value)
{
if (!component_value.is_function())
@ -2896,7 +2927,7 @@ RefPtr<StyleValue> Parser::parse_color_value(TokenStream<ComponentValue>& tokens
// https://drafts.csswg.org/css-lists-3/#counter-functions
RefPtr<StyleValue> Parser::parse_counter_value(TokenStream<ComponentValue>& tokens)
{
auto parse_counter_name = [](TokenStream<ComponentValue>& tokens) -> Optional<FlyString> {
auto parse_counter_name = [this](TokenStream<ComponentValue>& tokens) -> Optional<FlyString> {
// https://drafts.csswg.org/css-lists-3/#typedef-counter-name
// Counters are referred to in CSS syntax using the <counter-name> type, which represents
// their name as a <custom-ident>. A <counter-name> name cannot match the keyword none;
@ -2904,8 +2935,8 @@ RefPtr<StyleValue> Parser::parse_counter_value(TokenStream<ComponentValue>& toke
auto transaction = tokens.begin_transaction();
tokens.skip_whitespace();
auto& token = tokens.next_token();
if (!token.is(Token::Type::Ident) || token.token().ident() == "none"sv)
auto counter_name = parse_custom_ident_value(tokens, { "none"sv });
if (!counter_name)
return {};
tokens.skip_whitespace();
@ -2913,10 +2944,10 @@ RefPtr<StyleValue> Parser::parse_counter_value(TokenStream<ComponentValue>& toke
return {};
transaction.commit();
return token.token().ident();
return counter_name->custom_ident();
};
auto parse_counter_style = [](TokenStream<ComponentValue>& tokens) -> RefPtr<StyleValue> {
auto parse_counter_style = [this](TokenStream<ComponentValue>& tokens) -> RefPtr<StyleValue> {
// https://drafts.csswg.org/css-counter-styles-3/#typedef-counter-style
// <counter-style> = <counter-style-name> | <symbols()>
// For now we just support <counter-style-name>, found here:
@ -2925,8 +2956,8 @@ RefPtr<StyleValue> Parser::parse_counter_value(TokenStream<ComponentValue>& toke
auto transaction = tokens.begin_transaction();
tokens.skip_whitespace();
auto& token = tokens.next_token();
if (!token.is(Token::Type::Ident) || token.token().ident() == "none"sv)
auto counter_style_name = parse_custom_ident_value(tokens, { "none"sv });
if (!counter_style_name)
return {};
tokens.skip_whitespace();
@ -2934,7 +2965,7 @@ RefPtr<StyleValue> Parser::parse_counter_value(TokenStream<ComponentValue>& toke
return {};
transaction.commit();
return CustomIdentStyleValue::create(token.token().ident());
return counter_style_name.release_nonnull();
};
auto transaction = tokens.begin_transaction();
@ -6507,11 +6538,9 @@ RefPtr<StyleValue> Parser::parse_grid_track_placement(TokenStream<ComponentValue
return true;
return false;
};
auto is_custom_ident = [](auto& token) -> bool {
auto parse_custom_ident = [this](auto& tokens) {
// The <custom-ident> additionally excludes the keywords span and auto.
if (token.is(Token::Type::Ident) && !token.is_ident("span"sv) && !token.is_ident("auto"sv))
return true;
return false;
return parse_custom_ident_value(tokens, { "span"sv, "auto"sv });
};
auto transaction = tokens.begin_transaction();
@ -6519,6 +6548,10 @@ RefPtr<StyleValue> Parser::parse_grid_track_placement(TokenStream<ComponentValue
// FIXME: Handle the single-token case inside the loop instead, so that we can more easily call this from
// `parse_grid_area_shorthand_value()` using a single TokenStream.
if (tokens.remaining_token_count() == 1) {
if (auto custom_ident = parse_custom_ident(tokens)) {
transaction.commit();
return GridTrackPlacementStyleValue::create(GridTrackPlacement::make_line({}, custom_ident->custom_ident().to_string()));
}
auto& token = tokens.next_token();
if (auto maybe_calculated = parse_calculated_value(token); maybe_calculated && maybe_calculated->resolves_to_number()) {
transaction.commit();
@ -6536,10 +6569,6 @@ RefPtr<StyleValue> Parser::parse_grid_track_placement(TokenStream<ComponentValue
transaction.commit();
return GridTrackPlacementStyleValue::create(GridTrackPlacement::make_line(static_cast<int>(token.token().number_value()), {}));
}
if (is_custom_ident(token)) {
transaction.commit();
return GridTrackPlacementStyleValue::create(GridTrackPlacement::make_line({}, token.token().ident().to_string()));
}
return nullptr;
}
@ -6563,10 +6592,10 @@ RefPtr<StyleValue> Parser::parse_grid_track_placement(TokenStream<ComponentValue
span_or_position_value = static_cast<int>(tokens.next_token().token().to_integer());
continue;
}
if (is_custom_ident(token)) {
if (auto custom_ident = parse_custom_ident(tokens)) {
if (!identifier_value.is_empty())
return nullptr;
identifier_value = tokens.next_token().token().ident().to_string();
identifier_value = custom_ident->custom_ident().to_string();
continue;
}
break;
@ -7244,8 +7273,8 @@ Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(Readon
// Custom idents
if (auto property = any_property_accepts_type(property_ids, ValueType::CustomIdent); property.has_value()) {
(void)tokens.next_token();
return PropertyAndValue { *property, CustomIdentStyleValue::create(peek_token.token().ident()) };
if (auto custom_ident = parse_custom_ident_value(tokens, {}))
return PropertyAndValue { *property, custom_ident };
}
}

View file

@ -279,6 +279,7 @@ private:
Optional<PropertyAndValue> parse_css_value_for_properties(ReadonlySpan<PropertyID>, TokenStream<ComponentValue>&);
RefPtr<StyleValue> parse_builtin_value(ComponentValue const&);
RefPtr<CalculatedStyleValue> parse_calculated_value(ComponentValue const&);
RefPtr<CustomIdentStyleValue> parse_custom_ident_value(TokenStream<ComponentValue>&, std::initializer_list<StringView> blacklist);
// NOTE: Implemented in generated code. (GenerateCSSMathFunctions.cpp)
OwnPtr<CalculationNode> parse_math_function(PropertyID, Function const&);
OwnPtr<CalculationNode> parse_a_calc_function_node(Function const&);