/* * Copyright (c) 2018-2020, Andreas Kling * Copyright (c) 2021, Tobias Christiansen * Copyright (c) 2021-2025, Sam Atkins * Copyright (c) 2022-2023, MacDue * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include namespace Web::CSS { ValueComparingNonnullRefPtr UnresolvedStyleValue::create(Vector&& values, Optional substitution_presence, Optional original_source_text) { if (!substitution_presence.has_value()) { substitution_presence = Parser::SubstitutionFunctionsPresence {}; for (auto const& value : values) { if (value.is_function()) value.function().contains_arbitrary_substitution_function(*substitution_presence); if (value.is_block()) value.block().contains_arbitrary_substitution_function(*substitution_presence); } } return adopt_ref(*new (nothrow) UnresolvedStyleValue(move(values), *substitution_presence, move(original_source_text))); } UnresolvedStyleValue::UnresolvedStyleValue(Vector&& values, Parser::SubstitutionFunctionsPresence substitution_presence, Optional original_source_text) : StyleValue(Type::Unresolved) , m_values(move(values)) , m_substitution_functions_presence(substitution_presence) , m_original_source_text(move(original_source_text)) { } String UnresolvedStyleValue::to_string(SerializationMode) const { if (m_original_source_text.has_value()) return *m_original_source_text; return serialize_a_series_of_component_values(m_values, InsertWhitespace::Yes); } bool UnresolvedStyleValue::equals(StyleValue const& other) const { if (type() != other.type()) return false; return values() == other.as_unresolved().values(); } static GC::Ref reify_a_list_of_component_values(JS::Realm&, Vector); // https://drafts.css-houdini.org/css-typed-om-1/#reify-var static GC::Root reify_a_var_reference(JS::Realm& realm, Parser::Function function) { // NB: A var() might not be representable as a CSSVariableReferenceValue, for example if it has invalid syntax or // it contains an ASF in its variable-name slot. In those cases, we return null here, so it's treated like a // regular function. auto maybe_var_arguments = Parser::parse_according_to_argument_grammar(Parser::ArbitrarySubstitutionFunction::Var, function.value); if (!maybe_var_arguments.has_value()) return nullptr; auto var_arguments = maybe_var_arguments.release_value(); // NB: Try to parse the variable name. If we can't, return null as above. Parser::TokenStream tokens { var_arguments.first() }; tokens.discard_whitespace(); auto& maybe_variable = tokens.consume_a_token(); tokens.discard_whitespace(); if (tokens.has_next_token() || !maybe_variable.is(Parser::Token::Type::Ident) || !is_a_custom_property_name_string(maybe_variable.token().ident())) return nullptr; // To reify a var() reference var: // 1. Let object be a new CSSVariableReferenceValue. // 2. Set object’s variable internal slot to the serialization of the providing the variable name. FlyString variable = maybe_variable.token().ident(); // 3. If var has a fallback value, set object’s fallback internal slot to the result of reifying the fallback’s // component values. Otherwise, set it to null. GC::Ptr fallback; if (var_arguments.size() > 1) fallback = reify_a_list_of_component_values(realm, var_arguments[1]); // 4. Return object. return CSSVariableReferenceValue::create(realm, move(variable), move(fallback)); } class Reifier { public: static Vector reify(JS::Realm& realm, Vector const& source_values) { Reifier reifier; reifier.process_values(realm, source_values); if (!reifier.m_unserialized_values.is_empty()) reifier.serialize_unserialized_values(); return move(reifier.m_reified_values); } private: void process_values(JS::Realm& realm, Vector const& source_values) { // NB: var() could be arbitrarily nested within other functions and blocks, so we have to walk the tree. // Also, a var() might not be representable, if it has an ASF in place of its name, so those will be part // of a string instead. for (auto const& component_value : source_values) { if (component_value.is_function("var"sv)) { // First parse the var() to see if it is representable as a CSSVariableReferenceValue. It might not be, // for example if it has an ASF in the place of its variable name. In that case we fall back to // serializing it like a regular function. if (auto var_reference = reify_a_var_reference(realm, component_value.function())) { serialize_unserialized_values(); m_reified_values.append(move(var_reference)); continue; } } if (component_value.is_function()) { auto& function = component_value.function(); m_unserialized_values.append(function.name_token); process_values(realm, function.value); m_unserialized_values.append(function.end_token); continue; } if (component_value.is_block()) { auto& block = component_value.block(); m_unserialized_values.append(block.token); process_values(realm, block.value); m_unserialized_values.append(block.end_token); continue; } m_unserialized_values.append(component_value); } } void serialize_unserialized_values() { // FIXME: Stop inserting whitespace once we stop removing it during parsing. m_reified_values.append(serialize_a_series_of_component_values(m_unserialized_values, InsertWhitespace::Yes)); m_unserialized_values.clear_with_capacity(); } Vector m_reified_values {}; Vector m_unserialized_values {}; }; static GC::Ref reify_a_list_of_component_values(JS::Realm& realm, Vector component_values) { // To reify a list of component values from a list: // 1. Replace all var() references in list with CSSVariableReferenceValue objects, as described in §5.4 var() References. // 2. Replace each remaining maximal subsequence of component values in list with a single string of their concatenated serializations. auto reified_values = Reifier::reify(realm, component_values); // 3. Return a new CSSUnparsedValue whose [[tokens]] slot is set to list. return CSSUnparsedValue::create(realm, move(reified_values)); } // https://drafts.css-houdini.org/css-typed-om-1/#reify-a-list-of-component-values GC::Ref UnresolvedStyleValue::reify(JS::Realm& realm, String const&) const { return reify_a_list_of_component_values(realm, m_values); } }