LibWeb/CSS: Make CSSStyleValue.to_string() return ExceptionOr

DOMMatrix.to_string() throws exceptions if any of its values are
non-finite. This ends up affecting CSSStyleValue because its subclass
CSSTransformValue (which is about to be added) serializes
CSSTransformComponents, and one of those is CSSMatrixComponent, which
calls DOMMatrix.to_string().

This is all quite unfortunate, and because at the time the spec for
DOMMatrix was written, CSS couldn't represent NaN or infinity. That's
no longer true, so I'm hoping the spec can be updated and this can be
reverted. https://github.com/w3c/fxtf-drafts/issues/611
This commit is contained in:
Sam Atkins 2025-09-16 15:06:38 +01:00
commit d3d695e9d2
Notes: github-actions[bot] 2025-09-24 11:28:48 +00:00
19 changed files with 48 additions and 44 deletions

View file

@ -7,6 +7,7 @@
#include "CSSImageValue.h" #include "CSSImageValue.h"
#include <LibWeb/Bindings/CSSImageValuePrototype.h> #include <LibWeb/Bindings/CSSImageValuePrototype.h>
#include <LibWeb/Bindings/Intrinsics.h> #include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::CSS { namespace Web::CSS {
@ -30,7 +31,7 @@ void CSSImageValue::initialize(JS::Realm& realm)
} }
// https://drafts.css-houdini.org/css-typed-om-1/#stylevalue-serialization // https://drafts.css-houdini.org/css-typed-om-1/#stylevalue-serialization
String CSSImageValue::to_string() const WebIDL::ExceptionOr<String> CSSImageValue::to_string() const
{ {
// AD-HOC: The spec doesn't say how to serialize this, as it's intentionally a black box. // AD-HOC: The spec doesn't say how to serialize this, as it's intentionally a black box.
// We just serialize the source string that was used to construct this. // We just serialize the source string that was used to construct this.

View file

@ -20,7 +20,7 @@ public:
virtual ~CSSImageValue() override = default; virtual ~CSSImageValue() override = default;
virtual String to_string() const override; virtual WebIDL::ExceptionOr<String> to_string() const override;
private: private:
explicit CSSImageValue(JS::Realm&, String constructed_from_string); explicit CSSImageValue(JS::Realm&, String constructed_from_string);

View file

@ -55,7 +55,7 @@ WebIDL::ExceptionOr<void> CSSKeywordValue::set_value(FlyString value)
} }
// https://drafts.css-houdini.org/css-typed-om-1/#keywordvalue-serialization // https://drafts.css-houdini.org/css-typed-om-1/#keywordvalue-serialization
String CSSKeywordValue::to_string() const WebIDL::ExceptionOr<String> CSSKeywordValue::to_string() const
{ {
// To serialize a CSSKeywordValue this: // To serialize a CSSKeywordValue this:
// 1. Return thiss value internal slot. // 1. Return thiss value internal slot.

View file

@ -28,7 +28,7 @@ public:
FlyString const& value() const { return m_value; } FlyString const& value() const { return m_value; }
WebIDL::ExceptionOr<void> set_value(FlyString value); WebIDL::ExceptionOr<void> set_value(FlyString value);
virtual String to_string() const override; virtual WebIDL::ExceptionOr<String> to_string() const override;
private: private:
explicit CSSKeywordValue(JS::Realm&, FlyString value); explicit CSSKeywordValue(JS::Realm&, FlyString value);

View file

@ -85,7 +85,7 @@ WebIDL::ExceptionOr<GC::Ref<CSSUnitValue>> CSSNumericValue::to(FlyString const&
// 2. Let sum be the result of creating a sum value from this. If sum is failure, throw a TypeError. // 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(); auto sum = create_a_sum_value();
if (!sum.has_value()) if (!sum.has_value())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to create a sum from input '{}'", to_string())) }; return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to create a sum from input '{}'", MUST(to_string()))) };
// 3. If sum has more than one item, throw a TypeError. // 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 // Otherwise, let item be the result of creating a CSSUnitValue from the sole item in sum, then converting it to
@ -94,11 +94,11 @@ WebIDL::ExceptionOr<GC::Ref<CSSUnitValue>> CSSNumericValue::to(FlyString const&
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Sum contains more than one item"sv }; return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Sum contains more than one item"sv };
auto item = CSSUnitValue::create_from_sum_value_item(realm(), sum->first()); auto item = CSSUnitValue::create_from_sum_value_item(realm(), sum->first());
if (!item) if (!item)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to create CSSUnitValue from input '{}'", to_string())) }; return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to create CSSUnitValue from input '{}'", MUST(to_string()))) };
auto converted_item = item->converted_to_unit(unit); auto converted_item = item->converted_to_unit(unit);
if (!converted_item) if (!converted_item)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to convert input '{}' to unit '{}'", to_string(), unit)) }; return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Unable to convert input '{}' to unit '{}'", MUST(to_string()), unit)) };
// 4. Return item. // 4. Return item.
return converted_item.as_nonnull(); return converted_item.as_nonnull();

View file

@ -10,6 +10,7 @@
#include <LibWeb/Bindings/CSSNumericValuePrototype.h> #include <LibWeb/Bindings/CSSNumericValuePrototype.h>
#include <LibWeb/CSS/CSSStyleValue.h> #include <LibWeb/CSS/CSSStyleValue.h>
#include <LibWeb/CSS/NumericType.h> #include <LibWeb/CSS/NumericType.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
#include <LibWeb/WebIDL/Types.h> #include <LibWeb/WebIDL/Types.h>
namespace Web::CSS { namespace Web::CSS {
@ -59,7 +60,7 @@ public:
CSSNumericType type_for_bindings() const; CSSNumericType type_for_bindings() const;
NumericType const& type() const { return m_type; } NumericType const& type() const { return m_type; }
virtual String to_string() const final override { return to_string({}); } virtual WebIDL::ExceptionOr<String> to_string() const final override { return to_string({}); }
String to_string(SerializationParams const&) const; String to_string(SerializationParams const&) const;
static WebIDL::ExceptionOr<GC::Ref<CSSNumericValue>> parse(JS::VM&, String const& css_text); static WebIDL::ExceptionOr<GC::Ref<CSSNumericValue>> parse(JS::VM&, String const& css_text);

View file

@ -89,13 +89,13 @@ WebIDL::ExceptionOr<Utf16String> CSSPerspective::to_string() const
builder.append("perspective("sv); builder.append("perspective("sv);
// 2. Serialize thiss length internal slot, with a minimum of 0px, and append it to s. // 2. Serialize thiss length internal slot, with a minimum of 0px, and append it to s.
auto serialized_length = m_length.visit( auto serialized_length = TRY(m_length.visit(
[](GC::Ref<CSSNumericValue> const& numeric_value) { [](GC::Ref<CSSNumericValue> const& numeric_value) -> WebIDL::ExceptionOr<String> {
return numeric_value->to_string({ .minimum = 0 }); return numeric_value->to_string({ .minimum = 0 });
}, },
[](GC::Ref<CSSKeywordValue> const& keyword_value) { [](GC::Ref<CSSKeywordValue> const& keyword_value) -> WebIDL::ExceptionOr<String> {
return keyword_value->to_string(); return keyword_value->to_string();
}); }));
builder.append(serialized_length); builder.append(serialized_length);
// 3. Append ")" to s, and return s. // 3. Append ")" to s, and return s.

View file

@ -104,25 +104,25 @@ WebIDL::ExceptionOr<Utf16String> CSSRotate::to_string() const
builder.append("rotate3d("sv); builder.append("rotate3d("sv);
// 2. Serialize thiss x internal slot, and append it to s. // 2. Serialize thiss x internal slot, and append it to s.
builder.append(m_x->to_string()); builder.append(TRY(m_x->to_string()));
// 3. Append ", " to s. // 3. Append ", " to s.
builder.append(", "sv); builder.append(", "sv);
// 4. Serialize thiss y internal slot, and append it to s. // 4. Serialize thiss y internal slot, and append it to s.
builder.append(m_y->to_string()); builder.append(TRY(m_y->to_string()));
// 5. Append ", " to s. // 5. Append ", " to s.
builder.append(", "sv); builder.append(", "sv);
// 6. Serialize thiss z internal slot, and append it to s. // 6. Serialize thiss z internal slot, and append it to s.
builder.append(m_z->to_string()); builder.append(TRY(m_z->to_string()));
// 7. Append "," to s. // 7. Append "," to s.
builder.append(", "sv); builder.append(", "sv);
// 8. Serialize thiss angle internal slot, and append it to s. // 8. Serialize thiss angle internal slot, and append it to s.
builder.append(m_angle->to_string()); builder.append(TRY(m_angle->to_string()));
// 9. Append ")" to s, and return s. // 9. Append ")" to s, and return s.
builder.append(")"sv); builder.append(")"sv);
@ -134,7 +134,7 @@ WebIDL::ExceptionOr<Utf16String> CSSRotate::to_string() const
builder.append("rotate("sv); builder.append("rotate("sv);
// 2. Serialize thiss angle internal slot, and append it to s. // 2. Serialize thiss angle internal slot, and append it to s.
builder.append(m_angle->to_string()); builder.append(TRY(m_angle->to_string()));
// 3. Append ")" to s, and return s. // 3. Append ")" to s, and return s.
builder.append(")"sv); builder.append(")"sv);

View file

@ -89,19 +89,19 @@ WebIDL::ExceptionOr<Utf16String> CSSScale::to_string() const
builder.append("scale3d("sv); builder.append("scale3d("sv);
// 2. Serialize thiss x internal slot, and append it to s. // 2. Serialize thiss x internal slot, and append it to s.
builder.append(m_x->to_string()); builder.append(TRY(m_x->to_string()));
// 3. Append ", " to s. // 3. Append ", " to s.
builder.append(", "sv); builder.append(", "sv);
// 4. Serialize thiss y internal slot, and append it to s. // 4. Serialize thiss y internal slot, and append it to s.
builder.append(m_y->to_string()); builder.append(TRY(m_y->to_string()));
// 5. Append ", " to s. // 5. Append ", " to s.
builder.append(", "sv); builder.append(", "sv);
// 6. Serialize thiss z internal slot, and append it to s. // 6. Serialize thiss z internal slot, and append it to s.
builder.append(m_z->to_string()); builder.append(TRY(m_z->to_string()));
// 7. Append ")" to s, and return s. // 7. Append ")" to s, and return s.
builder.append(")"sv); builder.append(")"sv);
@ -114,7 +114,7 @@ WebIDL::ExceptionOr<Utf16String> CSSScale::to_string() const
builder.append("scale("sv); builder.append("scale("sv);
// 2. Serialize thiss x internal slot, and append it to s. // 2. Serialize thiss x internal slot, and append it to s.
builder.append(m_x->to_string()); builder.append(TRY(m_x->to_string()));
// 3. If thiss x and y internal slots are equal numeric values, append ")" to s and return s. // 3. If thiss x and y internal slots are equal numeric values, append ")" to s and return s.
if (m_x->is_equal_numeric_value(m_y)) { if (m_x->is_equal_numeric_value(m_y)) {
@ -126,7 +126,7 @@ WebIDL::ExceptionOr<Utf16String> CSSScale::to_string() const
builder.append(", "sv); builder.append(", "sv);
// 5. Serialize thiss y internal slot, and append it to s. // 5. Serialize thiss y internal slot, and append it to s.
builder.append(m_y->to_string()); builder.append(TRY(m_y->to_string()));
// 6. Append ")" to s, and return s. // 6. Append ")" to s, and return s.
builder.append(")"sv); builder.append(")"sv);

View file

@ -67,7 +67,7 @@ WebIDL::ExceptionOr<Utf16String> CSSSkew::to_string() const
builder.append("skew("sv); builder.append("skew("sv);
// 2. Serialize thiss ax internal slot, and append it to s. // 2. Serialize thiss ax internal slot, and append it to s.
builder.append(m_ax->to_string()); builder.append(TRY(m_ax->to_string()));
// 3. If thiss ay internal slot is a CSSUnitValue with a value of 0, then append ")" to s and return s. // 3. If thiss ay internal slot is a CSSUnitValue with a value of 0, then append ")" to s and return s.
if (auto* ay_unit_value = as_if<CSSUnitValue>(*m_ay); ay_unit_value && ay_unit_value->value() == 0) { if (auto* ay_unit_value = as_if<CSSUnitValue>(*m_ay); ay_unit_value && ay_unit_value->value() == 0) {
@ -79,7 +79,7 @@ WebIDL::ExceptionOr<Utf16String> CSSSkew::to_string() const
builder.append(", "sv); builder.append(", "sv);
// 5. Serialize thiss ay internal slot, and append it to s. // 5. Serialize thiss ay internal slot, and append it to s.
builder.append(m_ay->to_string()); builder.append(TRY(m_ay->to_string()));
// 6. Append ")" to s, and return s. // 6. Append ")" to s, and return s.
builder.append(")"sv); builder.append(")"sv);

View file

@ -62,7 +62,7 @@ WebIDL::ExceptionOr<Utf16String> CSSSkewX::to_string() const
builder.append("skewX("sv); builder.append("skewX("sv);
// 2. Serialize thiss ax internal slot, and append it to s. // 2. Serialize thiss ax internal slot, and append it to s.
builder.append(m_ax->to_string()); builder.append(TRY(m_ax->to_string()));
// 3. Append ")" to s, and return s. // 3. Append ")" to s, and return s.
builder.append(")"sv); builder.append(")"sv);

View file

@ -62,7 +62,7 @@ WebIDL::ExceptionOr<Utf16String> CSSSkewY::to_string() const
builder.append("skewY("sv); builder.append("skewY("sv);
// 2. Serialize thiss ay internal slot, and append it to s. // 2. Serialize thiss ay internal slot, and append it to s.
builder.append(m_ay->to_string()); builder.append(TRY(m_ay->to_string()));
// 3. Append ")" to s, and return s. // 3. Append ")" to s, and return s.
builder.append(")"sv); builder.append(")"sv);

View file

@ -92,7 +92,7 @@ WebIDL::ExceptionOr<Variant<GC::Ref<CSSStyleValue>, GC::RootVector<GC::Ref<CSSSt
} }
// https://drafts.css-houdini.org/css-typed-om-1/#stylevalue-serialization // https://drafts.css-houdini.org/css-typed-om-1/#stylevalue-serialization
String CSSStyleValue::to_string() const WebIDL::ExceptionOr<String> CSSStyleValue::to_string() const
{ {
// if the value was constructed from a USVString // if the value was constructed from a USVString
if (m_constructed_from_string.has_value()) { if (m_constructed_from_string.has_value()) {
@ -108,7 +108,7 @@ String CSSStyleValue::to_string() const
{ {
// the serialization is specified in §6.7 Serialization from CSSOM Values below. // the serialization is specified in §6.7 Serialization from CSSOM Values below.
} }
return {}; return String {};
} }
} }

View file

@ -25,7 +25,7 @@ public:
static WebIDL::ExceptionOr<GC::Ref<CSSStyleValue>> parse(JS::VM&, String property, String css_text); static WebIDL::ExceptionOr<GC::Ref<CSSStyleValue>> parse(JS::VM&, String property, String css_text);
static WebIDL::ExceptionOr<GC::RootVector<GC::Ref<CSSStyleValue>>> parse_all(JS::VM&, String property, String css_text); static WebIDL::ExceptionOr<GC::RootVector<GC::Ref<CSSStyleValue>>> parse_all(JS::VM&, String property, String css_text);
virtual String to_string() const; virtual WebIDL::ExceptionOr<String> to_string() const;
protected: protected:
explicit CSSStyleValue(JS::Realm&); explicit CSSStyleValue(JS::Realm&);

View file

@ -87,19 +87,19 @@ WebIDL::ExceptionOr<Utf16String> CSSTranslate::to_string() const
builder.append("translate3d("sv); builder.append("translate3d("sv);
// 2. Serialize thiss x internal slot, and append it to s. // 2. Serialize thiss x internal slot, and append it to s.
builder.append(m_x->to_string()); builder.append(TRY(m_x->to_string()));
// 3. Append ", " to s. // 3. Append ", " to s.
builder.append(", "sv); builder.append(", "sv);
// 4. Serialize thiss y internal slot, and append it to s. // 4. Serialize thiss y internal slot, and append it to s.
builder.append(m_y->to_string()); builder.append(TRY(m_y->to_string()));
// 5. Append ", " to s. // 5. Append ", " to s.
builder.append(", "sv); builder.append(", "sv);
// 6. Serialize thiss z internal slot, and append it to s. // 6. Serialize thiss z internal slot, and append it to s.
builder.append(m_z->to_string()); builder.append(TRY(m_z->to_string()));
// 7. Append ")" to s, and return s. // 7. Append ")" to s, and return s.
builder.append(")"sv); builder.append(")"sv);
@ -111,13 +111,13 @@ WebIDL::ExceptionOr<Utf16String> CSSTranslate::to_string() const
builder.append("translate("sv); builder.append("translate("sv);
// 2. Serialize thiss x internal slot, and append it to s. // 2. Serialize thiss x internal slot, and append it to s.
builder.append(m_x->to_string()); builder.append(TRY(m_x->to_string()));
// 3. Append ", " to s. // 3. Append ", " to s.
builder.append(", "sv); builder.append(", "sv);
// 4. Serialize thiss y internal slot, and append it to s. // 4. Serialize thiss y internal slot, and append it to s.
builder.append(m_y->to_string()); builder.append(TRY(m_y->to_string()));
// 5. Append ")" to s, and return s. // 5. Append ")" to s, and return s.
builder.append(")"sv); builder.append(")"sv);

View file

@ -116,7 +116,7 @@ WebIDL::ExceptionOr<void> CSSUnparsedValue::set_value_of_new_indexed_property(u3
} }
// https://drafts.css-houdini.org/css-typed-om-1/#serialize-a-cssunparsedvalue // https://drafts.css-houdini.org/css-typed-om-1/#serialize-a-cssunparsedvalue
String CSSUnparsedValue::to_string() const WebIDL::ExceptionOr<String> CSSUnparsedValue::to_string() const
{ {
// To serialize a CSSUnparsedValue this: // To serialize a CSSUnparsedValue this:
// 1. Let s initially be the empty string. // 1. Let s initially be the empty string.
@ -126,15 +126,17 @@ String CSSUnparsedValue::to_string() const
for (auto const& item : m_tokens) { for (auto const& item : m_tokens) {
// FIXME: In order to match the expected test behaviour, this should insert comments, with the same rules as // FIXME: In order to match the expected test behaviour, this should insert comments, with the same rules as
// serialize_a_series_of_component_values(). See https://github.com/w3c/css-houdini-drafts/issues/1148 // serialize_a_series_of_component_values(). See https://github.com/w3c/css-houdini-drafts/issues/1148
item.visit( TRY(item.visit(
// 1. If item is a USVString, append it to s. // 1. If item is a USVString, append it to s.
[&](String const& string) { [&](String const& string) -> WebIDL::ExceptionOr<void> {
s.append(string); s.append(string);
return {};
}, },
// 2. Otherwise, item is a CSSVariableReferenceValue. Serialize it, then append the result to s. // 2. Otherwise, item is a CSSVariableReferenceValue. Serialize it, then append the result to s.
[&](GC::Ref<CSSVariableReferenceValue> const& variable) { [&](GC::Ref<CSSVariableReferenceValue> const& variable) -> WebIDL::ExceptionOr<void> {
s.append(variable->to_string()); s.append(TRY(variable->to_string()));
}); return {};
}));
} }
// 3. Return s. // 3. Return s.

View file

@ -30,7 +30,7 @@ public:
virtual WebIDL::ExceptionOr<void> set_value_of_existing_indexed_property(u32, JS::Value) override; virtual WebIDL::ExceptionOr<void> set_value_of_existing_indexed_property(u32, JS::Value) override;
virtual WebIDL::ExceptionOr<void> set_value_of_new_indexed_property(u32, JS::Value) override; virtual WebIDL::ExceptionOr<void> set_value_of_new_indexed_property(u32, JS::Value) override;
virtual String to_string() const override; virtual WebIDL::ExceptionOr<String> to_string() const override;
private: private:
explicit CSSUnparsedValue(JS::Realm&, Vector<CSSUnparsedSegment>); explicit CSSUnparsedValue(JS::Realm&, Vector<CSSUnparsedSegment>);

View file

@ -89,7 +89,7 @@ WebIDL::ExceptionOr<void> CSSVariableReferenceValue::set_fallback(GC::Ptr<CSSUnp
} }
// https://drafts.css-houdini.org/css-typed-om-1/#serialize-a-cssvariablereferencevalue // https://drafts.css-houdini.org/css-typed-om-1/#serialize-a-cssvariablereferencevalue
String CSSVariableReferenceValue::to_string() const WebIDL::ExceptionOr<String> CSSVariableReferenceValue::to_string() const
{ {
// To serialize a CSSVariableReferenceValue this: // To serialize a CSSVariableReferenceValue this:
// 1. Let s initially be "var(". // 1. Let s initially be "var(".
@ -103,7 +103,7 @@ String CSSVariableReferenceValue::to_string() const
if (m_fallback) { if (m_fallback) {
// AD-HOC: Tested behaviour requires we append "," without the space. https://github.com/w3c/css-houdini-drafts/issues/1148 // AD-HOC: Tested behaviour requires we append "," without the space. https://github.com/w3c/css-houdini-drafts/issues/1148
s.append(","sv); s.append(","sv);
s.append(m_fallback->to_string()); s.append(TRY(m_fallback->to_string()));
} }
// 4. Append ")" to s and return s. // 4. Append ")" to s and return s.

View file

@ -27,7 +27,7 @@ public:
GC::Ptr<CSSUnparsedValue> fallback() const; GC::Ptr<CSSUnparsedValue> fallback() const;
WebIDL::ExceptionOr<void> set_fallback(GC::Ptr<CSSUnparsedValue>); WebIDL::ExceptionOr<void> set_fallback(GC::Ptr<CSSUnparsedValue>);
String to_string() const; WebIDL::ExceptionOr<String> to_string() const;
private: private:
CSSVariableReferenceValue(JS::Realm&, FlyString variable, GC::Ptr<CSSUnparsedValue> fallback); CSSVariableReferenceValue(JS::Realm&, FlyString variable, GC::Ptr<CSSUnparsedValue> fallback);