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
parent c7d22d8cfd
commit d348d8d9b8
11 changed files with 335 additions and 46 deletions

View File

@ -134,6 +134,7 @@ set(SOURCES
CSS/CSSPageRule.cpp
CSS/CSSPageDescriptors.cpp
CSS/CSSPropertyRule.cpp
CSS/CSSRotate.cpp
CSS/CSSRule.cpp
CSS/CSSRuleList.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 CSSPageDescriptors;
class CSSPropertyRule;
class CSSRotate;
class CSSRule;
class CSSRuleList;
class CSSStyleDeclaration;

View File

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

View File

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

View File

@ -2,8 +2,8 @@ Harness status: OK
Found 545 tests
305 Pass
240 Fail
315 Pass
230 Fail
Pass idl_test setup
Pass idl_test validation
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 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 CSSRotate interface: existence and properties of interface object
Fail CSSRotate interface object length
Fail CSSRotate interface object name
Fail CSSRotate interface: existence and properties of interface prototype object
Fail 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
Fail CSSRotate interface: attribute x
Fail CSSRotate interface: attribute y
Fail CSSRotate interface: attribute z
Fail CSSRotate interface: attribute angle
Pass CSSRotate interface: existence and properties of interface object
Pass CSSRotate interface object length
Pass CSSRotate interface object name
Pass CSSRotate interface: existence and properties of interface prototype object
Pass CSSRotate interface: existence and properties of interface prototype object's "constructor" property
Pass CSSRotate interface: existence and properties of interface prototype object's @@unscopables property
Pass CSSRotate interface: attribute x
Pass CSSRotate interface: attribute y
Pass CSSRotate interface: attribute z
Pass CSSRotate interface: attribute angle
Fail CSSRotate must be primary interface of rotate
Fail Stringification of rotate
Fail CSSRotate interface: rotate must inherit property "x" with the proper type

View File

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

View File

@ -2,9 +2,9 @@ Harness status: OK
Found 4 tests
1 Pass
3 Fail
2 Pass
2 Fail
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 CSSMatrixComponent.toMatrix() flattens when told it is 2d

View File

@ -2,10 +2,10 @@ Harness status: OK
Found 8 tests
1 Pass
7 Fail
2 Pass
6 Fail
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 CSSSkew.toMatrix() returns correct matrix
Fail CSSSkewX.toMatrix() returns correct matrix