LibWeb: Clamp CSS <integer> value to i32 at parse time

This matches the behavior of other browsers. Previously we implemented
this at used-value time for z-index specifically.
This commit is contained in:
Callum Law 2026-03-26 21:33:07 +13:00 committed by Jelle Raaijmakers
parent 0ab06e119e
commit b86377b9dc
Notes: github-actions[bot] 2026-03-26 11:31:21 +00:00
24 changed files with 74 additions and 51 deletions

View file

@ -48,7 +48,7 @@ NonnullRefPtr<StyleValue const> FrequencyOrCalculated::create_style_value() cons
return FrequencyStyleValue::create(value());
}
Optional<i64> IntegerOrCalculated::resolve_calculated(NonnullRefPtr<CalculatedStyleValue const> const& calculated, CalculationResolutionContext const& context) const
Optional<i32> IntegerOrCalculated::resolve_calculated(NonnullRefPtr<CalculatedStyleValue const> const& calculated, CalculationResolutionContext const& context) const
{
return calculated->resolve_integer(context);
}

View file

@ -135,11 +135,11 @@ public:
NonnullRefPtr<StyleValue const> create_style_value() const;
};
class IntegerOrCalculated : public CalculatedOr<IntegerOrCalculated, i64> {
class IntegerOrCalculated : public CalculatedOr<IntegerOrCalculated, i32> {
public:
using CalculatedOr::CalculatedOr;
Optional<i64> resolve_calculated(NonnullRefPtr<CalculatedStyleValue const> const&, CalculationResolutionContext const&) const;
Optional<i32> resolve_calculated(NonnullRefPtr<CalculatedStyleValue const> const&, CalculationResolutionContext const&) const;
NonnullRefPtr<StyleValue const> create_style_value() const;
};

View file

@ -19,7 +19,7 @@ NonnullRefPtr<CounterStyle const> CounterStyle::decimal()
CounterStyleNegativeSign { .prefix = "-"_fly_string, .suffix = ""_fly_string },
""_fly_string,
". "_fly_string,
{ { NumericLimits<i64>::min(), NumericLimits<i64>::max() } },
{ { NumericLimits<i32>::min(), NumericLimits<i32>::max() } },
{},
CounterStylePad { .minimum_length = 0, .symbol = ""_fly_string });
}
@ -33,7 +33,7 @@ NonnullRefPtr<CounterStyle const> CounterStyle::disc()
CounterStyleNegativeSign { .prefix = ""_fly_string, .suffix = " "_fly_string },
""_fly_string,
" "_fly_string,
{ { NumericLimits<i64>::min(), NumericLimits<i64>::max() } },
{ { NumericLimits<i32>::min(), NumericLimits<i32>::max() } },
"decimal"_fly_string,
CounterStylePad { .minimum_length = 0, .symbol = ""_fly_string });
}

View file

