LibWeb/CSS: Replace Parser "current property" with a stack of contexts

`current_property_id()` is insufficient to determine if a quirk is
allowed. For example, unitless lengths are allowed in certain
properties, but NOT if they are inside a calc() or other function. It's
also incorrect when we are parsing a longhand inside a shorthand. So
instead, replace that with a stack of value-parsing contexts. For now,
this is either properties or CSS functions, but in future can be
expanded to include media features and other places.

This lets us disallow quirks inside functions, like we're supposed to.

It also lays the groundwork for being able to more easily determine
what type a percentage inside a calculation should become, as this is
based on the same stack of contexts.
This commit is contained in:
Sam Atkins 2025-01-06 12:48:17 +00:00
commit bc00ef8314
Notes: github-actions[bot] 2025-01-13 11:00:39 +00:00
4 changed files with 196 additions and 76 deletions

View file

@ -150,6 +150,8 @@ RefPtr<CSSStyleValue> Parser::parse_linear_gradient_function(TokenStream<Compone
gradient_type = GradientType::WebKit; gradient_type = GradientType::WebKit;
}); });
auto context_guard = push_temporary_value_parsing_context(FunctionContext { function_name });
function_name = consume_if_starts_with(function_name, "repeating-"sv, [&] { function_name = consume_if_starts_with(function_name, "repeating-"sv, [&] {
repeating_gradient = GradientRepeating::Yes; repeating_gradient = GradientRepeating::Yes;
}); });
@ -274,6 +276,7 @@ RefPtr<CSSStyleValue> Parser::parse_conic_gradient_function(TokenStream<Componen
GradientRepeating repeating_gradient = GradientRepeating::No; GradientRepeating repeating_gradient = GradientRepeating::No;
auto function_name = component_value.function().name.bytes_as_string_view(); auto function_name = component_value.function().name.bytes_as_string_view();
auto context_guard = push_temporary_value_parsing_context(FunctionContext { function_name });
function_name = consume_if_starts_with(function_name, "repeating-"sv, [&] { function_name = consume_if_starts_with(function_name, "repeating-"sv, [&] {
repeating_gradient = GradientRepeating::Yes; repeating_gradient = GradientRepeating::Yes;
@ -384,6 +387,7 @@ RefPtr<CSSStyleValue> Parser::parse_radial_gradient_function(TokenStream<Compone
auto repeating_gradient = GradientRepeating::No; auto repeating_gradient = GradientRepeating::No;
auto function_name = component_value.function().name.bytes_as_string_view(); auto function_name = component_value.function().name.bytes_as_string_view();
auto context_guard = push_temporary_value_parsing_context(FunctionContext { function_name });
function_name = consume_if_starts_with(function_name, "repeating-"sv, [&] { function_name = consume_if_starts_with(function_name, "repeating-"sv, [&] {
repeating_gradient = GradientRepeating::Yes; repeating_gradient = GradientRepeating::Yes;

View file

@ -1935,6 +1935,8 @@ RefPtr<CalculatedStyleValue> Parser::parse_calculated_value(ComponentValue const
OwnPtr<CalculationNode> Parser::parse_a_calc_function_node(Function const& function) OwnPtr<CalculationNode> Parser::parse_a_calc_function_node(Function const& function)
{ {
auto context_guard = push_temporary_value_parsing_context(FunctionContext { function.name });
if (function.name.equals_ignoring_ascii_case("calc"sv)) if (function.name.equals_ignoring_ascii_case("calc"sv))
return parse_a_calculation(function.value); return parse_a_calculation(function.value);
@ -1976,13 +1978,9 @@ Optional<Dimension> Parser::parse_dimension(ComponentValue const& component_valu
auto numeric_value = component_value.token().number_value(); auto numeric_value = component_value.token().number_value();
if (numeric_value == 0) if (numeric_value == 0)
return Length::make_px(0); return Length::make_px(0);
if (m_context.in_quirks_mode() && property_has_quirk(m_context.current_property_id(), Quirk::UnitlessLength)) {
// https://quirks.spec.whatwg.org/#quirky-length-value if (context_allows_quirky_length())
// FIXME: Disallow quirk when inside a CSS sub-expression (like `calc()`)
// "The <quirky-length> value must not be supported in arguments to CSS expressions other than the rect()
// expression, and must not be supported in the supports() static method of the CSS interface."
return Length::make_px(CSSPixels::nearest_value_for(numeric_value)); return Length::make_px(CSSPixels::nearest_value_for(numeric_value));
}
} }
return {}; return {};
@ -2663,6 +2661,38 @@ RefPtr<CSSStyleValue> Parser::parse_frequency_percentage_value(TokenStream<Compo
return nullptr; return nullptr;
} }
bool Parser::context_allows_quirky_length() const
{
if (!m_context.in_quirks_mode())
return false;
// https://drafts.csswg.org/css-values-4/#deprecated-quirky-length
// "When CSS is being parsed in quirks mode, <quirky-length> is a type of <length> that is only valid in certain properties:"
// (NOTE: List skipped for brevity; quirks data is assigned in Properties.json)
// "It is not valid in properties that include or reference these properties, such as the background shorthand,
// or inside functional notations such as calc(), except that they must be allowed in rect() in the clip property."
// So, it must be allowed in the top-level ValueParsingContext, and then not disallowed by any child contexts.
Optional<PropertyID> top_level_property;
if (!m_value_context.is_empty()) {
top_level_property = m_value_context.first().visit(
[](PropertyID const& property_id) -> Optional<PropertyID> { return property_id; },
[](auto const&) -> Optional<PropertyID> { return OptionalNone {}; });
}
bool unitless_length_allowed = top_level_property.has_value() && property_has_quirk(top_level_property.value(), Quirk::UnitlessLength);
for (auto i = 1u; i < m_value_context.size() && unitless_length_allowed; i++) {
unitless_length_allowed = m_value_context[i].visit(
[](PropertyID const& property_id) { return property_has_quirk(property_id, Quirk::UnitlessLength); },
[top_level_property](Parser::FunctionContext const& function_context) {
return function_context.name == "rect"sv && top_level_property == PropertyID::Clip;
});
}
return unitless_length_allowed;
}
RefPtr<CSSStyleValue> Parser::parse_length_value(TokenStream<ComponentValue>& tokens) RefPtr<CSSStyleValue> Parser::parse_length_value(TokenStream<ComponentValue>& tokens)
{ {
if (tokens.next_token().is(Token::Type::Dimension)) { if (tokens.next_token().is(Token::Type::Dimension)) {
@ -2682,11 +2712,7 @@ RefPtr<CSSStyleValue> Parser::parse_length_value(TokenStream<ComponentValue>& to
transaction.commit(); transaction.commit();
return LengthStyleValue::create(Length::make_px(0)); return LengthStyleValue::create(Length::make_px(0));
} }
if (m_context.in_quirks_mode() && property_has_quirk(m_context.current_property_id(), Quirk::UnitlessLength)) { if (context_allows_quirky_length()) {
// https://quirks.spec.whatwg.org/#quirky-length-value
// FIXME: Disallow quirk when inside a CSS sub-expression (like `calc()`)
// "The <quirky-length> value must not be supported in arguments to CSS expressions other than the rect()
// expression, and must not be supported in the supports() static method of the CSS interface."
transaction.commit(); transaction.commit();
return LengthStyleValue::create(Length::make_px(CSSPixels::nearest_value_for(numeric_value))); return LengthStyleValue::create(Length::make_px(CSSPixels::nearest_value_for(numeric_value)));
} }
@ -2733,11 +2759,7 @@ RefPtr<CSSStyleValue> Parser::parse_length_percentage_value(TokenStream<Componen
transaction.commit(); transaction.commit();
return LengthStyleValue::create(Length::make_px(0)); return LengthStyleValue::create(Length::make_px(0));
} }
if (m_context.in_quirks_mode() && property_has_quirk(m_context.current_property_id(), Quirk::UnitlessLength)) { if (context_allows_quirky_length()) {
// https://quirks.spec.whatwg.org/#quirky-length-value
// FIXME: Disallow quirk when inside a CSS sub-expression (like `calc()`)
// "The <quirky-length> value must not be supported in arguments to CSS expressions other than the rect()
// expression, and must not be supported in the supports() static method of the CSS interface."
transaction.commit(); transaction.commit();
return LengthStyleValue::create(Length::make_px(CSSPixels::nearest_value_for(numeric_value))); return LengthStyleValue::create(Length::make_px(CSSPixels::nearest_value_for(numeric_value)));
} }
@ -2853,6 +2875,8 @@ RefPtr<CSSStyleValue> Parser::parse_rect_value(TokenStream<ComponentValue>& toke
if (!function_token.is_function("rect"sv)) if (!function_token.is_function("rect"sv))
return nullptr; return nullptr;
auto context_guard = push_temporary_value_parsing_context(FunctionContext { "rect"sv });
Vector<Length, 4> params; Vector<Length, 4> params;
auto argument_tokens = TokenStream { function_token.function().value }; auto argument_tokens = TokenStream { function_token.function().value };
@ -2988,6 +3012,8 @@ RefPtr<CSSStyleValue> Parser::parse_rgb_color_value(TokenStream<ComponentValue>&
if (!function_token.is_function("rgb"sv) && !function_token.is_function("rgba"sv)) if (!function_token.is_function("rgb"sv) && !function_token.is_function("rgba"sv))
return {}; return {};
auto context_guard = push_temporary_value_parsing_context(FunctionContext { function_token.function().name });
RefPtr<CSSStyleValue> red; RefPtr<CSSStyleValue> red;
RefPtr<CSSStyleValue> green; RefPtr<CSSStyleValue> green;
RefPtr<CSSStyleValue> blue; RefPtr<CSSStyleValue> blue;
@ -3110,6 +3136,8 @@ RefPtr<CSSStyleValue> Parser::parse_hsl_color_value(TokenStream<ComponentValue>&
if (!function_token.is_function("hsl"sv) && !function_token.is_function("hsla"sv)) if (!function_token.is_function("hsl"sv) && !function_token.is_function("hsla"sv))
return {}; return {};
auto context_guard = push_temporary_value_parsing_context(FunctionContext { function_token.function().name });
RefPtr<CSSStyleValue> h; RefPtr<CSSStyleValue> h;
RefPtr<CSSStyleValue> s; RefPtr<CSSStyleValue> s;
RefPtr<CSSStyleValue> l; RefPtr<CSSStyleValue> l;
@ -3211,6 +3239,8 @@ RefPtr<CSSStyleValue> Parser::parse_hwb_color_value(TokenStream<ComponentValue>&
if (!function_token.is_function("hwb"sv)) if (!function_token.is_function("hwb"sv))
return {}; return {};
auto context_guard = push_temporary_value_parsing_context(FunctionContext { function_token.function().name });
RefPtr<CSSStyleValue> h; RefPtr<CSSStyleValue> h;
RefPtr<CSSStyleValue> w; RefPtr<CSSStyleValue> w;
RefPtr<CSSStyleValue> b; RefPtr<CSSStyleValue> b;
@ -3444,6 +3474,8 @@ RefPtr<CSSStyleValue> Parser::parse_color_function(TokenStream<ComponentValue>&
if (!function_token.is_function("color"sv)) if (!function_token.is_function("color"sv))
return {}; return {};
auto context_guard = push_temporary_value_parsing_context(FunctionContext { function_token.function().name });
auto inner_tokens = TokenStream { function_token.function().value }; auto inner_tokens = TokenStream { function_token.function().value };
inner_tokens.discard_whitespace(); inner_tokens.discard_whitespace();
@ -3579,66 +3611,89 @@ RefPtr<CSSStyleValue> Parser::parse_color_value(TokenStream<ComponentValue>& tok
return {}; return {};
} }
// https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk // https://drafts.csswg.org/css-color-4/#quirky-color
if (m_context.in_quirks_mode() && property_has_quirk(m_context.current_property_id(), Quirk::HashlessHexColor)) { if (m_context.in_quirks_mode()) {
// The value of a quirky color is obtained from the possible component values using the following algorithm, // "When CSS is being parsed in quirks mode, <quirky-color> is a type of <color> that is only valid in certain properties:"
// aborting on the first step that returns a value: // (NOTE: List skipped for brevity; quirks data is assigned in Properties.json)
// "It is not valid in properties that include or reference these properties, such as the background shorthand,
// or inside functional notations such as color-mix()"
// 1. Let cv be the component value. bool quirky_color_allowed = false;
auto const& cv = component_value; if (!m_value_context.is_empty()) {
String serialization; quirky_color_allowed = m_value_context.first().visit(
// 2. If cv is a <number-token> or a <dimension-token>, follow these substeps: [](PropertyID const& property_id) { return property_has_quirk(property_id, Quirk::HashlessHexColor); },
if (cv.is(Token::Type::Number) || cv.is(Token::Type::Dimension)) { [](FunctionContext const&) { return false; });
// 1. If cvs type flag is not "integer", return an error. }
// This means that values that happen to use scientific notation, e.g., 5e5e5e, will fail to parse. for (auto i = 1u; i < m_value_context.size() && quirky_color_allowed; i++) {
if (!cv.token().number().is_integer()) quirky_color_allowed = m_value_context[i].visit(
return {}; [](PropertyID const& property_id) { return property_has_quirk(property_id, Quirk::UnitlessLength); },
[](FunctionContext const&) {
return false;
});
}
if (quirky_color_allowed) {
// NOTE: This algorithm is no longer in the spec, since the concept got moved and renamed. However, it works,
// and so we might as well keep using it.
// 2. If cvs value is less than zero, return an error. // The value of a quirky color is obtained from the possible component values using the following algorithm,
auto value = cv.is(Token::Type::Number) ? cv.token().to_integer() : cv.token().dimension_value_int(); // aborting on the first step that returns a value:
if (value < 0)
return {};
// 3. Let serialization be the serialization of cvs value, as a base-ten integer using digits 0-9 (U+0030 to U+0039) in the shortest form possible. // 1. Let cv be the component value.
StringBuilder serialization_builder; auto const& cv = component_value;
serialization_builder.appendff("{}", value); String serialization;
// 2. If cv is a <number-token> or a <dimension-token>, follow these substeps:
if (cv.is(Token::Type::Number) || cv.is(Token::Type::Dimension)) {
// 1. If cvs type flag is not "integer", return an error.
// This means that values that happen to use scientific notation, e.g., 5e5e5e, will fail to parse.
if (!cv.token().number().is_integer())
return {};
// 4. If cv is a <dimension-token>, append the unit to serialization. // 2. If cvs value is less than zero, return an error.
if (cv.is(Token::Type::Dimension)) auto value = cv.is(Token::Type::Number) ? cv.token().to_integer() : cv.token().dimension_value_int();
serialization_builder.append(cv.token().dimension_unit()); if (value < 0)
return {};
// 5. If serialization consists of fewer than six characters, prepend zeros (U+0030) so that it becomes six characters. // 3. Let serialization be the serialization of cvs value, as a base-ten integer using digits 0-9 (U+0030 to U+0039) in the shortest form possible.
serialization = MUST(serialization_builder.to_string()); StringBuilder serialization_builder;
if (serialization_builder.length() < 6) { serialization_builder.appendff("{}", value);
StringBuilder builder;
for (size_t i = 0; i < (6 - serialization_builder.length()); i++) // 4. If cv is a <dimension-token>, append the unit to serialization.
builder.append('0'); if (cv.is(Token::Type::Dimension))
builder.append(serialization_builder.string_view()); serialization_builder.append(cv.token().dimension_unit());
serialization = MUST(builder.to_string());
// 5. If serialization consists of fewer than six characters, prepend zeros (U+0030) so that it becomes six characters.
serialization = MUST(serialization_builder.to_string());
if (serialization_builder.length() < 6) {
StringBuilder builder;
for (size_t i = 0; i < (6 - serialization_builder.length()); i++)
builder.append('0');
builder.append(serialization_builder.string_view());
serialization = MUST(builder.to_string());
}
} }
} // 3. Otherwise, cv is an <ident-token>; let serialization be cvs value.
// 3. Otherwise, cv is an <ident-token>; let serialization be cvs value. else {
else { if (!cv.is(Token::Type::Ident))
if (!cv.is(Token::Type::Ident)) return {};
serialization = cv.token().ident().to_string();
}
// 4. If serialization does not consist of three or six characters, return an error.
if (serialization.bytes().size() != 3 && serialization.bytes().size() != 6)
return {}; return {};
serialization = cv.token().ident().to_string();
}
// 4. If serialization does not consist of three or six characters, return an error. // 5. If serialization contains any characters not in the range [0-9A-Fa-f] (U+0030 to U+0039, U+0041 to U+0046, U+0061 to U+0066), return an error.
if (serialization.bytes().size() != 3 && serialization.bytes().size() != 6) for (auto c : serialization.bytes_as_string_view()) {
return {}; if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')))
return {};
}
// 5. If serialization contains any characters not in the range [0-9A-Fa-f] (U+0030 to U+0039, U+0041 to U+0046, U+0061 to U+0066), return an error. // 6. Return the concatenation of "#" (U+0023) and serialization.
for (auto c : serialization.bytes_as_string_view()) { auto color = Color::from_string(MUST(String::formatted("#{}", serialization)));
if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))) if (color.has_value()) {
return {}; transaction.commit();
} return CSSColorValue::create_from_color(color.release_value());
}
// 6. Return the concatenation of "#" (U+0023) and serialization.
auto color = Color::from_string(MUST(String::formatted("#{}", serialization)));
if (color.has_value()) {
transaction.commit();
return CSSColorValue::create_from_color(color.release_value());
} }
} }
@ -3759,6 +3814,8 @@ RefPtr<CSSStyleValue> Parser::parse_counter_value(TokenStream<ComponentValue>& t
if (token.is_function("counter"sv)) { if (token.is_function("counter"sv)) {
// counter() = counter( <counter-name>, <counter-style>? ) // counter() = counter( <counter-name>, <counter-style>? )
auto& function = token.function(); auto& function = token.function();
auto context_guard = push_temporary_value_parsing_context(FunctionContext { function.name });
TokenStream function_tokens { function.value }; TokenStream function_tokens { function.value };
auto function_values = parse_a_comma_separated_list_of_component_values(function_tokens); auto function_values = parse_a_comma_separated_list_of_component_values(function_tokens);
if (function_values.is_empty() || function_values.size() > 2) if (function_values.is_empty() || function_values.size() > 2)
@ -3787,6 +3844,8 @@ RefPtr<CSSStyleValue> Parser::parse_counter_value(TokenStream<ComponentValue>& t
if (token.is_function("counters"sv)) { if (token.is_function("counters"sv)) {
// counters() = counters( <counter-name>, <string>, <counter-style>? ) // counters() = counters( <counter-name>, <string>, <counter-style>? )
auto& function = token.function(); auto& function = token.function();
auto context_guard = push_temporary_value_parsing_context(FunctionContext { function.name });
TokenStream function_tokens { function.value }; TokenStream function_tokens { function.value };
auto function_values = parse_a_comma_separated_list_of_component_values(function_tokens); auto function_values = parse_a_comma_separated_list_of_component_values(function_tokens);
if (function_values.size() < 2 || function_values.size() > 3) if (function_values.size() < 2 || function_values.size() > 3)
@ -5649,6 +5708,8 @@ RefPtr<CSSStyleValue> Parser::parse_filter_value_list_value(TokenStream<Componen
auto filter_token = parse_filter_function_name(token.function().name); auto filter_token = parse_filter_function_name(token.function().name);
if (!filter_token.has_value()) if (!filter_token.has_value())
return nullptr; return nullptr;
auto context_guard = push_temporary_value_parsing_context(FunctionContext { token.function().name });
auto filter_function = parse_filter_function(*filter_token, token.function().value); auto filter_function = parse_filter_function(*filter_token, token.function().value);
if (!filter_function.has_value()) if (!filter_function.has_value())
return nullptr; return nullptr;
@ -6654,6 +6715,8 @@ Vector<ParsedFontFace::Source> Parser::parse_font_face_src(TokenStream<T>& compo
auto const& function = maybe_function.function(); auto const& function = maybe_function.function();
if (function.name.equals_ignoring_ascii_case("format"sv)) { if (function.name.equals_ignoring_ascii_case("format"sv)) {
auto context_guard = push_temporary_value_parsing_context(FunctionContext { function.name });
TokenStream format_tokens { function.value }; TokenStream format_tokens { function.value };
format_tokens.discard_whitespace(); format_tokens.discard_whitespace();
auto const& format_name_token = format_tokens.consume_a_token(); auto const& format_name_token = format_tokens.consume_a_token();
@ -6815,6 +6878,8 @@ RefPtr<CSSStyleValue> Parser::parse_math_depth_value(TokenStream<ComponentValue>
// add(<integer>) // add(<integer>)
if (token.is_function("add"sv)) { if (token.is_function("add"sv)) {
auto context_guard = push_temporary_value_parsing_context(FunctionContext { token.function().name });
auto add_tokens = TokenStream { token.function().value }; auto add_tokens = TokenStream { token.function().value };
add_tokens.discard_whitespace(); add_tokens.discard_whitespace();
auto const& integer_token = add_tokens.consume_a_token(); auto const& integer_token = add_tokens.consume_a_token();
@ -7105,6 +7170,8 @@ RefPtr<CSSStyleValue> Parser::parse_easing_value(TokenStream<ComponentValue>& to
argument.remove_all_matching([](auto& value) { return value.is(Token::Type::Whitespace); }); argument.remove_all_matching([](auto& value) { return value.is(Token::Type::Whitespace); });
auto name = part.function().name; auto name = part.function().name;
auto context_guard = push_temporary_value_parsing_context(FunctionContext { name });
if (name.equals_ignoring_ascii_case("linear"sv)) { if (name.equals_ignoring_ascii_case("linear"sv)) {
// linear() = linear( [ <number> && <percentage>{0,2} ]# ) // linear() = linear( [ <number> && <percentage>{0,2} ]# )
Vector<EasingStyleValue::Linear::Stop> stops; Vector<EasingStyleValue::Linear::Stop> stops;
@ -7255,6 +7322,9 @@ RefPtr<CSSStyleValue> Parser::parse_transform_value(TokenStream<ComponentValue>&
auto maybe_function = transform_function_from_string(part.function().name); auto maybe_function = transform_function_from_string(part.function().name);
if (!maybe_function.has_value()) if (!maybe_function.has_value())
return nullptr; return nullptr;
auto context_guard = push_temporary_value_parsing_context(FunctionContext { part.function().name });
auto function = maybe_function.release_value(); auto function = maybe_function.release_value();
auto function_metadata = transform_function_metadata(function); auto function_metadata = transform_function_metadata(function);
@ -7783,6 +7853,8 @@ Optional<CSS::ExplicitGridTrack> Parser::parse_track_sizing_function(ComponentVa
{ {
if (token.is_function()) { if (token.is_function()) {
auto const& function_token = token.function(); auto const& function_token = token.function();
auto context_guard = push_temporary_value_parsing_context(FunctionContext { function_token.name });
if (function_token.name.equals_ignoring_ascii_case("repeat"sv)) { if (function_token.name.equals_ignoring_ascii_case("repeat"sv)) {
auto maybe_repeat = parse_repeat(function_token.value); auto maybe_repeat = parse_repeat(function_token.value);
if (maybe_repeat.has_value()) if (maybe_repeat.has_value())
@ -8398,7 +8470,8 @@ bool block_contains_var_or_attr(SimpleBlock const& block)
Parser::ParseErrorOr<NonnullRefPtr<CSSStyleValue>> Parser::parse_css_value(PropertyID property_id, TokenStream<ComponentValue>& unprocessed_tokens, Optional<String> original_source_text) Parser::ParseErrorOr<NonnullRefPtr<CSSStyleValue>> Parser::parse_css_value(PropertyID property_id, TokenStream<ComponentValue>& unprocessed_tokens, Optional<String> original_source_text)
{ {
m_context.set_current_property_id(property_id); auto context_guard = push_temporary_value_parsing_context(property_id);
Vector<ComponentValue> component_values; Vector<ComponentValue> component_values;
bool contains_var_or_attr = false; bool contains_var_or_attr = false;
bool const property_accepts_custom_ident = property_accepts_type(property_id, ValueType::CustomIdent); bool const property_accepts_custom_ident = property_accepts_type(property_id, ValueType::CustomIdent);
@ -8824,6 +8897,7 @@ Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(Readon
auto& peek_token = tokens.next_token(); auto& peek_token = tokens.next_token();
if (auto property = any_property_accepts_type(property_ids, ValueType::EasingFunction); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::EasingFunction); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto maybe_easing_function = parse_easing_value(tokens)) if (auto maybe_easing_function = parse_easing_value(tokens))
return PropertyAndValue { *property, maybe_easing_function }; return PropertyAndValue { *property, maybe_easing_function };
} }
@ -8841,68 +8915,82 @@ Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(Readon
// Custom idents // Custom idents
if (auto property = any_property_accepts_type(property_ids, ValueType::CustomIdent); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::CustomIdent); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto custom_ident = parse_custom_ident_value(tokens, {})) if (auto custom_ident = parse_custom_ident_value(tokens, {}))
return PropertyAndValue { *property, custom_ident }; return PropertyAndValue { *property, custom_ident };
} }
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::Color); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::Color); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto maybe_color = parse_color_value(tokens)) if (auto maybe_color = parse_color_value(tokens))
return PropertyAndValue { *property, maybe_color }; return PropertyAndValue { *property, maybe_color };
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::Counter); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::Counter); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto maybe_counter = parse_counter_value(tokens)) if (auto maybe_counter = parse_counter_value(tokens))
return PropertyAndValue { *property, maybe_counter }; return PropertyAndValue { *property, maybe_counter };
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::Image); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::Image); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto maybe_image = parse_image_value(tokens)) if (auto maybe_image = parse_image_value(tokens))
return PropertyAndValue { *property, maybe_image }; return PropertyAndValue { *property, maybe_image };
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::Position); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::Position); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto maybe_position = parse_position_value(tokens)) if (auto maybe_position = parse_position_value(tokens))
return PropertyAndValue { *property, maybe_position }; return PropertyAndValue { *property, maybe_position };
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::BackgroundPosition); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::BackgroundPosition); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto maybe_position = parse_position_value(tokens, PositionParsingMode::BackgroundPosition)) if (auto maybe_position = parse_position_value(tokens, PositionParsingMode::BackgroundPosition))
return PropertyAndValue { *property, maybe_position }; return PropertyAndValue { *property, maybe_position };
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::BasicShape); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::BasicShape); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto maybe_basic_shape = parse_basic_shape_value(tokens)) if (auto maybe_basic_shape = parse_basic_shape_value(tokens))
return PropertyAndValue { *property, maybe_basic_shape }; return PropertyAndValue { *property, maybe_basic_shape };
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::Ratio); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::Ratio); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto maybe_ratio = parse_ratio_value(tokens)) if (auto maybe_ratio = parse_ratio_value(tokens))
return PropertyAndValue { *property, maybe_ratio }; return PropertyAndValue { *property, maybe_ratio };
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::OpenTypeTag); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::OpenTypeTag); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto maybe_rect = parse_opentype_tag_value(tokens)) if (auto maybe_rect = parse_opentype_tag_value(tokens))
return PropertyAndValue { *property, maybe_rect }; return PropertyAndValue { *property, maybe_rect };
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::Rect); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::Rect); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto maybe_rect = parse_rect_value(tokens)) if (auto maybe_rect = parse_rect_value(tokens))
return PropertyAndValue { *property, maybe_rect }; return PropertyAndValue { *property, maybe_rect };
} }
if (peek_token.is(Token::Type::String)) { if (peek_token.is(Token::Type::String)) {
if (auto property = any_property_accepts_type(property_ids, ValueType::String); property.has_value()) if (auto property = any_property_accepts_type(property_ids, ValueType::String); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
return PropertyAndValue { *property, StringStyleValue::create(tokens.consume_a_token().token().string()) }; return PropertyAndValue { *property, StringStyleValue::create(tokens.consume_a_token().token().string()) };
}
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::Url); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::Url); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto url = parse_url_value(tokens)) if (auto url = parse_url_value(tokens))
return PropertyAndValue { *property, url }; return PropertyAndValue { *property, url };
} }
// <integer>/<number> come before <length>, so that 0 is not interpreted as a <length> in case both are allowed. // <integer>/<number> come before <length>, so that 0 is not interpreted as a <length> in case both are allowed.
if (auto property = any_property_accepts_type(property_ids, ValueType::Integer); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::Integer); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto value = parse_integer_value(tokens)) { if (auto value = parse_integer_value(tokens)) {
if (value->is_calculated()) if (value->is_calculated())
return PropertyAndValue { *property, value }; return PropertyAndValue { *property, value };
@ -8912,6 +9000,7 @@ Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(Readon
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::Number); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::Number); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto value = parse_number_value(tokens)) { if (auto value = parse_number_value(tokens)) {
if (value->is_calculated()) if (value->is_calculated())
return PropertyAndValue { *property, value }; return PropertyAndValue { *property, value };
@ -8921,6 +9010,7 @@ Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(Readon
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::Angle); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::Angle); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (property_accepts_type(*property, ValueType::Percentage)) { if (property_accepts_type(*property, ValueType::Percentage)) {
if (auto value = parse_angle_percentage_value(tokens)) { if (auto value = parse_angle_percentage_value(tokens)) {
if (value->is_calculated()) if (value->is_calculated())
@ -8940,6 +9030,7 @@ Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(Readon
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::Flex); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::Flex); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto value = parse_flex_value(tokens)) { if (auto value = parse_flex_value(tokens)) {
if (value->is_calculated()) if (value->is_calculated())
return PropertyAndValue { *property, value }; return PropertyAndValue { *property, value };
@ -8949,6 +9040,7 @@ Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(Readon
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::Frequency); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::Frequency); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (property_accepts_type(*property, ValueType::Percentage)) { if (property_accepts_type(*property, ValueType::Percentage)) {
if (auto value = parse_frequency_percentage_value(tokens)) { if (auto value = parse_frequency_percentage_value(tokens)) {
if (value->is_calculated()) if (value->is_calculated())
@ -8968,6 +9060,7 @@ Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(Readon
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::Length); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::Length); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (property_accepts_type(*property, ValueType::Percentage)) { if (property_accepts_type(*property, ValueType::Percentage)) {
if (auto value = parse_length_percentage_value(tokens)) { if (auto value = parse_length_percentage_value(tokens)) {
if (value->is_calculated()) if (value->is_calculated())
@ -8987,6 +9080,7 @@ Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(Readon
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::Resolution); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::Resolution); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto value = parse_resolution_value(tokens)) { if (auto value = parse_resolution_value(tokens)) {
if (value->is_calculated()) if (value->is_calculated())
return PropertyAndValue { *property, value }; return PropertyAndValue { *property, value };
@ -8996,6 +9090,7 @@ Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(Readon
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::Time); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::Time); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (property_accepts_type(*property, ValueType::Percentage)) { if (property_accepts_type(*property, ValueType::Percentage)) {
if (auto value = parse_time_percentage_value(tokens)) { if (auto value = parse_time_percentage_value(tokens)) {
if (value->is_calculated()) if (value->is_calculated())
@ -9016,6 +9111,7 @@ Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(Readon
// <percentage> is checked after the <foo-percentage> types. // <percentage> is checked after the <foo-percentage> types.
if (auto property = any_property_accepts_type(property_ids, ValueType::Percentage); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::Percentage); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto value = parse_percentage_value(tokens)) { if (auto value = parse_percentage_value(tokens)) {
if (value->is_calculated()) if (value->is_calculated())
return PropertyAndValue { *property, value }; return PropertyAndValue { *property, value };
@ -9025,6 +9121,7 @@ Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(Readon
} }
if (auto property = any_property_accepts_type(property_ids, ValueType::Paint); property.has_value()) { if (auto property = any_property_accepts_type(property_ids, ValueType::Paint); property.has_value()) {
auto context_guard = push_temporary_value_parsing_context(*property);
if (auto value = parse_paint_value(tokens)) if (auto value = parse_paint_value(tokens))
return PropertyAndValue { *property, value.release_nonnull() }; return PropertyAndValue { *property, value.release_nonnull() };
} }
@ -9245,8 +9342,19 @@ OwnPtr<CalculationNode> Parser::convert_to_calculation_node(CalcParsing::Node co
} }
if (component_value->is(Token::Type::Percentage)) { if (component_value->is(Token::Type::Percentage)) {
// FIXME: Figure this out in non-property contexts Optional<ValueType> percentage_resolved_type;
auto percentage_resolved_type = property_resolves_percentages_relative_to(m_context.current_property_id()); for (auto const& value_context : m_value_context.in_reverse()) {
percentage_resolved_type = value_context.visit(
[](PropertyID property_id) -> Optional<ValueType> {
return property_resolves_percentages_relative_to(property_id);
},
[](FunctionContext const&) -> Optional<ValueType> {
// FIXME: Some functions provide this. The spec mentions `media-progress()` as an example.
return {};
});
if (percentage_resolved_type.has_value())
break;
}
return NumericCalculationNode::create(Percentage { component_value->token().percentage() }, percentage_resolved_type); return NumericCalculationNode::create(Percentage { component_value->token().percentage() }, percentage_resolved_type);
} }

View file

@ -443,6 +443,18 @@ private:
Vector<Token> m_tokens; Vector<Token> m_tokens;
TokenStream<Token> m_token_stream; TokenStream<Token> m_token_stream;
struct FunctionContext {
StringView name;
};
using ValueParsingContext = Variant<PropertyID, FunctionContext>;
Vector<ValueParsingContext> m_value_context;
auto push_temporary_value_parsing_context(ValueParsingContext&& context)
{
m_value_context.append(context);
return ScopeGuard { [&] { m_value_context.take_last(); } };
}
bool context_allows_quirky_length() const;
enum class ContextType { enum class ContextType {
Unknown, Unknown,
Style, Style,

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2020-2021, the SerenityOS developers. * Copyright (c) 2020-2021, the SerenityOS developers.
* Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org> * Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -34,9 +34,6 @@ public:
HTML::Window const* window() const; HTML::Window const* window() const;
URL::URL complete_url(StringView) const; URL::URL complete_url(StringView) const;
PropertyID current_property_id() const { return m_current_property_id; }
void set_current_property_id(PropertyID property_id) { m_current_property_id = property_id; }
JS::Realm& realm() const JS::Realm& realm() const
{ {
VERIFY(m_realm); VERIFY(m_realm);
@ -46,7 +43,6 @@ public:
private: private:
GC::Ptr<JS::Realm> m_realm; GC::Ptr<JS::Realm> m_realm;
GC::Ptr<DOM::Document const> m_document; GC::Ptr<DOM::Document const> m_document;
PropertyID m_current_property_id { PropertyID::Invalid };
URL::URL m_url; URL::URL m_url;
Mode m_mode { Mode::Normal }; Mode m_mode { Mode::Normal };
}; };