/* * Copyright (c) 2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ #include "CSSNumericValue.h" #include #include #include #include #include #include #include #include #include namespace Web::CSS { GC_DEFINE_ALLOCATOR(CSSNumericValue); static Bindings::CSSNumericBaseType to_om_numeric_base_type(NumericType::BaseType source) { switch (source) { case NumericType::BaseType::Length: return Bindings::CSSNumericBaseType::Length; case NumericType::BaseType::Angle: return Bindings::CSSNumericBaseType::Angle; case NumericType::BaseType::Time: return Bindings::CSSNumericBaseType::Time; case NumericType::BaseType::Frequency: return Bindings::CSSNumericBaseType::Frequency; case NumericType::BaseType::Resolution: return Bindings::CSSNumericBaseType::Resolution; case NumericType::BaseType::Flex: return Bindings::CSSNumericBaseType::Flex; case NumericType::BaseType::Percent: return Bindings::CSSNumericBaseType::Percent; case NumericType::BaseType::__Count: VERIFY_NOT_REACHED(); } VERIFY_NOT_REACHED(); } CSSNumericValue::CSSNumericValue(JS::Realm& realm, NumericType type) : CSSStyleValue(realm) , m_type(move(type)) { } void CSSNumericValue::initialize(JS::Realm& realm) { WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSNumericValue); Base::initialize(realm); } // https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-type CSSNumericType CSSNumericValue::type_for_bindings() const { // 1. Let result be a new CSSNumericType. CSSNumericType result {}; // 2. For each baseType → power in the type of this, m_type.for_each_type_and_exponent([&result](NumericType::BaseType base_type, auto power) { // 1. If power is not 0, set result[baseType] to power. if (power == 0) return; switch (base_type) { case NumericType::BaseType::Length: result.length = power; break; case NumericType::BaseType::Angle: result.angle = power; break; case NumericType::BaseType::Time: result.time = power; break; case NumericType::BaseType::Frequency: result.frequency = power; break; case NumericType::BaseType::Resolution: result.resolution = power; break; case NumericType::BaseType::Flex: result.flex = power; break; case NumericType::BaseType::Percent: result.percent = power; break; case NumericType::BaseType::__Count: VERIFY_NOT_REACHED(); } }); // 3. If the percent hint of this is not null, if (auto percent_hint = m_type.percent_hint(); percent_hint.has_value()) { // 1. Set result[percentHint] to the percent hint of this. result.percent_hint = to_om_numeric_base_type(percent_hint.value()); } // 4. Return result. return result; } // https://drafts.css-houdini.org/css-typed-om-1/#serialize-a-cssnumericvalue String CSSNumericValue::to_string(SerializationParams const& params) const { // To serialize a CSSNumericValue this, given an optional minimum, a numeric value, and optional maximum, a numeric value: // 1. If this is a CSSUnitValue, serialize a CSSUnitValue from this, passing minimum and maximum. Return the result. if (auto* unit_value = as_if(this)) { return unit_value->serialize_unit_value(params.minimum, params.maximum); } // 2. Otherwise, serialize a CSSMathValue from this, and return the result. auto& math_value = as(*this); return math_value.serialize_math_value( params.nested ? CSSMathValue::Nested::Yes : CSSMathValue::Nested::No, params.parenless ? CSSMathValue::Parens::Without : CSSMathValue::Parens::With); } // https://drafts.css-houdini.org/css-typed-om-1/#rectify-a-numberish-value GC::Ref rectify_a_numberish_value(JS::Realm& realm, CSSNumberish const& numberish, Optional unit) { // To rectify a numberish value num, optionally to a given unit unit (defaulting to "number"), perform the following steps: return numberish.visit( // 1. If num is a CSSNumericValue, return num. [](GC::Root const& num) -> GC::Ref { return GC::Ref { *num }; }, // 2. If num is a double, return a new CSSUnitValue with its value internal slot set to num and its unit // internal slot set to unit. [&realm, &unit](double num) -> GC::Ref { return CSSUnitValue::create(realm, num, unit.value_or("number"_fly_string)); }); } // https://drafts.css-houdini.org/css-typed-om-1/#reify-a-numeric-value static WebIDL::ExceptionOr> reify_a_numeric_value(JS::Realm& realm, Parser::ComponentValue const& numeric_value) { // To reify a numeric value num: // 1. If num is a math function, reify a math expression from num and return the result. if (numeric_value.is_function()) { // AD-HOC: The only feasible way is to parse it as a StyleValue and rely on the reification code there. auto parser = Parser::Parser::create(Parser::ParsingParams {}, {}); if (auto calculation = parser.parse_calculated_value(numeric_value)) { auto reified = calculation->reify(realm, {}); // AD-HOC: Not all math functions can be reified. Until we have clear guidance on that, throw a SyntaxError. // See: https://github.com/w3c/css-houdini-drafts/issues/1090#issuecomment-3200229996 if (auto* reified_numeric = as_if(*reified)) { return GC::Ref { *reified_numeric }; } return WebIDL::SyntaxError::create(realm, "Unable to reify this math function."_utf16); } // AD-HOC: If we failed to parse it, I guess we throw a SyntaxError like in step 1 of CSSNumericValue::parse(). return WebIDL::SyntaxError::create(realm, "Unable to parse input as a calculation tree."_utf16); } // 2. If num is the unitless value 0 and num is a , return a new CSSUnitValue with its value internal // slot set to 0, and its unit internal slot set to "px". // FIXME: What does this mean? We just have a component value, it doesn't have any knowledge about whether 0 should // be interpreted as a dimension. // 3. Return a new CSSUnitValue with its value internal slot set to the numeric value of num, and its unit internal // slot set to "number" if num is a , "percent" if num is a , and num’s unit if num is a // . // If the value being reified is a computed value, the unit used must be the appropriate canonical unit for the // value’s type, with the numeric value scaled accordingly. // NB: The computed value part is irrelevant here, I think. if (numeric_value.is(Parser::Token::Type::Number)) return CSSUnitValue::create(realm, numeric_value.token().number_value(), "number"_fly_string); if (numeric_value.is(Parser::Token::Type::Percentage)) return CSSUnitValue::create(realm, numeric_value.token().percentage(), "percent"_fly_string); VERIFY(numeric_value.is(Parser::Token::Type::Dimension)); return CSSUnitValue::create(realm, numeric_value.token().dimension_value(), numeric_value.token().dimension_unit()); } // https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-parse WebIDL::ExceptionOr> CSSNumericValue::parse(JS::VM& vm, String const& css_text) { // The parse(cssText) method, when called, must perform the following steps: auto& realm = *vm.current_realm(); // 1. Parse a component value from cssText and let result be the result. If result is a syntax error, throw a // SyntaxError and abort this algorithm. auto maybe_component_value = Parser::Parser::create(Parser::ParsingParams {}, css_text).parse_as_component_value(); if (!maybe_component_value.has_value()) { return WebIDL::SyntaxError::create(realm, "Unable to parse input as a component value."_utf16); } auto& result = maybe_component_value.value(); // 2. If result is not a , , , or a math function, throw a // SyntaxError and abort this algorithm. auto is_a_math_function = [](Parser::ComponentValue const& component_value) -> bool { if (!component_value.is_function()) return false; return math_function_from_string(component_value.function().name).has_value(); }; if (!(result.is(Parser::Token::Type::Number) || result.is(Parser::Token::Type::Percentage) || result.is(Parser::Token::Type::Dimension) || is_a_math_function(result))) { return WebIDL::SyntaxError::create(realm, "Input not a , , , or a math function."_utf16); } // 3. If result is a and creating a type from result’s unit returns failure, throw a SyntaxError // and abort this algorithm. if (result.is(Parser::Token::Type::Dimension)) { if (!NumericType::create_from_unit(result.token().dimension_unit()).has_value()) { return WebIDL::SyntaxError::create(realm, "Input is with an unrecognized unit."_utf16); } } // 4. Reify a numeric value result, and return the result. return reify_a_numeric_value(realm, result); } }