From a139ad1c44ca741cb8691c6ebae90f97f01df64c Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Thu, 11 Sep 2025 17:00:26 +0100 Subject: [PATCH] LibWeb/CSS: Implement CSSNumericValue.to() Tries to convert the CSSNumericValue to a CSSUnitValue with the given unit. This gets us the remaining 11 WPT subtests for this method. --- Libraries/LibWeb/CSS/CSSNumericValue.cpp | 33 +++++++++++++++++++ Libraries/LibWeb/CSS/CSSNumericValue.h | 2 ++ Libraries/LibWeb/CSS/CSSNumericValue.idl | 3 +- Libraries/LibWeb/CSS/CSSUnitValue.cpp | 23 +++++++++++++ Libraries/LibWeb/CSS/CSSUnitValue.h | 1 + .../numeric-objects/to.tentative.txt | 25 +++++++------- 6 files changed, 73 insertions(+), 14 deletions(-) diff --git a/Libraries/LibWeb/CSS/CSSNumericValue.cpp b/Libraries/LibWeb/CSS/CSSNumericValue.cpp index 4829579d767..ac4f2f49ccb 100644 --- a/Libraries/LibWeb/CSS/CSSNumericValue.cpp +++ b/Libraries/LibWeb/CSS/CSSNumericValue.cpp @@ -71,6 +71,39 @@ bool CSSNumericValue::equals_for_bindings(Vector values) const return true; } +// https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-to +WebIDL::ExceptionOr> CSSNumericValue::to(FlyString const& unit) const +{ + // The to(unit) method converts an existing CSSNumericValue this into another one with the specified unit, if + // possible. When called, it must perform the following steps: + + // 1. Let type be the result of creating a type from unit. If type is failure, throw a SyntaxError. + auto maybe_type = NumericType::create_from_unit(unit); + if (!maybe_type.has_value()) + return WebIDL::SyntaxError::create(realm(), Utf16String::formatted("Unrecognized unit '{}'", unit)); + + // 2. Let sum be the result of creating a sum value from this. If sum is failure, throw a TypeError. + auto sum = create_a_sum_value(); + if (!sum.has_value()) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to create a sum from input '{}'", to_string())) }; + + // 3. If sum has more than one item, throw a TypeError. + // Otherwise, let item be the result of creating a CSSUnitValue from the sole item in sum, then converting it to + // unit. If item is failure, throw a TypeError. + if (sum->size() > 1) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Sum contains more than one item"sv }; + auto item = CSSUnitValue::create_from_sum_value_item(realm(), sum->first()); + if (!item) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to create CSSUnitValue from input '{}'", to_string())) }; + + auto converted_item = item->converted_to_unit(unit); + if (!converted_item) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to convert input '{}' to unit '{}'", to_string(), unit)) }; + + // 4. Return item. + return converted_item.as_nonnull(); +} + // https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-type CSSNumericType CSSNumericValue::type_for_bindings() const { diff --git a/Libraries/LibWeb/CSS/CSSNumericValue.h b/Libraries/LibWeb/CSS/CSSNumericValue.h index ddeecc746f2..a01ded379d8 100644 --- a/Libraries/LibWeb/CSS/CSSNumericValue.h +++ b/Libraries/LibWeb/CSS/CSSNumericValue.h @@ -52,6 +52,8 @@ public: bool equals_for_bindings(Vector) const; virtual bool is_equal_numeric_value(GC::Ref other) const = 0; + WebIDL::ExceptionOr> to(FlyString const& unit) const; + virtual Optional create_a_sum_value() const = 0; CSSNumericType type_for_bindings() const; diff --git a/Libraries/LibWeb/CSS/CSSNumericValue.idl b/Libraries/LibWeb/CSS/CSSNumericValue.idl index a955ae35ad9..deaaa2ebea9 100644 --- a/Libraries/LibWeb/CSS/CSSNumericValue.idl +++ b/Libraries/LibWeb/CSS/CSSNumericValue.idl @@ -1,4 +1,5 @@ #import +#import // https://drafts.css-houdini.org/css-typed-om-1/#enumdef-cssnumericbasetype enum CSSNumericBaseType { @@ -35,7 +36,7 @@ interface CSSNumericValue : CSSStyleValue { [ImplementedAs=equals_for_bindings] boolean equals(CSSNumberish... value); - // FIXME: CSSUnitValue to(USVString unit); + CSSUnitValue to(USVString unit); // FIXME: CSSMathSum toSum(USVString... units); [ImplementedAs=type_for_bindings] CSSNumericType type(); diff --git a/Libraries/LibWeb/CSS/CSSUnitValue.cpp b/Libraries/LibWeb/CSS/CSSUnitValue.cpp index 4230434ba5b..43d72e901ba 100644 --- a/Libraries/LibWeb/CSS/CSSUnitValue.cpp +++ b/Libraries/LibWeb/CSS/CSSUnitValue.cpp @@ -23,6 +23,29 @@ GC::Ref CSSUnitValue::create(JS::Realm& realm, double value, FlySt return realm.create(realm, value, move(unit), numeric_type.release_value()); } +// https://drafts.css-houdini.org/css-typed-om-1/#create-a-cssunitvalue-from-a-sum-value-item +GC::Ptr CSSUnitValue::create_from_sum_value_item(JS::Realm& realm, SumValueItem const& item) +{ + // 1. If item has more than one entry in its unit map, return failure. + if (item.unit_map.size() > 1) + return {}; + + // 2. If item has no entries in its unit map, return a new CSSUnitValue whose unit internal slot is set to + // "number", and whose value internal slot is set to item’s value. + if (item.unit_map.is_empty()) + return CSSUnitValue::create(realm, item.value, "number"_fly_string); + + // 3. Otherwise, item has a single entry in its unit map. If that entry’s value is anything other than 1, return + // failure. + auto single_type_entry = item.unit_map.begin(); + if (single_type_entry->value != 1) + return {}; + + // 4. Otherwise, return a new CSSUnitValue whose unit internal slot is set to that entry’s key, and whose value + // internal slot is set to item’s value. + return CSSUnitValue::create(realm, item.value, single_type_entry->key); +} + // https://drafts.css-houdini.org/css-typed-om-1/#dom-cssunitvalue-cssunitvalue WebIDL::ExceptionOr> CSSUnitValue::construct_impl(JS::Realm& realm, double value, FlyString unit) { diff --git a/Libraries/LibWeb/CSS/CSSUnitValue.h b/Libraries/LibWeb/CSS/CSSUnitValue.h index aea29ee0a76..1c7c8e0fddc 100644 --- a/Libraries/LibWeb/CSS/CSSUnitValue.h +++ b/Libraries/LibWeb/CSS/CSSUnitValue.h @@ -18,6 +18,7 @@ class CSSUnitValue final : public CSSNumericValue { public: [[nodiscard]] static GC::Ref create(JS::Realm&, double value, FlyString unit); + static GC::Ptr create_from_sum_value_item(JS::Realm&, SumValueItem const&); static WebIDL::ExceptionOr> construct_impl(JS::Realm&, double value, FlyString unit); virtual ~CSSUnitValue() override = default; diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/numeric-objects/to.tentative.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/numeric-objects/to.tentative.txt index cf8e639b7e6..592b7c07252 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/numeric-objects/to.tentative.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/numeric-objects/to.tentative.txt @@ -2,22 +2,21 @@ Harness status: OK Found 17 tests -6 Pass -11 Fail -Fail Converting a CSSUnitValue to an invalid unit throws SyntaxError +17 Pass +Pass Converting a CSSUnitValue to an invalid unit throws SyntaxError Pass Converting a CSSNumericValue with invalid sum value throws TypeError Pass Converting a CSSNumericValue with sum value containing more than one value throws TypeError Pass Converting a CSSUnitValue to an incompatible unit throws TypeError -Fail Converting a CSSUnitValue to its own unit returns itself -Fail Converting a CSSUnitValue to its canonical unit returns correct value -Fail Converting a CSSMathSum to a single unit adds the values -Fail Converting a CSSMathProduct to a single unit multiplies the values -Fail Converting a CSSMathMin to a single unit finds the min value +Pass Converting a CSSUnitValue to its own unit returns itself +Pass Converting a CSSUnitValue to its canonical unit returns correct value +Pass Converting a CSSMathSum to a single unit adds the values +Pass Converting a CSSMathProduct to a single unit multiplies the values +Pass Converting a CSSMathMin to a single unit finds the min value Pass Converting a CSSMathMin to a single unit with different units throws a TypeError -Fail Converting a CSSMathMax to a single unit finds the max value +Pass Converting a CSSMathMax to a single unit finds the max value Pass Converting a CSSMathMax to a single unit with different units throws a TypeError -Fail Converting a CSSMathClamp to a single unit returns the clamped value +Pass Converting a CSSMathClamp to a single unit returns the clamped value Pass Converting a CSSMathClamp to a single unit with different units throws a TypeError -Fail Converting a CSSMathNegate to a single unit negates its value -Fail Converting a CSSMathInvert to a single unit inverts its value and units -Fail Converting a complex expression to a single unit \ No newline at end of file +Pass Converting a CSSMathNegate to a single unit negates its value +Pass Converting a CSSMathInvert to a single unit inverts its value and units +Pass Converting a complex expression to a single unit \ No newline at end of file