mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-28 13:18:19 +00:00
LibWeb/CSS: Implement env() as an arbitrary substitution function
Technically, env() should not be an ASF. (😱) This is why some tests
still fail - env() as specced is expected to have its syntax checked
fully at parse-time, whereas ASFs are not properly syntax-checked until
later. However, I think this approach was worth doing for a few reasons:
- env() behaves like an ASF otherwise. (It is replaced with a set of
arbitrary component-values that are not known until computed-value
time.)
- env() was defined before the ASF concept existed, so I strongly
suspect it will be updated in the future to match that definition,
with a couple of adjustments. (eg, env() is allowed in some extra
places compared to var() and attr().)
- This was much quicker and easier to implement (under 3 hours in total)
compared to the greater amount of work to implement a whole separate
system just for env().
- Most of these tests are marked tentative, and the spec definition of
env() is still somewhat in flux, so failing some is not a huge deal.
If in the future I turn out to be wrong on this, we can convert it to
its own special thing.
This commit is contained in:
parent
22e00451b9
commit
89b59cb5c3
Notes:
github-actions[bot]
2025-08-07 14:41:43 +00:00
Author: https://github.com/AtkinsSJ
Commit: 89b59cb5c3
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5736
Reviewed-by: https://github.com/kalenikaliaksandr
11 changed files with 122 additions and 50 deletions
|
@ -12,6 +12,7 @@
|
|||
#include <LibWeb/CSS/StyleComputer.h>
|
||||
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/UnresolvedStyleValue.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/Element.h>
|
||||
|
||||
namespace Web::CSS::Parser {
|
||||
|
@ -64,6 +65,8 @@ Optional<ArbitrarySubstitutionFunction> to_arbitrary_substitution_function(FlySt
|
|||
{
|
||||
if (name.equals_ignoring_ascii_case("attr"sv))
|
||||
return ArbitrarySubstitutionFunction::Attr;
|
||||
if (name.equals_ignoring_ascii_case("env"sv))
|
||||
return ArbitrarySubstitutionFunction::Env;
|
||||
if (name.equals_ignoring_ascii_case("var"sv))
|
||||
return ArbitrarySubstitutionFunction::Var;
|
||||
return {};
|
||||
|
@ -198,6 +201,64 @@ static Vector<ComponentValue> replace_an_attr_function(DOM::AbstractElement& ele
|
|||
// NB: Step 6 is a lambda defined at the top of the function.
|
||||
}
|
||||
|
||||
// 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 {} } };
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
|
@ -386,6 +447,11 @@ Optional<ArbitrarySubstitutionFunctionArguments> parse_according_to_argument_gra
|
|||
// 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::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);
|
||||
case ArbitrarySubstitutionFunction::Var:
|
||||
// https://drafts.csswg.org/css-variables/#funcdef-var
|
||||
// <var-args> = var( <declaration-value> , <declaration-value>? )
|
||||
|
@ -400,6 +466,8 @@ Vector<ComponentValue> replace_an_arbitrary_substitution_function(DOM::AbstractE
|
|||
switch (function) {
|
||||
case ArbitrarySubstitutionFunction::Attr:
|
||||
return replace_an_attr_function(element, guarded_contexts, arguments);
|
||||
case ArbitrarySubstitutionFunction::Env:
|
||||
return replace_an_env_function(element, guarded_contexts, arguments);
|
||||
case ArbitrarySubstitutionFunction::Var:
|
||||
return replace_a_var_function(element, guarded_contexts, arguments);
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ private:
|
|||
|
||||
enum class ArbitrarySubstitutionFunction : u8 {
|
||||
Attr,
|
||||
Env,
|
||||
Var,
|
||||
};
|
||||
[[nodiscard]] Optional<ArbitrarySubstitutionFunction> to_arbitrary_substitution_function(FlyString const& name);
|
||||
|
|
|
@ -87,10 +87,12 @@ String Function::original_source_text() const
|
|||
|
||||
void Function::contains_arbitrary_substitution_function(SubstitutionFunctionsPresence& presence) const
|
||||
{
|
||||
if (name.equals_ignoring_ascii_case("var"sv))
|
||||
presence.var = true;
|
||||
else if (name.equals_ignoring_ascii_case("attr"sv))
|
||||
if (name.equals_ignoring_ascii_case("attr"sv))
|
||||
presence.attr = true;
|
||||
else if (name.equals_ignoring_ascii_case("env"sv))
|
||||
presence.env = true;
|
||||
else if (name.equals_ignoring_ascii_case("var"sv))
|
||||
presence.var = true;
|
||||
for (auto const& component_value : value) {
|
||||
if (component_value.is_function())
|
||||
component_value.function().contains_arbitrary_substitution_function(presence);
|
||||
|
|
|
@ -63,9 +63,10 @@ struct Declaration {
|
|||
|
||||
struct SubstitutionFunctionsPresence {
|
||||
bool attr { false };
|
||||
bool env { false };
|
||||
bool var { false };
|
||||
|
||||
bool has_any() const { return attr || var; }
|
||||
bool has_any() const { return attr || env || var; }
|
||||
};
|
||||
|
||||
// https://drafts.csswg.org/css-syntax/#simple-block
|
||||
|
|
|
@ -2,5 +2,5 @@ Harness status: OK
|
|||
|
||||
Found 1 tests
|
||||
|
||||
1 Fail
|
||||
Fail Test that CSS env vars work with @support
|
||||
1 Pass
|
||||
Pass Test that CSS env vars work with @support
|
|
@ -2,6 +2,6 @@ Harness status: OK
|
|||
|
||||
Found 2 tests
|
||||
|
||||
2 Fail
|
||||
Fail env() is substituted into a custom property
|
||||
Fail Substitution of unrecognized env() causes guaranteed-invalid
|
||||
2 Pass
|
||||
Pass env() is substituted into a custom property
|
||||
Pass Substitution of unrecognized env() causes guaranteed-invalid
|
|
@ -2,13 +2,13 @@ Harness status: OK
|
|||
|
||||
Found 8 tests
|
||||
|
||||
5 Pass
|
||||
3 Fail
|
||||
Fail e.style['width'] = "env(safe-area-inset-top)" should set the property value
|
||||
Fail e.style['width'] = "env(safe-area-inset-top,)" should set the property value
|
||||
Fail e.style['width'] = "env(safe-area-inset-top, )" should set the property value
|
||||
Pass e.style['width'] = "env(safe-area-inset-top ())" should not set the property value
|
||||
Pass e.style['width'] = "env(safe-area-inset-top () )" should not set the property value
|
||||
Pass e.style['width'] = "env(safe-area-inset-top() )" should not set the property value
|
||||
Pass e.style['width'] = "env(safe-area-inset-top (),)" should not set the property value
|
||||
Pass e.style['width'] = "env(safe-area-inset-top(),)" should not set the property value
|
||||
3 Pass
|
||||
5 Fail
|
||||
Pass e.style['width'] = "env(safe-area-inset-top)" should set the property value
|
||||
Pass e.style['width'] = "env(safe-area-inset-top,)" should set the property value
|
||||
Pass e.style['width'] = "env(safe-area-inset-top, )" should set the property value
|
||||
Fail e.style['width'] = "env(safe-area-inset-top ())" should not set the property value
|
||||
Fail e.style['width'] = "env(safe-area-inset-top () )" should not set the property value
|
||||
Fail e.style['width'] = "env(safe-area-inset-top() )" should not set the property value
|
||||
Fail e.style['width'] = "env(safe-area-inset-top (),)" should not set the property value
|
||||
Fail e.style['width'] = "env(safe-area-inset-top(),)" should not set the property value
|
|
@ -2,5 +2,5 @@ Harness status: OK
|
|||
|
||||
Found 1 tests
|
||||
|
||||
1 Fail
|
||||
Fail Test that nested var() fallback values work with CSS env vars
|
||||
1 Pass
|
||||
Pass Test that nested var() fallback values work with CSS env vars
|
|
@ -4,11 +4,11 @@ Found 8 tests
|
|||
|
||||
4 Pass
|
||||
4 Fail
|
||||
Pass CSS Environment variable value "env(test1 test2, green)" must not successfully parse
|
||||
Pass CSS Environment variable value "env(test1 10 20 test2, green)" must not successfully parse
|
||||
Pass CSS Environment variable value "env(test 0.1, green)" must not successfully parse
|
||||
Pass CSS Environment variable value "env(test -1, green)" must not successfully parse
|
||||
Fail CSS Environment variable value "env(test 0, green)" must successfully parse and roundtrip
|
||||
Fail CSS Environment variable value "env(test 0,)" must successfully parse and roundtrip
|
||||
Fail CSS Environment variable value "env(test 0)" must successfully parse and roundtrip
|
||||
Fail CSS Environment variable value "env(test 0 1 2 3 4, green)" must successfully parse and roundtrip
|
||||
Fail CSS Environment variable value "env(test1 test2, green)" must not successfully parse
|
||||
Fail CSS Environment variable value "env(test1 10 20 test2, green)" must not successfully parse
|
||||
Fail CSS Environment variable value "env(test 0.1, green)" must not successfully parse
|
||||
Fail CSS Environment variable value "env(test -1, green)" must not successfully parse
|
||||
Pass CSS Environment variable value "env(test 0, green)" must successfully parse and roundtrip
|
||||
Pass CSS Environment variable value "env(test 0,)" must successfully parse and roundtrip
|
||||
Pass CSS Environment variable value "env(test 0)" must successfully parse and roundtrip
|
||||
Pass CSS Environment variable value "env(test 0 1 2 3 4, green)" must successfully parse and roundtrip
|
|
@ -2,23 +2,23 @@ Harness status: OK
|
|||
|
||||
Found 18 tests
|
||||
|
||||
4 Pass
|
||||
14 Fail
|
||||
15 Pass
|
||||
3 Fail
|
||||
Pass rgb(0, 128, 0)
|
||||
Fail background-color: env(test) rgba(0, 0, 0, 0)
|
||||
Fail background-color: ENV(test) rgba(0, 0, 0, 0)
|
||||
Fail background-color: env(test) !important rgba(0, 0, 0, 0)
|
||||
Fail background-color: env(test, 10px) rgba(0, 0, 0, 0)
|
||||
Fail background-color: env(test, blue) rgb(0, 0, 255)
|
||||
Fail background-color: env(test, env(another)) rgba(0, 0, 0, 0)
|
||||
Fail background-color: env(test, env(another, blue)) rgb(0, 0, 255)
|
||||
Fail background-color: env(-test) rgba(0, 0, 0, 0)
|
||||
Fail background-color: env(--test) rgba(0, 0, 0, 0)
|
||||
Pass background-color: env(10px) rgb(0, 128, 0)
|
||||
Pass background-color: env(env(test)) rgb(0, 128, 0)
|
||||
Fail background-color: env( test) rgba(0, 0, 0, 0)
|
||||
Fail background-color: env(test ) rgba(0, 0, 0, 0)
|
||||
Fail background-color: env( test ) rgba(0, 0, 0, 0)
|
||||
Fail background-color: env(test /**/, blue) rgb(0, 0, 255)
|
||||
Fail background-color: env(test, {}) rgba(0, 0, 0, 0)
|
||||
Pass background-color: env(test, {) rgb(0, 128, 0)
|
||||
Pass background-color: env(test) rgba(0, 0, 0, 0)
|
||||
Pass background-color: ENV(test) rgba(0, 0, 0, 0)
|
||||
Pass background-color: env(test) !important rgba(0, 0, 0, 0)
|
||||
Pass background-color: env(test, 10px) rgba(0, 0, 0, 0)
|
||||
Pass background-color: env(test, blue) rgb(0, 0, 255)
|
||||
Pass background-color: env(test, env(another)) rgba(0, 0, 0, 0)
|
||||
Pass background-color: env(test, env(another, blue)) rgb(0, 0, 255)
|
||||
Pass background-color: env(-test) rgba(0, 0, 0, 0)
|
||||
Pass background-color: env(--test) rgba(0, 0, 0, 0)
|
||||
Fail background-color: env(10px) rgb(0, 128, 0)
|
||||
Fail background-color: env(env(test)) rgb(0, 128, 0)
|
||||
Pass background-color: env( test) rgba(0, 0, 0, 0)
|
||||
Pass background-color: env(test ) rgba(0, 0, 0, 0)
|
||||
Pass background-color: env( test ) rgba(0, 0, 0, 0)
|
||||
Pass background-color: env(test /**/, blue) rgb(0, 0, 255)
|
||||
Pass background-color: env(test, {}) rgba(0, 0, 0, 0)
|
||||
Fail background-color: env(test, {) rgb(0, 128, 0)
|
|
@ -2,5 +2,5 @@ Harness status: OK
|
|||
|
||||
Found 1 tests
|
||||
|
||||
1 Fail
|
||||
Fail Test unknown env() names will override previous values
|
||||
1 Pass
|
||||
Pass Test unknown env() names will override previous values
|
Loading…
Add table
Add a link
Reference in a new issue