LibWeb/CSS: Implement "Create a sum value"

This commit is contained in:
Sam Atkins 2025-09-11 16:48:57 +01:00 committed by Jelle Raaijmakers
commit 80abffd2e8
Notes: github-actions[bot] 2025-09-12 11:46:44 +00:00
17 changed files with 320 additions and 1 deletions

View file

@ -105,7 +105,7 @@ GC::Ref<CSSNumericValue> CSSMathClamp::upper() const
// https://drafts.css-houdini.org/css-typed-om-1/#equal-numeric-value // https://drafts.css-houdini.org/css-typed-om-1/#equal-numeric-value
bool CSSMathClamp::is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const bool CSSMathClamp::is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const
{ {
// AD-HOC: Spec doesn't handle clamp() // AD-HOC: Spec doesn't handle clamp(). https://github.com/w3c/css-houdini-drafts/issues/1152
// 1. If value1 and value2 are not members of the same interface, return false. // 1. If value1 and value2 are not members of the same interface, return false.
auto* other_clamp = as_if<CSSMathClamp>(*other); auto* other_clamp = as_if<CSSMathClamp>(*other);
if (!other_clamp) if (!other_clamp)
@ -116,4 +116,34 @@ bool CSSMathClamp::is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const
&& m_upper->is_equal_numeric_value(other_clamp->m_upper); && m_upper->is_equal_numeric_value(other_clamp->m_upper);
} }
// https://drafts.css-houdini.org/css-typed-om-1/#create-a-sum-value
Optional<SumValue> CSSMathClamp::create_a_sum_value() const
{
// AD-HOC: There is no spec for this. https://github.com/w3c/css-houdini-drafts/issues/1152
// So, basing it on the spec for CSSMathMin.
// Create sum values from lower, value, and upper.
auto lower = m_lower->create_a_sum_value();
auto value = m_value->create_a_sum_value();
auto upper = m_upper->create_a_sum_value();
// If any of those are failure, or has a length greater than one, return failure.
if (!lower.has_value() || lower->size() > 1
|| !value.has_value() || value->size() > 1
|| !upper.has_value() || upper->size() > 1)
return {};
// If not all their unit maps are identical, return failure.
if (lower->first().unit_map != value->first().unit_map || value->first().unit_map != upper->first().unit_map)
return {};
// Return value clamped between lower and upper.
return SumValue {
SumValueItem {
clamp(value->first().value, lower->first().value, upper->first().value),
value->first().unit_map,
}
};
}
} }

View file

@ -30,6 +30,7 @@ public:
virtual String serialize_math_value(Nested, Parens) const override; virtual String serialize_math_value(Nested, Parens) const override;
virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const override; virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const override;
virtual Optional<SumValue> create_a_sum_value() const override;
private: private:
CSSMathClamp(JS::Realm&, NumericType, GC::Ref<CSSNumericValue> lower, GC::Ref<CSSNumericValue> value, GC::Ref<CSSNumericValue> upper); CSSMathClamp(JS::Realm&, NumericType, GC::Ref<CSSNumericValue> lower, GC::Ref<CSSNumericValue> value, GC::Ref<CSSNumericValue> upper);

View file

@ -106,4 +106,29 @@ bool CSSMathInvert::is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const
return m_value->is_equal_numeric_value(other_invert->m_value); return m_value->is_equal_numeric_value(other_invert->m_value);
} }
// https://drafts.css-houdini.org/css-typed-om-1/#create-a-sum-value
Optional<SumValue> CSSMathInvert::create_a_sum_value() const
{
// 1. Let values be the result of creating a sum value from thiss value internal slot.
auto values = m_value->create_a_sum_value();
// 2. If values is failure, return failure.
if (!values.has_value())
return {};
// 3. If the length of values is more than one, return failure.
if (values->size() > 1)
return {};
// 4. Invert (find the reciprocal of) the value of the item in values, and negate the value of each entry in its unit map.
for (auto& [value, unit_map] : *values) {
value = 1.0 / value;
for (auto& [_, power] : unit_map)
power = -power;
}
// 5. Return values.
return values;
}
} }

View file

@ -28,6 +28,7 @@ public:
virtual String serialize_math_value(Nested, Parens) const override; virtual String serialize_math_value(Nested, Parens) const override;
virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const override; virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const override;
virtual Optional<SumValue> create_a_sum_value() const override;
private: private:
CSSMathInvert(JS::Realm&, NumericType, GC::Ref<CSSNumericValue>); CSSMathInvert(JS::Realm&, NumericType, GC::Ref<CSSNumericValue>);

View file

@ -129,4 +129,32 @@ bool CSSMathMax::is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const
return m_values->is_equal_numeric_values(other_max->m_values); return m_values->is_equal_numeric_values(other_max->m_values);
} }
// https://drafts.css-houdini.org/css-typed-om-1/#create-a-sum-value
Optional<SumValue> CSSMathMax::create_a_sum_value() const
{
// 1. Let args be the result of creating a sum value for each item in thiss values internal slot.
Vector<Optional<SumValue>> args;
args.ensure_capacity(m_values->length());
for (auto const& value : m_values->values()) {
args.unchecked_append(value->create_a_sum_value());
}
Optional<SumValue> item_with_largest_value = args.first();
for (auto const& item : args) {
// 2. If any item of args is failure, or has a length greater than one, return failure.
if (!item.has_value() || item->size() > 1)
return {};
// 3. If not all of the unit maps among the items of args are identical, return failure.
if (item->first().unit_map != item_with_largest_value->first().unit_map)
return {};
if (item->first().value > item_with_largest_value->first().value)
item_with_largest_value = item;
}
// 4. Return the item of args whose sole item has the largest value.
return item_with_largest_value;
}
} }

View file

@ -28,6 +28,7 @@ public:
virtual String serialize_math_value(Nested, Parens) const override; virtual String serialize_math_value(Nested, Parens) const override;
virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const override; virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const override;
virtual Optional<SumValue> create_a_sum_value() const override;
private: private:
CSSMathMax(JS::Realm&, NumericType, GC::Ref<CSSNumericArray>); CSSMathMax(JS::Realm&, NumericType, GC::Ref<CSSNumericArray>);

View file

@ -130,4 +130,32 @@ bool CSSMathMin::is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const
return m_values->is_equal_numeric_values(other_min->m_values); return m_values->is_equal_numeric_values(other_min->m_values);
} }
// https://drafts.css-houdini.org/css-typed-om-1/#create-a-sum-value
Optional<SumValue> CSSMathMin::create_a_sum_value() const
{
// 1. Let args be the result of creating a sum value for each item in thiss values internal slot.
Vector<Optional<SumValue>> args;
args.ensure_capacity(m_values->length());
for (auto const& value : m_values->values()) {
args.unchecked_append(value->create_a_sum_value());
}
Optional<SumValue> item_with_smallest_value = args.first();
for (auto const& item : args) {
// 2. If any item of args is failure, or has a length greater than one, return failure.
if (!item.has_value() || item->size() > 1)
return {};
// 3. If not all of the unit maps among the items of args are identical, return failure.
if (item->first().unit_map != item_with_smallest_value->first().unit_map)
return {};
if (item->first().value < item_with_smallest_value->first().value)
item_with_smallest_value = item;
}
// 4. Return the item of args whose sole item has the smallest value.
return item_with_smallest_value;
}
} }

View file

@ -28,6 +28,7 @@ public:
virtual String serialize_math_value(Nested, Parens) const override; virtual String serialize_math_value(Nested, Parens) const override;
virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const override; virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const override;
virtual Optional<SumValue> create_a_sum_value() const override;
private: private:
CSSMathMin(JS::Realm&, NumericType, GC::Ref<CSSNumericArray>); CSSMathMin(JS::Realm&, NumericType, GC::Ref<CSSNumericArray>);

View file

@ -103,4 +103,22 @@ bool CSSMathNegate::is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const
return m_value->is_equal_numeric_value(other_negate->m_value); return m_value->is_equal_numeric_value(other_negate->m_value);
} }
// https://drafts.css-houdini.org/css-typed-om-1/#create-a-sum-value
Optional<SumValue> CSSMathNegate::create_a_sum_value() const
{
// 1. Let values be the result of creating a sum value from thiss value internal slot.
auto values = m_value->create_a_sum_value();
// 2. If values is failure, return failure.
if (!values.has_value())
return {};
// 3. Negate the value of each item of values.
for (auto& value : *values)
value.value = -value.value;
// 4. Return values.
return values;
}
} }

View file

@ -28,6 +28,7 @@ public:
virtual String serialize_math_value(Nested, Parens) const override; virtual String serialize_math_value(Nested, Parens) const override;
virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const override; virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const override;
virtual Optional<SumValue> create_a_sum_value() const override;
private: private:
CSSMathNegate(JS::Realm&, NumericType, GC::Ref<CSSNumericValue>); CSSMathNegate(JS::Realm&, NumericType, GC::Ref<CSSNumericValue>);

View file

@ -152,4 +152,46 @@ bool CSSMathProduct::is_equal_numeric_value(GC::Ref<CSSNumericValue> other) cons
return m_values->is_equal_numeric_values(other_product->m_values); return m_values->is_equal_numeric_values(other_product->m_values);
} }
// https://drafts.css-houdini.org/css-typed-om-1/#create-a-sum-value
Optional<SumValue> CSSMathProduct::create_a_sum_value() const
{
// 1. Let values initially be the sum value «(1, «[ ]»)». (I.e. what youd get from 1.)
SumValue values {
SumValueItem { 1, {} }
};
// 2. For each item in thiss values internal slot:
for (auto const& item : m_values->values()) {
// 1. Let new values be the result of creating a sum value from item.
// Let temp initially be an empty list.
auto new_values = item->create_a_sum_value();
SumValue temp;
// 2. If new values is failure, return failure.
if (!new_values.has_value())
return {};
// 3. For each item1 in values:
for (auto const& item1 : values) {
// 1. For each item2 in new values:
for (auto const& item2 : *new_values) {
// 1. Let item be a tuple with its value set to the product of the values of item1 and item2, and its
// unit map set to the product of the unit maps of item1 and item2, with all entries with a zero
// value removed.
auto unit_map = product_of_two_unit_maps(item1.unit_map, item2.unit_map);
unit_map.remove_all_matching([](auto&, auto& value) { return value == 0; });
// 2. Append item to temp.
temp.empend(item1.value * item2.value, move(unit_map));
}
}
// 4. Set values to temp.
values = move(temp);
}
// 3. Return values.
return values;
}
} }

View file

@ -28,6 +28,7 @@ public:
virtual String serialize_math_value(Nested, Parens) const override; virtual String serialize_math_value(Nested, Parens) const override;
virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const override; virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const override;
virtual Optional<SumValue> create_a_sum_value() const override;
private: private:
CSSMathProduct(JS::Realm&, NumericType, GC::Ref<CSSNumericArray>); CSSMathProduct(JS::Realm&, NumericType, GC::Ref<CSSNumericArray>);

View file

@ -151,4 +151,59 @@ bool CSSMathSum::is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const
return m_values->is_equal_numeric_values(other_sum->m_values); return m_values->is_equal_numeric_values(other_sum->m_values);
} }
// https://drafts.css-houdini.org/css-typed-om-1/#create-a-sum-value
Optional<SumValue> CSSMathSum::create_a_sum_value() const
{
// 1. Let values initially be an empty list.
SumValue values;
// 2. For each item in thiss values internal slot:
for (auto const item : m_values->values()) {
// 1. Let value be the result of creating a sum value from item. If value is failure, return failure.
auto maybe_value = item->create_a_sum_value();
if (!maybe_value.has_value())
return {};
auto const& value = maybe_value.value();
// 2. For each subvalue of value:
for (auto const& subvalue : value) {
// 1. If values already contains an item with the same unit map as subvalue, increment that items value by
// the value of subvalue.
auto existing_item = values.find_if([&subvalue](SumValueItem const& other) {
return subvalue.unit_map == other.unit_map;
});
if (existing_item != values.end()) {
existing_item->value += subvalue.value;
}
// 2. Otherwise, append subvalue to values.
else {
values.append(subvalue);
}
}
}
// 3. Create a type from the unit map of each item of values, and add all the types together.
// If the result is failure, return failure.
auto added_type = NumericType::create_from_unit_map(values.first().unit_map);
if (!added_type.has_value())
return {};
bool first = true;
for (auto const& [_, unit_map] : values) {
if (first) {
first = false;
continue;
}
auto type = NumericType::create_from_unit_map(unit_map);
if (!type.has_value())
return {};
added_type = added_type->added_to(type.value());
if (!added_type.has_value())
return {};
}
// 4. Return values.
return values;
}
} }

View file

@ -28,6 +28,7 @@ public:
virtual String serialize_math_value(Nested, Parens) const override; virtual String serialize_math_value(Nested, Parens) const override;
virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const override; virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const override;
virtual Optional<SumValue> create_a_sum_value() const override;
private: private:
CSSMathSum(JS::Realm&, NumericType, GC::Ref<CSSNumericArray>); CSSMathSum(JS::Realm&, NumericType, GC::Ref<CSSNumericArray>);

View file

