mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-16 05:51:55 +00:00
LibWeb/CSS: Reimplement var()/attr() as arbitrary substitution functions
"Arbitrary substitution functions" are a family of functions that includes var() and attr(). All of them resolve to an arbitrary set of component values that are not known at parse-time, so they have to be substituted at computed-value time. Besides it being nice to follow the spec closely, this means we'll be able to implement the others (such as `if()` and `inherit()`) more easily. The main omission here is the new "spread syntax", which can be implemented in the future.
This commit is contained in:
parent
b417d13a7b
commit
b6032b0fcd
Notes:
github-actions[bot]
2025-07-09 15:45:46 +00:00
Author: https://github.com/AtkinsSJ
Commit: b6032b0fcd
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5226
Reviewed-by: https://github.com/tcl3 ✅
11 changed files with 480 additions and 346 deletions
|
@ -132,6 +132,7 @@ set(SOURCES
|
|||
CSS/Number.cpp
|
||||
CSS/PageSelector.cpp
|
||||
CSS/ParsedFontFace.cpp
|
||||
CSS/Parser/ArbitrarySubstitutionFunctions.cpp
|
||||
CSS/Parser/ComponentValue.cpp
|
||||
CSS/Parser/DescriptorParsing.cpp
|
||||
CSS/Parser/GradientParsing.cpp
|
||||
|
|
369
Libraries/LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.cpp
Normal file
369
Libraries/LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.cpp
Normal file
|
@ -0,0 +1,369 @@
|
|||
/*
|
||||
* 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>
|
||||
#include <LibWeb/CSS/PropertyName.h>
|
||||
#include <LibWeb/CSS/StyleComputer.h>
|
||||
#include <LibWeb/CSS/StyleValues/UnresolvedStyleValue.h>
|
||||
#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;
|
||||
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;
|
||||
Optional<FlyString> maybe_syntax = {};
|
||||
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>.
|
||||
if (!second_argument.has_value() && !maybe_syntax.has_value())
|
||||
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>
|
||||
// FIXME: Support type(<syntax>)
|
||||
bool is_dimension_unit = false;
|
||||
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)) {
|
||||
maybe_syntax = first_argument_tokens.consume_a_token().token().ident();
|
||||
} else {
|
||||
is_dimension_unit = syntax_ident == "%"sv
|
||||
|| Angle::unit_from_name(syntax_ident).has_value()
|
||||
|| Flex::unit_from_name(syntax_ident).has_value()
|
||||
|| Frequency::unit_from_name(syntax_ident).has_value()
|
||||
|| Length::unit_from_name(syntax_ident).has_value()
|
||||
|| Resolution::unit_from_name(syntax_ident).has_value()
|
||||
|| Time::unit_from_name(syntax_ident).has_value();
|
||||
if (is_dimension_unit)
|
||||
maybe_syntax = first_argument_tokens.consume_a_token().token().ident();
|
||||
}
|
||||
}
|
||||
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.
|
||||
if (!maybe_syntax.has_value() || maybe_syntax->equals_ignoring_ascii_case("raw-string"sv))
|
||||
return { Token::create_string(*attribute_value) };
|
||||
auto syntax = maybe_syntax.release_value();
|
||||
|
||||
// 5. Substitute arbitrary substitution functions in attr value, with «"attribute", attr name» as the substitution
|
||||
// context, then parse with a attr value, with syntax and el. If that succeeds, return the result; otherwise,
|
||||
// jump to the last step (labeled FAILURE).
|
||||
auto parser = Parser::create(ParsingParams { element.element().document() }, attribute_value.value());
|
||||
auto unsubstituted_values = parser.parse_as_list_of_component_values();
|
||||
auto substituted_values = substitute_arbitrary_substitution_functions(element, guarded_contexts, unsubstituted_values, SubstitutionContext { SubstitutionContext::DependencyType::Attribute, attribute_name.to_string() });
|
||||
|
||||
// FIXME: Parse using the syntax. For now we just handle `<attr-unit>` here.
|
||||
TokenStream value_tokens { substituted_values };
|
||||
value_tokens.discard_whitespace();
|
||||
auto const& component_value = value_tokens.consume_a_token();
|
||||
value_tokens.discard_whitespace();
|
||||
if (value_tokens.has_next_token())
|
||||
return failure();
|
||||
|
||||
if (component_value.is(Token::Type::Number) && is_dimension_unit)
|
||||
return { Token::create_dimension(component_value.token().number_value(), syntax) };
|
||||
|
||||
return failure();
|
||||
|
||||
// 6. FAILURE:
|
||||
// NB: Step 6 is a lambda defined at the top of the function.
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
static void substitute_arbitrary_substitution_functions_step_2(DOM::AbstractElement& element, GuardedSubstitutionContexts& guarded_contexts, TokenStream<ComponentValue>& source, Vector<ComponentValue>& dest)
|
||||
{
|
||||
// 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;
|
||||
substitute_arbitrary_substitution_functions_step_2(element, guarded_contexts, result_stream, result_after_processing);
|
||||
dest.extend(result_after_processing);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector<ComponentValue> function_values;
|
||||
TokenStream source_function_contents { source_function.value };
|
||||
substitute_arbitrary_substitution_functions_step_2(element, guarded_contexts, source_function_contents, function_values);
|
||||
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;
|
||||
substitute_arbitrary_substitution_functions_step_2(element, guarded_contexts, source_block_values, block_values);
|
||||
dest.empend(SimpleBlock { source_block.token, move(block_values) });
|
||||
continue;
|
||||
}
|
||||
dest.empend(value);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 };
|
||||
substitute_arbitrary_substitution_functions_step_2(element, guarded_contexts, source, new_values);
|
||||
|
||||
// 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() };
|
||||
|
||||
VERIFY(tokens.next_token().is(Token::Type::Comma));
|
||||
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);
|
||||
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);
|
||||
case ArbitrarySubstitutionFunction::Var:
|
||||
return replace_a_var_function(element, guarded_contexts, arguments);
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
}
|
54
Libraries/LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.h
Normal file
54
Libraries/LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/CSS/Parser/Parser.h>
|
||||
|
||||
namespace Web::CSS::Parser {
|
||||
|
||||
// https://drafts.csswg.org/css-values-5/#substitution-context
|
||||
struct SubstitutionContext {
|
||||
enum class DependencyType : u8 {
|
||||
Property,
|
||||
Attribute,
|
||||
Function,
|
||||
};
|
||||
DependencyType dependency_type;
|
||||
String first;
|
||||
Optional<String> second {};
|
||||
|
||||
bool is_cyclic { false };
|
||||
|
||||
bool operator==(SubstitutionContext const&) const;
|
||||
String to_string() const;
|
||||
};
|
||||
|
||||
class GuardedSubstitutionContexts {
|
||||
public:
|
||||
void guard(SubstitutionContext&);
|
||||
void unguard(SubstitutionContext const&);
|
||||
|
||||
private:
|
||||
Vector<SubstitutionContext&> m_contexts;
|
||||
};
|
||||
|
||||
enum class ArbitrarySubstitutionFunction : u8 {
|
||||
Attr,
|
||||
Var,
|
||||
};
|
||||
[[nodiscard]] Optional<ArbitrarySubstitutionFunction> to_arbitrary_substitution_function(FlyString const& name);
|
||||
|
||||
bool contains_guaranteed_invalid_value(Vector<ComponentValue> const&);
|
||||
|
||||
[[nodiscard]] Vector<ComponentValue> substitute_arbitrary_substitution_functions(DOM::AbstractElement&, GuardedSubstitutionContexts&, Vector<ComponentValue> const&, Optional<SubstitutionContext> = {});
|
||||
|
||||
using ArbitrarySubstitutionFunctionArguments = Vector<Vector<ComponentValue>>;
|
||||
[[nodiscard]] Optional<ArbitrarySubstitutionFunctionArguments> parse_according_to_argument_grammar(ArbitrarySubstitutionFunction, Vector<ComponentValue> const&);
|
||||
|
||||
[[nodiscard]] Vector<ComponentValue> replace_an_arbitrary_substitution_function(DOM::AbstractElement&, GuardedSubstitutionContexts&, ArbitrarySubstitutionFunction, ArbitrarySubstitutionFunctionArguments const&);
|
||||
|
||||
}
|
|
@ -134,7 +134,7 @@ public:
|
|||
|
||||
Vector<ComponentValue> parse_as_list_of_component_values();
|
||||
|
||||
static NonnullRefPtr<CSSStyleValue const> resolve_unresolved_style_value(ParsingParams const&, DOM::Element&, Optional<PseudoElement>, PropertyIDOrCustomPropertyName, UnresolvedStyleValue const&);
|
||||
static NonnullRefPtr<CSSStyleValue const> resolve_unresolved_style_value(ParsingParams const&, DOM::Element&, Optional<PseudoElement>, PropertyIDOrCustomPropertyName, UnresolvedStyleValue const&, Optional<GuardedSubstitutionContexts&> = {});
|
||||
|
||||
[[nodiscard]] LengthOrCalculated parse_as_sizes_attribute(DOM::Element const& element, HTML::HTMLImageElement const* img = nullptr);
|
||||
|
||||
|
@ -513,10 +513,7 @@ private:
|
|||
|
||||
OwnPtr<BooleanExpression> parse_supports_feature(TokenStream<ComponentValue>&);
|
||||
|
||||
NonnullRefPtr<CSSStyleValue const> resolve_unresolved_style_value(DOM::Element&, Optional<PseudoElement>, PropertyIDOrCustomPropertyName, UnresolvedStyleValue const&);
|
||||
bool expand_variables(DOM::Element&, Optional<PseudoElement>, FlyString const& property_name, HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>>& dependencies, TokenStream<ComponentValue>& source, Vector<ComponentValue>& dest);
|
||||
bool expand_unresolved_values(DOM::Element&, FlyString const& property_name, TokenStream<ComponentValue>& source, Vector<ComponentValue>& dest);
|
||||
bool substitute_attr_function(DOM::Element& element, FlyString const& property_name, Function const& attr_function, Vector<ComponentValue>& dest);
|
||||
NonnullRefPtr<CSSStyleValue const> resolve_unresolved_style_value(DOM::AbstractElement&, GuardedSubstitutionContexts&, PropertyIDOrCustomPropertyName, UnresolvedStyleValue const&);
|
||||
|
||||
static bool has_ignored_vendor_prefix(StringView);
|
||||
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
#include <AK/StringConversions.h>
|
||||
#include <AK/TemporaryChange.h>
|
||||
#include <LibWeb/CSS/FontFace.h>
|
||||
#include <LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.h>
|
||||
#include <LibWeb/CSS/Parser/Parser.h>
|
||||
#include <LibWeb/CSS/PropertyName.h>
|
||||
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/BackgroundRepeatStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.h>
|
||||
|
@ -4273,342 +4273,56 @@ RefPtr<FontSourceStyleValue const> Parser::parse_font_source_value(TokenStream<C
|
|||
return FontSourceStyleValue::create(url.release_value(), move(format), move(tech));
|
||||
}
|
||||
|
||||
NonnullRefPtr<CSSStyleValue const> Parser::resolve_unresolved_style_value(ParsingParams const& context, DOM::Element& element, Optional<PseudoElement> pseudo_element, PropertyIDOrCustomPropertyName property, UnresolvedStyleValue const& unresolved)
|
||||
NonnullRefPtr<CSSStyleValue const> Parser::resolve_unresolved_style_value(ParsingParams const& context, DOM::Element& element, Optional<PseudoElement> pseudo_element, PropertyIDOrCustomPropertyName property, UnresolvedStyleValue const& unresolved, Optional<GuardedSubstitutionContexts&> existing_guarded_contexts)
|
||||
{
|
||||
// Unresolved always contains a var() or attr(), unless it is a custom property's value, in which case we shouldn't be trying
|
||||
// to produce a different CSSStyleValue from it.
|
||||
VERIFY(unresolved.contains_arbitrary_substitution_function());
|
||||
|
||||
DOM::AbstractElement abstract_element { element, pseudo_element };
|
||||
auto parser = Parser::create(context, ""sv);
|
||||
return parser.resolve_unresolved_style_value(element, pseudo_element, property, unresolved);
|
||||
if (existing_guarded_contexts.has_value())
|
||||
return parser.resolve_unresolved_style_value(abstract_element, existing_guarded_contexts.value(), property, unresolved);
|
||||
GuardedSubstitutionContexts guarded_contexts;
|
||||
return parser.resolve_unresolved_style_value(abstract_element, guarded_contexts, property, unresolved);
|
||||
}
|
||||
|
||||
class PropertyDependencyNode : public RefCounted<PropertyDependencyNode> {
|
||||
public:
|
||||
static NonnullRefPtr<PropertyDependencyNode> create(FlyString name)
|
||||
// https://drafts.csswg.org/css-values-5/#property-replacement
|
||||
NonnullRefPtr<CSSStyleValue const> Parser::resolve_unresolved_style_value(DOM::AbstractElement& element, GuardedSubstitutionContexts& guarded_contexts, PropertyIDOrCustomPropertyName property, UnresolvedStyleValue const& unresolved)
|
||||
{
|
||||
return adopt_ref(*new PropertyDependencyNode(move(name)));
|
||||
}
|
||||
|
||||
void add_child(NonnullRefPtr<PropertyDependencyNode> new_child)
|
||||
{
|
||||
for (auto const& child : m_children) {
|
||||
if (child->m_name == new_child->m_name)
|
||||
return;
|
||||
}
|
||||
|
||||
// We detect self-reference already.
|
||||
VERIFY(new_child->m_name != m_name);
|
||||
m_children.append(move(new_child));
|
||||
}
|
||||
|
||||
bool has_cycles()
|
||||
{
|
||||
if (m_marked)
|
||||
return true;
|
||||
|
||||
TemporaryChange change { m_marked, true };
|
||||
for (auto& child : m_children) {
|
||||
if (child->has_cycles())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit PropertyDependencyNode(FlyString name)
|
||||
: m_name(move(name))
|
||||
{
|
||||
}
|
||||
|
||||
FlyString m_name;
|
||||
Vector<NonnullRefPtr<PropertyDependencyNode>> m_children;
|
||||
bool m_marked { false };
|
||||
};
|
||||
|
||||
NonnullRefPtr<CSSStyleValue const> Parser::resolve_unresolved_style_value(DOM::Element& element, Optional<PseudoElement> pseudo_element, PropertyIDOrCustomPropertyName property, UnresolvedStyleValue const& unresolved)
|
||||
{
|
||||
TokenStream unresolved_values_without_variables_expanded { unresolved.values() };
|
||||
Vector<ComponentValue> values_with_variables_expanded;
|
||||
// AD-HOC: Report that we might rely on custom properties.
|
||||
// FIXME: This over-invalidates. Find a way of invalidating only when we need to - specifically, when var() is used.
|
||||
element.element().set_style_uses_css_custom_properties(true);
|
||||
|
||||
// To replace substitution functions in a property prop:
|
||||
auto const& property_name = property.visit(
|
||||
[](PropertyID const& property_id) { return string_from_property_id(property_id); },
|
||||
[](FlyString const& name) { return name; });
|
||||
auto const& property_id = property.visit(
|
||||
[](PropertyID const& property_id) { return property_id; },
|
||||
[](FlyString const&) { return PropertyID::Custom; });
|
||||
|
||||
HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>> dependencies;
|
||||
ScopeGuard mark_element_if_uses_custom_properties = [&] {
|
||||
for (auto const& name : dependencies.keys()) {
|
||||
if (is_a_custom_property_name_string(name)) {
|
||||
element.set_style_uses_css_custom_properties(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
if (!expand_variables(element, pseudo_element, property_name, dependencies, unresolved_values_without_variables_expanded, values_with_variables_expanded))
|
||||
// 1. Substitute arbitrary substitution functions in prop’s value, given «"property", prop’s name» as the
|
||||
// substitution context. Let result be the returned component value sequence.
|
||||
auto result = substitute_arbitrary_substitution_functions(element, guarded_contexts, unresolved.values(), SubstitutionContext { SubstitutionContext::DependencyType::Property, property_name.to_string() });
|
||||
|
||||
// 2. If result contains the guaranteed-invalid value, prop is invalid at computed-value time; return.
|
||||
if (contains_guaranteed_invalid_value(result))
|
||||
return GuaranteedInvalidStyleValue::create();
|
||||
|
||||
TokenStream unresolved_values_with_variables_expanded { values_with_variables_expanded };
|
||||
Vector<ComponentValue> expanded_values;
|
||||
if (!expand_unresolved_values(element, property_name, unresolved_values_with_variables_expanded, expanded_values))
|
||||
// 3. Parse result according to prop’s grammar. If this returns failure, prop is invalid at computed-value time; return.
|
||||
// NB: Custom properties have no grammar as such, so we skip this step for them.
|
||||
// FIXME: Parse according to @property syntax once we support that.
|
||||
if (property_id == PropertyID::Custom)
|
||||
return UnresolvedStyleValue::create(move(result), false, {});
|
||||
|
||||
auto expanded_value_tokens = TokenStream { result };
|
||||
auto parsed_value = parse_css_value(property_id, expanded_value_tokens);
|
||||
if (parsed_value.is_error())
|
||||
return GuaranteedInvalidStyleValue::create();
|
||||
|
||||
return property.visit(
|
||||
[&](PropertyID const& property_id) -> NonnullRefPtr<CSSStyleValue const> {
|
||||
auto expanded_value_tokens = TokenStream { expanded_values };
|
||||
if (auto parsed_value = parse_css_value(property_id, expanded_value_tokens); !parsed_value.is_error())
|
||||
// 4. Otherwise, replace prop’s value with the parsed result.
|
||||
return parsed_value.release_value();
|
||||
|
||||
return GuaranteedInvalidStyleValue::create();
|
||||
},
|
||||
[&](FlyString const&) -> NonnullRefPtr<CSSStyleValue const> {
|
||||
return UnresolvedStyleValue::create(move(expanded_values), false, {});
|
||||
});
|
||||
}
|
||||
|
||||
static RefPtr<CSSStyleValue const> get_custom_property(DOM::Element const& element, Optional<CSS::PseudoElement> pseudo_element, FlyString const& custom_property_name)
|
||||
{
|
||||
if (pseudo_element.has_value()) {
|
||||
if (auto it = element.custom_properties(pseudo_element).find(custom_property_name); it != element.custom_properties(pseudo_element).end())
|
||||
return it->value.value;
|
||||
}
|
||||
|
||||
for (auto const* current_element = &element; current_element; current_element = current_element->parent_or_shadow_host_element()) {
|
||||
if (auto it = current_element->custom_properties({}).find(custom_property_name); it != current_element->custom_properties({}).end())
|
||||
return it->value.value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Parser::expand_variables(DOM::Element& element, Optional<PseudoElement> pseudo_element, FlyString const& property_name, HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>>& dependencies, TokenStream<ComponentValue>& source, Vector<ComponentValue>& dest)
|
||||
{
|
||||
// Arbitrary large value chosen to avoid the billion-laughs attack.
|
||||
// https://www.w3.org/TR/css-variables-1/#long-variables
|
||||
size_t const MAX_VALUE_COUNT = 16384;
|
||||
if (source.remaining_token_count() + dest.size() > MAX_VALUE_COUNT) {
|
||||
dbgln("Stopped expanding CSS variables: maximum length reached.");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get_dependency_node = [&](FlyString const& name) -> NonnullRefPtr<PropertyDependencyNode> {
|
||||
if (auto existing = dependencies.get(name); existing.has_value())
|
||||
return *existing.value();
|
||||
auto new_node = PropertyDependencyNode::create(name);
|
||||
dependencies.set(name, new_node);
|
||||
return new_node;
|
||||
};
|
||||
|
||||
while (source.has_next_token()) {
|
||||
auto const& value = source.consume_a_token();
|
||||
if (value.is_block()) {
|
||||
auto const& source_block = value.block();
|
||||
Vector<ComponentValue> block_values;
|
||||
TokenStream source_block_contents { source_block.value };
|
||||
if (!expand_variables(element, pseudo_element, property_name, dependencies, source_block_contents, block_values))
|
||||
return false;
|
||||
dest.empend(SimpleBlock { source_block.token, move(block_values) });
|
||||
continue;
|
||||
}
|
||||
if (!value.is_function()) {
|
||||
dest.empend(value.token());
|
||||
continue;
|
||||
}
|
||||
if (!value.function().name.equals_ignoring_ascii_case("var"sv)) {
|
||||
auto const& source_function = value.function();
|
||||
Vector<ComponentValue> function_values;
|
||||
TokenStream source_function_contents { source_function.value };
|
||||
if (!expand_variables(element, pseudo_element, property_name, dependencies, source_function_contents, function_values))
|
||||
return false;
|
||||
dest.empend(Function { source_function.name, move(function_values) });
|
||||
continue;
|
||||
}
|
||||
|
||||
TokenStream var_contents { value.function().value };
|
||||
var_contents.discard_whitespace();
|
||||
if (!var_contents.has_next_token())
|
||||
return false;
|
||||
|
||||
auto const& custom_property_name_token = var_contents.consume_a_token();
|
||||
if (!custom_property_name_token.is(Token::Type::Ident))
|
||||
return false;
|
||||
auto custom_property_name = custom_property_name_token.token().ident();
|
||||
if (!custom_property_name.bytes_as_string_view().starts_with("--"sv))
|
||||
return false;
|
||||
|
||||
// Detect dependency cycles. https://www.w3.org/TR/css-variables-1/#cycles
|
||||
// We do not do this by the spec, since we are not keeping a graph of var dependencies around,
|
||||
// but rebuilding it every time.
|
||||
if (custom_property_name == property_name)
|
||||
return false;
|
||||
auto parent = get_dependency_node(property_name);
|
||||
auto child = get_dependency_node(custom_property_name);
|
||||
parent->add_child(child);
|
||||
if (parent->has_cycles())
|
||||
return false;
|
||||
|
||||
if (auto custom_property_value = get_custom_property(element, pseudo_element, custom_property_name);
|
||||
custom_property_value && custom_property_value->is_unresolved()) {
|
||||
// FIXME: We should properly cascade here instead of doing a basic fallback for CSS-wide keywords.
|
||||
TokenStream custom_property_tokens { custom_property_value->as_unresolved().values() };
|
||||
|
||||
auto dest_size_before = dest.size();
|
||||
if (!expand_variables(element, pseudo_element, custom_property_name, dependencies, custom_property_tokens, dest))
|
||||
return false;
|
||||
|
||||
// If the size of dest has increased, then the custom property is not the initial guaranteed-invalid value.
|
||||
// If it hasn't increased, then it is the initial guaranteed-invalid value, and thus we should move on to the fallback value.
|
||||
if (dest_size_before < dest.size())
|
||||
continue;
|
||||
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Expanding custom property '{}' did not return any tokens, treating it as invalid and moving on to the fallback value.", custom_property_name);
|
||||
}
|
||||
|
||||
// Use the provided fallback value, if any.
|
||||
var_contents.discard_whitespace();
|
||||
if (var_contents.has_next_token()) {
|
||||
auto const& comma_token = var_contents.consume_a_token();
|
||||
if (!comma_token.is(Token::Type::Comma))
|
||||
return false;
|
||||
var_contents.discard_whitespace();
|
||||
if (!expand_variables(element, pseudo_element, property_name, dependencies, var_contents, dest))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Parser::expand_unresolved_values(DOM::Element& element, FlyString const& property_name, TokenStream<ComponentValue>& source, Vector<ComponentValue>& dest)
|
||||
{
|
||||
while (source.has_next_token()) {
|
||||
auto const& value = source.consume_a_token();
|
||||
if (value.is_function()) {
|
||||
if (value.function().name.equals_ignoring_ascii_case("attr"sv)) {
|
||||
if (!substitute_attr_function(element, property_name, value.function(), dest))
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const& source_function = value.function();
|
||||
Vector<ComponentValue> function_values;
|
||||
TokenStream source_function_contents { source_function.value };
|
||||
if (!expand_unresolved_values(element, property_name, source_function_contents, function_values))
|
||||
return false;
|
||||
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;
|
||||
if (!expand_unresolved_values(element, property_name, source_block_values, block_values))
|
||||
return false;
|
||||
dest.empend(SimpleBlock { source_block.token, move(block_values) });
|
||||
continue;
|
||||
}
|
||||
dest.empend(value.token());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/css-values-5/#attr-substitution
|
||||
bool Parser::substitute_attr_function(DOM::Element& element, FlyString const& property_name, Function const& attr_function, Vector<ComponentValue>& dest)
|
||||
{
|
||||
// attr() = attr( <attr-name> <attr-type>? , <declaration-value>?)
|
||||
// <attr-name> = [ <ident-token>? '|' ]? <ident-token>
|
||||
// <attr-type> = type( <syntax> ) | raw-string | <attr-unit>
|
||||
// The <attr-unit> production matches any identifier that is an ASCII case-insensitive match for the name of a CSS dimension unit, such as px, or the <delim-token> %.
|
||||
TokenStream attr_contents { attr_function.value };
|
||||
attr_contents.discard_whitespace();
|
||||
if (!attr_contents.has_next_token())
|
||||
return false;
|
||||
|
||||
// - Attribute name
|
||||
// FIXME: Support optional attribute namespace
|
||||
if (!attr_contents.next_token().is(Token::Type::Ident))
|
||||
return false;
|
||||
auto attribute_name = attr_contents.consume_a_token().token().ident();
|
||||
attr_contents.discard_whitespace();
|
||||
|
||||
// - Attribute type (optional)
|
||||
auto attribute_type = "raw-string"_fly_string;
|
||||
if (attr_contents.next_token().is(Token::Type::Ident)) {
|
||||
attribute_type = attr_contents.consume_a_token().token().ident();
|
||||
attr_contents.discard_whitespace();
|
||||
}
|
||||
|
||||
// - Comma, then fallback values (optional)
|
||||
bool has_fallback_values = false;
|
||||
if (attr_contents.has_next_token()) {
|
||||
if (!attr_contents.next_token().is(Token::Type::Comma))
|
||||
return false;
|
||||
(void)attr_contents.consume_a_token(); // Comma
|
||||
has_fallback_values = true;
|
||||
}
|
||||
|
||||
// Then, run the substitution algorithm:
|
||||
|
||||
// 1. If the attr() function has a substitution value, replace the attr() function by the substitution value.
|
||||
// https://drafts.csswg.org/css-values-5/#attr-types
|
||||
if (element.has_attribute(attribute_name)) {
|
||||
auto parse_string_as_component_value = [this](String const& string) {
|
||||
auto tokens = Tokenizer::tokenize(string, "utf-8"sv);
|
||||
TokenStream stream { tokens };
|
||||
return parse_a_component_value(stream);
|
||||
};
|
||||
|
||||
auto attribute_value = element.get_attribute_value(attribute_name);
|
||||
if (attribute_type.equals_ignoring_ascii_case("raw-string"_fly_string)) {
|
||||
// The substitution value is a CSS string, whose value is the literal value of the attribute.
|
||||
// (No CSS parsing or "cleanup" of the value is performed.)
|
||||
// No value triggers fallback.
|
||||
dest.empend(Token::create_string(attribute_value));
|
||||
return true;
|
||||
} else {
|
||||
// Dimension units
|
||||
// Parse a component value from the attribute’s value.
|
||||
// If the result is a <number-token>, the substitution value is a dimension with the result’s value, and the given unit.
|
||||
// Otherwise, there is no substitution value.
|
||||
auto component_value = parse_string_as_component_value(attribute_value);
|
||||
if (component_value.has_value() && component_value->is(Token::Type::Number)) {
|
||||
if (attribute_value == "%"sv) {
|
||||
dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
|
||||
return true;
|
||||
} else if (auto angle_unit = Angle::unit_from_name(attribute_type); angle_unit.has_value()) {
|
||||
dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
|
||||
return true;
|
||||
} else if (auto flex_unit = Flex::unit_from_name(attribute_type); flex_unit.has_value()) {
|
||||
dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
|
||||
return true;
|
||||
} else if (auto frequency_unit = Frequency::unit_from_name(attribute_type); frequency_unit.has_value()) {
|
||||
dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
|
||||
return true;
|
||||
} else if (auto length_unit = Length::unit_from_name(attribute_type); length_unit.has_value()) {
|
||||
dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
|
||||
return true;
|
||||
} else if (auto time_unit = Time::unit_from_name(attribute_type); time_unit.has_value()) {
|
||||
dest.empend(Token::create_dimension(component_value->token().number_value(), attribute_type));
|
||||
return true;
|
||||
} else {
|
||||
// Not a dimension unit.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Otherwise, if the attr() function has a fallback value as its last argument, replace the attr() function by the fallback value.
|
||||
// If there are any var() or attr() references in the fallback, substitute them as well.
|
||||
if (has_fallback_values)
|
||||
return expand_unresolved_values(element, property_name, attr_contents, dest);
|
||||
|
||||
if (attribute_type.equals_ignoring_ascii_case("raw-string"_fly_string)) {
|
||||
// If the <attr-type> argument is string, defaults to the empty string if omitted
|
||||
dest.empend(Token::create_string({}));
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. Otherwise, the property containing the attr() function is invalid at computed-value time.
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#include <LibWeb/CSS/Fetch.h>
|
||||
#include <LibWeb/CSS/Interpolation.h>
|
||||
#include <LibWeb/CSS/InvalidationSet.h>
|
||||
#include <LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.h>
|
||||
#include <LibWeb/CSS/Parser/Parser.h>
|
||||
#include <LibWeb/CSS/SelectorEngine.h>
|
||||
#include <LibWeb/CSS/StyleComputer.h>
|
||||
|
@ -3095,7 +3096,7 @@ void StyleComputer::unload_fonts_from_sheet(CSSStyleSheet& sheet)
|
|||
}
|
||||
}
|
||||
|
||||
NonnullRefPtr<CSSStyleValue const> StyleComputer::compute_value_of_custom_property(DOM::AbstractElement abstract_element, FlyString const& name)
|
||||
NonnullRefPtr<CSSStyleValue const> StyleComputer::compute_value_of_custom_property(DOM::AbstractElement abstract_element, FlyString const& name, Optional<Parser::GuardedSubstitutionContexts&> guarded_contexts)
|
||||
{
|
||||
// https://drafts.csswg.org/css-variables/#propdef-
|
||||
// The computed value of a custom property is its specified value with any arbitrary-substitution functions replaced.
|
||||
|
@ -3129,7 +3130,7 @@ NonnullRefPtr<CSSStyleValue const> StyleComputer::compute_value_of_custom_proper
|
|||
return value.release_nonnull();
|
||||
|
||||
auto& unresolved = value->as_unresolved();
|
||||
return Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams {}, abstract_element.element(), abstract_element.pseudo_element(), name, unresolved);
|
||||
return Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams {}, abstract_element.element(), abstract_element.pseudo_element(), name, unresolved, guarded_contexts);
|
||||
}
|
||||
|
||||
void StyleComputer::compute_custom_properties(ComputedProperties&, DOM::AbstractElement abstract_element) const
|
||||
|
|
|
@ -193,7 +193,7 @@ public:
|
|||
|
||||
[[nodiscard]] inline bool should_reject_with_ancestor_filter(Selector const&) const;
|
||||
|
||||
static NonnullRefPtr<CSSStyleValue const> compute_value_of_custom_property(DOM::AbstractElement, FlyString const& custom_property);
|
||||
static NonnullRefPtr<CSSStyleValue const> compute_value_of_custom_property(DOM::AbstractElement, FlyString const& custom_property, Optional<Parser::GuardedSubstitutionContexts&> = {});
|
||||
|
||||
private:
|
||||
enum class ComputeStyleMode {
|
||||
|
|
|
@ -335,6 +335,7 @@ struct StyleSheetIdentifier;
|
|||
namespace Web::CSS::Parser {
|
||||
|
||||
class ComponentValue;
|
||||
class GuardedSubstitutionContexts;
|
||||
class Parser;
|
||||
class Token;
|
||||
class Tokenizer;
|
||||
|
|
|
@ -8,9 +8,7 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
|||
TextNode <#text>
|
||||
BlockContainer <(anonymous)> at (8,30) content-size 784x0 children: inline
|
||||
TextNode <#text>
|
||||
BlockContainer <div.string-no-fallback> at (9,31) content-size 100x20 children: inline
|
||||
InlineNode <(anonymous)>
|
||||
TextNode <#text>
|
||||
BlockContainer <div.string-no-fallback> at (9,31) content-size 100x20 children: not-inline
|
||||
BlockContainer <(anonymous)> at (8,52) content-size 784x0 children: inline
|
||||
TextNode <#text>
|
||||
BlockContainer <div.px> at (9,53) content-size 200x20 children: not-inline
|
||||
|
|
|
@ -2,19 +2,19 @@ Harness status: OK
|
|||
|
||||
Found 30 tests
|
||||
|
||||
4 Pass
|
||||
26 Fail
|
||||
9 Pass
|
||||
21 Fail
|
||||
Pass `initial` as a value for an unregistered custom property
|
||||
Fail `inherit` as a value for an unregistered custom property
|
||||
Fail `unset` as a value for an unregistered custom property
|
||||
Pass `inherit` as a value for an unregistered custom property
|
||||
Pass `unset` as a value for an unregistered custom property
|
||||
Fail `revert` as a value for an unregistered custom property
|
||||
Pass `revert-layer` as a value for an unregistered custom property
|
||||
Fail `initial` as a value for a non-inheriting registered custom property
|
||||
Fail `initial` as a value for an inheriting registered custom property
|
||||
Fail `inherit` as a value for a non-inheriting registered custom property
|
||||
Fail `inherit` as a value for an inheriting registered custom property
|
||||
Pass `inherit` as a value for a non-inheriting registered custom property
|
||||
Pass `inherit` as a value for an inheriting registered custom property
|
||||
Fail `unset` as a value for a non-inheriting registered custom property
|
||||
Fail `unset` as a value for an inheriting registered custom property
|
||||
Pass `unset` as a value for an inheriting registered custom property
|
||||
Fail `revert` as a value for a non-inheriting registered custom property
|
||||
Fail `revert` as a value for an inheriting registered custom property
|
||||
Pass `revert-layer` as a value for a non-inheriting registered custom property
|
||||
|
|
|
@ -2,16 +2,15 @@ Harness status: OK
|
|||
|
||||
Found 11 tests
|
||||
|
||||
4 Pass
|
||||
7 Fail
|
||||
11 Pass
|
||||
Pass Self-cycle
|
||||
Pass Simple a/b cycle
|
||||
Pass Three-var cycle
|
||||
Fail Cycle that starts in the middle of a chain
|
||||
Fail Cycle with extra edge
|
||||
Fail Cycle with extra edge (2)
|
||||
Fail Cycle with extra edge (3)
|
||||
Fail Cycle with secondary cycle
|
||||
Fail Cycle with overlapping secondary cycle
|
||||
Fail Cycle with deeper secondary cycle
|
||||
Pass Cycle that starts in the middle of a chain
|
||||
Pass Cycle with extra edge
|
||||
Pass Cycle with extra edge (2)
|
||||
Pass Cycle with extra edge (3)
|
||||
Pass Cycle with secondary cycle
|
||||
Pass Cycle with overlapping secondary cycle
|
||||
Pass Cycle with deeper secondary cycle
|
||||
Pass Cycle in unused fallback
|
Loading…
Add table
Add a link
Reference in a new issue