LibWeb: Avoid copying ASF argument component values

Parse arbitrary substitution function arguments as spans into the
existing component value list. This avoids copying each argument into a
new vector for var(), attr(), env(), if(), and inherit() parsing.

Expose a span-returning declaration-value parser that shares the same
walker as the existing vector-returning API, so the argument parser does
not duplicate declaration-value grammar logic. Substitution output still
uses owned vectors, and the unresolved Typed OM reifier now consumes
spans too.
This commit is contained in:
Andreas Kling 2026-05-28 00:56:25 +02:00 committed by Andreas Kling
parent 70e82d39ed
commit ff13ac2b79
5 changed files with 114 additions and 98 deletions

View file

@ -76,7 +76,7 @@ Optional<ArbitrarySubstitutionFunction> to_arbitrary_substitution_function(FlySt
return {};
}
bool contains_guaranteed_invalid_value(Vector<ComponentValue> const& values)
bool contains_guaranteed_invalid_value(ReadonlySpan<ComponentValue> values)
{
for (auto const& value : values) {
if (value.contains_guaranteed_invalid_value())
@ -85,7 +85,7 @@ bool contains_guaranteed_invalid_value(Vector<ComponentValue> const& values)
return false;
}
static bool contains_attr_tainted_value(Vector<ComponentValue> const& values)
static bool contains_attr_tainted_value(ReadonlySpan<ComponentValue> values)
{
for (auto const& value : values) {
if (value.contains_attr_tainted_value())
@ -107,7 +107,7 @@ static Vector<ComponentValue> replace_an_attr_function(DOM::AbstractElement& ele
// 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 declaration_value_list = arguments.get<DeclarationValueList>();
auto const& declaration_value_list = arguments.get<DeclarationValueList>();
auto const& first_argument = declaration_value_list.first();
auto const second_argument = declaration_value_list.get(1);
@ -269,7 +269,7 @@ static Vector<ComponentValue> replace_an_env_function(DOM::AbstractElement& elem
{
// 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 declaration_value_list = arguments.get<DeclarationValueList>();
auto const& declaration_value_list = arguments.get<DeclarationValueList>();
auto const& first_argument = declaration_value_list.first();
auto const second_argument = declaration_value_list.get(1);
@ -370,7 +370,7 @@ static Vector<ComponentValue> replace_an_if_function(DOM::AbstractElement& eleme
static Vector<ComponentValue> replace_an_inherit_function(DOM::AbstractElement& element, GuardedSubstitutionContexts& guarded_contexts, ArbitrarySubstitutionFunctionArguments const& arguments)
{
// To replace an inherit() function, given a list of arguments:
auto declaration_value_list = arguments.get<DeclarationValueList>();
auto const& declaration_value_list = arguments.get<DeclarationValueList>();
auto const& first_argument = declaration_value_list.first();
auto const second_argument = declaration_value_list.get(1);
@ -410,7 +410,7 @@ static Vector<ComponentValue> replace_a_var_function(DOM::AbstractElement& eleme
// 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 declaration_value_list = arguments.get<DeclarationValueList>();
auto const& declaration_value_list = arguments.get<DeclarationValueList>();
auto const& first_argument = declaration_value_list.first();
auto const second_argument = declaration_value_list.get(1);
@ -524,7 +524,7 @@ static ErrorOr<void> substitute_arbitrary_substitution_functions_step_2(DOM::Abs
}
// 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)
Vector<ComponentValue> substitute_arbitrary_substitution_functions(DOM::AbstractElement& element, GuardedSubstitutionContexts& guarded_contexts, ReadonlySpan<ComponentValue> values, Optional<SubstitutionContext> context)
{
// To substitute arbitrary substitution functions in a sequence of component values values, given an optional
// substitution context context:
@ -558,11 +558,11 @@ Vector<ComponentValue> substitute_arbitrary_substitution_functions(DOM::Abstract
return new_values;
}
Optional<ArbitrarySubstitutionFunctionArguments> parse_according_to_argument_grammar(ArbitrarySubstitutionFunction function, Vector<ComponentValue> const& values)
Optional<ArbitrarySubstitutionFunctionArguments> parse_according_to_argument_grammar(ArbitrarySubstitutionFunction function, ReadonlySpan<ComponentValue> values)
{
// Equivalent to `<declaration-value> , <declaration-value>?`, used by multiple argument grammars.
auto parse_declaration_value_then_optional_declaration_value = [](TokenStream<ComponentValue>& tokens, Token::Type separator) -> Optional<DeclarationValueList> {
auto first_argument = Parser::parse_declaration_value(tokens, separator);
auto first_argument = Parser::parse_declaration_value_as_span(tokens, separator);
if (!first_argument.has_value())
return OptionalNone {};
@ -574,7 +574,7 @@ Optional<ArbitrarySubstitutionFunctionArguments> parse_according_to_argument_gra
tokens.discard_a_token(); // separator
auto second_argument = Parser::parse_declaration_value(tokens);
auto second_argument = Parser::parse_declaration_value_as_span(tokens);
return DeclarationValueList { first_argument.release_value(), second_argument.value_or({}) };
};
@ -610,7 +610,10 @@ Optional<ArbitrarySubstitutionFunctionArguments> parse_according_to_argument_gra
if (!if_args_branch.has_value())
break;
args.append({ if_args_branch->first(), if_args_branch->get(1).map([](auto const& value) { return value; }) });
Optional<ReadonlySpan<ComponentValue>> value;
if (auto second_argument = if_args_branch->get(1); second_argument.has_value())
value = second_argument.value();
args.append({ if_args_branch->first(), value });
if (!tokens.next_token().is(Token::Type::Semicolon))
break;

View file

@ -6,6 +6,7 @@
#pragma once
#include <AK/Span.h>
#include <AK/String.h>
#include <LibWeb/Forward.h>
@ -46,20 +47,21 @@ enum class ArbitrarySubstitutionFunction : u8 {
};
[[nodiscard]] Optional<ArbitrarySubstitutionFunction> to_arbitrary_substitution_function(FlyString const& name);
bool contains_guaranteed_invalid_value(Vector<ComponentValue> const&);
bool contains_guaranteed_invalid_value(ReadonlySpan<ComponentValue>);
[[nodiscard]] Vector<ComponentValue> substitute_arbitrary_substitution_functions(DOM::AbstractElement&, GuardedSubstitutionContexts&, Vector<ComponentValue> const&, Optional<SubstitutionContext> = {});
[[nodiscard]] Vector<ComponentValue> substitute_arbitrary_substitution_functions(DOM::AbstractElement&, GuardedSubstitutionContexts&, ReadonlySpan<ComponentValue>, Optional<SubstitutionContext> = {});
using DeclarationValueList = Vector<Vector<ComponentValue>>;
using DeclarationValueList = Vector<ReadonlySpan<ComponentValue>>;
struct IfArgsBranch {
Vector<ComponentValue> condition;
Optional<Vector<ComponentValue>> value;
ReadonlySpan<ComponentValue> condition;
Optional<ReadonlySpan<ComponentValue>> value;
};
using IfArgs = Vector<IfArgsBranch>;
using ArbitrarySubstitutionFunctionArguments = Variant<DeclarationValueList, IfArgs>;
[[nodiscard]] Optional<ArbitrarySubstitutionFunctionArguments> parse_according_to_argument_grammar(ArbitrarySubstitutionFunction, Vector<ComponentValue> const&);
// The returned argument spans borrow from the input component value list.
[[nodiscard]] Optional<ArbitrarySubstitutionFunctionArguments> parse_according_to_argument_grammar(ArbitrarySubstitutionFunction, ReadonlySpan<ComponentValue>);
[[nodiscard]] Vector<ComponentValue> replace_an_arbitrary_substitution_function(DOM::AbstractElement&, GuardedSubstitutionContexts&, ArbitrarySubstitutionFunction, ArbitrarySubstitutionFunctionArguments const&);

View file

@ -161,6 +161,7 @@ public:
[[nodiscard]] NonnullRefPtr<StyleValue const> parse_as_sizes_attribute(DOM::Element const& element, HTML::HTMLImageElement const* img = nullptr);
static Optional<Vector<ComponentValue>> parse_declaration_value(TokenStream<ComponentValue>&, Optional<Token::Type> end_token_type = {});
static Optional<ReadonlySpan<ComponentValue>> parse_declaration_value_as_span(TokenStream<ComponentValue>&, Optional<Token::Type> end_token_type = {});
NonnullRefPtr<StyleValue const> parse_with_a_syntax(Vector<ComponentValue> const& input, SyntaxNode const& syntax);

View file

@ -113,97 +113,106 @@ RefPtr<StyleValueList const> Parser::parse_comma_separated_value_list(TokenStrea
return StyleValueList::create(move(values), StyleValueList::Separator::Comma);
}
// https://drafts.csswg.org/css-syntax/#typedef-declaration-value
Optional<Vector<ComponentValue>> Parser::parse_declaration_value(TokenStream<ComponentValue>& tokens, Optional<Token::Type> end_token_type)
enum class DeclarationValueNested : u8 {
No,
Yes,
};
static void consume_declaration_value(TokenStream<ComponentValue>& tokens, Optional<Token::Type> end_token_type, DeclarationValueNested nested)
{
// The <declaration-value> production matches any sequence of one or more tokens, so long as the sequence does not
// contain <bad-string-token>, <bad-url-token>, unmatched <)-token>, <]-token>, or <}-token>, or top-level
// <semicolon-token> tokens or <delim-token> tokens with a value of "!". It represents the entirety of what a valid
// declaration can have as its value.
Vector<ComponentValue> top_level_declaration_value;
auto transaction = tokens.begin_transaction();
while (tokens.has_next_token()) {
auto const& peek = tokens.next_token();
AK::Function<void(TokenStream<ComponentValue>&, Nested)> const parse_declaration_value_impl = [&](TokenStream<ComponentValue>& current_tokens, Nested nested) {
auto consume_a_token = [&]() {
if (nested == Nested::No)
top_level_declaration_value.append(current_tokens.consume_a_token());
else
current_tokens.discard_a_token();
};
auto transaction = current_tokens.begin_transaction();
while (current_tokens.has_next_token()) {
auto const& peek = current_tokens.next_token();
if (peek.is_block()) {
TokenStream block_stream { peek.block().value };
parse_declaration_value_impl(block_stream, Nested::Yes);
if (block_stream.is_empty()) {
consume_a_token();
continue;
}
break;
}
if (peek.is_function()) {
TokenStream function_stream { peek.function().value };
parse_declaration_value_impl(function_stream, Nested::Yes);
if (function_stream.is_empty()) {
consume_a_token();
continue;
}
break;
}
if (!peek.is_token()) {
consume_a_token();
if (peek.is_block()) {
TokenStream block_stream { peek.block().value };
consume_declaration_value(block_stream, end_token_type, DeclarationValueNested::Yes);
if (block_stream.is_empty()) {
tokens.discard_a_token();
continue;
}
bool valid = true;
switch (peek.token().type()) {
case Token::Type::Invalid:
case Token::Type::EndOfFile:
case Token::Type::BadString:
case Token::Type::BadUrl:
// NB: We're dealing with ComponentValues, so all valid function and block-related tokens will already be
// converted to Function or SimpleBlock ComponentValues. Any remaining ones are invalid.
case Token::Type::Function:
case Token::Type::OpenCurly:
case Token::Type::OpenParen:
case Token::Type::OpenSquare:
case Token::Type::CloseCurly:
case Token::Type::CloseParen:
case Token::Type::CloseSquare:
valid = false;
break;
case Token::Type::Semicolon:
valid = nested == Nested::Yes;
break;
case Token::Type::Delim:
valid = nested == Nested::Yes || peek.token().delim() != '!';
break;
default:
valid = nested == Nested::Yes || !end_token_type.has_value() || !peek.is(end_token_type.value());
break;
}
if (!valid)
break;
consume_a_token();
break;
}
transaction.commit();
};
if (peek.is_function()) {
TokenStream function_stream { peek.function().value };
consume_declaration_value(function_stream, end_token_type, DeclarationValueNested::Yes);
if (function_stream.is_empty()) {
tokens.discard_a_token();
continue;
}
parse_declaration_value_impl(tokens, Nested::No);
break;
}
if (top_level_declaration_value.is_empty())
if (!peek.is_token()) {
tokens.discard_a_token();
continue;
}
bool valid = true;
switch (peek.token().type()) {
case Token::Type::Invalid:
case Token::Type::EndOfFile:
case Token::Type::BadString:
case Token::Type::BadUrl:
// NB: We're dealing with ComponentValues, so all valid function and block-related tokens will already be
// converted to Function or SimpleBlock ComponentValues. Any remaining ones are invalid.
case Token::Type::Function:
case Token::Type::OpenCurly:
case Token::Type::OpenParen:
case Token::Type::OpenSquare:
case Token::Type::CloseCurly:
case Token::Type::CloseParen:
case Token::Type::CloseSquare:
valid = false;
break;
case Token::Type::Semicolon:
valid = nested == DeclarationValueNested::Yes;
break;
case Token::Type::Delim:
valid = nested == DeclarationValueNested::Yes || peek.token().delim() != '!';
break;
default:
valid = nested == DeclarationValueNested::Yes || !end_token_type.has_value() || !peek.is(end_token_type.value());
break;
}
if (!valid)
break;
tokens.discard_a_token();
}
transaction.commit();
}
// https://drafts.csswg.org/css-syntax/#typedef-declaration-value
Optional<ReadonlySpan<ComponentValue>> Parser::parse_declaration_value_as_span(TokenStream<ComponentValue>& tokens, Optional<Token::Type> end_token_type)
{
auto start_index = tokens.current_index();
consume_declaration_value(tokens, end_token_type, DeclarationValueNested::No);
auto declaration_value = tokens.tokens_since(start_index);
if (declaration_value.is_empty())
return OptionalNone {};
return top_level_declaration_value;
return declaration_value;
}
// https://drafts.csswg.org/css-syntax/#typedef-declaration-value
Optional<Vector<ComponentValue>> Parser::parse_declaration_value(TokenStream<ComponentValue>& tokens, Optional<Token::Type> end_token_type)
{
auto declaration_value = parse_declaration_value_as_span(tokens, end_token_type);
if (!declaration_value.has_value())
return OptionalNone {};
return Vector<ComponentValue> { declaration_value.value() };
}
// https://drafts.csswg.org/css-fonts-4/#family-name-syntax

View file

@ -106,7 +106,7 @@ bool UnresolvedStyleValue::equals(StyleValue const& other) const
return comparison_text() == other_unresolved.comparison_text();
}
static GC::Ref<CSSUnparsedValue> reify_a_list_of_component_values(JS::Realm&, Vector<Parser::ComponentValue>);
static GC::Ref<CSSUnparsedValue> reify_a_list_of_component_values(JS::Realm&, ReadonlySpan<Parser::ComponentValue>);
// https://drafts.css-houdini.org/css-typed-om-1/#reify-var
static GC::Ptr<CSSVariableReferenceValue> reify_a_var_reference(JS::Realm& realm, Parser::Function function)
@ -147,7 +147,7 @@ static GC::Ptr<CSSVariableReferenceValue> reify_a_var_reference(JS::Realm& realm
class Reifier {
public:
static Vector<CSSUnparsedSegment> reify(JS::Realm& realm, Vector<Parser::ComponentValue> const& source_values)
static Vector<CSSUnparsedSegment> reify(JS::Realm& realm, ReadonlySpan<Parser::ComponentValue> source_values)
{
Reifier reifier;
reifier.process_values(realm, source_values);
@ -157,7 +157,7 @@ public:
}
private:
void process_values(JS::Realm& realm, Vector<Parser::ComponentValue> const& source_values)
void process_values(JS::Realm& realm, ReadonlySpan<Parser::ComponentValue> 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
@ -204,7 +204,7 @@ private:
Vector<Parser::ComponentValue> m_unserialized_values {};
};
static GC::Ref<CSSUnparsedValue> reify_a_list_of_component_values(JS::Realm& realm, Vector<Parser::ComponentValue> component_values)
static GC::Ref<CSSUnparsedValue> reify_a_list_of_component_values(JS::Realm& realm, ReadonlySpan<Parser::ComponentValue> 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.
@ -218,7 +218,8 @@ static GC::Ref<CSSUnparsedValue> reify_a_list_of_component_values(JS::Realm& rea
// https://drafts.css-houdini.org/css-typed-om-1/#reify-a-list-of-component-values
GC::Ref<CSSStyleValue> UnresolvedStyleValue::reify(JS::Realm& realm, FlyString const&) const
{
return reify_a_list_of_component_values(realm, values());
auto component_values = values();
return reify_a_list_of_component_values(realm, component_values);
}
}