diff --git a/Libraries/LibWeb/Animations/KeyframeEffect.cpp b/Libraries/LibWeb/Animations/KeyframeEffect.cpp index c5dbcac6ede..8370ff47ac5 100644 --- a/Libraries/LibWeb/Animations/KeyframeEffect.cpp +++ b/Libraries/LibWeb/Animations/KeyframeEffect.cpp @@ -863,9 +863,10 @@ WebIDL::ExceptionOr KeyframeEffect::set_keyframes(Optionalis_unresolved() && target) property_value = CSS::Parser::Parser::resolve_unresolved_style_value(CSS::Parser::ParsingParams { target->document() }, *target, pseudo_element_type(), property_id, property_value->as_unresolved()); - CSS::StyleComputer::for_each_property_expanding_shorthands(property_id, property_value, CSS::StyleComputer::AllowUnresolved::Yes, [&](CSS::PropertyID shorthand_id, CSS::CSSStyleValue const& shorthand_value) { - m_target_properties.set(shorthand_id); - resolved_keyframe.properties.set(shorthand_id, NonnullRefPtr { shorthand_value }); + + CSS::StyleComputer::for_each_property_expanding_shorthands(property_id, property_value, CSS::StyleComputer::AllowUnresolved::Yes, [&](CSS::PropertyID longhand_id, CSS::CSSStyleValue const& longhand_value) { + m_target_properties.set(longhand_id); + resolved_keyframe.properties.set(longhand_id, NonnullRefPtr { longhand_value }); }); } diff --git a/Libraries/LibWeb/CSS/Interpolation.cpp b/Libraries/LibWeb/CSS/Interpolation.cpp index 1693eb473db..e3bc9f27977 100644 --- a/Libraries/LibWeb/CSS/Interpolation.cpp +++ b/Libraries/LibWeb/CSS/Interpolation.cpp @@ -45,14 +45,20 @@ static T interpolate_raw(T from, T to, float delta) static NonnullRefPtr with_keyword_values_resolved(DOM::Element& element, PropertyID property_id, CSSStyleValue const& value) { + if (value.is_guaranteed_invalid()) { + // At the moment, we're only dealing with "real" properties, so this behaves the same as `unset`. + // https://drafts.csswg.org/css-values-5/#invalid-at-computed-value-time + return property_initial_value(property_id); + } + if (!value.is_keyword()) return value; switch (value.as_keyword().keyword()) { - case CSS::Keyword::Initial: - case CSS::Keyword::Unset: + case Keyword::Initial: + case Keyword::Unset: return property_initial_value(property_id); - case CSS::Keyword::Inherit: - return CSS::StyleComputer::get_inherit_value(property_id, &element); + case Keyword::Inherit: + return StyleComputer::get_inherit_value(property_id, &element); default: break; } diff --git a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp index 075dc06ed6b..eb68681a3eb 100644 --- a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -3941,8 +3942,6 @@ NonnullRefPtr Parser::resolve_unresolved_style_value(Parsin // to produce a different CSSStyleValue from it. VERIFY(unresolved.contains_var_or_attr()); - // If the value is invalid, we fall back to `unset`: https://www.w3.org/TR/css-variables-1/#invalid-at-computed-value-time - auto parser = Parser::create(context, ""sv); return parser.resolve_unresolved_style_value(element, pseudo_element, property_id, unresolved); } @@ -4005,18 +4004,18 @@ NonnullRefPtr Parser::resolve_unresolved_style_value(DOM::E } }; if (!expand_variables(element, pseudo_element, string_from_property_id(property_id), dependencies, unresolved_values_without_variables_expanded, values_with_variables_expanded)) - return CSSKeywordValue::create(Keyword::Unset); + return GuaranteedInvalidStyleValue::create(); TokenStream unresolved_values_with_variables_expanded { values_with_variables_expanded }; Vector expanded_values; if (!expand_unresolved_values(element, string_from_property_id(property_id), unresolved_values_with_variables_expanded, expanded_values)) - return CSSKeywordValue::create(Keyword::Unset); + return GuaranteedInvalidStyleValue::create(); auto expanded_value_tokens = TokenStream { expanded_values }; if (auto parsed_value = parse_css_value(property_id, expanded_value_tokens); !parsed_value.is_error()) return parsed_value.release_value(); - return CSSKeywordValue::create(Keyword::Unset); + return GuaranteedInvalidStyleValue::create(); } static RefPtr get_custom_property(DOM::Element const& element, Optional pseudo_element, FlyString const& custom_property_name) diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index f6c0aceee81..09191d09823 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -16,11 +17,9 @@ #include #include #include -#include #include #include #include -#include #include #include #include @@ -60,6 +59,7 @@ #include #include #include +#include #include #include #include @@ -81,14 +81,12 @@ #include #include #include -#include #include #include #include #include #include #include -#include #include #include @@ -614,6 +612,21 @@ static void sort_matching_rules(Vector& matching_rules) void StyleComputer::for_each_property_expanding_shorthands(PropertyID property_id, CSSStyleValue const& value, AllowUnresolved allow_unresolved, Function const& set_longhand_property) { + if (property_is_shorthand(property_id) && (value.is_unresolved() || value.is_pending_substitution())) { + // If a shorthand property contains an arbitrary substitution function in its value, the longhand properties + // it’s associated with must instead be filled in with a special, unobservable-to-authors pending-substitution + // value that indicates the shorthand contains an arbitrary substitution function, and thus the longhand’s + // value can’t be determined until after substituted. + // https://drafts.csswg.org/css-values-5/#pending-substitution-value + // Ensure we keep the longhand around until it can be resolved. + set_longhand_property(property_id, value); + auto pending_substitution_value = PendingSubstitutionStyleValue::create(); + for (auto longhand_id : longhands_for_shorthand(property_id)) { + for_each_property_expanding_shorthands(longhand_id, pending_substitution_value, allow_unresolved, set_longhand_property); + } + return; + } + auto map_logical_property_to_real_property = [](PropertyID property_id) -> Optional { // FIXME: Honor writing-mode, direction and text-orientation. switch (property_id) { @@ -969,12 +982,12 @@ void StyleComputer::for_each_property_expanding_shorthands(PropertyID property_i } if (property_is_shorthand(property_id)) { - // ShorthandStyleValue was handled already. - // That means if we got here, that `value` must be a CSS-wide keyword, which we should apply to our longhand properties. + // ShorthandStyleValue was handled already, as were unresolved shorthands. + // That means the only values we should see are the CSS-wide keywords, or the guaranteed-invalid value. + // Both should be applied to our longhand properties. // We don't directly call `set_longhand_property()` because the longhands might have longhands of their own. // (eg `grid` -> `grid-template` -> `grid-template-areas` & `grid-template-rows` & `grid-template-columns`) - // Forget this requirement if we're ignoring unresolved values and the value is unresolved. - VERIFY(value.is_css_wide_keyword() || (allow_unresolved == AllowUnresolved::Yes && value.is_unresolved())); + VERIFY(value.is_css_wide_keyword() || value.is_guaranteed_invalid()); for (auto longhand : longhands_for_shorthand(property_id)) for_each_property_expanding_shorthands(longhand, value, allow_unresolved, set_longhand_property); return; @@ -1043,7 +1056,9 @@ void StyleComputer::cascade_declarations( Important important, Optional layer_name) const { + auto seen_properties = MUST(Bitmap::create(to_underlying(last_property_id) + 1, false)); auto cascade_style_declaration = [&](CSSStyleProperties const& declaration) { + seen_properties.fill(false); for (auto const& property : declaration.properties()) { if (important != property.important) continue; @@ -1051,16 +1066,52 @@ void StyleComputer::cascade_declarations( if (pseudo_element.has_value() && !pseudo_element_supports_property(*pseudo_element, property.property_id)) continue; - if (property.property_id == CSS::PropertyID::All) { - set_all_properties(cascaded_properties, element, pseudo_element, property.value, m_document, &declaration, cascade_origin, important, layer_name); + auto property_value = property.value; + + if (property_value->is_unresolved()) + property_value = Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { element.document() }, element, pseudo_element, property.property_id, property_value->as_unresolved()); + + if (property_value->is_guaranteed_invalid()) { + // https://drafts.csswg.org/css-values-5/#invalid-at-computed-value-time + // When substitution results in a property’s value containing the guaranteed-invalid value, this makes the + // declaration invalid at computed-value time. When this happens, the computed value is one of the + // following depending on the property’s type: + + // -> The property is a non-registered custom property + // -> The property is a registered custom property with universal syntax + // FIXME: Process custom properties here? + if (false) { + // The computed value is the guaranteed-invalid value. + } + // -> Otherwise + else { + // Either the property’s inherited value or its initial value depending on whether the property is + // inherited or not, respectively, as if the property’s value had been specified as the unset keyword. + property_value = CSSKeywordValue::create(Keyword::Unset); + } + } + + if (property.property_id == PropertyID::All) { + set_all_properties(cascaded_properties, element, pseudo_element, property_value, m_document, &declaration, cascade_origin, important, layer_name); continue; } - auto property_value = property.value; - if (property.value->is_unresolved()) - property_value = Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { document() }, element, pseudo_element, property.property_id, property.value->as_unresolved()); - if (!property_value->is_unresolved()) - set_property_expanding_shorthands(cascaded_properties, property.property_id, property_value, &declaration, cascade_origin, important, layer_name); + // NOTE: This is a duplicate of set_property_expanding_shorthands() with some extra checks. + for_each_property_expanding_shorthands(property.property_id, property_value, AllowUnresolved::No, [&](PropertyID longhand_id, CSSStyleValue const& longhand_value) { + // If we're a PSV that's already been seen, that should mean that our shorthand already got + // resolved and gave us a value, so we don't want to overwrite it with a PSV. + if (seen_properties.get(to_underlying(longhand_id)) && property_value->is_pending_substitution()) + return; + seen_properties.set(to_underlying(longhand_id), true); + + if (longhand_value.is_revert()) { + cascaded_properties.revert_property(longhand_id, important, cascade_origin); + } else if (longhand_value.is_revert_layer()) { + cascaded_properties.revert_layer_property(longhand_id, important, layer_name); + } else { + cascaded_properties.set_property(longhand_id, longhand_value, important, cascade_origin, layer_name, declaration); + } + }); } }; @@ -1155,27 +1206,46 @@ void StyleComputer::collect_animation_into(DOM::Element& element, Optionalid(), valid_properties, progress_in_keyframe * 100); } - for (auto const& it : keyframe_values.properties) { - auto resolve_property = [&](auto& property) { - return property.visit( + // FIXME: Follow https://drafts.csswg.org/web-animations-1/#ref-for-computed-keyframes in whatever the right place is. + auto compute_keyframe_values = [refresh, &computed_properties, &element, &pseudo_element](auto const& keyframe_values) { + HashMap> result; + for (auto const& [property_id, value] : keyframe_values.properties) { + auto style_value = value.visit( [&](Animations::KeyframeEffect::KeyFrameSet::UseInitial) -> RefPtr { if (refresh == AnimationRefresh::Yes) return {}; - return computed_properties.property(it.key); + if (property_is_shorthand(property_id)) + return {}; + return computed_properties.property(property_id); }, [&](RefPtr value) -> RefPtr { - if (value->is_revert() || value->is_revert_layer()) - return computed_properties.property(it.key); - if (value->is_unresolved()) - return Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { element.document() }, element, pseudo_element, it.key, value->as_unresolved()); return value; }); - }; - auto resolved_start_property = resolve_property(it.value); + if (!style_value) { + result.set(property_id, nullptr); + continue; + } - auto const& end_property = keyframe_end_values.properties.get(it.key); - if (!end_property.has_value()) { + if (style_value->is_revert() || style_value->is_revert_layer()) + style_value = computed_properties.property(property_id); + if (style_value->is_unresolved()) + style_value = Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { element.document() }, element, pseudo_element, property_id, style_value->as_unresolved()); + + for_each_property_expanding_shorthands(property_id, *style_value, AllowUnresolved::No, [&result](PropertyID id, CSSStyleValue const& longhand_value) { + result.set(id, { longhand_value }); + }); + } + return result; + }; + HashMap> computed_start_values = compute_keyframe_values(keyframe_values); + HashMap> computed_end_values = compute_keyframe_values(keyframe_end_values); + + for (auto const& it : computed_start_values) { + auto resolved_start_property = it.value; + RefPtr resolved_end_property = computed_end_values.get(it.key).value_or(nullptr); + + if (!resolved_end_property) { if (resolved_start_property) { computed_properties.set_animated_property(it.key, *resolved_start_property); dbgln_if(LIBWEB_CSS_ANIMATION_DEBUG, "No end property for property {}, using {}", string_from_property_id(it.key), resolved_start_property->to_string(CSSStyleValue::SerializationMode::Normal)); @@ -1183,8 +1253,6 @@ void StyleComputer::collect_animation_into(DOM::Element& element, Optional + +
+
+
+
+