mirror of
https://github.com/zebrajr/ladybird.git
synced 2025-12-06 00:19:53 +01:00
LibWeb/CSS: Implement CSSPerspective
Equivalent to the perspective() transform function. +34 WPT subtests, and the transformvalue-normalization test now runs to completion instead of throwing an error - though its cases still fail until CSSTransformValue is implemented.
This commit is contained in:
parent
68ceacb0c5
commit
2ffbb284f2
|
|
@ -133,6 +133,7 @@ set(SOURCES
|
|||
CSS/CSSNumericValue.cpp
|
||||
CSS/CSSPageRule.cpp
|
||||
CSS/CSSPageDescriptors.cpp
|
||||
CSS/CSSPerspective.cpp
|
||||
CSS/CSSPropertyRule.cpp
|
||||
CSS/CSSRotate.cpp
|
||||
CSS/CSSRule.cpp
|
||||
|
|
|
|||
160
Libraries/LibWeb/CSS/CSSPerspective.cpp
Normal file
160
Libraries/LibWeb/CSS/CSSPerspective.cpp
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "CSSPerspective.h"
|
||||
#include <LibWeb/Bindings/CSSPerspectivePrototype.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(CSSPerspective);
|
||||
|
||||
static WebIDL::ExceptionOr<CSSPerspectiveValueInternal> to_internal(JS::Realm& realm, CSSPerspectiveValue const& value)
|
||||
{
|
||||
// Steps 1 and 2 of The CSSPerspective(length) constructor:
|
||||
// https://drafts.css-houdini.org/css-typed-om-1/#dom-cssperspective-cssperspective
|
||||
return value.visit(
|
||||
// 1. If length is a CSSNumericValue:
|
||||
[](GC::Root<CSSNumericValue> const& numeric_value) -> WebIDL::ExceptionOr<CSSPerspectiveValueInternal> {
|
||||
// 1. If length does not match <length>, throw a TypeError.
|
||||
if (!numeric_value->type().matches_length({})) {
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSPerspective length component doesn't match <length>"sv };
|
||||
}
|
||||
return { GC::Ref { *numeric_value } };
|
||||
},
|
||||
// 2. Otherwise (that is, if length is not a CSSNumericValue):
|
||||
[&realm](CSSKeywordish const& keywordish) -> WebIDL::ExceptionOr<CSSPerspectiveValueInternal> {
|
||||
// 1. Rectify a keywordish value from length, then set length to the result’s value.
|
||||
auto rectified_length = rectify_a_keywordish_value(realm, keywordish);
|
||||
|
||||
// 2. If length does not represent a value that is an ASCII case-insensitive match for the keyword none,
|
||||
// throw a TypeError.
|
||||
if (!rectified_length->value().equals_ignoring_ascii_case("none"_fly_string)) {
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSPerspective length component is a keyword other than `none`"sv };
|
||||
}
|
||||
|
||||
return { rectified_length };
|
||||
});
|
||||
}
|
||||
|
||||
GC::Ref<CSSPerspective> CSSPerspective::create(JS::Realm& realm, CSSPerspectiveValueInternal length)
|
||||
{
|
||||
return realm.create<CSSPerspective>(realm, length);
|
||||
}
|
||||
|
||||
// https://drafts.css-houdini.org/css-typed-om-1/#dom-cssperspective-cssperspective
|
||||
WebIDL::ExceptionOr<GC::Ref<CSSPerspective>> CSSPerspective::construct_impl(JS::Realm& realm, CSSPerspectiveValue length)
|
||||
{
|
||||
// The CSSPerspective(length) constructor must, when invoked, perform the following steps:
|
||||
// NB: Steps 1 and 2 are implemented in to_internal().
|
||||
auto internal_length = TRY(to_internal(realm, length));
|
||||
|
||||
// 3. Return a new CSSPerspective object with its length internal slot set to length, and its is2D internal slot
|
||||
// set to false.
|
||||
return CSSPerspective::create(realm, internal_length);
|
||||
}
|
||||
|
||||
CSSPerspective::CSSPerspective(JS::Realm& realm, CSSPerspectiveValueInternal length)
|
||||
: CSSTransformComponent(realm, Is2D::No)
|
||||
, m_length(length)
|
||||
{
|
||||
}
|
||||
|
||||
CSSPerspective::~CSSPerspective() = default;
|
||||
|
||||
void CSSPerspective::initialize(JS::Realm& realm)
|
||||
{
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSPerspective);
|
||||
Base::initialize(realm);
|
||||
}
|
||||
|
||||
void CSSPerspective::visit_edges(Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
m_length.visit([&visitor](auto const& it) { visitor.visit(it); });
|
||||
}
|
||||
|
||||
// https://drafts.css-houdini.org/css-typed-om-1/#serialize-a-cssperspective
|
||||
WebIDL::ExceptionOr<Utf16String> CSSPerspective::to_string() const
|
||||
{
|
||||
// 1. Let s initially be "perspective(".
|
||||
StringBuilder builder { StringBuilder::Mode::UTF16 };
|
||||
builder.append("perspective("sv);
|
||||
|
||||
// 2. Serialize this’s length internal slot, with a minimum of 0px, and append it to s.
|
||||
auto serialized_length = m_length.visit(
|
||||
[](GC::Ref<CSSNumericValue> const& numeric_value) {
|
||||
return numeric_value->to_string({ .minimum = 0 });
|
||||
},
|
||||
[](GC::Ref<CSSKeywordValue> const& keyword_value) {
|
||||
return keyword_value->to_string();
|
||||
});
|
||||
builder.append(serialized_length);
|
||||
|
||||
// 3. Append ")" to s, and return s.
|
||||
builder.append(")"sv);
|
||||
return builder.to_utf16_string();
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<GC::Ref<Geometry::DOMMatrix>> CSSPerspective::to_matrix() const
|
||||
{
|
||||
// 1. Let matrix be a new DOMMatrix object, initialized to this’s 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 this’s 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());
|
||||
|
||||
TRY(m_length.visit(
|
||||
[&matrix](GC::Ref<CSSNumericValue> const& numeric_value) -> WebIDL::ExceptionOr<void> {
|
||||
// NB: to() throws a TypeError if the conversion can't be done.
|
||||
auto distance = TRY(numeric_value->to("px"_fly_string))->value();
|
||||
matrix->set_m34(-1 / (distance <= 0 ? 1 : distance));
|
||||
return {};
|
||||
},
|
||||
[](GC::Ref<CSSKeywordValue> const&) -> WebIDL::ExceptionOr<void> {
|
||||
// NB: This is `none`, so do nothing.
|
||||
return {};
|
||||
}));
|
||||
|
||||
// 2. Return matrix.
|
||||
return matrix;
|
||||
}
|
||||
|
||||
CSSPerspectiveValue CSSPerspective::length() const
|
||||
{
|
||||
return m_length.visit(
|
||||
[](GC::Ref<CSSNumericValue> const& numeric_value) -> CSSPerspectiveValue {
|
||||
return GC::Root { numeric_value };
|
||||
},
|
||||
[](GC::Ref<CSSKeywordValue> const& keyword_value) -> CSSPerspectiveValue {
|
||||
return CSSKeywordish { keyword_value };
|
||||
});
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<void> CSSPerspective::set_length(CSSPerspectiveValue value)
|
||||
{
|
||||
// AD-HOC: Not specced. https://github.com/w3c/css-houdini-drafts/issues/1153
|
||||
// WPT expects this to throw for invalid values, so just reuse the constructor code.
|
||||
auto length = TRY(to_internal(realm(), value));
|
||||
m_length = length;
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://drafts.css-houdini.org/css-typed-om-1/#dom-cssperspective-is2d
|
||||
void CSSPerspective::set_is_2d(bool)
|
||||
{
|
||||
// The is2D attribute of a CSSPerspective object must, on setting, do nothing.
|
||||
}
|
||||
|
||||
}
|
||||
52
Libraries/LibWeb/CSS/CSSPerspective.h
Normal file
52
Libraries/LibWeb/CSS/CSSPerspective.h
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/CSS/CSSKeywordValue.h>
|
||||
#include <LibWeb/CSS/CSSNumericValue.h>
|
||||
#include <LibWeb/CSS/CSSTransformComponent.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
// https://drafts.css-houdini.org/css-typed-om-1/#typedefdef-cssperspectivevalue
|
||||
// NB: CSSKeywordish is flattened here, because our bindings generator flattens nested variants.
|
||||
using CSSPerspectiveValue = Variant<GC::Root<CSSNumericValue>, String, GC::Root<CSSKeywordValue>>;
|
||||
using CSSPerspectiveValueInternal = Variant<GC::Ref<CSSNumericValue>, GC::Ref<CSSKeywordValue>>;
|
||||
|
||||
// https://drafts.css-houdini.org/css-typed-om-1/#cssperspective
|
||||
class CSSPerspective final : public CSSTransformComponent {
|
||||
WEB_PLATFORM_OBJECT(CSSPerspective, CSSTransformComponent);
|
||||
GC_DECLARE_ALLOCATOR(CSSPerspective);
|
||||
|
||||
public:
|
||||
[[nodiscard]] static GC::Ref<CSSPerspective> create(JS::Realm&, CSSPerspectiveValueInternal);
|
||||
static WebIDL::ExceptionOr<GC::Ref<CSSPerspective>> construct_impl(JS::Realm&, CSSPerspectiveValue);
|
||||
|
||||
virtual ~CSSPerspective() override;
|
||||
|
||||
virtual WebIDL::ExceptionOr<Utf16String> to_string() const override;
|
||||
|
||||
virtual WebIDL::ExceptionOr<GC::Ref<Geometry::DOMMatrix>> to_matrix() const override;
|
||||
|
||||
CSSPerspectiveValue length() const;
|
||||
WebIDL::ExceptionOr<void> set_length(CSSPerspectiveValue value);
|
||||
|
||||
virtual void set_is_2d(bool value) override;
|
||||
|
||||
private:
|
||||
explicit CSSPerspective(JS::Realm&, CSSPerspectiveValueInternal);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
CSSPerspectiveValueInternal m_length;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
class CSSPerspective {
|
||||
};
|
||||
12
Libraries/LibWeb/CSS/CSSPerspective.idl
Normal file
12
Libraries/LibWeb/CSS/CSSPerspective.idl
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#import <CSS/CSSKeywordValue.idl>
|
||||
#import <CSS/CSSNumericValue.idl>
|
||||
#import <CSS/CSSTransformComponent.idl>
|
||||
|
||||
typedef (CSSNumericValue or CSSKeywordish) CSSPerspectiveValue;
|
||||
|
||||
// https://drafts.css-houdini.org/css-typed-om-1/#cssperspective
|
||||
[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
|
||||
interface CSSPerspective : CSSTransformComponent {
|
||||
constructor(CSSPerspectiveValue length);
|
||||
attribute CSSPerspectiveValue length;
|
||||
};
|
||||
|
|
@ -258,6 +258,7 @@ class CSSNumericArray;
|
|||
class CSSNumericValue;
|
||||
class CSSPageRule;
|
||||
class CSSPageDescriptors;
|
||||
class CSSPerspective;
|
||||
class CSSPropertyRule;
|
||||
class CSSRotate;
|
||||
class CSSRule;
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ libweb_js_bindings(CSS/CSSNumericArray)
|
|||
libweb_js_bindings(CSS/CSSNumericValue)
|
||||
libweb_js_bindings(CSS/CSSPageRule)
|
||||
libweb_js_bindings(CSS/CSSPageDescriptors)
|
||||
libweb_js_bindings(CSS/CSSPerspective)
|
||||
libweb_js_bindings(CSS/CSSPropertyRule)
|
||||
libweb_js_bindings(CSS/CSSRotate)
|
||||
libweb_js_bindings(CSS/CSSRule)
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ CSSNumericArray
|
|||
CSSNumericValue
|
||||
CSSPageDescriptors
|
||||
CSSPageRule
|
||||
CSSPerspective
|
||||
CSSPropertyRule
|
||||
CSSRotate
|
||||
CSSRule
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ Harness status: OK
|
|||
|
||||
Found 545 tests
|
||||
|
||||
346 Pass
|
||||
199 Fail
|
||||
353 Pass
|
||||
192 Fail
|
||||
Pass idl_test setup
|
||||
Pass idl_test validation
|
||||
Pass Partial interface Element: original interface defined
|
||||
|
|
@ -364,13 +364,13 @@ Fail Stringification of skewY
|
|||
Fail CSSSkewY interface: skewY must inherit property "ay" with the proper type
|
||||
Fail CSSTransformComponent interface: skewY must inherit property "is2D" with the proper type
|
||||
Fail CSSTransformComponent interface: skewY must inherit property "toMatrix()" with the proper type
|
||||
Fail CSSPerspective interface: existence and properties of interface object
|
||||
Fail CSSPerspective interface object length
|
||||
Fail CSSPerspective interface object name
|
||||
Fail CSSPerspective interface: existence and properties of interface prototype object
|
||||
Fail CSSPerspective interface: existence and properties of interface prototype object's "constructor" property
|
||||
Fail CSSPerspective interface: existence and properties of interface prototype object's @@unscopables property
|
||||
Fail CSSPerspective interface: attribute length
|
||||
Pass CSSPerspective interface: existence and properties of interface object
|
||||
Pass CSSPerspective interface object length
|
||||
Pass CSSPerspective interface object name
|
||||
Pass CSSPerspective interface: existence and properties of interface prototype object
|
||||
Pass CSSPerspective interface: existence and properties of interface prototype object's "constructor" property
|
||||
Pass CSSPerspective interface: existence and properties of interface prototype object's @@unscopables property
|
||||
Pass CSSPerspective interface: attribute length
|
||||
Fail CSSPerspective must be primary interface of perspective
|
||||
Fail Stringification of perspective
|
||||
Fail CSSPerspective interface: perspective must inherit property "length" with the proper type
|
||||
|
|
|
|||
|
|
@ -1,7 +1,33 @@
|
|||
Harness status: Error
|
||||
Harness status: OK
|
||||
|
||||
Found 2 tests
|
||||
Found 28 tests
|
||||
|
||||
2 Fail
|
||||
28 Fail
|
||||
Fail Normalizing a matrix() returns a CSSMatrixComponent
|
||||
Fail Normalizing a matrix3d() returns a CSSMatrixComponent
|
||||
Fail Normalizing a matrix3d() returns a CSSMatrixComponent
|
||||
Fail Normalizing a translate() with X returns a CSSTranslate
|
||||
Fail Normalizing a translate() with X and Y returns a CSSTranslate
|
||||
Fail Normalizing a translateX() returns a CSSTranslate
|
||||
Fail Normalizing a translateY() returns a CSSTranslate
|
||||
Fail Normalizing a translate3d() returns a CSSTranslate
|
||||
Fail Normalizing a translateZ() returns a CSSTranslate
|
||||
Fail Normalizing a scale() with one argument returns a CSSScale
|
||||
Fail Normalizing a scale() with two arguments returns a CSSScale
|
||||
Fail Normalizing a scaleX() returns a CSSScale
|
||||
Fail Normalizing a scaleY() returns a CSSScale
|
||||
Fail Normalizing a scale3d() returns a CSSScale
|
||||
Fail Normalizing a scaleZ() returns a CSSScale
|
||||
Fail Normalizing a rotate() returns a CSSRotate
|
||||
Fail Normalizing a rotate3d() returns a CSSRotate
|
||||
Fail Normalizing a rotateX() returns a CSSRotate
|
||||
Fail Normalizing a rotateY() returns a CSSRotate
|
||||
Fail Normalizing a rotateZ() returns a CSSRotate
|
||||
Fail Normalizing a skew() with only X returns a CSSSkew
|
||||
Fail Normalizing a skew() with X and Y which is 0 value returns a CSSSkew
|
||||
Fail Normalizing a skew() with X and Y returns a CSSSkew
|
||||
Fail Normalizing a skewX() returns a CSSSkewX
|
||||
Fail Normalizing a skewY() returns a CSSSkewY
|
||||
Fail Normalizing a perspective() returns a CSSPerspective
|
||||
Fail Normalizing a perspective(none) returns a CSSPerspective
|
||||
Fail Normalizing a <transform-list> returns a CSSTransformValue containing all the transforms
|
||||
Fail Normalizing transforms with calc values contains CSSMathValues
|
||||
|
|
@ -2,29 +2,29 @@ Harness status: OK
|
|||
|
||||
Found 25 tests
|
||||
|
||||
25 Fail
|
||||
Fail Constructing a CSSPerspective with a keyword other than none (string) throws a TypeError
|
||||
Fail Constructing a CSSPerspective with a keyword other than none (CSSKeywordValue) throws a TypeError
|
||||
Fail Constructing a CSSPerspective with a double throws a TypeError
|
||||
Fail Constructing a CSSPerspective with a unitless zero throws a TypeError
|
||||
Fail Constructing a CSSPerspective with a string length throws a TypeError
|
||||
Fail Constructing a CSSPerspective with a number CSSUnitValue throws a TypeError
|
||||
Fail Constructing a CSSPerspective with a time dimension CSSUnitValue throws a TypeError
|
||||
Fail Constructing a CSSPerspective with a CSSMathValue of angle type throws a TypeError
|
||||
Fail Updating CSSPerspective.length with a keyword other than none (string) throws a TypeError
|
||||
Fail Updating CSSPerspective.length with a keyword other than none (CSSKeywordValue) throws a TypeError
|
||||
Fail Updating CSSPerspective.length with a double throws a TypeError
|
||||
Fail Updating CSSPerspective.length with a unitless zero throws a TypeError
|
||||
Fail Updating CSSPerspective.length with a string length throws a TypeError
|
||||
Fail Updating CSSPerspective.length with a number CSSUnitValue throws a TypeError
|
||||
Fail Updating CSSPerspective.length with a time dimension CSSUnitValue throws a TypeError
|
||||
Fail Updating CSSPerspective.length with a CSSMathValue of angle type throws a TypeError
|
||||
Fail CSSPerspective can be constructed from a length CSSUnitValue
|
||||
Fail CSSPerspective.length can be updated to a length CSSUnitValue
|
||||
Fail CSSPerspective can be constructed from a CSSMathValue of length type
|
||||
Fail CSSPerspective.length can be updated to a CSSMathValue of length type
|
||||
Fail CSSPerspective can be constructed from none (CSSKeywordValue)
|
||||
Fail CSSPerspective.length can be updated to none (CSSKeywordValue)
|
||||
Fail CSSPerspective can be constructed from none (string)
|
||||
Fail CSSPerspective.length can be updated to none (string)
|
||||
Fail Modifying CSSPerspective.is2D is a no-op
|
||||
25 Pass
|
||||
Pass Constructing a CSSPerspective with a keyword other than none (string) throws a TypeError
|
||||
Pass Constructing a CSSPerspective with a keyword other than none (CSSKeywordValue) throws a TypeError
|
||||
Pass Constructing a CSSPerspective with a double throws a TypeError
|
||||
Pass Constructing a CSSPerspective with a unitless zero throws a TypeError
|
||||
Pass Constructing a CSSPerspective with a string length throws a TypeError
|
||||
Pass Constructing a CSSPerspective with a number CSSUnitValue throws a TypeError
|
||||
Pass Constructing a CSSPerspective with a time dimension CSSUnitValue throws a TypeError
|
||||
Pass Constructing a CSSPerspective with a CSSMathValue of angle type throws a TypeError
|
||||
Pass Updating CSSPerspective.length with a keyword other than none (string) throws a TypeError
|
||||
Pass Updating CSSPerspective.length with a keyword other than none (CSSKeywordValue) throws a TypeError
|
||||
Pass Updating CSSPerspective.length with a double throws a TypeError
|
||||
Pass Updating CSSPerspective.length with a unitless zero throws a TypeError
|
||||
Pass Updating CSSPerspective.length with a string length throws a TypeError
|
||||
Pass Updating CSSPerspective.length with a number CSSUnitValue throws a TypeError
|
||||
Pass Updating CSSPerspective.length with a time dimension CSSUnitValue throws a TypeError
|
||||
Pass Updating CSSPerspective.length with a CSSMathValue of angle type throws a TypeError
|
||||
Pass CSSPerspective can be constructed from a length CSSUnitValue
|
||||
Pass CSSPerspective.length can be updated to a length CSSUnitValue
|
||||
Pass CSSPerspective can be constructed from a CSSMathValue of length type
|
||||
Pass CSSPerspective.length can be updated to a CSSMathValue of length type
|
||||
Pass CSSPerspective can be constructed from none (CSSKeywordValue)
|
||||
Pass CSSPerspective.length can be updated to none (CSSKeywordValue)
|
||||
Pass CSSPerspective can be constructed from none (string)
|
||||
Pass CSSPerspective.length can be updated to none (string)
|
||||
Pass Modifying CSSPerspective.is2D is a no-op
|
||||
|
|
@ -2,7 +2,6 @@ Harness status: OK
|
|||
|
||||
Found 2 tests
|
||||
|
||||
1 Pass
|
||||
1 Fail
|
||||
2 Pass
|
||||
Pass CSSTranslate.toMatrix() containing relative units throws TypeError
|
||||
Fail CSSPerspective.toMatrix() containing relative units throws TypeError
|
||||
Pass CSSPerspective.toMatrix() containing relative units throws TypeError
|
||||
|
|
@ -2,13 +2,13 @@ Harness status: OK
|
|||
|
||||
Found 8 tests
|
||||
|
||||
6 Pass
|
||||
2 Fail
|
||||
7 Pass
|
||||
1 Fail
|
||||
Pass CSSTranslate.toMatrix() returns correct matrix
|
||||
Pass CSSRotate.toMatrix() returns correct matrix
|
||||
Pass CSSScale.toMatrix() returns correct matrix
|
||||
Pass CSSSkew.toMatrix() returns correct matrix
|
||||
Pass CSSSkewX.toMatrix() returns correct matrix
|
||||
Pass CSSSkewY.toMatrix() returns correct matrix
|
||||
Fail CSSPerspective.toMatrix() returns correct matrix
|
||||
Pass CSSPerspective.toMatrix() returns correct matrix
|
||||
Fail CSSMatrixComponent.toMatrix() returns correct matrix
|
||||
Loading…
Reference in New Issue
Block a user