diff --git a/Libraries/LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.cpp b/Libraries/LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.cpp index b2d81c3a868..5cbad6e30a7 100644 --- a/Libraries/LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.cpp +++ b/Libraries/LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace Web::CSS::Parser { @@ -64,6 +65,8 @@ Optional 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 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 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( *, ? ) + 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 indices; + // FIXME: Are non-literal 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 replace_a_var_function(DOM::AbstractElement& element, GuardedSubstitutionContexts& guarded_contexts, ArbitrarySubstitutionFunctionArguments const& arguments) { @@ -386,6 +447,11 @@ Optional parse_according_to_argument_gra // https://drafts.csswg.org/css-values-5/#attr-notation // = attr( , ? ) 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( , ? ) @@ -400,6 +466,8 @@ Vector 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); } diff --git a/Libraries/LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.h b/Libraries/LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.h index 9ec1ad856ba..9805764c9f9 100644 --- a/Libraries/LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.h +++ b/Libraries/LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.h @@ -38,6 +38,7 @@ private: enum class ArbitrarySubstitutionFunction : u8 { Attr, + Env, Var, }; [[nodiscard]] Optional to_arbitrary_substitution_function(FlyString const& name); diff --git a/Libraries/LibWeb/CSS/Parser/Types.cpp b/Libraries/LibWeb/CSS/Parser/Types.cpp index a4afb861ff1..0020bf120e6 100644 --- a/Libraries/LibWeb/CSS/Parser/Types.cpp +++ b/Libraries/LibWeb/CSS/Parser/Types.cpp @@ -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); diff --git a/Libraries/LibWeb/CSS/Parser/Types.h b/Libraries/LibWeb/CSS/Parser/Types.h index ccc45199a8f..ade9a7af5dc 100644 --- a/Libraries/LibWeb/CSS/Parser/Types.h +++ b/Libraries/LibWeb/CSS/Parser/Types.h @@ -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 diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-env/at-supports.tentative.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-env/at-supports.tentative.txt index 7326aca0e8f..9a0ff6870c8 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-env/at-supports.tentative.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-env/at-supports.tentative.txt @@ -2,5 +2,5 @@ Harness status: OK Found 1 tests -1 Fail -Fail Test that CSS env vars work with @support \ No newline at end of file +1 Pass +Pass Test that CSS env vars work with @support \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-env/env-in-custom-properties.tentative.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-env/env-in-custom-properties.tentative.txt index ad0ff933ede..9c0bb85a0fb 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-env/env-in-custom-properties.tentative.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-env/env-in-custom-properties.tentative.txt @@ -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 \ No newline at end of file +2 Pass +Pass env() is substituted into a custom property +Pass Substitution of unrecognized env() causes guaranteed-invalid \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-env/env-parsing.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-env/env-parsing.txt index f09b6907f3a..6c68490d83f 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-env/env-parsing.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-env/env-parsing.txt @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-env/fallback-nested-var.tentative.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-env/fallback-nested-var.tentative.txt index f5245cafaa5..2a23e2f370a 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-env/fallback-nested-var.tentative.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-env/fallback-nested-var.tentative.txt @@ -2,5 +2,5 @@ Harness status: OK Found 1 tests -1 Fail -Fail Test that nested var() fallback values work with CSS env vars \ No newline at end of file +1 Pass +Pass Test that nested var() fallback values work with CSS env vars \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-env/indexed-env.tentative.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-env/indexed-env.tentative.txt index f1021850174..069fa2bfda6 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-env/indexed-env.tentative.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-env/indexed-env.tentative.txt @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-env/syntax.tentative.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-env/syntax.tentative.txt index e5ddc1794c4..7c876d43eb1 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-env/syntax.tentative.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-env/syntax.tentative.txt @@ -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) \ No newline at end of file +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) \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-env/unknown-env-names-override-previous.tentative.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-env/unknown-env-names-override-previous.tentative.txt index ed8db5b5baf..de4f4862a33 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-env/unknown-env-names-override-previous.tentative.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-env/unknown-env-names-override-previous.tentative.txt @@ -2,5 +2,5 @@ Harness status: OK Found 1 tests -1 Fail -Fail Test unknown env() names will override previous values \ No newline at end of file +1 Pass +Pass Test unknown env() names will override previous values \ No newline at end of file