diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index e98e31d076a..c1c0a672abc 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -144,6 +144,7 @@ set(SOURCES CSS/CSSSupportsRule.cpp CSS/CSSTransformComponent.cpp CSS/CSSTransition.cpp + CSS/CSSTranslate.cpp CSS/CSSUnitValue.cpp CSS/CSSUnparsedValue.cpp CSS/CSSVariableReferenceValue.cpp diff --git a/Libraries/LibWeb/CSS/CSSTranslate.cpp b/Libraries/LibWeb/CSS/CSSTranslate.cpp new file mode 100644 index 00000000000..b282743c092 --- /dev/null +++ b/Libraries/LibWeb/CSS/CSSTranslate.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2025, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "CSSTranslate.h" +#include +#include +#include +#include +#include +#include + +namespace Web::CSS { + +GC_DEFINE_ALLOCATOR(CSSTranslate); + +GC::Ref CSSTranslate::create(JS::Realm& realm, Is2D is_2d, GC::Ref x, GC::Ref y, GC::Ref z) +{ + return realm.create(realm, is_2d, x, y, z); +} + +// https://drafts.css-houdini.org/css-typed-om-1/#dom-csstranslate-csstranslate +WebIDL::ExceptionOr> CSSTranslate::construct_impl(JS::Realm& realm, GC::Ref x, GC::Ref y, GC::Ptr z) +{ + // The CSSTranslate(x, y, z) constructor must, when invoked, perform the following steps: + + // 1. If x or y don’t match , throw a TypeError. + if (!x->type().matches_length_percentage({})) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSTranslate x component doesn't match "sv }; + + if (!y->type().matches_length_percentage({})) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSTranslate y component doesn't match "sv }; + + // 2. If z was passed, but doesn’t match , throw a TypeError. + if (z && !z->type().matches_length({})) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSTranslate z component doesn't match "sv }; + + // 3. Let this be a new CSSTranslate object, with its x and y internal slots set to x and y. + // 4. If z was passed, set this’s z internal slot to z, and set this’s is2D internal slot to false. + // 5. If z was not passed, set this’s z internal slot to a new unit value of (0, "px"), and set this’s is2D internal slot to true. + Is2D is_2d = Is2D::No; + if (!z) { + is_2d = Is2D::Yes; + z = CSSUnitValue::create(realm, 0, "px"_fly_string); + } + auto this_ = realm.create(realm, is_2d, x, y, z.as_nonnull()); + + // 6. Return this. + return this_; +} + +CSSTranslate::CSSTranslate(JS::Realm& realm, Is2D is_2d, GC::Ref x, GC::Ref y, GC::Ref z) + : CSSTransformComponent(realm, is_2d) + , m_x(x) + , m_y(y) + , m_z(z) +{ +} + +CSSTranslate::~CSSTranslate() = default; + +void CSSTranslate::initialize(JS::Realm& realm) +{ + WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSTranslate); + Base::initialize(realm); +} + +void CSSTranslate::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_x); + visitor.visit(m_y); + visitor.visit(m_z); +} + +// https://drafts.css-houdini.org/css-typed-om-1/#serialize-a-csstranslate +WebIDL::ExceptionOr CSSTranslate::to_string() const +{ + // 1. Let s initially be the empty string. + StringBuilder builder { StringBuilder::Mode::UTF16 }; + + // 2. If this’s is2D internal slot is false: + if (!is_2d()) { + // 1. Append "translate3d(" to s. + builder.append("translate3d("sv); + + // 2. Serialize this’s x internal slot, and append it to s. + builder.append(m_x->to_string()); + + // 3. Append ", " to s. + builder.append(", "sv); + + // 4. Serialize this’s y internal slot, and append it to s. + builder.append(m_y->to_string()); + + // 5. Append ", " to s. + builder.append(", "sv); + + // 6. Serialize this’s z internal slot, and append it to s. + builder.append(m_z->to_string()); + + // 7. Append ")" to s, and return s. + builder.append(")"sv); + return builder.to_utf16_string(); + } + // 3. Otherwise: + else { + // 1. Append "translate(" to s. + builder.append("translate("sv); + + // 2. Serialize this’s x internal slot, and append it to s. + builder.append(m_x->to_string()); + + // 3. Append ", " to s. + builder.append(", "sv); + + // 4. Serialize this’s y internal slot, and append it to s. + builder.append(m_y->to_string()); + + // 5. Append ")" to s, and return s. + builder.append(")"sv); + return builder.to_utf16_string(); + } +} + +// https://drafts.css-houdini.org/css-typed-om-1/#dom-csstransformcomponent-tomatrix +WebIDL::ExceptionOr> CSSTranslate::to_matrix() const +{ + // 1. Let matrix be a new DOMMatrix object, initialized to this’s equivalent 4x4 transform matrix, as defined in + // CSS Transforms 1 § 12. Mathematical Description of Transform Functions, and with its is2D internal slot set + // to the same value as this’s is2D internal slot. + // NOTE: Recall that the is2D flag affects what transform, and thus what equivalent matrix, a + // CSSTransformComponent represents. + // As the entries of such a matrix are defined relative to the px unit, if any s in this involved in + // generating the matrix are not compatible units with px (such as relative lengths or percentages), throw a + // TypeError. + auto matrix = Geometry::DOMMatrix::create(realm()); + + // NB: to() throws a TypeError if the conversion can't be done. + matrix->set_m41(TRY(m_x->to("px"_fly_string))->value()); + matrix->set_m42(TRY(m_y->to("px"_fly_string))->value()); + if (!is_2d()) + matrix->set_m43(TRY(m_z->to("px"_fly_string))->value()); + + // 2. Return matrix. + return matrix; +} + +WebIDL::ExceptionOr CSSTranslate::set_x(GC::Ref x) +{ + // AD-HOC: Not specced. https://github.com/w3c/css-houdini-drafts/issues/1153 + // WPT expects this to throw for invalid values. + if (!x->type().matches_length_percentage({})) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSTranslate x component doesn't match "sv }; + m_x = x; + return {}; +} + +WebIDL::ExceptionOr CSSTranslate::set_y(GC::Ref y) +{ + // AD-HOC: Not specced. https://github.com/w3c/css-houdini-drafts/issues/1153 + // WPT expects this to throw for invalid values. + if (!y->type().matches_length_percentage({})) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSTranslate y component doesn't match "sv }; + m_y = y; + return {}; +} + +WebIDL::ExceptionOr CSSTranslate::set_z(GC::Ref z) +{ + // AD-HOC: Not specced. https://github.com/w3c/css-houdini-drafts/issues/1153 + // WPT expects this to throw for invalid values. + if (!z->type().matches_length({})) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSTranslate z component doesn't match "sv }; + m_z = z; + return {}; +} + +} diff --git a/Libraries/LibWeb/CSS/CSSTranslate.h b/Libraries/LibWeb/CSS/CSSTranslate.h new file mode 100644 index 00000000000..e303b88d0c1 --- /dev/null +++ b/Libraries/LibWeb/CSS/CSSTranslate.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::CSS { + +// https://drafts.css-houdini.org/css-typed-om-1/#csstranslate +class CSSTranslate final : public CSSTransformComponent { + WEB_PLATFORM_OBJECT(CSSTranslate, CSSTransformComponent); + GC_DECLARE_ALLOCATOR(CSSTranslate); + +public: + [[nodiscard]] static GC::Ref create(JS::Realm&, Is2D, GC::Ref x, GC::Ref y, GC::Ref z); + static WebIDL::ExceptionOr> construct_impl(JS::Realm&, GC::Ref x, GC::Ref y, GC::Ptr z = {}); + + virtual ~CSSTranslate() override; + + virtual WebIDL::ExceptionOr to_string() const override; + + virtual WebIDL::ExceptionOr> to_matrix() const override; + + GC::Ref x() const { return m_x; } + GC::Ref y() const { return m_y; } + GC::Ref z() const { return m_z; } + WebIDL::ExceptionOr set_x(GC::Ref value); + WebIDL::ExceptionOr set_y(GC::Ref value); + WebIDL::ExceptionOr set_z(GC::Ref value); + +private: + explicit CSSTranslate(JS::Realm&, Is2D, GC::Ref x, GC::Ref y, GC::Ref z); + + virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Visitor&) override; + + GC::Ref m_x; + GC::Ref m_y; + GC::Ref m_z; +}; + +} diff --git a/Libraries/LibWeb/CSS/CSSTranslate.idl b/Libraries/LibWeb/CSS/CSSTranslate.idl new file mode 100644 index 00000000000..e344e893206 --- /dev/null +++ b/Libraries/LibWeb/CSS/CSSTranslate.idl @@ -0,0 +1,11 @@ +#import +#import + +// https://drafts.css-houdini.org/css-typed-om-1/#csstranslate +[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)] +interface CSSTranslate : CSSTransformComponent { + constructor(CSSNumericValue x, CSSNumericValue y, optional CSSNumericValue z); + attribute CSSNumericValue x; + attribute CSSNumericValue y; + attribute CSSNumericValue z; +}; diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 7fec4a5ff7f..8735dd53f5b 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -268,6 +268,7 @@ class CSSStyleSheet; class CSSStyleValue; class CSSSupportsRule; class CSSTransformComponent; +class CSSTranslate; class CSSUnitValue; class CSSUnparsedValue; class CSSVariableReferenceValue; diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index e419a55867d..6fa213a36dc 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -64,6 +64,7 @@ libweb_js_bindings(CSS/CSSStyleValue) libweb_js_bindings(CSS/CSSSupportsRule) libweb_js_bindings(CSS/CSSTransformComponent) libweb_js_bindings(CSS/CSSTransition) +libweb_js_bindings(CSS/CSSTranslate) libweb_js_bindings(CSS/CSSUnitValue) libweb_js_bindings(CSS/CSSUnparsedValue) libweb_js_bindings(CSS/CSSVariableReferenceValue) diff --git a/Tests/LibWeb/Text/expected/all-window-properties.txt b/Tests/LibWeb/Text/expected/all-window-properties.txt index 7d534d791d4..c46a319535e 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -76,6 +76,7 @@ CSSStyleValue CSSSupportsRule CSSTransformComponent CSSTransition +CSSTranslate CSSUnitValue CSSUnparsedValue CSSVariableReferenceValue diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/idlharness.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/idlharness.txt index 87cfcfbc77d..ba850ae09a5 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/idlharness.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/idlharness.txt @@ -2,8 +2,8 @@ Harness status: OK Found 545 tests -296 Pass -249 Fail +305 Pass +240 Fail Pass idl_test setup Pass idl_test validation Pass Partial interface Element: original interface defined @@ -276,15 +276,15 @@ Pass CSSTransformComponent interface: existence and properties of interface prot Pass CSSTransformComponent interface: stringifier Pass CSSTransformComponent interface: attribute is2D Pass CSSTransformComponent interface: operation toMatrix() -Fail CSSTranslate interface: existence and properties of interface object -Fail CSSTranslate interface object length -Fail CSSTranslate interface object name -Fail CSSTranslate interface: existence and properties of interface prototype object -Fail CSSTranslate interface: existence and properties of interface prototype object's "constructor" property -Fail CSSTranslate interface: existence and properties of interface prototype object's @@unscopables property -Fail CSSTranslate interface: attribute x -Fail CSSTranslate interface: attribute y -Fail CSSTranslate interface: attribute z +Pass CSSTranslate interface: existence and properties of interface object +Pass CSSTranslate interface object length +Pass CSSTranslate interface object name +Pass CSSTranslate interface: existence and properties of interface prototype object +Pass CSSTranslate interface: existence and properties of interface prototype object's "constructor" property +Pass CSSTranslate interface: existence and properties of interface prototype object's @@unscopables property +Pass CSSTranslate interface: attribute x +Pass CSSTranslate interface: attribute y +Pass CSSTranslate interface: attribute z Fail CSSTranslate must be primary interface of transformValue[0] Fail Stringification of transformValue[0] Fail CSSTranslate interface: transformValue[0] must inherit property "x" with the proper type diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-2d-flattening.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-2d-flattening.txt index 15befd3443e..fcc9a612eae 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-2d-flattening.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-2d-flattening.txt @@ -2,8 +2,9 @@ Harness status: OK Found 4 tests -4 Fail -Fail CSSTranslate.toMatrix() flattens when told it is 2d +1 Pass +3 Fail +Pass CSSTranslate.toMatrix() flattens when told it is 2d Fail CSSRotate.toMatrix() flattens when told it is 2d Fail CSSScale.toMatrix() flattens when told it is 2d Fail CSSMatrixComponent.toMatrix() flattens when told it is 2d \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-toMatrix-relative-units.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-toMatrix-relative-units.txt index 45b29683e83..654544bee94 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-toMatrix-relative-units.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-toMatrix-relative-units.txt @@ -2,6 +2,7 @@ Harness status: OK Found 2 tests -2 Fail -Fail CSSTranslate.toMatrix() containing relative units throws TypeError +1 Pass +1 Fail +Pass CSSTranslate.toMatrix() containing relative units throws TypeError Fail CSSPerspective.toMatrix() containing relative units throws TypeError \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-toMatrix.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-toMatrix.txt index 2fa1407e86f..c164901b3ee 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-toMatrix.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-toMatrix.txt @@ -2,8 +2,9 @@ Harness status: OK Found 8 tests -8 Fail -Fail CSSTranslate.toMatrix() returns correct matrix +1 Pass +7 Fail +Pass CSSTranslate.toMatrix() returns correct matrix Fail CSSRotate.toMatrix() returns correct matrix Fail CSSScale.toMatrix() returns correct matrix Fail CSSSkew.toMatrix() returns correct matrix diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTranslate.tentative.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTranslate.tentative.txt index fd3815eb55e..a217a8c6738 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTranslate.tentative.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTranslate.tentative.txt @@ -2,26 +2,26 @@ Harness status: OK Found 22 tests -22 Fail -Fail Constructing a CSSTranslate with a CSSUnitValue with type other than length or percent for the coordinates throws a TypeError -Fail Constructing a CSSTranslate with a CSSMathValue that doesn't match for the coordinates throws a TypeError -Fail Constructing a CSSTranslate with a percent for the Z coordinate throws a TypeError -Fail Updating CSSTranslate.x to a CSSUnitValue with type other than length or percent throws a TypeError -Fail Updating CSSTranslate.x to a CSSMathValue that doesn't match throws a TypeError -Fail Updating CSSTranslate.y to a CSSUnitValue with type other than length or percent throws a TypeError -Fail Updating CSSTranslate.y to a CSSMathValue that doesn't match throws a TypeError -Fail Updating CSSTranslate.z to a CSSUnitValue with type other than length or percent throws a TypeError -Fail Updating CSSTranslate.z to a CSSMathValue that doesn't match throws a TypeError -Fail Updating CSSTranslate.z to a percent throws a TypeError -Fail CSSTranslate can be constructed from two length or percent coordinates -Fail CSSTranslate can be constructed from three length or percent coordinates -Fail CSSTranslate can be constructed from CSSMathValues -Fail CSSTranslate.x can be updated to a length -Fail CSSTranslate.x can be updated to a percent -Fail CSSTranslate.x can be updated to a CSSMathValue -Fail CSSTranslate.y can be updated to a length -Fail CSSTranslate.y can be updated to a percent -Fail CSSTranslate.y can be updated to a CSSMathValue -Fail CSSTranslate.z can be updated to a length -Fail CSSTranslate.z can be updated to a CSSMathValue -Fail Modifying CSSTranslate.is2D can be updated to true or false \ No newline at end of file +22 Pass +Pass Constructing a CSSTranslate with a CSSUnitValue with type other than length or percent for the coordinates throws a TypeError +Pass Constructing a CSSTranslate with a CSSMathValue that doesn't match for the coordinates throws a TypeError +Pass Constructing a CSSTranslate with a percent for the Z coordinate throws a TypeError +Pass Updating CSSTranslate.x to a CSSUnitValue with type other than length or percent throws a TypeError +Pass Updating CSSTranslate.x to a CSSMathValue that doesn't match throws a TypeError +Pass Updating CSSTranslate.y to a CSSUnitValue with type other than length or percent throws a TypeError +Pass Updating CSSTranslate.y to a CSSMathValue that doesn't match throws a TypeError +Pass Updating CSSTranslate.z to a CSSUnitValue with type other than length or percent throws a TypeError +Pass Updating CSSTranslate.z to a CSSMathValue that doesn't match throws a TypeError +Pass Updating CSSTranslate.z to a percent throws a TypeError +Pass CSSTranslate can be constructed from two length or percent coordinates +Pass CSSTranslate can be constructed from three length or percent coordinates +Pass CSSTranslate can be constructed from CSSMathValues +Pass CSSTranslate.x can be updated to a length +Pass CSSTranslate.x can be updated to a percent +Pass CSSTranslate.x can be updated to a CSSMathValue +Pass CSSTranslate.y can be updated to a length +Pass CSSTranslate.y can be updated to a percent +Pass CSSTranslate.y can be updated to a CSSMathValue +Pass CSSTranslate.z can be updated to a length +Pass CSSTranslate.z can be updated to a CSSMathValue +Pass Modifying CSSTranslate.is2D can be updated to true or false \ No newline at end of file