@ -28,6 +28,13 @@ struct CSSNumericType {
// https://drafts.css-houdini.org/css-typed-om-1/#typedefdef-cssnumberish // https://drafts.css-houdini.org/css-typed-om-1/#typedefdef-cssnumberish
using CSSNumberish = Variant<double, GC::Root<CSSNumericValue>>; using CSSNumberish = Variant<double, GC::Root<CSSNumericValue>>;
// https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-sum-value
struct SumValueItem {
double value;
UnitMap unit_map;
};
using SumValue = Vector<SumValueItem>;
// https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue // https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue
class CSSNumericValue : public CSSStyleValue { class CSSNumericValue : public CSSStyleValue {
WEB_PLATFORM_OBJECT(CSSNumericValue, CSSStyleValue); WEB_PLATFORM_OBJECT(CSSNumericValue, CSSStyleValue);
@ -45,6 +52,8 @@ public:
bool equals_for_bindings(Vector<CSSNumberish>) const; bool equals_for_bindings(Vector<CSSNumberish>) const;
virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const = 0; virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const = 0;
virtual Optional<SumValue> create_a_sum_value() const = 0;
CSSNumericType type_for_bindings() const; CSSNumericType type_for_bindings() const;
NumericType const& type() const { return m_type; } NumericType const& type() const { return m_type; }

View file

@ -186,4 +186,80 @@ bool CSSUnitValue::is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const
&& m_value == other_unit_value->m_value; && m_value == other_unit_value->m_value;
} }
// https://drafts.css-houdini.org/css-typed-om-1/#create-a-sum-value
Optional<SumValue> CSSUnitValue::create_a_sum_value() const
{
// 1. Let unit be the value of thiss unit internal slot, and value be the value of thiss value internal slot.
auto unit = m_unit;
auto value = m_value;
// 2. If unit is a member of a set of compatible units, and is not the sets canonical unit, multiply value
// by the conversion ratio between unit and the canonical unit, and change unit to the canonical unit.
if (auto dimension_type = dimension_for_unit(unit); dimension_type.has_value()) {
switch (*dimension_type) {
case DimensionType::Angle: {
auto angle_unit = string_to_angle_unit(unit).release_value();
auto canonical_unit = canonical_angle_unit();
if (angle_unit != canonical_unit && units_are_compatible(angle_unit, canonical_unit)) {
value *= ratio_between_units(angle_unit, canonical_unit);
unit = CSS::to_string(canonical_unit);
}
break;
}
case DimensionType::Flex: {
auto flex_unit = string_to_flex_unit(unit).release_value();
auto canonical_unit = canonical_flex_unit();
if (flex_unit != canonical_unit && units_are_compatible(flex_unit, canonical_unit)) {
value *= ratio_between_units(flex_unit, canonical_unit);
unit = CSS::to_string(canonical_unit);
}
break;
}
case DimensionType::Frequency: {
auto frequency_unit = string_to_frequency_unit(unit).release_value();
auto canonical_unit = canonical_frequency_unit();
if (frequency_unit != canonical_unit && units_are_compatible(frequency_unit, canonical_unit)) {
value *= ratio_between_units(frequency_unit, canonical_unit);
unit = CSS::to_string(canonical_unit);
}
break;
}
case DimensionType::Length: {
auto length_unit = string_to_length_unit(unit).release_value();
auto canonical_unit = canonical_length_unit();
if (length_unit != canonical_unit && units_are_compatible(length_unit, canonical_unit)) {
value *= ratio_between_units(length_unit, canonical_unit);
unit = CSS::to_string(canonical_unit);
}
break;
}
case DimensionType::Resolution: {
auto resolution_unit = string_to_resolution_unit(unit).release_value();
auto canonical_unit = canonical_resolution_unit();
if (resolution_unit != canonical_unit && units_are_compatible(resolution_unit, canonical_unit)) {
value *= ratio_between_units(resolution_unit, canonical_unit);
unit = CSS::to_string(canonical_unit);
}
break;
}
case DimensionType::Time: {
auto time_unit = string_to_time_unit(unit).release_value();
auto canonical_unit = canonical_time_unit();
if (time_unit != canonical_unit && units_are_compatible(time_unit, canonical_unit)) {
value *= ratio_between_units(time_unit, canonical_unit);
unit = CSS::to_string(canonical_unit);
}
break;
}
}
}
// 3. If unit is "number", return «(value, «[ ]»)».
if (unit == "number"_fly_string)
return SumValue { SumValueItem { value, {} } };
// 4. Otherwise, return «(value, «[unit → 1]»)».
return SumValue { SumValueItem { value, { { unit, 1 } } } };
}
} }

View file

@ -32,6 +32,7 @@ public:
GC::Ptr<CSSUnitValue> converted_to_unit(FlyString const& unit) const; GC::Ptr<CSSUnitValue> converted_to_unit(FlyString const& unit) const;
virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const override; virtual bool is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const override;
virtual Optional<SumValue> create_a_sum_value() const override;
private: private:
explicit CSSUnitValue(JS::Realm&, double value, FlyString unit, NumericType type); explicit CSSUnitValue(JS::Realm&, double value, FlyString unit, NumericType type);