@ -19,12 +19,12 @@ Vector<CounterStyleRangeEntry> AutoRange::resolve(CounterStyleAlgorithm const& a
return algorithm.visit(
[](AdditiveCounterStyleAlgorithm const&) -> Vector<CounterStyleRangeEntry> {
// For additive systems, the range is 0 to positive infinity.
return { { 0, NumericLimits<i64>::max() } };
return { { 0, NumericLimits<i32>::max() } };
},
[](FixedCounterStyleAlgorithm const&) -> Vector<CounterStyleRangeEntry> {
// For cyclic, numeric, and fixed systems, the range is negative infinity to positive infinity.
// NB: cyclic and numeric are handled below.
return { { NumericLimits<i64>::min(), NumericLimits<i64>::max() } };
return { { NumericLimits<i32>::min(), NumericLimits<i32>::max() } };
},
[](GenericCounterStyleAlgorithm const& generic_algorithm) -> Vector<CounterStyleRangeEntry> {
switch (generic_algorithm.type) {
@ -32,11 +32,11 @@ Vector<CounterStyleRangeEntry> AutoRange::resolve(CounterStyleAlgorithm const& a
case CounterStyleSystem::Numeric:
// For cyclic, numeric, and fixed systems, the range is negative infinity to positive infinity.
// NB: Fixed is handled above.
return { { NumericLimits<i64>::min(), NumericLimits<i64>::max() } };
return { { NumericLimits<i32>::min(), NumericLimits<i32>::max() } };
case CounterStyleSystem::Alphabetic:
case CounterStyleSystem::Symbolic:
// For alphabetic and symbolic systems, the range is 1 to positive infinity.
return { { 1, NumericLimits<i64>::max() } };
return { { 1, NumericLimits<i32>::max() } };
case CounterStyleSystem::Additive:
// NB: Additive is handled above.
VERIFY_NOT_REACHED();
@ -129,7 +129,7 @@ Variant<Empty, CounterStyleAlgorithm, CounterStyleSystemStyleValue::Extends> Cou
// https://drafts.csswg.org/css-counter-styles-3/#fixed-system
// If it is omitted, the first symbol value is 1.
i64 first_symbol = 1;
i32 first_symbol = 1;
if (fixed.first_symbol)
first_symbol = int_from_style_value(fixed.first_symbol->absolutized(computation_context));
@ -216,14 +216,14 @@ Variant<AutoRange, Vector<CounterStyleRangeEntry>> CounterStyleDefinition::resol
auto const& range_values = entry->as_value_list().values();
VERIFY(range_values.size() == 2);
auto const resolve_value = [&](NonnullRefPtr<StyleValue const> const& value, i64 infinite_value) {
auto const resolve_value = [&](NonnullRefPtr<StyleValue const> const& value, i32 infinite_value) {
if (value->is_keyword() && value->to_keyword() == Keyword::Infinite)
return infinite_value;
return int_from_style_value(value->absolutized(computation_context));
};
ranges.unchecked_append({ resolve_value(range_values[0], NumericLimits<i64>::min()), resolve_value(range_values[1], NumericLimits<i64>::max()) });
ranges.unchecked_append({ resolve_value(range_values[0], NumericLimits<i32>::min()), resolve_value(range_values[1], NumericLimits<i32>::max()) });
}
return ranges;

View file

@ -11,8 +11,8 @@
namespace Web::CSS {
struct CounterStyleRangeEntry {
i64 start;
i64 end;
i32 start;
i32 end;
};
// https://drafts.csswg.org/css-counter-styles-3/#counter-style-symbols
@ -42,7 +42,7 @@ struct AdditiveCounterStyleAlgorithm {
};
struct FixedCounterStyleAlgorithm {
i64 first_symbol;
i32 first_symbol;
Vector<CounterStyleSymbol> symbol_list;
};

View file

@ -33,7 +33,7 @@ struct CubicBezierEasingFunction {
};
struct StepsEasingFunction {
i64 interval_count;
i32 interval_count;
StepPosition position;
String stringified;

View file

@ -74,7 +74,7 @@ public:
return Resolution::from_style_value(m_value->absolutized(computation_context));
}
i64 integer(ComputationContext const& computation_context) const
i32 integer(ComputationContext const& computation_context) const
{
VERIFY(is_integer());
return int_from_style_value(m_value->absolutized(computation_context));

View file

@ -10,7 +10,7 @@
namespace Web::CSS {
i64 round_to_nearest_integer(double value)
i32 round_to_nearest_integer(double value)
{
// https://drafts.csswg.org/css-values-4/#css-round-to-the-nearest-integer
// Unless otherwise specified, in the CSS specifications rounding to the nearest integer requires rounding in
@ -20,12 +20,12 @@ i64 round_to_nearest_integer(double value)
if (isinf(value)) {
if (value > 0)
return AK::NumericLimits<i64>::max();
return AK::NumericLimits<i32>::max();
return AK::NumericLimits<i64>::min();
return AK::NumericLimits<i32>::min();
}
return AK::clamp_to<i64>(floor(value + 0.5));
return AK::clamp_to<i32>(floor(value + 0.5));
}
void Number::serialize(StringBuilder& builder, SerializationMode) const

View file

@ -14,7 +14,7 @@
namespace Web::CSS {
i64 round_to_nearest_integer(double);
i32 round_to_nearest_integer(double);
class Number {
public:
@ -37,7 +37,7 @@ public:
Type type() const { return m_type; }
double value() const { return m_value; }
i64 integer_value() const
i32 integer_value() const
{
// https://www.w3.org/TR/css-values-4/#numeric-types
// When a value cannot be explicitly supported due to range/precision limitations, it must be converted

View file

@ -165,13 +165,13 @@ ParsedFontFace ParsedFontFace::from_descriptors(CSSFontFaceDescriptors const& de
font_language_override = value->as_string().string_value();
}
Optional<OrderedHashMap<FlyString, i64>> font_feature_settings;
Optional<OrderedHashMap<FlyString, i32>> font_feature_settings;
if (auto value = descriptors.descriptor_or_initial_value(DescriptorID::FontFeatureSettings)) {
if (value->to_keyword() == Keyword::Normal) {
font_feature_settings.clear();
} else if (value->is_value_list()) {
auto const& feature_tags = value->as_value_list().values();
OrderedHashMap<FlyString, i64> settings;
OrderedHashMap<FlyString, i32> settings;
settings.ensure_capacity(feature_tags.size());
for (auto const& feature_tag_style_value : feature_tags) {
auto const& feature_tag = feature_tag_style_value->as_open_type_tagged();
@ -219,7 +219,7 @@ ParsedFontFace ParsedFontFace::from_descriptors(CSSFontFaceDescriptors const& de
};
}
ParsedFontFace::ParsedFontFace(GC::Ref<CSSRule> parent_rule, FlyString font_family, Optional<FontWeightRange> weight, Optional<int> slope, Optional<int> width, Vector<Source> sources, Vector<Gfx::UnicodeRange> unicode_ranges, Optional<Percentage> ascent_override, Optional<Percentage> descent_override, Optional<Percentage> line_gap_override, FontDisplay font_display, Optional<FlyString> font_named_instance, Optional<FlyString> font_language_override, Optional<OrderedHashMap<FlyString, i64>> font_feature_settings, Optional<OrderedHashMap<FlyString, double>> font_variation_settings)
ParsedFontFace::ParsedFontFace(GC::Ref<CSSRule> parent_rule, FlyString font_family, Optional<FontWeightRange> weight, Optional<int> slope, Optional<int> width, Vector<Source> sources, Vector<Gfx::UnicodeRange> unicode_ranges, Optional<Percentage> ascent_override, Optional<Percentage> descent_override, Optional<Percentage> line_gap_override, FontDisplay font_display, Optional<FlyString> font_named_instance, Optional<FlyString> font_language_override, Optional<OrderedHashMap<FlyString, i32>> font_feature_settings, Optional<OrderedHashMap<FlyString, double>> font_variation_settings)
: m_parent_rule(parent_rule)
, m_font_family(move(font_family))
, m_font_named_instance(move(font_named_instance))

View file

@ -27,7 +27,7 @@ public:
static Vector<Source> sources_from_style_value(StyleValue const&);
static ParsedFontFace from_descriptors(CSSFontFaceDescriptors const&);
ParsedFontFace(GC::Ref<CSSRule> parent_rule, FlyString font_family, Optional<FontWeightRange> weight, Optional<int> slope, Optional<int> width, Vector<Source> sources, Vector<Gfx::UnicodeRange> unicode_ranges, Optional<Percentage> ascent_override, Optional<Percentage> descent_override, Optional<Percentage> line_gap_override, FontDisplay font_display, Optional<FlyString> font_named_instance, Optional<FlyString> font_language_override, Optional<OrderedHashMap<FlyString, i64>> font_feature_settings, Optional<OrderedHashMap<FlyString, double>> font_variation_settings);
ParsedFontFace(GC::Ref<CSSRule> parent_rule, FlyString font_family, Optional<FontWeightRange> weight, Optional<int> slope, Optional<int> width, Vector<Source> sources, Vector<Gfx::UnicodeRange> unicode_ranges, Optional<Percentage> ascent_override, Optional<Percentage> descent_override, Optional<Percentage> line_gap_override, FontDisplay font_display, Optional<FlyString> font_named_instance, Optional<FlyString> font_language_override, Optional<OrderedHashMap<FlyString, i32>> font_feature_settings, Optional<OrderedHashMap<FlyString, double>> font_variation_settings);
~ParsedFontFace() = default;
GC::Ref<CSSRule> parent_rule() const { return m_parent_rule; }
@ -35,7 +35,7 @@ public:
Optional<Percentage> descent_override() const { return m_descent_override; }
FontDisplay font_display() const { return m_font_display; }
FlyString const& font_family() const { return m_font_family; }
Optional<OrderedHashMap<FlyString, i64>> font_feature_settings() const { return m_font_feature_settings; }
Optional<OrderedHashMap<FlyString, i32>> font_feature_settings() const { return m_font_feature_settings; }
Optional<FlyString> font_language_override() const { return m_font_language_override; }
Optional<FlyString> font_named_instance() const { return m_font_named_instance; }
Optional<OrderedHashMap<FlyString, double>> font_variation_settings() const { return m_font_variation_settings; }
@ -60,7 +60,7 @@ private:
Optional<Percentage> m_line_gap_override;
FontDisplay m_font_display;
Optional<FlyString> m_font_language_override;
Optional<OrderedHashMap<FlyString, i64>> m_font_feature_settings;
Optional<OrderedHashMap<FlyString, i32>> m_font_feature_settings;
Optional<OrderedHashMap<FlyString, double>> m_font_variation_settings;
};

View file

@ -271,7 +271,7 @@ static Vector<ComponentValue> replace_an_env_function(DOM::AbstractElement& elem
auto& name = name_token.token().ident();
first_argument_tokens.discard_whitespace();
Vector<i64> indices;
Vector<i32> indices;
// FIXME: Are non-literal <integer>s allowed here?
while (first_argument_tokens.has_next_token()) {
auto& maybe_integer = first_argument_tokens.consume_a_token();

View file

@ -83,12 +83,12 @@ Parser::ParseErrorOr<NonnullRefPtr<StyleValue const>> Parser::parse_descriptor_v
// of a counter symbol and an integer weight. Each weight must be a non-negative integer, and the
// additive tuples must be specified in order of strictly descending weight; otherwise, the
// declaration is invalid and must be ignored.
i64 previous_weight = NumericLimits<i64>::max();
i32 previous_weight = NumericLimits<i32>::max();
for (auto const& tuple_style_value : additive_tuples->as_value_list().values()) {
auto const& weight = tuple_style_value->as_value_list().value_at(0, false);
i64 resolved_weight;
i32 resolved_weight;
if (weight->is_integer()) {
resolved_weight = weight->as_integer().integer();
@ -180,7 +180,7 @@ Parser::ParseErrorOr<NonnullRefPtr<StyleValue const>> Parser::parse_descriptor_v
return nullptr;
};
auto const resolve_value = [&](StyleValue const& value, i64 infinite_value) -> Optional<i64> {
auto const resolve_value = [&](StyleValue const& value, i32 infinite_value) -> Optional<i32> {
if (value.is_integer())
return value.as_integer().integer();
@ -202,8 +202,8 @@ Parser::ParseErrorOr<NonnullRefPtr<StyleValue const>> Parser::parse_descriptor_v
// If the lower bound of any range is higher than the upper bound, the entire descriptor is
// invalid and must be ignored.
auto first_int = resolve_value(*first_value, NumericLimits<i64>::min());
auto second_int = resolve_value(*second_value, NumericLimits<i64>::max());
auto first_int = resolve_value(*first_value, NumericLimits<i32>::min());
auto second_int = resolve_value(*second_value, NumericLimits<i32>::max());
if (!first_int.has_value() || !second_int.has_value() || first_int.value() > second_int.value())
return nullptr;

View file

@ -134,7 +134,7 @@ public:
VERIFY(m_type == Type::Number);
return m_number_value.value();
}
i64 to_integer() const
i32 to_integer() const
{
VERIFY(m_type == Type::Number && m_number_value.is_integer());
return m_number_value.integer_value();
@ -150,7 +150,7 @@ public:
VERIFY(m_type == Type::Dimension);
return m_number_value.value();
}
i64 dimension_value_int() const { return m_number_value.integer_value(); }
i32 dimension_value_int() const { return m_number_value.integer_value(); }
double percentage() const
{

View file

@ -3166,7 +3166,7 @@ Optional<double> CalculatedStyleValue::resolve_number(CalculationResolutionConte
return {};
}
Optional<i64> CalculatedStyleValue::resolve_integer(CalculationResolutionContext const& context) const
Optional<i32> CalculatedStyleValue::resolve_integer(CalculationResolutionContext const& context) const
{
auto result = resolve_value(context);

View file

@ -104,7 +104,7 @@ public:
bool resolves_to_number() const { return m_resolved_type.matches_number(m_context.percentages_resolve_as); }
Optional<double> resolve_number(CalculationResolutionContext const&) const;
Optional<i64> resolve_integer(CalculationResolutionContext const&) const;
Optional<i32> resolve_integer(CalculationResolutionContext const&) const;
bool resolves_to_dimension() const { return m_resolved_type.matches_dimension(); }

View file

@ -12,12 +12,12 @@ namespace Web::CSS {
class IntegerStyleValue final : public StyleValue {
public:
static ValueComparingNonnullRefPtr<IntegerStyleValue const> create(i64 value)
static ValueComparingNonnullRefPtr<IntegerStyleValue const> create(i32 value)
{
return adopt_ref(*new (nothrow) IntegerStyleValue(value));
}
i64 integer() const { return m_value; }
i32 integer() const { return m_value; }
virtual void serialize(StringBuilder&, SerializationMode) const override;
virtual Vector<Parser::ComponentValue> tokenize() const override;
@ -34,13 +34,13 @@ public:
virtual bool is_computationally_independent() const override { return true; }
private:
explicit IntegerStyleValue(i64 value)
explicit IntegerStyleValue(i32 value)
: StyleValue(Type::Integer)
, m_value(value)
{
}
i64 m_value { 0 };
i32 m_value { 0 };
};
}

View file

@ -187,7 +187,7 @@ StyleValueVector StyleValue::subdivide_into_iterations(PropertyNameAndID const&)
return StyleValueVector { *this };
}
i64 int_from_style_value(NonnullRefPtr<StyleValue const> const& style_value)
i32 int_from_style_value(NonnullRefPtr<StyleValue const> const& style_value)
{
if (style_value->is_integer())
return style_value->as_integer().integer();

View file

@ -210,7 +210,7 @@ struct StyleValueWithDefaultOperators : public StyleValue {
}
};
i64 int_from_style_value(NonnullRefPtr<StyleValue const> const& style_value);
i32 int_from_style_value(NonnullRefPtr<StyleValue const> const& style_value);
double number_from_style_value(NonnullRefPtr<StyleValue const> const& style_value, Optional<double> percentage_basis);
FlyString const& string_from_style_value(NonnullRefPtr<StyleValue const> const& style_value);

View file

@ -7704,7 +7704,7 @@ String Document::dump_stacking_context_tree()
return builder.to_string_without_validation();
}
Optional<Vector<CSS::Parser::ComponentValue>> Document::environment_variable_value(CSS::EnvironmentVariable environment_variable, Span<i64> indices) const
Optional<Vector<CSS::Parser::ComponentValue>> Document::environment_variable_value(CSS::EnvironmentVariable environment_variable, Span<i32> indices) const
{
auto invalid = [] {
return Vector { CSS::Parser::ComponentValue { CSS::Parser::GuaranteedInvalidValue {} } };
@ -7851,14 +7851,15 @@ void Document::build_counter_style_cache()
{},
{},
"/ "_fly_string,
Vector<CSS::CounterStyleRangeEntry> { { 1, AK::NumericLimits<i64>::max() } },
Vector<CSS::CounterStyleRangeEntry> { { 1, AK::NumericLimits<i32>::max() } },
{},
{}));
// https://drafts.csswg.org/css-counter-styles-3/#extended-range-optional
// For all of these counter styles, the descriptors are the same as for the limited range variants, except for
// the range, which is calc(-1 * pow(10, 16) + 1) calc(pow(10, 16) - 1).
Vector<CSS::CounterStyleRangeEntry> extended_cjk_range { { -9999999999999999, 9999999999999999 } };
// AD-HOC: Ranges (as with all other CSS <integer>s are limited to i32 range)
Vector<CSS::CounterStyleRangeEntry> extended_cjk_range { { AK::clamp_to<i32>(-9999999999999999), AK::clamp_to<i32>(9999999999999999) } };
// https://drafts.csswg.org/css-counter-styles-3/#limited-chinese
// For all of these counter styles, the suffix is "、" U+3001, the fallback is cjk-decimal, the range is -9999

View file

@ -1018,7 +1018,7 @@ public:
StyleInvalidator& style_invalidator() { return m_style_invalidator; }
Optional<Vector<CSS::Parser::ComponentValue>> environment_variable_value(CSS::EnvironmentVariable, Span<i64> indices = {}) const;
Optional<Vector<CSS::Parser::ComponentValue>> environment_variable_value(CSS::EnvironmentVariable, Span<i32> indices = {}) const;
// https://www.w3.org/TR/css-properties-values-api-1/#dom-window-registeredpropertyset-slot
HashMap<FlyString, CSS::CustomPropertyRegistration>& registered_property_set();

View file

@ -282,8 +282,8 @@ GridFormattingContext::PlacementPosition GridFormattingContext::resolve_grid_pos
CSS::CalculationResolutionContext resolution_context { .length_resolution_context = CSS::Length::ResolutionContext::for_layout_node(child_box) };
Optional<i64> placement_start_line_number = placement_start.has_line_number() ? placement_start.line_number().resolved(resolution_context) : Optional<i64> {};
Optional<i64> placement_end_line_number = placement_end.has_line_number() ? placement_end.line_number().resolved(resolution_context) : Optional<i64> {};
Optional<i32> placement_start_line_number = placement_start.has_line_number() ? placement_start.line_number().resolved(resolution_context) : Optional<i32> {};
Optional<i32> placement_end_line_number = placement_end.has_line_number() ? placement_end.line_number().resolved(resolution_context) : Optional<i32> {};
PlacementPosition result;

View file

@ -0,0 +1,4 @@
Specified order: -2147483648
Computed order: -2147483648
Specified animation-timing-function: steps(2147483647, jump-start)
Computed animation-timing-function: steps(2147483647, jump-start)

View file

@ -0,0 +1,18 @@
<!doctype html>
<style>
#foo {
order: -2147483649;
animation-timing-function: steps(2147483648, jump-start);
}
</style>
<div id="foo"></div>
<script src="../include.js"></script>
<script>
test(() => {
const style = document.styleSheets[0].cssRules[0].style;
println(`Specified order: ${style.order}`);
println(`Computed order: ${getComputedStyle(foo).order}`);
println(`Specified animation-timing-function: ${style.animationTimingFunction}`);
println(`Computed animation-timing-function: ${getComputedStyle(foo).animationTimingFunction}`);
});
</script>