/* * Copyright (c) 2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ #include "CSSTransformValue.h" #include #include #include #include #include #include #include #include namespace Web::CSS { GC_DEFINE_ALLOCATOR(CSSTransformValue); GC::Ref CSSTransformValue::create(JS::Realm& realm, Vector> transforms) { return realm.create(realm, move(transforms)); } // https://drafts.css-houdini.org/css-typed-om-1/#dom-csstransformvalue-csstransformvalue WebIDL::ExceptionOr> CSSTransformValue::construct_impl(JS::Realm& realm, GC::RootVector> 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> 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> 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 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 this’s // [[values]] internal slot, and return values[n]. if (index >= m_transforms.size()) return {}; return m_transforms[index]; } static WebIDL::ExceptionOr> transform_component_from_js_value(JS::Value& value) { if (value.is_object()) { if (auto* transform_component = as_if(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 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 this’s [[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 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 this’s [[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 this’s values // to iterate over, the func’s 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> 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 this’s 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 CSSTransformValue::to_string() const { // 1. Return the result of serializing each item in this’s 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(); } // https://drafts.css-houdini.org/css-typed-om-1/#create-an-internal-representation WebIDL::ExceptionOr> CSSTransformValue::create_an_internal_representation(PropertyNameAndID const& property, PerformTypeCheck perform_type_check) const { // NB: This can become or , and we don't know which is wanted without performing the type checking. // We can worry about that if and when we ever do have a CSSTransformValue that isn't top-level. VERIFY(perform_type_check == PerformTypeCheck::Yes); // If value is a CSSStyleValue subclass, // If value does not match the grammar of a list-valued property iteration of property, throw a TypeError. // // If any component of property’s CSS grammar has a limited numeric range, and the corresponding part of value // is a CSSUnitValue that is outside of that range, replace that value with the result of wrapping it in a // fresh CSSMathSum whose values internal slot contains only that part of value. // // Return the value. // NB: We match if we have 1 transform. We match always. if (m_transforms.size() == 1 && property_accepts_type(property.id(), ValueType::TransformFunction)) return TRY(m_transforms.first()->create_style_value(property)); if (property_accepts_type(property.id(), ValueType::TransformList)) { StyleValueVector transforms; transforms.ensure_capacity(m_transforms.size()); for (auto const transform : m_transforms) transforms.unchecked_append(TRY(transform->create_style_value(property))); return StyleValueList::create(move(transforms), StyleValueList::Separator::Space); } return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Property does not accept values of this type."sv }; } }