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/CSSSupportsRule.cpp
CSS/CSSTransformComponent.cpp CSS/CSSTransformComponent.cpp
CSS/CSSTransition.cpp CSS/CSSTransition.cpp
CSS/CSSTranslate.cpp
CSS/CSSUnitValue.cpp CSS/CSSUnitValue.cpp
CSS/CSSUnparsedValue.cpp CSS/CSSUnparsedValue.cpp
CSS/CSSVariableReferenceValue.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 CSSStyleValue;
class CSSSupportsRule; class CSSSupportsRule;
class CSSTransformComponent; class CSSTransformComponent;
class CSSTranslate;
class CSSUnitValue; class CSSUnitValue;
class CSSUnparsedValue; class CSSUnparsedValue;
class CSSVariableReferenceValue; class CSSVariableReferenceValue;

View file

@ -64,6 +64,7 @@ libweb_js_bindings(CSS/CSSStyleValue)
libweb_js_bindings(CSS/CSSSupportsRule) libweb_js_bindings(CSS/CSSSupportsRule)
libweb_js_bindings(CSS/CSSTransformComponent) libweb_js_bindings(CSS/CSSTransformComponent)
libweb_js_bindings(CSS/CSSTransition) libweb_js_bindings(CSS/CSSTransition)
libweb_js_bindings(CSS/CSSTranslate)
libweb_js_bindings(CSS/CSSUnitValue) libweb_js_bindings(CSS/CSSUnitValue)
libweb_js_bindings(CSS/CSSUnparsedValue) libweb_js_bindings(CSS/CSSUnparsedValue)
libweb_js_bindings(CSS/CSSVariableReferenceValue) libweb_js_bindings(CSS/CSSVariableReferenceValue)

View file

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

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 545 tests Found 545 tests
296 Pass 305 Pass
249 Fail 240 Fail
Pass idl_test setup Pass idl_test setup
Pass idl_test validation Pass idl_test validation
Pass Partial interface Element: original interface defined 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: stringifier
Pass CSSTransformComponent interface: attribute is2D Pass CSSTransformComponent interface: attribute is2D
Pass CSSTransformComponent interface: operation toMatrix() Pass CSSTransformComponent interface: operation toMatrix()
Fail CSSTranslate interface: existence and properties of interface object Pass CSSTranslate interface: existence and properties of interface object
Fail CSSTranslate interface object length Pass CSSTranslate interface object length
Fail CSSTranslate interface object name Pass CSSTranslate interface object name
Fail CSSTranslate interface: existence and properties of interface prototype object Pass CSSTranslate interface: existence and properties of interface prototype object
Fail CSSTranslate interface: existence and properties of interface prototype object's "constructor" property Pass 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 Pass CSSTranslate interface: existence and properties of interface prototype object's @@unscopables property
Fail CSSTranslate interface: attribute x Pass CSSTranslate interface: attribute x
Fail CSSTranslate interface: attribute y Pass CSSTranslate interface: attribute y
Fail CSSTranslate interface: attribute z Pass CSSTranslate interface: attribute z
Fail CSSTranslate must be primary interface of transformValue[0] Fail CSSTranslate must be primary interface of transformValue[0]
Fail Stringification of transformValue[0] Fail Stringification of transformValue[0]
Fail CSSTranslate interface: transformValue[0] must inherit property "x" with the proper type 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 Found 4 tests
4 Fail 1 Pass
Fail CSSTranslate.toMatrix() flattens when told it is 2d 3 Fail
Pass CSSTranslate.toMatrix() flattens when told it is 2d
Fail CSSRotate.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 CSSScale.toMatrix() flattens when told it is 2d
Fail CSSMatrixComponent.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 Found 2 tests
2 Fail 1 Pass
Fail CSSTranslate.toMatrix() containing relative units throws TypeError 1 Fail
Pass CSSTranslate.toMatrix() containing relative units throws TypeError
Fail CSSPerspective.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 Found 8 tests
8 Fail 1 Pass
Fail CSSTranslate.toMatrix() returns correct matrix 7 Fail
Pass CSSTranslate.toMatrix() returns correct matrix
Fail CSSRotate.toMatrix() returns correct matrix Fail CSSRotate.toMatrix() returns correct matrix
Fail CSSScale.toMatrix() returns correct matrix Fail CSSScale.toMatrix() returns correct matrix
Fail CSSSkew.toMatrix() returns correct matrix Fail CSSSkew.toMatrix() returns correct matrix

View file

@ -2,26 +2,26 @@ Harness status: OK
Found 22 tests Found 22 tests
22 Fail 22 Pass
Fail 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 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 Pass 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 Pass 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 Pass 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 Pass 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 Pass 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 Pass 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 Pass 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 Pass 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 Pass Updating CSSTranslate.z to a percent throws a TypeError
Fail CSSTranslate can be constructed from two length or percent coordinates Pass CSSTranslate can be constructed from two length or percent coordinates
Fail CSSTranslate can be constructed from three length or percent coordinates Pass CSSTranslate can be constructed from three length or percent coordinates
Fail CSSTranslate can be constructed from CSSMathValues Pass CSSTranslate can be constructed from CSSMathValues
Fail CSSTranslate.x can be updated to a length Pass CSSTranslate.x can be updated to a length
Fail CSSTranslate.x can be updated to a percent Pass CSSTranslate.x can be updated to a percent
Fail CSSTranslate.x can be updated to a CSSMathValue Pass CSSTranslate.x can be updated to a CSSMathValue
Fail CSSTranslate.y can be updated to a length Pass CSSTranslate.y can be updated to a length
Fail CSSTranslate.y can be updated to a percent Pass CSSTranslate.y can be updated to a percent
Fail CSSTranslate.y can be updated to a CSSMathValue Pass CSSTranslate.y can be updated to a CSSMathValue
Fail CSSTranslate.z can be updated to a length Pass CSSTranslate.z can be updated to a length
Fail CSSTranslate.z can be updated to a CSSMathValue Pass CSSTranslate.z can be updated to a CSSMathValue
Fail Modifying CSSTranslate.is2D can be updated to true or false Pass Modifying CSSTranslate.is2D can be updated to true or false