Skip to content

Commit c7d22d8

Browse files
committed
LibWeb/CSS: Implement CSSTranslate
Equivalent to the translate() transform functions. +34 WPT subtests.
1 parent 8e86bf2 commit c7d22d8

File tree

12 files changed

+285
-40
lines changed

12 files changed

+285
-40
lines changed

Libraries/LibWeb/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ set(SOURCES
144144
CSS/CSSSupportsRule.cpp
145145
CSS/CSSTransformComponent.cpp
146146
CSS/CSSTransition.cpp
147+
CSS/CSSTranslate.cpp
147148
CSS/CSSUnitValue.cpp
148149
CSS/CSSUnparsedValue.cpp
149150
CSS/CSSVariableReferenceValue.cpp
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/*
2+
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include "CSSTranslate.h"
8+
#include <LibWeb/Bindings/CSSTranslatePrototype.h>
9+
#include <LibWeb/Bindings/Intrinsics.h>
10+
#include <LibWeb/CSS/CSSNumericValue.h>
11+
#include <LibWeb/CSS/CSSUnitValue.h>
12+
#include <LibWeb/Geometry/DOMMatrix.h>
13+
#include <LibWeb/WebIDL/ExceptionOr.h>
14+
15+
namespace Web::CSS {
16+
17+
GC_DEFINE_ALLOCATOR(CSSTranslate);
18+
19+
GC::Ref<CSSTranslate> CSSTranslate::create(JS::Realm& realm, Is2D is_2d, GC::Ref<CSSNumericValue> x, GC::Ref<CSSNumericValue> y, GC::Ref<CSSNumericValue> z)
20+
{
21+
return realm.create<CSSTranslate>(realm, is_2d, x, y, z);
22+
}
23+
24+
// https://drafts.css-houdini.org/css-typed-om-1/#dom-csstranslate-csstranslate
25+
WebIDL::ExceptionOr<GC::Ref<CSSTranslate>> CSSTranslate::construct_impl(JS::Realm& realm, GC::Ref<CSSNumericValue> x, GC::Ref<CSSNumericValue> y, GC::Ptr<CSSNumericValue> z)
26+
{
27+
// The CSSTranslate(x, y, z) constructor must, when invoked, perform the following steps:
28+
29+
// 1. If x or y don’t match <length-percentage>, throw a TypeError.
30+
if (!x->type().matches_length_percentage({}))
31+
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSTranslate x component doesn't match <length-percentage>"sv };
32+
33+
if (!y->type().matches_length_percentage({}))
34+
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSTranslate y component doesn't match <length-percentage>"sv };
35+
36+
// 2. If z was passed, but doesn’t match <length>, throw a TypeError.
37+
if (z && !z->type().matches_length({}))
38+
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSTranslate z component doesn't match <length>"sv };
39+
40+
// 3. Let this be a new CSSTranslate object, with its x and y internal slots set to x and y.
41+
// 4. If z was passed, set this’s z internal slot to z, and set this’s is2D internal slot to false.
42+
// 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.
43+
Is2D is_2d = Is2D::No;
44+
if (!z) {
45+
is_2d = Is2D::Yes;
46+
z = CSSUnitValue::create(realm, 0, "px"_fly_string);
47+
}
48+
auto this_ = realm.create<CSSTranslate>(realm, is_2d, x, y, z.as_nonnull());
49+
50+
// 6. Return this.
51+
return this_;
52+
}
53+
54+
CSSTranslate::CSSTranslate(JS::Realm& realm, Is2D is_2d, GC::Ref<CSSNumericValue> x, GC::Ref<CSSNumericValue> y, GC::Ref<CSSNumericValue> z)
55+
: CSSTransformComponent(realm, is_2d)
56+
, m_x(x)
57+
, m_y(y)
58+
, m_z(z)
59+
{
60+
}
61+
62+
CSSTranslate::~CSSTranslate() = default;
63+
64+
void CSSTranslate::initialize(JS::Realm& realm)
65+
{
66+
WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSTranslate);
67+
Base::initialize(realm);
68+
}
69+
70+
void CSSTranslate::visit_edges(Visitor& visitor)
71+
{
72+
Base::visit_edges(visitor);
73+
visitor.visit(m_x);
74+
visitor.visit(m_y);
75+
visitor.visit(m_z);
76+
}
77+
78+
// https://drafts.css-houdini.org/css-typed-om-1/#serialize-a-csstranslate
79+
WebIDL::ExceptionOr<Utf16String> CSSTranslate::to_string() const
80+
{
81+
// 1. Let s initially be the empty string.
82+
StringBuilder builder { StringBuilder::Mode::UTF16 };
83+
84+
// 2. If this’s is2D internal slot is false:
85+
if (!is_2d()) {
86+
// 1. Append "translate3d(" to s.
87+
builder.append("translate3d("sv);
88+
89+
// 2. Serialize this’s x internal slot, and append it to s.
90+
builder.append(m_x->to_string());
91+
92+
// 3. Append ", " to s.
93+
builder.append(", "sv);
94+
95+
// 4. Serialize this’s y internal slot, and append it to s.
96+
builder.append(m_y->to_string());
97+
98+
// 5. Append ", " to s.
99+
builder.append(", "sv);
100+
101+
// 6. Serialize this’s z internal slot, and append it to s.
102+
builder.append(m_z->to_string());
103+
104+
// 7. Append ")" to s, and return s.
105+
builder.append(")"sv);
106+
return builder.to_utf16_string();
107+
}
108+
// 3. Otherwise:
109+
else {
110+
// 1. Append "translate(" to s.
111+
builder.append("translate("sv);
112+
113+
// 2. Serialize this’s x internal slot, and append it to s.
114+
builder.append(m_x->to_string());
115+
116+
// 3. Append ", " to s.
117+
builder.append(", "sv);
118+
119+
// 4. Serialize this’s y internal slot, and append it to s.
120+
builder.append(m_y->to_string());
121+
122+
// 5. Append ")" to s, and return s.
123+
builder.append(")"sv);
124+
return builder.to_utf16_string();
125+
}
126+
}
127+
128+
// https://drafts.css-houdini.org/css-typed-om-1/#dom-csstransformcomponent-tomatrix
129+
WebIDL::ExceptionOr<GC::Ref<Geometry::DOMMatrix>> CSSTranslate::to_matrix() const
130+
{
131+
// 1. Let matrix be a new DOMMatrix object, initialized to this’s equivalent 4x4 transform matrix, as defined in
132+
// CSS Transforms 1 § 12. Mathematical Description of Transform Functions, and with its is2D internal slot set
133+
// to the same value as this’s is2D internal slot.
134+
// NOTE: Recall that the is2D flag affects what transform, and thus what equivalent matrix, a
135+
// CSSTransformComponent represents.
136+
// As the entries of such a matrix are defined relative to the px unit, if any <length>s in this involved in
137+
// generating the matrix are not compatible units with px (such as relative lengths or percentages), throw a
138+
// TypeError.
139+
auto matrix = Geometry::DOMMatrix::create(realm());
140+
141+
// NB: to() throws a TypeError if the conversion can't be done.
142+
matrix->set_m41(TRY(m_x->to("px"_fly_string))->value());
143+
matrix->set_m42(TRY(m_y->to("px"_fly_string))->value());
144+
if (!is_2d())
145+
matrix->set_m43(TRY(m_z->to("px"_fly_string))->value());
146+
147+
// 2. Return matrix.
148+
return matrix;
149+
}
150+
151+
WebIDL::ExceptionOr<void> CSSTranslate::set_x(GC::Ref<CSSNumericValue> x)
152+
{
153+
// AD-HOC: Not specced. https://github.com/w3c/css-houdini-drafts/issues/1153
154+
// WPT expects this to throw for invalid values.
155+
if (!x->type().matches_length_percentage({}))
156+
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSTranslate x component doesn't match <length-percentage>"sv };
157+
m_x = x;
158+
return {};
159+
}
160+
161+
WebIDL::ExceptionOr<void> CSSTranslate::set_y(GC::Ref<CSSNumericValue> y)
162+
{
163+
// AD-HOC: Not specced. https://github.com/w3c/css-houdini-drafts/issues/1153
164+
// WPT expects this to throw for invalid values.
165+
if (!y->type().matches_length_percentage({}))
166+
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSTranslate y component doesn't match <length-percentage>"sv };
167+
m_y = y;
168+
return {};
169+
}
170+
171+
WebIDL::ExceptionOr<void> CSSTranslate::set_z(GC::Ref<CSSNumericValue> z)
172+
{
173+
// AD-HOC: Not specced. https://github.com/w3c/css-houdini-drafts/issues/1153
174+
// WPT expects this to throw for invalid values.
175+
if (!z->type().matches_length({}))
176+
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSTranslate z component doesn't match <length>"sv };
177+
m_z = z;
178+
return {};
179+
}
180+
181+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#pragma once
8+
9+
#include <LibWeb/CSS/CSSTransformComponent.h>
10+
11+
namespace Web::CSS {
12+
13+
// https://drafts.css-houdini.org/css-typed-om-1/#csstranslate
14+
class CSSTranslate final : public CSSTransformComponent {
15+
WEB_PLATFORM_OBJECT(CSSTranslate, CSSTransformComponent);
16+
GC_DECLARE_ALLOCATOR(CSSTranslate);
17+
18+
public:
19+
[[nodiscard]] static GC::Ref<CSSTranslate> create(JS::Realm&, Is2D, GC::Ref<CSSNumericValue> x, GC::Ref<CSSNumericValue> y, GC::Ref<CSSNumericValue> z);
20+
static WebIDL::ExceptionOr<GC::Ref<CSSTranslate>> construct_impl(JS::Realm&, GC::Ref<CSSNumericValue> x, GC::Ref<CSSNumericValue> y, GC::Ptr<CSSNumericValue> z = {});
21+
22+
virtual ~CSSTranslate() override;
23+
24+
virtual WebIDL::ExceptionOr<Utf16String> to_string() const override;
25+
26+
virtual WebIDL::ExceptionOr<GC::Ref<Geometry::DOMMatrix>> to_matrix() const override;
27+
28+
GC::Ref<CSSNumericValue> x() const { return m_x; }
29+
GC::Ref<CSSNumericValue> y() const { return m_y; }
30+
GC::Ref<CSSNumericValue> z() const { return m_z; }
31+
WebIDL::ExceptionOr<void> set_x(GC::Ref<CSSNumericValue> value);
32+
WebIDL::ExceptionOr<void> set_y(GC::Ref<CSSNumericValue> value);
33+
WebIDL::ExceptionOr<void> set_z(GC::Ref<CSSNumericValue> value);
34+
35+
private:
36+
explicit CSSTranslate(JS::Realm&, Is2D, GC::Ref<CSSNumericValue> x, GC::Ref<CSSNumericValue> y, GC::Ref<CSSNumericValue> z);
37+
38+
virtual void initialize(JS::Realm&) override;
39+
virtual void visit_edges(Visitor&) override;
40+
41+
GC::Ref<CSSNumericValue> m_x;
42+
GC::Ref<CSSNumericValue> m_y;
43+
GC::Ref<CSSNumericValue> m_z;
44+
};
45+
46+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#import <CSS/CSSNumericValue.idl>
2+
#import <CSS/CSSTransformComponent.idl>
3+
4+
// https://drafts.css-houdini.org/css-typed-om-1/#csstranslate
5+
[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
6+
interface CSSTranslate : CSSTransformComponent {
7+
constructor(CSSNumericValue x, CSSNumericValue y, optional CSSNumericValue z);
8+
attribute CSSNumericValue x;
9+
attribute CSSNumericValue y;
10+
attribute CSSNumericValue z;
11+
};

Libraries/LibWeb/Forward.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ class CSSStyleSheet;
268268
class CSSStyleValue;
269269
class CSSSupportsRule;
270270
class CSSTransformComponent;
271+
class CSSTranslate;
271272
class CSSUnitValue;
272273
class CSSUnparsedValue;
273274
class CSSVariableReferenceValue;

Libraries/LibWeb/idl_files.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ libweb_js_bindings(CSS/CSSStyleValue)
6464
libweb_js_bindings(CSS/CSSSupportsRule)
6565
libweb_js_bindings(CSS/CSSTransformComponent)
6666
libweb_js_bindings(CSS/CSSTransition)
67+
libweb_js_bindings(CSS/CSSTranslate)
6768
libweb_js_bindings(CSS/CSSUnitValue)
6869
libweb_js_bindings(CSS/CSSUnparsedValue)
6970
libweb_js_bindings(CSS/CSSVariableReferenceValue)

Tests/LibWeb/Text/expected/all-window-properties.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ CSSStyleValue
7676
CSSSupportsRule
7777
CSSTransformComponent
7878
CSSTransition
79+
CSSTranslate
7980
CSSUnitValue
8081
CSSUnparsedValue
8182
CSSVariableReferenceValue

Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/idlharness.txt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ Harness status: OK
22

33
Found 545 tests
44

5-
296 Pass
6-
249 Fail
5+
305 Pass
6+
240 Fail
77
Pass idl_test setup
88
Pass idl_test validation
99
Pass Partial interface Element: original interface defined
@@ -276,15 +276,15 @@ Pass CSSTransformComponent interface: existence and properties of interface prot
276276
Pass CSSTransformComponent interface: stringifier
277277
Pass CSSTransformComponent interface: attribute is2D
278278
Pass CSSTransformComponent interface: operation toMatrix()
279-
Fail CSSTranslate interface: existence and properties of interface object
280-
Fail CSSTranslate interface object length
281-
Fail CSSTranslate interface object name
282-
Fail CSSTranslate interface: existence and properties of interface prototype object
283-
Fail CSSTranslate interface: existence and properties of interface prototype object's "constructor" property
284-
Fail CSSTranslate interface: existence and properties of interface prototype object's @@unscopables property
285-
Fail CSSTranslate interface: attribute x
286-
Fail CSSTranslate interface: attribute y
287-
Fail CSSTranslate interface: attribute z
279+
Pass CSSTranslate interface: existence and properties of interface object
280+
Pass CSSTranslate interface object length
281+
Pass CSSTranslate interface object name
282+
Pass CSSTranslate interface: existence and properties of interface prototype object
283+
Pass CSSTranslate interface: existence and properties of interface prototype object's "constructor" property
284+
Pass CSSTranslate interface: existence and properties of interface prototype object's @@unscopables property
285+
Pass CSSTranslate interface: attribute x
286+
Pass CSSTranslate interface: attribute y
287+
Pass CSSTranslate interface: attribute z
288288
Fail CSSTranslate must be primary interface of transformValue[0]
289289
Fail Stringification of transformValue[0]
290290
Fail CSSTranslate interface: transformValue[0] must inherit property "x" with the proper type

Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-2d-flattening.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ Harness status: OK
22

33
Found 4 tests
44

5-
4 Fail
6-
Fail CSSTranslate.toMatrix() flattens when told it is 2d
5+
1 Pass
6+
3 Fail
7+
Pass CSSTranslate.toMatrix() flattens when told it is 2d
78
Fail CSSRotate.toMatrix() flattens when told it is 2d
89
Fail CSSScale.toMatrix() flattens when told it is 2d
910
Fail CSSMatrixComponent.toMatrix() flattens when told it is 2d

Tests/LibWeb/Text/expected/wpt-import/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-toMatrix-relative-units.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ Harness status: OK
22

33
Found 2 tests
44

5-
2 Fail
6-
Fail CSSTranslate.toMatrix() containing relative units throws TypeError
5+
1 Pass
6+
1 Fail
7+
Pass CSSTranslate.toMatrix() containing relative units throws TypeError
78
Fail CSSPerspective.toMatrix() containing relative units throws TypeError

0 commit comments

Comments
 (0)