2025-06-19 17:03:26 +01:00
/*
* Copyright ( c ) 2025 , Sam Atkins < sam @ ladybird . org >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.h>
# include <LibWeb/CSS/Parser/Parser.h>
2025-07-10 17:19:53 +01:00
# include <LibWeb/CSS/Parser/Syntax.h>
# include <LibWeb/CSS/Parser/SyntaxParsing.h>
2025-06-19 17:03:26 +01:00
# include <LibWeb/CSS/PropertyName.h>
# include <LibWeb/CSS/StyleComputer.h>
2025-07-10 17:19:53 +01:00
# include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
2025-06-19 17:03:26 +01:00
# include <LibWeb/CSS/StyleValues/UnresolvedStyleValue.h>
2025-08-05 12:32:54 +01:00
# include <LibWeb/DOM/Document.h>
2025-06-19 17:03:26 +01:00
# include <LibWeb/DOM/Element.h>
namespace Web : : CSS : : Parser {
bool SubstitutionContext : : operator = = ( SubstitutionContext const & other ) const
{
return dependency_type = = other . dependency_type
& & first = = other . first
& & second = = other . second ;
}
String SubstitutionContext : : to_string ( ) const
{
StringView type_name = [ this ] {
switch ( dependency_type ) {
case DependencyType : : Property :
return " Property " sv ;
case DependencyType : : Attribute :
return " Attribute " sv ;
case DependencyType : : Function :
return " Function " sv ;
}
VERIFY_NOT_REACHED ( ) ;
} ( ) ;
return MUST ( String : : formatted ( " {} {} {} " , type_name , first , second ) ) ;
}
void GuardedSubstitutionContexts : : guard ( SubstitutionContext & context )
{
for ( auto & existing_context : m_contexts ) {
if ( existing_context = = context ) {
existing_context . is_cyclic = true ;
context . is_cyclic = true ;
return ;
}
}
m_contexts . append ( context ) ;
}
void GuardedSubstitutionContexts : : unguard ( SubstitutionContext const & context )
{
[[maybe_unused]] auto const was_removed = m_contexts . remove_first_matching ( [ context ] ( auto const & other ) {
return context = = other ;
} ) ;
VERIFY ( was_removed ) ;
}
Optional < ArbitrarySubstitutionFunction > to_arbitrary_substitution_function ( FlyString const & name )
{
if ( name . equals_ignoring_ascii_case ( " attr " sv ) )
return ArbitrarySubstitutionFunction : : Attr ;
2025-08-05 12:32:54 +01:00
if ( name . equals_ignoring_ascii_case ( " env " sv ) )
return ArbitrarySubstitutionFunction : : Env ;
2025-06-19 17:03:26 +01:00
if ( name . equals_ignoring_ascii_case ( " var " sv ) )
return ArbitrarySubstitutionFunction : : Var ;
return { } ;
}
bool contains_guaranteed_invalid_value ( Vector < ComponentValue > const & values )
{
for ( auto const & value : values ) {
if ( value . contains_guaranteed_invalid_value ( ) )
return true ;
}
return false ;
}
// https://drafts.csswg.org/css-values-5/#replace-an-attr-function
static Vector < ComponentValue > replace_an_attr_function ( DOM : : AbstractElement & element , GuardedSubstitutionContexts & guarded_contexts , ArbitrarySubstitutionFunctionArguments const & arguments )
{
// 1. Let el be the element that the style containing the attr() function is being applied to.
// Let first arg be the first <declaration-value> in arguments.
// Let second arg be the <declaration-value>? passed after the comma, or null if there was no comma.
auto const & first_argument = arguments . first ( ) ;
auto const second_argument = arguments . get ( 1 ) ;
FlyString attribute_name ;
2025-07-10 17:19:53 +01:00
struct RawString { } ;
Variant < Empty , NonnullOwnPtr < SyntaxNode > , RawString > syntax ;
Optional < FlyString > unit_name ;
2025-06-19 17:03:26 +01:00
auto failure = [ & ] - > Vector < ComponentValue > {
// This is step 6, but defined here for convenience.
// 1. If second arg is null, and syntax was omitted, return an empty CSS <string>.
2025-07-10 17:19:53 +01:00
if ( ! second_argument . has_value ( ) & & syntax . has < Empty > ( ) )
2025-06-19 17:03:26 +01:00
return { Token : : create_string ( { } ) } ;
// 2. If second arg is null, return the guaranteed-invalid value.
if ( ! second_argument . has_value ( ) )
return { ComponentValue { GuaranteedInvalidValue { } } } ;
// 3. Substitute arbitrary substitution functions in second arg, and return the result.
return substitute_arbitrary_substitution_functions ( element , guarded_contexts , second_argument . value ( ) ) ;
} ;
// 2. Substitute arbitrary substitution functions in first arg, then parse it as <attr-name> <attr-type>?.
// If that returns failure, jump to the last step (labeled FAILURE).
// Otherwise, let attr name and syntax be the results of parsing (with syntax being null if <attr-type> was
// omitted), processed as specified in the definition of those arguments.
auto substituted = substitute_arbitrary_substitution_functions ( element , guarded_contexts , first_argument ) ;
TokenStream first_argument_tokens { substituted } ;
// <attr-name> = [ <ident-token>? '|' ]? <ident-token>
// FIXME: Support optional attribute namespace
if ( ! first_argument_tokens . next_token ( ) . is ( Token : : Type : : Ident ) )
return failure ( ) ;
attribute_name = first_argument_tokens . consume_a_token ( ) . token ( ) . ident ( ) ;
first_argument_tokens . discard_whitespace ( ) ;
// <attr-type> = type( <syntax> ) | raw-string | <attr-unit>
if ( first_argument_tokens . next_token ( ) . is ( Token : : Type : : Ident ) ) {
auto const & syntax_ident = first_argument_tokens . next_token ( ) . token ( ) . ident ( ) ;
if ( syntax_ident . equals_ignoring_ascii_case ( " raw-string " sv ) ) {
2025-07-10 17:19:53 +01:00
first_argument_tokens . discard_a_token ( ) ; // raw-string
syntax = RawString { } ;
} else if ( syntax_ident = = " % " sv
2025-09-02 13:58:20 +01:00
| | dimension_for_unit ( syntax_ident ) . has_value ( ) ) {
2025-07-10 17:19:53 +01:00
syntax = TypeSyntaxNode : : create ( " number " _fly_string ) . release_nonnull < SyntaxNode > ( ) ;
unit_name = first_argument_tokens . consume_a_token ( ) . token ( ) . ident ( ) ;
} else {
return failure ( ) ;
}
} else if ( first_argument_tokens . next_token ( ) . is_function ( " type " sv ) ) {
auto const & type_function = first_argument_tokens . consume_a_token ( ) . function ( ) ;
if ( auto parsed_syntax = parse_as_syntax ( type_function . value ) ) {
syntax = parsed_syntax . release_nonnull ( ) ;
2025-06-19 17:03:26 +01:00
} else {
2025-07-10 17:19:53 +01:00
return failure ( ) ;
2025-06-19 17:03:26 +01:00
}
}
first_argument_tokens . discard_whitespace ( ) ;
if ( first_argument_tokens . has_next_token ( ) )
return failure ( ) ;
// 3. If attr name exists as an attribute on el, let attr value be its value; otherwise jump to the last step (labeled FAILURE).
// FIXME: Attribute namespaces
auto attribute_value = element . element ( ) . get_attribute ( attribute_name ) ;
if ( ! attribute_value . has_value ( ) )
return failure ( ) ;
// 4. If syntax is null or the keyword raw-string, return a CSS <string> whose value is attr value.
// NOTE: No parsing or modification of any kind is performed on the value.
2025-07-10 17:19:53 +01:00
if ( syntax . visit (
[ ] ( Empty ) { return true ; } ,
[ ] ( RawString ) { return true ; } ,
[ ] ( auto & ) { return false ; } ) ) {
2025-06-19 17:03:26 +01:00
return { Token : : create_string ( * attribute_value ) } ;
2025-07-10 17:19:53 +01:00
}
2025-06-19 17:03:26 +01:00
// 5. Substitute arbitrary substitution functions in attr value, with «"attribute", attr name» as the substitution
2025-07-10 17:19:53 +01:00
// context, then parse with a <syntax> attr value, with syntax and el. If that succeeds, return the result;
// otherwise, jump to the last step (labeled FAILURE).
2025-06-19 17:03:26 +01:00
auto parser = Parser : : create ( ParsingParams { element . element ( ) . document ( ) } , attribute_value . value ( ) ) ;
auto unsubstituted_values = parser . parse_as_list_of_component_values ( ) ;
2025-07-10 17:19:53 +01:00
auto substituted_values = substitute_arbitrary_substitution_functions ( element , guarded_contexts , unsubstituted_values ,
SubstitutionContext { SubstitutionContext : : DependencyType : : Attribute , attribute_name . to_string ( ) } ) ;
auto parsed_value = parse_with_a_syntax ( ParsingParams { element . document ( ) } , substituted_values , * syntax . get < NonnullOwnPtr < SyntaxNode > > ( ) , element ) ;
if ( parsed_value - > is_guaranteed_invalid ( ) )
2025-06-19 17:03:26 +01:00
return failure ( ) ;
2025-07-10 17:19:53 +01:00
if ( unit_name . has_value ( ) ) {
// https://drafts.csswg.org/css-values-5/#ref-for-typedef-attr-type%E2%91%A0
// If given as an <attr-unit> value, the value is first parsed as if type(<number>) was specified, then the
// resulting numeric value is turned into a dimension with the corresponding unit, or a percentage if % was
// given. Values that fail to parse as a <number> trigger fallback.
// FIXME: The spec is ambiguous about what we should do for non-number-literals.
// Chromium treats them as invalid, so copy that for now.
// Spec issue: https://github.com/w3c/csswg-drafts/issues/12479
if ( ! parsed_value - > is_number ( ) )
return failure ( ) ;
return { Token : : create_dimension ( parsed_value - > as_number ( ) . number ( ) , unit_name . release_value ( ) ) } ;
}
2025-06-19 17:03:26 +01:00
2025-07-10 17:19:53 +01:00
return parsed_value - > tokenize ( ) ;
2025-06-19 17:03:26 +01:00
// 6. FAILURE:
// NB: Step 6 is a lambda defined at the top of the function.
}
2025-08-05 12:32:54 +01:00
// https://drafts.csswg.org/css-env/#substitute-an-env
static Vector < ComponentValue > replace_an_env_function ( DOM : : AbstractElement & element , GuardedSubstitutionContexts & guarded_contexts , ArbitrarySubstitutionFunctionArguments const & arguments )
{
// AD-HOC: env() is not defined as an ASF (and was defined before the ASF concept was), but behaves a lot like one.
// So, this is a combination of the spec's "substitute an env()" algorithm linked above, and the "replace a FOO function()" algorithms.
auto const & first_argument = arguments . first ( ) ;
auto const second_argument = arguments . get ( 1 ) ;
// AD-HOC: Substitute ASFs in the first argument.
auto substituted_first_argument = substitute_arbitrary_substitution_functions ( element , guarded_contexts , first_argument ) ;
// AD-HOC: Parse the arguments.
// env() = env( <custom-ident> <integer [0,∞]>*, <declaration-value>? )
TokenStream first_argument_tokens { substituted_first_argument } ;
first_argument_tokens . discard_whitespace ( ) ;
auto & name_token = first_argument_tokens . consume_a_token ( ) ;
if ( ! name_token . is ( Token : : Type : : Ident ) )
return { ComponentValue { GuaranteedInvalidValue { } } } ;
auto & name = name_token . token ( ) . ident ( ) ;
first_argument_tokens . discard_whitespace ( ) ;
Vector < i64 > indices ;
// FIXME: Are non-literal <integer>s allowed here?
while ( first_argument_tokens . has_next_token ( ) ) {
auto & maybe_integer = first_argument_tokens . consume_a_token ( ) ;
if ( ! maybe_integer . is ( Token : : Type : : Number ) )
return { ComponentValue { GuaranteedInvalidValue { } } } ;
auto & number = maybe_integer . token ( ) . number ( ) ;
if ( number . is_integer ( ) & & number . integer_value ( ) > = 0 )
indices . append ( number . integer_value ( ) ) ;
else
return { ComponentValue { GuaranteedInvalidValue { } } } ;
first_argument_tokens . discard_whitespace ( ) ;
}
// 1. If the name provided by the first argument of the env() function is a recognized environment variable name,
// the number of supplied integers matches the number of dimensions of the environment variable referenced by
// that name, and values of the indices correspond to a known sub-value, replace the env() function by the value
// of the named environment variable.
if ( auto environment_variable = environment_variable_from_string ( name ) ;
environment_variable . has_value ( ) & & indices . size ( ) = = environment_variable_dimension_count ( * environment_variable ) ) {
auto result = element . document ( ) . environment_variable_value ( * environment_variable , indices ) ;
if ( result . has_value ( ) )
return result . release_value ( ) ;
}
// 2. Otherwise, if the env() function has a fallback value as its second argument, replace the env() function by
// the fallback value. If there are any env() references in the fallback, substitute them as well.
// AD-HOC: Substitute all ASFs in the result.
if ( second_argument . has_value ( ) )
return substitute_arbitrary_substitution_functions ( element , guarded_contexts , second_argument . value ( ) ) ;
// 3. Otherwise, the property or descriptor containing the env() function is invalid at computed-value time.
return { ComponentValue { GuaranteedInvalidValue { } } } ;
}
2025-06-19 17:03:26 +01:00
// https://drafts.csswg.org/css-variables-1/#replace-a-var-function
static Vector < ComponentValue > replace_a_var_function ( DOM : : AbstractElement & element , GuardedSubstitutionContexts & guarded_contexts , ArbitrarySubstitutionFunctionArguments const & arguments )
{
// 1. Let el be the element that the style containing the var() function is being applied to.
// Let first arg be the first <declaration-value> in arguments.
// Let second arg be the <declaration-value>? passed after the comma, or null if there was no comma.
auto const & first_argument = arguments . first ( ) ;
auto const second_argument = arguments . get ( 1 ) ;
// 2. Substitute arbitrary substitution functions in first arg, then parse it as a <custom-property-name>.
// If parsing returned a <custom-property-name>, let result be the computed value of the corresponding custom
// property on el. Otherwise, let result be the guaranteed-invalid value.
auto substituted_first_argument = substitute_arbitrary_substitution_functions ( element , guarded_contexts , first_argument ) ;
TokenStream name_tokens { substituted_first_argument } ;
name_tokens . discard_whitespace ( ) ;
auto & name_token = name_tokens . consume_a_token ( ) ;
name_tokens . discard_whitespace ( ) ;
Vector < ComponentValue > result ;
if ( name_tokens . has_next_token ( ) | | ! name_token . is ( Token : : Type : : Ident ) | | ! is_a_custom_property_name_string ( name_token . token ( ) . ident ( ) ) ) {
result = { ComponentValue { GuaranteedInvalidValue { } } } ;
} else {
// Look up the value of the custom property
auto & custom_property_name = name_token . token ( ) . ident ( ) ;
auto custom_property_value = StyleComputer : : compute_value_of_custom_property ( element , custom_property_name , guarded_contexts ) ;
if ( custom_property_value - > is_guaranteed_invalid ( ) ) {
result = { ComponentValue { GuaranteedInvalidValue { } } } ;
} else if ( custom_property_value - > is_unresolved ( ) ) {
result = custom_property_value - > as_unresolved ( ) . values ( ) ;
} else {
dbgln_if ( CSS_PARSER_DEBUG , " Custom property `{}` is an unsupported type: {} " , custom_property_name , to_underlying ( custom_property_value - > type ( ) ) ) ;
result = { ComponentValue { GuaranteedInvalidValue { } } } ;
}
}
// FIXME: 3. If the custom property named by the var()’ s first argument is animation-tainted, and the var() is being used
// in a property that is not animatable, set result to the guaranteed-invalid value.
// 4. If result contains the guaranteed-invalid value, and second arg was provided, set result to the result of substitute arbitrary substitution functions on second arg.
if ( contains_guaranteed_invalid_value ( result ) & & second_argument . has_value ( ) )
result = substitute_arbitrary_substitution_functions ( element , guarded_contexts , second_argument . value ( ) ) ;
// 5. Return result.
return result ;
}
2025-06-27 10:45:46 +01:00
static ErrorOr < void > substitute_arbitrary_substitution_functions_step_2 ( DOM : : AbstractElement & element , GuardedSubstitutionContexts & guarded_contexts , TokenStream < ComponentValue > & source , Vector < ComponentValue > & dest )
2025-06-19 17:03:26 +01:00
{
// Step 2 of https://drafts.csswg.org/css-values-5/#substitute-arbitrary-substitution-function
// 2. For each arbitrary substitution function func in values (ordered via a depth-first pre-order traversal) that
// is not nested in the contents of another arbitrary substitution function:
while ( source . has_next_token ( ) ) {
auto const & value = source . consume_a_token ( ) ;
if ( value . is_function ( ) ) {
auto const & source_function = value . function ( ) ;
if ( auto maybe_function_id = to_arbitrary_substitution_function ( source_function . name ) ; maybe_function_id . has_value ( ) ) {
auto function_id = maybe_function_id . release_value ( ) ;
// FIXME: 1. Substitute early-invoked functions in func’ s contents, and let early result be the result.
auto const & early_result = source_function . value ;
// 2. If early result contains the guaranteed-invalid value, replace func in values with the guaranteed-invalid
// value and continue.
if ( contains_guaranteed_invalid_value ( early_result ) ) {
dest . empend ( GuaranteedInvalidValue { } ) ;
continue ;
}
// 3. Parse early result according to func’ s argument grammar. If this returns failure, replace func in values
// with the guaranteed-invalid value and continue; otherwise, let arguments be the result.
auto maybe_arguments = parse_according_to_argument_grammar ( function_id , early_result ) ;
if ( ! maybe_arguments . has_value ( ) ) {
dest . empend ( GuaranteedInvalidValue { } ) ;
continue ;
}
auto arguments = maybe_arguments . release_value ( ) ;
// 4. Replace an arbitrary substitution function for func, given arguments, as defined by that function.
// Let result be the returned list of component values.
auto result = replace_an_arbitrary_substitution_function ( element , guarded_contexts , function_id , arguments ) ;
// 5. If result contains the guaranteed-invalid value, replace func in values with the guaranteed-invalid value.
// Otherwise, replace func in values with result.
if ( contains_guaranteed_invalid_value ( result ) ) {
dest . empend ( GuaranteedInvalidValue { } ) ;
} else {
// NB: Because we're doing this in one pass recursively, we now need to substitute any ASFs in result.
TokenStream result_stream { result } ;
Vector < ComponentValue > result_after_processing ;
2025-06-27 10:45:46 +01:00
TRY ( substitute_arbitrary_substitution_functions_step_2 ( element , guarded_contexts , result_stream , result_after_processing ) ) ;
// NB: Protect against the billion-laughs attack by limiting to an arbitrary large number of tokens.
// https://drafts.csswg.org/css-values-5/#long-substitution
if ( source . remaining_token_count ( ) + result_after_processing . size ( ) > 16384 ) {
dest . clear ( ) ;
dest . empend ( GuaranteedInvalidValue { } ) ;
return Error : : from_string_literal ( " Stopped expanding arbitrary substitution functions: maximum length reached. " ) ;
}
2025-06-19 17:03:26 +01:00
dest . extend ( result_after_processing ) ;
}
continue ;
}
Vector < ComponentValue > function_values ;
TokenStream source_function_contents { source_function . value } ;
2025-06-27 10:45:46 +01:00
TRY ( substitute_arbitrary_substitution_functions_step_2 ( element , guarded_contexts , source_function_contents , function_values ) ) ;
2025-06-19 17:03:26 +01:00
dest . empend ( Function { source_function . name , move ( function_values ) } ) ;
continue ;
}
if ( value . is_block ( ) ) {
auto const & source_block = value . block ( ) ;
TokenStream source_block_values { source_block . value } ;
Vector < ComponentValue > block_values ;
2025-06-27 10:45:46 +01:00
TRY ( substitute_arbitrary_substitution_functions_step_2 ( element , guarded_contexts , source_block_values , block_values ) ) ;
2025-06-19 17:03:26 +01:00
dest . empend ( SimpleBlock { source_block . token , move ( block_values ) } ) ;
continue ;
}
dest . empend ( value ) ;
}
2025-06-27 10:45:46 +01:00
return { } ;
2025-06-19 17:03:26 +01:00
}
// https://drafts.csswg.org/css-values-5/#substitute-arbitrary-substitution-function
Vector < ComponentValue > substitute_arbitrary_substitution_functions ( DOM : : AbstractElement & element , GuardedSubstitutionContexts & guarded_contexts , Vector < ComponentValue > const & values , Optional < SubstitutionContext > context )
{
// To substitute arbitrary substitution functions in a sequence of component values values, given an optional
// substitution context context:
// 1. Guard context for the remainder of this algorithm. If context is marked as a cyclic substitution context,
// return the guaranteed-invalid value.
if ( context . has_value ( ) ) {
guarded_contexts . guard ( context . value ( ) ) ;
if ( context - > is_cyclic )
return { ComponentValue { GuaranteedInvalidValue { } } } ;
}
ScopeGuard const guard { [ & ] {
if ( context . has_value ( ) )
guarded_contexts . unguard ( context . value ( ) ) ;
} } ;
// 2. For each arbitrary substitution function func in values (ordered via a depth-first pre-order traversal) that
// is not nested in the contents of another arbitrary substitution function:
Vector < ComponentValue > new_values ;
TokenStream source { values } ;
2025-06-27 10:45:46 +01:00
auto maybe_error = substitute_arbitrary_substitution_functions_step_2 ( element , guarded_contexts , source , new_values ) ;
2025-07-21 13:30:42 +01:00
if ( maybe_error . is_error ( ) )
2025-06-27 10:45:46 +01:00
return { ComponentValue { GuaranteedInvalidValue { } } } ;
2025-06-19 17:03:26 +01:00
// 3. If context is marked as a cyclic substitution context, return the guaranteed-invalid value.
// NOTE: Nested arbitrary substitution functions may have marked context as cyclic in step 2.
if ( context . has_value ( ) & & context - > is_cyclic )
return { ComponentValue { GuaranteedInvalidValue { } } } ;
// 4. Return values.
return new_values ;
}
Optional < ArbitrarySubstitutionFunctionArguments > parse_according_to_argument_grammar ( ArbitrarySubstitutionFunction function , Vector < ComponentValue > const & values )
{
// Equivalent to `<declaration-value> , <declaration-value>?`, used by multiple argument grammars.
auto parse_declaration_value_then_optional_declaration_value = [ ] ( Vector < ComponentValue > const & values ) - > Optional < ArbitrarySubstitutionFunctionArguments > {
TokenStream tokens { values } ;
auto first_argument = Parser : : parse_declaration_value ( tokens , Parser : : StopAtComma : : Yes ) ;
if ( ! first_argument . has_value ( ) )
return OptionalNone { } ;
if ( ! tokens . has_next_token ( ) )
return ArbitrarySubstitutionFunctionArguments { first_argument . release_value ( ) } ;
2025-07-09 18:39:09 +01:00
if ( ! tokens . next_token ( ) . is ( Token : : Type : : Comma ) )
return { } ;
2025-06-19 17:03:26 +01:00
tokens . discard_a_token ( ) ; // ,
auto second_argument = Parser : : parse_declaration_value ( tokens , Parser : : StopAtComma : : No ) ;
if ( tokens . has_next_token ( ) )
return OptionalNone { } ;
return ArbitrarySubstitutionFunctionArguments { first_argument . release_value ( ) , second_argument . value_or ( { } ) } ;
} ;
switch ( function ) {
case ArbitrarySubstitutionFunction : : Attr :
// https://drafts.csswg.org/css-values-5/#attr-notation
// <attr-args> = attr( <declaration-value> , <declaration-value>? )
return parse_declaration_value_then_optional_declaration_value ( values ) ;
2025-08-05 12:32:54 +01:00
case ArbitrarySubstitutionFunction : : Env :
// https://drafts.csswg.org/css-env/#env-function
// AD-HOC: This doesn't have an argument-grammar definition.
// However, it follows the same format of "some CVs, then an optional comma and a fallback".
return parse_declaration_value_then_optional_declaration_value ( values ) ;
2025-06-19 17:03:26 +01:00
case ArbitrarySubstitutionFunction : : Var :
// https://drafts.csswg.org/css-variables/#funcdef-var
// <var-args> = var( <declaration-value> , <declaration-value>? )
return parse_declaration_value_then_optional_declaration_value ( values ) ;
}
VERIFY_NOT_REACHED ( ) ;
}
// https://drafts.csswg.org/css-values-5/#replace-an-arbitrary-substitution-function
Vector < ComponentValue > replace_an_arbitrary_substitution_function ( DOM : : AbstractElement & element , GuardedSubstitutionContexts & guarded_contexts , ArbitrarySubstitutionFunction function , ArbitrarySubstitutionFunctionArguments const & arguments )
{
switch ( function ) {
case ArbitrarySubstitutionFunction : : Attr :
return replace_an_attr_function ( element , guarded_contexts , arguments ) ;
2025-08-05 12:32:54 +01:00
case ArbitrarySubstitutionFunction : : Env :
return replace_an_env_function ( element , guarded_contexts , arguments ) ;
2025-06-19 17:03:26 +01:00
case ArbitrarySubstitutionFunction : : Var :
return replace_a_var_function ( element , guarded_contexts , arguments ) ;
}
VERIFY_NOT_REACHED ( ) ;
}
}