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:
parent
70e82d39ed
commit
ff13ac2b79
5 changed files with 114 additions and 98 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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&);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue