ladybird/Libraries/LibWeb/CSS/CSSMathMin.cpp
Sam Atkins 078bc1a471 LibWeb/CSS: Allow converting CSSMathValues to StyleValues
Because we store calculations as a tree of CalculationNodes inside a
CalculatedStyleValue, instead of a tree of StyleValues directly, this
implements a create_calculation_node() method on CSSNumericValue.
CSSMathValue::create_an_internal_representation() then calls
create_calculation_node() on itself, and wraps it in a
CalculatedStyleValue.

Lots of WPT passes again! Some regressions, which are expected: `cursor`
fails a test for the same reason it fails other that set it to some
kind of numeric value: We don't distinguish between "can contain a
number" and "can accept a number by itself". This will affect any
similar properties, but overall this is a big improvement.
2025-10-13 09:59:38 +01:00

172 lines
6.2 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 "CSSMathMin.h"
#include <LibWeb/Bindings/CSSMathMinPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/CSSMathNegate.h>
#include <LibWeb/CSS/CSSNumericArray.h>
#include <LibWeb/CSS/CSSNumericValue.h>
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
#include <LibWeb/WebIDL/DOMException.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::CSS {
GC_DEFINE_ALLOCATOR(CSSMathMin);
GC::Ref<CSSMathMin> CSSMathMin::create(JS::Realm& realm, NumericType type, GC::Ref<CSSNumericArray> values)
{
return realm.create<CSSMathMin>(realm, move(type), move(values));
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-cssmathmin-cssmathmin
WebIDL::ExceptionOr<GC::Ref<CSSMathMin>> CSSMathMin::construct_impl(JS::Realm& realm, Vector<CSSNumberish> values)
{
// The CSSMathMin(...args) and CSSMathMax(...args) constructors are defined identically to the above, except that
// in the last step they return a new CSSMathMin or CSSMathMax object, respectively.
// NB: So, the steps below are a modification of the CSSMathSum steps.
// 1. Replace each item of args with the result of rectifying a numberish value for the item.
GC::RootVector<GC::Ref<CSSNumericValue>> converted_values { realm.heap() };
converted_values.ensure_capacity(values.size());
for (auto const& value : values) {
converted_values.append(rectify_a_numberish_value(realm, value));
}
// 2. If args is empty, throw a SyntaxError.
if (converted_values.is_empty())
return WebIDL::SyntaxError::create(realm, "Cannot create an empty CSSMathMin"_utf16);
// 3. Let type be the result of adding the types of all the items of args. If type is failure, throw a TypeError.
auto type = converted_values.first()->type();
bool first = true;
for (auto const& value : converted_values) {
if (first) {
first = false;
continue;
}
if (auto added_types = type.added_to(value->type()); added_types.has_value()) {
type = added_types.release_value();
} else {
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Cannot create a CSSMathMin with values of incompatible types"sv };
}
}
// 4. Return a new CSSMathMin whose values internal slot is set to args.
auto values_array = CSSNumericArray::create(realm, { converted_values });
return CSSMathMin::create(realm, move(type), move(values_array));
}
CSSMathMin::CSSMathMin(JS::Realm& realm, NumericType type, GC::Ref<CSSNumericArray> values)
: CSSMathValue(realm, Bindings::CSSMathOperator::Min, move(type))
, m_values(move(values))
{
}
CSSMathMin::~CSSMathMin() = default;
void CSSMathMin::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSMathMin);
Base::initialize(realm);
}
void CSSMathMin::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_values);
}
// https://drafts.css-houdini.org/css-typed-om-1/#serialize-a-cssmathvalue
String CSSMathMin::serialize_math_value(Nested, Parens) const
{
// NB: Only steps 1 and 2 apply here.
// 1. Let s initially be the empty string.
StringBuilder s;
// 2. If this is a CSSMathMin or CSSMathMax:
{
// 1. Append "min(" or "max(" to s, as appropriate.
s.append("min("sv);
// 2. For each arg in thiss values internal slot, serialize arg with nested and paren-less both true, and
// append the result to s, appending a ", " between successive values.
bool first = true;
for (auto const& arg : m_values->values()) {
if (first) {
first = false;
} else {
s.append(", "sv);
}
s.append(arg->to_string({ .nested = true, .parenless = true }));
}
// 3. Append ")" to s and return s.
s.append(")"sv);
return s.to_string_without_validation();
}
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-cssmathmin-values
GC::Ref<CSSNumericArray> CSSMathMin::values() const
{
return m_values;
}
// https://drafts.css-houdini.org/css-typed-om-1/#equal-numeric-value
bool CSSMathMin::is_equal_numeric_value(GC::Ref<CSSNumericValue> other) const
{
// NB: Only steps 1 and 3 are relevant.
// 1. If value1 and value2 are not members of the same interface, return false.
auto* other_min = as_if<CSSMathMin>(*other);
if (!other_min)
return false;
// 3. If value1 and value2 are both CSSMathSums, CSSMathProducts, CSSMathMins, or CSSMathMaxs:
// NB: Substeps are implemented in CSSNumericArray.
return m_values->is_equal_numeric_values(other_min->m_values);
}
// https://drafts.css-houdini.org/css-typed-om-1/#create-a-sum-value
Optional<SumValue> CSSMathMin::create_a_sum_value() const
{
// 1. Let args be the result of creating a sum value for each item in thiss values internal slot.
Vector<Optional<SumValue>> args;
args.ensure_capacity(m_values->length());
for (auto const& value : m_values->values()) {
args.unchecked_append(value->create_a_sum_value());
}
Optional<SumValue> item_with_smallest_value = args.first();
for (auto const& item : args) {
// 2. If any item of args is failure, or has a length greater than one, return failure.
if (!item.has_value() || item->size() > 1)
return {};
// 3. If not all of the unit maps among the items of args are identical, return failure.
if (item->first().unit_map != item_with_smallest_value->first().unit_map)
return {};
if (item->first().value < item_with_smallest_value->first().value)
item_with_smallest_value = item;
}
// 4. Return the item of args whose sole item has the smallest value.
return item_with_smallest_value;
}
WebIDL::ExceptionOr<NonnullRefPtr<CalculationNode const>> CSSMathMin::create_calculation_node(CalculationContext const& context) const
{
Vector<NonnullRefPtr<CalculationNode const>> child_nodes;
for (auto const& child_value : m_values->values()) {
child_nodes.append(TRY(child_value->create_calculation_node(context)));
}
return MinCalculationNode::create(move(child_nodes));
}
}