LibWeb/CSS: Implement CSSSkew

Equivalent to the skew() transform function.

+39 WPT subtests.
This commit is contained in:
Sam Atkins 2025-09-15 15:02:16 +01:00
parent 456946368e
commit 161e384521
10 changed files with 242 additions and 44 deletions

View File

@ -138,6 +138,7 @@ set(SOURCES
CSS/CSSRule.cpp
CSS/CSSRuleList.cpp
CSS/CSSScale.cpp
CSS/CSSSkew.cpp
CSS/CSSStyleDeclaration.cpp
CSS/CSSStyleProperties.cpp
CSS/CSSStyleRule.cpp

View File

@ -0,0 +1,139 @@
/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "CSSSkew.h"
#include <LibWeb/Bindings/CSSSkewPrototype.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(CSSSkew);
GC::Ref<CSSSkew> CSSSkew::create(JS::Realm& realm, GC::Ref<CSSNumericValue> ax, GC::Ref<CSSNumericValue> ay)
{
return realm.create<CSSSkew>(realm, ax, ay);
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-cssskew-cssskew
WebIDL::ExceptionOr<GC::Ref<CSSSkew>> CSSSkew::construct_impl(JS::Realm& realm, GC::Ref<CSSNumericValue> ax, GC::Ref<CSSNumericValue> ay)
{
// The CSSSkew(ax, ay) constructor must, when invoked, perform the following steps:
// 1. If ax or ay do not match <angle>, throw a TypeError.
if (!ax->type().matches_angle({}))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSSkew ax component doesn't match <angle>"sv };
if (!ay->type().matches_angle({}))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSSkew ay component doesn't match <angle>"sv };
// 2. Return a new CSSSkew object with its ax and ay internal slots set to ax and ay, and its is2D internal slot
// set to true.
return CSSSkew::create(realm, ax, ay);
}
CSSSkew::CSSSkew(JS::Realm& realm, GC::Ref<CSSNumericValue> ax, GC::Ref<CSSNumericValue> ay)
: CSSTransformComponent(realm, Is2D::Yes)
, m_ax(ax)
, m_ay(ay)
{
}
CSSSkew::~CSSSkew() = default;
void CSSSkew::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSSkew);
Base::initialize(realm);
}
void CSSSkew::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_ax);
visitor.visit(m_ay);
}
// https://drafts.css-houdini.org/css-typed-om-1/#serialize-a-cssskew
WebIDL::ExceptionOr<Utf16String> CSSSkew::to_string() const
{
// 1. Let s initially be "skew(".
StringBuilder builder { StringBuilder::Mode::UTF16 };
builder.append("skew("sv);
// 2. Serialize thiss ax internal slot, and append it to s.
builder.append(m_ax->to_string());
// 3. If thiss ay internal slot is a CSSUnitValue with a value of 0, then append ")" to s and return s.
if (auto* ay_unit_value = as_if<CSSUnitValue>(*m_ay); ay_unit_value && ay_unit_value->value() == 0) {
builder.append(")"sv);
return builder.to_utf16_string();
}
// 4. Otherwise, append ", " to s.
builder.append(", "sv);
// 5. Serialize thiss ay internal slot, and append it to s.
builder.append(m_ay->to_string());
// 6. 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>> CSSSkew::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.
auto ax_rad = TRY(m_ax->to("rad"_fly_string))->value();
auto ay_rad = TRY(m_ay->to("rad"_fly_string))->value();
matrix->set_m21(tanf(ax_rad));
matrix->set_m12(tanf(ay_rad));
// 2. Return matrix.
return matrix;
}
WebIDL::ExceptionOr<void> CSSSkew::set_ax(GC::Ref<CSSNumericValue> ax)
{
// AD-HOC: Not specced. https://github.com/w3c/css-houdini-drafts/issues/1153
// WPT expects this to throw for invalid values.
if (!ax->type().matches_angle({}))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSSkew ax component doesn't match <angle>"sv };
m_ax = ax;
return {};
}
WebIDL::ExceptionOr<void> CSSSkew::set_ay(GC::Ref<CSSNumericValue> ay)
{
// AD-HOC: Not specced. https://github.com/w3c/css-houdini-drafts/issues/1153
// WPT expects this to throw for invalid values.
if (!ay->type().matches_angle({}))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSSkew ay component doesn't match <angle>"sv };
m_ay = ay;
return {};
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-cssskew-is2d
void CSSSkew::set_is_2d(bool)
{
// The is2D attribute of a CSSSkew, CSSSkewX, or CSSSkewY object must, on setting, do nothing.
}
}

View File

