ladybird/Libraries/LibWeb/CSS/CSSTransformValue.cpp
Sam Atkins d5977b9f74 LibWeb/CSS: Implement CSSTransformValue
This is the final CSSStyleValue class used by the per-property test
harness, so those now actually run instead of throwing an exception on
load. 🎉

+39 WPT subtests. (Plus however many from the per-property tests finally
running.)

The two failing serialization tests are also failed by Safari in exactly
the same way, so that seems more like a spec issue. (The spec is
incomplete in quite a few places.) The failing subtest for toMatrix() is
also a spec issue: is2D is handled oddly by CSSMatrixComponent and this
subtest fails because of the `matrix` getter, which is unspecified. See
https://github.com/w3c/css-houdini-drafts/issues/1155 for details.
2025-09-24 12:27:05 +01:00

158 lines
6.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "CSSTransformValue.h"
#include <LibWeb/Bindings/CSSTransformValuePrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/CSSTransformComponent.h>
#include <LibWeb/Geometry/DOMMatrix.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::CSS {
GC_DEFINE_ALLOCATOR(CSSTransformValue);
GC::Ref<CSSTransformValue> CSSTransformValue::create(JS::Realm& realm, Vector<GC::Ref<CSSTransformComponent>> transforms)
{
return realm.create<CSSTransformValue>(realm, move(transforms));
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-csstransformvalue-csstransformvalue
WebIDL::ExceptionOr<GC::Ref<CSSTransformValue>> CSSTransformValue::construct_impl(JS::Realm& realm, GC::RootVector<GC::Root<CSSTransformComponent>> transforms)
{
// The CSSTransformValue(transforms) constructor must, when called, perform the following steps:
// 1. If transforms is empty, throw a TypeError.
if (transforms.is_empty())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "CSSTransformValue's transforms list cannot be empty."sv };
// 2. Return a new CSSTransformValue whose values to iterate over is transforms.
Vector<GC::Ref<CSSTransformComponent>> converted_transforms;
converted_transforms.ensure_capacity(transforms.size());
for (auto const& transform : transforms)
converted_transforms.append(*transform);
return CSSTransformValue::create(realm, move(converted_transforms));
}
CSSTransformValue::CSSTransformValue(JS::Realm& realm, Vector<GC::Ref<CSSTransformComponent>> transforms)
: CSSStyleValue(realm)
, m_transforms(move(transforms))
{
m_legacy_platform_object_flags = LegacyPlatformObjectFlags {
.supports_indexed_properties = true,
.has_indexed_property_setter = true,
};
}
CSSTransformValue::~CSSTransformValue() = default;
void CSSTransformValue::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSTransformValue);
Base::initialize(realm);
}
void CSSTransformValue::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_transforms);
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-csstransformvalue-length
WebIDL::UnsignedLong CSSTransformValue::length() const
{
// The length attribute indicates how many transform components are contained within the CSSTransformValue.
return m_transforms.size();
}
// https://drafts.css-houdini.org/css-typed-om-1/#ref-for-dfn-determine-the-value-of-an-indexed-property%E2%91%A0
Optional<JS::Value> CSSTransformValue::item_value(size_t index) const
{
// To determine the value of an indexed property of a CSSTransformValue this and an index n, let values be thiss
// [[values]] internal slot, and return values[n].
if (index >= m_transforms.size())
return {};
return m_transforms[index];
}
static WebIDL::ExceptionOr<GC::Ref<CSSTransformComponent>> transform_component_from_js_value(JS::Value& value)
{
if (value.is_object()) {
if (auto* transform_component = as_if<CSSTransformComponent>(value.as_object()))
return GC::Ref { *transform_component };
}
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Value must be a CSSTransformComponent"sv };
}
// https://drafts.css-houdini.org/css-typed-om-1/#ref-for-dfn-set-the-value-of-an-existing-indexed-property%E2%91%A0
WebIDL::ExceptionOr<void> CSSTransformValue::set_value_of_existing_indexed_property(u32 n, JS::Value new_value)
{
// To set the value of an existing indexed property of a CSSTransformValue this, an index n, and a value new value,
// let values be thiss [[values]] internal slot, and set values[n] to new value.
m_transforms[n] = TRY(transform_component_from_js_value(new_value));
return {};
}
// https://drafts.css-houdini.org/css-typed-om-1/#ref-for-dfn-set-the-value-of-a-new-indexed-property①
WebIDL::ExceptionOr<void> CSSTransformValue::set_value_of_new_indexed_property(u32 n, JS::Value new_value)
{
// To set the value of a new indexed property of a CSSTransformValue this, an index n, and a value new value, let
// values be thiss [[values]] internal slot. If n is not equal to the size of values, throw a RangeError.
// Otherwise, append new value to values.
if (n != m_transforms.size())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "Index out of range"sv };
m_transforms.append(TRY(transform_component_from_js_value(new_value)));
return {};
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-csstransformvalue-is2d
bool CSSTransformValue::is_2d() const
{
// The is2D attribute of a CSSTransformValue this must, on getting, return true if, for each func in thiss values
// to iterate over, the funcs is2D attribute would return true; otherwise, the attribute returns false.
return all_of(m_transforms, [](auto& transform) { return transform->is_2d(); });
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-csstransformvalue-tomatrix
WebIDL::ExceptionOr<GC::Ref<Geometry::DOMMatrix>> CSSTransformValue::to_matrix() const
{
// The toMatrix() method of a CSSTransformValue this must, when called, perform the following steps:
// 1. Let matrix be a new DOMMatrix, initialized to the identity matrix, with its is2D internal slot set to true.
auto matrix = Geometry::DOMMatrix::create(realm());
// 2. For each func in thiss values to iterate over:
for (auto const& function : m_transforms) {
// 1. Let funcMatrix be the DOMMatrix returned by calling toMatrix() on func.
// AD-HOC: This can throw exceptions.
auto function_matrix = TRY(function->to_matrix());
// 2. Set matrix to the result of multiplying matrix and the matrix represented by funcMatrix.
TRY(matrix->multiply_self(*function_matrix));
}
// 3. Return matrix.
return matrix;
}
// https://drafts.css-houdini.org/css-typed-om-1/#serialize-a-csstransformvalue
WebIDL::ExceptionOr<String> CSSTransformValue::to_string() const
{
// 1. Return the result of serializing each item in thiss values to iterate over, then concatenating them
// separated by " ".
StringBuilder builder;
bool first = true;
for (auto const& transform : m_transforms) {
if (!first)
builder.append(" "sv);
first = false;
builder.append(TRY(transform->to_string()));
}
return builder.to_string_without_validation();
}
}