LibWeb/CSS: Implement CSSTranslate

Equivalent to the translate() transform functions.

+34 WPT subtests.
This commit is contained in:
Sam Atkins 2025-09-15 11:27:45 +01:00
parent 8e86bf2dd0
commit c7d22d8cfd
Notes: github-actions[bot] 2025-09-24 11:29:48 +00:00
12 changed files with 285 additions and 40 deletions

View file

@ -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

View file

@ -0,0 +1,181 @@
/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "CSSTranslate.h"
#include <LibWeb/Bindings/CSSTranslatePrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/CSSNumericValue.h>
#include <LibWeb/CSS/CSSUnitValue.h>
#include <LibWeb/Geometry/DOMMatrix.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::CSS {
GC_DEFINE_ALLOCATOR(CSSTranslate);
GC::Ref<CSSTranslate> CSSTranslate::create(JS::Realm& realm, Is2D is_2d, GC::Ref<CSSNumericValue> x, GC::Ref<CSSNumericValue> y, GC::Ref<CSSNumericValue> z)
{
return realm.create<CSSTranslate>(realm, is_2d, x, y, z);
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-csstranslate-csstranslate
WebIDL::ExceptionOr<GC::Ref<CSSTranslate>> CSSTranslate::construct_impl(JS::Realm& realm, GC::Ref<CSSNumericValue> x, GC::Ref<CSSNumericValue> y, GC::Ptr<CSSNumericValue> z)
{
// The CSSTranslate(x, y, z) constructor must, when invoked, perform the following steps:
// 1. If x or y dont match <length-percentage>, throw a TypeError.
if (!x->type().matches_length_percentage({}))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSTranslate x component doesn't match <length-percentage>"sv };
if (!y->type().matches_length_percentage({}))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSTranslate y component doesn't match <length-percentage>"sv };
// 2. If z was passed, but doesnt match <length>, throw a TypeError.
if (z && !z->type().matches_length({}))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSTranslate z component doesn't match <length>"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 thiss z internal slot to z, and set thiss is2D internal slot to false.
// 5. If z was not passed, set thiss z internal slot to a new unit value of (0, "px"), and set thiss 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<CSSTranslate>(realm, is_2d, x, y, z.as_nonnull());
// 6. Return this.
return this_;
}
CSSTranslate::CSSTranslate(JS::Realm& realm, Is2D is_2d, GC::Ref<CSSNumericValue> x, GC::Ref<CSSNumericValue> y, GC::Ref<CSSNumericValue> 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<Utf16String> CSSTranslate::to_string() const
{
// 1. Let s initially be the empty string.
StringBuilder builder { StringBuilder::Mode::UTF16 };
// 2. If thiss is2D internal slot is false:
if (!is_2d()) {
// 1. Append "translate3d(" to s.
builder.append("translate3d("sv);
// 2. Serialize thiss x internal slot, and append it to s.
builder.append(m_x->to_string());
// 3. Append ", " to s.
builder.append(", "sv);
// 4. Serialize thiss y internal slot, and append it to s.
builder.append(m_y->to_string());
// 5. Append ", " to s.
builder.append(", "sv);
// 6. Serialize thiss 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 thiss x internal slot, and append it to s.
builder.append(m_x->to_string());
// 3. Append ", " to s.
builder.append(", "sv);
// 4. Serialize thiss 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<GC::Ref<Geometry::DOMMatrix>> CSSTranslate::to_matrix() const
{
// 1. Let matrix be a new DOMMatrix object, initialized to thiss 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 thiss 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 <length>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<void> CSSTranslate::set_x(GC::Ref<CSSNumericValue> 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 <length-percentage>"sv };
m_x = x;
return {};
}
WebIDL::ExceptionOr<void> CSSTranslate::set_y(GC::Ref<CSSNumericValue> 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 <length-percentage>"sv };
m_y = y;
return {};
}
WebIDL::ExceptionOr<void> CSSTranslate::set_z(GC::Ref<CSSNumericValue> 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 <length>"sv };
m_z = z;
return {};
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/CSS/CSSTransformComponent.h>
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<CSSTranslate> create(JS::Realm&, Is2D, GC::Ref<CSSNumericValue> x, GC::Ref<CSSNumericValue> y, GC::Ref<CSSNumericValue> z);
static WebIDL::ExceptionOr<GC::Ref<CSSTranslate>> construct_impl(JS::Realm&, GC::Ref<CSSNumericValue> x, GC::Ref<CSSNumericValue> y, GC::Ptr<CSSNumericValue> z = {});
virtual ~CSSTranslate() override;
virtual WebIDL::ExceptionOr<Utf16String> to_string() const override;
virtual WebIDL::ExceptionOr<GC::Ref<Geometry::DOMMatrix>> to_matrix() const override;
GC::Ref<CSSNumericValue> x() const { return m_x; }
GC::Ref<CSSNumericValue> y() const { return m_y; }
GC::Ref<CSSNumericValue> z() const { return m_z; }
WebIDL::ExceptionOr<void> set_x(GC::Ref<CSSNumericValue> value);
WebIDL::ExceptionOr<void> set_y(GC::Ref<CSSNumericValue> value);
WebIDL::ExceptionOr<void> set_z(GC::Ref<CSSNumericValue> value);
private:
explicit CSSTranslate(JS::Realm&, Is2D, GC::Ref<CSSNumericValue> x, GC::Ref<CSSNumericValue> y, GC::Ref<CSSNumericValue> z);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Visitor&) override;
GC::Ref<CSSNumericValue> m_x;
GC::Ref<CSSNumericValue> m_y;
GC::Ref<CSSNumericValue> m_z;
};
}

View file

@ -0,0 +1,11 @@
#import <CSS/CSSNumericValue.idl>
#import <CSS/CSSTransformComponent.idl>
// 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;
};

View file

@ -268,6 +268,7 @@ class CSSStyleSheet;
class CSSStyleValue;
class CSSSupportsRule;
class CSSTransformComponent;
class CSSTranslate;
class CSSUnitValue;
class CSSUnparsedValue;
class CSSVariableReferenceValue;

View file

@ -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)

View file

@ -76,6 +76,7 @@ CSSStyleValue
CSSSupportsRule
CSSTransformComponent
CSSTransition
CSSTranslate
CSSUnitValue
CSSUnparsedValue
CSSVariableReferenceValue

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 <length-percentage> 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 <length-percentage> 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 <length-percentage> 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 <length-percentage> 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
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 <length-percentage> 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 <length-percentage> 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 <length-percentage> 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 <length-percentage> 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