@ -0,0 +1,45 @@
/*
* 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/#cssskew
class CSSSkew final : public CSSTransformComponent {
WEB_PLATFORM_OBJECT(CSSSkew, CSSTransformComponent);
GC_DECLARE_ALLOCATOR(CSSSkew);
public:
[[nodiscard]] static GC::Ref<CSSSkew> create(JS::Realm&, GC::Ref<CSSNumericValue> ax, GC::Ref<CSSNumericValue> ay);
static WebIDL::ExceptionOr<GC::Ref<CSSSkew>> construct_impl(JS::Realm&, GC::Ref<CSSNumericValue> ax, GC::Ref<CSSNumericValue> ay);
virtual ~CSSSkew() override;
virtual WebIDL::ExceptionOr<Utf16String> to_string() const override;
virtual WebIDL::ExceptionOr<GC::Ref<Geometry::DOMMatrix>> to_matrix() const override;
GC::Ref<CSSNumericValue> ax() const { return m_ax; }
GC::Ref<CSSNumericValue> ay() const { return m_ay; }
WebIDL::ExceptionOr<void> set_ax(GC::Ref<CSSNumericValue> value);
WebIDL::ExceptionOr<void> set_ay(GC::Ref<CSSNumericValue> value);
virtual void set_is_2d(bool value) override;
private:
explicit CSSSkew(JS::Realm&, GC::Ref<CSSNumericValue> ax, GC::Ref<CSSNumericValue> ay);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Visitor&) override;
GC::Ref<CSSNumericValue> m_ax;
GC::Ref<CSSNumericValue> m_ay;
};
}

View File

@ -0,0 +1,10 @@
#import <CSS/CSSNumericValue.idl>
#import <CSS/CSSTransformComponent.idl>
// https://drafts.css-houdini.org/css-typed-om-1/#cssskew
[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
interface CSSSkew : CSSTransformComponent {
constructor(CSSNumericValue ax, CSSNumericValue ay);
attribute CSSNumericValue ax;
attribute CSSNumericValue ay;
};

View File

@ -263,6 +263,7 @@ class CSSRotate;
class CSSRule;
class CSSRuleList;
class CSSScale;
class CSSSkew;
class CSSStyleDeclaration;
class CSSStyleProperties;
class CSSStyleRule;

View File

@ -58,6 +58,7 @@ libweb_js_bindings(CSS/CSSRotate)
libweb_js_bindings(CSS/CSSRule)
libweb_js_bindings(CSS/CSSRuleList)
libweb_js_bindings(CSS/CSSScale)
libweb_js_bindings(CSS/CSSSkew)
libweb_js_bindings(CSS/CSSStyleDeclaration)
libweb_js_bindings(CSS/CSSStyleProperties)
libweb_js_bindings(CSS/CSSStyleRule)

View File

@ -70,6 +70,7 @@ CSSRotate
CSSRule
CSSRuleList
CSSScale
CSSSkew
CSSStyleDeclaration
CSSStyleProperties
CSSStyleRule

View File

@ -2,8 +2,8 @@ Harness status: OK
Found 545 tests
324 Pass
221 Fail
332 Pass
213 Fail
Pass idl_test setup
Pass idl_test validation
Pass Partial interface Element: original interface defined
@ -326,14 +326,14 @@ Fail CSSScale interface: scale must inherit property "y" with the proper type
Fail CSSScale interface: scale must inherit property "z" with the proper type
Fail CSSTransformComponent interface: scale must inherit property "is2D" with the proper type
Fail CSSTransformComponent interface: scale must inherit property "toMatrix()" with the proper type
Fail CSSSkew interface: existence and properties of interface object
Fail CSSSkew interface object length
Fail CSSSkew interface object name
Fail CSSSkew interface: existence and properties of interface prototype object
Fail CSSSkew interface: existence and properties of interface prototype object's "constructor" property
Fail CSSSkew interface: existence and properties of interface prototype object's @@unscopables property
Fail CSSSkew interface: attribute ax
Fail CSSSkew interface: attribute ay
Pass CSSSkew interface: existence and properties of interface object
Pass CSSSkew interface object length
Pass CSSSkew interface object name
Pass CSSSkew interface: existence and properties of interface prototype object
Pass CSSSkew interface: existence and properties of interface prototype object's "constructor" property
Pass CSSSkew interface: existence and properties of interface prototype object's @@unscopables property
Pass CSSSkew interface: attribute ax
Pass CSSSkew interface: attribute ay
Fail CSSSkew must be primary interface of skew
Fail Stringification of skew
Fail CSSSkew interface: skew must inherit property "ax" with the proper type

View File

@ -2,34 +2,34 @@ Harness status: OK
Found 30 tests
30 Fail
Fail Constructing a CSSSkew with a keyword throws a TypeError
Fail Constructing a CSSSkew with a double throws a TypeError
Fail Constructing a CSSSkew with a unitless zero throws a TypeError
Fail Constructing a CSSSkew with a string angle throws a TypeError
Fail Constructing a CSSSkew with a number CSSUnitValue throws a TypeError
Fail Constructing a CSSSkew with a time dimension CSSUnitValue throws a TypeError
Fail Constructing a CSSSkew with a CSSMathValue of length type throws a TypeError
Fail Updating CSSSkew.ax with a keyword throws a TypeError
Fail Updating CSSSkew.ax with a double throws a TypeError
Fail Updating CSSSkew.ax with a unitless zero throws a TypeError
Fail Updating CSSSkew.ax with a string angle throws a TypeError
Fail Updating CSSSkew.ax with a number CSSUnitValue throws a TypeError
Fail Updating CSSSkew.ax with a time dimension CSSUnitValue throws a TypeError
Fail Updating CSSSkew.ax with a CSSMathValue of length type throws a TypeError
Fail Updating CSSSkew.ay with a keyword throws a TypeError
Fail Updating CSSSkew.ay with a double throws a TypeError
Fail Updating CSSSkew.ay with a unitless zero throws a TypeError
Fail Updating CSSSkew.ay with a string angle throws a TypeError
Fail Updating CSSSkew.ay with a number CSSUnitValue throws a TypeError
Fail Updating CSSSkew.ay with a time dimension CSSUnitValue throws a TypeError
Fail Updating CSSSkew.ay with a CSSMathValue of length type throws a TypeError
Fail CSSSkew can be constructed from an angle CSSUnitValue and an angle CSSUnitValue
Fail CSSSkew can be constructed from an angle CSSUnitValue and a CSSMathValue of angle type
Fail CSSSkew can be constructed from a CSSMathValue of angle type and an angle CSSUnitValue
Fail CSSSkew can be constructed from a CSSMathValue of angle type and a CSSMathValue of angle type
Fail CSSSkew.ax can be updated to an angle CSSUnitValue
Fail CSSSkew.ax can be updated to a CSSMathValue of angle type
Fail CSSSkew.ay can be updated to an angle CSSUnitValue
Fail CSSSkew.ay can be updated to a CSSMathValue of angle type
Fail Modifying CSSSkew.is2D is a no-op
30 Pass
Pass Constructing a CSSSkew with a keyword throws a TypeError
Pass Constructing a CSSSkew with a double throws a TypeError
Pass Constructing a CSSSkew with a unitless zero throws a TypeError
Pass Constructing a CSSSkew with a string angle throws a TypeError
Pass Constructing a CSSSkew with a number CSSUnitValue throws a TypeError
Pass Constructing a CSSSkew with a time dimension CSSUnitValue throws a TypeError
Pass Constructing a CSSSkew with a CSSMathValue of length type throws a TypeError
Pass Updating CSSSkew.ax with a keyword throws a TypeError
Pass Updating CSSSkew.ax with a double throws a TypeError
Pass Updating CSSSkew.ax with a unitless zero throws a TypeError
Pass Updating CSSSkew.ax with a string angle throws a TypeError
Pass Updating CSSSkew.ax with a number CSSUnitValue throws a TypeError
Pass Updating CSSSkew.ax with a time dimension CSSUnitValue throws a TypeError
Pass Updating CSSSkew.ax with a CSSMathValue of length type throws a TypeError
Pass Updating CSSSkew.ay with a keyword throws a TypeError
Pass Updating CSSSkew.ay with a double throws a TypeError
Pass Updating CSSSkew.ay with a unitless zero throws a TypeError
Pass Updating CSSSkew.ay with a string angle throws a TypeError
Pass Updating CSSSkew.ay with a number CSSUnitValue throws a TypeError
Pass Updating CSSSkew.ay with a time dimension CSSUnitValue throws a TypeError
Pass Updating CSSSkew.ay with a CSSMathValue of length type throws a TypeError
Pass CSSSkew can be constructed from an angle CSSUnitValue and an angle CSSUnitValue
Pass CSSSkew can be constructed from an angle CSSUnitValue and a CSSMathValue of angle type
Pass CSSSkew can be constructed from a CSSMathValue of angle type and an angle CSSUnitValue
Pass CSSSkew can be constructed from a CSSMathValue of angle type and a CSSMathValue of angle type
Pass CSSSkew.ax can be updated to an angle CSSUnitValue
Pass CSSSkew.ax can be updated to a CSSMathValue of angle type
Pass CSSSkew.ay can be updated to an angle CSSUnitValue
Pass CSSSkew.ay can be updated to a CSSMathValue of angle type
Pass Modifying CSSSkew.is2D is a no-op

View File

@ -2,12 +2,12 @@ Harness status: OK
Found 8 tests
3 Pass
5 Fail
4 Pass
4 Fail
Pass CSSTranslate.toMatrix() returns correct matrix
Pass CSSRotate.toMatrix() returns correct matrix
Pass CSSScale.toMatrix() returns correct matrix
Fail CSSSkew.toMatrix() returns correct matrix
Pass CSSSkew.toMatrix() returns correct matrix
Fail CSSSkewX.toMatrix() returns correct matrix
Fail CSSSkewY.toMatrix() returns correct matrix
Fail CSSPerspective.toMatrix() returns correct matrix