LibWeb/CSS: Implement CSSRotate

Equivalent to the rotate() transform functions.

+39 WPT subtests.
This commit is contained in:
Sam Atkins 2025-09-15 12:58:09 +01:00
commit d348d8d9b8
Notes: github-actions[bot] 2025-09-24 11:29:41 +00:00
11 changed files with 335 additions and 46 deletions

View file

@ -134,6 +134,7 @@ set(SOURCES
CSS/CSSPageRule.cpp CSS/CSSPageRule.cpp
CSS/CSSPageDescriptors.cpp CSS/CSSPageDescriptors.cpp
CSS/CSSPropertyRule.cpp CSS/CSSPropertyRule.cpp
CSS/CSSRotate.cpp
CSS/CSSRule.cpp CSS/CSSRule.cpp
CSS/CSSRuleList.cpp CSS/CSSRuleList.cpp
CSS/CSSStyleDeclaration.cpp CSS/CSSStyleDeclaration.cpp

View file

@ -0,0 +1,221 @@
/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "CSSRotate.h"
#include <LibWeb/Bindings/CSSRotatePrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/CSSUnitValue.h>
#include <LibWeb/Geometry/DOMMatrix.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::CSS {
GC_DEFINE_ALLOCATOR(CSSRotate);
GC::Ref<CSSRotate> CSSRotate::create(JS::Realm& realm, Is2D is_2d, GC::Ref<CSSNumericValue> x, GC::Ref<CSSNumericValue> y, GC::Ref<CSSNumericValue> z, GC::Ref<CSSNumericValue> angle)
{
return realm.create<CSSRotate>(realm, is_2d, x, y, z, angle);
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-cssrotate-cssrotate
WebIDL::ExceptionOr<GC::Ref<CSSRotate>> CSSRotate::construct_impl(JS::Realm& realm, GC::Ref<CSSNumericValue> angle)
{
// The CSSRotate(angle) constructor must, when invoked, perform the following steps:
// 1. If angle doesnt match <angle>, throw a TypeError.
if (!angle->type().matches_angle({}))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSRotate angle component doesn't match <angle>"sv };
// 2. Return a new CSSRotate with its angle internal slot set to angle, its x and y internal slots set to new unit
// values of (0, "number"), its z internal slot set to a new unit value of (1, "number"), and its is2D internal
// slot set to true.
return realm.create<CSSRotate>(realm, Is2D::Yes,
CSSUnitValue::create(realm, 0, "number"_fly_string),
CSSUnitValue::create(realm, 0, "number"_fly_string),
CSSUnitValue::create(realm, 1, "number"_fly_string),
angle);
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-cssrotate-cssrotate-x-y-z-anglec
WebIDL::ExceptionOr<GC::Ref<CSSRotate>> CSSRotate::construct_impl(JS::Realm& realm, CSSNumberish x, CSSNumberish y, CSSNumberish z, GC::Ref<CSSNumericValue> angle)
{
// The CSSRotate(x, y, z, angle) constructor must, when invoked, perform the following steps:
// 1. If angle doesnt match <angle>, throw a TypeError.
if (!angle->type().matches_angle({}))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSRotate angle component doesn't match <angle>"sv };
// 2. Let x, y, and z be replaced by the result of rectifying a numberish value.
auto rectified_x = rectify_a_numberish_value(realm, x);
auto rectified_y = rectify_a_numberish_value(realm, y);
auto rectified_z = rectify_a_numberish_value(realm, z);
// 3. If x, y, or z dont match <number>, throw a TypeError.
if (!rectified_x->type().matches_number({}))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSRotate x component doesn't match <number>"sv };
if (!rectified_y->type().matches_number({}))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSRotate y component doesn't match <number>"sv };
if (!rectified_z->type().matches_number({}))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSRotate z component doesn't match <number>"sv };
// 4. Return a new CSSRotate with its angle internal slot set to angle, its x, y, z internal slots set to x, y, and
// z, and its is2D internal slot set to false.
return realm.create<CSSRotate>(realm, Is2D::No, rectified_x, rectified_y, rectified_z, angle);
}
CSSRotate::CSSRotate(JS::Realm& realm, Is2D is_2d, GC::Ref<CSSNumericValue> x, GC::Ref<CSSNumericValue> y, GC::Ref<CSSNumericValue> z, GC::Ref<CSSNumericValue> angle)
: CSSTransformComponent(realm, is_2d)
, m_x(x)
, m_y(y)
, m_z(z)
, m_angle(angle)
{
}
CSSRotate::~CSSRotate() = default;
void CSSRotate::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSRotate);
Base::initialize(realm);
}
void CSSRotate::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_x);
visitor.visit(m_y);
visitor.visit(m_z);
visitor.visit(m_angle);
}
// https://drafts.css-houdini.org/css-typed-om-1/#serialize-a-cssrotate
WebIDL::ExceptionOr<Utf16String> CSSRotate::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 "rotate3d(" to s.
builder.append("rotate3d("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.
builder.append(", "sv);
// 8. Serialize thiss angle internal slot, and append it to s.
builder.append(m_angle->to_string());
// 9. Append ")" to s, and return s.
builder.append(")"sv);
return builder.to_utf16_string();
}
// 2. Otherwise:
else {
// 1. Append "rotate(" to s.
builder.append("rotate("sv);
// 2. Serialize thiss angle internal slot, and append it to s.
builder.append(m_angle->to_string());
// 3. 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>> CSSRotate::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.
// 2. Return matrix.
auto matrix = Geometry::DOMMatrix::create(realm());
// NB: to() throws a TypeError if the conversion can't be done.
auto angle = TRY(m_angle->to("deg"_fly_string))->value();
if (is_2d())
return matrix->rotate_axis_angle_self(0, 0, 1, angle);
auto x = TRY(m_x->to("number"_fly_string))->value();
auto y = TRY(m_y->to("number"_fly_string))->value();
auto z = TRY(m_z->to("number"_fly_string))->value();
return matrix->rotate_axis_angle_self(x, y, z, angle);
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-cssrotate-x
WebIDL::ExceptionOr<void> CSSRotate::set_x(CSSNumberish value)
{
// The x, y, and z attributes must, on setting to a new value val, rectify a numberish value from val and set the
// corresponding internal slot to the result of that.
// AD-HOC: WPT expects this to throw for invalid values. https://github.com/w3c/css-houdini-drafts/issues/1153
auto rectified_x = rectify_a_numberish_value(realm(), value);
if (!rectified_x->type().matches_number({}))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSRotate x component doesn't match <number>"sv };
m_x = rectified_x;
return {};
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-cssrotate-y
WebIDL::ExceptionOr<void> CSSRotate::set_y(CSSNumberish value)
{
// The x, y, and z attributes must, on setting to a new value val, rectify a numberish value from val and set the
// corresponding internal slot to the result of that.
// AD-HOC: WPT expects this to throw for invalid values. https://github.com/w3c/css-houdini-drafts/issues/1153
auto rectified_y = rectify_a_numberish_value(realm(), value);
if (!rectified_y->type().matches_number({}))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSRotate y component doesn't match <number>"sv };
m_y = rectified_y;
return {};
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-cssrotate-z
WebIDL::ExceptionOr<void> CSSRotate::set_z(CSSNumberish value)
{
// The x, y, and z attributes must, on setting to a new value val, rectify a numberish value from val and set the
// corresponding internal slot to the result of that.
// AD-HOC: WPT expects this to throw for invalid values. https://github.com/w3c/css-houdini-drafts/issues/1153
auto rectified_z = rectify_a_numberish_value(realm(), value);
if (!rectified_z->type().matches_number({}))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSRotate z component doesn't match <number>"sv };
m_z = rectified_z;
return {};
}
WebIDL::ExceptionOr<void> CSSRotate::set_angle(GC::Ref<CSSNumericValue> value)
{
// AD-HOC: Not specced. WPT expects this to throw for invalid values. https://github.com/w3c/css-houdini-drafts/issues/1153
if (!value->type().matches_angle({}))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSRotate angle component doesn't match <angle>"sv };
m_angle = value;
return {};
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/CSS/CSSNumericValue.h>
#include <LibWeb/CSS/CSSTransformComponent.h>
namespace Web::CSS {
// https://drafts.css-houdini.org/css-typed-om-1/#cssrotate
class CSSRotate final : public CSSTransformComponent {
WEB_PLATFORM_OBJECT(CSSRotate, CSSTransformComponent);
GC_DECLARE_ALLOCATOR(CSSRotate);
public:
[[nodiscard]] static GC::Ref<CSSRotate> create(JS::Realm&, Is2D, GC::Ref<CSSNumericValue> x, GC::Ref<CSSNumericValue> y, GC::Ref<CSSNumericValue> z, GC::Ref<CSSNumericValue> angle);
static WebIDL::ExceptionOr<GC::Ref<CSSRotate>> construct_impl(JS::Realm&, GC::Ref<CSSNumericValue> angle);
static WebIDL::ExceptionOr<GC::Ref<CSSRotate>> construct_impl(JS::Realm&, CSSNumberish x, CSSNumberish y, CSSNumberish z, GC::Ref<CSSNumericValue> angle);
virtual ~CSSRotate() override;
virtual WebIDL::ExceptionOr<Utf16String> to_string() const override;
virtual WebIDL::ExceptionOr<GC::Ref<Geometry::DOMMatrix>> to_matrix() const override;
CSSNumberish x() const { return GC::Root { m_x }; }
CSSNumberish y() const { return GC::Root { m_y }; }
CSSNumberish z() const { return GC::Root { m_z }; }
GC::Ref<CSSNumericValue> angle() const { return m_angle; }
WebIDL::ExceptionOr<void> set_x(CSSNumberish value);
WebIDL::ExceptionOr<void> set_y(CSSNumberish value);
WebIDL::ExceptionOr<void> set_z(CSSNumberish value);
WebIDL::ExceptionOr<void> set_angle(GC::Ref<CSSNumericValue> value);
private:
explicit CSSRotate(JS::Realm&, Is2D, GC::Ref<CSSNumericValue> x, GC::Ref<CSSNumericValue> y, GC::Ref<CSSNumericValue> z, GC::Ref<CSSNumericValue> angle);
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;
GC::Ref<CSSNumericValue> m_angle;
};
}

View file

@ -0,0 +1,13 @@
#import <CSS/CSSNumericValue.idl>
#import <CSS/CSSTransformComponent.idl>
// https://drafts.css-houdini.org/css-typed-om-1/#cssrotate
[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
interface CSSRotate : CSSTransformComponent {
constructor(CSSNumericValue angle);
constructor(CSSNumberish x, CSSNumberish y, CSSNumberish z, CSSNumericValue angle);
attribute CSSNumberish x;
attribute CSSNumberish y;
attribute CSSNumberish z;
attribute CSSNumericValue angle;
};

View file

@ -259,6 +259,7 @@ class CSSNumericValue;
class CSSPageRule; class CSSPageRule;
class CSSPageDescriptors; class CSSPageDescriptors;
class CSSPropertyRule; class CSSPropertyRule;
class CSSRotate;
class CSSRule; class CSSRule;
class CSSRuleList; class CSSRuleList;
class CSSStyleDeclaration; class CSSStyleDeclaration;

View file

@ -54,6 +54,7 @@ libweb_js_bindings(CSS/CSSNumericValue)
libweb_js_bindings(CSS/CSSPageRule) libweb_js_bindings(CSS/CSSPageRule)
libweb_js_bindings(CSS/CSSPageDescriptors) libweb_js_bindings(CSS/CSSPageDescriptors)
libweb_js_bindings(CSS/CSSPropertyRule) libweb_js_bindings(CSS/CSSPropertyRule)
libweb_js_bindings(CSS/CSSRotate)
libweb_js_bindings(CSS/CSSRule) libweb_js_bindings(CSS/CSSRule)
libweb_js_bindings(CSS/CSSRuleList) libweb_js_bindings(CSS/CSSRuleList)
libweb_js_bindings(CSS/CSSStyleDeclaration) libweb_js_bindings(CSS/CSSStyleDeclaration)

View file

@ -66,6 +66,7 @@ CSSNumericValue
CSSPageDescriptors CSSPageDescriptors
CSSPageRule CSSPageRule
CSSPropertyRule CSSPropertyRule
CSSRotate
CSSRule CSSRule
CSSRuleList CSSRuleList
CSSStyleDeclaration CSSStyleDeclaration

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 545 tests Found 545 tests
305 Pass 315 Pass
240 Fail 230 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
@ -292,16 +292,16 @@ Fail CSSTranslate interface: transformValue[0] must inherit property "y" with th
Fail CSSTranslate interface: transformValue[0] must inherit property "z" with the proper type Fail CSSTranslate interface: transformValue[0] must inherit property "z" with the proper type
Fail CSSTransformComponent interface: transformValue[0] must inherit property "is2D" with the proper type Fail CSSTransformComponent interface: transformValue[0] must inherit property "is2D" with the proper type
Fail CSSTransformComponent interface: transformValue[0] must inherit property "toMatrix()" with the proper type Fail CSSTransformComponent interface: transformValue[0] must inherit property "toMatrix()" with the proper type
Fail CSSRotate interface: existence and properties of interface object Pass CSSRotate interface: existence and properties of interface object
Fail CSSRotate interface object length Pass CSSRotate interface object length
Fail CSSRotate interface object name Pass CSSRotate interface object name
Fail CSSRotate interface: existence and properties of interface prototype object Pass CSSRotate interface: existence and properties of interface prototype object
Fail CSSRotate interface: existence and properties of interface prototype object's "constructor" property Pass CSSRotate interface: existence and properties of interface prototype object's "constructor" property
Fail CSSRotate interface: existence and properties of interface prototype object's @@unscopables property Pass CSSRotate interface: existence and properties of interface prototype object's @@unscopables property
Fail CSSRotate interface: attribute x Pass CSSRotate interface: attribute x
Fail CSSRotate interface: attribute y Pass CSSRotate interface: attribute y
Fail CSSRotate interface: attribute z Pass CSSRotate interface: attribute z
Fail CSSRotate interface: attribute angle Pass CSSRotate interface: attribute angle
Fail CSSRotate must be primary interface of rotate Fail CSSRotate must be primary interface of rotate
Fail Stringification of rotate Fail Stringification of rotate
Fail CSSRotate interface: rotate must inherit property "x" with the proper type Fail CSSRotate interface: rotate must inherit property "x" with the proper type

View file

@ -2,31 +2,31 @@ Harness status: OK
Found 27 tests Found 27 tests
27 Fail 27 Pass
Fail Constructing a CSSRotate with a CSSUnitValue with type other than angle for the angle throws a TypeError Pass Constructing a CSSRotate with a CSSUnitValue with type other than angle for the angle throws a TypeError
Fail Constructing a CSSRotate with a CSSMathValue that doesn't match <angle> for the angle throws a TypeError Pass Constructing a CSSRotate with a CSSMathValue that doesn't match <angle> for the angle throws a TypeError
Fail Constructing a CSSRotate with a CSSUnitValue with type other than number for the coordinates throws a TypeError Pass Constructing a CSSRotate with a CSSUnitValue with type other than number for the coordinates throws a TypeError
Fail Constructing a CSSRotate with a CSSMathValue that doesn't match <number> for the coordinates throws a TypeError Pass Constructing a CSSRotate with a CSSMathValue that doesn't match <number> for the coordinates throws a TypeError
Fail Updating CSSRotate.x to a CSSUnitValue with type other than number throws a TypeError Pass Updating CSSRotate.x to a CSSUnitValue with type other than number throws a TypeError
Fail Updating CSSRotate.x to a CSSMathValue that doesn't match <number> throws a TypeError Pass Updating CSSRotate.x to a CSSMathValue that doesn't match <number> throws a TypeError
Fail Updating CSSRotate.y to a CSSUnitValue with type other than number throws a TypeError Pass Updating CSSRotate.y to a CSSUnitValue with type other than number throws a TypeError
Fail Updating CSSRotate.y to a CSSMathValue that doesn't match <number> throws a TypeError Pass Updating CSSRotate.y to a CSSMathValue that doesn't match <number> throws a TypeError
Fail Updating CSSRotate.z to a CSSUnitValue with type other than number throws a TypeError Pass Updating CSSRotate.z to a CSSUnitValue with type other than number throws a TypeError
Fail Updating CSSRotate.z to a CSSMathValue that doesn't match <number> throws a TypeError Pass Updating CSSRotate.z to a CSSMathValue that doesn't match <number> throws a TypeError
Fail Updating CSSRotate.angle to a CSSUnitValue with type other than angle throws a TypeError Pass Updating CSSRotate.angle to a CSSUnitValue with type other than angle throws a TypeError
Fail Updating CSSRotate.angle to a CSSMathValue that doesn't match <angle> throws a TypeError Pass Updating CSSRotate.angle to a CSSMathValue that doesn't match <angle> throws a TypeError
Fail CSSRotate can be constructed from a single angle Pass CSSRotate can be constructed from a single angle
Fail CSSRotate can be constructed from numberish coordinates Pass CSSRotate can be constructed from numberish coordinates
Fail CSSRotate can be constructed from CSSMathValues Pass CSSRotate can be constructed from CSSMathValues
Fail CSSRotate.x can be updated to a double Pass CSSRotate.x can be updated to a double
Fail CSSRotate.x can be updated to a number CSSUnitValue Pass CSSRotate.x can be updated to a number CSSUnitValue
Fail CSSRotate.x can be updated to a CSSMathValue matching <number> Pass CSSRotate.x can be updated to a CSSMathValue matching <number>
Fail CSSRotate.y can be updated to a double Pass CSSRotate.y can be updated to a double
Fail CSSRotate.y can be updated to a number CSSUnitValue Pass CSSRotate.y can be updated to a number CSSUnitValue
Fail CSSRotate.y can be updated to a CSSMathValue matching <number> Pass CSSRotate.y can be updated to a CSSMathValue matching <number>
Fail CSSRotate.z can be updated to a double Pass CSSRotate.z can be updated to a double
Fail CSSRotate.z can be updated to a number CSSUnitValue Pass CSSRotate.z can be updated to a number CSSUnitValue
Fail CSSRotate.z can be updated to a CSSMathValue matching <number> Pass CSSRotate.z can be updated to a CSSMathValue matching <number>
Fail CSSRotate.angle can be updated to a degree CSSUnitValue Pass CSSRotate.angle can be updated to a degree CSSUnitValue
Fail CSSRotate.angle can be updated to a CSSMathValue matching <angle> Pass CSSRotate.angle can be updated to a CSSMathValue matching <angle>
Fail Modifying CSSRotate.is2D can be updated to true or false Pass Modifying CSSRotate.is2D can be updated to true or false

View file

@ -2,9 +2,9 @@ Harness status: OK
Found 4 tests Found 4 tests
1 Pass 2 Pass
3 Fail 2 Fail
Pass CSSTranslate.toMatrix() flattens when told it is 2d Pass CSSTranslate.toMatrix() flattens when told it is 2d
Fail CSSRotate.toMatrix() flattens when told it is 2d Pass 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,10 +2,10 @@ Harness status: OK
Found 8 tests Found 8 tests
1 Pass 2 Pass
7 Fail 6 Fail
Pass CSSTranslate.toMatrix() returns correct matrix Pass CSSTranslate.toMatrix() returns correct matrix
Fail CSSRotate.toMatrix() returns correct matrix Pass 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
Fail CSSSkewX.toMatrix() returns correct matrix Fail CSSSkewX.toMatrix() returns correct matrix